From ee1c3d42d884167d1657027ca9dad17704df7231 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Tue, 20 Aug 2019 21:11:26 +0300 Subject: [PATCH 0001/1782] Add spinner tick judgement --- .../Judgements/OsuSpinnerTickJudgement.cs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 osu.Game.Rulesets.Osu/Judgements/OsuSpinnerTickJudgement.cs diff --git a/osu.Game.Rulesets.Osu/Judgements/OsuSpinnerTickJudgement.cs b/osu.Game.Rulesets.Osu/Judgements/OsuSpinnerTickJudgement.cs new file mode 100644 index 0000000000..f9cac7a2c1 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Judgements/OsuSpinnerTickJudgement.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.Rulesets.Scoring; + +namespace osu.Game.Rulesets.Osu.Judgements +{ + public class OsuSpinnerTickJudgement : OsuJudgement + { + internal bool HasBonusPoints; + + public override bool AffectsCombo => false; + + protected override int NumericResultFor(HitResult result) => 100 + (HasBonusPoints ? 1000 : 0); + + protected override double HealthIncreaseFor(HitResult result) => 0; + } +} From bb4178fa037a2b9a4d361b7a89715958d773db3e Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Tue, 20 Aug 2019 21:17:27 +0300 Subject: [PATCH 0002/1782] Add drawable spinner ticks implementation --- .../Objects/Drawables/DrawableSpinnerTick.cs | 49 +++++++++++++++++++ osu.Game.Rulesets.Osu/Objects/Spinner.cs | 11 +++++ osu.Game.Rulesets.Osu/Objects/SpinnerTick.cs | 19 +++++++ .../Replays/OsuAutoGeneratorBase.cs | 2 +- 4 files changed, 80 insertions(+), 1 deletion(-) create mode 100644 osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinnerTick.cs create mode 100644 osu.Game.Rulesets.Osu/Objects/SpinnerTick.cs diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinnerTick.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinnerTick.cs new file mode 100644 index 0000000000..9c316591a9 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinnerTick.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.Audio; +using osu.Framework.Bindables; +using osu.Game.Rulesets.Osu.Judgements; +using osu.Game.Rulesets.Scoring; + +namespace osu.Game.Rulesets.Osu.Objects.Drawables +{ + public class DrawableSpinnerTick : DrawableOsuHitObject + { + private readonly BindableDouble bonusSampleVolume = new BindableDouble(); + + private bool hasBonusPoints; + + /// + /// Whether this judgement has a bonus of 1,000 points additional to the numeric result. + /// Should be set when a spin occured after the spinner has completed. + /// + public bool HasBonusPoints + { + get => hasBonusPoints; + internal set + { + hasBonusPoints = value; + + bonusSampleVolume.Value = value ? 1 : 0; + ((OsuSpinnerTickJudgement)Result.Judgement).HasBonusPoints = value; + } + } + + public override bool DisplayResult => false; + + public DrawableSpinnerTick(SpinnerTick spinnerTick) + : base(spinnerTick) + { + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + Samples.AddAdjustment(AdjustableProperty.Volume, bonusSampleVolume); + } + + public void TriggerResult(HitResult result) => ApplyResult(r => r.Type = result); + } +} diff --git a/osu.Game.Rulesets.Osu/Objects/Spinner.cs b/osu.Game.Rulesets.Osu/Objects/Spinner.cs index 8a2fd3b7aa..c32ec7be1c 100644 --- a/osu.Game.Rulesets.Osu/Objects/Spinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Spinner.cs @@ -7,6 +7,8 @@ using osu.Game.Rulesets.Objects.Types; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Osu.Judgements; +using osu.Game.Rulesets.Osu.Replays; +using osuTK; namespace osu.Game.Rulesets.Osu.Objects { @@ -30,6 +32,15 @@ namespace osu.Game.Rulesets.Osu.Objects SpinsRequired = (int)Math.Max(1, SpinsRequired * 0.6); } + protected override void CreateNestedHitObjects() + { + base.CreateNestedHitObjects(); + + var maximumSpins = OsuAutoGeneratorBase.SPIN_RADIUS * (Duration / 1000) / MathHelper.TwoPi; + for (int i = 0; i < maximumSpins; i++) + AddNested(new SpinnerTick()); + } + public override Judgement CreateJudgement() => new OsuJudgement(); } } diff --git a/osu.Game.Rulesets.Osu/Objects/SpinnerTick.cs b/osu.Game.Rulesets.Osu/Objects/SpinnerTick.cs new file mode 100644 index 0000000000..18a3dc771b --- /dev/null +++ b/osu.Game.Rulesets.Osu/Objects/SpinnerTick.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.Audio; +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Osu.Judgements; + +namespace osu.Game.Rulesets.Osu.Objects +{ + public class SpinnerTick : OsuHitObject + { + public SpinnerTick() + { + Samples.Add(new HitSampleInfo { Name = "spinnerbonus" }); + } + + public override Judgement CreateJudgement() => new OsuSpinnerTickJudgement(); + } +} diff --git a/osu.Game.Rulesets.Osu/Replays/OsuAutoGeneratorBase.cs b/osu.Game.Rulesets.Osu/Replays/OsuAutoGeneratorBase.cs index 9ab358ee12..3356a0fbe0 100644 --- a/osu.Game.Rulesets.Osu/Replays/OsuAutoGeneratorBase.cs +++ b/osu.Game.Rulesets.Osu/Replays/OsuAutoGeneratorBase.cs @@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Osu.Replays /// protected static readonly Vector2 SPINNER_CENTRE = OsuPlayfield.BASE_SIZE / 2; - protected const float SPIN_RADIUS = 50; + public const float SPIN_RADIUS = 50; /// /// The time in ms between each ReplayFrame. From 07795c9922cc4b3ce5197010b03fc53e0b1f565b Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Tue, 20 Aug 2019 21:50:49 +0300 Subject: [PATCH 0003/1782] Add logic to gain bonus score from spinner ticks --- .../Objects/Drawables/DrawableSpinner.cs | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index a0bd301fdb..d166d6b845 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -12,7 +12,9 @@ using osu.Game.Graphics; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics.Sprites; +using osu.Game.Graphics.Sprites; using osu.Game.Screens.Ranking; using osu.Game.Rulesets.Scoring; @@ -22,6 +24,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { protected readonly Spinner Spinner; + private readonly Container ticks; + private readonly OsuSpriteText bonusCounter; + public readonly SpinnerDisc Disc; public readonly SpinnerTicks Ticks; private readonly SpinnerSpmCounter spmCounter; @@ -58,6 +63,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables InternalChildren = new Drawable[] { + ticks = new Container(), circleContainer = new Container { AutoSizeAxes = Axes.Both, @@ -115,8 +121,24 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables Origin = Anchor.Centre, Y = 120, Alpha = 0 + }, + bonusCounter = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Y = -120, + Font = OsuFont.Numeric.With(size: 24), + Alpha = 0, } }; + + foreach (var tick in Spinner.NestedHitObjects.OfType()) + { + var drawableTick = new DrawableSpinnerTick(tick); + + ticks.Add(drawableTick); + AddNested(drawableTick); + } } [BackgroundDependencyLoader] @@ -182,6 +204,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables base.Update(); } + private int currentSpins; + protected override void UpdateAfterChildren() { base.UpdateAfterChildren(); @@ -190,6 +214,22 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables Ticks.Rotation = Disc.Rotation; spmCounter.SetRotation(Disc.RotationAbsolute); + var newSpins = (int)(Disc.RotationAbsolute / 360) - currentSpins; + + for (int i = currentSpins; i < currentSpins + newSpins; i++) + { + if (i < 0 || i >= ticks.Count) + break; + + var tick = ticks[i]; + + tick.HasBonusPoints = Progress >= 1 && i > Spinner.SpinsRequired; + + tick.TriggerResult(HitResult.Great); + } + + currentSpins += newSpins; + float relativeCircleScale = Spinner.Scale * circle.DrawHeight / mainContainer.DrawHeight; Disc.ScaleTo(relativeCircleScale + (1 - relativeCircleScale) * Progress, 200, Easing.OutQuint); @@ -232,6 +272,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables break; } + if (state != ArmedState.Idle) + Schedule(() => NestedHitObjects.Where(t => !t.IsHit).OfType().ForEach(t => t.TriggerResult(HitResult.Miss))); + Expire(); } } From e4179fe4403232aa5663c80c7ee21800a20bd204 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Tue, 20 Aug 2019 21:51:32 +0300 Subject: [PATCH 0004/1782] Show bonus text if contains bonus points (1,000) --- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index d166d6b845..b97f4e0a57 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -225,6 +225,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables tick.HasBonusPoints = Progress >= 1 && i > Spinner.SpinsRequired; + if (tick.HasBonusPoints) + bonusCounter + .TransformTextTo($"{(i - Spinner.SpinsRequired) * 1000}") + .FadeOutFromOne(1500) + .ScaleTo(1.5f).ScaleTo(1f, 1000, Easing.OutQuint); + tick.TriggerResult(HitResult.Great); } From dbf4884cbc64c736b16d334a2ed29e3f7780ce5b Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Tue, 20 Aug 2019 21:52:13 +0300 Subject: [PATCH 0005/1782] Adjust test spinner rotation --- osu.Game.Rulesets.Osu.Tests/TestSceneSpinner.cs | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinner.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinner.cs index 3ed3f3e981..6e0745d125 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinner.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinner.cs @@ -64,7 +64,7 @@ namespace osu.Game.Rulesets.Osu.Tests private class TestDrawableSpinner : DrawableSpinner { - private bool auto; + private readonly bool auto; public TestDrawableSpinner(Spinner s, bool auto) : base(s) @@ -74,12 +74,8 @@ namespace osu.Game.Rulesets.Osu.Tests protected override void CheckForResult(bool userTriggered, double timeOffset) { - 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; - auto = false; - } + if (auto && !userTriggered && Time.Current > Spinner.StartTime) + Disc.RotationAbsolute += Progress >= 1 ? 10 : (float)(Spinner.Duration / 120); base.CheckForResult(userTriggered, timeOffset); } From 6b7cb46ddaf9518e9f876535a86f385ed0db1a26 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Sat, 7 Sep 2019 17:27:02 +0300 Subject: [PATCH 0006/1782] Add null hit windows --- osu.Game.Rulesets.Osu/Objects/SpinnerTick.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Objects/SpinnerTick.cs b/osu.Game.Rulesets.Osu/Objects/SpinnerTick.cs index 18a3dc771b..c2104e68ee 100644 --- a/osu.Game.Rulesets.Osu/Objects/SpinnerTick.cs +++ b/osu.Game.Rulesets.Osu/Objects/SpinnerTick.cs @@ -4,6 +4,7 @@ using osu.Game.Audio; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Osu.Judgements; +using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Osu.Objects { @@ -15,5 +16,7 @@ namespace osu.Game.Rulesets.Osu.Objects } public override Judgement CreateJudgement() => new OsuSpinnerTickJudgement(); + + protected override HitWindows CreateHitWindows() => null; } } From 33f4a6897cd315ba7e3790378a586a9adf424b1d Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Sat, 7 Sep 2019 18:01:15 +0300 Subject: [PATCH 0007/1782] Assign to the text property directly --- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index fc1e410d5f..62cec0f124 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -226,10 +226,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables tick.HasBonusPoints = Progress >= 1 && i > Spinner.SpinsRequired; if (tick.HasBonusPoints) + { + bonusCounter.Text = $"{(i - Spinner.SpinsRequired) * 1000}"; bonusCounter - .TransformTextTo($"{(i - Spinner.SpinsRequired) * 1000}") .FadeOutFromOne(1500) .ScaleTo(1.5f).ScaleTo(1f, 1000, Easing.OutQuint); + } tick.TriggerResult(HitResult.Great); } From 812d33f850c1f83351f8a6c17376e11f178f7c22 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Tue, 1 Oct 2019 08:09:01 +0300 Subject: [PATCH 0008/1782] Add ExpandNumberPiece configuration with OsuLegacySkinTransformer --- osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs | 4 ++++ osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs | 1 + 2 files changed, 5 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs index 479c250eab..c9ed313593 100644 --- a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs @@ -123,6 +123,10 @@ namespace osu.Game.Rulesets.Osu.Skinning return SkinUtils.As(new BindableFloat(legacy_circle_radius)); break; + + case OsuSkinConfiguration.ExpandNumberPiece: + string legacyVersion = source.GetConfig("Version")?.Value ?? "1"; + return SkinUtils.As(new BindableBool(double.TryParse(legacyVersion, out double version) && version < 2.0)); } break; diff --git a/osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs b/osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs index 98219cafe8..85a7f5b0cd 100644 --- a/osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs +++ b/osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs @@ -7,6 +7,7 @@ namespace osu.Game.Rulesets.Osu.Skinning { HitCirclePrefix, HitCircleOverlap, + ExpandNumberPiece, SliderBorderSize, SliderPathRadius, AllowSliderBallTint, From 6ba1bc381c07d68d7af35d3e631dbe6cac7b9b93 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Tue, 1 Oct 2019 08:14:15 +0300 Subject: [PATCH 0009/1782] Add version value to the DefaultSkinConfiguration dictionary --- osu.Game/Skinning/DefaultSkinConfiguration.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Skinning/DefaultSkinConfiguration.cs b/osu.Game/Skinning/DefaultSkinConfiguration.cs index f52fac6077..481360beb9 100644 --- a/osu.Game/Skinning/DefaultSkinConfiguration.cs +++ b/osu.Game/Skinning/DefaultSkinConfiguration.cs @@ -19,6 +19,8 @@ namespace osu.Game.Skinning new Color4(204, 102, 0, 255), new Color4(121, 9, 13, 255) }); + + ConfigDictionary.Add(@"Version", "latest"); } } } From 9e314cd664991c1836d0cb258405b6652e5f4a03 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Tue, 1 Oct 2019 08:15:48 +0300 Subject: [PATCH 0010/1782] Add expand number piece bindable to hit circle --- .../Objects/Drawables/DrawableHitCircle.cs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs index bb227d76df..15c768fff9 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs @@ -10,9 +10,10 @@ 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 osu.Game.Rulesets.Osu.Skinning; using osu.Game.Rulesets.Scoring; -using osuTK; using osu.Game.Skinning; +using osuTK; namespace osu.Game.Rulesets.Osu.Objects.Drawables { @@ -20,6 +21,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { public ApproachCircle ApproachCircle { get; } + public IBindable ExpandNumberPiece => expandNumberPiece; + + private readonly BindableBool expandNumberPiece = new BindableBool(); + private readonly IBindable positionBindable = new Bindable(); private readonly IBindable stackHeightBindable = new Bindable(); private readonly IBindable scaleBindable = new Bindable(); @@ -106,6 +111,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables } } + protected override void ApplySkin(ISkinSource skin, bool allowFallback) + { + base.ApplySkin(skin, allowFallback); + + expandNumberPiece.Value = skin.GetConfig(OsuSkinConfiguration.ExpandNumberPiece).Value; + } + protected override void CheckForResult(bool userTriggered, double timeOffset) { Debug.Assert(HitObject.HitWindows != null); From 5aa85968c232927d42a35344437a5f3376fe3321 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Tue, 1 Oct 2019 08:23:41 +0300 Subject: [PATCH 0011/1782] Expand number piece for old skins in legacy circle pieces --- .../Skinning/LegacyMainCirclePiece.cs | 40 ++++++++++++++----- 1 file changed, 31 insertions(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs b/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs index 93ae0371df..d93d0506c3 100644 --- a/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs +++ b/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs @@ -10,9 +10,12 @@ using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Skinning; using osuTK; using osuTK.Graphics; +using System.Collections.Generic; +using System.Linq; namespace osu.Game.Rulesets.Osu.Skinning { @@ -23,17 +26,24 @@ namespace osu.Game.Rulesets.Osu.Skinning Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2); } + private Sprite hitCircleSprite; + private SkinnableSpriteText hitCircleText; + + private List scalables; + private readonly IBindable state = new Bindable(); private readonly Bindable accentColour = new Bindable(); private readonly IBindable indexInCurrentCombo = new Bindable(); + private readonly IBindable expandNumberPiece = new BindableBool(); + + [Resolved] + private DrawableHitObject drawableObject { get; set; } [BackgroundDependencyLoader] - private void load(DrawableHitObject drawableObject, ISkinSource skin) + private void load(ISkinSource skin) { OsuHitObject osuObject = (OsuHitObject)drawableObject.HitObject; - - Sprite hitCircleSprite; - SkinnableSpriteText hitCircleText; + DrawableHitCircle drawableCircle = (DrawableHitCircle)drawableObject; InternalChildren = new Drawable[] { @@ -58,13 +68,25 @@ namespace osu.Game.Rulesets.Osu.Skinning }; state.BindTo(drawableObject.State); - state.BindValueChanged(updateState, true); - accentColour.BindTo(drawableObject.AccentColour); - accentColour.BindValueChanged(colour => hitCircleSprite.Colour = colour.NewValue, true); - indexInCurrentCombo.BindTo(osuObject.IndexInCurrentComboBindable); + expandNumberPiece.BindTo(drawableCircle.ExpandNumberPiece); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + state.BindValueChanged(updateState, true); + accentColour.BindValueChanged(colour => hitCircleSprite.Colour = colour.NewValue, true); indexInCurrentCombo.BindValueChanged(index => hitCircleText.Text = (index.NewValue + 1).ToString(), true); + expandNumberPiece.BindValueChanged(expand => + { + scalables = InternalChildren.ToList(); + + if (!expand.NewValue) + scalables.Remove(hitCircleText); + }, true); } private void updateState(ValueChangedEvent state) @@ -75,7 +97,7 @@ namespace osu.Game.Rulesets.Osu.Skinning { case ArmedState.Hit: this.FadeOut(legacy_fade_duration, Easing.Out); - this.ScaleTo(1.4f, legacy_fade_duration, Easing.Out); + scalables.ForEach(d => d.ScaleTo(1.4f, legacy_fade_duration, Easing.Out)); break; } } From ef8f9aa276f9359b1c1f81df3644f52917b7dbfb Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Tue, 1 Oct 2019 08:43:03 +0300 Subject: [PATCH 0012/1782] Fix possible nullref exception --- 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 15c768fff9..bd4cb1f112 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs @@ -115,7 +115,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { base.ApplySkin(skin, allowFallback); - expandNumberPiece.Value = skin.GetConfig(OsuSkinConfiguration.ExpandNumberPiece).Value; + expandNumberPiece.Value = skin.GetConfig(OsuSkinConfiguration.ExpandNumberPiece)?.Value ?? false; } protected override void CheckForResult(bool userTriggered, double timeOffset) From 957bbee3e4eb4569310eacf11c59f7e167795efe Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Thu, 3 Oct 2019 05:58:20 +0300 Subject: [PATCH 0013/1782] Scale pieces individually and use skin source directly --- .../Objects/Drawables/DrawableHitCircle.cs | 11 ------- .../Skinning/LegacyMainCirclePiece.cs | 30 ++++++++----------- 2 files changed, 13 insertions(+), 28 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs index bd4cb1f112..c37535521d 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs @@ -21,10 +21,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { public ApproachCircle ApproachCircle { get; } - public IBindable ExpandNumberPiece => expandNumberPiece; - - private readonly BindableBool expandNumberPiece = new BindableBool(); - private readonly IBindable positionBindable = new Bindable(); private readonly IBindable stackHeightBindable = new Bindable(); private readonly IBindable scaleBindable = new Bindable(); @@ -111,13 +107,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables } } - protected override void ApplySkin(ISkinSource skin, bool allowFallback) - { - base.ApplySkin(skin, allowFallback); - - expandNumberPiece.Value = skin.GetConfig(OsuSkinConfiguration.ExpandNumberPiece)?.Value ?? false; - } - protected override void CheckForResult(bool userTriggered, double timeOffset) { Debug.Assert(HitObject.HitWindows != null); diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs b/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs index d93d0506c3..8ba21d9f89 100644 --- a/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs +++ b/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs @@ -14,8 +14,6 @@ using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Skinning; using osuTK; using osuTK.Graphics; -using System.Collections.Generic; -using System.Linq; namespace osu.Game.Rulesets.Osu.Skinning { @@ -26,21 +24,21 @@ namespace osu.Game.Rulesets.Osu.Skinning Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2); } - private Sprite hitCircleSprite; + private Sprite hitCircleSprite, hitCircleOverlay; private SkinnableSpriteText hitCircleText; - private List scalables; - private readonly IBindable state = new Bindable(); private readonly Bindable accentColour = new Bindable(); private readonly IBindable indexInCurrentCombo = new Bindable(); - private readonly IBindable expandNumberPiece = new BindableBool(); [Resolved] private DrawableHitObject drawableObject { get; set; } + [Resolved] + private ISkinSource skin { get; set; } + [BackgroundDependencyLoader] - private void load(ISkinSource skin) + private void load() { OsuHitObject osuObject = (OsuHitObject)drawableObject.HitObject; DrawableHitCircle drawableCircle = (DrawableHitCircle)drawableObject; @@ -59,7 +57,7 @@ namespace osu.Game.Rulesets.Osu.Skinning Font = OsuFont.Numeric.With(size: 40), UseFullGlyphHeight = false, }, confineMode: ConfineMode.NoScaling), - new Sprite + hitCircleOverlay = new Sprite { Texture = skin.GetTexture("hitcircleoverlay"), Anchor = Anchor.Centre, @@ -70,7 +68,6 @@ namespace osu.Game.Rulesets.Osu.Skinning state.BindTo(drawableObject.State); accentColour.BindTo(drawableObject.AccentColour); indexInCurrentCombo.BindTo(osuObject.IndexInCurrentComboBindable); - expandNumberPiece.BindTo(drawableCircle.ExpandNumberPiece); } protected override void LoadComplete() @@ -80,13 +77,6 @@ namespace osu.Game.Rulesets.Osu.Skinning state.BindValueChanged(updateState, true); accentColour.BindValueChanged(colour => hitCircleSprite.Colour = colour.NewValue, true); indexInCurrentCombo.BindValueChanged(index => hitCircleText.Text = (index.NewValue + 1).ToString(), true); - expandNumberPiece.BindValueChanged(expand => - { - scalables = InternalChildren.ToList(); - - if (!expand.NewValue) - scalables.Remove(hitCircleText); - }, true); } private void updateState(ValueChangedEvent state) @@ -97,7 +87,13 @@ namespace osu.Game.Rulesets.Osu.Skinning { case ArmedState.Hit: this.FadeOut(legacy_fade_duration, Easing.Out); - scalables.ForEach(d => d.ScaleTo(1.4f, legacy_fade_duration, Easing.Out)); + + hitCircleSprite.ScaleTo(1.4f, legacy_fade_duration, Easing.Out); + hitCircleOverlay.ScaleTo(1.4f, legacy_fade_duration, Easing.Out); + + if (skin.GetConfig(OsuSkinConfiguration.ExpandNumberPiece).Value) + hitCircleText.ScaleTo(1.4f, legacy_fade_duration, Easing.Out); + break; } } From 023c4d64d811e1abc49655ba37b766568117333f Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Thu, 3 Oct 2019 06:00:22 +0300 Subject: [PATCH 0014/1782] Remove redundant using directive --- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs index c37535521d..4579e5cb59 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.Framework.Graphics.Containers; using osu.Framework.Input.Bindings; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces; -using osu.Game.Rulesets.Osu.Skinning; using osu.Game.Rulesets.Scoring; using osu.Game.Skinning; using osuTK; From a73f6c6a5a3dfc12518a85442732a4c2469d0535 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Thu, 3 Oct 2019 06:46:13 +0300 Subject: [PATCH 0015/1782] Specify version of osu!classic skin --- 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..8370b5e8ee 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.ConfigDictionary["Version"] = "2"; } public static SkinInfo Info { get; } = new SkinInfo From 89075c5655618c70b2d05f4943a11dca18350b89 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Thu, 3 Oct 2019 06:48:07 +0300 Subject: [PATCH 0016/1782] Set version of not-configured skins to latest only --- osu.Game/Skinning/DefaultSkinConfiguration.cs | 9 +++++++-- osu.Game/Skinning/LegacySkin.cs | 2 +- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/osu.Game/Skinning/DefaultSkinConfiguration.cs b/osu.Game/Skinning/DefaultSkinConfiguration.cs index 481360beb9..1d22ce8c1d 100644 --- a/osu.Game/Skinning/DefaultSkinConfiguration.cs +++ b/osu.Game/Skinning/DefaultSkinConfiguration.cs @@ -10,7 +10,7 @@ namespace osu.Game.Skinning /// public class DefaultSkinConfiguration : SkinConfiguration { - public DefaultSkinConfiguration() + public DefaultSkinConfiguration(string version) { ComboColours.AddRange(new[] { @@ -20,7 +20,12 @@ namespace osu.Game.Skinning new Color4(121, 9, 13, 255) }); - ConfigDictionary.Add(@"Version", "latest"); + ConfigDictionary["Version"] = version; + } + + public DefaultSkinConfiguration() + : this("1") + { } } } diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 0b1076be01..0f80aade1e 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -38,7 +38,7 @@ namespace osu.Game.Skinning using (StreamReader reader = new StreamReader(stream)) Configuration = new LegacySkinDecoder().Decode(reader); else - Configuration = new DefaultSkinConfiguration(); + Configuration = new DefaultSkinConfiguration("latest"); if (storage != null) { From 3fe56117005f375a8dcbea223cd48a0365d0af85 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Thu, 3 Oct 2019 06:48:59 +0300 Subject: [PATCH 0017/1782] Retrieve numeric version value from legacy configuration --- .../Skinning/OsuLegacySkinTransformer.cs | 3 +-- osu.Game/Skinning/LegacySkin.cs | 13 +++++++++++++ osu.Game/Skinning/LegacySkinConfiguration.cs | 10 ++++++++++ 3 files changed, 24 insertions(+), 2 deletions(-) create mode 100644 osu.Game/Skinning/LegacySkinConfiguration.cs diff --git a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs index c9ed313593..539b3d7d34 100644 --- a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs @@ -125,8 +125,7 @@ namespace osu.Game.Rulesets.Osu.Skinning break; case OsuSkinConfiguration.ExpandNumberPiece: - string legacyVersion = source.GetConfig("Version")?.Value ?? "1"; - return SkinUtils.As(new BindableBool(double.TryParse(legacyVersion, out double version) && version < 2.0)); + return SkinUtils.As(new BindableBool(source.GetConfig(LegacySkinConfiguration.LegacyVersion).Value < 2.0)); } break; diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 0f80aade1e..90265ec066 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -70,6 +70,19 @@ namespace osu.Game.Skinning case GlobalSkinColour colour: return SkinUtils.As(getCustomColour(colour.ToString())); + case LegacySkinConfiguration legacy: + switch (legacy) + { + case LegacySkinConfiguration.LegacyVersion: + var versionString = GetConfig("Version").Value; + if (!double.TryParse(versionString, out double version)) + version = versionString == "latest" ? 2.7 : 1; + + return SkinUtils.As(new BindableDouble(version)); + } + + 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..2e75313d0c --- /dev/null +++ b/osu.Game/Skinning/LegacySkinConfiguration.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 LegacySkinConfiguration + { + LegacyVersion, + } +} From dabc22403009b8d976ce4971c309ca4fea709cea Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Thu, 3 Oct 2019 06:49:32 +0300 Subject: [PATCH 0018/1782] Fix hit circle positioning --- osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs b/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs index 8ba21d9f89..705828131d 100644 --- a/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs +++ b/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs @@ -56,7 +56,11 @@ namespace osu.Game.Rulesets.Osu.Skinning { Font = OsuFont.Numeric.With(size: 40), UseFullGlyphHeight = false, - }, confineMode: ConfineMode.NoScaling), + }, confineMode: ConfineMode.NoScaling) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, hitCircleOverlay = new Sprite { Texture = skin.GetTexture("hitcircleoverlay"), From 2d7acef0800903e3d6f52ce98de724576f107bbf Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Thu, 3 Oct 2019 11:06:38 +0300 Subject: [PATCH 0019/1782] Fix CI issues --- osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs | 4 +--- osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs | 3 ++- osu.Game/Skinning/LegacySkin.cs | 4 ++-- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs b/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs index 705828131d..89c8ea9d6c 100644 --- a/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs +++ b/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs @@ -10,7 +10,6 @@ using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects; -using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Skinning; using osuTK; using osuTK.Graphics; @@ -41,7 +40,6 @@ namespace osu.Game.Rulesets.Osu.Skinning private void load() { OsuHitObject osuObject = (OsuHitObject)drawableObject.HitObject; - DrawableHitCircle drawableCircle = (DrawableHitCircle)drawableObject; InternalChildren = new Drawable[] { @@ -95,7 +93,7 @@ namespace osu.Game.Rulesets.Osu.Skinning hitCircleSprite.ScaleTo(1.4f, legacy_fade_duration, Easing.Out); hitCircleOverlay.ScaleTo(1.4f, legacy_fade_duration, Easing.Out); - if (skin.GetConfig(OsuSkinConfiguration.ExpandNumberPiece).Value) + if (skin.GetConfig(OsuSkinConfiguration.ExpandNumberPiece)?.Value ?? true) hitCircleText.ScaleTo(1.4f, legacy_fade_duration, Easing.Out); break; diff --git a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs index 539b3d7d34..1303e2cace 100644 --- a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs @@ -125,7 +125,8 @@ namespace osu.Game.Rulesets.Osu.Skinning break; case OsuSkinConfiguration.ExpandNumberPiece: - return SkinUtils.As(new BindableBool(source.GetConfig(LegacySkinConfiguration.LegacyVersion).Value < 2.0)); + double legacyVersion = source.GetConfig(LegacySkinConfiguration.LegacyVersion)?.Value ?? 1.0; + return SkinUtils.As(new BindableBool(legacyVersion < 2.0)); } break; diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 90265ec066..af5309eecd 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -74,9 +74,9 @@ namespace osu.Game.Skinning switch (legacy) { case LegacySkinConfiguration.LegacyVersion: - var versionString = GetConfig("Version").Value; + var versionString = GetConfig("Version")?.Value ?? "1"; if (!double.TryParse(versionString, out double version)) - version = versionString == "latest" ? 2.7 : 1; + version = versionString == "latest" ? 2.7 : 1.0; return SkinUtils.As(new BindableDouble(version)); } From 5d2fe8733997295bbbecee0cdbc947440e305d06 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Mon, 14 Oct 2019 00:38:45 +0300 Subject: [PATCH 0020/1782] Use empty hit windows for spinner ticks --- osu.Game.Rulesets.Osu/Objects/SpinnerTick.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Objects/SpinnerTick.cs b/osu.Game.Rulesets.Osu/Objects/SpinnerTick.cs index c2104e68ee..318e8e71a2 100644 --- a/osu.Game.Rulesets.Osu/Objects/SpinnerTick.cs +++ b/osu.Game.Rulesets.Osu/Objects/SpinnerTick.cs @@ -17,6 +17,6 @@ namespace osu.Game.Rulesets.Osu.Objects public override Judgement CreateJudgement() => new OsuSpinnerTickJudgement(); - protected override HitWindows CreateHitWindows() => null; + protected override HitWindows CreateHitWindows() => HitWindows.Empty; } } From 68e370ce7cd72c51a7eda6f9863ed37b0f86b3d5 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Mon, 14 Oct 2019 00:39:20 +0300 Subject: [PATCH 0021/1782] Set spinner tick start time to allow result reverting --- .../Objects/Drawables/DrawableSpinnerTick.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinnerTick.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinnerTick.cs index 9c316591a9..21cf7b3acb 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinnerTick.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinnerTick.cs @@ -44,6 +44,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables Samples.AddAdjustment(AdjustableProperty.Volume, bonusSampleVolume); } - public void TriggerResult(HitResult result) => ApplyResult(r => r.Type = result); + public void TriggerResult(HitResult result) + { + HitObject.StartTime = Time.Current; + ApplyResult(r => r.Type = result); + } } } From a75ae14cb20efca1673d863001736361c29c07f8 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Mon, 14 Oct 2019 00:40:36 +0300 Subject: [PATCH 0022/1782] Use foreach loop to avoid too long lines --- .../Objects/Drawables/DrawableSpinner.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index 08e64b7ecf..965303ba7a 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -12,7 +12,6 @@ using osu.Game.Graphics; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Allocation; using osu.Framework.Bindables; -using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics.Sprites; using osu.Game.Graphics.Sprites; using osu.Game.Rulesets.Scoring; @@ -279,7 +278,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables } if (state != ArmedState.Idle) - Schedule(() => NestedHitObjects.Where(t => !t.IsHit).OfType().ForEach(t => t.TriggerResult(HitResult.Miss))); + { + Schedule(() => + { + foreach (var tick in ticks.Where(t => !t.IsHit)) + tick.TriggerResult(HitResult.Miss); + }); + } } } } From a8514ecd0f220f39c214e13cd89409f1a6694c3e Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Mon, 14 Oct 2019 00:43:46 +0300 Subject: [PATCH 0023/1782] Add tests ensuring correct spinner ticks score results --- .../TestSceneSpinnerRotation.cs | 45 ++++++++++++++++--- 1 file changed, 40 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs index cded7f0e95..b03788a7d6 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs @@ -15,6 +15,8 @@ using osu.Game.Tests.Visual; using osuTK; using System.Collections.Generic; using System.Linq; +using osu.Game.Rulesets.Scoring; +using osu.Game.Screens.Play; using static osu.Game.Tests.Visual.OsuTestScene.ClockBackedTestWorkingBeatmap; namespace osu.Game.Rulesets.Osu.Tests @@ -28,6 +30,8 @@ namespace osu.Game.Rulesets.Osu.Tests protected override bool Autoplay => true; + protected override Player CreatePlayer(Ruleset ruleset) => new ScoreExposedPlayer(); + protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap) { var working = new ClockBackedTestWorkingBeatmap(beatmap, new FramedClock(new ManualClock { Rate = 1 }), audioManager); @@ -69,6 +73,32 @@ namespace osu.Game.Rulesets.Osu.Tests AddAssert("is rotation absolute almost same", () => Precision.AlmostEquals(drawableSpinner.Disc.RotationAbsolute, estimatedRotation, 100)); } + [Test] + public void TestSpinnerNormalBonusRewinding() + { + addSeekStep(1000); + + AddAssert("player score matching expected bonus score", () => + { + // multipled by 2 to nullify the score multiplier. (autoplay mod selected) + var totalScore = ((ScoreExposedPlayer)Player).ScoreProcessor.TotalScore.Value * 2; + return totalScore == (int)(drawableSpinner.Disc.RotationAbsolute / 360) * 100; + }); + + addSeekStep(0); + + AddAssert("player score is 0", () => ((ScoreExposedPlayer)Player).ScoreProcessor.TotalScore.Value == 0); + } + + [Test] + public void TestSpinnerCompleteBonusRewinding() + { + addSeekStep(2500); + addSeekStep(0); + + AddAssert("player score is 0", () => ((ScoreExposedPlayer)Player).ScoreProcessor.TotalScore.Value == 0); + } + private void addSeekStep(double time) { AddStep($"seek to {time}", () => track.Seek(time)); @@ -85,12 +115,17 @@ namespace osu.Game.Rulesets.Osu.Tests Position = new Vector2(256, 192), EndTime = 5000, }, - // placeholder object to avoid hitting the results screen - new HitObject - { - StartTime = 99999, - } } }; + + private class ScoreExposedPlayer : TestPlayer + { + public new ScoreProcessor ScoreProcessor => base.ScoreProcessor; + + public ScoreExposedPlayer() + : base(false, false) + { + } + } } } From f54eb448fa245c2fde8e5b23b3f526f25560122a Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Tue, 15 Oct 2019 22:00:34 +0300 Subject: [PATCH 0024/1782] Revert skin legacy version changes --- osu.Game/Skinning/DefaultLegacySkin.cs | 2 -- osu.Game/Skinning/DefaultSkinConfiguration.cs | 9 +-------- osu.Game/Skinning/LegacySkin.cs | 15 +-------------- osu.Game/Skinning/LegacySkinConfiguration.cs | 10 ---------- 4 files changed, 2 insertions(+), 34 deletions(-) delete mode 100644 osu.Game/Skinning/LegacySkinConfiguration.cs diff --git a/osu.Game/Skinning/DefaultLegacySkin.cs b/osu.Game/Skinning/DefaultLegacySkin.cs index 8370b5e8ee..4b6eea6b6e 100644 --- a/osu.Game/Skinning/DefaultLegacySkin.cs +++ b/osu.Game/Skinning/DefaultLegacySkin.cs @@ -20,8 +20,6 @@ namespace osu.Game.Skinning new Color4(18, 124, 255, 255), new Color4(242, 24, 57, 255), }); - - Configuration.ConfigDictionary["Version"] = "2"; } public static SkinInfo Info { get; } = new SkinInfo diff --git a/osu.Game/Skinning/DefaultSkinConfiguration.cs b/osu.Game/Skinning/DefaultSkinConfiguration.cs index 8c87e1d054..cd5975edac 100644 --- a/osu.Game/Skinning/DefaultSkinConfiguration.cs +++ b/osu.Game/Skinning/DefaultSkinConfiguration.cs @@ -10,7 +10,7 @@ namespace osu.Game.Skinning /// public class DefaultSkinConfiguration : SkinConfiguration { - public DefaultSkinConfiguration(string version) + public DefaultSkinConfiguration() { ComboColours.AddRange(new[] { @@ -19,13 +19,6 @@ namespace osu.Game.Skinning new Color4(18, 124, 255, 255), new Color4(242, 24, 57, 255), }); - - ConfigDictionary["Version"] = version; - } - - public DefaultSkinConfiguration() - : this("1") - { } } } diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index cbebdd9bd6..fea15458e4 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -39,7 +39,7 @@ namespace osu.Game.Skinning using (LineBufferedReader reader = new LineBufferedReader(stream)) Configuration = new LegacySkinDecoder().Decode(reader); else - Configuration = new DefaultSkinConfiguration("latest"); + Configuration = new DefaultSkinConfiguration(); if (storage != null) { @@ -71,19 +71,6 @@ namespace osu.Game.Skinning case GlobalSkinColour colour: return SkinUtils.As(getCustomColour(colour.ToString())); - case LegacySkinConfiguration legacy: - switch (legacy) - { - case LegacySkinConfiguration.LegacyVersion: - var versionString = GetConfig("Version")?.Value ?? "1"; - if (!double.TryParse(versionString, out double version)) - version = versionString == "latest" ? 2.7 : 1.0; - - return SkinUtils.As(new BindableDouble(version)); - } - - 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 deleted file mode 100644 index 2e75313d0c..0000000000 --- a/osu.Game/Skinning/LegacySkinConfiguration.cs +++ /dev/null @@ -1,10 +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.Skinning -{ - public enum LegacySkinConfiguration - { - LegacyVersion, - } -} From 9dcbef49d300458c897f3f40c77c9dddaf3bb5b4 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Tue, 15 Oct 2019 22:28:50 +0300 Subject: [PATCH 0025/1782] Resolve DHO inside load() --- osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs b/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs index 89c8ea9d6c..f9e6400b18 100644 --- a/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs +++ b/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs @@ -30,14 +30,11 @@ namespace osu.Game.Rulesets.Osu.Skinning private readonly Bindable accentColour = new Bindable(); private readonly IBindable indexInCurrentCombo = new Bindable(); - [Resolved] - private DrawableHitObject drawableObject { get; set; } - [Resolved] private ISkinSource skin { get; set; } [BackgroundDependencyLoader] - private void load() + private void load(DrawableHitObject drawableObject) { OsuHitObject osuObject = (OsuHitObject)drawableObject.HitObject; From 10e1e512fd45abf199bea01c8d70ebfa2337df4c Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 12 Dec 2019 15:15:16 +0300 Subject: [PATCH 0026/1782] Update the nested hitobject logic inline with new implementation --- .../Objects/Drawables/DrawableSpinner.cs | 32 ++++++++++++++++--- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index 39330f08c3..2c21b4244a 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -15,6 +15,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics.Sprites; using osu.Game.Graphics.Sprites; +using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Ranking; @@ -131,16 +132,37 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables Alpha = 0, } }; + } - foreach (var tick in Spinner.NestedHitObjects.OfType()) + protected override void AddNestedHitObject(DrawableHitObject hitObject) + { + base.AddNestedHitObject(hitObject); + + switch (hitObject) { - var drawableTick = new DrawableSpinnerTick(tick); - - ticks.Add(drawableTick); - AddNestedHitObject(drawableTick); + case DrawableSpinnerTick tick: + ticks.Add(tick); + break; } } + protected override void ClearNestedHitObjects() + { + base.ClearNestedHitObjects(); + ticks.Clear(); + } + + protected override DrawableHitObject CreateNestedHitObject(HitObject hitObject) + { + switch (hitObject) + { + case SpinnerTick tick: + return new DrawableSpinnerTick(tick); + } + + return base.CreateNestedHitObject(hitObject); + } + [BackgroundDependencyLoader] private void load(OsuColour colours) { From d6fb2283385c9cec966d01dcf684cb402ab1e7e1 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 12 Dec 2019 16:02:53 +0300 Subject: [PATCH 0027/1782] Update version retrieval logic in-line with new implementation --- osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs index 1303e2cace..1c716b3ee9 100644 --- a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs @@ -9,6 +9,7 @@ using osu.Framework.Graphics.Textures; using osu.Game.Audio; using osu.Game.Skinning; using osuTK; +using static osu.Game.Skinning.LegacySkinConfiguration; namespace osu.Game.Rulesets.Osu.Skinning { @@ -125,8 +126,8 @@ namespace osu.Game.Rulesets.Osu.Skinning break; case OsuSkinConfiguration.ExpandNumberPiece: - double legacyVersion = source.GetConfig(LegacySkinConfiguration.LegacyVersion)?.Value ?? 1.0; - return SkinUtils.As(new BindableBool(legacyVersion < 2.0)); + decimal legacyVersion = source.GetConfig(LegacySetting.Version)?.Value ?? 1.0m; + return SkinUtils.As(new BindableBool(legacyVersion < 2.0m)); } break; From 41ca084fa591253d6406253c76cef99cc1196305 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 17 Dec 2019 22:00:21 +0300 Subject: [PATCH 0028/1782] Simplify expand number check --- osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs index 1c716b3ee9..5926332ea5 100644 --- a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs @@ -126,8 +126,8 @@ namespace osu.Game.Rulesets.Osu.Skinning break; case OsuSkinConfiguration.ExpandNumberPiece: - decimal legacyVersion = source.GetConfig(LegacySetting.Version)?.Value ?? 1.0m; - return SkinUtils.As(new BindableBool(legacyVersion < 2.0m)); + bool expand = source.GetConfig(LegacySetting.Version)?.Value < 2.0m; + return SkinUtils.As(new BindableBool(expand)); } break; From 121ce2c3df5b95f48fec3546caee2e0d257b1d8b Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 19 Dec 2019 14:44:52 +0300 Subject: [PATCH 0029/1782] Fix checking for expand incorrectly --- osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs index 5926332ea5..3e41dc08d7 100644 --- a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs @@ -126,7 +126,7 @@ namespace osu.Game.Rulesets.Osu.Skinning break; case OsuSkinConfiguration.ExpandNumberPiece: - bool expand = source.GetConfig(LegacySetting.Version)?.Value < 2.0m; + bool expand = !(source.GetConfig(LegacySetting.Version)?.Value >= 2.0m); return SkinUtils.As(new BindableBool(expand)); } From 949ab4e0d3889e4ea88850b49715c1e3f8cc46d2 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 25 Dec 2019 05:34:12 +0300 Subject: [PATCH 0030/1782] Move spinner bonus scoring to it's own component class Also fixes counter rewinding issue and does optimizations. --- .../Objects/Drawables/DrawableSpinner.cs | 42 +-------- .../Objects/Drawables/DrawableSpinnerTick.cs | 10 ++- .../Drawables/Pieces/SpinnerBonusComponent.cs | 90 +++++++++++++++++++ 3 files changed, 100 insertions(+), 42 deletions(-) create mode 100644 osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerBonusComponent.cs diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index f7f4275d2a..86e8840425 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -26,11 +26,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables protected readonly Spinner Spinner; private readonly Container ticks; - private readonly OsuSpriteText bonusCounter; public readonly SpinnerDisc Disc; public readonly SpinnerTicks Ticks; public readonly SpinnerSpmCounter SpmCounter; + private readonly SpinnerBonusComponent bonusComponent; private readonly Container mainContainer; @@ -123,13 +123,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables Y = 120, Alpha = 0 }, - bonusCounter = new OsuSpriteText + bonusComponent = new SpinnerBonusComponent(this, ticks) { Anchor = Anchor.Centre, Origin = Anchor.Centre, Y = -120, - Font = OsuFont.Numeric.With(size: 24), - Alpha = 0, } }; } @@ -226,8 +224,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables base.Update(); } - private int currentSpins; - protected override void UpdateAfterChildren() { base.UpdateAfterChildren(); @@ -235,30 +231,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables circle.Rotation = Disc.Rotation; Ticks.Rotation = Disc.Rotation; SpmCounter.SetRotation(Disc.RotationAbsolute); - - var newSpins = (int)(Disc.RotationAbsolute / 360) - currentSpins; - - for (int i = currentSpins; i < currentSpins + newSpins; i++) - { - if (i < 0 || i >= ticks.Count) - break; - - var tick = ticks[i]; - - tick.HasBonusPoints = Progress >= 1 && i > Spinner.SpinsRequired; - - if (tick.HasBonusPoints) - { - bonusCounter.Text = $"{(i - Spinner.SpinsRequired) * 1000}"; - bonusCounter - .FadeOutFromOne(1500) - .ScaleTo(1.5f).ScaleTo(1f, 1000, Easing.OutQuint); - } - - tick.TriggerResult(HitResult.Great); - } - - currentSpins += newSpins; + bonusComponent.UpdateRotation(Disc.RotationAbsolute); float relativeCircleScale = Spinner.Scale * circle.DrawHeight / mainContainer.DrawHeight; Disc.ScaleTo(relativeCircleScale + (1 - relativeCircleScale) * Progress, 200, Easing.OutQuint); @@ -299,15 +272,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables sequence.ScaleTo(Scale * 0.8f, 320, Easing.In); break; } - - if (state != ArmedState.Idle) - { - Schedule(() => - { - foreach (var tick in ticks.Where(t => !t.IsHit)) - tick.TriggerResult(HitResult.Miss); - }); - } } } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinnerTick.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinnerTick.cs index 21cf7b3acb..6512a9526e 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinnerTick.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinnerTick.cs @@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables /// /// Whether this judgement has a bonus of 1,000 points additional to the numeric result. - /// Should be set when a spin occured after the spinner has completed. + /// Set when a spin occured after the spinner has completed. /// public bool HasBonusPoints { @@ -44,10 +44,14 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables Samples.AddAdjustment(AdjustableProperty.Volume, bonusSampleVolume); } - public void TriggerResult(HitResult result) + /// + /// Apply a judgement result. + /// + /// Whether to apply a result, otherwise. + internal void TriggerResult(bool hit) { HitObject.StartTime = Time.Current; - ApplyResult(r => r.Type = result); + ApplyResult(r => r.Type = hit ? HitResult.Great : HitResult.Miss); } } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerBonusComponent.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerBonusComponent.cs new file mode 100644 index 0000000000..5c96751b3a --- /dev/null +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerBonusComponent.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.Linq; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Objects.Drawables; + +namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces +{ + /// + /// A component that tracks spinner spins and add bonus score for it. + /// + public class SpinnerBonusComponent : CompositeDrawable + { + private readonly DrawableSpinner drawableSpinner; + private readonly Container ticks; + private readonly OsuSpriteText bonusCounter; + + public SpinnerBonusComponent(DrawableSpinner drawableSpinner, Container ticks) + { + this.drawableSpinner = drawableSpinner; + this.ticks = ticks; + + drawableSpinner.OnNewResult += onNewResult; + + AutoSizeAxes = Axes.Both; + InternalChild = bonusCounter = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Font = OsuFont.Numeric.With(size: 24), + Alpha = 0, + }; + } + + private int currentSpins; + + public void UpdateRotation(double rotation) + { + if (ticks.Count == 0) + return; + + int spinsRequired = ((Spinner)drawableSpinner.HitObject).SpinsRequired; + + int newSpins = Math.Clamp((int)(rotation / 360), 0, ticks.Count - 1); + int direction = Math.Sign(newSpins - currentSpins); + + while (currentSpins != newSpins) + { + var tick = ticks[currentSpins]; + + if (direction >= 0) + { + tick.HasBonusPoints = currentSpins > spinsRequired; + tick.TriggerResult(true); + } + + if (tick.HasBonusPoints) + { + bonusCounter.Text = $"{1000 * (currentSpins - spinsRequired)}"; + bonusCounter.FadeOutFromOne(1500); + bonusCounter.ScaleTo(1.5f).Then().ScaleTo(1f, 1000, Easing.OutQuint); + } + + currentSpins += direction; + } + } + + private void onNewResult(DrawableHitObject hitObject, JudgementResult result) + { + if (!result.HasResult || hitObject != drawableSpinner) + return; + + // Trigger a miss result for remaining ticks to avoid infinite gameplay. + foreach (var tick in ticks.Where(t => !t.IsHit)) + tick.TriggerResult(false); + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + drawableSpinner.OnNewResult -= onNewResult; + } + } +} From b7565f5943f05247b6469491f052dd6287c95db3 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 25 Dec 2019 05:36:58 +0300 Subject: [PATCH 0031/1782] Remove unnecessary 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 86e8840425..edcaa947ac 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -14,7 +14,6 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics.Sprites; -using osu.Game.Graphics.Sprites; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Ranking; From ea29f7c34435635b1b9f2a3e5a12acdefce1d100 Mon Sep 17 00:00:00 2001 From: Lucas A Date: Wed, 4 Mar 2020 17:01:37 +0100 Subject: [PATCH 0032/1782] Use an OsuAnimatedButton in LoginPlaceholder to get the correct animations. --- .../Online/Placeholders/LoginPlaceholder.cs | 48 +++++++++---------- 1 file changed, 22 insertions(+), 26 deletions(-) diff --git a/osu.Game/Online/Placeholders/LoginPlaceholder.cs b/osu.Game/Online/Placeholders/LoginPlaceholder.cs index 73b0fa27c3..a17fb8f2b1 100644 --- a/osu.Game/Online/Placeholders/LoginPlaceholder.cs +++ b/osu.Game/Online/Placeholders/LoginPlaceholder.cs @@ -4,43 +4,39 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; -using osu.Framework.Input.Events; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.UserInterface; using osu.Game.Overlays; namespace osu.Game.Online.Placeholders { public sealed class LoginPlaceholder : Placeholder { - [Resolved(CanBeNull = true)] - private LoginOverlay login { get; set; } - public LoginPlaceholder(string actionMessage) { - AddIcon(FontAwesome.Solid.UserLock, cp => + AddArbitraryDrawable(new LoginButton(actionMessage)); + } + + private class LoginButton : OsuAnimatedButton + { + [Resolved(CanBeNull = true)] + private LoginOverlay login { get; set; } + + public LoginButton(string actionMessage) { - cp.Font = cp.Font.With(size: TEXT_SIZE); - cp.Padding = new MarginPadding { Right = 10 }; - }); + AutoSizeAxes = Axes.Both; - AddText(actionMessage); - } + Child = new OsuTextFlowContainer(cp => cp.Font = cp.Font.With(size: TEXT_SIZE)) + .With(t => t.AutoSizeAxes = Axes.Both) + .With(t => t.AddIcon(FontAwesome.Solid.UserLock, icon => + { + icon.Padding = new MarginPadding { Right = 10 }; + })) + .With(t => t.AddText(actionMessage)) + .With(t => t.Margin = new MarginPadding(5)); - protected override bool OnMouseDown(MouseDownEvent e) - { - this.ScaleTo(0.8f, 4000, Easing.OutQuint); - return base.OnMouseDown(e); - } - - protected override void OnMouseUp(MouseUpEvent e) - { - this.ScaleTo(1, 1000, Easing.OutElastic); - base.OnMouseUp(e); - } - - protected override bool OnClick(ClickEvent e) - { - login?.Show(); - return base.OnClick(e); + Action = () => login?.Show(); + } } } } From b1b3e01abdc6dc7ccde647f262b1936cc6e7265b Mon Sep 17 00:00:00 2001 From: Lucas A Date: Wed, 4 Mar 2020 22:59:48 +0100 Subject: [PATCH 0033/1782] Apply review suggestion. --- .../Online/Placeholders/LoginPlaceholder.cs | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/osu.Game/Online/Placeholders/LoginPlaceholder.cs b/osu.Game/Online/Placeholders/LoginPlaceholder.cs index a17fb8f2b1..543c108642 100644 --- a/osu.Game/Online/Placeholders/LoginPlaceholder.cs +++ b/osu.Game/Online/Placeholders/LoginPlaceholder.cs @@ -26,14 +26,20 @@ namespace osu.Game.Online.Placeholders { AutoSizeAxes = Axes.Both; - Child = new OsuTextFlowContainer(cp => cp.Font = cp.Font.With(size: TEXT_SIZE)) - .With(t => t.AutoSizeAxes = Axes.Both) - .With(t => t.AddIcon(FontAwesome.Solid.UserLock, icon => - { - icon.Padding = new MarginPadding { Right = 10 }; - })) - .With(t => t.AddText(actionMessage)) - .With(t => t.Margin = new MarginPadding(5)); + var textFlowContainer = new OsuTextFlowContainer(cp => cp.Font = cp.Font.With(size: TEXT_SIZE)) + { + AutoSizeAxes = Axes.Both, + Margin = new MarginPadding(5) + }; + + Child = textFlowContainer; + + textFlowContainer.AddIcon(FontAwesome.Solid.UserLock, icon => + { + icon.Padding = new MarginPadding { Right = 10 }; + }); + + textFlowContainer.AddText(actionMessage); Action = () => login?.Show(); } From e136ecec5f131087bd2f1f2ba4df3ee79576adde Mon Sep 17 00:00:00 2001 From: Lucas A Date: Fri, 6 Mar 2020 22:12:02 +0100 Subject: [PATCH 0034/1782] Create ClickablePlaceholder and make of use it where applicable. --- .../SongSelect/TestSceneBeatmapLeaderboard.cs | 2 +- .../TestSceneDeleteLocalScore.cs | 2 +- osu.Game/Online/Leaderboards/Leaderboard.cs | 5 +- .../RetrievalFailurePlaceholder.cs | 65 ------------------- .../Placeholders/ClickablePlaceholder.cs | 38 +++++++++++ .../Online/Placeholders/LoginPlaceholder.cs | 39 ++--------- 6 files changed, 49 insertions(+), 102 deletions(-) delete mode 100644 osu.Game/Online/Leaderboards/RetrievalFailurePlaceholder.cs create mode 100644 osu.Game/Online/Placeholders/ClickablePlaceholder.cs diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs index 1198488bda..44c77b1bd3 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs @@ -24,7 +24,7 @@ namespace osu.Game.Tests.Visual.SongSelect { typeof(Placeholder), typeof(MessagePlaceholder), - typeof(RetrievalFailurePlaceholder), + typeof(ClickablePlaceholder), typeof(UserTopScoreContainer), typeof(Leaderboard), }; diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs index a812b4dc79..fdeff7e434 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs @@ -33,7 +33,7 @@ namespace osu.Game.Tests.Visual.UserInterface { typeof(Placeholder), typeof(MessagePlaceholder), - typeof(RetrievalFailurePlaceholder), + typeof(ClickablePlaceholder), typeof(UserTopScoreContainer), typeof(Leaderboard), typeof(LeaderboardScore), diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs index e2a817aaff..cb70cfb97f 100644 --- a/osu.Game/Online/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -10,6 +10,7 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; using osu.Framework.Threading; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Cursor; @@ -133,9 +134,9 @@ namespace osu.Game.Online.Leaderboards switch (placeholderState = value) { case PlaceholderState.NetworkFailure: - replacePlaceholder(new RetrievalFailurePlaceholder + replacePlaceholder(new ClickablePlaceholder(@"Couldn't fetch scores!", FontAwesome.Solid.Sync) { - OnRetry = UpdateScores, + Action = UpdateScores, }); break; diff --git a/osu.Game/Online/Leaderboards/RetrievalFailurePlaceholder.cs b/osu.Game/Online/Leaderboards/RetrievalFailurePlaceholder.cs deleted file mode 100644 index d109f28e72..0000000000 --- a/osu.Game/Online/Leaderboards/RetrievalFailurePlaceholder.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 osu.Framework.Graphics; -using osu.Framework.Graphics.Sprites; -using osu.Framework.Input.Events; -using osu.Game.Graphics.Containers; -using osu.Game.Online.Placeholders; -using osuTK; - -namespace osu.Game.Online.Leaderboards -{ - public class RetrievalFailurePlaceholder : Placeholder - { - public Action OnRetry; - - public RetrievalFailurePlaceholder() - { - AddArbitraryDrawable(new RetryButton - { - Action = () => OnRetry?.Invoke(), - Padding = new MarginPadding { Right = 10 } - }); - - AddText(@"Couldn't retrieve scores!"); - } - - public class RetryButton : OsuHoverContainer - { - private readonly SpriteIcon icon; - - public new Action Action; - - public RetryButton() - { - AutoSizeAxes = Axes.Both; - - Child = new OsuClickableContainer - { - AutoSizeAxes = Axes.Both, - Action = () => Action?.Invoke(), - Child = icon = new SpriteIcon - { - Icon = FontAwesome.Solid.Sync, - Size = new Vector2(TEXT_SIZE), - Shadow = true, - }, - }; - } - - protected override bool OnMouseDown(MouseDownEvent e) - { - icon.ScaleTo(0.8f, 4000, Easing.OutQuint); - return base.OnMouseDown(e); - } - - protected override void OnMouseUp(MouseUpEvent e) - { - icon.ScaleTo(1, 1000, Easing.OutElastic); - base.OnMouseUp(e); - } - } - } -} diff --git a/osu.Game/Online/Placeholders/ClickablePlaceholder.cs b/osu.Game/Online/Placeholders/ClickablePlaceholder.cs new file mode 100644 index 0000000000..936ad79c64 --- /dev/null +++ b/osu.Game/Online/Placeholders/ClickablePlaceholder.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 System; +using osu.Framework.Graphics.Sprites; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.UserInterface; + +namespace osu.Game.Online.Placeholders +{ + public class ClickablePlaceholder : Placeholder + { + public Action Action; + + public ClickablePlaceholder(string actionMessage, IconUsage icon) + { + OsuTextFlowContainer textFlow; + + AddArbitraryDrawable(new OsuAnimatedButton + { + AutoSizeAxes = Framework.Graphics.Axes.Both, + Child = textFlow = new OsuTextFlowContainer(cp => cp.Font = cp.Font.With(size: TEXT_SIZE)) + { + AutoSizeAxes = Framework.Graphics.Axes.Both, + Margin = new Framework.Graphics.MarginPadding(5) + }, + Action = () => Action?.Invoke() + }); + + textFlow.AddIcon(icon, i => + { + i.Padding = new Framework.Graphics.MarginPadding { Right = 10 }; + }); + + textFlow.AddText(actionMessage); + } + } +} diff --git a/osu.Game/Online/Placeholders/LoginPlaceholder.cs b/osu.Game/Online/Placeholders/LoginPlaceholder.cs index 543c108642..f8a326a52e 100644 --- a/osu.Game/Online/Placeholders/LoginPlaceholder.cs +++ b/osu.Game/Online/Placeholders/LoginPlaceholder.cs @@ -2,47 +2,20 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; -using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; -using osu.Game.Graphics.Containers; -using osu.Game.Graphics.UserInterface; using osu.Game.Overlays; namespace osu.Game.Online.Placeholders { - public sealed class LoginPlaceholder : Placeholder + public sealed class LoginPlaceholder : ClickablePlaceholder { + [Resolved(CanBeNull = true)] + private LoginOverlay login { get; set; } + public LoginPlaceholder(string actionMessage) + : base(actionMessage, FontAwesome.Solid.UserLock) { - AddArbitraryDrawable(new LoginButton(actionMessage)); - } - - private class LoginButton : OsuAnimatedButton - { - [Resolved(CanBeNull = true)] - private LoginOverlay login { get; set; } - - public LoginButton(string actionMessage) - { - AutoSizeAxes = Axes.Both; - - var textFlowContainer = new OsuTextFlowContainer(cp => cp.Font = cp.Font.With(size: TEXT_SIZE)) - { - AutoSizeAxes = Axes.Both, - Margin = new MarginPadding(5) - }; - - Child = textFlowContainer; - - textFlowContainer.AddIcon(FontAwesome.Solid.UserLock, icon => - { - icon.Padding = new MarginPadding { Right = 10 }; - }); - - textFlowContainer.AddText(actionMessage); - - Action = () => login?.Show(); - } + Action = () => login?.Show(); } } } From d61388880364b45b49ac037c9d04bd0427fcf80c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 11 May 2020 14:50:02 +0900 Subject: [PATCH 0035/1782] Add initial changes --- .../Preprocessing/StaminaCheeseDetector.cs | 95 ++++++++++++ .../Preprocessing/TaikoDifficultyHitObject.cs | 30 +++- .../TaikoDifficultyHitObjectRhythm.cs | 124 +++++++++++++++ .../Difficulty/Skills/Colour.cs | 144 ++++++++++++++++++ .../Difficulty/Skills/SpeedInvariantRhythm.cs | 133 ++++++++++++++++ .../Difficulty/Skills/Stamina.cs | 103 +++++++++++++ .../Difficulty/Skills/Strain.cs | 95 ------------ .../Difficulty/TaikoDifficultyCalculator.cs | 99 +++++++++++- .../Difficulty/TaikoPerformanceCalculator.cs | 10 +- 9 files changed, 726 insertions(+), 107 deletions(-) create mode 100644 osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/StaminaCheeseDetector.cs create mode 100644 osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/TaikoDifficultyHitObjectRhythm.cs create mode 100644 osu.Game.Rulesets.Taiko/Difficulty/Skills/Colour.cs create mode 100644 osu.Game.Rulesets.Taiko/Difficulty/Skills/SpeedInvariantRhythm.cs create mode 100644 osu.Game.Rulesets.Taiko/Difficulty/Skills/Stamina.cs delete mode 100644 osu.Game.Rulesets.Taiko/Difficulty/Skills/Strain.cs diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/StaminaCheeseDetector.cs b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/StaminaCheeseDetector.cs new file mode 100644 index 0000000000..ffdf4cb82a --- /dev/null +++ b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/StaminaCheeseDetector.cs @@ -0,0 +1,95 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using osu.Game.Rulesets.Difficulty.Preprocessing; + +namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing +{ + public class StaminaCheeseDetector + { + + private const int roll_min_repetitions = 12; + private const int tl_min_repetitions = 16; + + private List hitObjects; + + public void FindCheese(List difficultyHitObjects) + { + this.hitObjects = difficultyHitObjects; + findRolls(3); + findRolls(4); + findTLTap(0, true); + findTLTap(1, true); + findTLTap(0, false); + findTLTap(1, false); + } + + private void findRolls(int patternLength) + { + List history = new List(); + + int repititionStart = 0; + + for (int i = 0; i < hitObjects.Count; i++) + { + history.Add(hitObjects[i]); + if (history.Count < 2 * patternLength) continue; + if (history.Count > 2 * patternLength) history.RemoveAt(0); + + bool isRepeat = true; + for (int j = 0; j < patternLength; j++) + { + if (history[j].IsKat != history[j + patternLength].IsKat) + { + isRepeat = false; + } + } + + if (!isRepeat) + { + repititionStart = i - 2 * patternLength; + } + + int repeatedLength = i - repititionStart; + + if (repeatedLength >= roll_min_repetitions) + { + // Console.WriteLine("Found Roll Cheese.\tStart: " + repititionStart + "\tEnd: " + i); + for (int j = repititionStart; j < i; j++) + { + (hitObjects[i]).StaminaCheese = true; + } + } + + } + } + + private void findTLTap(int parity, bool kat) + { + int tl_length = -2; + for (int i = parity; i < hitObjects.Count; i += 2) + { + if (kat == hitObjects[i].IsKat) + { + tl_length += 2; + } + else + { + tl_length = -2; + } + + if (tl_length >= tl_min_repetitions) + { + // Console.WriteLine("Found TL Cheese.\tStart: " + (i - tl_length) + "\tEnd: " + i); + for (int j = i - tl_length; j < i; j++) + { + (hitObjects[i]).StaminaCheese = true; + } + } + } + } + + } +} diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/TaikoDifficultyHitObject.cs b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/TaikoDifficultyHitObject.cs index 6807142327..abad494e62 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/TaikoDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/TaikoDifficultyHitObject.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using osu.Game.Rulesets.Difficulty.Preprocessing; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Taiko.Objects; @@ -10,11 +11,36 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing public class TaikoDifficultyHitObject : DifficultyHitObject { public readonly bool HasTypeChange; + public readonly bool HasTimingChange; + public readonly TaikoDifficultyHitObjectRhythm Rhythm; + public readonly bool IsKat; - public TaikoDifficultyHitObject(HitObject hitObject, HitObject lastObject, double clockRate) + public bool StaminaCheese = false; + + public readonly int RhythmID; + + public readonly double NoteLength; + + public readonly int n; + private int counter = 0; + + public TaikoDifficultyHitObject(HitObject hitObject, HitObject lastObject, HitObject lastLastObject, double clockRate) : base(hitObject, lastObject, clockRate) { - HasTypeChange = (lastObject as Hit)?.Type != (hitObject as Hit)?.Type; + NoteLength = DeltaTime; + double prevLength = (lastObject.StartTime - lastLastObject.StartTime) / clockRate; + Rhythm = TaikoDifficultyHitObjectRhythm.GetClosest(NoteLength / prevLength); + RhythmID = Rhythm.ID; + HasTypeChange = lastObject is RimHit != hitObject is RimHit; + IsKat = lastObject is RimHit; + HasTimingChange = !TaikoDifficultyHitObjectRhythm.IsRepeat(RhythmID); + + n = counter; + counter++; } + + public const int CONST_RHYTHM_ID = 0; + + } } diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/TaikoDifficultyHitObjectRhythm.cs b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/TaikoDifficultyHitObjectRhythm.cs new file mode 100644 index 0000000000..74b3d285aa --- /dev/null +++ b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/TaikoDifficultyHitObjectRhythm.cs @@ -0,0 +1,124 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; + +namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing +{ + public class TaikoDifficultyHitObjectRhythm + { + + private static TaikoDifficultyHitObjectRhythm[] commonRhythms; + private static TaikoDifficultyHitObjectRhythm constRhythm; + private static int constRhythmID; + + public int ID = 0; + public readonly double Difficulty; + private readonly double ratio; + + private static void initialiseCommonRhythms() + { + + /* + + ALCHYRS CODE + + If (change < 0.48) Then 'sometimes gaps are slightly different due to position rounding + Return 0.65 'This number increases value of anything that more than doubles speed. Affects doubles. + ElseIf (change < 0.52) Then + Return 0.5 'speed doubling - this one affects pretty much every map other than stream maps + ElseIf change <= 0.9 Then + Return 1.0 'This number increases value of 1/4 -> 1/6 and other weird rhythms. + ElseIf change < 0.95 Then + Return 0.25 '.9 + ElseIf change > 1.95 Then + Return 0.3 'half speed or more - this affects pretty much every map + ElseIf change > 1.15 Then + Return 0.425 'in between - this affects (mostly) 1/6 -> 1/4 + ElseIf change > 1.05 Then + Return 0.15 '.9, small speed changes + + */ + + + commonRhythms = new TaikoDifficultyHitObjectRhythm[] + { + new TaikoDifficultyHitObjectRhythm(1, 1, 0.1), + new TaikoDifficultyHitObjectRhythm(2, 1, 0.3), + new TaikoDifficultyHitObjectRhythm(1, 2, 0.5), + new TaikoDifficultyHitObjectRhythm(3, 1, 0.3), + new TaikoDifficultyHitObjectRhythm(1, 3, 0.35), + new TaikoDifficultyHitObjectRhythm(3, 2, 0.6), + new TaikoDifficultyHitObjectRhythm(2, 3, 0.4), + new TaikoDifficultyHitObjectRhythm(5, 4, 0.5), + new TaikoDifficultyHitObjectRhythm(4, 5, 0.7) + }; + + for (int i = 0; i < commonRhythms.Length; i++) + { + commonRhythms[i].ID = i; + } + + constRhythmID = 0; + constRhythm = commonRhythms[constRhythmID]; + + } + + public bool IsRepeat() + { + return ID == constRhythmID; + } + + public static bool IsRepeat(int id) + { + return id == constRhythmID; + } + + public bool IsSpeedup() + { + return ratio < 1.0; + } + + public bool IsLargeSpeedup() + { + return ratio < 0.49; + } + + private TaikoDifficultyHitObjectRhythm(double ratio, double difficulty) + { + this.ratio = ratio; + this.Difficulty = difficulty; + } + + private TaikoDifficultyHitObjectRhythm(int numerator, int denominator, double difficulty) + { + this.ratio = ((double)numerator) / ((double)denominator); + this.Difficulty = difficulty; + } + + // Code is inefficient - we are searching exhaustively through the sorted list commonRhythms + public static TaikoDifficultyHitObjectRhythm GetClosest(double ratio) + { + if (commonRhythms == null) + { + initialiseCommonRhythms(); + } + + TaikoDifficultyHitObjectRhythm closestRhythm = commonRhythms[0]; + double closestDistance = Double.MaxValue; + + foreach (TaikoDifficultyHitObjectRhythm r in commonRhythms) + { + if (Math.Abs(r.ratio - ratio) < closestDistance) + { + closestRhythm = r; + closestDistance = Math.Abs(r.ratio - ratio); + } + } + + return closestRhythm; + + } + + } +} diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Colour.cs b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Colour.cs new file mode 100644 index 0000000000..6ed826f345 --- /dev/null +++ b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Colour.cs @@ -0,0 +1,144 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Game.Rulesets.Difficulty.Preprocessing; +using osu.Game.Rulesets.Difficulty.Skills; +using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing; +using osu.Game.Rulesets.Taiko.Objects; + +namespace osu.Game.Rulesets.Taiko.Difficulty.Skills +{ + public class Colour : Skill + { + protected override double SkillMultiplier => 1; + protected override double StrainDecayBase => 0.3; + + private ColourSwitch lastColourSwitch = ColourSwitch.None; + private int sameColourCount = 1; + + private int[] previousDonLengths = {0, 0}, previousKatLengths = {0, 0}; + private int sameTypeCount = 1; + // TODO: make this smarter (dont initialise with "Don") + private bool previousIsKat = false; + + protected override double StrainValueOf(DifficultyHitObject current) + { + return StrainValueOfNew(current); + } + + protected double StrainValueOfNew(DifficultyHitObject current) + { + + double returnVal = 0.0; + double returnMultiplier = 1.0; + + if (previousIsKat != ((TaikoDifficultyHitObject) current).IsKat) + { + returnVal = 1.5 - (1.75 / (sameTypeCount + 0.65)); + + if (previousIsKat) + { + if (sameTypeCount % 2 == previousDonLengths[0] % 2) + { + returnMultiplier *= 0.8; + } + + if (previousKatLengths[0] == sameTypeCount) + { + returnMultiplier *= 0.525; + } + + if (previousKatLengths[1] == sameTypeCount) + { + returnMultiplier *= 0.75; + } + + previousKatLengths[1] = previousKatLengths[0]; + previousKatLengths[0] = sameTypeCount; + } + else + { + if (sameTypeCount % 2 == previousKatLengths[0] % 2) + { + returnMultiplier *= 0.8; + } + + if (previousDonLengths[0] == sameTypeCount) + { + returnMultiplier *= 0.525; + } + + if (previousDonLengths[1] == sameTypeCount) + { + returnMultiplier *= 0.75; + } + + previousDonLengths[1] = previousDonLengths[0]; + previousDonLengths[0] = sameTypeCount; + } + + + sameTypeCount = 1; + previousIsKat = ((TaikoDifficultyHitObject) current).IsKat; + + } + + else + { + sameTypeCount += 1; + } + + return Math.Min(1.25, returnVal) * returnMultiplier; + } + + protected double StrainValueOfOld(DifficultyHitObject current) + { + + double addition = 0; + + // We get an extra addition if we are not a slider or spinner + if (current.LastObject is Hit && current.BaseObject is Hit && current.DeltaTime < 1000) + { + if (hasColourChange(current)) + addition = 0.75; + } + else + { + lastColourSwitch = ColourSwitch.None; + sameColourCount = 1; + } + + return addition; + } + + + private bool hasColourChange(DifficultyHitObject current) + { + var taikoCurrent = (TaikoDifficultyHitObject) current; + + if (!taikoCurrent.HasTypeChange) + { + sameColourCount++; + return false; + } + + var oldColourSwitch = lastColourSwitch; + var newColourSwitch = sameColourCount % 2 == 0 ? ColourSwitch.Even : ColourSwitch.Odd; + + lastColourSwitch = newColourSwitch; + sameColourCount = 1; + + // We only want a bonus if the parity of the color switch changes + return oldColourSwitch != ColourSwitch.None && oldColourSwitch != newColourSwitch; + } + + private enum ColourSwitch + { + None, + Even, + Odd + } + + } +} diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Skills/SpeedInvariantRhythm.cs b/osu.Game.Rulesets.Taiko/Difficulty/Skills/SpeedInvariantRhythm.cs new file mode 100644 index 0000000000..b48cfc675f --- /dev/null +++ b/osu.Game.Rulesets.Taiko/Difficulty/Skills/SpeedInvariantRhythm.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; +using System.Collections.Generic; +using osu.Game.Rulesets.Difficulty.Preprocessing; +using osu.Game.Rulesets.Difficulty.Skills; +using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing; + +namespace osu.Game.Rulesets.Taiko.Difficulty.Skills +{ + public class Rhythm : Skill + { + + protected override double SkillMultiplier => 1; + protected override double StrainDecayBase => 0; + private const double strain_decay = 0.96; + private double currentStrain = 0.0; + + private readonly List ratioObjectHistory = new List(); + private int ratioHistoryLength = 0; + private const int ratio_history_max_length = 8; + + private int rhythmLength = 0; + + // Penalty for repeated sequences of rhythm changes + private double repititionPenalty(double timeSinceRepititionMS) + { + double t = Math.Atan(timeSinceRepititionMS / 3000) / (Math.PI / 2); + return t; + } + + private double repititionPenalty(int notesSince) + { + double t = notesSince * 150; + t = Math.Atan(t / 3000) / (Math.PI / 2); + return t; + } + + // Penalty for short patterns + // Must be low to buff maps like wizodmiot + // Must not be too low for maps like inverse world + private double patternLengthPenalty(int patternLength) + { + double shortPatternPenalty = Math.Min(0.15 * patternLength, 1.0); + double longPatternPenalty = Math.Max(Math.Min(2.5 - 0.15 * patternLength, 1.0), 0.0); + return Math.Min(shortPatternPenalty, longPatternPenalty); + } + + // Penalty for notes so slow that alting is not necessary. + private double speedPenalty(double noteLengthMS) + { + if (noteLengthMS < 80) return 1; + if (noteLengthMS < 160) return Math.Max(0, 1.4 - 0.005 * noteLengthMS); + if (noteLengthMS < 300) return 0.6; + return 0.0; + } + + // Penalty for the first rhythm change in a pattern + private const double first_burst_penalty = 0.1; + private bool prevIsSpeedup = true; + + protected override double StrainValueOf(DifficultyHitObject dho) + { + currentStrain *= strain_decay; + + TaikoDifficultyHitObject currentHO = (TaikoDifficultyHitObject) dho; + rhythmLength += 1; + if (!currentHO.HasTimingChange) + { + return 0.0; + } + + double objectDifficulty = currentHO.Rhythm.Difficulty; + + // find repeated ratios + + ratioObjectHistory.Add(currentHO); + ratioHistoryLength += 1; + if (ratioHistoryLength > ratio_history_max_length) + { + ratioObjectHistory.RemoveAt(0); + ratioHistoryLength -= 1; + } + + for (int l = 2; l <= ratio_history_max_length / 2; l++) + { + for (int start = ratioHistoryLength - l - 1; start >= 0; start--) + { + bool samePattern = true; + for (int i = 0; i < l; i++) + { + if (ratioObjectHistory[start + i].RhythmID != ratioObjectHistory[ratioHistoryLength - l + i].RhythmID) + { + samePattern = false; + } + } + + if (samePattern) // Repitition found! + { + int notesSince = currentHO.n - ratioObjectHistory[start].n; + objectDifficulty *= repititionPenalty(notesSince); + break; + } + } + } + + + if (currentHO.Rhythm.IsSpeedup()) + { + objectDifficulty *= 1; + if (currentHO.Rhythm.IsLargeSpeedup()) objectDifficulty *= 1; + if (prevIsSpeedup) objectDifficulty *= 1; + + prevIsSpeedup = true; + } + else + { + prevIsSpeedup = false; + } + + objectDifficulty *= patternLengthPenalty(rhythmLength); + objectDifficulty *= speedPenalty(currentHO.NoteLength); + + rhythmLength = 0; + + currentStrain += objectDifficulty; + return currentStrain; + + } + + } +} diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Stamina.cs b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Stamina.cs new file mode 100644 index 0000000000..349f4c29fa --- /dev/null +++ b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Stamina.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 System; +using System.Linq; +using System.Collections.Generic; +using osu.Game.Rulesets.Difficulty.Preprocessing; +using osu.Game.Rulesets.Difficulty.Skills; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing; +using osu.Game.Rulesets.Taiko.Objects; + +namespace osu.Game.Rulesets.Taiko.Difficulty.Skills +{ + public class Stamina : Skill + { + + private int hand; + private int noteNumber = 0; + + protected override double SkillMultiplier => 1; + protected override double StrainDecayBase => 0.4; + // i only add strain every second note so its kind of like using 0.16 + + private readonly int maxHistoryLength = 2; + private List noteDurationHistory = new List(); + + private List lastHitObjects = new List(); + + private double offhandObjectDuration = double.MaxValue; + + // Penalty for tl tap or roll + private double cheesePenalty(double last2NoteDuration) + { + if (last2NoteDuration > 125) return 1; + if (last2NoteDuration < 100) return 0.6; + + return 0.6 + (last2NoteDuration - 100) * 0.016; + } + + private double speedBonus(double last2NoteDuration) + { + // note that we are only looking at every 2nd note, so a 300bpm stream has a note duration of 100ms. + if (last2NoteDuration >= 200) return 0; + double bonus = 200 - last2NoteDuration; + bonus *= bonus; + return bonus / 100000; + } + + protected override double StrainValueOf(DifficultyHitObject current) + { + noteNumber += 1; + + TaikoDifficultyHitObject currentHO = (TaikoDifficultyHitObject) current; + + if (noteNumber % 2 == hand) + { + lastHitObjects.Add(currentHO); + noteDurationHistory.Add(currentHO.NoteLength + offhandObjectDuration); + + if (noteNumber == 1) + return 1; + + if (noteDurationHistory.Count > maxHistoryLength) + noteDurationHistory.RemoveAt(0); + + double shortestRecentNote = min(noteDurationHistory); + double bonus = 0; + bonus += speedBonus(shortestRecentNote); + + double objectStaminaStrain = 1 + bonus; + if (currentHO.StaminaCheese) objectStaminaStrain *= cheesePenalty(currentHO.NoteLength + offhandObjectDuration); + + return objectStaminaStrain; + } + + offhandObjectDuration = currentHO.NoteLength; + return 0; + } + + private static double min(List l) + { + double minimum = double.MaxValue; + + foreach (double d in l) + { + if (d < minimum) + minimum = d; + } + return minimum; + } + + public Stamina(bool rightHand) + { + hand = 0; + if (rightHand) + { + hand = 1; + } + } + + } +} diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Strain.cs b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Strain.cs deleted file mode 100644 index c6fe273b50..0000000000 --- a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Strain.cs +++ /dev/null @@ -1,95 +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.Game.Rulesets.Difficulty.Preprocessing; -using osu.Game.Rulesets.Difficulty.Skills; -using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing; -using osu.Game.Rulesets.Taiko.Objects; - -namespace osu.Game.Rulesets.Taiko.Difficulty.Skills -{ - public class Strain : Skill - { - private const double rhythm_change_base_threshold = 0.2; - private const double rhythm_change_base = 2.0; - - protected override double SkillMultiplier => 1; - protected override double StrainDecayBase => 0.3; - - private ColourSwitch lastColourSwitch = ColourSwitch.None; - - private int sameColourCount = 1; - - protected override double StrainValueOf(DifficultyHitObject current) - { - double addition = 1; - - // We get an extra addition if we are not a slider or spinner - if (current.LastObject is Hit && current.BaseObject is Hit && current.DeltaTime < 1000) - { - if (hasColourChange(current)) - addition += 0.75; - - if (hasRhythmChange(current)) - addition += 1; - } - else - { - lastColourSwitch = ColourSwitch.None; - sameColourCount = 1; - } - - double additionFactor = 1; - - // Scale the addition factor linearly from 0.4 to 1 for DeltaTime from 0 to 50 - if (current.DeltaTime < 50) - additionFactor = 0.4 + 0.6 * current.DeltaTime / 50; - - return additionFactor * addition; - } - - private bool hasRhythmChange(DifficultyHitObject current) - { - // We don't want a division by zero if some random mapper decides to put two HitObjects at the same time. - if (current.DeltaTime == 0 || Previous.Count == 0 || Previous[0].DeltaTime == 0) - return false; - - double timeElapsedRatio = Math.Max(Previous[0].DeltaTime / current.DeltaTime, current.DeltaTime / Previous[0].DeltaTime); - - if (timeElapsedRatio >= 8) - return false; - - double difference = Math.Log(timeElapsedRatio, rhythm_change_base) % 1.0; - - return difference > rhythm_change_base_threshold && difference < 1 - rhythm_change_base_threshold; - } - - private bool hasColourChange(DifficultyHitObject current) - { - var taikoCurrent = (TaikoDifficultyHitObject)current; - - if (!taikoCurrent.HasTypeChange) - { - sameColourCount++; - return false; - } - - var oldColourSwitch = lastColourSwitch; - var newColourSwitch = sameColourCount % 2 == 0 ? ColourSwitch.Even : ColourSwitch.Odd; - - lastColourSwitch = newColourSwitch; - sameColourCount = 1; - - // We only want a bonus if the parity of the color switch changes - return oldColourSwitch != ColourSwitch.None && oldColourSwitch != newColourSwitch; - } - - private enum ColourSwitch - { - None, - Even, - Odd - } - } -} diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs index 32d49ea39c..68da0f0e02 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.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; @@ -19,39 +20,121 @@ namespace osu.Game.Rulesets.Taiko.Difficulty { public class TaikoDifficultyCalculator : DifficultyCalculator { - private const double star_scaling_factor = 0.04125; + + private const double rhythmSkillMultiplier = 0.15; + private const double colourSkillMultiplier = 0.01; + private const double staminaSkillMultiplier = 0.02; public TaikoDifficultyCalculator(Ruleset ruleset, WorkingBeatmap beatmap) : base(ruleset, beatmap) { } + private double readingPenalty(double staminaDifficulty) + { + return Math.Max(0, 1 - staminaDifficulty / 14); + // return 1; + } + + private double norm(double p, double v1, double v2, double v3) + { + return Math.Pow( + Math.Pow(v1, p) + + Math.Pow(v2, p) + + Math.Pow(v3, p) + , 1 / p); + } + + private double rescale(double sr) + { + if (sr <= 1) return sr; + sr -= 1; + sr = 1.5 * Math.Pow(sr, 0.76); + sr += 1; + return sr; + } + + private double combinedDifficulty(Skill colour, Skill rhythm, Skill stamina1, Skill stamina2) + { + + double staminaRating = (stamina1.DifficultyValue() + stamina2.DifficultyValue()) * staminaSkillMultiplier; + double readingPenalty = this.readingPenalty(staminaRating); + + + double difficulty = 0; + double weight = 1; + List peaks = new List(); + for (int i = 0; i < colour.StrainPeaks.Count; i++) + { + double colourPeak = colour.StrainPeaks[i] * colourSkillMultiplier * readingPenalty; + double rhythmPeak = rhythm.StrainPeaks[i] * rhythmSkillMultiplier; + double staminaPeak = (stamina1.StrainPeaks[i] + stamina2.StrainPeaks[i]) * staminaSkillMultiplier; + peaks.Add(norm(2, colourPeak, rhythmPeak, staminaPeak)); + } + foreach (double strain in peaks.OrderByDescending(d => d)) + { + difficulty += strain * weight; + weight *= 0.9; + } + + return difficulty; + } + protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate) { if (beatmap.HitObjects.Count == 0) return new TaikoDifficultyAttributes { Mods = mods, Skills = skills }; + double staminaRating = (skills[2].DifficultyValue() + skills[3].DifficultyValue()) * staminaSkillMultiplier; + double readingPenalty = this.readingPenalty(staminaRating); + + double colourRating = skills[0].DifficultyValue() * colourSkillMultiplier * readingPenalty; + double rhythmRating = skills[1].DifficultyValue() * rhythmSkillMultiplier; + double combinedRating = combinedDifficulty(skills[0], skills[1], skills[2], skills[3]); + + // Console.WriteLine("colour\t" + colourRating); + // Console.WriteLine("rhythm\t" + rhythmRating); + // Console.WriteLine("stamina\t" + staminaRating); + double separatedRating = norm(1.5, colourRating, rhythmRating, staminaRating); + // Console.WriteLine("combinedRating\t" + combinedRating); + // Console.WriteLine("separatedRating\t" + separatedRating); + double starRating = 1.4 * separatedRating + 0.5 * combinedRating; + starRating = rescale(starRating); + HitWindows hitWindows = new TaikoHitWindows(); hitWindows.SetDifficulty(beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty); return new TaikoDifficultyAttributes { - StarRating = skills.Single().DifficultyValue() * star_scaling_factor, + StarRating = starRating, 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.WindowFor(HitResult.Great)) / clockRate, MaxCombo = beatmap.HitObjects.Count(h => h is Hit), Skills = skills }; + } protected override IEnumerable CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate) { - for (int i = 1; i < beatmap.HitObjects.Count; i++) - yield return new TaikoDifficultyHitObject(beatmap.HitObjects[i], beatmap.HitObjects[i - 1], clockRate); + List taikoDifficultyHitObjects = new List(); + for (int i = 2; i < beatmap.HitObjects.Count; i++) + { + taikoDifficultyHitObjects.Add(new TaikoDifficultyHitObject(beatmap.HitObjects[i], beatmap.HitObjects[i - 1], beatmap.HitObjects[i - 2], clockRate)); + } + new StaminaCheeseDetector().FindCheese(taikoDifficultyHitObjects); + for (int i = 0; i < taikoDifficultyHitObjects.Count; i++) + yield return taikoDifficultyHitObjects[i]; } - protected override Skill[] CreateSkills(IBeatmap beatmap) => new Skill[] { new Strain() }; + protected override Skill[] CreateSkills(IBeatmap beatmap) => new Skill[] + { + new Colour(), + new Rhythm(), + new Stamina(true), + new Stamina(false), + }; protected override Mod[] DifficultyAdjustmentMods => new Mod[] { @@ -60,5 +143,11 @@ namespace osu.Game.Rulesets.Taiko.Difficulty new TaikoModEasy(), new TaikoModHardRock(), }; + + /* + protected override DifficultyAttributes VirtualCalculate(IBeatmap beatmap, Mod[] mods, double clockRate) + => taikoCalculate(beatmap, mods, clockRate); + */ + } } diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs index 3a0fb64622..70249db0f6 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 = Score.Statistics[HitResult.Great]; - countGood = Score.Statistics[HitResult.Good]; - countMeh = Score.Statistics[HitResult.Meh]; - countMiss = Score.Statistics[HitResult.Miss]; + 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]); // Don't count scores made with supposedly unranked mods if (mods.Any(m => !m.Ranked)) @@ -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.1 * Math.Min(1.0, totalHits / 1500.0); + double lengthBonus = 1 + 0.1f * 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 779af48802ad135529b9e60e0d9df58871fc03f8 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 11 May 2020 14:53:42 +0900 Subject: [PATCH 0036/1782] Resolve errors + auto-format --- .../Preprocessing/StaminaCheeseDetector.cs | 7 ++----- .../Preprocessing/TaikoDifficultyHitObject.cs | 10 +++++----- .../Difficulty/Skills/Colour.cs | 16 ++++++---------- .../Difficulty/Skills/SpeedInvariantRhythm.cs | 9 ++++----- .../Difficulty/Skills/Stamina.cs | 11 ++++------- .../Difficulty/TaikoDifficultyCalculator.cs | 9 ++++----- 6 files changed, 25 insertions(+), 37 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/StaminaCheeseDetector.cs b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/StaminaCheeseDetector.cs index ffdf4cb82a..4f645d7e51 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/StaminaCheeseDetector.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/StaminaCheeseDetector.cs @@ -1,15 +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 osu.Game.Rulesets.Difficulty.Preprocessing; namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing { public class StaminaCheeseDetector { - private const int roll_min_repetitions = 12; private const int tl_min_repetitions = 16; @@ -39,6 +36,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing if (history.Count > 2 * patternLength) history.RemoveAt(0); bool isRepeat = true; + for (int j = 0; j < patternLength; j++) { if (history[j].IsKat != history[j + patternLength].IsKat) @@ -62,13 +60,13 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing (hitObjects[i]).StaminaCheese = true; } } - } } private void findTLTap(int parity, bool kat) { int tl_length = -2; + for (int i = parity; i < hitObjects.Count; i += 2) { if (kat == hitObjects[i].IsKat) @@ -90,6 +88,5 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing } } } - } } diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/TaikoDifficultyHitObject.cs b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/TaikoDifficultyHitObject.cs index abad494e62..42c23a3d14 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/TaikoDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/TaikoDifficultyHitObject.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.Game.Rulesets.Difficulty.Preprocessing; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Taiko.Objects; @@ -27,12 +26,15 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing public TaikoDifficultyHitObject(HitObject hitObject, HitObject lastObject, HitObject lastLastObject, double clockRate) : base(hitObject, lastObject, clockRate) { + var lastHit = lastObject as Hit; + var currentHit = hitObject as Hit; + NoteLength = DeltaTime; double prevLength = (lastObject.StartTime - lastLastObject.StartTime) / clockRate; Rhythm = TaikoDifficultyHitObjectRhythm.GetClosest(NoteLength / prevLength); RhythmID = Rhythm.ID; - HasTypeChange = lastObject is RimHit != hitObject is RimHit; - IsKat = lastObject is RimHit; + HasTypeChange = lastHit?.Type != currentHit?.Type; + IsKat = lastHit?.Type == HitType.Rim; HasTimingChange = !TaikoDifficultyHitObjectRhythm.IsRepeat(RhythmID); n = counter; @@ -40,7 +42,5 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing } public const int CONST_RHYTHM_ID = 0; - - } } diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Colour.cs b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Colour.cs index 6ed826f345..8b3cc0bb8f 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Colour.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Colour.cs @@ -17,8 +17,10 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills private ColourSwitch lastColourSwitch = ColourSwitch.None; private int sameColourCount = 1; - private int[] previousDonLengths = {0, 0}, previousKatLengths = {0, 0}; + private int[] previousDonLengths = { 0, 0 }, previousKatLengths = { 0, 0 }; + private int sameTypeCount = 1; + // TODO: make this smarter (dont initialise with "Don") private bool previousIsKat = false; @@ -29,11 +31,10 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills protected double StrainValueOfNew(DifficultyHitObject current) { - double returnVal = 0.0; double returnMultiplier = 1.0; - if (previousIsKat != ((TaikoDifficultyHitObject) current).IsKat) + if (previousIsKat != ((TaikoDifficultyHitObject)current).IsKat) { returnVal = 1.5 - (1.75 / (sameTypeCount + 0.65)); @@ -78,10 +79,8 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills previousDonLengths[0] = sameTypeCount; } - sameTypeCount = 1; - previousIsKat = ((TaikoDifficultyHitObject) current).IsKat; - + previousIsKat = ((TaikoDifficultyHitObject)current).IsKat; } else @@ -94,7 +93,6 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills protected double StrainValueOfOld(DifficultyHitObject current) { - double addition = 0; // We get an extra addition if we are not a slider or spinner @@ -112,10 +110,9 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills return addition; } - private bool hasColourChange(DifficultyHitObject current) { - var taikoCurrent = (TaikoDifficultyHitObject) current; + var taikoCurrent = (TaikoDifficultyHitObject)current; if (!taikoCurrent.HasTypeChange) { @@ -139,6 +136,5 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills Even, Odd } - } } diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Skills/SpeedInvariantRhythm.cs b/osu.Game.Rulesets.Taiko/Difficulty/Skills/SpeedInvariantRhythm.cs index b48cfc675f..cdd1d2d5d0 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/Skills/SpeedInvariantRhythm.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/Skills/SpeedInvariantRhythm.cs @@ -11,7 +11,6 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills { public class Rhythm : Skill { - protected override double SkillMultiplier => 1; protected override double StrainDecayBase => 0; private const double strain_decay = 0.96; @@ -64,8 +63,9 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills { currentStrain *= strain_decay; - TaikoDifficultyHitObject currentHO = (TaikoDifficultyHitObject) dho; + TaikoDifficultyHitObject currentHO = (TaikoDifficultyHitObject)dho; rhythmLength += 1; + if (!currentHO.HasTimingChange) { return 0.0; @@ -77,6 +77,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills ratioObjectHistory.Add(currentHO); ratioHistoryLength += 1; + if (ratioHistoryLength > ratio_history_max_length) { ratioObjectHistory.RemoveAt(0); @@ -88,6 +89,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills for (int start = ratioHistoryLength - l - 1; start >= 0; start--) { bool samePattern = true; + for (int i = 0; i < l; i++) { if (ratioObjectHistory[start + i].RhythmID != ratioObjectHistory[ratioHistoryLength - l + i].RhythmID) @@ -105,7 +107,6 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills } } - if (currentHO.Rhythm.IsSpeedup()) { objectDifficulty *= 1; @@ -126,8 +127,6 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills currentStrain += objectDifficulty; return currentStrain; - } - } } diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Stamina.cs b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Stamina.cs index 349f4c29fa..1ecca886df 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Stamina.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Stamina.cs @@ -1,24 +1,20 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; -using System.Linq; using System.Collections.Generic; using osu.Game.Rulesets.Difficulty.Preprocessing; using osu.Game.Rulesets.Difficulty.Skills; -using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing; -using osu.Game.Rulesets.Taiko.Objects; namespace osu.Game.Rulesets.Taiko.Difficulty.Skills { public class Stamina : Skill { - private int hand; private int noteNumber = 0; protected override double SkillMultiplier => 1; + protected override double StrainDecayBase => 0.4; // i only add strain every second note so its kind of like using 0.16 @@ -51,7 +47,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills { noteNumber += 1; - TaikoDifficultyHitObject currentHO = (TaikoDifficultyHitObject) current; + TaikoDifficultyHitObject currentHO = (TaikoDifficultyHitObject)current; if (noteNumber % 2 == hand) { @@ -87,17 +83,18 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills if (d < minimum) minimum = d; } + return minimum; } public Stamina(bool rightHand) { hand = 0; + if (rightHand) { hand = 1; } } - } } diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs index 68da0f0e02..26e92a1ea1 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs @@ -20,7 +20,6 @@ namespace osu.Game.Rulesets.Taiko.Difficulty { public class TaikoDifficultyCalculator : DifficultyCalculator { - private const double rhythmSkillMultiplier = 0.15; private const double colourSkillMultiplier = 0.01; private const double staminaSkillMultiplier = 0.02; @@ -56,14 +55,13 @@ namespace osu.Game.Rulesets.Taiko.Difficulty private double combinedDifficulty(Skill colour, Skill rhythm, Skill stamina1, Skill stamina2) { - double staminaRating = (stamina1.DifficultyValue() + stamina2.DifficultyValue()) * staminaSkillMultiplier; double readingPenalty = this.readingPenalty(staminaRating); - double difficulty = 0; double weight = 1; List peaks = new List(); + for (int i = 0; i < colour.StrainPeaks.Count; i++) { double colourPeak = colour.StrainPeaks[i] * colourSkillMultiplier * readingPenalty; @@ -71,6 +69,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty double staminaPeak = (stamina1.StrainPeaks[i] + stamina2.StrainPeaks[i]) * staminaSkillMultiplier; peaks.Add(norm(2, colourPeak, rhythmPeak, staminaPeak)); } + foreach (double strain in peaks.OrderByDescending(d => d)) { difficulty += strain * weight; @@ -113,16 +112,17 @@ namespace osu.Game.Rulesets.Taiko.Difficulty MaxCombo = beatmap.HitObjects.Count(h => h is Hit), Skills = skills }; - } protected override IEnumerable CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate) { List taikoDifficultyHitObjects = new List(); + for (int i = 2; i < beatmap.HitObjects.Count; i++) { taikoDifficultyHitObjects.Add(new TaikoDifficultyHitObject(beatmap.HitObjects[i], beatmap.HitObjects[i - 1], beatmap.HitObjects[i - 2], clockRate)); } + new StaminaCheeseDetector().FindCheese(taikoDifficultyHitObjects); for (int i = 0; i < taikoDifficultyHitObjects.Count; i++) yield return taikoDifficultyHitObjects[i]; @@ -148,6 +148,5 @@ namespace osu.Game.Rulesets.Taiko.Difficulty protected override DifficultyAttributes VirtualCalculate(IBeatmap beatmap, Mod[] mods, double clockRate) => taikoCalculate(beatmap, mods, clockRate); */ - } } From b0ed39f32baafa5de158cf95b7dc025bf6ce4d6c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 11 May 2020 14:57:47 +0900 Subject: [PATCH 0037/1782] Do not use statics --- .../Preprocessing/TaikoDifficultyHitObject.cs | 6 +- .../TaikoDifficultyHitObjectRhythm.cs | 67 +++++++------------ .../Difficulty/Skills/Colour.cs | 5 +- .../Difficulty/Skills/SpeedInvariantRhythm.cs | 6 +- .../Difficulty/TaikoDifficultyCalculator.cs | 3 +- 5 files changed, 36 insertions(+), 51 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/TaikoDifficultyHitObject.cs b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/TaikoDifficultyHitObject.cs index 42c23a3d14..75b1b3e268 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/TaikoDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/TaikoDifficultyHitObject.cs @@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing public readonly int n; private int counter = 0; - public TaikoDifficultyHitObject(HitObject hitObject, HitObject lastObject, HitObject lastLastObject, double clockRate) + public TaikoDifficultyHitObject(HitObject hitObject, HitObject lastObject, HitObject lastLastObject, double clockRate, TaikoDifficultyHitObjectRhythm rhythm) : base(hitObject, lastObject, clockRate) { var lastHit = lastObject as Hit; @@ -31,11 +31,11 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing NoteLength = DeltaTime; double prevLength = (lastObject.StartTime - lastLastObject.StartTime) / clockRate; - Rhythm = TaikoDifficultyHitObjectRhythm.GetClosest(NoteLength / prevLength); + Rhythm = rhythm.GetClosest(NoteLength / prevLength); RhythmID = Rhythm.ID; HasTypeChange = lastHit?.Type != currentHit?.Type; IsKat = lastHit?.Type == HitType.Rim; - HasTimingChange = !TaikoDifficultyHitObjectRhythm.IsRepeat(RhythmID); + HasTimingChange = !rhythm.IsRepeat(RhythmID); n = counter; counter++; diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/TaikoDifficultyHitObjectRhythm.cs b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/TaikoDifficultyHitObjectRhythm.cs index 74b3d285aa..8a6f0e5bfe 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/TaikoDifficultyHitObjectRhythm.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/TaikoDifficultyHitObjectRhythm.cs @@ -7,18 +7,36 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing { public class TaikoDifficultyHitObjectRhythm { - - private static TaikoDifficultyHitObjectRhythm[] commonRhythms; - private static TaikoDifficultyHitObjectRhythm constRhythm; - private static int constRhythmID; + private readonly TaikoDifficultyHitObjectRhythm[] commonRhythms; + private readonly TaikoDifficultyHitObjectRhythm constRhythm; + private int constRhythmID; public int ID = 0; public readonly double Difficulty; private readonly double ratio; - private static void initialiseCommonRhythms() + public bool IsRepeat() { + return ID == constRhythmID; + } + public bool IsRepeat(int id) + { + return id == constRhythmID; + } + + public bool IsSpeedup() + { + return ratio < 1.0; + } + + public bool IsLargeSpeedup() + { + return ratio < 0.49; + } + + public TaikoDifficultyHitObjectRhythm() + { /* ALCHYRS CODE @@ -40,8 +58,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing */ - - commonRhythms = new TaikoDifficultyHitObjectRhythm[] + commonRhythms = new[] { new TaikoDifficultyHitObjectRhythm(1, 1, 0.1), new TaikoDifficultyHitObjectRhythm(2, 1, 0.3), @@ -61,33 +78,6 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing constRhythmID = 0; constRhythm = commonRhythms[constRhythmID]; - - } - - public bool IsRepeat() - { - return ID == constRhythmID; - } - - public static bool IsRepeat(int id) - { - return id == constRhythmID; - } - - public bool IsSpeedup() - { - return ratio < 1.0; - } - - public bool IsLargeSpeedup() - { - return ratio < 0.49; - } - - private TaikoDifficultyHitObjectRhythm(double ratio, double difficulty) - { - this.ratio = ratio; - this.Difficulty = difficulty; } private TaikoDifficultyHitObjectRhythm(int numerator, int denominator, double difficulty) @@ -97,13 +87,8 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing } // Code is inefficient - we are searching exhaustively through the sorted list commonRhythms - public static TaikoDifficultyHitObjectRhythm GetClosest(double ratio) + public TaikoDifficultyHitObjectRhythm GetClosest(double ratio) { - if (commonRhythms == null) - { - initialiseCommonRhythms(); - } - TaikoDifficultyHitObjectRhythm closestRhythm = commonRhythms[0]; double closestDistance = Double.MaxValue; @@ -117,8 +102,6 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing } return closestRhythm; - } - } } diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Colour.cs b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Colour.cs index 8b3cc0bb8f..da255dcdd7 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Colour.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Colour.cs @@ -17,12 +17,13 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills private ColourSwitch lastColourSwitch = ColourSwitch.None; private int sameColourCount = 1; - private int[] previousDonLengths = { 0, 0 }, previousKatLengths = { 0, 0 }; + private readonly int[] previousDonLengths = { 0, 0 }; + private readonly int[] previousKatLengths = { 0, 0 }; private int sameTypeCount = 1; // TODO: make this smarter (dont initialise with "Don") - private bool previousIsKat = false; + private bool previousIsKat; protected override double StrainValueOf(DifficultyHitObject current) { diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Skills/SpeedInvariantRhythm.cs b/osu.Game.Rulesets.Taiko/Difficulty/Skills/SpeedInvariantRhythm.cs index cdd1d2d5d0..2d99bac7a9 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/Skills/SpeedInvariantRhythm.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/Skills/SpeedInvariantRhythm.cs @@ -14,13 +14,13 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills protected override double SkillMultiplier => 1; protected override double StrainDecayBase => 0; private const double strain_decay = 0.96; - private double currentStrain = 0.0; + private double currentStrain; private readonly List ratioObjectHistory = new List(); - private int ratioHistoryLength = 0; + private int ratioHistoryLength; private const int ratio_history_max_length = 8; - private int rhythmLength = 0; + private int rhythmLength; // Penalty for repeated sequences of rhythm changes private double repititionPenalty(double timeSinceRepititionMS) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs index 26e92a1ea1..6e1fae01ee 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs @@ -117,10 +117,11 @@ namespace osu.Game.Rulesets.Taiko.Difficulty protected override IEnumerable CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate) { List taikoDifficultyHitObjects = new List(); + var rhythm = new TaikoDifficultyHitObjectRhythm(); for (int i = 2; i < beatmap.HitObjects.Count; i++) { - taikoDifficultyHitObjects.Add(new TaikoDifficultyHitObject(beatmap.HitObjects[i], beatmap.HitObjects[i - 1], beatmap.HitObjects[i - 2], clockRate)); + taikoDifficultyHitObjects.Add(new TaikoDifficultyHitObject(beatmap.HitObjects[i], beatmap.HitObjects[i - 1], beatmap.HitObjects[i - 2], clockRate, rhythm)); } new StaminaCheeseDetector().FindCheese(taikoDifficultyHitObjects); From 9461097b0070c15f275c69ea67be7df638e80208 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 22 May 2020 20:50:21 +0900 Subject: [PATCH 0038/1782] Update with latest changes --- .../Difficulty/Skills/Colour.cs | 198 ++++++++---------- .../Difficulty/Skills/SpeedInvariantRhythm.cs | 8 +- .../Difficulty/TaikoDifficultyAttributes.cs | 4 + .../Difficulty/TaikoDifficultyCalculator.cs | 28 +-- 4 files changed, 114 insertions(+), 124 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Colour.cs b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Colour.cs index da255dcdd7..bd94c8aa65 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Colour.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Colour.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.IO; using osu.Game.Rulesets.Difficulty.Preprocessing; using osu.Game.Rulesets.Difficulty.Skills; using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing; @@ -12,130 +14,108 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills public class Colour : Skill { protected override double SkillMultiplier => 1; - protected override double StrainDecayBase => 0.3; + protected override double StrainDecayBase => 0.4; - private ColourSwitch lastColourSwitch = ColourSwitch.None; - private int sameColourCount = 1; + private bool prevIsKat = false; - private readonly int[] previousDonLengths = { 0, 0 }; - private readonly int[] previousKatLengths = { 0, 0 }; + private int currentMonoLength = 1; + private List monoHistory = new List(); + private readonly int mono_history_max_length = 5; + private int monoHistoryLength = 0; - private int sameTypeCount = 1; + private double sameParityPenalty() + { + return 0.0; + } - // TODO: make this smarter (dont initialise with "Don") - private bool previousIsKat; + private double repititionPenalty(int notesSince) + { + double d = notesSince; + return Math.Atan(d / 30) / (Math.PI / 2); + } + + private double patternLengthPenalty(int patternLength) + { + double shortPatternPenalty = Math.Min(0.25 * patternLength, 1.0); + double longPatternPenalty = Math.Max(Math.Min(2.5 - 0.15 * patternLength, 1.0), 0.0); + return Math.Min(shortPatternPenalty, longPatternPenalty); + } protected override double StrainValueOf(DifficultyHitObject current) { - return StrainValueOfNew(current); - } + double objectDifficulty = 0.0; - protected double StrainValueOfNew(DifficultyHitObject current) - { - double returnVal = 0.0; - double returnMultiplier = 1.0; - - if (previousIsKat != ((TaikoDifficultyHitObject)current).IsKat) - { - returnVal = 1.5 - (1.75 / (sameTypeCount + 0.65)); - - if (previousIsKat) - { - if (sameTypeCount % 2 == previousDonLengths[0] % 2) - { - returnMultiplier *= 0.8; - } - - if (previousKatLengths[0] == sameTypeCount) - { - returnMultiplier *= 0.525; - } - - if (previousKatLengths[1] == sameTypeCount) - { - returnMultiplier *= 0.75; - } - - previousKatLengths[1] = previousKatLengths[0]; - previousKatLengths[0] = sameTypeCount; - } - else - { - if (sameTypeCount % 2 == previousKatLengths[0] % 2) - { - returnMultiplier *= 0.8; - } - - if (previousDonLengths[0] == sameTypeCount) - { - returnMultiplier *= 0.525; - } - - if (previousDonLengths[1] == sameTypeCount) - { - returnMultiplier *= 0.75; - } - - previousDonLengths[1] = previousDonLengths[0]; - previousDonLengths[0] = sameTypeCount; - } - - sameTypeCount = 1; - previousIsKat = ((TaikoDifficultyHitObject)current).IsKat; - } - - else - { - sameTypeCount += 1; - } - - return Math.Min(1.25, returnVal) * returnMultiplier; - } - - protected double StrainValueOfOld(DifficultyHitObject current) - { - double addition = 0; - - // We get an extra addition if we are not a slider or spinner if (current.LastObject is Hit && current.BaseObject is Hit && current.DeltaTime < 1000) { - if (hasColourChange(current)) - addition = 0.75; + + TaikoDifficultyHitObject currentHO = (TaikoDifficultyHitObject)current; + + if (currentHO.IsKat == prevIsKat) + { + currentMonoLength += 1; + } + + else + { + + objectDifficulty = 1.0; + + if (monoHistoryLength > 0 && (monoHistory[monoHistoryLength - 1] + currentMonoLength) % 2 == 0) + { + objectDifficulty *= sameParityPenalty(); + } + + monoHistory.Add(currentMonoLength); + monoHistoryLength += 1; + + if (monoHistoryLength > mono_history_max_length) + { + monoHistory.RemoveAt(0); + monoHistoryLength -= 1; + } + + for (int l = 2; l <= mono_history_max_length / 2; l++) + { + for (int start = monoHistoryLength - l - 1; start >= 0; start--) + { + bool samePattern = true; + + for (int i = 0; i < l; i++) + { + if (monoHistory[start + i] != monoHistory[monoHistoryLength - l + i]) + { + samePattern = false; + } + } + + if (samePattern) // Repitition found! + { + int notesSince = 0; + for (int i = start; i < monoHistoryLength; i++) notesSince += monoHistory[i]; + objectDifficulty *= repititionPenalty(notesSince); + break; + } + } + } + + currentMonoLength = 1; + prevIsKat = currentHO.IsKat; + + } + } - else + + /* + string path = @"out.txt"; + using (StreamWriter sw = File.AppendText(path)) { - lastColourSwitch = ColourSwitch.None; - sameColourCount = 1; + if (((TaikoDifficultyHitObject)current).IsKat) sw.WriteLine("k " + Math.Min(1.25, returnVal) * returnMultiplier); + else sw.WriteLine("d " + Math.Min(1.25, returnVal) * returnMultiplier); } + */ - return addition; + return objectDifficulty; } - private bool hasColourChange(DifficultyHitObject current) - { - var taikoCurrent = (TaikoDifficultyHitObject)current; - - if (!taikoCurrent.HasTypeChange) - { - sameColourCount++; - return false; - } - - var oldColourSwitch = lastColourSwitch; - var newColourSwitch = sameColourCount % 2 == 0 ? ColourSwitch.Even : ColourSwitch.Odd; - - lastColourSwitch = newColourSwitch; - sameColourCount = 1; - - // We only want a bonus if the parity of the color switch changes - return oldColourSwitch != ColourSwitch.None && oldColourSwitch != newColourSwitch; - } - - private enum ColourSwitch - { - None, - Even, - Odd - } } } diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Skills/SpeedInvariantRhythm.cs b/osu.Game.Rulesets.Taiko/Difficulty/Skills/SpeedInvariantRhythm.cs index 2d99bac7a9..28198612b2 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/Skills/SpeedInvariantRhythm.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/Skills/SpeedInvariantRhythm.cs @@ -49,9 +49,13 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills // Penalty for notes so slow that alting is not necessary. private double speedPenalty(double noteLengthMS) { + if (noteLengthMS < 80) return 1; - if (noteLengthMS < 160) return Math.Max(0, 1.4 - 0.005 * noteLengthMS); - if (noteLengthMS < 300) return 0.6; + // return Math.Max(0, 1.4 - 0.005 * noteLengthMS); + if (noteLengthMS < 210) return Math.Max(0, 1.4 - 0.005 * noteLengthMS); + if (noteLengthMS < 210) return 0.6; + + currentStrain = 0.0; return 0.0; } diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyAttributes.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyAttributes.cs index 75d3807bba..783f1ba696 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyAttributes.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyAttributes.cs @@ -7,6 +7,10 @@ namespace osu.Game.Rulesets.Taiko.Difficulty { public class TaikoDifficultyAttributes : DifficultyAttributes { + public double StaminaStrain; + public double RhythmStrain; + public double ColourStrain; + public double ApproachRate; public double GreatHitWindow; public int MaxCombo; } diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs index 6e1fae01ee..2a6fa81a57 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs @@ -29,10 +29,9 @@ namespace osu.Game.Rulesets.Taiko.Difficulty { } - private double readingPenalty(double staminaDifficulty) + private double simpleColourPenalty(double staminaDifficulty, double colorDifficulty) { - return Math.Max(0, 1 - staminaDifficulty / 14); - // return 1; + return 0.79 - Math.Atan(staminaDifficulty / colorDifficulty - 12) / Math.PI / 2; } private double norm(double p, double v1, double v2, double v3) @@ -48,15 +47,13 @@ namespace osu.Game.Rulesets.Taiko.Difficulty { if (sr <= 1) return sr; sr -= 1; - sr = 1.5 * Math.Pow(sr, 0.76); + sr = 1.6 * Math.Pow(sr, 0.7); sr += 1; return sr; } - private double combinedDifficulty(Skill colour, Skill rhythm, Skill stamina1, Skill stamina2) + private double combinedDifficulty(double staminaPenalty, Skill colour, Skill rhythm, Skill stamina1, Skill stamina2) { - double staminaRating = (stamina1.DifficultyValue() + stamina2.DifficultyValue()) * staminaSkillMultiplier; - double readingPenalty = this.readingPenalty(staminaRating); double difficulty = 0; double weight = 1; @@ -64,9 +61,9 @@ namespace osu.Game.Rulesets.Taiko.Difficulty for (int i = 0; i < colour.StrainPeaks.Count; i++) { - double colourPeak = colour.StrainPeaks[i] * colourSkillMultiplier * readingPenalty; + double colourPeak = colour.StrainPeaks[i] * colourSkillMultiplier; double rhythmPeak = rhythm.StrainPeaks[i] * rhythmSkillMultiplier; - double staminaPeak = (stamina1.StrainPeaks[i] + stamina2.StrainPeaks[i]) * staminaSkillMultiplier; + double staminaPeak = (stamina1.StrainPeaks[i] + stamina2.StrainPeaks[i]) * staminaSkillMultiplier * staminaPenalty; peaks.Add(norm(2, colourPeak, rhythmPeak, staminaPeak)); } @@ -85,11 +82,13 @@ namespace osu.Game.Rulesets.Taiko.Difficulty return new TaikoDifficultyAttributes { Mods = mods, Skills = skills }; double staminaRating = (skills[2].DifficultyValue() + skills[3].DifficultyValue()) * staminaSkillMultiplier; - double readingPenalty = this.readingPenalty(staminaRating); - - double colourRating = skills[0].DifficultyValue() * colourSkillMultiplier * readingPenalty; + double colourRating = skills[0].DifficultyValue() * colourSkillMultiplier; double rhythmRating = skills[1].DifficultyValue() * rhythmSkillMultiplier; - double combinedRating = combinedDifficulty(skills[0], skills[1], skills[2], skills[3]); + + double staminaPenalty = simpleColourPenalty(staminaRating, colourRating); + staminaRating *= staminaPenalty; + + double combinedRating = combinedDifficulty(staminaPenalty, skills[0], skills[1], skills[2], skills[3]); // Console.WriteLine("colour\t" + colourRating); // Console.WriteLine("rhythm\t" + rhythmRating); @@ -107,6 +106,9 @@ namespace osu.Game.Rulesets.Taiko.Difficulty { StarRating = starRating, Mods = mods, + StaminaStrain = staminaRating, + RhythmStrain = rhythmRating, + ColourStrain = colourRating, // Todo: This int cast is temporary to achieve 1:1 results with osu!stable, and should be removed in the future GreatHitWindow = (int)(hitWindows.WindowFor(HitResult.Great)) / clockRate, MaxCombo = beatmap.HitObjects.Count(h => h is Hit), From 5852a37eb7499ac3969def1845fd3e2115f3236d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Sun, 24 May 2020 11:48:56 +0900 Subject: [PATCH 0039/1782] Update with latest changes --- .../Difficulty/Skills/SpeedInvariantRhythm.cs | 1 - .../Difficulty/TaikoDifficultyCalculator.cs | 5 ++++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Skills/SpeedInvariantRhythm.cs b/osu.Game.Rulesets.Taiko/Difficulty/Skills/SpeedInvariantRhythm.cs index 28198612b2..dd90463113 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/Skills/SpeedInvariantRhythm.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/Skills/SpeedInvariantRhythm.cs @@ -49,7 +49,6 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills // Penalty for notes so slow that alting is not necessary. private double speedPenalty(double noteLengthMS) { - if (noteLengthMS < 80) return 1; // return Math.Max(0, 1.4 - 0.005 * noteLengthMS); if (noteLengthMS < 210) return Math.Max(0, 1.4 - 0.005 * noteLengthMS); diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs index 2a6fa81a57..dc2b68e0ca 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs @@ -31,6 +31,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty private double simpleColourPenalty(double staminaDifficulty, double colorDifficulty) { + if (colorDifficulty <= 0) return 0.79 - 0.25; return 0.79 - Math.Atan(staminaDifficulty / colorDifficulty - 12) / Math.PI / 2; } @@ -123,7 +124,9 @@ namespace osu.Game.Rulesets.Taiko.Difficulty for (int i = 2; i < beatmap.HitObjects.Count; i++) { - taikoDifficultyHitObjects.Add(new TaikoDifficultyHitObject(beatmap.HitObjects[i], beatmap.HitObjects[i - 1], beatmap.HitObjects[i - 2], clockRate, rhythm)); + // Check for negative durations + if (beatmap.HitObjects[i].StartTime > beatmap.HitObjects[i - 1].StartTime && beatmap.HitObjects[i - 1].StartTime > beatmap.HitObjects[i - 2].StartTime) + taikoDifficultyHitObjects.Add(new TaikoDifficultyHitObject(beatmap.HitObjects[i], beatmap.HitObjects[i - 1], beatmap.HitObjects[i - 2], clockRate, rhythm)); } new StaminaCheeseDetector().FindCheese(taikoDifficultyHitObjects); From 17cd9569ed2875c3fd418fc018b356eb2d092806 Mon Sep 17 00:00:00 2001 From: Shivam Date: Mon, 8 Jun 2020 00:46:40 +0200 Subject: [PATCH 0040/1782] Introduce new storage class and manager --- .../Components/TourneyVideo.cs | 4 +-- osu.Game.Tournament/TournamentGameBase.cs | 12 ++++--- osu.Game.Tournament/TournamentStorage.cs | 34 +++++++++++++++++-- .../TournamentStorageManager.cs | 30 ++++++++++++++++ 4 files changed, 70 insertions(+), 10 deletions(-) create mode 100644 osu.Game.Tournament/TournamentStorageManager.cs diff --git a/osu.Game.Tournament/Components/TourneyVideo.cs b/osu.Game.Tournament/Components/TourneyVideo.cs index 317c5f6a56..259cb95035 100644 --- a/osu.Game.Tournament/Components/TourneyVideo.cs +++ b/osu.Game.Tournament/Components/TourneyVideo.cs @@ -27,9 +27,9 @@ namespace osu.Game.Tournament.Components } [BackgroundDependencyLoader] - private void load(TournamentStorage storage) + private void load(NewTournamentStorage storage) { - var stream = storage.GetStream($@"videos/{filename}"); + var stream = storage.VideoStorage.GetStream($@"{filename}"); if (stream != null) { diff --git a/osu.Game.Tournament/TournamentGameBase.cs b/osu.Game.Tournament/TournamentGameBase.cs index 85db9e61fb..427a33f871 100644 --- a/osu.Game.Tournament/TournamentGameBase.cs +++ b/osu.Game.Tournament/TournamentGameBase.cs @@ -19,6 +19,8 @@ using osu.Framework.Platform; using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Online.API.Requests; +using osu.Framework.Logging; +using osu.Game.Tournament.Configuration; using osu.Game.Tournament.IPC; using osu.Game.Tournament.Models; using osu.Game.Users; @@ -37,7 +39,7 @@ namespace osu.Game.Tournament private Storage storage; - private TournamentStorage tournamentStorage; + private NewTournamentStorage newTournamentStorage; private DependencyContainer dependencies; @@ -52,15 +54,15 @@ namespace osu.Game.Tournament } [BackgroundDependencyLoader] - private void load(Storage storage, FrameworkConfigManager frameworkConfig) + private void load(FrameworkConfigManager frameworkConfig) { Resources.AddStore(new DllResourceStore(typeof(TournamentGameBase).Assembly)); - dependencies.CacheAs(tournamentStorage = new TournamentStorage(storage)); + dependencies.CacheAs(newTournamentStorage = new NewTournamentStorage(Host)); - Textures.AddStore(new TextureLoaderStore(tournamentStorage)); + Textures.AddStore(new TextureLoaderStore(newTournamentStorage.VideoStorage)); - this.storage = storage; + this.storage = newTournamentStorage; windowSize = frameworkConfig.GetBindable(FrameworkSetting.WindowedSize); windowSize.BindValueChanged(size => ScheduleAfterChildren(() => diff --git a/osu.Game.Tournament/TournamentStorage.cs b/osu.Game.Tournament/TournamentStorage.cs index 139ad3857b..defeceab93 100644 --- a/osu.Game.Tournament/TournamentStorage.cs +++ b/osu.Game.Tournament/TournamentStorage.cs @@ -2,18 +2,46 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.IO.Stores; +using osu.Framework.Logging; using osu.Framework.Platform; +using osu.Game.IO; +using osu.Game.Tournament.Configuration; namespace osu.Game.Tournament { - internal class TournamentStorage : NamespacedResourceStore + internal class TournamentVideoStorage : NamespacedResourceStore { - public TournamentStorage(Storage storage) - : base(new StorageBackedResourceStore(storage), "tournament") + public TournamentVideoStorage(Storage storage) + : base(new StorageBackedResourceStore(storage), "videos") { AddExtension("m4v"); AddExtension("avi"); AddExtension("mp4"); } } + + internal class NewTournamentStorage : WrappedStorage + { + private readonly GameHost host; + private readonly TournamentStorageManager storageConfig; + public readonly TournamentVideoStorage VideoStorage; + + public NewTournamentStorage(GameHost host) + : base(host.Storage, string.Empty) + { + this.host = host; + + storageConfig = new TournamentStorageManager(host.Storage); + var customTournamentPath = storageConfig.Get(StorageConfig.CurrentTournament); + + if (!string.IsNullOrEmpty(customTournamentPath)) + { + ChangeTargetStorage(UnderlyingStorage.GetStorageForDirectory("tournaments/" + customTournamentPath)); + } else { + ChangeTargetStorage(UnderlyingStorage.GetStorageForDirectory("tournaments/default")); + } + VideoStorage = new TournamentVideoStorage(this); + Logger.Log("Using tournament storage: " + GetFullPath(string.Empty)); + } + } } diff --git a/osu.Game.Tournament/TournamentStorageManager.cs b/osu.Game.Tournament/TournamentStorageManager.cs new file mode 100644 index 0000000000..b1f84ecf44 --- /dev/null +++ b/osu.Game.Tournament/TournamentStorageManager.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.Configuration; +using osu.Framework.Platform; + +namespace osu.Game.Tournament.Configuration +{ + public class TournamentStorageManager : IniConfigManager + { + protected override string Filename => "tournament.ini"; + + public TournamentStorageManager(Storage storage) + : base(storage) + { + } + + protected override void InitialiseDefaults() + { + base.InitialiseDefaults(); + Set(StorageConfig.CurrentTournament, string.Empty); + } + + } + + public enum StorageConfig + { + CurrentTournament, + } +} \ No newline at end of file From 9a20ffa8a35ac048b7279dec2fd3752061a12987 Mon Sep 17 00:00:00 2001 From: Shivam Date: Mon, 8 Jun 2020 00:47:47 +0200 Subject: [PATCH 0041/1782] Rename to TournamentStorage --- osu.Game.Tournament/Components/TourneyVideo.cs | 2 +- osu.Game.Tournament/TournamentGameBase.cs | 8 ++++---- osu.Game.Tournament/TournamentStorage.cs | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tournament/Components/TourneyVideo.cs b/osu.Game.Tournament/Components/TourneyVideo.cs index 259cb95035..131fa9450d 100644 --- a/osu.Game.Tournament/Components/TourneyVideo.cs +++ b/osu.Game.Tournament/Components/TourneyVideo.cs @@ -27,7 +27,7 @@ namespace osu.Game.Tournament.Components } [BackgroundDependencyLoader] - private void load(NewTournamentStorage storage) + private void load(TournamentStorage storage) { var stream = storage.VideoStorage.GetStream($@"{filename}"); diff --git a/osu.Game.Tournament/TournamentGameBase.cs b/osu.Game.Tournament/TournamentGameBase.cs index 427a33f871..991c586a56 100644 --- a/osu.Game.Tournament/TournamentGameBase.cs +++ b/osu.Game.Tournament/TournamentGameBase.cs @@ -39,7 +39,7 @@ namespace osu.Game.Tournament private Storage storage; - private NewTournamentStorage newTournamentStorage; + private TournamentStorage tournamentStorage; private DependencyContainer dependencies; @@ -58,11 +58,11 @@ namespace osu.Game.Tournament { Resources.AddStore(new DllResourceStore(typeof(TournamentGameBase).Assembly)); - dependencies.CacheAs(newTournamentStorage = new NewTournamentStorage(Host)); + dependencies.CacheAs(tournamentStorage = new TournamentStorage(Host)); - Textures.AddStore(new TextureLoaderStore(newTournamentStorage.VideoStorage)); + Textures.AddStore(new TextureLoaderStore(tournamentStorage.VideoStorage)); - this.storage = newTournamentStorage; + this.storage = tournamentStorage; windowSize = frameworkConfig.GetBindable(FrameworkSetting.WindowedSize); windowSize.BindValueChanged(size => ScheduleAfterChildren(() => diff --git a/osu.Game.Tournament/TournamentStorage.cs b/osu.Game.Tournament/TournamentStorage.cs index defeceab93..e5d19831d0 100644 --- a/osu.Game.Tournament/TournamentStorage.cs +++ b/osu.Game.Tournament/TournamentStorage.cs @@ -20,13 +20,13 @@ namespace osu.Game.Tournament } } - internal class NewTournamentStorage : WrappedStorage + internal class TournamentStorage : WrappedStorage { private readonly GameHost host; private readonly TournamentStorageManager storageConfig; public readonly TournamentVideoStorage VideoStorage; - public NewTournamentStorage(GameHost host) + public TournamentStorage(GameHost host) : base(host.Storage, string.Empty) { this.host = host; From ba5a747ac9e1b83ff38b28884d86952895d34ab9 Mon Sep 17 00:00:00 2001 From: Shivam Date: Mon, 8 Jun 2020 03:03:57 +0200 Subject: [PATCH 0042/1782] Implement migration for TournamentStorage --- osu.Game.Tournament/TournamentStorage.cs | 75 ++++++++++++++++++++++-- 1 file changed, 70 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tournament/TournamentStorage.cs b/osu.Game.Tournament/TournamentStorage.cs index e5d19831d0..48eef76a28 100644 --- a/osu.Game.Tournament/TournamentStorage.cs +++ b/osu.Game.Tournament/TournamentStorage.cs @@ -1,10 +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 System.Threading; using osu.Framework.IO.Stores; using osu.Framework.Logging; using osu.Framework.Platform; using osu.Game.IO; +using System.IO; using osu.Game.Tournament.Configuration; namespace osu.Game.Tournament @@ -32,16 +36,77 @@ namespace osu.Game.Tournament this.host = host; storageConfig = new TournamentStorageManager(host.Storage); - var customTournamentPath = storageConfig.Get(StorageConfig.CurrentTournament); + var currentTournament = storageConfig.Get(StorageConfig.CurrentTournament); - if (!string.IsNullOrEmpty(customTournamentPath)) + if (!string.IsNullOrEmpty(currentTournament)) { - ChangeTargetStorage(UnderlyingStorage.GetStorageForDirectory("tournaments/" + customTournamentPath)); - } else { - ChangeTargetStorage(UnderlyingStorage.GetStorageForDirectory("tournaments/default")); + ChangeTargetStorage(UnderlyingStorage.GetStorageForDirectory("tournaments" + Path.DirectorySeparatorChar + currentTournament)); } + else + { + // Migrating old storage format to the new one. + Migrate(); + Logger.Log("Migrating files from old storage to new."); + } + VideoStorage = new TournamentVideoStorage(this); Logger.Log("Using tournament storage: " + GetFullPath(string.Empty)); } + + private void Migrate() + { + var defaultPath = "tournaments/default"; + var source = new DirectoryInfo(GetFullPath("tournament")); + var destination = new DirectoryInfo(GetFullPath(defaultPath)); + + Directory.CreateDirectory(destination.FullName); + + if (host.Storage.Exists("bracket.json")) + { + Logger.Log("Migrating bracket to default tournament storage."); + var bracketFile = new System.IO.FileInfo(GetFullPath(string.Empty) + Path.DirectorySeparatorChar + GetFiles(string.Empty, "bracket.json").First()); + attemptOperation(() => bracketFile.CopyTo(Path.Combine(destination.FullName, bracketFile.Name), true)); + } + + Logger.Log("Migrating other assets to default tournament storage."); + copyRecursive(source, destination); + ChangeTargetStorage(UnderlyingStorage.GetStorageForDirectory(defaultPath)); + storageConfig.Set(StorageConfig.CurrentTournament, defaultPath); + storageConfig.Save(); + } + + private void copyRecursive(DirectoryInfo source, DirectoryInfo destination, bool topLevelExcludes = true) + { + // based off example code https://docs.microsoft.com/en-us/dotnet/api/system.io.directoryinfo + + foreach (System.IO.FileInfo fi in source.GetFiles()) + { + attemptOperation(() => fi.CopyTo(Path.Combine(destination.FullName, fi.Name), true)); + } + + foreach (DirectoryInfo dir in source.GetDirectories()) + { + copyRecursive(dir, destination.CreateSubdirectory(dir.Name), false); + } + } + + private void attemptOperation(Action action, int attempts = 10) + { + while (true) + { + try + { + action(); + return; + } + catch (Exception) + { + if (attempts-- == 0) + throw; + } + + Thread.Sleep(250); + } + } } } From f01a86f5b1c4395e99aec8a37fe9aa7c2563c5dd Mon Sep 17 00:00:00 2001 From: Shivam Date: Mon, 8 Jun 2020 03:12:37 +0200 Subject: [PATCH 0043/1782] Fix styling issues and move StorageManager to Configuration Folder --- .../TournamentStorageManager.cs | 1 - osu.Game.Tournament/TournamentGameBase.cs | 4 +--- osu.Game.Tournament/TournamentStorage.cs | 16 ++++++++-------- 3 files changed, 9 insertions(+), 12 deletions(-) rename osu.Game.Tournament/{ => Configuration}/TournamentStorageManager.cs (99%) diff --git a/osu.Game.Tournament/TournamentStorageManager.cs b/osu.Game.Tournament/Configuration/TournamentStorageManager.cs similarity index 99% rename from osu.Game.Tournament/TournamentStorageManager.cs rename to osu.Game.Tournament/Configuration/TournamentStorageManager.cs index b1f84ecf44..6ccc2b6308 100644 --- a/osu.Game.Tournament/TournamentStorageManager.cs +++ b/osu.Game.Tournament/Configuration/TournamentStorageManager.cs @@ -20,7 +20,6 @@ namespace osu.Game.Tournament.Configuration base.InitialiseDefaults(); Set(StorageConfig.CurrentTournament, string.Empty); } - } public enum StorageConfig diff --git a/osu.Game.Tournament/TournamentGameBase.cs b/osu.Game.Tournament/TournamentGameBase.cs index 991c586a56..e3d310a497 100644 --- a/osu.Game.Tournament/TournamentGameBase.cs +++ b/osu.Game.Tournament/TournamentGameBase.cs @@ -19,8 +19,6 @@ using osu.Framework.Platform; using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Online.API.Requests; -using osu.Framework.Logging; -using osu.Game.Tournament.Configuration; using osu.Game.Tournament.IPC; using osu.Game.Tournament.Models; using osu.Game.Users; @@ -62,7 +60,7 @@ namespace osu.Game.Tournament Textures.AddStore(new TextureLoaderStore(tournamentStorage.VideoStorage)); - this.storage = tournamentStorage; + storage = tournamentStorage; windowSize = frameworkConfig.GetBindable(FrameworkSetting.WindowedSize); windowSize.BindValueChanged(size => ScheduleAfterChildren(() => diff --git a/osu.Game.Tournament/TournamentStorage.cs b/osu.Game.Tournament/TournamentStorage.cs index 48eef76a28..d1c8635466 100644 --- a/osu.Game.Tournament/TournamentStorage.cs +++ b/osu.Game.Tournament/TournamentStorage.cs @@ -45,7 +45,7 @@ namespace osu.Game.Tournament else { // Migrating old storage format to the new one. - Migrate(); + migrate(); Logger.Log("Migrating files from old storage to new."); } @@ -53,16 +53,16 @@ namespace osu.Game.Tournament Logger.Log("Using tournament storage: " + GetFullPath(string.Empty)); } - private void Migrate() + private void migrate() { - var defaultPath = "tournaments/default"; + const string default_path = "tournaments/default"; var source = new DirectoryInfo(GetFullPath("tournament")); - var destination = new DirectoryInfo(GetFullPath(defaultPath)); + var destination = new DirectoryInfo(GetFullPath(default_path)); Directory.CreateDirectory(destination.FullName); - + if (host.Storage.Exists("bracket.json")) - { + { Logger.Log("Migrating bracket to default tournament storage."); var bracketFile = new System.IO.FileInfo(GetFullPath(string.Empty) + Path.DirectorySeparatorChar + GetFiles(string.Empty, "bracket.json").First()); attemptOperation(() => bracketFile.CopyTo(Path.Combine(destination.FullName, bracketFile.Name), true)); @@ -70,8 +70,8 @@ namespace osu.Game.Tournament Logger.Log("Migrating other assets to default tournament storage."); copyRecursive(source, destination); - ChangeTargetStorage(UnderlyingStorage.GetStorageForDirectory(defaultPath)); - storageConfig.Set(StorageConfig.CurrentTournament, defaultPath); + ChangeTargetStorage(UnderlyingStorage.GetStorageForDirectory(default_path)); + storageConfig.Set(StorageConfig.CurrentTournament, default_path); storageConfig.Save(); } From 68027fcc2c46dacfbfda5a64eb5745d40eae6c81 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 8 Jun 2020 16:30:26 +0900 Subject: [PATCH 0044/1782] Update with latest changes --- .../Preprocessing/StaminaCheeseDetector.cs | 37 +++-- .../Preprocessing/TaikoDifficultyHitObject.cs | 28 ++-- .../TaikoDifficultyHitObjectRhythm.cs | 100 +----------- .../Difficulty/Skills/Colour.cs | 153 +++++++++--------- .../Difficulty/Skills/Rhythm.cs | 115 +++++++++++++ .../Difficulty/Skills/SpeedInvariantRhythm.cs | 135 ---------------- .../Difficulty/Skills/Stamina.cs | 79 ++++----- .../Difficulty/TaikoDifficultyCalculator.cs | 71 ++++---- .../Difficulty/TaikoPerformanceCalculator.cs | 4 - 9 files changed, 295 insertions(+), 427 deletions(-) create mode 100644 osu.Game.Rulesets.Taiko/Difficulty/Skills/Rhythm.cs delete mode 100644 osu.Game.Rulesets.Taiko/Difficulty/Skills/SpeedInvariantRhythm.cs diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/StaminaCheeseDetector.cs b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/StaminaCheeseDetector.cs index 4f645d7e51..b52dad5198 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/StaminaCheeseDetector.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/StaminaCheeseDetector.cs @@ -14,25 +14,26 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing public void FindCheese(List difficultyHitObjects) { - this.hitObjects = difficultyHitObjects; + hitObjects = difficultyHitObjects; findRolls(3); findRolls(4); - findTLTap(0, true); - findTLTap(1, true); - findTLTap(0, false); - findTLTap(1, false); + findTlTap(0, true); + findTlTap(1, true); + findTlTap(0, false); + findTlTap(1, false); } private void findRolls(int patternLength) { List history = new List(); - int repititionStart = 0; + int repetitionStart = 0; for (int i = 0; i < hitObjects.Count; i++) { history.Add(hitObjects[i]); if (history.Count < 2 * patternLength) continue; + if (history.Count > 2 * patternLength) history.RemoveAt(0); bool isRepeat = true; @@ -47,43 +48,41 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing if (!isRepeat) { - repititionStart = i - 2 * patternLength; + repetitionStart = i - 2 * patternLength; } - int repeatedLength = i - repititionStart; + int repeatedLength = i - repetitionStart; if (repeatedLength >= roll_min_repetitions) { - // Console.WriteLine("Found Roll Cheese.\tStart: " + repititionStart + "\tEnd: " + i); - for (int j = repititionStart; j < i; j++) + for (int j = repetitionStart; j < i; j++) { - (hitObjects[i]).StaminaCheese = true; + hitObjects[i].StaminaCheese = true; } } } } - private void findTLTap(int parity, bool kat) + private void findTlTap(int parity, bool kat) { - int tl_length = -2; + int tlLength = -2; for (int i = parity; i < hitObjects.Count; i += 2) { if (kat == hitObjects[i].IsKat) { - tl_length += 2; + tlLength += 2; } else { - tl_length = -2; + tlLength = -2; } - if (tl_length >= tl_min_repetitions) + if (tlLength >= tl_min_repetitions) { - // Console.WriteLine("Found TL Cheese.\tStart: " + (i - tl_length) + "\tEnd: " + i); - for (int j = i - tl_length; j < i; j++) + for (int j = i - tlLength; j < i; j++) { - (hitObjects[i]).StaminaCheese = true; + hitObjects[i].StaminaCheese = true; } } } diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/TaikoDifficultyHitObject.cs b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/TaikoDifficultyHitObject.cs index 75b1b3e268..cd45db2119 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/TaikoDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/TaikoDifficultyHitObject.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; +using System.Collections.Generic; +using System.Linq; using osu.Game.Rulesets.Difficulty.Preprocessing; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Taiko.Objects; @@ -9,38 +12,31 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing { public class TaikoDifficultyHitObject : DifficultyHitObject { - public readonly bool HasTypeChange; - public readonly bool HasTimingChange; public readonly TaikoDifficultyHitObjectRhythm Rhythm; public readonly bool IsKat; public bool StaminaCheese = false; - public readonly int RhythmID; - public readonly double NoteLength; - public readonly int n; - private int counter = 0; + public readonly int N; - public TaikoDifficultyHitObject(HitObject hitObject, HitObject lastObject, HitObject lastLastObject, double clockRate, TaikoDifficultyHitObjectRhythm rhythm) + public TaikoDifficultyHitObject(HitObject hitObject, HitObject lastObject, HitObject lastLastObject, double clockRate, int n, IEnumerable commonRhythms) : base(hitObject, lastObject, clockRate) { - var lastHit = lastObject as Hit; var currentHit = hitObject as Hit; NoteLength = DeltaTime; double prevLength = (lastObject.StartTime - lastLastObject.StartTime) / clockRate; - Rhythm = rhythm.GetClosest(NoteLength / prevLength); - RhythmID = Rhythm.ID; - HasTypeChange = lastHit?.Type != currentHit?.Type; - IsKat = lastHit?.Type == HitType.Rim; - HasTimingChange = !rhythm.IsRepeat(RhythmID); + Rhythm = getClosestRhythm(NoteLength / prevLength, commonRhythms); + IsKat = currentHit?.Type == HitType.Rim; - n = counter; - counter++; + N = n; } - public const int CONST_RHYTHM_ID = 0; + private TaikoDifficultyHitObjectRhythm getClosestRhythm(double ratio, IEnumerable commonRhythms) + { + return commonRhythms.OrderBy(x => Math.Abs(x.Ratio - ratio)).First(); + } } } diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/TaikoDifficultyHitObjectRhythm.cs b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/TaikoDifficultyHitObjectRhythm.cs index 8a6f0e5bfe..0ad885d9bd 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/TaikoDifficultyHitObjectRhythm.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/TaikoDifficultyHitObjectRhythm.cs @@ -1,107 +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; - namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing { public class TaikoDifficultyHitObjectRhythm { - private readonly TaikoDifficultyHitObjectRhythm[] commonRhythms; - private readonly TaikoDifficultyHitObjectRhythm constRhythm; - private int constRhythmID; - - public int ID = 0; public readonly double Difficulty; - private readonly double ratio; + public readonly double Ratio; + public readonly bool IsRepeat; - public bool IsRepeat() + public TaikoDifficultyHitObjectRhythm(int numerator, int denominator, double difficulty, bool isRepeat) { - return ID == constRhythmID; - } - - public bool IsRepeat(int id) - { - return id == constRhythmID; - } - - public bool IsSpeedup() - { - return ratio < 1.0; - } - - public bool IsLargeSpeedup() - { - return ratio < 0.49; - } - - public TaikoDifficultyHitObjectRhythm() - { - /* - - ALCHYRS CODE - - If (change < 0.48) Then 'sometimes gaps are slightly different due to position rounding - Return 0.65 'This number increases value of anything that more than doubles speed. Affects doubles. - ElseIf (change < 0.52) Then - Return 0.5 'speed doubling - this one affects pretty much every map other than stream maps - ElseIf change <= 0.9 Then - Return 1.0 'This number increases value of 1/4 -> 1/6 and other weird rhythms. - ElseIf change < 0.95 Then - Return 0.25 '.9 - ElseIf change > 1.95 Then - Return 0.3 'half speed or more - this affects pretty much every map - ElseIf change > 1.15 Then - Return 0.425 'in between - this affects (mostly) 1/6 -> 1/4 - ElseIf change > 1.05 Then - Return 0.15 '.9, small speed changes - - */ - - commonRhythms = new[] - { - new TaikoDifficultyHitObjectRhythm(1, 1, 0.1), - new TaikoDifficultyHitObjectRhythm(2, 1, 0.3), - new TaikoDifficultyHitObjectRhythm(1, 2, 0.5), - new TaikoDifficultyHitObjectRhythm(3, 1, 0.3), - new TaikoDifficultyHitObjectRhythm(1, 3, 0.35), - new TaikoDifficultyHitObjectRhythm(3, 2, 0.6), - new TaikoDifficultyHitObjectRhythm(2, 3, 0.4), - new TaikoDifficultyHitObjectRhythm(5, 4, 0.5), - new TaikoDifficultyHitObjectRhythm(4, 5, 0.7) - }; - - for (int i = 0; i < commonRhythms.Length; i++) - { - commonRhythms[i].ID = i; - } - - constRhythmID = 0; - constRhythm = commonRhythms[constRhythmID]; - } - - private TaikoDifficultyHitObjectRhythm(int numerator, int denominator, double difficulty) - { - this.ratio = ((double)numerator) / ((double)denominator); - this.Difficulty = difficulty; - } - - // Code is inefficient - we are searching exhaustively through the sorted list commonRhythms - public TaikoDifficultyHitObjectRhythm GetClosest(double ratio) - { - TaikoDifficultyHitObjectRhythm closestRhythm = commonRhythms[0]; - double closestDistance = Double.MaxValue; - - foreach (TaikoDifficultyHitObjectRhythm r in commonRhythms) - { - if (Math.Abs(r.ratio - ratio) < closestDistance) - { - closestRhythm = r; - closestDistance = Math.Abs(r.ratio - ratio); - } - } - - return closestRhythm; + Ratio = numerator / (double)denominator; + Difficulty = difficulty; + IsRepeat = isRepeat; } } } diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Colour.cs b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Colour.cs index bd94c8aa65..7c1623c54e 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Colour.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Colour.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.IO; using osu.Game.Rulesets.Difficulty.Preprocessing; using osu.Game.Rulesets.Difficulty.Skills; using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing; @@ -16,106 +15,100 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills protected override double SkillMultiplier => 1; protected override double StrainDecayBase => 0.4; - private bool prevIsKat = false; + private NoteColour prevNoteColour = NoteColour.None; private int currentMonoLength = 1; - private List monoHistory = new List(); - private readonly int mono_history_max_length = 5; - private int monoHistoryLength = 0; + private readonly List monoHistory = new List(); + private const int mono_history_max_length = 5; private double sameParityPenalty() { return 0.0; } - private double repititionPenalty(int notesSince) + private double repetitionPenalty(int notesSince) { - double d = notesSince; - return Math.Atan(d / 30) / (Math.PI / 2); + double n = notesSince; + return Math.Min(1.0, 0.032 * n); } - private double patternLengthPenalty(int patternLength) + private double repetitionPenalties() { - double shortPatternPenalty = Math.Min(0.25 * patternLength, 1.0); - double longPatternPenalty = Math.Max(Math.Min(2.5 - 0.15 * patternLength, 1.0), 0.0); - return Math.Min(shortPatternPenalty, longPatternPenalty); + double penalty = 1.0; + + monoHistory.Add(currentMonoLength); + + if (monoHistory.Count > mono_history_max_length) + monoHistory.RemoveAt(0); + + for (int l = 2; l <= mono_history_max_length / 2; l++) + { + for (int start = monoHistory.Count - l - 1; start >= 0; start--) + { + bool samePattern = true; + + for (int i = 0; i < l; i++) + { + if (monoHistory[start + i] != monoHistory[monoHistory.Count - l + i]) + { + samePattern = false; + } + } + + if (samePattern) // Repetition found! + { + int notesSince = 0; + for (int i = start; i < monoHistory.Count; i++) notesSince += monoHistory[i]; + penalty *= repetitionPenalty(notesSince); + break; + } + } + } + + return penalty; } protected override double StrainValueOf(DifficultyHitObject current) { - double objectDifficulty = 0.0; - - if (current.LastObject is Hit && current.BaseObject is Hit && current.DeltaTime < 1000) + if (!(current.LastObject is Hit && current.BaseObject is Hit && current.DeltaTime < 1000)) { - - TaikoDifficultyHitObject currentHO = (TaikoDifficultyHitObject)current; - - if (currentHO.IsKat == prevIsKat) - { - currentMonoLength += 1; - } - - else - { - - objectDifficulty = 1.0; - - if (monoHistoryLength > 0 && (monoHistory[monoHistoryLength - 1] + currentMonoLength) % 2 == 0) - { - objectDifficulty *= sameParityPenalty(); - } - - monoHistory.Add(currentMonoLength); - monoHistoryLength += 1; - - if (monoHistoryLength > mono_history_max_length) - { - monoHistory.RemoveAt(0); - monoHistoryLength -= 1; - } - - for (int l = 2; l <= mono_history_max_length / 2; l++) - { - for (int start = monoHistoryLength - l - 1; start >= 0; start--) - { - bool samePattern = true; - - for (int i = 0; i < l; i++) - { - if (monoHistory[start + i] != monoHistory[monoHistoryLength - l + i]) - { - samePattern = false; - } - } - - if (samePattern) // Repitition found! - { - int notesSince = 0; - for (int i = start; i < monoHistoryLength; i++) notesSince += monoHistory[i]; - objectDifficulty *= repititionPenalty(notesSince); - break; - } - } - } - - currentMonoLength = 1; - prevIsKat = currentHO.IsKat; - - } - + prevNoteColour = NoteColour.None; + return 0.0; } - /* - string path = @"out.txt"; - using (StreamWriter sw = File.AppendText(path)) - { - if (((TaikoDifficultyHitObject)current).IsKat) sw.WriteLine("k " + Math.Min(1.25, returnVal) * returnMultiplier); - else sw.WriteLine("d " + Math.Min(1.25, returnVal) * returnMultiplier); - } - */ + TaikoDifficultyHitObject hitObject = (TaikoDifficultyHitObject)current; - return objectDifficulty; + double objectStrain = 0.0; + + NoteColour noteColour = hitObject.IsKat ? NoteColour.Ka : NoteColour.Don; + + if (noteColour == NoteColour.Don && prevNoteColour == NoteColour.Ka || + noteColour == NoteColour.Ka && prevNoteColour == NoteColour.Don) + { + objectStrain = 1.0; + + if (monoHistory.Count < 2) + objectStrain = 0.0; + else if ((monoHistory[^1] + currentMonoLength) % 2 == 0) + objectStrain *= sameParityPenalty(); + + objectStrain *= repetitionPenalties(); + currentMonoLength = 1; + } + else + { + currentMonoLength += 1; + } + + prevNoteColour = noteColour; + return objectStrain; } + private enum NoteColour + { + Don, + Ka, + None + } } } diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Rhythm.cs b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Rhythm.cs new file mode 100644 index 0000000000..c3e6ee4d12 --- /dev/null +++ b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Rhythm.cs @@ -0,0 +1,115 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using osu.Game.Rulesets.Difficulty.Preprocessing; +using osu.Game.Rulesets.Difficulty.Skills; +using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing; +using osu.Game.Rulesets.Taiko.Objects; + +namespace osu.Game.Rulesets.Taiko.Difficulty.Skills +{ + public class Rhythm : Skill + { + protected override double SkillMultiplier => 10; + protected override double StrainDecayBase => 0; + private const double strain_decay = 0.96; + private double currentStrain; + + private readonly List rhythmHistory = new List(); + private const int rhythm_history_max_length = 8; + + private int notesSinceRhythmChange; + + private double repetitionPenalty(int notesSince) + { + return Math.Min(1.0, 0.032 * notesSince); + } + + // Finds repetitions and applies penalties + private double repetitionPenalties(TaikoDifficultyHitObject hitobject) + { + double penalty = 1; + + rhythmHistory.Add(hitobject); + + if (rhythmHistory.Count > rhythm_history_max_length) + rhythmHistory.RemoveAt(0); + + for (int l = 2; l <= rhythm_history_max_length / 2; l++) + { + for (int start = rhythmHistory.Count - l - 1; start >= 0; start--) + { + bool samePattern = true; + + for (int i = 0; i < l; i++) + { + if (rhythmHistory[start + i].Rhythm != rhythmHistory[rhythmHistory.Count - l + i].Rhythm) + { + samePattern = false; + } + } + + if (samePattern) // Repetition found! + { + int notesSince = hitobject.N - rhythmHistory[start].N; + penalty *= repetitionPenalty(notesSince); + break; + } + } + } + + return penalty; + } + + private double patternLengthPenalty(int patternLength) + { + double shortPatternPenalty = Math.Min(0.15 * patternLength, 1.0); + double longPatternPenalty = Math.Max(Math.Min(2.5 - 0.15 * patternLength, 1.0), 0.0); + return Math.Min(shortPatternPenalty, longPatternPenalty); + } + + // Penalty for notes so slow that alternating is not necessary. + private double speedPenalty(double noteLengthMs) + { + if (noteLengthMs < 80) return 1; + if (noteLengthMs < 210) return Math.Max(0, 1.4 - 0.005 * noteLengthMs); + + currentStrain = 0.0; + notesSinceRhythmChange = 0; + return 0.0; + } + + protected override double StrainValueOf(DifficultyHitObject current) + { + if (!(current.BaseObject is Hit)) + { + currentStrain = 0.0; + notesSinceRhythmChange = 0; + return 0.0; + } + + currentStrain *= strain_decay; + + TaikoDifficultyHitObject hitobject = (TaikoDifficultyHitObject)current; + notesSinceRhythmChange += 1; + + if (hitobject.Rhythm.IsRepeat) + { + return 0.0; + } + + double objectStrain = hitobject.Rhythm.Difficulty; + + objectStrain *= repetitionPenalties(hitobject); + objectStrain *= patternLengthPenalty(notesSinceRhythmChange); + objectStrain *= speedPenalty(hitobject.NoteLength); + + notesSinceRhythmChange = 0; + + currentStrain += objectStrain; + return currentStrain; + } + } +} diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Skills/SpeedInvariantRhythm.cs b/osu.Game.Rulesets.Taiko/Difficulty/Skills/SpeedInvariantRhythm.cs deleted file mode 100644 index dd90463113..0000000000 --- a/osu.Game.Rulesets.Taiko/Difficulty/Skills/SpeedInvariantRhythm.cs +++ /dev/null @@ -1,135 +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.Game.Rulesets.Difficulty.Preprocessing; -using osu.Game.Rulesets.Difficulty.Skills; -using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing; - -namespace osu.Game.Rulesets.Taiko.Difficulty.Skills -{ - public class Rhythm : Skill - { - protected override double SkillMultiplier => 1; - protected override double StrainDecayBase => 0; - private const double strain_decay = 0.96; - private double currentStrain; - - private readonly List ratioObjectHistory = new List(); - private int ratioHistoryLength; - private const int ratio_history_max_length = 8; - - private int rhythmLength; - - // Penalty for repeated sequences of rhythm changes - private double repititionPenalty(double timeSinceRepititionMS) - { - double t = Math.Atan(timeSinceRepititionMS / 3000) / (Math.PI / 2); - return t; - } - - private double repititionPenalty(int notesSince) - { - double t = notesSince * 150; - t = Math.Atan(t / 3000) / (Math.PI / 2); - return t; - } - - // Penalty for short patterns - // Must be low to buff maps like wizodmiot - // Must not be too low for maps like inverse world - private double patternLengthPenalty(int patternLength) - { - double shortPatternPenalty = Math.Min(0.15 * patternLength, 1.0); - double longPatternPenalty = Math.Max(Math.Min(2.5 - 0.15 * patternLength, 1.0), 0.0); - return Math.Min(shortPatternPenalty, longPatternPenalty); - } - - // Penalty for notes so slow that alting is not necessary. - private double speedPenalty(double noteLengthMS) - { - if (noteLengthMS < 80) return 1; - // return Math.Max(0, 1.4 - 0.005 * noteLengthMS); - if (noteLengthMS < 210) return Math.Max(0, 1.4 - 0.005 * noteLengthMS); - if (noteLengthMS < 210) return 0.6; - - currentStrain = 0.0; - return 0.0; - } - - // Penalty for the first rhythm change in a pattern - private const double first_burst_penalty = 0.1; - private bool prevIsSpeedup = true; - - protected override double StrainValueOf(DifficultyHitObject dho) - { - currentStrain *= strain_decay; - - TaikoDifficultyHitObject currentHO = (TaikoDifficultyHitObject)dho; - rhythmLength += 1; - - if (!currentHO.HasTimingChange) - { - return 0.0; - } - - double objectDifficulty = currentHO.Rhythm.Difficulty; - - // find repeated ratios - - ratioObjectHistory.Add(currentHO); - ratioHistoryLength += 1; - - if (ratioHistoryLength > ratio_history_max_length) - { - ratioObjectHistory.RemoveAt(0); - ratioHistoryLength -= 1; - } - - for (int l = 2; l <= ratio_history_max_length / 2; l++) - { - for (int start = ratioHistoryLength - l - 1; start >= 0; start--) - { - bool samePattern = true; - - for (int i = 0; i < l; i++) - { - if (ratioObjectHistory[start + i].RhythmID != ratioObjectHistory[ratioHistoryLength - l + i].RhythmID) - { - samePattern = false; - } - } - - if (samePattern) // Repitition found! - { - int notesSince = currentHO.n - ratioObjectHistory[start].n; - objectDifficulty *= repititionPenalty(notesSince); - break; - } - } - } - - if (currentHO.Rhythm.IsSpeedup()) - { - objectDifficulty *= 1; - if (currentHO.Rhythm.IsLargeSpeedup()) objectDifficulty *= 1; - if (prevIsSpeedup) objectDifficulty *= 1; - - prevIsSpeedup = true; - } - else - { - prevIsSpeedup = false; - } - - objectDifficulty *= patternLengthPenalty(rhythmLength); - objectDifficulty *= speedPenalty(currentHO.NoteLength); - - rhythmLength = 0; - - currentStrain += objectDifficulty; - return currentStrain; - } - } -} diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Stamina.cs b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Stamina.cs index 1ecca886df..29c1c3c322 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Stamina.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Stamina.cs @@ -2,91 +2,78 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using System.Linq; using osu.Game.Rulesets.Difficulty.Preprocessing; using osu.Game.Rulesets.Difficulty.Skills; using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing; +using osu.Game.Rulesets.Taiko.Objects; namespace osu.Game.Rulesets.Taiko.Difficulty.Skills { public class Stamina : Skill { - private int hand; - private int noteNumber = 0; + private readonly int hand; protected override double SkillMultiplier => 1; - protected override double StrainDecayBase => 0.4; - // i only add strain every second note so its kind of like using 0.16 - private readonly int maxHistoryLength = 2; - private List noteDurationHistory = new List(); - - private List lastHitObjects = new List(); + private const int max_history_length = 2; + private readonly List notePairDurationHistory = new List(); private double offhandObjectDuration = double.MaxValue; // Penalty for tl tap or roll - private double cheesePenalty(double last2NoteDuration) + private double cheesePenalty(double notePairDuration) { - if (last2NoteDuration > 125) return 1; - if (last2NoteDuration < 100) return 0.6; + if (notePairDuration > 125) return 1; + if (notePairDuration < 100) return 0.6; - return 0.6 + (last2NoteDuration - 100) * 0.016; + return 0.6 + (notePairDuration - 100) * 0.016; } - private double speedBonus(double last2NoteDuration) + private double speedBonus(double notePairDuration) { - // note that we are only looking at every 2nd note, so a 300bpm stream has a note duration of 100ms. - if (last2NoteDuration >= 200) return 0; - double bonus = 200 - last2NoteDuration; + if (notePairDuration >= 200) return 0; + + double bonus = 200 - notePairDuration; bonus *= bonus; return bonus / 100000; } protected override double StrainValueOf(DifficultyHitObject current) { - noteNumber += 1; - - TaikoDifficultyHitObject currentHO = (TaikoDifficultyHitObject)current; - - if (noteNumber % 2 == hand) + if (!(current.BaseObject is Hit)) { - lastHitObjects.Add(currentHO); - noteDurationHistory.Add(currentHO.NoteLength + offhandObjectDuration); + return 0.0; + } - if (noteNumber == 1) + TaikoDifficultyHitObject hitObject = (TaikoDifficultyHitObject)current; + + if (hitObject.N % 2 == hand) + { + double objectStrain = 1; + + if (hitObject.N == 1) return 1; - if (noteDurationHistory.Count > maxHistoryLength) - noteDurationHistory.RemoveAt(0); + notePairDurationHistory.Add(hitObject.NoteLength + offhandObjectDuration); - double shortestRecentNote = min(noteDurationHistory); - double bonus = 0; - bonus += speedBonus(shortestRecentNote); + if (notePairDurationHistory.Count > max_history_length) + notePairDurationHistory.RemoveAt(0); - double objectStaminaStrain = 1 + bonus; - if (currentHO.StaminaCheese) objectStaminaStrain *= cheesePenalty(currentHO.NoteLength + offhandObjectDuration); + double shortestRecentNote = notePairDurationHistory.Min(); + objectStrain += speedBonus(shortestRecentNote); - return objectStaminaStrain; + if (hitObject.StaminaCheese) + objectStrain *= cheesePenalty(hitObject.NoteLength + offhandObjectDuration); + + return objectStrain; } - offhandObjectDuration = currentHO.NoteLength; + offhandObjectDuration = hitObject.NoteLength; return 0; } - private static double min(List l) - { - double minimum = double.MaxValue; - - foreach (double d in l) - { - if (d < minimum) - minimum = d; - } - - return minimum; - } - public Stamina(bool rightHand) { hand = 0; diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs index dc2b68e0ca..789fd7c63b 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs @@ -20,9 +20,22 @@ namespace osu.Game.Rulesets.Taiko.Difficulty { public class TaikoDifficultyCalculator : DifficultyCalculator { - private const double rhythmSkillMultiplier = 0.15; - private const double colourSkillMultiplier = 0.01; - private const double staminaSkillMultiplier = 0.02; + private const double rhythm_skill_multiplier = 0.014; + private const double colour_skill_multiplier = 0.01; + private const double stamina_skill_multiplier = 0.02; + + private readonly TaikoDifficultyHitObjectRhythm[] commonRhythms = + { + new TaikoDifficultyHitObjectRhythm(1, 1, 0.0, true), + new TaikoDifficultyHitObjectRhythm(2, 1, 0.3, false), + new TaikoDifficultyHitObjectRhythm(1, 2, 0.5, false), + new TaikoDifficultyHitObjectRhythm(3, 1, 0.3, false), + new TaikoDifficultyHitObjectRhythm(1, 3, 0.35, false), + new TaikoDifficultyHitObjectRhythm(3, 2, 0.6, false), + new TaikoDifficultyHitObjectRhythm(2, 3, 0.4, false), + new TaikoDifficultyHitObjectRhythm(5, 4, 0.5, false), + new TaikoDifficultyHitObjectRhythm(4, 5, 0.7, false) + }; public TaikoDifficultyCalculator(Ruleset ruleset, WorkingBeatmap beatmap) : base(ruleset, beatmap) @@ -32,6 +45,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty private double simpleColourPenalty(double staminaDifficulty, double colorDifficulty) { if (colorDifficulty <= 0) return 0.79 - 0.25; + return 0.79 - Math.Atan(staminaDifficulty / colorDifficulty - 12) / Math.PI / 2; } @@ -46,25 +60,22 @@ namespace osu.Game.Rulesets.Taiko.Difficulty private double rescale(double sr) { - if (sr <= 1) return sr; - sr -= 1; - sr = 1.6 * Math.Pow(sr, 0.7); - sr += 1; - return sr; + if (sr < 0) return sr; + + return 10.43 * Math.Log(sr / 8 + 1); } - private double combinedDifficulty(double staminaPenalty, Skill colour, Skill rhythm, Skill stamina1, Skill stamina2) + private double locallyCombinedDifficulty(double staminaPenalty, Skill colour, Skill rhythm, Skill stamina1, Skill stamina2) { - double difficulty = 0; double weight = 1; List peaks = new List(); for (int i = 0; i < colour.StrainPeaks.Count; i++) { - double colourPeak = colour.StrainPeaks[i] * colourSkillMultiplier; - double rhythmPeak = rhythm.StrainPeaks[i] * rhythmSkillMultiplier; - double staminaPeak = (stamina1.StrainPeaks[i] + stamina2.StrainPeaks[i]) * staminaSkillMultiplier * staminaPenalty; + double colourPeak = colour.StrainPeaks[i] * colour_skill_multiplier; + double rhythmPeak = rhythm.StrainPeaks[i] * rhythm_skill_multiplier; + double staminaPeak = (stamina1.StrainPeaks[i] + stamina2.StrainPeaks[i]) * stamina_skill_multiplier * staminaPenalty; peaks.Add(norm(2, colourPeak, rhythmPeak, staminaPeak)); } @@ -82,21 +93,15 @@ namespace osu.Game.Rulesets.Taiko.Difficulty if (beatmap.HitObjects.Count == 0) return new TaikoDifficultyAttributes { Mods = mods, Skills = skills }; - double staminaRating = (skills[2].DifficultyValue() + skills[3].DifficultyValue()) * staminaSkillMultiplier; - double colourRating = skills[0].DifficultyValue() * colourSkillMultiplier; - double rhythmRating = skills[1].DifficultyValue() * rhythmSkillMultiplier; + double colourRating = skills[0].DifficultyValue() * colour_skill_multiplier; + double rhythmRating = skills[1].DifficultyValue() * rhythm_skill_multiplier; + double staminaRating = (skills[2].DifficultyValue() + skills[3].DifficultyValue()) * stamina_skill_multiplier; double staminaPenalty = simpleColourPenalty(staminaRating, colourRating); staminaRating *= staminaPenalty; - double combinedRating = combinedDifficulty(staminaPenalty, skills[0], skills[1], skills[2], skills[3]); - - // Console.WriteLine("colour\t" + colourRating); - // Console.WriteLine("rhythm\t" + rhythmRating); - // Console.WriteLine("stamina\t" + staminaRating); + double combinedRating = locallyCombinedDifficulty(staminaPenalty, skills[0], skills[1], skills[2], skills[3]); double separatedRating = norm(1.5, colourRating, rhythmRating, staminaRating); - // Console.WriteLine("combinedRating\t" + combinedRating); - // Console.WriteLine("separatedRating\t" + separatedRating); double starRating = 1.4 * separatedRating + 0.5 * combinedRating; starRating = rescale(starRating); @@ -111,7 +116,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty RhythmStrain = rhythmRating, ColourStrain = colourRating, // Todo: This int cast is temporary to achieve 1:1 results with osu!stable, and should be removed in the future - GreatHitWindow = (int)(hitWindows.WindowFor(HitResult.Great)) / clockRate, + GreatHitWindow = (int)hitWindows.WindowFor(HitResult.Great) / clockRate, MaxCombo = beatmap.HitObjects.Count(h => h is Hit), Skills = skills }; @@ -120,18 +125,23 @@ namespace osu.Game.Rulesets.Taiko.Difficulty protected override IEnumerable CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate) { List taikoDifficultyHitObjects = new List(); - var rhythm = new TaikoDifficultyHitObjectRhythm(); for (int i = 2; i < beatmap.HitObjects.Count; i++) { // Check for negative durations if (beatmap.HitObjects[i].StartTime > beatmap.HitObjects[i - 1].StartTime && beatmap.HitObjects[i - 1].StartTime > beatmap.HitObjects[i - 2].StartTime) - taikoDifficultyHitObjects.Add(new TaikoDifficultyHitObject(beatmap.HitObjects[i], beatmap.HitObjects[i - 1], beatmap.HitObjects[i - 2], clockRate, rhythm)); + { + taikoDifficultyHitObjects.Add( + new TaikoDifficultyHitObject( + beatmap.HitObjects[i], beatmap.HitObjects[i - 1], beatmap.HitObjects[i - 2], clockRate, i, commonRhythms + ) + ); + } } new StaminaCheeseDetector().FindCheese(taikoDifficultyHitObjects); - for (int i = 0; i < taikoDifficultyHitObjects.Count; i++) - yield return taikoDifficultyHitObjects[i]; + foreach (var hitobject in taikoDifficultyHitObjects) + yield return hitobject; } protected override Skill[] CreateSkills(IBeatmap beatmap) => new Skill[] @@ -149,10 +159,5 @@ namespace osu.Game.Rulesets.Taiko.Difficulty new TaikoModEasy(), new TaikoModHardRock(), }; - - /* - protected override DifficultyAttributes VirtualCalculate(IBeatmap beatmap, Mod[] mods, double clockRate) - => taikoCalculate(beatmap, mods, clockRate); - */ } } diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs index 9585a6a369..e6dd9f5084 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs @@ -78,10 +78,6 @@ namespace osu.Game.Rulesets.Taiko.Difficulty // Penalize misses exponentially. This mainly fixes tag4 maps and the likes until a per-hitobject solution is available strainValue *= Math.Pow(0.985, countMiss); - // Combo scaling - if (Attributes.MaxCombo > 0) - strainValue *= Math.Min(Math.Pow(Score.MaxCombo, 0.5) / Math.Pow(Attributes.MaxCombo, 0.5), 1.0); - if (mods.Any(m => m is ModHidden)) strainValue *= 1.025; From ce66b723908356476fdce35691e390a1e3a8b2b5 Mon Sep 17 00:00:00 2001 From: Shivam Date: Mon, 8 Jun 2020 18:25:20 +0200 Subject: [PATCH 0045/1782] Refactor paths --- osu.Game.Tournament/TournamentStorage.cs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tournament/TournamentStorage.cs b/osu.Game.Tournament/TournamentStorage.cs index d1c8635466..32dd904b2f 100644 --- a/osu.Game.Tournament/TournamentStorage.cs +++ b/osu.Game.Tournament/TournamentStorage.cs @@ -31,7 +31,7 @@ namespace osu.Game.Tournament public readonly TournamentVideoStorage VideoStorage; public TournamentStorage(GameHost host) - : base(host.Storage, string.Empty) + : base(host.Storage.GetStorageForDirectory("tournaments"), string.Empty) { this.host = host; @@ -40,11 +40,10 @@ namespace osu.Game.Tournament if (!string.IsNullOrEmpty(currentTournament)) { - ChangeTargetStorage(UnderlyingStorage.GetStorageForDirectory("tournaments" + Path.DirectorySeparatorChar + currentTournament)); + ChangeTargetStorage(UnderlyingStorage.GetStorageForDirectory(currentTournament)); } else { - // Migrating old storage format to the new one. migrate(); Logger.Log("Migrating files from old storage to new."); } @@ -55,8 +54,8 @@ namespace osu.Game.Tournament private void migrate() { - const string default_path = "tournaments/default"; - var source = new DirectoryInfo(GetFullPath("tournament")); + const string default_path = "default"; + var source = new DirectoryInfo(host.Storage.GetFullPath("tournament")); var destination = new DirectoryInfo(GetFullPath(default_path)); Directory.CreateDirectory(destination.FullName); @@ -64,7 +63,7 @@ namespace osu.Game.Tournament if (host.Storage.Exists("bracket.json")) { Logger.Log("Migrating bracket to default tournament storage."); - var bracketFile = new System.IO.FileInfo(GetFullPath(string.Empty) + Path.DirectorySeparatorChar + GetFiles(string.Empty, "bracket.json").First()); + var bracketFile = new System.IO.FileInfo(host.Storage.GetFullPath("bracket.json")); attemptOperation(() => bracketFile.CopyTo(Path.Combine(destination.FullName, bracketFile.Name), true)); } From d2ae146c1ffb56dcb6eababc9df624008d50a589 Mon Sep 17 00:00:00 2001 From: Shivam Date: Mon, 8 Jun 2020 19:51:44 +0200 Subject: [PATCH 0046/1782] Remove unnecessary parameters and implement delete --- osu.Game.Tournament/TournamentStorage.cs | 42 ++++++++++++++++-------- 1 file changed, 29 insertions(+), 13 deletions(-) diff --git a/osu.Game.Tournament/TournamentStorage.cs b/osu.Game.Tournament/TournamentStorage.cs index 32dd904b2f..87a2604d0b 100644 --- a/osu.Game.Tournament/TournamentStorage.cs +++ b/osu.Game.Tournament/TournamentStorage.cs @@ -13,17 +13,6 @@ using osu.Game.Tournament.Configuration; namespace osu.Game.Tournament { - internal class TournamentVideoStorage : NamespacedResourceStore - { - public TournamentVideoStorage(Storage storage) - : base(new StorageBackedResourceStore(storage), "videos") - { - AddExtension("m4v"); - AddExtension("avi"); - AddExtension("mp4"); - } - } - internal class TournamentStorage : WrappedStorage { private readonly GameHost host; @@ -72,9 +61,10 @@ namespace osu.Game.Tournament ChangeTargetStorage(UnderlyingStorage.GetStorageForDirectory(default_path)); storageConfig.Set(StorageConfig.CurrentTournament, default_path); storageConfig.Save(); + deleteRecursive(source); } - private void copyRecursive(DirectoryInfo source, DirectoryInfo destination, bool topLevelExcludes = true) + private void copyRecursive(DirectoryInfo source, DirectoryInfo destination) { // based off example code https://docs.microsoft.com/en-us/dotnet/api/system.io.directoryinfo @@ -85,10 +75,26 @@ namespace osu.Game.Tournament foreach (DirectoryInfo dir in source.GetDirectories()) { - copyRecursive(dir, destination.CreateSubdirectory(dir.Name), false); + copyRecursive(dir, destination.CreateSubdirectory(dir.Name)); } } + private void deleteRecursive(DirectoryInfo target) + { + foreach (System.IO.FileInfo fi in target.GetFiles()) + { + attemptOperation(() => fi.Delete()); + } + + foreach (DirectoryInfo dir in target.GetDirectories()) + { + attemptOperation(() => dir.Delete(true)); + } + + if (target.GetFiles().Length == 0 && target.GetDirectories().Length == 0) + attemptOperation(target.Delete); + } + private void attemptOperation(Action action, int attempts = 10) { while (true) @@ -108,4 +114,14 @@ namespace osu.Game.Tournament } } } + internal class TournamentVideoStorage : NamespacedResourceStore + { + public TournamentVideoStorage(Storage storage) + : base(new StorageBackedResourceStore(storage), "videos") + { + AddExtension("m4v"); + AddExtension("avi"); + AddExtension("mp4"); + } + } } From 2f15d7fbac96b253cda0b9c1156e660c9c40dee6 Mon Sep 17 00:00:00 2001 From: Shivam Date: Mon, 8 Jun 2020 20:04:38 +0200 Subject: [PATCH 0047/1782] Code styling fixes --- osu.Game.Tournament/TournamentStorage.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tournament/TournamentStorage.cs b/osu.Game.Tournament/TournamentStorage.cs index 87a2604d0b..49f3d69be1 100644 --- a/osu.Game.Tournament/TournamentStorage.cs +++ b/osu.Game.Tournament/TournamentStorage.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Linq; using System.Threading; using osu.Framework.IO.Stores; using osu.Framework.Logging; @@ -114,6 +113,7 @@ namespace osu.Game.Tournament } } } + internal class TournamentVideoStorage : NamespacedResourceStore { public TournamentVideoStorage(Storage storage) From 417919320cfa8400146832ca44bcce33ca660e8b Mon Sep 17 00:00:00 2001 From: Shivam Date: Tue, 9 Jun 2020 17:28:42 +0200 Subject: [PATCH 0048/1782] change namespace to osu.Game.Tournament.IO --- osu.Game.Tournament/Components/TourneyVideo.cs | 1 + osu.Game.Tournament/{ => IO}/TournamentStorage.cs | 2 +- osu.Game.Tournament/TournamentGameBase.cs | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) rename osu.Game.Tournament/{ => IO}/TournamentStorage.cs (99%) diff --git a/osu.Game.Tournament/Components/TourneyVideo.cs b/osu.Game.Tournament/Components/TourneyVideo.cs index 131fa9450d..dcb08464dd 100644 --- a/osu.Game.Tournament/Components/TourneyVideo.cs +++ b/osu.Game.Tournament/Components/TourneyVideo.cs @@ -9,6 +9,7 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Video; using osu.Framework.Timing; using osu.Game.Graphics; +using osu.Game.Tournament.IO; namespace osu.Game.Tournament.Components { diff --git a/osu.Game.Tournament/TournamentStorage.cs b/osu.Game.Tournament/IO/TournamentStorage.cs similarity index 99% rename from osu.Game.Tournament/TournamentStorage.cs rename to osu.Game.Tournament/IO/TournamentStorage.cs index 49f3d69be1..7690051c7a 100644 --- a/osu.Game.Tournament/TournamentStorage.cs +++ b/osu.Game.Tournament/IO/TournamentStorage.cs @@ -10,7 +10,7 @@ using osu.Game.IO; using System.IO; using osu.Game.Tournament.Configuration; -namespace osu.Game.Tournament +namespace osu.Game.Tournament.IO { internal class TournamentStorage : WrappedStorage { diff --git a/osu.Game.Tournament/TournamentGameBase.cs b/osu.Game.Tournament/TournamentGameBase.cs index e3d310a497..ccfbf37d48 100644 --- a/osu.Game.Tournament/TournamentGameBase.cs +++ b/osu.Game.Tournament/TournamentGameBase.cs @@ -20,6 +20,7 @@ using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Online.API.Requests; using osu.Game.Tournament.IPC; +using osu.Game.Tournament.IO; using osu.Game.Tournament.Models; using osu.Game.Users; using osuTK; From c2e01e198f1da6682bd8d1601ffa207509ee0dfc Mon Sep 17 00:00:00 2001 From: Shivam Date: Thu, 11 Jun 2020 13:55:29 +0200 Subject: [PATCH 0049/1782] Rename tournamentStorage to storage --- osu.Game.Tournament/TournamentGameBase.cs | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/osu.Game.Tournament/TournamentGameBase.cs b/osu.Game.Tournament/TournamentGameBase.cs index ccfbf37d48..4c0c8cc28f 100644 --- a/osu.Game.Tournament/TournamentGameBase.cs +++ b/osu.Game.Tournament/TournamentGameBase.cs @@ -15,7 +15,6 @@ using osu.Framework.Graphics.Shapes; 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.Online.API.Requests; @@ -36,9 +35,7 @@ namespace osu.Game.Tournament private LadderInfo ladder; - private Storage storage; - - private TournamentStorage tournamentStorage; + private TournamentStorage storage; private DependencyContainer dependencies; @@ -57,11 +54,9 @@ namespace osu.Game.Tournament { Resources.AddStore(new DllResourceStore(typeof(TournamentGameBase).Assembly)); - dependencies.CacheAs(tournamentStorage = new TournamentStorage(Host)); + dependencies.CacheAs(storage = new TournamentStorage(Host)); - Textures.AddStore(new TextureLoaderStore(tournamentStorage.VideoStorage)); - - storage = tournamentStorage; + Textures.AddStore(new TextureLoaderStore(storage.VideoStorage)); windowSize = frameworkConfig.GetBindable(FrameworkSetting.WindowedSize); windowSize.BindValueChanged(size => ScheduleAfterChildren(() => From b69ff307d83beb9aa40d81e00b8d8c16ab0b5b88 Mon Sep 17 00:00:00 2001 From: Shivam Date: Thu, 11 Jun 2020 13:56:16 +0200 Subject: [PATCH 0050/1782] Fixed migration logic --- osu.Game.Tournament/IO/TournamentStorage.cs | 33 ++++++++++++--------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/osu.Game.Tournament/IO/TournamentStorage.cs b/osu.Game.Tournament/IO/TournamentStorage.cs index 7690051c7a..a0f07c354b 100644 --- a/osu.Game.Tournament/IO/TournamentStorage.cs +++ b/osu.Game.Tournament/IO/TournamentStorage.cs @@ -15,15 +15,16 @@ namespace osu.Game.Tournament.IO internal class TournamentStorage : WrappedStorage { private readonly GameHost host; - private readonly TournamentStorageManager storageConfig; public readonly TournamentVideoStorage VideoStorage; + private const string default_tournament = "default"; public TournamentStorage(GameHost host) : base(host.Storage.GetStorageForDirectory("tournaments"), string.Empty) { this.host = host; - storageConfig = new TournamentStorageManager(host.Storage); + TournamentStorageManager storageConfig = new TournamentStorageManager(host.Storage); + var currentTournament = storageConfig.Get(StorageConfig.CurrentTournament); if (!string.IsNullOrEmpty(currentTournament)) @@ -32,35 +33,39 @@ namespace osu.Game.Tournament.IO } else { - migrate(); Logger.Log("Migrating files from old storage to new."); + Migrate(); + storageConfig.Set(StorageConfig.CurrentTournament, default_tournament); + storageConfig.Save(); + ChangeTargetStorage(UnderlyingStorage.GetStorageForDirectory(default_tournament)); } VideoStorage = new TournamentVideoStorage(this); Logger.Log("Using tournament storage: " + GetFullPath(string.Empty)); } - private void migrate() + internal void Migrate() { - const string default_path = "default"; var source = new DirectoryInfo(host.Storage.GetFullPath("tournament")); - var destination = new DirectoryInfo(GetFullPath(default_path)); + var destination = new DirectoryInfo(GetFullPath(default_tournament)); - Directory.CreateDirectory(destination.FullName); + if (!destination.Exists) + destination.Create(); if (host.Storage.Exists("bracket.json")) { Logger.Log("Migrating bracket to default tournament storage."); var bracketFile = new System.IO.FileInfo(host.Storage.GetFullPath("bracket.json")); attemptOperation(() => bracketFile.CopyTo(Path.Combine(destination.FullName, bracketFile.Name), true)); + bracketFile.Delete(); } - Logger.Log("Migrating other assets to default tournament storage."); - copyRecursive(source, destination); - ChangeTargetStorage(UnderlyingStorage.GetStorageForDirectory(default_path)); - storageConfig.Set(StorageConfig.CurrentTournament, default_path); - storageConfig.Save(); - deleteRecursive(source); + if (source.Exists) + { + Logger.Log("Migrating tournament assets to default tournament storage."); + copyRecursive(source, destination); + deleteRecursive(source); + } } private void copyRecursive(DirectoryInfo source, DirectoryInfo destination) @@ -113,7 +118,7 @@ namespace osu.Game.Tournament.IO } } } - + internal class TournamentVideoStorage : NamespacedResourceStore { public TournamentVideoStorage(Storage storage) From 18a9e5a0a6e52069dba001144f26436c8e240d4a Mon Sep 17 00:00:00 2001 From: Shivam Date: Thu, 11 Jun 2020 13:57:29 +0200 Subject: [PATCH 0051/1782] Add NonVisual tests for custom tournaments Can test the default directory from a clean instance, it can test a custom directory and can execute migration from an instance using the older directory setup. --- .../NonVisual/CustomTourneyDirectoryTest.cs | 157 ++++++++++++++++++ 1 file changed, 157 insertions(+) create mode 100644 osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs diff --git a/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs b/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs new file mode 100644 index 0000000000..757465d4ad --- /dev/null +++ b/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.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.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using NUnit.Framework; +using osu.Framework; +using osu.Framework.Allocation; +using osu.Framework.Platform; +using osu.Game.Tournament.Configuration; +using osu.Game.Tournament.IO; +using osu.Game.Tests; + +namespace osu.Game.Tournament.Tests.NonVisual +{ + [TestFixture] + public class CustomTourneyDirectoryTest + { + [SetUp] + public void SetUp() + { + } + + [Test] + public void TestDefaultDirectory() + { + using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestDefaultDirectory))) + { + try + { + var osu = loadOsu(host); + var storage = osu.Dependencies.Get(); + var defaultStorage = Path.Combine(RuntimeInfo.StartupDirectory, "headless", nameof(TestDefaultDirectory), "tournaments", "default"); + Assert.That(storage.GetFullPath("."), Is.EqualTo(defaultStorage)); + } + finally + { + host.Exit(); + } + } + } + + [Test] + public void TestCustomDirectory() + { + using (HeadlessGameHost host = new HeadlessGameHost(nameof(TestCustomDirectory))) + { + string osuDesktopStorage = Path.Combine(RuntimeInfo.StartupDirectory, "headless", nameof(TestCustomDirectory)); + const string custom_tournament = "custom"; + + // need access before the game has constructed its own storage yet. + Storage storage = new DesktopStorage(osuDesktopStorage, host); + // manual cleaning so we can prepare a config file. + storage.DeleteDirectory(string.Empty); + + using (var storageConfig = new TournamentStorageManager(storage)) + storageConfig.Set(StorageConfig.CurrentTournament, custom_tournament); + + try + { + var osu = loadOsu(host); + + storage = osu.Dependencies.Get(); + + Assert.That(storage.GetFullPath("."), Is.EqualTo(Path.Combine(tournamentBasePath(nameof(TestCustomDirectory)), "custom"))); + } + finally + { + host.Exit(); + } + } + } + + [Test] + public void TestMigration() + { + using (HeadlessGameHost host = new HeadlessGameHost(nameof(TestMigration))) + { + string basePath = Path.Combine(RuntimeInfo.StartupDirectory, "headless", nameof(TestMigration), "tournament"); + + string videosPath = Path.Combine(basePath, "videos"); + string modsPath = Path.Combine(basePath, "mods"); + string flagsPath = Path.Combine(basePath, "flags"); + + Directory.CreateDirectory(videosPath); + Directory.CreateDirectory(modsPath); + Directory.CreateDirectory(flagsPath); + + string bracketFile = Path.Combine(RuntimeInfo.StartupDirectory, "headless", nameof(TestMigration), "bracket.json"); + string videoFile = Path.Combine(videosPath, "video.mp4"); + string modFile = Path.Combine(modsPath, "mod.png"); + string flagFile = Path.Combine(flagsPath, "flag.png"); + + File.WriteAllText(bracketFile, "{}"); + + File.WriteAllText(videoFile, "test"); + File.WriteAllText(modFile, "test"); + File.WriteAllText(flagFile, "test"); + + try + { + var osu = loadOsu(host); + + var storage = osu.Dependencies.Get(); + + var migratedPath = Path.Combine(RuntimeInfo.StartupDirectory, "headless", nameof(TestMigration), "tournaments", "default"); + + videosPath = Path.Combine(migratedPath, "videos"); + modsPath = Path.Combine(migratedPath, "mods"); + flagsPath = Path.Combine(migratedPath, "flags"); + + videoFile = Path.Combine(videosPath, "video.mp4"); + modFile = Path.Combine(modsPath, "mod.png"); + flagFile = Path.Combine(flagsPath, "flag.png"); + + Assert.That(storage.GetFullPath("."), Is.EqualTo(migratedPath)); + Assert.That(storage.GetFiles(".", "bracket.json").Single(), Is.EqualTo("bracket.json")); + Assert.True(storage.Exists(videoFile)); + Assert.True(storage.Exists(modFile)); + Assert.True(storage.Exists(flagFile)); + } + finally + { + // Cleaning up after ourselves. + host.Storage.Delete("tournament.ini"); + host.Storage.DeleteDirectory("tournaments"); + + host.Exit(); + } + } + } + + private TournamentGameBase loadOsu(GameHost host) + { + var osu = new TournamentGameBase(); + Task.Run(() => host.Run(osu)); + waitForOrAssert(() => osu.IsLoaded, @"osu! failed to start in a reasonable amount of time"); + return osu; + } + + private static void waitForOrAssert(Func result, string failureMessage, int timeout = 90000) + { + Task task = Task.Run(() => + { + while (!result()) Thread.Sleep(200); + }); + + Assert.IsTrue(task.Wait(timeout), failureMessage); + } + + private string oldPath => Path.Combine(RuntimeInfo.StartupDirectory, "tournament"); + private string tournamentBasePath(string testInstance) => Path.Combine(RuntimeInfo.StartupDirectory, "headless", testInstance, "tournaments"); + } +} \ No newline at end of file From a317b85fd823f1988a1514b93b99e59c866ea8ff Mon Sep 17 00:00:00 2001 From: Shivam Date: Thu, 11 Jun 2020 14:06:03 +0200 Subject: [PATCH 0052/1782] Remove misleading log --- osu.Game.Tournament/IO/TournamentStorage.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Tournament/IO/TournamentStorage.cs b/osu.Game.Tournament/IO/TournamentStorage.cs index a0f07c354b..2379967125 100644 --- a/osu.Game.Tournament/IO/TournamentStorage.cs +++ b/osu.Game.Tournament/IO/TournamentStorage.cs @@ -33,7 +33,6 @@ namespace osu.Game.Tournament.IO } else { - Logger.Log("Migrating files from old storage to new."); Migrate(); storageConfig.Set(StorageConfig.CurrentTournament, default_tournament); storageConfig.Save(); From 5d49b709b99f5a8d1da59cfc76e5025423cd5977 Mon Sep 17 00:00:00 2001 From: Shivam Date: Thu, 11 Jun 2020 14:09:21 +0200 Subject: [PATCH 0053/1782] Change access modifier public -> internal --- osu.Game.Tournament/IO/TournamentStorage.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tournament/IO/TournamentStorage.cs b/osu.Game.Tournament/IO/TournamentStorage.cs index 2379967125..c6f314032f 100644 --- a/osu.Game.Tournament/IO/TournamentStorage.cs +++ b/osu.Game.Tournament/IO/TournamentStorage.cs @@ -15,7 +15,7 @@ namespace osu.Game.Tournament.IO internal class TournamentStorage : WrappedStorage { private readonly GameHost host; - public readonly TournamentVideoStorage VideoStorage; + internal readonly TournamentVideoStorage VideoStorage; private const string default_tournament = "default"; public TournamentStorage(GameHost host) From 2964b457a01894c559c329d9ea98f895e66a9b55 Mon Sep 17 00:00:00 2001 From: Shivam Date: Thu, 11 Jun 2020 15:05:28 +0200 Subject: [PATCH 0054/1782] Rename VideoStorage to VideoStore --- osu.Game.Tournament/Components/TourneyVideo.cs | 2 +- osu.Game.Tournament/IO/TournamentStorage.cs | 8 ++++---- osu.Game.Tournament/TournamentGameBase.cs | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tournament/Components/TourneyVideo.cs b/osu.Game.Tournament/Components/TourneyVideo.cs index dcb08464dd..5a595f4f44 100644 --- a/osu.Game.Tournament/Components/TourneyVideo.cs +++ b/osu.Game.Tournament/Components/TourneyVideo.cs @@ -30,7 +30,7 @@ namespace osu.Game.Tournament.Components [BackgroundDependencyLoader] private void load(TournamentStorage storage) { - var stream = storage.VideoStorage.GetStream($@"{filename}"); + var stream = storage.VideoStore.GetStream($@"{filename}"); if (stream != null) { diff --git a/osu.Game.Tournament/IO/TournamentStorage.cs b/osu.Game.Tournament/IO/TournamentStorage.cs index c6f314032f..2eb052a2e3 100644 --- a/osu.Game.Tournament/IO/TournamentStorage.cs +++ b/osu.Game.Tournament/IO/TournamentStorage.cs @@ -15,7 +15,7 @@ namespace osu.Game.Tournament.IO internal class TournamentStorage : WrappedStorage { private readonly GameHost host; - internal readonly TournamentVideoStorage VideoStorage; + internal readonly TournamentVideoResourceStore VideoStore; private const string default_tournament = "default"; public TournamentStorage(GameHost host) @@ -39,7 +39,7 @@ namespace osu.Game.Tournament.IO ChangeTargetStorage(UnderlyingStorage.GetStorageForDirectory(default_tournament)); } - VideoStorage = new TournamentVideoStorage(this); + VideoStore = new TournamentVideoResourceStore(this); Logger.Log("Using tournament storage: " + GetFullPath(string.Empty)); } @@ -118,9 +118,9 @@ namespace osu.Game.Tournament.IO } } - internal class TournamentVideoStorage : NamespacedResourceStore + internal class TournamentVideoResourceStore : NamespacedResourceStore { - public TournamentVideoStorage(Storage storage) + public TournamentVideoResourceStore(Storage storage) : base(new StorageBackedResourceStore(storage), "videos") { AddExtension("m4v"); diff --git a/osu.Game.Tournament/TournamentGameBase.cs b/osu.Game.Tournament/TournamentGameBase.cs index 4c0c8cc28f..9716f0cd5f 100644 --- a/osu.Game.Tournament/TournamentGameBase.cs +++ b/osu.Game.Tournament/TournamentGameBase.cs @@ -56,7 +56,7 @@ namespace osu.Game.Tournament dependencies.CacheAs(storage = new TournamentStorage(Host)); - Textures.AddStore(new TextureLoaderStore(storage.VideoStorage)); + Textures.AddStore(new TextureLoaderStore(storage.VideoStore)); windowSize = frameworkConfig.GetBindable(FrameworkSetting.WindowedSize); windowSize.BindValueChanged(size => ScheduleAfterChildren(() => From af1bbe78578f7388c7af7b08e42969038d9333e1 Mon Sep 17 00:00:00 2001 From: Shivam Date: Thu, 11 Jun 2020 15:13:19 +0200 Subject: [PATCH 0055/1782] move TournamentVideoResourceStore to separate file --- osu.Game.Tournament/IO/TournamentStorage.cs | 12 ------------ .../IO/TournamentVideoResourceStore.cs | 19 +++++++++++++++++++ 2 files changed, 19 insertions(+), 12 deletions(-) create mode 100644 osu.Game.Tournament/IO/TournamentVideoResourceStore.cs diff --git a/osu.Game.Tournament/IO/TournamentStorage.cs b/osu.Game.Tournament/IO/TournamentStorage.cs index 2eb052a2e3..ab7a5f63d2 100644 --- a/osu.Game.Tournament/IO/TournamentStorage.cs +++ b/osu.Game.Tournament/IO/TournamentStorage.cs @@ -3,7 +3,6 @@ using System; using System.Threading; -using osu.Framework.IO.Stores; using osu.Framework.Logging; using osu.Framework.Platform; using osu.Game.IO; @@ -117,15 +116,4 @@ namespace osu.Game.Tournament.IO } } } - - internal class TournamentVideoResourceStore : NamespacedResourceStore - { - public TournamentVideoResourceStore(Storage storage) - : base(new StorageBackedResourceStore(storage), "videos") - { - AddExtension("m4v"); - AddExtension("avi"); - AddExtension("mp4"); - } - } } diff --git a/osu.Game.Tournament/IO/TournamentVideoResourceStore.cs b/osu.Game.Tournament/IO/TournamentVideoResourceStore.cs new file mode 100644 index 0000000000..6a44240f65 --- /dev/null +++ b/osu.Game.Tournament/IO/TournamentVideoResourceStore.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.IO.Stores; +using osu.Framework.Platform; + +namespace osu.Game.Tournament.IO +{ + internal class TournamentVideoResourceStore : NamespacedResourceStore + { + public TournamentVideoResourceStore(Storage storage) + : base(new StorageBackedResourceStore(storage), "videos") + { + AddExtension("m4v"); + AddExtension("avi"); + AddExtension("mp4"); + } + } +} \ No newline at end of file From 883185d3497832ab7309bb566f31010c8ef9a883 Mon Sep 17 00:00:00 2001 From: Shivam Date: Thu, 11 Jun 2020 15:18:21 +0200 Subject: [PATCH 0056/1782] Add a comment to describe what's going on before the headless game starts --- .../NonVisual/CustomTourneyDirectoryTest.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs b/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs index 757465d4ad..867851e06b 100644 --- a/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs +++ b/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs @@ -79,6 +79,7 @@ namespace osu.Game.Tournament.Tests.NonVisual { using (HeadlessGameHost host = new HeadlessGameHost(nameof(TestMigration))) { + // Recreate the old setup that uses "tournament" as the base path. string basePath = Path.Combine(RuntimeInfo.StartupDirectory, "headless", nameof(TestMigration), "tournament"); string videosPath = Path.Combine(basePath, "videos"); From 603054f5214beb6b4ae744d62e7b61c5a03e8f14 Mon Sep 17 00:00:00 2001 From: Shivam Date: Thu, 11 Jun 2020 15:47:21 +0200 Subject: [PATCH 0057/1782] Remove unused property and reuse tournamentBasePath --- .../NonVisual/CustomTourneyDirectoryTest.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs b/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs index 867851e06b..7ac4e51711 100644 --- a/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs +++ b/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs @@ -33,7 +33,7 @@ namespace osu.Game.Tournament.Tests.NonVisual { var osu = loadOsu(host); var storage = osu.Dependencies.Get(); - var defaultStorage = Path.Combine(RuntimeInfo.StartupDirectory, "headless", nameof(TestDefaultDirectory), "tournaments", "default"); + var defaultStorage = Path.Combine(tournamentBasePath(nameof(TestDefaultDirectory)), "default"); Assert.That(storage.GetFullPath("."), Is.EqualTo(defaultStorage)); } finally @@ -107,7 +107,7 @@ namespace osu.Game.Tournament.Tests.NonVisual var storage = osu.Dependencies.Get(); - var migratedPath = Path.Combine(RuntimeInfo.StartupDirectory, "headless", nameof(TestMigration), "tournaments", "default"); + var migratedPath = Path.Combine(tournamentBasePath(nameof(TestMigration)), "default"); videosPath = Path.Combine(migratedPath, "videos"); modsPath = Path.Combine(migratedPath, "mods"); @@ -151,8 +151,6 @@ namespace osu.Game.Tournament.Tests.NonVisual Assert.IsTrue(task.Wait(timeout), failureMessage); } - - private string oldPath => Path.Combine(RuntimeInfo.StartupDirectory, "tournament"); private string tournamentBasePath(string testInstance) => Path.Combine(RuntimeInfo.StartupDirectory, "headless", testInstance, "tournaments"); } } \ No newline at end of file From 222ac863042e8367b8e4a582eabafb453a3a0531 Mon Sep 17 00:00:00 2001 From: Shivam Date: Thu, 11 Jun 2020 15:52:14 +0200 Subject: [PATCH 0058/1782] Add newlines at the end of the file --- osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs | 1 + .../NonVisual/CustomTourneyDirectoryTest.cs | 2 +- osu.Game.Tournament/Configuration/TournamentStorageManager.cs | 2 +- osu.Game.Tournament/IO/TournamentVideoResourceStore.cs | 2 +- 4 files changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs b/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs index f3d54d876a..5f0ca303e3 100644 --- a/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs +++ b/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs @@ -293,3 +293,4 @@ namespace osu.Game.Tests.NonVisual } } } + diff --git a/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs b/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs index 7ac4e51711..851efb9a3d 100644 --- a/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs +++ b/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs @@ -153,4 +153,4 @@ namespace osu.Game.Tournament.Tests.NonVisual } private string tournamentBasePath(string testInstance) => Path.Combine(RuntimeInfo.StartupDirectory, "headless", testInstance, "tournaments"); } -} \ No newline at end of file +} diff --git a/osu.Game.Tournament/Configuration/TournamentStorageManager.cs b/osu.Game.Tournament/Configuration/TournamentStorageManager.cs index 6ccc2b6308..653ea14352 100644 --- a/osu.Game.Tournament/Configuration/TournamentStorageManager.cs +++ b/osu.Game.Tournament/Configuration/TournamentStorageManager.cs @@ -26,4 +26,4 @@ namespace osu.Game.Tournament.Configuration { CurrentTournament, } -} \ No newline at end of file +} diff --git a/osu.Game.Tournament/IO/TournamentVideoResourceStore.cs b/osu.Game.Tournament/IO/TournamentVideoResourceStore.cs index 6a44240f65..1ccd20fe21 100644 --- a/osu.Game.Tournament/IO/TournamentVideoResourceStore.cs +++ b/osu.Game.Tournament/IO/TournamentVideoResourceStore.cs @@ -16,4 +16,4 @@ namespace osu.Game.Tournament.IO AddExtension("mp4"); } } -} \ No newline at end of file +} From 1d4d749b539490f295036c00aa9740fb6d2403f4 Mon Sep 17 00:00:00 2001 From: Shivam Date: Thu, 11 Jun 2020 15:56:34 +0200 Subject: [PATCH 0059/1782] Undo blank line removal Was too excited to add blank lines before submitting the PR that I overdid it --- osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs b/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs index 5f0ca303e3..f3d54d876a 100644 --- a/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs +++ b/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs @@ -293,4 +293,3 @@ namespace osu.Game.Tests.NonVisual } } } - From c9dc17f3d8868679bb99b37ea425dff4624626eb Mon Sep 17 00:00:00 2001 From: Shivam Date: Thu, 11 Jun 2020 17:51:07 +0200 Subject: [PATCH 0060/1782] Introduce migrations for drawings --- osu.Game.Tournament/IO/TournamentStorage.cs | 23 +++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tournament/IO/TournamentStorage.cs b/osu.Game.Tournament/IO/TournamentStorage.cs index ab7a5f63d2..0879d27aac 100644 --- a/osu.Game.Tournament/IO/TournamentStorage.cs +++ b/osu.Game.Tournament/IO/TournamentStorage.cs @@ -54,8 +54,21 @@ namespace osu.Game.Tournament.IO { Logger.Log("Migrating bracket to default tournament storage."); var bracketFile = new System.IO.FileInfo(host.Storage.GetFullPath("bracket.json")); - attemptOperation(() => bracketFile.CopyTo(Path.Combine(destination.FullName, bracketFile.Name), true)); - bracketFile.Delete(); + moveFile(bracketFile, destination); + } + + if (host.Storage.Exists("drawings.txt")) + { + Logger.Log("Migrating drawings to default tournament storage."); + var drawingsFile = new System.IO.FileInfo(host.Storage.GetFullPath("drawings.txt")); + moveFile(drawingsFile, destination); + } + + if (host.Storage.Exists("drawings_results.txt")) + { + Logger.Log("Migrating drawings results to default tournament storage."); + var drawingsResultsFile = new System.IO.FileInfo(host.Storage.GetFullPath("drawings_results.txt")); + moveFile(drawingsResultsFile, destination); } if (source.Exists) @@ -97,6 +110,12 @@ namespace osu.Game.Tournament.IO attemptOperation(target.Delete); } + private void moveFile(System.IO.FileInfo file, DirectoryInfo destination) + { + attemptOperation(() => file.CopyTo(Path.Combine(destination.FullName, file.Name), true)); + file.Delete(); + } + private void attemptOperation(Action action, int attempts = 10) { while (true) From 327795ba9933f8bcf7447c1b4f62ebfa40e763e0 Mon Sep 17 00:00:00 2001 From: Shivam Date: Thu, 11 Jun 2020 18:00:47 +0200 Subject: [PATCH 0061/1782] Switch drawing storage to tournamentstorage --- osu.Game.Tournament/IO/TournamentStorage.cs | 2 +- .../Screens/Drawings/Components/StorageBackedTeamList.cs | 6 +++--- osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tournament/IO/TournamentStorage.cs b/osu.Game.Tournament/IO/TournamentStorage.cs index 0879d27aac..195448f2d5 100644 --- a/osu.Game.Tournament/IO/TournamentStorage.cs +++ b/osu.Game.Tournament/IO/TournamentStorage.cs @@ -11,7 +11,7 @@ using osu.Game.Tournament.Configuration; namespace osu.Game.Tournament.IO { - internal class TournamentStorage : WrappedStorage + public class TournamentStorage : WrappedStorage { private readonly GameHost host; internal readonly TournamentVideoResourceStore VideoStore; diff --git a/osu.Game.Tournament/Screens/Drawings/Components/StorageBackedTeamList.cs b/osu.Game.Tournament/Screens/Drawings/Components/StorageBackedTeamList.cs index f96ec01cbb..ecc23181be 100644 --- a/osu.Game.Tournament/Screens/Drawings/Components/StorageBackedTeamList.cs +++ b/osu.Game.Tournament/Screens/Drawings/Components/StorageBackedTeamList.cs @@ -5,7 +5,7 @@ using System; using System.Collections.Generic; using System.IO; using osu.Framework.Logging; -using osu.Framework.Platform; +using osu.Game.Tournament.IO; using osu.Game.Tournament.Models; namespace osu.Game.Tournament.Screens.Drawings.Components @@ -14,9 +14,9 @@ namespace osu.Game.Tournament.Screens.Drawings.Components { private const string teams_filename = "drawings.txt"; - private readonly Storage storage; + private readonly TournamentStorage storage; - public StorageBackedTeamList(Storage storage) + public StorageBackedTeamList(TournamentStorage storage) { this.storage = storage; } diff --git a/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs b/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs index 8be66ff98c..bf0d6f4871 100644 --- a/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs +++ b/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs @@ -12,9 +12,9 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; using osu.Framework.Logging; -using osu.Framework.Platform; using osu.Game.Graphics; using osu.Game.Tournament.Components; +using osu.Game.Tournament.IO; using osu.Game.Tournament.Models; using osu.Game.Tournament.Screens.Drawings.Components; using osuTK; @@ -36,12 +36,12 @@ namespace osu.Game.Tournament.Screens.Drawings private Task writeOp; - private Storage storage; + private TournamentStorage storage; public ITeamList TeamList; [BackgroundDependencyLoader] - private void load(TextureStore textures, Storage storage) + private void load(TextureStore textures, TournamentStorage storage) { RelativeSizeAxes = Axes.Both; From 32d86d6fab0a34f502de05fc5becec996b0947f0 Mon Sep 17 00:00:00 2001 From: Shivam Date: Thu, 11 Jun 2020 18:07:24 +0200 Subject: [PATCH 0062/1782] Create storage for config files of a tournament --- osu.Game.Tournament/IO/TournamentStorage.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game.Tournament/IO/TournamentStorage.cs b/osu.Game.Tournament/IO/TournamentStorage.cs index 195448f2d5..b658dfdb69 100644 --- a/osu.Game.Tournament/IO/TournamentStorage.cs +++ b/osu.Game.Tournament/IO/TournamentStorage.cs @@ -15,6 +15,7 @@ namespace osu.Game.Tournament.IO { private readonly GameHost host; internal readonly TournamentVideoResourceStore VideoStore; + internal readonly Storage ConfigurationStorage; private const string default_tournament = "default"; public TournamentStorage(GameHost host) @@ -38,6 +39,8 @@ namespace osu.Game.Tournament.IO ChangeTargetStorage(UnderlyingStorage.GetStorageForDirectory(default_tournament)); } + ConfigurationStorage = UnderlyingStorage.GetStorageForDirectory("config"); + VideoStore = new TournamentVideoResourceStore(this); Logger.Log("Using tournament storage: " + GetFullPath(string.Empty)); } From 592e3bf4c91bc6f976a80d1b416309c60503bcac Mon Sep 17 00:00:00 2001 From: Shivam Date: Thu, 11 Jun 2020 18:21:56 +0200 Subject: [PATCH 0063/1782] Implement migrations for the drawings config file --- osu.Game.Tournament/IO/TournamentStorage.cs | 14 +++++++++++++- .../Screens/Drawings/DrawingsScreen.cs | 2 +- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tournament/IO/TournamentStorage.cs b/osu.Game.Tournament/IO/TournamentStorage.cs index b658dfdb69..298d02e6bb 100644 --- a/osu.Game.Tournament/IO/TournamentStorage.cs +++ b/osu.Game.Tournament/IO/TournamentStorage.cs @@ -17,6 +17,7 @@ namespace osu.Game.Tournament.IO internal readonly TournamentVideoResourceStore VideoStore; internal readonly Storage ConfigurationStorage; private const string default_tournament = "default"; + private const string config_directory = "config"; public TournamentStorage(GameHost host) : base(host.Storage.GetStorageForDirectory("tournaments"), string.Empty) @@ -39,7 +40,7 @@ namespace osu.Game.Tournament.IO ChangeTargetStorage(UnderlyingStorage.GetStorageForDirectory(default_tournament)); } - ConfigurationStorage = UnderlyingStorage.GetStorageForDirectory("config"); + ConfigurationStorage = UnderlyingStorage.GetStorageForDirectory(config_directory); VideoStore = new TournamentVideoResourceStore(this); Logger.Log("Using tournament storage: " + GetFullPath(string.Empty)); @@ -49,9 +50,13 @@ namespace osu.Game.Tournament.IO { var source = new DirectoryInfo(host.Storage.GetFullPath("tournament")); var destination = new DirectoryInfo(GetFullPath(default_tournament)); + var cfgDestination = new DirectoryInfo(GetFullPath(default_tournament + Path.DirectorySeparatorChar + config_directory)); if (!destination.Exists) destination.Create(); + + if (!cfgDestination.Exists) + destination.CreateSubdirectory(config_directory); if (host.Storage.Exists("bracket.json")) { @@ -67,6 +72,13 @@ namespace osu.Game.Tournament.IO moveFile(drawingsFile, destination); } + if (host.Storage.Exists("drawings.ini")) + { + Logger.Log("Migrating drawing configuration to default tournament storage."); + var drawingsConfigFile = new System.IO.FileInfo(host.Storage.GetFullPath("drawings.ini")); + moveFile(drawingsConfigFile, cfgDestination); + } + if (host.Storage.Exists("drawings_results.txt")) { Logger.Log("Migrating drawings results to default tournament storage."); diff --git a/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs b/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs index bf0d6f4871..7f2563c948 100644 --- a/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs +++ b/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs @@ -55,7 +55,7 @@ namespace osu.Game.Tournament.Screens.Drawings return; } - drawingsConfig = new DrawingsConfigManager(storage); + drawingsConfig = new DrawingsConfigManager(storage.ConfigurationStorage); InternalChildren = new Drawable[] { From 56a40e616b0688a4969577a6b5b540854556b8bc Mon Sep 17 00:00:00 2001 From: Shivam Date: Thu, 11 Jun 2020 20:11:44 +0200 Subject: [PATCH 0064/1782] Add drawings to the migration test --- .../NonVisual/CustomTourneyDirectoryTest.cs | 40 ++++++++++++++----- osu.Game.Tournament/IO/TournamentStorage.cs | 2 +- 2 files changed, 30 insertions(+), 12 deletions(-) diff --git a/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs b/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs index 851efb9a3d..37f456ae96 100644 --- a/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs +++ b/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs @@ -3,7 +3,6 @@ using System; using System.IO; -using System.Linq; using System.Threading; using System.Threading.Tasks; using NUnit.Framework; @@ -48,7 +47,7 @@ namespace osu.Game.Tournament.Tests.NonVisual { using (HeadlessGameHost host = new HeadlessGameHost(nameof(TestCustomDirectory))) { - string osuDesktopStorage = Path.Combine(RuntimeInfo.StartupDirectory, "headless", nameof(TestCustomDirectory)); + string osuDesktopStorage = basePath(nameof(TestCustomDirectory)); const string custom_tournament = "custom"; // need access before the game has constructed its own storage yet. @@ -80,23 +79,34 @@ namespace osu.Game.Tournament.Tests.NonVisual using (HeadlessGameHost host = new HeadlessGameHost(nameof(TestMigration))) { // Recreate the old setup that uses "tournament" as the base path. - string basePath = Path.Combine(RuntimeInfo.StartupDirectory, "headless", nameof(TestMigration), "tournament"); + string osuRoot = basePath(nameof(TestMigration)); - string videosPath = Path.Combine(basePath, "videos"); - string modsPath = Path.Combine(basePath, "mods"); - string flagsPath = Path.Combine(basePath, "flags"); + // Define all the paths for the old scenario + string oldPath = Path.Combine(osuRoot, "tournament"); + string videosPath = Path.Combine(oldPath, "videos"); + string modsPath = Path.Combine(oldPath, "mods"); + string flagsPath = Path.Combine(oldPath, "flags"); Directory.CreateDirectory(videosPath); Directory.CreateDirectory(modsPath); Directory.CreateDirectory(flagsPath); - string bracketFile = Path.Combine(RuntimeInfo.StartupDirectory, "headless", nameof(TestMigration), "bracket.json"); + // Define testing files corresponding to the specific file migrations that are needed + string bracketFile = Path.Combine(osuRoot, "bracket.json"); + + string drawingsConfig = Path.Combine(osuRoot, "drawings.ini"); + string drawingsFile = Path.Combine(osuRoot, "drawings.txt"); + string drawingsResult = Path.Combine(osuRoot, "drawings_results.txt"); + + // Define sample files to test recursive copying string videoFile = Path.Combine(videosPath, "video.mp4"); string modFile = Path.Combine(modsPath, "mod.png"); string flagFile = Path.Combine(flagsPath, "flag.png"); File.WriteAllText(bracketFile, "{}"); - + File.WriteAllText(drawingsConfig, "test"); + File.WriteAllText(drawingsFile, "test"); + File.WriteAllText(drawingsResult, "test"); File.WriteAllText(videoFile, "test"); File.WriteAllText(modFile, "test"); File.WriteAllText(flagFile, "test"); @@ -118,7 +128,13 @@ namespace osu.Game.Tournament.Tests.NonVisual flagFile = Path.Combine(flagsPath, "flag.png"); Assert.That(storage.GetFullPath("."), Is.EqualTo(migratedPath)); - Assert.That(storage.GetFiles(".", "bracket.json").Single(), Is.EqualTo("bracket.json")); + + Assert.True(storage.Exists("bracket.json")); + Assert.True(storage.Exists("drawings.txt")); + Assert.True(storage.Exists("drawings_results.txt")); + + Assert.True(storage.ConfigurationStorage.Exists("drawings.ini")); + Assert.True(storage.Exists(videoFile)); Assert.True(storage.Exists(modFile)); Assert.True(storage.Exists(flagFile)); @@ -128,7 +144,6 @@ namespace osu.Game.Tournament.Tests.NonVisual // Cleaning up after ourselves. host.Storage.Delete("tournament.ini"); host.Storage.DeleteDirectory("tournaments"); - host.Exit(); } } @@ -151,6 +166,9 @@ namespace osu.Game.Tournament.Tests.NonVisual Assert.IsTrue(task.Wait(timeout), failureMessage); } - private string tournamentBasePath(string testInstance) => Path.Combine(RuntimeInfo.StartupDirectory, "headless", testInstance, "tournaments"); + + private string basePath(string testInstance) => Path.Combine(RuntimeInfo.StartupDirectory, "headless", testInstance); + + private string tournamentBasePath(string testInstance) => Path.Combine(basePath(testInstance), "tournaments"); } } diff --git a/osu.Game.Tournament/IO/TournamentStorage.cs b/osu.Game.Tournament/IO/TournamentStorage.cs index 298d02e6bb..05ee7a3618 100644 --- a/osu.Game.Tournament/IO/TournamentStorage.cs +++ b/osu.Game.Tournament/IO/TournamentStorage.cs @@ -54,7 +54,7 @@ namespace osu.Game.Tournament.IO if (!destination.Exists) destination.Create(); - + if (!cfgDestination.Exists) destination.CreateSubdirectory(config_directory); From 5041c74c7a525de4261b2bf800f97222cd4ede4f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 12 Jun 2020 11:30:15 +0900 Subject: [PATCH 0065/1782] Fix merge issue --- 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 bc9999381b..a779135345 100644 --- a/osu.Game.Tournament/TournamentGameBase.cs +++ b/osu.Game.Tournament/TournamentGameBase.cs @@ -37,7 +37,7 @@ namespace osu.Game.Tournament } [BackgroundDependencyLoader] - private void load(Storage storage) + private void load() { Resources.AddStore(new DllResourceStore(typeof(TournamentGameBase).Assembly)); From 2feaf2c74a9e6ccdff263a597157abf483180229 Mon Sep 17 00:00:00 2001 From: BananeVolante Date: Fri, 12 Jun 2020 19:17:52 +0200 Subject: [PATCH 0066/1782] added music during pause --- osu.Game/Screens/Play/Player.cs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 83991ad027..e37cf9a348 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -78,6 +78,8 @@ namespace osu.Game.Screens.Play private IAPIProvider api { get; set; } private SampleChannel sampleRestart; + + private SampleChannel samplePause; public BreakOverlay BreakOverlay; @@ -161,6 +163,9 @@ namespace osu.Game.Screens.Play return; sampleRestart = audio.Samples.Get(@"Gameplay/restart"); + + samplePause = audio.Samples.Get(@"Gameplay/pause-loop"); + samplePause.Looping = true; mouseWheelDisabled = config.GetBindable(OsuSetting.MouseDisableWheel); @@ -407,7 +412,11 @@ namespace osu.Game.Screens.Play if (canPause) Pause(); else + { + samplePause?.Stop(); + Logger.LogPrint(@"_______sample stopped in performUserRequestedExit"); this.Exit(); + } } /// @@ -416,6 +425,8 @@ namespace osu.Game.Screens.Play /// public void Restart() { + Logger.LogPrint(@"_______sample stopped in Restart"); + samplePause?.Stop(); sampleRestart?.Play(); RestartRequested?.Invoke(); @@ -564,6 +575,8 @@ namespace osu.Game.Screens.Play GameplayClockContainer.Stop(); PauseOverlay.Show(); lastPauseActionTime = GameplayClockContainer.GameplayClock.CurrentTime; + + samplePause?.Play(); } public void Resume() @@ -583,6 +596,8 @@ namespace osu.Game.Screens.Play { GameplayClockContainer.Start(); IsResuming = false; + Logger.LogPrint(@"_______sample stopped in Resume"); + samplePause?.Stop(); } } From 6fd8548f79a772d90b08cefd4e508d32d92d3c5f Mon Sep 17 00:00:00 2001 From: BananeVolante Date: Sat, 13 Jun 2020 10:13:41 +0200 Subject: [PATCH 0067/1782] no longer crash if the restart sample isn't found --- 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 e37cf9a348..4025bbd442 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -165,7 +165,7 @@ namespace osu.Game.Screens.Play sampleRestart = audio.Samples.Get(@"Gameplay/restart"); samplePause = audio.Samples.Get(@"Gameplay/pause-loop"); - samplePause.Looping = true; + if(samplePause != null) { samplePause.Looping = true; } mouseWheelDisabled = config.GetBindable(OsuSetting.MouseDisableWheel); From 8b8f2dfda2ebfad979ce7fa148d824a80db7b418 Mon Sep 17 00:00:00 2001 From: BananeVolante Date: Sat, 13 Jun 2020 10:31:54 +0200 Subject: [PATCH 0068/1782] Removed duplicate samplepause.stop() calls, removed test lines Since restart() always call perform immediate exit when the function lead to a restart, there is no need to stop the pause sample in restart --- osu.Game/Screens/Play/Player.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 4025bbd442..f9e18db581 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -414,7 +414,6 @@ namespace osu.Game.Screens.Play else { samplePause?.Stop(); - Logger.LogPrint(@"_______sample stopped in performUserRequestedExit"); this.Exit(); } } @@ -425,8 +424,6 @@ namespace osu.Game.Screens.Play /// public void Restart() { - Logger.LogPrint(@"_______sample stopped in Restart"); - samplePause?.Stop(); sampleRestart?.Play(); RestartRequested?.Invoke(); @@ -596,7 +593,7 @@ namespace osu.Game.Screens.Play { GameplayClockContainer.Start(); IsResuming = false; - Logger.LogPrint(@"_______sample stopped in Resume"); + samplePause?.Stop(); } } From 794b8673e21b7ccc60e7bac938426c48e3a6abc9 Mon Sep 17 00:00:00 2001 From: BananeVolante Date: Sat, 13 Jun 2020 10:56:02 +0200 Subject: [PATCH 0069/1782] formated using dotnet format --- 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 f9e18db581..ce7bb60048 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -78,7 +78,7 @@ namespace osu.Game.Screens.Play private IAPIProvider api { get; set; } private SampleChannel sampleRestart; - + private SampleChannel samplePause; public BreakOverlay BreakOverlay; @@ -163,9 +163,9 @@ namespace osu.Game.Screens.Play return; sampleRestart = audio.Samples.Get(@"Gameplay/restart"); - + samplePause = audio.Samples.Get(@"Gameplay/pause-loop"); - if(samplePause != null) { samplePause.Looping = true; } + if (samplePause != null) { samplePause.Looping = true; } mouseWheelDisabled = config.GetBindable(OsuSetting.MouseDisableWheel); From 04c1efe298681c82da4b2342b9d0bb78e432d2ff Mon Sep 17 00:00:00 2001 From: BananeVolante Date: Sat, 13 Jun 2020 14:33:55 +0200 Subject: [PATCH 0070/1782] resolved issues with inspect code script --- osu.Game/Screens/Play/Player.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index ce7bb60048..d5e9c54e04 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -165,7 +165,8 @@ namespace osu.Game.Screens.Play sampleRestart = audio.Samples.Get(@"Gameplay/restart"); samplePause = audio.Samples.Get(@"Gameplay/pause-loop"); - if (samplePause != null) { samplePause.Looping = true; } + if (samplePause != null) + samplePause.Looping = true; mouseWheelDisabled = config.GetBindable(OsuSetting.MouseDisableWheel); From 29ae1c460aa9564c6fc04ffa0d001d1139c42c43 Mon Sep 17 00:00:00 2001 From: Shivam Date: Tue, 16 Jun 2020 17:00:20 +0200 Subject: [PATCH 0071/1782] TournamentStorage now takes in a parent storage --- osu.Game.Tournament/IO/TournamentStorage.cs | 28 ++++++++++----------- osu.Game.Tournament/TournamentGameBase.cs | 8 +++--- 2 files changed, 17 insertions(+), 19 deletions(-) diff --git a/osu.Game.Tournament/IO/TournamentStorage.cs b/osu.Game.Tournament/IO/TournamentStorage.cs index 05ee7a3618..d6e95bc4b6 100644 --- a/osu.Game.Tournament/IO/TournamentStorage.cs +++ b/osu.Game.Tournament/IO/TournamentStorage.cs @@ -13,18 +13,18 @@ namespace osu.Game.Tournament.IO { public class TournamentStorage : WrappedStorage { - private readonly GameHost host; + private readonly Storage storage; internal readonly TournamentVideoResourceStore VideoStore; internal readonly Storage ConfigurationStorage; private const string default_tournament = "default"; private const string config_directory = "config"; - public TournamentStorage(GameHost host) - : base(host.Storage.GetStorageForDirectory("tournaments"), string.Empty) + public TournamentStorage(Storage storage) + : base(storage.GetStorageForDirectory("tournaments"), string.Empty) { - this.host = host; + this.storage = storage; - TournamentStorageManager storageConfig = new TournamentStorageManager(host.Storage); + TournamentStorageManager storageConfig = new TournamentStorageManager(storage); var currentTournament = storageConfig.Get(StorageConfig.CurrentTournament); @@ -48,7 +48,7 @@ namespace osu.Game.Tournament.IO internal void Migrate() { - var source = new DirectoryInfo(host.Storage.GetFullPath("tournament")); + var source = new DirectoryInfo(storage.GetFullPath("tournament")); var destination = new DirectoryInfo(GetFullPath(default_tournament)); var cfgDestination = new DirectoryInfo(GetFullPath(default_tournament + Path.DirectorySeparatorChar + config_directory)); @@ -58,31 +58,31 @@ namespace osu.Game.Tournament.IO if (!cfgDestination.Exists) destination.CreateSubdirectory(config_directory); - if (host.Storage.Exists("bracket.json")) + if (storage.Exists("bracket.json")) { Logger.Log("Migrating bracket to default tournament storage."); - var bracketFile = new System.IO.FileInfo(host.Storage.GetFullPath("bracket.json")); + var bracketFile = new System.IO.FileInfo(storage.GetFullPath("bracket.json")); moveFile(bracketFile, destination); } - if (host.Storage.Exists("drawings.txt")) + if (storage.Exists("drawings.txt")) { Logger.Log("Migrating drawings to default tournament storage."); - var drawingsFile = new System.IO.FileInfo(host.Storage.GetFullPath("drawings.txt")); + var drawingsFile = new System.IO.FileInfo(storage.GetFullPath("drawings.txt")); moveFile(drawingsFile, destination); } - if (host.Storage.Exists("drawings.ini")) + if (storage.Exists("drawings.ini")) { Logger.Log("Migrating drawing configuration to default tournament storage."); - var drawingsConfigFile = new System.IO.FileInfo(host.Storage.GetFullPath("drawings.ini")); + var drawingsConfigFile = new System.IO.FileInfo(storage.GetFullPath("drawings.ini")); moveFile(drawingsConfigFile, cfgDestination); } - if (host.Storage.Exists("drawings_results.txt")) + if (storage.Exists("drawings_results.txt")) { Logger.Log("Migrating drawings results to default tournament storage."); - var drawingsResultsFile = new System.IO.FileInfo(host.Storage.GetFullPath("drawings_results.txt")); + var drawingsResultsFile = new System.IO.FileInfo(storage.GetFullPath("drawings_results.txt")); moveFile(drawingsResultsFile, destination); } diff --git a/osu.Game.Tournament/TournamentGameBase.cs b/osu.Game.Tournament/TournamentGameBase.cs index a779135345..7ec8d0f18a 100644 --- a/osu.Game.Tournament/TournamentGameBase.cs +++ b/osu.Game.Tournament/TournamentGameBase.cs @@ -8,6 +8,7 @@ using Newtonsoft.Json; using osu.Framework.Allocation; using osu.Framework.Graphics.Textures; using osu.Framework.Input; +using osu.Framework.Platform; using osu.Framework.IO.Stores; using osu.Game.Beatmaps; using osu.Game.Online.API.Requests; @@ -23,11 +24,8 @@ namespace osu.Game.Tournament public class TournamentGameBase : OsuGameBase { private const string bracket_filename = "bracket.json"; - private LadderInfo ladder; - private TournamentStorage storage; - private DependencyContainer dependencies; private FileBasedIPC ipc; @@ -37,11 +35,11 @@ namespace osu.Game.Tournament } [BackgroundDependencyLoader] - private void load() + private void load(Storage baseStorage) { Resources.AddStore(new DllResourceStore(typeof(TournamentGameBase).Assembly)); - dependencies.CacheAs(storage = new TournamentStorage(Host)); + dependencies.CacheAs(storage = new TournamentStorage(baseStorage)); Textures.AddStore(new TextureLoaderStore(storage.VideoStore)); From b75fd7bfa8756addf028665492b456334d55b620 Mon Sep 17 00:00:00 2001 From: Shivam Date: Tue, 16 Jun 2020 17:14:54 +0200 Subject: [PATCH 0072/1782] Refactor moving logic (1/2) --- osu.Game.Tournament/IO/TournamentStorage.cs | 43 +++++++-------------- 1 file changed, 15 insertions(+), 28 deletions(-) diff --git a/osu.Game.Tournament/IO/TournamentStorage.cs b/osu.Game.Tournament/IO/TournamentStorage.cs index d6e95bc4b6..1962cc46d8 100644 --- a/osu.Game.Tournament/IO/TournamentStorage.cs +++ b/osu.Game.Tournament/IO/TournamentStorage.cs @@ -57,34 +57,11 @@ namespace osu.Game.Tournament.IO if (!cfgDestination.Exists) destination.CreateSubdirectory(config_directory); - - if (storage.Exists("bracket.json")) - { - Logger.Log("Migrating bracket to default tournament storage."); - var bracketFile = new System.IO.FileInfo(storage.GetFullPath("bracket.json")); - moveFile(bracketFile, destination); - } - - if (storage.Exists("drawings.txt")) - { - Logger.Log("Migrating drawings to default tournament storage."); - var drawingsFile = new System.IO.FileInfo(storage.GetFullPath("drawings.txt")); - moveFile(drawingsFile, destination); - } - - if (storage.Exists("drawings.ini")) - { - Logger.Log("Migrating drawing configuration to default tournament storage."); - var drawingsConfigFile = new System.IO.FileInfo(storage.GetFullPath("drawings.ini")); - moveFile(drawingsConfigFile, cfgDestination); - } - - if (storage.Exists("drawings_results.txt")) - { - Logger.Log("Migrating drawings results to default tournament storage."); - var drawingsResultsFile = new System.IO.FileInfo(storage.GetFullPath("drawings_results.txt")); - moveFile(drawingsResultsFile, destination); - } + + moveFileIfExists("bracket.json", destination); + moveFileIfExists("drawings.txt", destination); + moveFileIfExists("drawings_results.txt", destination); + moveFileIfExists("drawings.ini", cfgDestination); if (source.Exists) { @@ -94,6 +71,16 @@ namespace osu.Game.Tournament.IO } } + private void moveFileIfExists(string file, DirectoryInfo destination) + { + if (storage.Exists(file)) + { + Logger.Log($"Migrating {file} to default tournament storage."); + var fileInfo = new System.IO.FileInfo(storage.GetFullPath(file)); + moveFile(fileInfo, destination); + } + } + private void copyRecursive(DirectoryInfo source, DirectoryInfo destination) { // based off example code https://docs.microsoft.com/en-us/dotnet/api/system.io.directoryinfo From 02d66c4856626e60226e1a4ebcac6dffdcef13a7 Mon Sep 17 00:00:00 2001 From: Shivam Date: Tue, 16 Jun 2020 17:15:43 +0200 Subject: [PATCH 0073/1782] Refactor moving (2/2) --- osu.Game.Tournament/IO/TournamentStorage.cs | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/osu.Game.Tournament/IO/TournamentStorage.cs b/osu.Game.Tournament/IO/TournamentStorage.cs index 1962cc46d8..611592e0e3 100644 --- a/osu.Game.Tournament/IO/TournamentStorage.cs +++ b/osu.Game.Tournament/IO/TournamentStorage.cs @@ -57,7 +57,7 @@ namespace osu.Game.Tournament.IO if (!cfgDestination.Exists) destination.CreateSubdirectory(config_directory); - + moveFileIfExists("bracket.json", destination); moveFileIfExists("drawings.txt", destination); moveFileIfExists("drawings_results.txt", destination); @@ -77,7 +77,8 @@ namespace osu.Game.Tournament.IO { Logger.Log($"Migrating {file} to default tournament storage."); var fileInfo = new System.IO.FileInfo(storage.GetFullPath(file)); - moveFile(fileInfo, destination); + attemptOperation(() => fileInfo.CopyTo(Path.Combine(destination.FullName, fileInfo.Name), true)); + fileInfo.Delete(); } } @@ -112,12 +113,6 @@ namespace osu.Game.Tournament.IO attemptOperation(target.Delete); } - private void moveFile(System.IO.FileInfo file, DirectoryInfo destination) - { - attemptOperation(() => file.CopyTo(Path.Combine(destination.FullName, file.Name), true)); - file.Delete(); - } - private void attemptOperation(Action action, int attempts = 10) { while (true) From dd9697032c0071931eecf6b563c1774647ae33dd Mon Sep 17 00:00:00 2001 From: Shivam Date: Tue, 16 Jun 2020 17:39:20 +0200 Subject: [PATCH 0074/1782] Introduce new class MigratableStorage --- osu.Game.Tournament/IO/TournamentStorage.cs | 70 +++------------ osu.Game/IO/MigratableStorage.cs | 95 +++++++++++++++++++++ 2 files changed, 105 insertions(+), 60 deletions(-) create mode 100644 osu.Game/IO/MigratableStorage.cs diff --git a/osu.Game.Tournament/IO/TournamentStorage.cs b/osu.Game.Tournament/IO/TournamentStorage.cs index 611592e0e3..1731b96095 100644 --- a/osu.Game.Tournament/IO/TournamentStorage.cs +++ b/osu.Game.Tournament/IO/TournamentStorage.cs @@ -11,7 +11,7 @@ using osu.Game.Tournament.Configuration; namespace osu.Game.Tournament.IO { - public class TournamentStorage : WrappedStorage + public class TournamentStorage : MigratableStorage { private readonly Storage storage; internal readonly TournamentVideoResourceStore VideoStore; @@ -52,8 +52,15 @@ namespace osu.Game.Tournament.IO var destination = new DirectoryInfo(GetFullPath(default_tournament)); var cfgDestination = new DirectoryInfo(GetFullPath(default_tournament + Path.DirectorySeparatorChar + config_directory)); - if (!destination.Exists) - destination.Create(); + // if (!destination.Exists) + // destination.Create(); + + if (source.Exists) + { + Logger.Log("Migrating tournament assets to default tournament storage."); + copyRecursive(source, destination); + deleteRecursive(source); + } if (!cfgDestination.Exists) destination.CreateSubdirectory(config_directory); @@ -62,13 +69,6 @@ namespace osu.Game.Tournament.IO moveFileIfExists("drawings.txt", destination); moveFileIfExists("drawings_results.txt", destination); moveFileIfExists("drawings.ini", cfgDestination); - - if (source.Exists) - { - Logger.Log("Migrating tournament assets to default tournament storage."); - copyRecursive(source, destination); - deleteRecursive(source); - } } private void moveFileIfExists(string file, DirectoryInfo destination) @@ -81,55 +81,5 @@ namespace osu.Game.Tournament.IO fileInfo.Delete(); } } - - private void copyRecursive(DirectoryInfo source, DirectoryInfo destination) - { - // based off example code https://docs.microsoft.com/en-us/dotnet/api/system.io.directoryinfo - - foreach (System.IO.FileInfo fi in source.GetFiles()) - { - attemptOperation(() => fi.CopyTo(Path.Combine(destination.FullName, fi.Name), true)); - } - - foreach (DirectoryInfo dir in source.GetDirectories()) - { - copyRecursive(dir, destination.CreateSubdirectory(dir.Name)); - } - } - - private void deleteRecursive(DirectoryInfo target) - { - foreach (System.IO.FileInfo fi in target.GetFiles()) - { - attemptOperation(() => fi.Delete()); - } - - foreach (DirectoryInfo dir in target.GetDirectories()) - { - attemptOperation(() => dir.Delete(true)); - } - - if (target.GetFiles().Length == 0 && target.GetDirectories().Length == 0) - attemptOperation(target.Delete); - } - - private void attemptOperation(Action action, int attempts = 10) - { - while (true) - { - try - { - action(); - return; - } - catch (Exception) - { - if (attempts-- == 0) - throw; - } - - Thread.Sleep(250); - } - } } } diff --git a/osu.Game/IO/MigratableStorage.cs b/osu.Game/IO/MigratableStorage.cs new file mode 100644 index 0000000000..0ab0ea9934 --- /dev/null +++ b/osu.Game/IO/MigratableStorage.cs @@ -0,0 +1,95 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.IO; +using System.Linq; +using System.Threading; +using osu.Framework.Logging; +using osu.Framework.Platform; +using osu.Game.Configuration; + +namespace osu.Game.IO +{ + public abstract class MigratableStorage : WrappedStorage + { + + virtual protected string[] IGNORE_DIRECTORIES { get; set; } = Array.Empty(); + + virtual protected string[] IGNORE_FILES { get; set; } = Array.Empty(); + + public MigratableStorage(Storage storage, string subPath = null) + : base(storage, subPath) + { + } + + protected void deleteRecursive(DirectoryInfo target, bool topLevelExcludes = true) + { + foreach (System.IO.FileInfo fi in target.GetFiles()) + { + if (topLevelExcludes && IGNORE_FILES.Contains(fi.Name)) + continue; + + attemptOperation(() => fi.Delete()); + } + + foreach (DirectoryInfo dir in target.GetDirectories()) + { + if (topLevelExcludes && IGNORE_DIRECTORIES.Contains(dir.Name)) + continue; + + attemptOperation(() => dir.Delete(true)); + } + + if (target.GetFiles().Length == 0 && target.GetDirectories().Length == 0) + attemptOperation(target.Delete); + } + + protected void copyRecursive(DirectoryInfo source, DirectoryInfo destination, bool topLevelExcludes = true) + { + // based off example code https://docs.microsoft.com/en-us/dotnet/api/system.io.directoryinfo + if (!destination.Exists) + Directory.CreateDirectory(destination.FullName); + + foreach (System.IO.FileInfo fi in source.GetFiles()) + { + if (topLevelExcludes && IGNORE_FILES.Contains(fi.Name)) + continue; + + attemptOperation(() => fi.CopyTo(Path.Combine(destination.FullName, fi.Name), true)); + } + + foreach (DirectoryInfo dir in source.GetDirectories()) + { + if (topLevelExcludes && IGNORE_DIRECTORIES.Contains(dir.Name)) + continue; + + copyRecursive(dir, destination.CreateSubdirectory(dir.Name), false); + } + } + + /// + /// Attempt an IO operation multiple times and only throw if none of the attempts succeed. + /// + /// The action to perform. + /// The number of attempts (250ms wait between each). + protected static void attemptOperation(Action action, int attempts = 10) + { + while (true) + { + try + { + action(); + return; + } + catch (Exception) + { + if (attempts-- == 0) + throw; + } + + Thread.Sleep(250); + } + } + } +} From 21774b8967ffd8dfd99c1f29b9179394197599f5 Mon Sep 17 00:00:00 2001 From: Shivam Date: Mon, 22 Jun 2020 11:38:50 +0200 Subject: [PATCH 0075/1782] Move static properties to parent class and inherit OsuStorage from it --- .../NonVisual/CustomDataDirectoryTest.cs | 4 +- osu.Game/IO/MigratableStorage.cs | 8 +- osu.Game/IO/OsuStorage.cs | 78 +------------------ 3 files changed, 9 insertions(+), 81 deletions(-) diff --git a/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs b/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs index f3d54d876a..5abefe3198 100644 --- a/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs +++ b/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs @@ -151,13 +151,13 @@ namespace osu.Game.Tests.NonVisual Assert.That(!host.Storage.ExistsDirectory(Path.Combine("test-nested", "cache"))); Assert.That(storage.ExistsDirectory(Path.Combine("test-nested", "cache"))); - foreach (var file in OsuStorage.IGNORE_FILES) + foreach (var file in MigratableStorage.IGNORE_FILES) { Assert.That(host.Storage.Exists(file), Is.True); Assert.That(storage.Exists(file), Is.False); } - foreach (var dir in OsuStorage.IGNORE_DIRECTORIES) + foreach (var dir in MigratableStorage.IGNORE_DIRECTORIES) { Assert.That(host.Storage.ExistsDirectory(dir), Is.True); Assert.That(storage.ExistsDirectory(dir), Is.False); diff --git a/osu.Game/IO/MigratableStorage.cs b/osu.Game/IO/MigratableStorage.cs index 0ab0ea9934..004aa4e09a 100644 --- a/osu.Game/IO/MigratableStorage.cs +++ b/osu.Game/IO/MigratableStorage.cs @@ -14,9 +14,13 @@ namespace osu.Game.IO public abstract class MigratableStorage : WrappedStorage { - virtual protected string[] IGNORE_DIRECTORIES { get; set; } = Array.Empty(); + internal static readonly string[] IGNORE_DIRECTORIES = { "cache" }; - virtual protected string[] IGNORE_FILES { get; set; } = Array.Empty(); + internal static readonly string[] IGNORE_FILES = + { + "framework.ini", + "storage.ini" + }; public MigratableStorage(Storage storage, string subPath = null) : base(storage, subPath) diff --git a/osu.Game/IO/OsuStorage.cs b/osu.Game/IO/OsuStorage.cs index 499bcb4063..416d2082c3 100644 --- a/osu.Game/IO/OsuStorage.cs +++ b/osu.Game/IO/OsuStorage.cs @@ -11,19 +11,11 @@ using osu.Game.Configuration; namespace osu.Game.IO { - public class OsuStorage : WrappedStorage + public class OsuStorage : MigratableStorage { private readonly GameHost host; private readonly StorageConfigManager storageConfig; - internal static readonly string[] IGNORE_DIRECTORIES = { "cache" }; - - internal static readonly string[] IGNORE_FILES = - { - "framework.ini", - "storage.ini" - }; - public OsuStorage(GameHost host) : base(host.Storage, string.Empty) { @@ -76,73 +68,5 @@ namespace osu.Game.IO deleteRecursive(source); } - - private static void deleteRecursive(DirectoryInfo target, bool topLevelExcludes = true) - { - foreach (System.IO.FileInfo fi in target.GetFiles()) - { - if (topLevelExcludes && IGNORE_FILES.Contains(fi.Name)) - continue; - - attemptOperation(() => fi.Delete()); - } - - foreach (DirectoryInfo dir in target.GetDirectories()) - { - if (topLevelExcludes && IGNORE_DIRECTORIES.Contains(dir.Name)) - continue; - - attemptOperation(() => dir.Delete(true)); - } - - if (target.GetFiles().Length == 0 && target.GetDirectories().Length == 0) - attemptOperation(target.Delete); - } - - private static void copyRecursive(DirectoryInfo source, DirectoryInfo destination, bool topLevelExcludes = true) - { - // based off example code https://docs.microsoft.com/en-us/dotnet/api/system.io.directoryinfo - Directory.CreateDirectory(destination.FullName); - - foreach (System.IO.FileInfo fi in source.GetFiles()) - { - if (topLevelExcludes && IGNORE_FILES.Contains(fi.Name)) - continue; - - attemptOperation(() => fi.CopyTo(Path.Combine(destination.FullName, fi.Name), true)); - } - - foreach (DirectoryInfo dir in source.GetDirectories()) - { - if (topLevelExcludes && IGNORE_DIRECTORIES.Contains(dir.Name)) - continue; - - copyRecursive(dir, destination.CreateSubdirectory(dir.Name), false); - } - } - - /// - /// Attempt an IO operation multiple times and only throw if none of the attempts succeed. - /// - /// The action to perform. - /// The number of attempts (250ms wait between each). - private static void attemptOperation(Action action, int attempts = 10) - { - while (true) - { - try - { - action(); - return; - } - catch (Exception) - { - if (attempts-- == 0) - throw; - } - - Thread.Sleep(250); - } - } } } From f878388d578a798698fb7de98935f3fe27239274 Mon Sep 17 00:00:00 2001 From: Shivam Date: Mon, 22 Jun 2020 11:56:14 +0200 Subject: [PATCH 0076/1782] Fix TestMigrationToSeeminglyNestedTarget failing --- osu.Game.Tournament/IO/TournamentStorage.cs | 3 --- osu.Game/IO/MigratableStorage.cs | 2 +- osu.Game/IO/OsuStorage.cs | 2 -- 3 files changed, 1 insertion(+), 6 deletions(-) diff --git a/osu.Game.Tournament/IO/TournamentStorage.cs b/osu.Game.Tournament/IO/TournamentStorage.cs index 1731b96095..c1629f270f 100644 --- a/osu.Game.Tournament/IO/TournamentStorage.cs +++ b/osu.Game.Tournament/IO/TournamentStorage.cs @@ -52,9 +52,6 @@ namespace osu.Game.Tournament.IO var destination = new DirectoryInfo(GetFullPath(default_tournament)); var cfgDestination = new DirectoryInfo(GetFullPath(default_tournament + Path.DirectorySeparatorChar + config_directory)); - // if (!destination.Exists) - // destination.Create(); - if (source.Exists) { Logger.Log("Migrating tournament assets to default tournament storage."); diff --git a/osu.Game/IO/MigratableStorage.cs b/osu.Game/IO/MigratableStorage.cs index 004aa4e09a..95721a736e 100644 --- a/osu.Game/IO/MigratableStorage.cs +++ b/osu.Game/IO/MigratableStorage.cs @@ -53,7 +53,7 @@ namespace osu.Game.IO { // based off example code https://docs.microsoft.com/en-us/dotnet/api/system.io.directoryinfo if (!destination.Exists) - Directory.CreateDirectory(destination.FullName); + Directory.CreateDirectory(destination.FullName); foreach (System.IO.FileInfo fi in source.GetFiles()) { diff --git a/osu.Game/IO/OsuStorage.cs b/osu.Game/IO/OsuStorage.cs index 416d2082c3..d37336234a 100644 --- a/osu.Game/IO/OsuStorage.cs +++ b/osu.Game/IO/OsuStorage.cs @@ -55,8 +55,6 @@ namespace osu.Game.IO { if (destination.GetFiles().Length > 0 || destination.GetDirectories().Length > 0) throw new ArgumentException("Destination provided already has files or directories present", nameof(newLocation)); - - deleteRecursive(destination); } copyRecursive(source, destination); From eec1e9ef4d660acf88de42ec6a814c96e2cf97cf Mon Sep 17 00:00:00 2001 From: Shivam Date: Mon, 22 Jun 2020 12:22:59 +0200 Subject: [PATCH 0077/1782] Remove unnecessary comments and added file check for tournament.ini on test start --- .../NonVisual/CustomTourneyDirectoryTest.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs b/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs index 37f456ae96..92ff39c67c 100644 --- a/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs +++ b/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs @@ -78,10 +78,13 @@ namespace osu.Game.Tournament.Tests.NonVisual { using (HeadlessGameHost host = new HeadlessGameHost(nameof(TestMigration))) { - // Recreate the old setup that uses "tournament" as the base path. string osuRoot = basePath(nameof(TestMigration)); + string configFile = Path.Combine(osuRoot, "tournament.ini"); - // Define all the paths for the old scenario + if (File.Exists(configFile)) + File.Delete(configFile); + + // Recreate the old setup that uses "tournament" as the base path. string oldPath = Path.Combine(osuRoot, "tournament"); string videosPath = Path.Combine(oldPath, "videos"); string modsPath = Path.Combine(oldPath, "mods"); @@ -141,7 +144,6 @@ namespace osu.Game.Tournament.Tests.NonVisual } finally { - // Cleaning up after ourselves. host.Storage.Delete("tournament.ini"); host.Storage.DeleteDirectory("tournaments"); host.Exit(); From 08759da3a7642647d08317231da389b7f78daa44 Mon Sep 17 00:00:00 2001 From: Shivam Date: Mon, 22 Jun 2020 12:41:43 +0200 Subject: [PATCH 0078/1782] Move drawings.ini out of config subfolder --- .../NonVisual/CustomTourneyDirectoryTest.cs | 2 +- osu.Game.Tournament/IO/TournamentStorage.cs | 9 +-------- osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs | 2 +- 3 files changed, 3 insertions(+), 10 deletions(-) diff --git a/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs b/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs index 92ff39c67c..9e6675e09f 100644 --- a/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs +++ b/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs @@ -136,7 +136,7 @@ namespace osu.Game.Tournament.Tests.NonVisual Assert.True(storage.Exists("drawings.txt")); Assert.True(storage.Exists("drawings_results.txt")); - Assert.True(storage.ConfigurationStorage.Exists("drawings.ini")); + Assert.True(storage.Exists("drawings.ini")); Assert.True(storage.Exists(videoFile)); Assert.True(storage.Exists(modFile)); diff --git a/osu.Game.Tournament/IO/TournamentStorage.cs b/osu.Game.Tournament/IO/TournamentStorage.cs index c1629f270f..c9d7ef3126 100644 --- a/osu.Game.Tournament/IO/TournamentStorage.cs +++ b/osu.Game.Tournament/IO/TournamentStorage.cs @@ -15,9 +15,7 @@ namespace osu.Game.Tournament.IO { private readonly Storage storage; internal readonly TournamentVideoResourceStore VideoStore; - internal readonly Storage ConfigurationStorage; private const string default_tournament = "default"; - private const string config_directory = "config"; public TournamentStorage(Storage storage) : base(storage.GetStorageForDirectory("tournaments"), string.Empty) @@ -40,8 +38,6 @@ namespace osu.Game.Tournament.IO ChangeTargetStorage(UnderlyingStorage.GetStorageForDirectory(default_tournament)); } - ConfigurationStorage = UnderlyingStorage.GetStorageForDirectory(config_directory); - VideoStore = new TournamentVideoResourceStore(this); Logger.Log("Using tournament storage: " + GetFullPath(string.Empty)); } @@ -50,7 +46,6 @@ namespace osu.Game.Tournament.IO { var source = new DirectoryInfo(storage.GetFullPath("tournament")); var destination = new DirectoryInfo(GetFullPath(default_tournament)); - var cfgDestination = new DirectoryInfo(GetFullPath(default_tournament + Path.DirectorySeparatorChar + config_directory)); if (source.Exists) { @@ -59,13 +54,11 @@ namespace osu.Game.Tournament.IO deleteRecursive(source); } - if (!cfgDestination.Exists) - destination.CreateSubdirectory(config_directory); moveFileIfExists("bracket.json", destination); moveFileIfExists("drawings.txt", destination); moveFileIfExists("drawings_results.txt", destination); - moveFileIfExists("drawings.ini", cfgDestination); + moveFileIfExists("drawings.ini", destination); } private void moveFileIfExists(string file, DirectoryInfo destination) diff --git a/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs b/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs index de909af152..8b6bd21ee6 100644 --- a/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs +++ b/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs @@ -54,7 +54,7 @@ namespace osu.Game.Tournament.Screens.Drawings return; } - drawingsConfig = new DrawingsConfigManager(storage.ConfigurationStorage); + drawingsConfig = new DrawingsConfigManager(storage); InternalChildren = new Drawable[] { From 6b14079c0a80bc256f49376d939a861c7cd59ba7 Mon Sep 17 00:00:00 2001 From: Shivam Date: Mon, 22 Jun 2020 12:43:01 +0200 Subject: [PATCH 0079/1782] InspectCode changes --- osu.Game.Tournament/IO/TournamentStorage.cs | 9 +++------ osu.Game/IO/MigratableStorage.cs | 21 +++++++++------------ osu.Game/IO/OsuStorage.cs | 6 ++---- 3 files changed, 14 insertions(+), 22 deletions(-) diff --git a/osu.Game.Tournament/IO/TournamentStorage.cs b/osu.Game.Tournament/IO/TournamentStorage.cs index c9d7ef3126..12dcc2195c 100644 --- a/osu.Game.Tournament/IO/TournamentStorage.cs +++ b/osu.Game.Tournament/IO/TournamentStorage.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.Threading; using osu.Framework.Logging; using osu.Framework.Platform; using osu.Game.IO; @@ -50,11 +48,10 @@ namespace osu.Game.Tournament.IO if (source.Exists) { Logger.Log("Migrating tournament assets to default tournament storage."); - copyRecursive(source, destination); - deleteRecursive(source); + CopyRecursive(source, destination); + DeleteRecursive(source); } - moveFileIfExists("bracket.json", destination); moveFileIfExists("drawings.txt", destination); moveFileIfExists("drawings_results.txt", destination); @@ -67,7 +64,7 @@ namespace osu.Game.Tournament.IO { Logger.Log($"Migrating {file} to default tournament storage."); var fileInfo = new System.IO.FileInfo(storage.GetFullPath(file)); - attemptOperation(() => fileInfo.CopyTo(Path.Combine(destination.FullName, fileInfo.Name), true)); + AttemptOperation(() => fileInfo.CopyTo(Path.Combine(destination.FullName, fileInfo.Name), true)); fileInfo.Delete(); } } diff --git a/osu.Game/IO/MigratableStorage.cs b/osu.Game/IO/MigratableStorage.cs index 95721a736e..7efc37990f 100644 --- a/osu.Game/IO/MigratableStorage.cs +++ b/osu.Game/IO/MigratableStorage.cs @@ -5,15 +5,12 @@ using System; using System.IO; using System.Linq; using System.Threading; -using osu.Framework.Logging; using osu.Framework.Platform; -using osu.Game.Configuration; namespace osu.Game.IO { public abstract class MigratableStorage : WrappedStorage { - internal static readonly string[] IGNORE_DIRECTORIES = { "cache" }; internal static readonly string[] IGNORE_FILES = @@ -22,19 +19,19 @@ namespace osu.Game.IO "storage.ini" }; - public MigratableStorage(Storage storage, string subPath = null) + protected MigratableStorage(Storage storage, string subPath = null) : base(storage, subPath) { } - protected void deleteRecursive(DirectoryInfo target, bool topLevelExcludes = true) + protected void DeleteRecursive(DirectoryInfo target, bool topLevelExcludes = true) { foreach (System.IO.FileInfo fi in target.GetFiles()) { if (topLevelExcludes && IGNORE_FILES.Contains(fi.Name)) continue; - attemptOperation(() => fi.Delete()); + AttemptOperation(() => fi.Delete()); } foreach (DirectoryInfo dir in target.GetDirectories()) @@ -42,14 +39,14 @@ namespace osu.Game.IO if (topLevelExcludes && IGNORE_DIRECTORIES.Contains(dir.Name)) continue; - attemptOperation(() => dir.Delete(true)); + AttemptOperation(() => dir.Delete(true)); } if (target.GetFiles().Length == 0 && target.GetDirectories().Length == 0) - attemptOperation(target.Delete); + AttemptOperation(target.Delete); } - protected void copyRecursive(DirectoryInfo source, DirectoryInfo destination, bool topLevelExcludes = true) + protected void CopyRecursive(DirectoryInfo source, DirectoryInfo destination, bool topLevelExcludes = true) { // based off example code https://docs.microsoft.com/en-us/dotnet/api/system.io.directoryinfo if (!destination.Exists) @@ -60,7 +57,7 @@ namespace osu.Game.IO if (topLevelExcludes && IGNORE_FILES.Contains(fi.Name)) continue; - attemptOperation(() => fi.CopyTo(Path.Combine(destination.FullName, fi.Name), true)); + AttemptOperation(() => fi.CopyTo(Path.Combine(destination.FullName, fi.Name), true)); } foreach (DirectoryInfo dir in source.GetDirectories()) @@ -68,7 +65,7 @@ namespace osu.Game.IO if (topLevelExcludes && IGNORE_DIRECTORIES.Contains(dir.Name)) continue; - copyRecursive(dir, destination.CreateSubdirectory(dir.Name), false); + CopyRecursive(dir, destination.CreateSubdirectory(dir.Name), false); } } @@ -77,7 +74,7 @@ namespace osu.Game.IO /// /// The action to perform. /// The number of attempts (250ms wait between each). - protected static void attemptOperation(Action action, int attempts = 10) + protected static void AttemptOperation(Action action, int attempts = 10) { while (true) { diff --git a/osu.Game/IO/OsuStorage.cs b/osu.Game/IO/OsuStorage.cs index d37336234a..3d224841f3 100644 --- a/osu.Game/IO/OsuStorage.cs +++ b/osu.Game/IO/OsuStorage.cs @@ -3,8 +3,6 @@ using System; using System.IO; -using System.Linq; -using System.Threading; using osu.Framework.Logging; using osu.Framework.Platform; using osu.Game.Configuration; @@ -57,14 +55,14 @@ namespace osu.Game.IO throw new ArgumentException("Destination provided already has files or directories present", nameof(newLocation)); } - copyRecursive(source, destination); + CopyRecursive(source, destination); ChangeTargetStorage(host.GetStorage(newLocation)); storageConfig.Set(StorageConfig.FullPath, newLocation); storageConfig.Save(); - deleteRecursive(source); + DeleteRecursive(source); } } } From a94dcc4923023d1322af32ed35ccb05f4ccfec57 Mon Sep 17 00:00:00 2001 From: Shivam Date: Mon, 22 Jun 2020 12:59:38 +0200 Subject: [PATCH 0080/1782] Add xmldoc to MigratableStorage --- osu.Game/IO/MigratableStorage.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/IO/MigratableStorage.cs b/osu.Game/IO/MigratableStorage.cs index 7efc37990f..45aba41315 100644 --- a/osu.Game/IO/MigratableStorage.cs +++ b/osu.Game/IO/MigratableStorage.cs @@ -9,6 +9,9 @@ using osu.Framework.Platform; namespace osu.Game.IO { + /// + /// A that is migratable to different locations. + /// public abstract class MigratableStorage : WrappedStorage { internal static readonly string[] IGNORE_DIRECTORIES = { "cache" }; From e0d5a9182e76eb44d9cf18ac6b2ba259316fbb9b Mon Sep 17 00:00:00 2001 From: Shivam Date: Mon, 22 Jun 2020 12:59:56 +0200 Subject: [PATCH 0081/1782] make tournament migration private --- osu.Game.Tournament/IO/TournamentStorage.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tournament/IO/TournamentStorage.cs b/osu.Game.Tournament/IO/TournamentStorage.cs index 12dcc2195c..ebd8d2b63f 100644 --- a/osu.Game.Tournament/IO/TournamentStorage.cs +++ b/osu.Game.Tournament/IO/TournamentStorage.cs @@ -30,7 +30,7 @@ namespace osu.Game.Tournament.IO } else { - Migrate(); + migrate(); storageConfig.Set(StorageConfig.CurrentTournament, default_tournament); storageConfig.Save(); ChangeTargetStorage(UnderlyingStorage.GetStorageForDirectory(default_tournament)); @@ -40,7 +40,7 @@ namespace osu.Game.Tournament.IO Logger.Log("Using tournament storage: " + GetFullPath(string.Empty)); } - internal void Migrate() + private void migrate() { var source = new DirectoryInfo(storage.GetFullPath("tournament")); var destination = new DirectoryInfo(GetFullPath(default_tournament)); From a899c754f11942c8967ff78139ef7bb5433e1b8c Mon Sep 17 00:00:00 2001 From: Shivam Date: Mon, 22 Jun 2020 13:03:24 +0200 Subject: [PATCH 0082/1782] Remove whitespace at the end of xmldoc line --- osu.Game/IO/MigratableStorage.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/IO/MigratableStorage.cs b/osu.Game/IO/MigratableStorage.cs index 45aba41315..c4dc4bcfb2 100644 --- a/osu.Game/IO/MigratableStorage.cs +++ b/osu.Game/IO/MigratableStorage.cs @@ -10,7 +10,7 @@ using osu.Framework.Platform; namespace osu.Game.IO { /// - /// A that is migratable to different locations. + /// A that is migratable to different locations. /// public abstract class MigratableStorage : WrappedStorage { From a4eb6c81c5a90b15d298fe705cc0699c98da1772 Mon Sep 17 00:00:00 2001 From: BananeVolante Date: Mon, 22 Jun 2020 13:40:31 +0200 Subject: [PATCH 0083/1782] undid changes to the file --- osu.Game/Screens/Play/Player.cs | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index d5e9c54e04..b6d87e658b 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -5,11 +5,13 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; +using Humanizer; 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.Audio; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Events; using osu.Framework.Logging; @@ -78,9 +80,7 @@ namespace osu.Game.Screens.Play private IAPIProvider api { get; set; } private SampleChannel sampleRestart; - - private SampleChannel samplePause; - + public BreakOverlay BreakOverlay; private BreakTracker breakTracker; @@ -164,10 +164,6 @@ namespace osu.Game.Screens.Play sampleRestart = audio.Samples.Get(@"Gameplay/restart"); - samplePause = audio.Samples.Get(@"Gameplay/pause-loop"); - if (samplePause != null) - samplePause.Looping = true; - mouseWheelDisabled = config.GetBindable(OsuSetting.MouseDisableWheel); DrawableRuleset = ruleset.CreateDrawableRulesetWith(playableBeatmap, Mods.Value); @@ -414,7 +410,6 @@ namespace osu.Game.Screens.Play Pause(); else { - samplePause?.Stop(); this.Exit(); } } @@ -569,21 +564,20 @@ namespace osu.Game.Screens.Play DrawableRuleset.CancelResume(); IsResuming = false; } - GameplayClockContainer.Stop(); PauseOverlay.Show(); lastPauseActionTime = GameplayClockContainer.GameplayClock.CurrentTime; - - samplePause?.Play(); + } public void Resume() { if (!canResume) return; + IsResuming = true; PauseOverlay.Hide(); - + // breaks and time-based conditions may allow instant resume. if (breakTracker.IsBreakTime.Value) completeResume(); @@ -594,8 +588,6 @@ namespace osu.Game.Screens.Play { GameplayClockContainer.Start(); IsResuming = false; - - samplePause?.Stop(); } } @@ -672,7 +664,9 @@ namespace osu.Game.Screens.Play // as we are no longer the current screen, we cannot guarantee the track is still usable. GameplayClockContainer?.StopUsingBeatmapClock(); + fadeOut(); + return base.OnExiting(next); } @@ -717,7 +711,12 @@ namespace osu.Game.Screens.Play Background.EnableUserDim.Value = false; storyboardReplacesBackground.Value = false; } + #endregion + } + + + } From 9dea96e5fdff2a741af29b10948be6a65d63d634 Mon Sep 17 00:00:00 2001 From: BananeVolante Date: Mon, 22 Jun 2020 14:02:21 +0200 Subject: [PATCH 0084/1782] added pause sound with fading --- osu.Game/Screens/Play/PauseOverlay.cs | 39 ++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/PauseOverlay.cs b/osu.Game/Screens/Play/PauseOverlay.cs index 6cc6027a03..6cca0c47fd 100644 --- a/osu.Game/Screens/Play/PauseOverlay.cs +++ b/osu.Game/Screens/Play/PauseOverlay.cs @@ -3,9 +3,18 @@ using System; using System.Linq; +using System.Runtime.CompilerServices; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using NUnit.Framework.Internal; using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Audio.Sample; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Audio; using osu.Game.Graphics; using osuTK.Graphics; +using osu.Framework.Logging; + namespace osu.Game.Screens.Play { @@ -16,14 +25,42 @@ namespace osu.Game.Screens.Play public override string Header => "paused"; public override string Description => "you're not going to do what i think you're going to do, are ya?"; + private DrawableSample pauseLoop; + protected override Action BackAction => () => InternalButtons.Children.First().Click(); [BackgroundDependencyLoader] - private void load(OsuColour colours) + private void load(OsuColour colours, AudioManager audio) { AddButton("Continue", colours.Green, () => OnResume?.Invoke()); AddButton("Retry", colours.YellowDark, () => OnRetry?.Invoke()); AddButton("Quit", new Color4(170, 27, 39, 255), () => OnQuit?.Invoke()); + + var sampleChannel = audio.Samples.Get(@"Gameplay/pause-loop"); + if (sampleChannel != null) + { + AddInternal(pauseLoop = new DrawableSample(sampleChannel) + { + Looping = true, + }); + pauseLoop?.VolumeTo(0.0f); + pauseLoop?.Play(); + } } + + + protected override void PopIn() + { + base.PopIn(); + pauseLoop?.VolumeTo(1.0f, 400, Easing.InQuint); + } + + protected override void PopOut() + { + base.PopOut(); + pauseLoop?.VolumeTo(0.0f); + } + + } } From 836386d03ba0da92f7b625d3cc361110512d15a8 Mon Sep 17 00:00:00 2001 From: BananeVolante Date: Mon, 22 Jun 2020 15:22:13 +0200 Subject: [PATCH 0085/1782] removed duplicate lines --- osu.Game/Screens/Play/PauseOverlay.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Play/PauseOverlay.cs b/osu.Game/Screens/Play/PauseOverlay.cs index 6cca0c47fd..fc4e509c2c 100644 --- a/osu.Game/Screens/Play/PauseOverlay.cs +++ b/osu.Game/Screens/Play/PauseOverlay.cs @@ -14,7 +14,7 @@ using osu.Framework.Graphics.Audio; using osu.Game.Graphics; using osuTK.Graphics; using osu.Framework.Logging; - +using SharpCompress.Common; namespace osu.Game.Screens.Play { @@ -43,7 +43,6 @@ namespace osu.Game.Screens.Play { Looping = true, }); - pauseLoop?.VolumeTo(0.0f); pauseLoop?.Play(); } } @@ -58,7 +57,7 @@ namespace osu.Game.Screens.Play protected override void PopOut() { base.PopOut(); - pauseLoop?.VolumeTo(0.0f); + pauseLoop.VolumeTo(0.0f); } From 624ad65806da84f82078cc188bc80c2feb4d0d54 Mon Sep 17 00:00:00 2001 From: BananeVolante Date: Tue, 23 Jun 2020 13:09:24 +0200 Subject: [PATCH 0086/1782] formating --- osu.Game/Screens/Play/PauseOverlay.cs | 17 +++++------------ osu.Game/Screens/Play/Player.cs | 18 +++--------------- 2 files changed, 8 insertions(+), 27 deletions(-) diff --git a/osu.Game/Screens/Play/PauseOverlay.cs b/osu.Game/Screens/Play/PauseOverlay.cs index fc4e509c2c..191bf0d901 100644 --- a/osu.Game/Screens/Play/PauseOverlay.cs +++ b/osu.Game/Screens/Play/PauseOverlay.cs @@ -3,18 +3,12 @@ using System; using System.Linq; -using System.Runtime.CompilerServices; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using NUnit.Framework.Internal; using osu.Framework.Allocation; using osu.Framework.Audio; -using osu.Framework.Audio.Sample; using osu.Framework.Graphics; using osu.Framework.Graphics.Audio; using osu.Game.Graphics; using osuTK.Graphics; -using osu.Framework.Logging; -using SharpCompress.Common; namespace osu.Game.Screens.Play { @@ -37,17 +31,18 @@ namespace osu.Game.Screens.Play AddButton("Quit", new Color4(170, 27, 39, 255), () => OnQuit?.Invoke()); var sampleChannel = audio.Samples.Get(@"Gameplay/pause-loop"); + if (sampleChannel != null) { - AddInternal(pauseLoop = new DrawableSample(sampleChannel) + pauseLoop = new DrawableSample(sampleChannel) { Looping = true, - }); + }; + AddInternal(pauseLoop); pauseLoop?.Play(); } } - protected override void PopIn() { base.PopIn(); @@ -57,9 +52,7 @@ namespace osu.Game.Screens.Play protected override void PopOut() { base.PopOut(); - pauseLoop.VolumeTo(0.0f); + pauseLoop?.VolumeTo(0.0f); } - - } } diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index ce790e1315..d3b88e56ae 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -5,13 +5,11 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; -using Humanizer; 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.Audio; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Events; using osu.Framework.Logging; @@ -80,7 +78,7 @@ namespace osu.Game.Screens.Play private IAPIProvider api { get; set; } private SampleChannel sampleRestart; - + public BreakOverlay BreakOverlay; private BreakTracker breakTracker; @@ -412,9 +410,7 @@ namespace osu.Game.Screens.Play if (canPause) Pause(); else - { this.Exit(); - } } /// @@ -567,20 +563,19 @@ namespace osu.Game.Screens.Play DrawableRuleset.CancelResume(); IsResuming = false; } + GameplayClockContainer.Stop(); PauseOverlay.Show(); lastPauseActionTime = GameplayClockContainer.GameplayClock.CurrentTime; - } public void Resume() { if (!canResume) return; - IsResuming = true; PauseOverlay.Hide(); - + // breaks and time-based conditions may allow instant resume. if (breakTracker.IsBreakTime.Value) completeResume(); @@ -671,9 +666,7 @@ namespace osu.Game.Screens.Play // as we are no longer the current screen, we cannot guarantee the track is still usable. GameplayClockContainer?.StopUsingBeatmapClock(); - fadeOut(); - return base.OnExiting(next); } @@ -718,12 +711,7 @@ namespace osu.Game.Screens.Play Background.EnableUserDim.Value = false; storyboardReplacesBackground.Value = false; } - #endregion - } - - - } From a47d34f1db3ef3f69be44b0194630b06ebefda84 Mon Sep 17 00:00:00 2001 From: Shivam Date: Tue, 23 Jun 2020 23:34:26 +0200 Subject: [PATCH 0087/1782] make ignore properties protected virtual get-only in base --- .../NonVisual/CustomDataDirectoryTest.cs | 5 +++-- osu.Game/IO/MigratableStorage.cs | 11 +++------- osu.Game/IO/OsuStorage.cs | 20 +++++++++++++++++++ 3 files changed, 26 insertions(+), 10 deletions(-) diff --git a/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs b/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs index 5abefe3198..5278837073 100644 --- a/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs +++ b/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs @@ -126,6 +126,7 @@ namespace osu.Game.Tests.NonVisual { var osu = loadOsu(host); var storage = osu.Dependencies.Get(); + var osuStorage = storage as OsuStorage; // ensure we perform a save host.Dependencies.Get().Save(); @@ -151,13 +152,13 @@ namespace osu.Game.Tests.NonVisual Assert.That(!host.Storage.ExistsDirectory(Path.Combine("test-nested", "cache"))); Assert.That(storage.ExistsDirectory(Path.Combine("test-nested", "cache"))); - foreach (var file in MigratableStorage.IGNORE_FILES) + foreach (var file in osuStorage.IGNORE_FILES) { Assert.That(host.Storage.Exists(file), Is.True); Assert.That(storage.Exists(file), Is.False); } - foreach (var dir in MigratableStorage.IGNORE_DIRECTORIES) + foreach (var dir in osuStorage.IGNORE_DIRECTORIES) { Assert.That(host.Storage.ExistsDirectory(dir), Is.True); Assert.That(storage.ExistsDirectory(dir), Is.False); diff --git a/osu.Game/IO/MigratableStorage.cs b/osu.Game/IO/MigratableStorage.cs index c4dc4bcfb2..0656e61f10 100644 --- a/osu.Game/IO/MigratableStorage.cs +++ b/osu.Game/IO/MigratableStorage.cs @@ -14,14 +14,9 @@ namespace osu.Game.IO /// public abstract class MigratableStorage : WrappedStorage { - internal static readonly string[] IGNORE_DIRECTORIES = { "cache" }; - - internal static readonly string[] IGNORE_FILES = - { - "framework.ini", - "storage.ini" - }; - + internal virtual string[] IGNORE_DIRECTORIES { get; } + internal virtual string[] IGNORE_FILES { get; } + protected MigratableStorage(Storage storage, string subPath = null) : base(storage, subPath) { diff --git a/osu.Game/IO/OsuStorage.cs b/osu.Game/IO/OsuStorage.cs index 3d224841f3..bbec6eb575 100644 --- a/osu.Game/IO/OsuStorage.cs +++ b/osu.Game/IO/OsuStorage.cs @@ -14,6 +14,26 @@ namespace osu.Game.IO private readonly GameHost host; private readonly StorageConfigManager storageConfig; + internal override string[] IGNORE_DIRECTORIES + { + get + { + return new string[] { "cache" }; + } + } + + internal override string[] IGNORE_FILES + { + get + { + return new string[] + { + "framework.ini", + "storage.ini" + }; + } + } + public OsuStorage(GameHost host) : base(host.Storage, string.Empty) { From 8b9cf6fc52e84ba1cd0f3ceac2c9561f3e6d0c3c Mon Sep 17 00:00:00 2001 From: Shivam Date: Tue, 23 Jun 2020 23:57:58 +0200 Subject: [PATCH 0088/1782] Remove default value in Storagemgr --- .../Configuration/TournamentStorageManager.cs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/osu.Game.Tournament/Configuration/TournamentStorageManager.cs b/osu.Game.Tournament/Configuration/TournamentStorageManager.cs index 653ea14352..e3d0a9e75c 100644 --- a/osu.Game.Tournament/Configuration/TournamentStorageManager.cs +++ b/osu.Game.Tournament/Configuration/TournamentStorageManager.cs @@ -14,12 +14,6 @@ namespace osu.Game.Tournament.Configuration : base(storage) { } - - protected override void InitialiseDefaults() - { - base.InitialiseDefaults(); - Set(StorageConfig.CurrentTournament, string.Empty); - } } public enum StorageConfig From 8e8458ab8fa082a7956265098a6a539f4be09244 Mon Sep 17 00:00:00 2001 From: Shivam Date: Tue, 23 Jun 2020 23:58:28 +0200 Subject: [PATCH 0089/1782] make migrate public abstract in base and override --- osu.Game.Tournament/IO/TournamentStorage.cs | 8 ++++---- osu.Game/IO/MigratableStorage.cs | 4 +++- osu.Game/IO/OsuStorage.cs | 2 +- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tournament/IO/TournamentStorage.cs b/osu.Game.Tournament/IO/TournamentStorage.cs index ebd8d2b63f..5c1d9a39c5 100644 --- a/osu.Game.Tournament/IO/TournamentStorage.cs +++ b/osu.Game.Tournament/IO/TournamentStorage.cs @@ -14,7 +14,7 @@ namespace osu.Game.Tournament.IO private readonly Storage storage; internal readonly TournamentVideoResourceStore VideoStore; private const string default_tournament = "default"; - + public TournamentStorage(Storage storage) : base(storage.GetStorageForDirectory("tournaments"), string.Empty) { @@ -30,7 +30,7 @@ namespace osu.Game.Tournament.IO } else { - migrate(); + Migrate(GetFullPath(default_tournament)); storageConfig.Set(StorageConfig.CurrentTournament, default_tournament); storageConfig.Save(); ChangeTargetStorage(UnderlyingStorage.GetStorageForDirectory(default_tournament)); @@ -40,10 +40,10 @@ namespace osu.Game.Tournament.IO Logger.Log("Using tournament storage: " + GetFullPath(string.Empty)); } - private void migrate() + override public void Migrate(string newLocation) { var source = new DirectoryInfo(storage.GetFullPath("tournament")); - var destination = new DirectoryInfo(GetFullPath(default_tournament)); + var destination = new DirectoryInfo(newLocation); if (source.Exists) { diff --git a/osu.Game/IO/MigratableStorage.cs b/osu.Game/IO/MigratableStorage.cs index 0656e61f10..0f064dfe2d 100644 --- a/osu.Game/IO/MigratableStorage.cs +++ b/osu.Game/IO/MigratableStorage.cs @@ -16,12 +16,14 @@ namespace osu.Game.IO { internal virtual string[] IGNORE_DIRECTORIES { get; } internal virtual string[] IGNORE_FILES { get; } - + protected MigratableStorage(Storage storage, string subPath = null) : base(storage, subPath) { } + abstract public void Migrate(string newLocation); + protected void DeleteRecursive(DirectoryInfo target, bool topLevelExcludes = true) { foreach (System.IO.FileInfo fi in target.GetFiles()) diff --git a/osu.Game/IO/OsuStorage.cs b/osu.Game/IO/OsuStorage.cs index bbec6eb575..8890ecf843 100644 --- a/osu.Game/IO/OsuStorage.cs +++ b/osu.Game/IO/OsuStorage.cs @@ -53,7 +53,7 @@ namespace osu.Game.IO Logger.Storage = UnderlyingStorage.GetStorageForDirectory("logs"); } - public void Migrate(string newLocation) + override public void Migrate(string newLocation) { var source = new DirectoryInfo(GetFullPath(".")); var destination = new DirectoryInfo(newLocation); From 7a3315dcf82b5aea1ce12343ca81d9da95b6176f Mon Sep 17 00:00:00 2001 From: Shivam Date: Wed, 24 Jun 2020 00:00:21 +0200 Subject: [PATCH 0090/1782] invert and early return --- osu.Game.Tournament/IO/TournamentStorage.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/osu.Game.Tournament/IO/TournamentStorage.cs b/osu.Game.Tournament/IO/TournamentStorage.cs index 5c1d9a39c5..5f90598890 100644 --- a/osu.Game.Tournament/IO/TournamentStorage.cs +++ b/osu.Game.Tournament/IO/TournamentStorage.cs @@ -14,7 +14,7 @@ namespace osu.Game.Tournament.IO private readonly Storage storage; internal readonly TournamentVideoResourceStore VideoStore; private const string default_tournament = "default"; - + public TournamentStorage(Storage storage) : base(storage.GetStorageForDirectory("tournaments"), string.Empty) { @@ -60,13 +60,13 @@ namespace osu.Game.Tournament.IO private void moveFileIfExists(string file, DirectoryInfo destination) { - if (storage.Exists(file)) - { - Logger.Log($"Migrating {file} to default tournament storage."); - var fileInfo = new System.IO.FileInfo(storage.GetFullPath(file)); - AttemptOperation(() => fileInfo.CopyTo(Path.Combine(destination.FullName, fileInfo.Name), true)); - fileInfo.Delete(); - } + if (!storage.Exists(file)) + return; + + Logger.Log($"Migrating {file} to default tournament storage."); + var fileInfo = new System.IO.FileInfo(storage.GetFullPath(file)); + AttemptOperation(() => fileInfo.CopyTo(Path.Combine(destination.FullName, fileInfo.Name), true)); + fileInfo.Delete(); } } } From 0ca8c961c8148813726a1172cc680b6d10a0fffb Mon Sep 17 00:00:00 2001 From: Shivam Date: Wed, 24 Jun 2020 00:04:57 +0200 Subject: [PATCH 0091/1782] Remove string interpolation & unnecessary test setup --- .../NonVisual/CustomTourneyDirectoryTest.cs | 5 ----- osu.Game.Tournament/Components/TourneyVideo.cs | 2 +- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs b/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs index 9e6675e09f..29e1725c6d 100644 --- a/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs +++ b/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs @@ -18,11 +18,6 @@ namespace osu.Game.Tournament.Tests.NonVisual [TestFixture] public class CustomTourneyDirectoryTest { - [SetUp] - public void SetUp() - { - } - [Test] public void TestDefaultDirectory() { diff --git a/osu.Game.Tournament/Components/TourneyVideo.cs b/osu.Game.Tournament/Components/TourneyVideo.cs index 5a595f4f44..0052b9a431 100644 --- a/osu.Game.Tournament/Components/TourneyVideo.cs +++ b/osu.Game.Tournament/Components/TourneyVideo.cs @@ -30,7 +30,7 @@ namespace osu.Game.Tournament.Components [BackgroundDependencyLoader] private void load(TournamentStorage storage) { - var stream = storage.VideoStore.GetStream($@"{filename}"); + var stream = storage.VideoStore.GetStream(filename); if (stream != null) { From e5851be9ad222094f0d710c79ac8d5d8567d439d Mon Sep 17 00:00:00 2001 From: Shivam Date: Wed, 24 Jun 2020 00:06:27 +0200 Subject: [PATCH 0092/1782] change accessor from internal readonly to public get-only Also changes the class accessor from internal to public --- osu.Game.Tournament/IO/TournamentStorage.cs | 2 +- osu.Game.Tournament/IO/TournamentVideoResourceStore.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tournament/IO/TournamentStorage.cs b/osu.Game.Tournament/IO/TournamentStorage.cs index 5f90598890..14ff8d59e5 100644 --- a/osu.Game.Tournament/IO/TournamentStorage.cs +++ b/osu.Game.Tournament/IO/TournamentStorage.cs @@ -12,7 +12,7 @@ namespace osu.Game.Tournament.IO public class TournamentStorage : MigratableStorage { private readonly Storage storage; - internal readonly TournamentVideoResourceStore VideoStore; + public TournamentVideoResourceStore VideoStore { get; } private const string default_tournament = "default"; public TournamentStorage(Storage storage) diff --git a/osu.Game.Tournament/IO/TournamentVideoResourceStore.cs b/osu.Game.Tournament/IO/TournamentVideoResourceStore.cs index 1ccd20fe21..4b26840b79 100644 --- a/osu.Game.Tournament/IO/TournamentVideoResourceStore.cs +++ b/osu.Game.Tournament/IO/TournamentVideoResourceStore.cs @@ -6,7 +6,7 @@ using osu.Framework.Platform; namespace osu.Game.Tournament.IO { - internal class TournamentVideoResourceStore : NamespacedResourceStore + public class TournamentVideoResourceStore : NamespacedResourceStore { public TournamentVideoResourceStore(Storage storage) : base(new StorageBackedResourceStore(storage), "videos") From 9d2392b6b1c8fccd9e15625a72cabf21419c6028 Mon Sep 17 00:00:00 2001 From: Shivam Date: Wed, 24 Jun 2020 00:14:44 +0200 Subject: [PATCH 0093/1782] Cache TournamentStorage as Storage and only cast when necessary --- osu.Game.Tournament/Components/TourneyVideo.cs | 7 ++++--- .../Screens/Drawings/Components/StorageBackedTeamList.cs | 6 +++--- osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs | 6 +++--- osu.Game.Tournament/TournamentGameBase.cs | 2 +- 4 files changed, 11 insertions(+), 10 deletions(-) diff --git a/osu.Game.Tournament/Components/TourneyVideo.cs b/osu.Game.Tournament/Components/TourneyVideo.cs index 0052b9a431..17d4eb7a28 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.Platform; using osu.Framework.Timing; using osu.Game.Graphics; using osu.Game.Tournament.IO; @@ -18,7 +19,6 @@ namespace osu.Game.Tournament.Components private readonly string filename; private readonly bool drawFallbackGradient; private Video video; - private ManualClock manualClock; public TourneyVideo(string filename, bool drawFallbackGradient = false) @@ -28,9 +28,10 @@ namespace osu.Game.Tournament.Components } [BackgroundDependencyLoader] - private void load(TournamentStorage storage) + private void load(Storage storage) { - var stream = storage.VideoStore.GetStream(filename); + var tournamentStorage = storage as TournamentStorage; + var stream = tournamentStorage.VideoStore.GetStream(filename); if (stream != null) { diff --git a/osu.Game.Tournament/Screens/Drawings/Components/StorageBackedTeamList.cs b/osu.Game.Tournament/Screens/Drawings/Components/StorageBackedTeamList.cs index ecc23181be..f96ec01cbb 100644 --- a/osu.Game.Tournament/Screens/Drawings/Components/StorageBackedTeamList.cs +++ b/osu.Game.Tournament/Screens/Drawings/Components/StorageBackedTeamList.cs @@ -5,7 +5,7 @@ using System; using System.Collections.Generic; using System.IO; using osu.Framework.Logging; -using osu.Game.Tournament.IO; +using osu.Framework.Platform; using osu.Game.Tournament.Models; namespace osu.Game.Tournament.Screens.Drawings.Components @@ -14,9 +14,9 @@ namespace osu.Game.Tournament.Screens.Drawings.Components { private const string teams_filename = "drawings.txt"; - private readonly TournamentStorage storage; + private readonly Storage storage; - public StorageBackedTeamList(TournamentStorage storage) + public StorageBackedTeamList(Storage storage) { this.storage = storage; } diff --git a/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs b/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs index 8b6bd21ee6..e10154b722 100644 --- a/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs +++ b/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs @@ -12,9 +12,9 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; using osu.Framework.Logging; +using osu.Framework.Platform; using osu.Game.Graphics; using osu.Game.Tournament.Components; -using osu.Game.Tournament.IO; using osu.Game.Tournament.Models; using osu.Game.Tournament.Screens.Drawings.Components; using osuTK; @@ -36,12 +36,12 @@ namespace osu.Game.Tournament.Screens.Drawings private Task writeOp; - private TournamentStorage storage; + private Storage storage; public ITeamList TeamList; [BackgroundDependencyLoader] - private void load(TextureStore textures, TournamentStorage storage) + private void load(TextureStore textures, Storage storage) { RelativeSizeAxes = Axes.Both; diff --git a/osu.Game.Tournament/TournamentGameBase.cs b/osu.Game.Tournament/TournamentGameBase.cs index 0702b435a5..6a533f96d8 100644 --- a/osu.Game.Tournament/TournamentGameBase.cs +++ b/osu.Game.Tournament/TournamentGameBase.cs @@ -39,7 +39,7 @@ namespace osu.Game.Tournament { Resources.AddStore(new DllResourceStore(typeof(TournamentGameBase).Assembly)); - dependencies.CacheAs(storage = new TournamentStorage(baseStorage)); + dependencies.CacheAs(storage = new TournamentStorage(baseStorage)); Textures.AddStore(new TextureLoaderStore(storage.VideoStore)); From c32ef5e718c4a7df5908ea8dcbce9d998f3a1926 Mon Sep 17 00:00:00 2001 From: Shivam Date: Wed, 24 Jun 2020 00:37:29 +0200 Subject: [PATCH 0094/1782] Address formatting issues --- .../NonVisual/CustomDataDirectoryTest.cs | 4 ++-- osu.Game.Tournament/IO/TournamentStorage.cs | 2 +- osu.Game/IO/MigratableStorage.cs | 14 +++++------ osu.Game/IO/OsuStorage.cs | 24 +++++-------------- 4 files changed, 16 insertions(+), 28 deletions(-) diff --git a/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs b/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs index 5278837073..125d2b3ef7 100644 --- a/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs +++ b/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs @@ -152,13 +152,13 @@ namespace osu.Game.Tests.NonVisual Assert.That(!host.Storage.ExistsDirectory(Path.Combine("test-nested", "cache"))); Assert.That(storage.ExistsDirectory(Path.Combine("test-nested", "cache"))); - foreach (var file in osuStorage.IGNORE_FILES) + foreach (var file in osuStorage.IgnoreFiles) { Assert.That(host.Storage.Exists(file), Is.True); Assert.That(storage.Exists(file), Is.False); } - foreach (var dir in osuStorage.IGNORE_DIRECTORIES) + foreach (var dir in osuStorage.IgnoreDirectories) { Assert.That(host.Storage.ExistsDirectory(dir), Is.True); Assert.That(storage.ExistsDirectory(dir), Is.False); diff --git a/osu.Game.Tournament/IO/TournamentStorage.cs b/osu.Game.Tournament/IO/TournamentStorage.cs index 14ff8d59e5..b906ea6c50 100644 --- a/osu.Game.Tournament/IO/TournamentStorage.cs +++ b/osu.Game.Tournament/IO/TournamentStorage.cs @@ -40,7 +40,7 @@ namespace osu.Game.Tournament.IO Logger.Log("Using tournament storage: " + GetFullPath(string.Empty)); } - override public void Migrate(string newLocation) + public override void Migrate(string newLocation) { var source = new DirectoryInfo(storage.GetFullPath("tournament")); var destination = new DirectoryInfo(newLocation); diff --git a/osu.Game/IO/MigratableStorage.cs b/osu.Game/IO/MigratableStorage.cs index 0f064dfe2d..41a057d016 100644 --- a/osu.Game/IO/MigratableStorage.cs +++ b/osu.Game/IO/MigratableStorage.cs @@ -14,21 +14,21 @@ namespace osu.Game.IO /// public abstract class MigratableStorage : WrappedStorage { - internal virtual string[] IGNORE_DIRECTORIES { get; } - internal virtual string[] IGNORE_FILES { get; } + internal virtual string[] IgnoreDirectories => new string[] { }; + internal virtual string[] IgnoreFiles => new string[] { }; protected MigratableStorage(Storage storage, string subPath = null) : base(storage, subPath) { } - abstract public void Migrate(string newLocation); + public abstract void Migrate(string newLocation); protected void DeleteRecursive(DirectoryInfo target, bool topLevelExcludes = true) { foreach (System.IO.FileInfo fi in target.GetFiles()) { - if (topLevelExcludes && IGNORE_FILES.Contains(fi.Name)) + if (topLevelExcludes && IgnoreFiles.Contains(fi.Name)) continue; AttemptOperation(() => fi.Delete()); @@ -36,7 +36,7 @@ namespace osu.Game.IO foreach (DirectoryInfo dir in target.GetDirectories()) { - if (topLevelExcludes && IGNORE_DIRECTORIES.Contains(dir.Name)) + if (topLevelExcludes && IgnoreDirectories.Contains(dir.Name)) continue; AttemptOperation(() => dir.Delete(true)); @@ -54,7 +54,7 @@ namespace osu.Game.IO foreach (System.IO.FileInfo fi in source.GetFiles()) { - if (topLevelExcludes && IGNORE_FILES.Contains(fi.Name)) + if (topLevelExcludes && IgnoreFiles.Contains(fi.Name)) continue; AttemptOperation(() => fi.CopyTo(Path.Combine(destination.FullName, fi.Name), true)); @@ -62,7 +62,7 @@ namespace osu.Game.IO foreach (DirectoryInfo dir in source.GetDirectories()) { - if (topLevelExcludes && IGNORE_DIRECTORIES.Contains(dir.Name)) + if (topLevelExcludes && IgnoreDirectories.Contains(dir.Name)) continue; CopyRecursive(dir, destination.CreateSubdirectory(dir.Name), false); diff --git a/osu.Game/IO/OsuStorage.cs b/osu.Game/IO/OsuStorage.cs index 8890ecf843..514f172f74 100644 --- a/osu.Game/IO/OsuStorage.cs +++ b/osu.Game/IO/OsuStorage.cs @@ -14,25 +14,13 @@ namespace osu.Game.IO private readonly GameHost host; private readonly StorageConfigManager storageConfig; - internal override string[] IGNORE_DIRECTORIES - { - get - { - return new string[] { "cache" }; - } - } + internal override string[] IgnoreDirectories => new[] { "cache" }; - internal override string[] IGNORE_FILES + internal override string[] IgnoreFiles => new[] { - get - { - return new string[] - { - "framework.ini", - "storage.ini" - }; - } - } + "framework.ini", + "storage.ini" + }; public OsuStorage(GameHost host) : base(host.Storage, string.Empty) @@ -53,7 +41,7 @@ namespace osu.Game.IO Logger.Storage = UnderlyingStorage.GetStorageForDirectory("logs"); } - override public void Migrate(string newLocation) + public override void Migrate(string newLocation) { var source = new DirectoryInfo(GetFullPath(".")); var destination = new DirectoryInfo(newLocation); From af1134084948db534072997b41ee7a7e5758c2b0 Mon Sep 17 00:00:00 2001 From: Shivam Date: Wed, 24 Jun 2020 02:13:28 +0200 Subject: [PATCH 0095/1782] Fix nullref exceptions and redundant explicit type --- osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs | 4 ++-- osu.Game.Tournament/Components/TourneyVideo.cs | 3 +-- osu.Game/IO/MigratableStorage.cs | 4 ++-- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs b/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs index 125d2b3ef7..c8a5988104 100644 --- a/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs +++ b/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs @@ -152,13 +152,13 @@ namespace osu.Game.Tests.NonVisual Assert.That(!host.Storage.ExistsDirectory(Path.Combine("test-nested", "cache"))); Assert.That(storage.ExistsDirectory(Path.Combine("test-nested", "cache"))); - foreach (var file in osuStorage.IgnoreFiles) + foreach (var file in osuStorage?.IgnoreFiles ?? Array.Empty()) { Assert.That(host.Storage.Exists(file), Is.True); Assert.That(storage.Exists(file), Is.False); } - foreach (var dir in osuStorage.IgnoreDirectories) + foreach (var dir in osuStorage?.IgnoreDirectories ?? Array.Empty()) { Assert.That(host.Storage.ExistsDirectory(dir), Is.True); Assert.That(storage.ExistsDirectory(dir), Is.False); diff --git a/osu.Game.Tournament/Components/TourneyVideo.cs b/osu.Game.Tournament/Components/TourneyVideo.cs index 17d4eb7a28..794b72b3a9 100644 --- a/osu.Game.Tournament/Components/TourneyVideo.cs +++ b/osu.Game.Tournament/Components/TourneyVideo.cs @@ -30,8 +30,7 @@ namespace osu.Game.Tournament.Components [BackgroundDependencyLoader] private void load(Storage storage) { - var tournamentStorage = storage as TournamentStorage; - var stream = tournamentStorage.VideoStore.GetStream(filename); + var stream = (storage as TournamentStorage)?.VideoStore.GetStream(filename); if (stream != null) { diff --git a/osu.Game/IO/MigratableStorage.cs b/osu.Game/IO/MigratableStorage.cs index 41a057d016..ec85e0bac9 100644 --- a/osu.Game/IO/MigratableStorage.cs +++ b/osu.Game/IO/MigratableStorage.cs @@ -14,8 +14,8 @@ namespace osu.Game.IO /// public abstract class MigratableStorage : WrappedStorage { - internal virtual string[] IgnoreDirectories => new string[] { }; - internal virtual string[] IgnoreFiles => new string[] { }; + internal virtual string[] IgnoreDirectories => Array.Empty(); + internal virtual string[] IgnoreFiles => Array.Empty(); protected MigratableStorage(Storage storage, string subPath = null) : base(storage, subPath) From 839f197111c55d00474da01aced1a07e46a7fe69 Mon Sep 17 00:00:00 2001 From: Shivam Date: Wed, 24 Jun 2020 02:37:59 +0200 Subject: [PATCH 0096/1782] Change type from TournamentStorage to Storage in tests --- .../NonVisual/CustomTourneyDirectoryTest.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs b/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs index 29e1725c6d..4cede15d7a 100644 --- a/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs +++ b/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs @@ -10,7 +10,6 @@ using osu.Framework; using osu.Framework.Allocation; using osu.Framework.Platform; using osu.Game.Tournament.Configuration; -using osu.Game.Tournament.IO; using osu.Game.Tests; namespace osu.Game.Tournament.Tests.NonVisual @@ -26,7 +25,7 @@ namespace osu.Game.Tournament.Tests.NonVisual try { var osu = loadOsu(host); - var storage = osu.Dependencies.Get(); + var storage = osu.Dependencies.Get(); var defaultStorage = Path.Combine(tournamentBasePath(nameof(TestDefaultDirectory)), "default"); Assert.That(storage.GetFullPath("."), Is.EqualTo(defaultStorage)); } @@ -57,7 +56,7 @@ namespace osu.Game.Tournament.Tests.NonVisual { var osu = loadOsu(host); - storage = osu.Dependencies.Get(); + storage = osu.Dependencies.Get(); Assert.That(storage.GetFullPath("."), Is.EqualTo(Path.Combine(tournamentBasePath(nameof(TestCustomDirectory)), "custom"))); } @@ -113,7 +112,7 @@ namespace osu.Game.Tournament.Tests.NonVisual { var osu = loadOsu(host); - var storage = osu.Dependencies.Get(); + var storage = osu.Dependencies.Get(); var migratedPath = Path.Combine(tournamentBasePath(nameof(TestMigration)), "default"); From c94f95cc0d6bf3ba92098d6fe5f3190d8ddf4153 Mon Sep 17 00:00:00 2001 From: Shivam Date: Wed, 24 Jun 2020 02:40:22 +0200 Subject: [PATCH 0097/1782] Check if the file exists before reading This is (also) to address the review from bdach about StorageManager initialising a default value that gets overwritten upon migration anyway. --- osu.Game.Tournament/IO/TournamentStorage.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tournament/IO/TournamentStorage.cs b/osu.Game.Tournament/IO/TournamentStorage.cs index b906ea6c50..ed1bfb7449 100644 --- a/osu.Game.Tournament/IO/TournamentStorage.cs +++ b/osu.Game.Tournament/IO/TournamentStorage.cs @@ -22,11 +22,9 @@ namespace osu.Game.Tournament.IO TournamentStorageManager storageConfig = new TournamentStorageManager(storage); - var currentTournament = storageConfig.Get(StorageConfig.CurrentTournament); - - if (!string.IsNullOrEmpty(currentTournament)) + if (storage.Exists("tournament.ini")) { - ChangeTargetStorage(UnderlyingStorage.GetStorageForDirectory(currentTournament)); + ChangeTargetStorage(UnderlyingStorage.GetStorageForDirectory(storageConfig.Get(StorageConfig.CurrentTournament))); } else { From 53107973a33a82bf5b1b70bb158d293b183536eb Mon Sep 17 00:00:00 2001 From: BananeVolante <42553638+BananeVolante@users.noreply.github.com> Date: Wed, 24 Jun 2020 14:01:13 +0200 Subject: [PATCH 0098/1782] merged 2 lines Co-authored-by: Salman Ahmed --- osu.Game/Screens/Play/PauseOverlay.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Play/PauseOverlay.cs b/osu.Game/Screens/Play/PauseOverlay.cs index 191bf0d901..2656ef1ebd 100644 --- a/osu.Game/Screens/Play/PauseOverlay.cs +++ b/osu.Game/Screens/Play/PauseOverlay.cs @@ -34,11 +34,10 @@ namespace osu.Game.Screens.Play if (sampleChannel != null) { - pauseLoop = new DrawableSample(sampleChannel) + AddInternal(pauseLoop = new DrawableSample(sampleChannel) { Looping = true, - }; - AddInternal(pauseLoop); + }); pauseLoop?.Play(); } } From 2e8f30461f63db8c651b20a463f8e4f34ae23c8a Mon Sep 17 00:00:00 2001 From: BananeVolante Date: Wed, 24 Jun 2020 14:22:12 +0200 Subject: [PATCH 0099/1782] play/stops music when entering the pause overlay, instead of letting it play silently in the background --- osu.Game/Screens/Play/PauseOverlay.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/PauseOverlay.cs b/osu.Game/Screens/Play/PauseOverlay.cs index 2656ef1ebd..8b35c69aa7 100644 --- a/osu.Game/Screens/Play/PauseOverlay.cs +++ b/osu.Game/Screens/Play/PauseOverlay.cs @@ -38,13 +38,13 @@ namespace osu.Game.Screens.Play { Looping = true, }); - pauseLoop?.Play(); } } protected override void PopIn() { base.PopIn(); + pauseLoop?.Play(); pauseLoop?.VolumeTo(1.0f, 400, Easing.InQuint); } @@ -52,6 +52,7 @@ namespace osu.Game.Screens.Play { base.PopOut(); pauseLoop?.VolumeTo(0.0f); + pauseLoop?.Stop(); } } } From 063503f4db9ec775ffaa1204fc2e69a55d25329c Mon Sep 17 00:00:00 2001 From: Shivam Date: Wed, 24 Jun 2020 20:43:56 +0200 Subject: [PATCH 0100/1782] Move null check outside of the loop --- .../NonVisual/CustomDataDirectoryTest.cs | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs b/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs index c8a5988104..4149e3d3ef 100644 --- a/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs +++ b/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs @@ -152,16 +152,19 @@ namespace osu.Game.Tests.NonVisual Assert.That(!host.Storage.ExistsDirectory(Path.Combine("test-nested", "cache"))); Assert.That(storage.ExistsDirectory(Path.Combine("test-nested", "cache"))); - foreach (var file in osuStorage?.IgnoreFiles ?? Array.Empty()) + if (osuStorage != null) { - Assert.That(host.Storage.Exists(file), Is.True); - Assert.That(storage.Exists(file), Is.False); - } + foreach (var file in osuStorage.IgnoreFiles) + { + Assert.That(host.Storage.Exists(file), Is.True); + Assert.That(storage.Exists(file), Is.False); + } - foreach (var dir in osuStorage?.IgnoreDirectories ?? Array.Empty()) - { - Assert.That(host.Storage.ExistsDirectory(dir), Is.True); - Assert.That(storage.ExistsDirectory(dir), Is.False); + foreach (var dir in osuStorage.IgnoreDirectories) + { + Assert.That(host.Storage.ExistsDirectory(dir), Is.True); + Assert.That(storage.ExistsDirectory(dir), Is.False); + } } Assert.That(new StreamReader(host.Storage.GetStream("storage.ini")).ReadToEnd().Contains($"FullPath = {customPath}")); From 47a732ef604ac2f675ef71683d69eb10930d4785 Mon Sep 17 00:00:00 2001 From: Shivam Date: Wed, 24 Jun 2020 23:01:56 +0200 Subject: [PATCH 0101/1782] Address review comments Now asserting instead of an if-statement, change cast from OsuStorage to MigratableStorage and make internal virtual properties protected. --- .../NonVisual/CustomDataDirectoryTest.cs | 25 +++++++++---------- osu.Game/IO/MigratableStorage.cs | 4 +-- osu.Game/IO/OsuStorage.cs | 4 +-- 3 files changed, 16 insertions(+), 17 deletions(-) diff --git a/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs b/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs index 4149e3d3ef..43c1c77786 100644 --- a/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs +++ b/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs @@ -126,7 +126,7 @@ namespace osu.Game.Tests.NonVisual { var osu = loadOsu(host); var storage = osu.Dependencies.Get(); - var osuStorage = storage as OsuStorage; + var osuStorage = storage as MigratableStorage; // ensure we perform a save host.Dependencies.Get().Save(); @@ -152,19 +152,18 @@ namespace osu.Game.Tests.NonVisual Assert.That(!host.Storage.ExistsDirectory(Path.Combine("test-nested", "cache"))); Assert.That(storage.ExistsDirectory(Path.Combine("test-nested", "cache"))); - if (osuStorage != null) - { - foreach (var file in osuStorage.IgnoreFiles) - { - Assert.That(host.Storage.Exists(file), Is.True); - Assert.That(storage.Exists(file), Is.False); - } + Assert.That(osuStorage, Is.Not.Null); - foreach (var dir in osuStorage.IgnoreDirectories) - { - Assert.That(host.Storage.ExistsDirectory(dir), Is.True); - Assert.That(storage.ExistsDirectory(dir), Is.False); - } + foreach (var file in osuStorage.IgnoreFiles) + { + Assert.That(host.Storage.Exists(file), Is.True); + Assert.That(storage.Exists(file), Is.False); + } + + foreach (var dir in osuStorage.IgnoreDirectories) + { + Assert.That(host.Storage.ExistsDirectory(dir), Is.True); + Assert.That(storage.ExistsDirectory(dir), Is.False); } Assert.That(new StreamReader(host.Storage.GetStream("storage.ini")).ReadToEnd().Contains($"FullPath = {customPath}")); diff --git a/osu.Game/IO/MigratableStorage.cs b/osu.Game/IO/MigratableStorage.cs index ec85e0bac9..faa39d2ef8 100644 --- a/osu.Game/IO/MigratableStorage.cs +++ b/osu.Game/IO/MigratableStorage.cs @@ -14,8 +14,8 @@ namespace osu.Game.IO /// public abstract class MigratableStorage : WrappedStorage { - internal virtual string[] IgnoreDirectories => Array.Empty(); - internal virtual string[] IgnoreFiles => Array.Empty(); + public virtual string[] IgnoreDirectories => Array.Empty(); + public virtual string[] IgnoreFiles => Array.Empty(); protected MigratableStorage(Storage storage, string subPath = null) : base(storage, subPath) diff --git a/osu.Game/IO/OsuStorage.cs b/osu.Game/IO/OsuStorage.cs index 514f172f74..31ee802141 100644 --- a/osu.Game/IO/OsuStorage.cs +++ b/osu.Game/IO/OsuStorage.cs @@ -14,9 +14,9 @@ namespace osu.Game.IO private readonly GameHost host; private readonly StorageConfigManager storageConfig; - internal override string[] IgnoreDirectories => new[] { "cache" }; + public override string[] IgnoreDirectories => new[] { "cache" }; - internal override string[] IgnoreFiles => new[] + public override string[] IgnoreFiles => new[] { "framework.ini", "storage.ini" From 9e5cc1b7a2d19bc7974065d9ed515425acf559dc Mon Sep 17 00:00:00 2001 From: BananeVolante Date: Thu, 25 Jun 2020 13:26:42 +0200 Subject: [PATCH 0102/1782] added skin support for the pause loop --- osu.Game/Screens/Play/PauseOverlay.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/PauseOverlay.cs b/osu.Game/Screens/Play/PauseOverlay.cs index 8b35c69aa7..fc13743fe5 100644 --- a/osu.Game/Screens/Play/PauseOverlay.cs +++ b/osu.Game/Screens/Play/PauseOverlay.cs @@ -7,7 +7,9 @@ using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Graphics; using osu.Framework.Graphics.Audio; +using osu.Game.Audio; using osu.Game.Graphics; +using osu.Game.Skinning; using osuTK.Graphics; namespace osu.Game.Screens.Play @@ -24,13 +26,13 @@ namespace osu.Game.Screens.Play protected override Action BackAction => () => InternalButtons.Children.First().Click(); [BackgroundDependencyLoader] - private void load(OsuColour colours, AudioManager audio) + private void load(OsuColour colours, AudioManager audio, SkinManager skins) { AddButton("Continue", colours.Green, () => OnResume?.Invoke()); AddButton("Retry", colours.YellowDark, () => OnRetry?.Invoke()); AddButton("Quit", new Color4(170, 27, 39, 255), () => OnQuit?.Invoke()); - var sampleChannel = audio.Samples.Get(@"Gameplay/pause-loop"); + var sampleChannel = skins.GetSample(new SampleInfo("pause-loop")) ?? audio.Samples.Get(@"Gameplay/pause-loop"); if (sampleChannel != null) { From 7d2d6a52c92a6871d9443f1700721eb837716d0e Mon Sep 17 00:00:00 2001 From: BananeVolante Date: Thu, 25 Jun 2020 18:58:04 +0200 Subject: [PATCH 0103/1782] now uses SkinnableSample instead of Drawable sample Still does not support switching skins after Pause overlay loading, there will be no sound for the first pause (works fine the the nexts) Also, the pause loop seems to play for approximately 1 second when exiting the screens via restart or quit finally, since SkinnableSound does not play a sound if its aggregate volume is at 0, i had turn up the volume a bit before playing the loop --- osu.Game/Screens/Play/PauseOverlay.cs | 29 +++++++++++++++------------ 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/osu.Game/Screens/Play/PauseOverlay.cs b/osu.Game/Screens/Play/PauseOverlay.cs index fc13743fe5..990d85b1cf 100644 --- a/osu.Game/Screens/Play/PauseOverlay.cs +++ b/osu.Game/Screens/Play/PauseOverlay.cs @@ -3,8 +3,11 @@ using System; using System.Linq; +using Humanizer; +using NUnit.Framework.Internal; using osu.Framework.Allocation; using osu.Framework.Audio; +using osu.Framework.Audio.Sample; using osu.Framework.Graphics; using osu.Framework.Graphics.Audio; using osu.Game.Audio; @@ -21,40 +24,40 @@ namespace osu.Game.Screens.Play public override string Header => "paused"; public override string Description => "you're not going to do what i think you're going to do, are ya?"; - private DrawableSample pauseLoop; + private SkinnableSound pauseLoop; protected override Action BackAction => () => InternalButtons.Children.First().Click(); [BackgroundDependencyLoader] - private void load(OsuColour colours, AudioManager audio, SkinManager skins) + private void load(OsuColour colours) { AddButton("Continue", colours.Green, () => OnResume?.Invoke()); AddButton("Retry", colours.YellowDark, () => OnRetry?.Invoke()); AddButton("Quit", new Color4(170, 27, 39, 255), () => OnQuit?.Invoke()); - var sampleChannel = skins.GetSample(new SampleInfo("pause-loop")) ?? audio.Samples.Get(@"Gameplay/pause-loop"); - - if (sampleChannel != null) + AddInternal(pauseLoop = new SkinnableSound(new SampleInfo("pause-loop")) { - AddInternal(pauseLoop = new DrawableSample(sampleChannel) - { - Looping = true, - }); - } + Looping = true, + }); + } protected override void PopIn() { base.PopIn(); + + //SkinnableSound only plays a sound if its aggregate volume is > 0, so the volume must be turned up before playing it + pauseLoop?.TransformBindableTo(pauseLoop.Volume, 0.00001); + pauseLoop?.TransformBindableTo(pauseLoop.Volume, 1.0f, 400, Easing.InQuint); pauseLoop?.Play(); - pauseLoop?.VolumeTo(1.0f, 400, Easing.InQuint); } protected override void PopOut() { base.PopOut(); - pauseLoop?.VolumeTo(0.0f); - pauseLoop?.Stop(); + pauseLoop?.Stop(); + pauseLoop?.TransformBindableTo(pauseLoop.Volume, 0.0f); } + } } From d82d901542ddb0d4018caf6b89ddc64d307abd8d Mon Sep 17 00:00:00 2001 From: Shivam Date: Thu, 25 Jun 2020 20:36:55 +0200 Subject: [PATCH 0104/1782] Reuse custom_tournament where it was still used as a literal --- .../NonVisual/CustomTourneyDirectoryTest.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs b/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs index 4cede15d7a..ce0ceae2e1 100644 --- a/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs +++ b/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs @@ -58,7 +58,7 @@ namespace osu.Game.Tournament.Tests.NonVisual storage = osu.Dependencies.Get(); - Assert.That(storage.GetFullPath("."), Is.EqualTo(Path.Combine(tournamentBasePath(nameof(TestCustomDirectory)), "custom"))); + Assert.That(storage.GetFullPath("."), Is.EqualTo(Path.Combine(tournamentBasePath(nameof(TestCustomDirectory)), custom_tournament))); } finally { @@ -80,6 +80,7 @@ namespace osu.Game.Tournament.Tests.NonVisual // Recreate the old setup that uses "tournament" as the base path. string oldPath = Path.Combine(osuRoot, "tournament"); + string videosPath = Path.Combine(oldPath, "videos"); string modsPath = Path.Combine(oldPath, "mods"); string flagsPath = Path.Combine(oldPath, "flags"); From a4bb238c4534c4bce43404c79b2e2402fb78b3d1 Mon Sep 17 00:00:00 2001 From: BananeVolante Date: Fri, 26 Jun 2020 14:07:27 +0200 Subject: [PATCH 0105/1782] fixed bug preventing the pause loop from playing during the first pause after changing a skin --- osu.Game/Screens/Play/PauseOverlay.cs | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/Play/PauseOverlay.cs b/osu.Game/Screens/Play/PauseOverlay.cs index 990d85b1cf..81c288f928 100644 --- a/osu.Game/Screens/Play/PauseOverlay.cs +++ b/osu.Game/Screens/Play/PauseOverlay.cs @@ -3,13 +3,8 @@ using System; using System.Linq; -using Humanizer; -using NUnit.Framework.Internal; using osu.Framework.Allocation; -using osu.Framework.Audio; -using osu.Framework.Audio.Sample; using osu.Framework.Graphics; -using osu.Framework.Graphics.Audio; using osu.Game.Audio; using osu.Game.Graphics; using osu.Game.Skinning; @@ -39,7 +34,9 @@ namespace osu.Game.Screens.Play { Looping = true, }); - + // PopIn is called before updating the skin, and when a sample is updated, its "playing" value is reset + // the sample must be played again(and if it plays when it shouldn't, the volume will be at 0) + pauseLoop.OnSkinChanged += () => pauseLoop.Play(); } protected override void PopIn() @@ -55,9 +52,9 @@ namespace osu.Game.Screens.Play protected override void PopOut() { base.PopOut(); - pauseLoop?.Stop(); + + pauseLoop?.Stop(); pauseLoop?.TransformBindableTo(pauseLoop.Volume, 0.0f); } - } } From 0cddb85f1b83eb1c2b7a4dab43ec17b2f4e35cee Mon Sep 17 00:00:00 2001 From: Shivam Date: Sun, 28 Jun 2020 15:27:50 +0200 Subject: [PATCH 0106/1782] Move storageconfig set and saving to migrate method --- .../NonVisual/CustomTourneyDirectoryTest.cs | 2 +- osu.Game.Tournament/IO/TournamentStorage.cs | 11 ++++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs b/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs index ce0ceae2e1..b75a9a6929 100644 --- a/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs +++ b/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs @@ -80,7 +80,7 @@ namespace osu.Game.Tournament.Tests.NonVisual // Recreate the old setup that uses "tournament" as the base path. string oldPath = Path.Combine(osuRoot, "tournament"); - + string videosPath = Path.Combine(oldPath, "videos"); string modsPath = Path.Combine(oldPath, "mods"); string flagsPath = Path.Combine(oldPath, "flags"); diff --git a/osu.Game.Tournament/IO/TournamentStorage.cs b/osu.Game.Tournament/IO/TournamentStorage.cs index ed1bfb7449..ddc298a7ea 100644 --- a/osu.Game.Tournament/IO/TournamentStorage.cs +++ b/osu.Game.Tournament/IO/TournamentStorage.cs @@ -11,16 +11,17 @@ namespace osu.Game.Tournament.IO { public class TournamentStorage : MigratableStorage { - private readonly Storage storage; - public TournamentVideoResourceStore VideoStore { get; } private const string default_tournament = "default"; + private readonly Storage storage; + private readonly TournamentStorageManager storageConfig; + public TournamentVideoResourceStore VideoStore { get; } public TournamentStorage(Storage storage) : base(storage.GetStorageForDirectory("tournaments"), string.Empty) { this.storage = storage; - TournamentStorageManager storageConfig = new TournamentStorageManager(storage); + storageConfig = new TournamentStorageManager(storage); if (storage.Exists("tournament.ini")) { @@ -29,8 +30,6 @@ namespace osu.Game.Tournament.IO else { Migrate(GetFullPath(default_tournament)); - storageConfig.Set(StorageConfig.CurrentTournament, default_tournament); - storageConfig.Save(); ChangeTargetStorage(UnderlyingStorage.GetStorageForDirectory(default_tournament)); } @@ -54,6 +53,8 @@ namespace osu.Game.Tournament.IO moveFileIfExists("drawings.txt", destination); moveFileIfExists("drawings_results.txt", destination); moveFileIfExists("drawings.ini", destination); + storageConfig.Set(StorageConfig.CurrentTournament, default_tournament); + storageConfig.Save(); } private void moveFileIfExists(string file, DirectoryInfo destination) From 641ea5b950f6087d79b24b8339e2f5fa9b4bc10a Mon Sep 17 00:00:00 2001 From: Lucas A Date: Tue, 30 Jun 2020 13:12:33 +0200 Subject: [PATCH 0107/1782] Make the disabling of the win key during gameplay a toggleable setting. --- osu.Game/Configuration/OsuConfigManager.cs | 4 +++- .../Overlays/Settings/Sections/Gameplay/GeneralSettings.cs | 5 +++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 9d31bc9bba..e7a86e080d 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -98,6 +98,7 @@ namespace osu.Game.Configuration Set(OsuSetting.ScoreDisplayMode, ScoringMode.Standardised); Set(OsuSetting.IncreaseFirstObjectVisibility, true); + Set(OsuSetting.GameplayDisableWinKey, true); // Update Set(OsuSetting.ReleaseStream, ReleaseStream.Lazer); @@ -227,6 +228,7 @@ namespace osu.Game.Configuration IntroSequence, UIHoldActivationDelay, HitLighting, - MenuBackgroundSource + MenuBackgroundSource, + GameplayDisableWinKey } } diff --git a/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs index 93a02ea0e4..60197c62b5 100644 --- a/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs @@ -76,6 +76,11 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay { LabelText = "Score display mode", Bindable = config.GetBindable(OsuSetting.ScoreDisplayMode) + }, + new SettingsCheckbox + { + LabelText = "Disable Win key during gameplay", + Bindable = config.GetBindable(OsuSetting.GameplayDisableWinKey) } }; } From ab134c0ed7f92a2c83a503c679a18d1af1e8a1bc Mon Sep 17 00:00:00 2001 From: BananeVolante Date: Wed, 1 Jul 2020 13:27:33 +0200 Subject: [PATCH 0108/1782] removed unneeded information in a comment --- osu.Game/Screens/Play/PauseOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/PauseOverlay.cs b/osu.Game/Screens/Play/PauseOverlay.cs index 81c288f928..56d0e2d958 100644 --- a/osu.Game/Screens/Play/PauseOverlay.cs +++ b/osu.Game/Screens/Play/PauseOverlay.cs @@ -35,7 +35,7 @@ namespace osu.Game.Screens.Play Looping = true, }); // PopIn is called before updating the skin, and when a sample is updated, its "playing" value is reset - // the sample must be played again(and if it plays when it shouldn't, the volume will be at 0) + // the sample must be played again pauseLoop.OnSkinChanged += () => pauseLoop.Play(); } From ab1eb469af357ecde23288cd14294e91c54dbe7e Mon Sep 17 00:00:00 2001 From: BananeVolante Date: Wed, 1 Jul 2020 13:30:23 +0200 Subject: [PATCH 0109/1782] removed unneeded null checks --- osu.Game/Screens/Play/PauseOverlay.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Play/PauseOverlay.cs b/osu.Game/Screens/Play/PauseOverlay.cs index 56d0e2d958..022183d82b 100644 --- a/osu.Game/Screens/Play/PauseOverlay.cs +++ b/osu.Game/Screens/Play/PauseOverlay.cs @@ -44,17 +44,17 @@ namespace osu.Game.Screens.Play base.PopIn(); //SkinnableSound only plays a sound if its aggregate volume is > 0, so the volume must be turned up before playing it - pauseLoop?.TransformBindableTo(pauseLoop.Volume, 0.00001); - pauseLoop?.TransformBindableTo(pauseLoop.Volume, 1.0f, 400, Easing.InQuint); - pauseLoop?.Play(); + pauseLoop.TransformBindableTo(pauseLoop.Volume, 0.00001); + pauseLoop.TransformBindableTo(pauseLoop.Volume, 1.0f, 400, Easing.InQuint); + pauseLoop.Play(); } protected override void PopOut() { base.PopOut(); - pauseLoop?.Stop(); - pauseLoop?.TransformBindableTo(pauseLoop.Volume, 0.0f); + pauseLoop.Stop(); + pauseLoop.TransformBindableTo(pauseLoop.Volume, 0.0f); } } } From fc1eb42a650fef5497bec37e20b5e2a29f773c07 Mon Sep 17 00:00:00 2001 From: Lucas A Date: Wed, 1 Jul 2020 17:15:41 +0200 Subject: [PATCH 0110/1782] Disable windows key while in gameplay. --- osu.Desktop/OsuGameDesktop.cs | 4 + osu.Desktop/Windows/GameplayWinKeyHandler.cs | 39 ++++++++++ osu.Desktop/Windows/WindowsKey.cs | 82 ++++++++++++++++++++ 3 files changed, 125 insertions(+) create mode 100644 osu.Desktop/Windows/GameplayWinKeyHandler.cs create mode 100644 osu.Desktop/Windows/WindowsKey.cs diff --git a/osu.Desktop/OsuGameDesktop.cs b/osu.Desktop/OsuGameDesktop.cs index cd31df316a..d05a4af126 100644 --- a/osu.Desktop/OsuGameDesktop.cs +++ b/osu.Desktop/OsuGameDesktop.cs @@ -16,6 +16,7 @@ using osu.Framework.Logging; using osu.Framework.Screens; using osu.Game.Screens.Menu; using osu.Game.Updater; +using osu.Desktop.Windows; namespace osu.Desktop { @@ -98,6 +99,9 @@ namespace osu.Desktop LoadComponentAsync(versionManager = new VersionManager { Depth = int.MinValue }, Add); LoadComponentAsync(new DiscordRichPresence(), Add); + + if (RuntimeInfo.OS == RuntimeInfo.Platform.Windows) + LoadComponentAsync(new GameplayWinKeyHandler(), Add); } protected override void ScreenChanged(IScreen lastScreen, IScreen newScreen) diff --git a/osu.Desktop/Windows/GameplayWinKeyHandler.cs b/osu.Desktop/Windows/GameplayWinKeyHandler.cs new file mode 100644 index 0000000000..cc0150497b --- /dev/null +++ b/osu.Desktop/Windows/GameplayWinKeyHandler.cs @@ -0,0 +1,39 @@ +// 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.Platform; +using osu.Game.Configuration; + +namespace osu.Desktop.Windows +{ + public class GameplayWinKeyHandler : Component + { + private Bindable winKeyEnabled; + private Bindable disableWinKey; + + private GameHost host; + + [BackgroundDependencyLoader] + private void load(GameHost host, OsuConfigManager config) + { + this.host = host; + + winKeyEnabled = host.AllowScreenSuspension.GetBoundCopy(); + winKeyEnabled.ValueChanged += toggleWinKey; + + disableWinKey = config.GetBindable(OsuSetting.GameplayDisableWinKey); + disableWinKey.BindValueChanged(t => winKeyEnabled.TriggerChange()); + } + + private void toggleWinKey(ValueChangedEvent e) + { + if (!e.NewValue && disableWinKey.Value) + host.InputThread.Scheduler.Add(WindowsKey.Disable); + else + host.InputThread.Scheduler.Add(WindowsKey.Enable); + } + } +} diff --git a/osu.Desktop/Windows/WindowsKey.cs b/osu.Desktop/Windows/WindowsKey.cs new file mode 100644 index 0000000000..748d9c55d6 --- /dev/null +++ b/osu.Desktop/Windows/WindowsKey.cs @@ -0,0 +1,82 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Runtime.InteropServices; + +namespace osu.Desktop.Windows +{ + internal class WindowsKey + { + private delegate int LowLevelKeyboardProcDelegate(int nCode, int wParam, ref KdDllHookStruct lParam); + + private static bool isBlocked; + + private const int wh_keyboard_ll = 13; + private const int wm_keydown = 256; + private const int wm_syskeyup = 261; + + //Resharper disable once NotAccessedField.Local + private static LowLevelKeyboardProcDelegate keyboardHookDelegate; // keeping a reference alive for the GC + private static IntPtr keyHook; + + [StructLayout(LayoutKind.Explicit)] + private struct KdDllHookStruct + { + [FieldOffset(0)] + public readonly int VkCode; + + [FieldOffset(8)] + public readonly int Flags; + } + + private static int lowLevelKeyboardProc(int nCode, int wParam, ref KdDllHookStruct lParam) + { + if (wParam >= wm_keydown && wParam <= wm_syskeyup) + { + switch (lParam.VkCode) + { + case 0x09 when lParam.Flags == 32: // alt + tab + case 0x1b when lParam.Flags == 32: // alt + esc + case 0x5B: // left windows key + case 0x5C: // right windows key + return 1; + } + } + + return callNextHookEx(0, nCode, wParam, ref lParam); + } + + internal static void Disable() + { + if (keyHook != IntPtr.Zero || isBlocked) + return; + + keyHook = setWindowsHookEx(wh_keyboard_ll, (keyboardHookDelegate = lowLevelKeyboardProc), Marshal.GetHINSTANCE(System.Reflection.Assembly.GetExecutingAssembly().GetModules()[0]), 0); + + isBlocked = true; + } + + internal static void Enable() + { + if (keyHook == IntPtr.Zero || !isBlocked) + return; + + keyHook = unhookWindowsHookEx(keyHook); + keyboardHookDelegate = null; + + keyHook = IntPtr.Zero; + + isBlocked = false; + } + + [DllImport(@"user32.dll", EntryPoint = @"SetWindowsHookExA")] + private static extern IntPtr setWindowsHookEx(int idHook, LowLevelKeyboardProcDelegate lpfn, IntPtr hMod, int dwThreadId); + + [DllImport(@"user32.dll", EntryPoint = @"UnhookWindowsHookEx")] + private static extern IntPtr unhookWindowsHookEx(IntPtr hHook); + + [DllImport(@"user32.dll", EntryPoint = @"CallNextHookEx")] + private static extern int callNextHookEx(int hHook, int nCode, int wParam, ref KdDllHookStruct lParam); + } +} From c3cd2a74f5f0ee89e531d564db3d7a5cb9e3ed04 Mon Sep 17 00:00:00 2001 From: Shivam Date: Wed, 1 Jul 2020 22:57:16 +0200 Subject: [PATCH 0111/1782] Move general purpose migration to MigratableStorage --- osu.Game.Tournament/IO/TournamentStorage.cs | 10 +++--- osu.Game/IO/MigratableStorage.cs | 31 +++++++++++++++++- osu.Game/IO/OsuStorage.cs | 36 ++------------------- osu.Game/OsuGameBase.cs | 2 +- 4 files changed, 38 insertions(+), 41 deletions(-) diff --git a/osu.Game.Tournament/IO/TournamentStorage.cs b/osu.Game.Tournament/IO/TournamentStorage.cs index ddc298a7ea..6505135b42 100644 --- a/osu.Game.Tournament/IO/TournamentStorage.cs +++ b/osu.Game.Tournament/IO/TournamentStorage.cs @@ -28,19 +28,16 @@ namespace osu.Game.Tournament.IO ChangeTargetStorage(UnderlyingStorage.GetStorageForDirectory(storageConfig.Get(StorageConfig.CurrentTournament))); } else - { - Migrate(GetFullPath(default_tournament)); - ChangeTargetStorage(UnderlyingStorage.GetStorageForDirectory(default_tournament)); - } + Migrate(UnderlyingStorage.GetStorageForDirectory(default_tournament)); VideoStore = new TournamentVideoResourceStore(this); Logger.Log("Using tournament storage: " + GetFullPath(string.Empty)); } - public override void Migrate(string newLocation) + public override void Migrate(Storage newStorage) { var source = new DirectoryInfo(storage.GetFullPath("tournament")); - var destination = new DirectoryInfo(newLocation); + var destination = new DirectoryInfo(newStorage.GetFullPath(".")); if (source.Exists) { @@ -53,6 +50,7 @@ namespace osu.Game.Tournament.IO moveFileIfExists("drawings.txt", destination); moveFileIfExists("drawings_results.txt", destination); moveFileIfExists("drawings.ini", destination); + ChangeTargetStorage(newStorage); storageConfig.Set(StorageConfig.CurrentTournament, default_tournament); storageConfig.Save(); } diff --git a/osu.Game/IO/MigratableStorage.cs b/osu.Game/IO/MigratableStorage.cs index faa39d2ef8..13aae92dfd 100644 --- a/osu.Game/IO/MigratableStorage.cs +++ b/osu.Game/IO/MigratableStorage.cs @@ -22,7 +22,36 @@ namespace osu.Game.IO { } - public abstract void Migrate(string newLocation); + /// + /// A general purpose migration method to move the storage to a different location. + /// The target storage of the migration. + /// + public virtual void Migrate(Storage newStorage) + { + var source = new DirectoryInfo(GetFullPath(".")); + var destination = new DirectoryInfo(newStorage.GetFullPath(".")); + + // using Uri is the easiest way to check equality and contains (https://stackoverflow.com/a/7710620) + var sourceUri = new Uri(source.FullName + Path.DirectorySeparatorChar); + var destinationUri = new Uri(destination.FullName + Path.DirectorySeparatorChar); + + if (sourceUri == destinationUri) + throw new ArgumentException("Destination provided is already the current location", nameof(newStorage)); + + if (sourceUri.IsBaseOf(destinationUri)) + throw new ArgumentException("Destination provided is inside the source", nameof(newStorage)); + + // ensure the new location has no files present, else hard abort + if (destination.Exists) + { + if (destination.GetFiles().Length > 0 || destination.GetDirectories().Length > 0) + throw new ArgumentException("Destination provided already has files or directories present", nameof(newStorage)); + } + + CopyRecursive(source, destination); + ChangeTargetStorage(newStorage); + DeleteRecursive(source); + } protected void DeleteRecursive(DirectoryInfo target, bool topLevelExcludes = true) { diff --git a/osu.Game/IO/OsuStorage.cs b/osu.Game/IO/OsuStorage.cs index 31ee802141..7104031b56 100644 --- a/osu.Game/IO/OsuStorage.cs +++ b/osu.Game/IO/OsuStorage.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.IO; using osu.Framework.Logging; using osu.Framework.Platform; using osu.Game.Configuration; @@ -11,7 +9,6 @@ namespace osu.Game.IO { public class OsuStorage : MigratableStorage { - private readonly GameHost host; private readonly StorageConfigManager storageConfig; public override string[] IgnoreDirectories => new[] { "cache" }; @@ -25,8 +22,6 @@ namespace osu.Game.IO public OsuStorage(GameHost host) : base(host.Storage, string.Empty) { - this.host = host; - storageConfig = new StorageConfigManager(host.Storage); var customStoragePath = storageConfig.Get(StorageConfig.FullPath); @@ -41,36 +36,11 @@ namespace osu.Game.IO Logger.Storage = UnderlyingStorage.GetStorageForDirectory("logs"); } - public override void Migrate(string newLocation) + public override void Migrate(Storage newStorage) { - var source = new DirectoryInfo(GetFullPath(".")); - var destination = new DirectoryInfo(newLocation); - - // using Uri is the easiest way to check equality and contains (https://stackoverflow.com/a/7710620) - var sourceUri = new Uri(source.FullName + Path.DirectorySeparatorChar); - var destinationUri = new Uri(destination.FullName + Path.DirectorySeparatorChar); - - if (sourceUri == destinationUri) - throw new ArgumentException("Destination provided is already the current location", nameof(newLocation)); - - if (sourceUri.IsBaseOf(destinationUri)) - throw new ArgumentException("Destination provided is inside the source", nameof(newLocation)); - - // ensure the new location has no files present, else hard abort - if (destination.Exists) - { - if (destination.GetFiles().Length > 0 || destination.GetDirectories().Length > 0) - throw new ArgumentException("Destination provided already has files or directories present", nameof(newLocation)); - } - - CopyRecursive(source, destination); - - ChangeTargetStorage(host.GetStorage(newLocation)); - - storageConfig.Set(StorageConfig.FullPath, newLocation); + base.Migrate(newStorage); + storageConfig.Set(StorageConfig.FullPath, newStorage.GetFullPath(".")); storageConfig.Save(); - - DeleteRecursive(source); } } } diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 3e7311092e..97a4e212e8 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -370,7 +370,7 @@ namespace osu.Game public void Migrate(string path) { contextFactory.FlushConnections(); - (Storage as OsuStorage)?.Migrate(path); + (Storage as OsuStorage)?.Migrate(Host.GetStorage(path)); } } } From 66e61aacff983d7354e6bb267cd472ee090d49fe Mon Sep 17 00:00:00 2001 From: Shivam Date: Thu, 2 Jul 2020 00:32:09 +0200 Subject: [PATCH 0112/1782] Logger now shows the actual path of the destination Forgot to change this while changing the param from string to Storage --- osu.Game/IO/MigratableStorage.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/IO/MigratableStorage.cs b/osu.Game/IO/MigratableStorage.cs index 13aae92dfd..21087d7dc6 100644 --- a/osu.Game/IO/MigratableStorage.cs +++ b/osu.Game/IO/MigratableStorage.cs @@ -36,16 +36,16 @@ namespace osu.Game.IO var destinationUri = new Uri(destination.FullName + Path.DirectorySeparatorChar); if (sourceUri == destinationUri) - throw new ArgumentException("Destination provided is already the current location", nameof(newStorage)); + throw new ArgumentException("Destination provided is already the current location", destination.FullName); if (sourceUri.IsBaseOf(destinationUri)) - throw new ArgumentException("Destination provided is inside the source", nameof(newStorage)); + throw new ArgumentException("Destination provided is inside the source", destination.FullName); // ensure the new location has no files present, else hard abort if (destination.Exists) { if (destination.GetFiles().Length > 0 || destination.GetDirectories().Length > 0) - throw new ArgumentException("Destination provided already has files or directories present", nameof(newStorage)); + throw new ArgumentException("Destination provided already has files or directories present", destination.FullName); } CopyRecursive(source, destination); From e8f23e35a572d658536a19e083e451d29dc610fa Mon Sep 17 00:00:00 2001 From: BananeVolante Date: Fri, 3 Jul 2020 14:33:42 +0200 Subject: [PATCH 0113/1782] WIP : replaced TransformBindableTo by VolumeTo Currently, the VolumeTO calls taht use a fading does not do anything. calling VolumeTo calls pauseLoop.samplesContainer.TransformBindableTo(....), while i used to call pauseLoop.TransformBindableTo(....) --- osu.Game/Screens/Play/PauseOverlay.cs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Play/PauseOverlay.cs b/osu.Game/Screens/Play/PauseOverlay.cs index 022183d82b..a8d291d6c3 100644 --- a/osu.Game/Screens/Play/PauseOverlay.cs +++ b/osu.Game/Screens/Play/PauseOverlay.cs @@ -3,8 +3,10 @@ using System; using System.Linq; +using NUnit.Framework.Internal; using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.Graphics.Audio; using osu.Game.Audio; using osu.Game.Graphics; using osu.Game.Skinning; @@ -44,8 +46,8 @@ namespace osu.Game.Screens.Play base.PopIn(); //SkinnableSound only plays a sound if its aggregate volume is > 0, so the volume must be turned up before playing it - pauseLoop.TransformBindableTo(pauseLoop.Volume, 0.00001); - pauseLoop.TransformBindableTo(pauseLoop.Volume, 1.0f, 400, Easing.InQuint); + pauseLoop.VolumeTo(0.00001f); + pauseLoop.VolumeTo(1.0f, 400, Easing.InQuint); pauseLoop.Play(); } @@ -53,8 +55,9 @@ namespace osu.Game.Screens.Play { base.PopOut(); - pauseLoop.Stop(); - pauseLoop.TransformBindableTo(pauseLoop.Volume, 0.0f); + var transformSeq = pauseLoop.VolumeTo(0.0f, 190, Easing.OutQuad ); + transformSeq.Finally(_ => pauseLoop.Stop()); + } } } From 8869979599b2b79371b0ef2278a5f32f7200e883 Mon Sep 17 00:00:00 2001 From: Lucas A Date: Sat, 4 Jul 2020 12:30:09 +0200 Subject: [PATCH 0114/1782] Trigger hook activation on bind. --- osu.Desktop/Windows/GameplayWinKeyHandler.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Desktop/Windows/GameplayWinKeyHandler.cs b/osu.Desktop/Windows/GameplayWinKeyHandler.cs index cc0150497b..394df9dd0c 100644 --- a/osu.Desktop/Windows/GameplayWinKeyHandler.cs +++ b/osu.Desktop/Windows/GameplayWinKeyHandler.cs @@ -25,7 +25,7 @@ namespace osu.Desktop.Windows winKeyEnabled.ValueChanged += toggleWinKey; disableWinKey = config.GetBindable(OsuSetting.GameplayDisableWinKey); - disableWinKey.BindValueChanged(t => winKeyEnabled.TriggerChange()); + disableWinKey.BindValueChanged(t => winKeyEnabled.TriggerChange(), true); } private void toggleWinKey(ValueChangedEvent e) From ce5da5c51b98136503052eb11df547497019e6fb Mon Sep 17 00:00:00 2001 From: Lucas A Date: Sun, 5 Jul 2020 18:52:27 +0200 Subject: [PATCH 0115/1782] Block CTRL + ESC --- osu.Desktop/Windows/GameplayWinKeyHandler.cs | 8 ++++---- osu.Desktop/Windows/WindowsKey.cs | 4 ++++ 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/osu.Desktop/Windows/GameplayWinKeyHandler.cs b/osu.Desktop/Windows/GameplayWinKeyHandler.cs index 394df9dd0c..4f74a4f492 100644 --- a/osu.Desktop/Windows/GameplayWinKeyHandler.cs +++ b/osu.Desktop/Windows/GameplayWinKeyHandler.cs @@ -11,7 +11,7 @@ namespace osu.Desktop.Windows { public class GameplayWinKeyHandler : Component { - private Bindable winKeyEnabled; + private Bindable allowScreenSuspension; private Bindable disableWinKey; private GameHost host; @@ -21,11 +21,11 @@ namespace osu.Desktop.Windows { this.host = host; - winKeyEnabled = host.AllowScreenSuspension.GetBoundCopy(); - winKeyEnabled.ValueChanged += toggleWinKey; + allowScreenSuspension = host.AllowScreenSuspension.GetBoundCopy(); + allowScreenSuspension.ValueChanged += toggleWinKey; disableWinKey = config.GetBindable(OsuSetting.GameplayDisableWinKey); - disableWinKey.BindValueChanged(t => winKeyEnabled.TriggerChange(), true); + disableWinKey.BindValueChanged(t => allowScreenSuspension.TriggerChange(), true); } private void toggleWinKey(ValueChangedEvent e) diff --git a/osu.Desktop/Windows/WindowsKey.cs b/osu.Desktop/Windows/WindowsKey.cs index 748d9c55d6..175401aaed 100644 --- a/osu.Desktop/Windows/WindowsKey.cs +++ b/osu.Desktop/Windows/WindowsKey.cs @@ -38,6 +38,7 @@ namespace osu.Desktop.Windows { case 0x09 when lParam.Flags == 32: // alt + tab case 0x1b when lParam.Flags == 32: // alt + esc + case 0x1b when (getKeyState(0x11) & 0x8000) != 0: //ctrl + esc case 0x5B: // left windows key case 0x5C: // right windows key return 1; @@ -78,5 +79,8 @@ namespace osu.Desktop.Windows [DllImport(@"user32.dll", EntryPoint = @"CallNextHookEx")] private static extern int callNextHookEx(int hHook, int nCode, int wParam, ref KdDllHookStruct lParam); + + [DllImport(@"user32.dll", EntryPoint = @"GetKeyState")] + private static extern int getKeyState(int vkey); } } From 022e4b6335c0ebdfbc48ec4f1764ba04257a01b4 Mon Sep 17 00:00:00 2001 From: Lucas A Date: Mon, 6 Jul 2020 11:15:56 +0200 Subject: [PATCH 0116/1782] Apply review suggestions. --- osu.Desktop/Windows/WindowsKey.cs | 6 ------ .../Settings/Sections/Gameplay/GeneralSettings.cs | 15 ++++++++++----- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/osu.Desktop/Windows/WindowsKey.cs b/osu.Desktop/Windows/WindowsKey.cs index 175401aaed..4a815b135e 100644 --- a/osu.Desktop/Windows/WindowsKey.cs +++ b/osu.Desktop/Windows/WindowsKey.cs @@ -36,9 +36,6 @@ namespace osu.Desktop.Windows { switch (lParam.VkCode) { - case 0x09 when lParam.Flags == 32: // alt + tab - case 0x1b when lParam.Flags == 32: // alt + esc - case 0x1b when (getKeyState(0x11) & 0x8000) != 0: //ctrl + esc case 0x5B: // left windows key case 0x5C: // right windows key return 1; @@ -79,8 +76,5 @@ namespace osu.Desktop.Windows [DllImport(@"user32.dll", EntryPoint = @"CallNextHookEx")] private static extern int callNextHookEx(int hHook, int nCode, int wParam, ref KdDllHookStruct lParam); - - [DllImport(@"user32.dll", EntryPoint = @"GetKeyState")] - private static extern int getKeyState(int vkey); } } diff --git a/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs index 60197c62b5..0149e6c3a6 100644 --- a/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Game.Configuration; @@ -76,13 +77,17 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay { LabelText = "Score display mode", Bindable = config.GetBindable(OsuSetting.ScoreDisplayMode) - }, - new SettingsCheckbox - { - LabelText = "Disable Win key during gameplay", - Bindable = config.GetBindable(OsuSetting.GameplayDisableWinKey) } }; + + if (RuntimeInfo.OS == RuntimeInfo.Platform.Windows) + { + Add(new SettingsCheckbox + { + LabelText = "Disable Windows key during gameplay", + Bindable = config.GetBindable(OsuSetting.GameplayDisableWinKey) + }); + } } } } From f03303573ef1bd2fe972b670d0f7ea4a9eedfb83 Mon Sep 17 00:00:00 2001 From: BananeVolante Date: Wed, 8 Jul 2020 13:54:22 +0200 Subject: [PATCH 0117/1782] formating --- osu.Game/Screens/Play/PauseOverlay.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/osu.Game/Screens/Play/PauseOverlay.cs b/osu.Game/Screens/Play/PauseOverlay.cs index a8d291d6c3..7b3fba7ddf 100644 --- a/osu.Game/Screens/Play/PauseOverlay.cs +++ b/osu.Game/Screens/Play/PauseOverlay.cs @@ -3,10 +3,8 @@ using System; using System.Linq; -using NUnit.Framework.Internal; using osu.Framework.Allocation; using osu.Framework.Graphics; -using osu.Framework.Graphics.Audio; using osu.Game.Audio; using osu.Game.Graphics; using osu.Game.Skinning; @@ -55,9 +53,8 @@ namespace osu.Game.Screens.Play { base.PopOut(); - var transformSeq = pauseLoop.VolumeTo(0.0f, 190, Easing.OutQuad ); + var transformSeq = pauseLoop.VolumeTo(0.0f, 190, Easing.OutQuad); transformSeq.Finally(_ => pauseLoop.Stop()); - } } } From de4c22c70923b2a9434eadc993eda361bd1c6bd6 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 8 Jul 2020 17:58:09 +0300 Subject: [PATCH 0118/1782] Implement news api request --- .../Online/API/Requests/GetNewsRequest.cs | 27 +++++++++++++++++++ .../Online/API/Requests/GetNewsResponse.cs | 15 +++++++++++ 2 files changed, 42 insertions(+) create mode 100644 osu.Game/Online/API/Requests/GetNewsRequest.cs create mode 100644 osu.Game/Online/API/Requests/GetNewsResponse.cs diff --git a/osu.Game/Online/API/Requests/GetNewsRequest.cs b/osu.Game/Online/API/Requests/GetNewsRequest.cs new file mode 100644 index 0000000000..36d9dc0652 --- /dev/null +++ b/osu.Game/Online/API/Requests/GetNewsRequest.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.IO.Network; +using osu.Game.Extensions; + +namespace osu.Game.Online.API.Requests +{ + public class GetNewsRequest : APIRequest + { + private readonly Cursor cursor; + + public GetNewsRequest(Cursor cursor = null) + { + this.cursor = cursor; + } + + protected override WebRequest CreateWebRequest() + { + var req = base.CreateWebRequest(); + req.AddCursor(cursor); + return req; + } + + protected override string Target => "news"; + } +} diff --git a/osu.Game/Online/API/Requests/GetNewsResponse.cs b/osu.Game/Online/API/Requests/GetNewsResponse.cs new file mode 100644 index 0000000000..835289a51d --- /dev/null +++ b/osu.Game/Online/API/Requests/GetNewsResponse.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 GetNewsResponse : ResponseWithCursor + { + [JsonProperty("news_posts")] + public IEnumerable NewsPosts; + } +} From 49d998c8db2f7a6be17d3fd25898015e5a3920b8 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 8 Jul 2020 18:24:13 +0300 Subject: [PATCH 0119/1782] Refactor NewsOverlay to use displays logic --- .../Visual/Online/TestSceneNewsOverlay.cs | 4 +- osu.Game/Overlays/DashboardOverlay.cs | 8 +-- osu.Game/Overlays/NewsOverlay.cs | 70 +++++++++++++------ 3 files changed, 53 insertions(+), 29 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneNewsOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneNewsOverlay.cs index d47c972564..0b3ede5d13 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneNewsOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneNewsOverlay.cs @@ -22,12 +22,12 @@ namespace osu.Game.Tests.Visual.Online AddStep(@"Show front page", () => news.ShowFrontPage()); AddStep(@"Custom article", () => news.Current.Value = "Test Article 101"); - AddStep(@"Article covers", () => news.LoadAndShowContent(new NewsCoverTest())); + AddStep(@"Article covers", () => news.LoadDisplay(new NewsCoverTest())); } private class TestNewsOverlay : NewsOverlay { - public new void LoadAndShowContent(NewsContent content) => base.LoadAndShowContent(content); + public void LoadDisplay(NewsContent content) => base.LoadDisplay(content); } private class NewsCoverTest : NewsContent diff --git a/osu.Game/Overlays/DashboardOverlay.cs b/osu.Game/Overlays/DashboardOverlay.cs index a72c3f4fa5..e3a4b0e152 100644 --- a/osu.Game/Overlays/DashboardOverlay.cs +++ b/osu.Game/Overlays/DashboardOverlay.cs @@ -19,7 +19,6 @@ namespace osu.Game.Overlays { private CancellationTokenSource cancellationToken; - private Box background; private Container content; private DashboardOverlayHeader header; private LoadingLayer loading; @@ -35,9 +34,10 @@ namespace osu.Game.Overlays { Children = new Drawable[] { - background = new Box + new Box { - RelativeSizeAxes = Axes.Both + RelativeSizeAxes = Axes.Both, + Colour = ColourProvider.Background5 }, scrollFlow = new OverlayScrollContainer { @@ -66,8 +66,6 @@ namespace osu.Game.Overlays }, loading = new LoadingLayer(content), }; - - background.Colour = ColourProvider.Background5; } protected override void LoadComplete() diff --git a/osu.Game/Overlays/NewsOverlay.cs b/osu.Game/Overlays/NewsOverlay.cs index 46d692d44d..ec25827b5a 100644 --- a/osu.Game/Overlays/NewsOverlay.cs +++ b/osu.Game/Overlays/NewsOverlay.cs @@ -7,37 +7,40 @@ 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.Graphics.UserInterface; using osu.Game.Overlays.News; namespace osu.Game.Overlays { public class NewsOverlay : FullscreenOverlay { - private NewsHeader header; - - private Container content; - public readonly Bindable Current = new Bindable(null); + private Container content; + private LoadingLayer loading; + private OverlayScrollContainer scrollFlow; + public NewsOverlay() : base(OverlayColourScheme.Purple) { } [BackgroundDependencyLoader] - private void load(OsuColour colours) + private void load() { + NewsHeader header; + Children = new Drawable[] { new Box { RelativeSizeAxes = Axes.Both, - Colour = colours.PurpleDarkAlternative + Colour = ColourProvider.Background5, }, - new OverlayScrollContainer + scrollFlow = new OverlayScrollContainer { RelativeSizeAxes = Axes.Both, + ScrollbarVisible = false, Child = new FillFlowContainer { RelativeSizeAxes = Axes.X, @@ -49,7 +52,7 @@ namespace osu.Game.Overlays { ShowFrontPage = ShowFrontPage }, - content = new Container + content = new Container { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, @@ -57,25 +60,16 @@ namespace osu.Game.Overlays }, }, }, + loading = new LoadingLayer(content), }; header.Post.BindTo(Current); - Current.TriggerChange(); } - private CancellationTokenSource loadContentCancellation; - - protected void LoadAndShowContent(NewsContent newContent) + protected override void LoadComplete() { - content.FadeTo(0.2f, 300, Easing.OutQuint); - - loadContentCancellation?.Cancel(); - - LoadComponentAsync(newContent, c => - { - content.Child = c; - content.FadeIn(300, Easing.OutQuint); - }, (loadContentCancellation = new CancellationTokenSource()).Token); + base.LoadComplete(); + Current.BindValueChanged(onCurrentChanged, true); } public void ShowFrontPage() @@ -83,5 +77,37 @@ namespace osu.Game.Overlays Current.Value = null; Show(); } + + private CancellationTokenSource cancellationToken; + + private void onCurrentChanged(ValueChangedEvent current) + { + cancellationToken?.Cancel(); + loading.Show(); + + if (current.NewValue == null) + { + LoadDisplay(Empty()); + return; + } + + LoadDisplay(Empty()); + } + + protected void LoadDisplay(Drawable display) + { + scrollFlow.ScrollToStart(); + LoadComponentAsync(display, loaded => + { + content.Child = loaded; + loading.Hide(); + }, (cancellationToken = new CancellationTokenSource()).Token); + } + + protected override void Dispose(bool isDisposing) + { + cancellationToken?.Cancel(); + base.Dispose(isDisposing); + } } } From 0b4213f3307c5c0145c9d2185e575d56833ea8a5 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 8 Jul 2020 20:07:29 +0300 Subject: [PATCH 0120/1782] Implement FrontPageDisplay --- .../Visual/Online/TestSceneNewsOverlay.cs | 2 + .../News/Displays/FrontpageDisplay.cs | 133 ++++++++++++++++++ osu.Game/Overlays/NewsOverlay.cs | 3 +- 3 files changed, 137 insertions(+), 1 deletion(-) create mode 100644 osu.Game/Overlays/News/Displays/FrontpageDisplay.cs diff --git a/osu.Game.Tests/Visual/Online/TestSceneNewsOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneNewsOverlay.cs index 0b3ede5d13..151b3df68f 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneNewsOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneNewsOverlay.cs @@ -12,6 +12,8 @@ namespace osu.Game.Tests.Visual.Online { private TestNewsOverlay news; + protected override bool UseOnlineAPI => true; + protected override void LoadComplete() { base.LoadComplete(); diff --git a/osu.Game/Overlays/News/Displays/FrontpageDisplay.cs b/osu.Game/Overlays/News/Displays/FrontpageDisplay.cs new file mode 100644 index 0000000000..611a072047 --- /dev/null +++ b/osu.Game/Overlays/News/Displays/FrontpageDisplay.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.Threading; +using osu.Framework.Allocation; +using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Graphics.UserInterface; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests; +using osuTK; + +namespace osu.Game.Overlays.News.Displays +{ + public class FrontpageDisplay : CompositeDrawable + { + [Resolved] + private IAPIProvider api { get; set; } + + private readonly FillFlowContainer content; + private readonly FrontpageShowMoreButton showMore; + + private GetNewsRequest request; + private Cursor lastCursor; + + public FrontpageDisplay() + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + Padding = new MarginPadding + { + Top = 20, + Bottom = 10, + Left = 35, + Right = 55 + }; + + InternalChild = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 10), + Children = new Drawable[] + { + content = new FillFlowContainer + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 10) + }, + showMore = new FrontpageShowMoreButton + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Margin = new MarginPadding + { + Vertical = 15 + }, + Action = fetchPage, + Alpha = 0 + } + } + }; + } + + [BackgroundDependencyLoader] + private void load() + { + fetchPage(); + } + + private void fetchPage() + { + request = new GetNewsRequest(lastCursor); + request.Success += response => Schedule(() => createContent(response)); + api.PerformAsync(request); + } + + private CancellationTokenSource cancellationToken; + + private void createContent(GetNewsResponse response) + { + lastCursor = response.Cursor; + + FillFlowContainer flow; + + flow = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 10) + }; + + response.NewsPosts.ForEach(p => + { + flow.Add(new NewsCard(p)); + }); + + LoadComponentAsync(flow, loaded => + { + content.Add(loaded); + showMore.IsLoading = false; + showMore.Show(); + }, (cancellationToken = new CancellationTokenSource()).Token); + } + + protected override void Dispose(bool isDisposing) + { + request?.Cancel(); + cancellationToken?.Cancel(); + base.Dispose(isDisposing); + } + + private class FrontpageShowMoreButton : ShowMoreButton + { + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider) + { + Height = 20; + + IdleColour = colourProvider.Background3; + HoverColour = colourProvider.Background2; + ChevronIconColour = colourProvider.Foreground1; + } + } + } +} diff --git a/osu.Game/Overlays/NewsOverlay.cs b/osu.Game/Overlays/NewsOverlay.cs index ec25827b5a..c6c5d132c1 100644 --- a/osu.Game/Overlays/NewsOverlay.cs +++ b/osu.Game/Overlays/NewsOverlay.cs @@ -9,6 +9,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics.UserInterface; using osu.Game.Overlays.News; +using osu.Game.Overlays.News.Displays; namespace osu.Game.Overlays { @@ -87,7 +88,7 @@ namespace osu.Game.Overlays if (current.NewValue == null) { - LoadDisplay(Empty()); + LoadDisplay(new FrontpageDisplay()); return; } From 57b935ec50b42a8c1eb2ebff1b04fd42f65e6a63 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 8 Jul 2020 20:17:15 +0300 Subject: [PATCH 0121/1782] Remove outdated elements --- .../Visual/Online/TestSceneNewsOverlay.cs | 47 +---- .../News/Displays/FrontpageDisplay.cs | 4 +- osu.Game/Overlays/News/NewsArticleCover.cs | 174 ------------------ osu.Game/Overlays/News/NewsContent.cs | 19 -- 4 files changed, 3 insertions(+), 241 deletions(-) delete mode 100644 osu.Game/Overlays/News/NewsArticleCover.cs delete mode 100644 osu.Game/Overlays/News/NewsContent.cs diff --git a/osu.Game.Tests/Visual/Online/TestSceneNewsOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneNewsOverlay.cs index 151b3df68f..f0dc309d01 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneNewsOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneNewsOverlay.cs @@ -1,68 +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.Graphics; using osu.Game.Overlays; -using osu.Game.Overlays.News; namespace osu.Game.Tests.Visual.Online { public class TestSceneNewsOverlay : OsuTestScene { - private TestNewsOverlay news; + private NewsOverlay news; protected override bool UseOnlineAPI => true; protected override void LoadComplete() { base.LoadComplete(); - Add(news = new TestNewsOverlay()); + 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"); - - AddStep(@"Article covers", () => news.LoadDisplay(new NewsCoverTest())); - } - - private class TestNewsOverlay : NewsOverlay - { - public void LoadDisplay(NewsContent content) => base.LoadDisplay(content); - } - - private class NewsCoverTest : NewsContent - { - public NewsCoverTest() - { - Spacing = new osuTK.Vector2(0, 10); - - var article = new NewsArticleCover.ArticleInfo - { - Author = "Ephemeral", - CoverUrl = "https://assets.ppy.sh/artists/58/header.jpg", - Time = new DateTime(2019, 12, 4), - Title = "New Featured Artist: Kurokotei" - }; - - Children = new Drawable[] - { - new NewsArticleCover(article) - { - Height = 200 - }, - new NewsArticleCover(article) - { - Height = 120 - }, - new NewsArticleCover(article) - { - RelativeSizeAxes = Axes.None, - Size = new osuTK.Vector2(400, 200), - } - }; - } } } } diff --git a/osu.Game/Overlays/News/Displays/FrontpageDisplay.cs b/osu.Game/Overlays/News/Displays/FrontpageDisplay.cs index 611a072047..270c62d42f 100644 --- a/osu.Game/Overlays/News/Displays/FrontpageDisplay.cs +++ b/osu.Game/Overlays/News/Displays/FrontpageDisplay.cs @@ -87,9 +87,7 @@ namespace osu.Game.Overlays.News.Displays { lastCursor = response.Cursor; - FillFlowContainer flow; - - flow = new FillFlowContainer + var flow = new FillFlowContainer { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, diff --git a/osu.Game/Overlays/News/NewsArticleCover.cs b/osu.Game/Overlays/News/NewsArticleCover.cs deleted file mode 100644 index e3f5a8cea3..0000000000 --- a/osu.Game/Overlays/News/NewsArticleCover.cs +++ /dev/null @@ -1,174 +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.Extensions.Color4Extensions; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Colour; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Cursor; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.Sprites; -using osu.Framework.Graphics.Textures; -using osu.Framework.Input.Events; -using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; -using osuTK.Graphics; - -namespace osu.Game.Overlays.News -{ - public class NewsArticleCover : Container - { - private const int hover_duration = 300; - - private readonly Box gradient; - - public NewsArticleCover(ArticleInfo info) - { - RelativeSizeAxes = Axes.X; - Masking = true; - CornerRadius = 4; - - NewsBackground bg; - - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = ColourInfo.GradientVertical(OsuColour.Gray(0.2f), OsuColour.Gray(0.1f)) - }, - new DelayedLoadWrapper(bg = new NewsBackground(info.CoverUrl) - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - FillMode = FillMode.Fill, - Alpha = 0 - }) - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - }, - gradient = new Box - { - RelativeSizeAxes = Axes.Both, - Colour = ColourInfo.GradientVertical(Color4.Black.Opacity(0.1f), Color4.Black.Opacity(0.7f)), - Alpha = 0 - }, - new DateContainer(info.Time) - { - Margin = new MarginPadding - { - Right = 20, - Top = 20, - } - }, - new OsuSpriteText - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - Margin = new MarginPadding - { - Left = 25, - Bottom = 50, - }, - Font = OsuFont.GetFont(Typeface.Torus, 24, FontWeight.Bold), - Text = info.Title, - }, - new OsuSpriteText - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - Margin = new MarginPadding - { - Left = 25, - Bottom = 30, - }, - Font = OsuFont.GetFont(Typeface.Torus, 16, FontWeight.Bold), - Text = "by " + info.Author - } - }; - - bg.OnLoadComplete += d => d.FadeIn(250, Easing.In); - } - - protected override bool OnHover(HoverEvent e) - { - gradient.FadeIn(hover_duration, Easing.OutQuint); - return base.OnHover(e); - } - - protected override void OnHoverLost(HoverLostEvent e) - { - base.OnHoverLost(e); - gradient.FadeOut(hover_duration, Easing.OutQuint); - } - - [LongRunningLoad] - private class NewsBackground : Sprite - { - private readonly string url; - - public NewsBackground(string coverUrl) - { - url = coverUrl ?? "Headers/news"; - } - - [BackgroundDependencyLoader] - private void load(LargeTextureStore store) - { - Texture = store.Get(url); - } - } - - private class DateContainer : Container, IHasTooltip - { - private readonly DateTime date; - - public DateContainer(DateTime date) - { - this.date = date; - - Anchor = Anchor.TopRight; - Origin = Anchor.TopRight; - Masking = true; - CornerRadius = 4; - AutoSizeAxes = Axes.Both; - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.Black.Opacity(0.5f), - }, - new OsuSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Font = OsuFont.GetFont(Typeface.Torus, 12, FontWeight.Bold, false, false), - Text = date.ToString("d MMM yyy").ToUpper(), - Margin = new MarginPadding - { - Vertical = 4, - Horizontal = 8, - } - } - }; - } - - public string TooltipText => date.ToString("dddd dd MMMM yyyy hh:mm:ss UTCz").ToUpper(); - } - - // fake API data struct to use for now as a skeleton for data, as there is no API struct for news article info for now - public class ArticleInfo - { - public string Title { get; set; } - public string CoverUrl { get; set; } - public DateTime Time { get; set; } - public string Author { get; set; } - } - } -} diff --git a/osu.Game/Overlays/News/NewsContent.cs b/osu.Game/Overlays/News/NewsContent.cs deleted file mode 100644 index 5ff210f9f5..0000000000 --- a/osu.Game/Overlays/News/NewsContent.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.Graphics; -using osu.Framework.Graphics.Containers; - -namespace osu.Game.Overlays.News -{ - public abstract class NewsContent : FillFlowContainer - { - protected NewsContent() - { - RelativeSizeAxes = Axes.X; - AutoSizeAxes = Axes.Y; - Direction = FillDirection.Vertical; - Padding = new MarginPadding { Bottom = 100, Top = 20, Horizontal = 50 }; - } - } -} From 900f2d309b84f43d151685488b6824bb8c5c40c5 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Thu, 9 Jul 2020 01:26:56 +0300 Subject: [PATCH 0122/1782] Classes naming adjustments --- osu.Game/Overlays/News/Displays/FrontpageDisplay.cs | 10 +++++----- osu.Game/Overlays/NewsOverlay.cs | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game/Overlays/News/Displays/FrontpageDisplay.cs b/osu.Game/Overlays/News/Displays/FrontpageDisplay.cs index 270c62d42f..f0b25c8143 100644 --- a/osu.Game/Overlays/News/Displays/FrontpageDisplay.cs +++ b/osu.Game/Overlays/News/Displays/FrontpageDisplay.cs @@ -13,18 +13,18 @@ using osuTK; namespace osu.Game.Overlays.News.Displays { - public class FrontpageDisplay : CompositeDrawable + public class FrontPageDisplay : CompositeDrawable { [Resolved] private IAPIProvider api { get; set; } private readonly FillFlowContainer content; - private readonly FrontpageShowMoreButton showMore; + private readonly FrontPageShowMoreButton showMore; private GetNewsRequest request; private Cursor lastCursor; - public FrontpageDisplay() + public FrontPageDisplay() { RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; @@ -53,7 +53,7 @@ namespace osu.Game.Overlays.News.Displays Direction = FillDirection.Vertical, Spacing = new Vector2(0, 10) }, - showMore = new FrontpageShowMoreButton + showMore = new FrontPageShowMoreButton { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, @@ -115,7 +115,7 @@ namespace osu.Game.Overlays.News.Displays base.Dispose(isDisposing); } - private class FrontpageShowMoreButton : ShowMoreButton + private class FrontPageShowMoreButton : ShowMoreButton { [BackgroundDependencyLoader] private void load(OverlayColourProvider colourProvider) diff --git a/osu.Game/Overlays/NewsOverlay.cs b/osu.Game/Overlays/NewsOverlay.cs index c6c5d132c1..4cd83f83af 100644 --- a/osu.Game/Overlays/NewsOverlay.cs +++ b/osu.Game/Overlays/NewsOverlay.cs @@ -88,7 +88,7 @@ namespace osu.Game.Overlays if (current.NewValue == null) { - LoadDisplay(new FrontpageDisplay()); + LoadDisplay(new FrontPageDisplay()); return; } From 62e2bc11983cc46feffb35badfa82deee9740e6d Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Thu, 9 Jul 2020 01:29:27 +0300 Subject: [PATCH 0123/1782] Fix potential double-request situation --- osu.Game/Overlays/News/Displays/FrontpageDisplay.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Overlays/News/Displays/FrontpageDisplay.cs b/osu.Game/Overlays/News/Displays/FrontpageDisplay.cs index f0b25c8143..73af51c342 100644 --- a/osu.Game/Overlays/News/Displays/FrontpageDisplay.cs +++ b/osu.Game/Overlays/News/Displays/FrontpageDisplay.cs @@ -76,6 +76,8 @@ namespace osu.Game.Overlays.News.Displays private void fetchPage() { + request?.Cancel(); + request = new GetNewsRequest(lastCursor); request.Success += response => Schedule(() => createContent(response)); api.PerformAsync(request); From dfa22b1e4c48fa297508ed57cc84565899c34cd2 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Thu, 9 Jul 2020 02:37:42 +0300 Subject: [PATCH 0124/1782] Styles improvements --- .../Visual/Online/TestSceneShowMoreButton.cs | 20 +++----- .../Graphics/UserInterface/ShowMoreButton.cs | 47 +++++++++++-------- .../Comments/CommentsShowMoreButton.cs | 11 ----- .../News/Displays/FrontpageDisplay.cs | 26 +++------- .../Profile/Sections/PaginatedContainer.cs | 5 +- .../Profile/Sections/ProfileShowMoreButton.cs | 19 -------- 6 files changed, 42 insertions(+), 86 deletions(-) delete mode 100644 osu.Game/Overlays/Profile/Sections/ProfileShowMoreButton.cs diff --git a/osu.Game.Tests/Visual/Online/TestSceneShowMoreButton.cs b/osu.Game.Tests/Visual/Online/TestSceneShowMoreButton.cs index 273f593c32..f1c69f0ac3 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneShowMoreButton.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneShowMoreButton.cs @@ -4,19 +4,22 @@ using osu.Framework.Graphics; using osu.Game.Graphics.UserInterface; using osu.Framework.Allocation; -using osu.Game.Graphics; +using osu.Game.Overlays; namespace osu.Game.Tests.Visual.Online { public class TestSceneShowMoreButton : OsuTestScene { + [Cached] + private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Red); + public TestSceneShowMoreButton() { - TestButton button = null; + ShowMoreButton button = null; int fireCount = 0; - Add(button = new TestButton + Add(button = new ShowMoreButton { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -46,16 +49,5 @@ 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; - } - } } } diff --git a/osu.Game/Graphics/UserInterface/ShowMoreButton.cs b/osu.Game/Graphics/UserInterface/ShowMoreButton.cs index c9cd9f1158..db563b346c 100644 --- a/osu.Game/Graphics/UserInterface/ShowMoreButton.cs +++ b/osu.Game/Graphics/UserInterface/ShowMoreButton.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 osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Game.Graphics.Sprites; +using osu.Game.Overlays; using osuTK; -using osuTK.Graphics; using System.Collections.Generic; namespace osu.Game.Graphics.UserInterface @@ -16,14 +17,6 @@ namespace osu.Game.Graphics.UserInterface { private const int duration = 200; - private Color4 chevronIconColour; - - protected Color4 ChevronIconColour - { - get => chevronIconColour; - set => chevronIconColour = leftChevron.Colour = rightChevron.Colour = value; - } - public string Text { get => text.Text; @@ -32,22 +25,26 @@ namespace osu.Game.Graphics.UserInterface protected override IEnumerable EffectTargets => new[] { background }; - private ChevronIcon leftChevron; - private ChevronIcon rightChevron; private SpriteText text; private Box background; private FillFlowContainer textContainer; public ShowMoreButton() { - Height = 30; - Width = 140; + AutoSizeAxes = Axes.Both; + } + + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider) + { + IdleColour = colourProvider.Background2; + HoverColour = colourProvider.Background1; } protected override Drawable CreateContent() => new CircularContainer { Masking = true, - RelativeSizeAxes = Axes.Both, + AutoSizeAxes = Axes.Both, Children = new Drawable[] { background = new Box @@ -56,22 +53,28 @@ namespace osu.Game.Graphics.UserInterface }, textContainer = new FillFlowContainer { + AlwaysPresent = true, Anchor = Anchor.Centre, Origin = Anchor.Centre, AutoSizeAxes = Axes.Both, Direction = FillDirection.Horizontal, Spacing = new Vector2(7), + Margin = new MarginPadding + { + Horizontal = 20, + Vertical = 5, + }, Children = new Drawable[] { - leftChevron = new ChevronIcon(), + new ChevronIcon(), text = new OsuSpriteText { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Font = OsuFont.GetFont(size: 12, weight: FontWeight.Bold), + Font = OsuFont.GetFont(size: 12, weight: FontWeight.SemiBold), Text = "show more".ToUpper(), }, - rightChevron = new ChevronIcon(), + new ChevronIcon() } } } @@ -83,15 +86,19 @@ namespace osu.Game.Graphics.UserInterface private class ChevronIcon : SpriteIcon { - private const int icon_size = 8; - public ChevronIcon() { Anchor = Anchor.Centre; Origin = Anchor.Centre; - Size = new Vector2(icon_size); + Size = new Vector2(8); Icon = FontAwesome.Solid.ChevronDown; } + + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider) + { + Colour = colourProvider.Foreground1; + } } } } diff --git a/osu.Game/Overlays/Comments/CommentsShowMoreButton.cs b/osu.Game/Overlays/Comments/CommentsShowMoreButton.cs index d2ff7ecb1f..adf64eabb1 100644 --- a/osu.Game/Overlays/Comments/CommentsShowMoreButton.cs +++ b/osu.Game/Overlays/Comments/CommentsShowMoreButton.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.Bindables; using osu.Game.Graphics.UserInterface; @@ -11,16 +10,6 @@ namespace osu.Game.Overlays.Comments { public readonly BindableInt Current = new BindableInt(); - [BackgroundDependencyLoader] - private void load(OverlayColourProvider colourProvider) - { - Height = 20; - - IdleColour = colourProvider.Background2; - HoverColour = colourProvider.Background1; - ChevronIconColour = colourProvider.Foreground1; - } - protected override void LoadComplete() { Current.BindValueChanged(onCurrentChanged, true); diff --git a/osu.Game/Overlays/News/Displays/FrontpageDisplay.cs b/osu.Game/Overlays/News/Displays/FrontpageDisplay.cs index 73af51c342..386e0b0dca 100644 --- a/osu.Game/Overlays/News/Displays/FrontpageDisplay.cs +++ b/osu.Game/Overlays/News/Displays/FrontpageDisplay.cs @@ -19,7 +19,7 @@ namespace osu.Game.Overlays.News.Displays private IAPIProvider api { get; set; } private readonly FillFlowContainer content; - private readonly FrontPageShowMoreButton showMore; + private readonly ShowMoreButton showMore; private GetNewsRequest request; private Cursor lastCursor; @@ -30,10 +30,9 @@ namespace osu.Game.Overlays.News.Displays AutoSizeAxes = Axes.Y; Padding = new MarginPadding { - Top = 20, - Bottom = 10, - Left = 35, - Right = 55 + Vertical = 20, + Left = 30, + Right = 50 }; InternalChild = new FillFlowContainer @@ -53,13 +52,13 @@ namespace osu.Game.Overlays.News.Displays Direction = FillDirection.Vertical, Spacing = new Vector2(0, 10) }, - showMore = new FrontPageShowMoreButton + showMore = new ShowMoreButton { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, Margin = new MarginPadding { - Vertical = 15 + Top = 15 }, Action = fetchPage, Alpha = 0 @@ -116,18 +115,5 @@ namespace osu.Game.Overlays.News.Displays cancellationToken?.Cancel(); base.Dispose(isDisposing); } - - private class FrontPageShowMoreButton : ShowMoreButton - { - [BackgroundDependencyLoader] - private void load(OverlayColourProvider colourProvider) - { - Height = 20; - - IdleColour = colourProvider.Background3; - HoverColour = colourProvider.Background2; - ChevronIconColour = colourProvider.Foreground1; - } - } } } diff --git a/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs b/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs index a30ff786fb..9720469548 100644 --- a/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs @@ -14,12 +14,13 @@ using osu.Game.Users; using System.Collections.Generic; using System.Linq; using System.Threading; +using osu.Game.Graphics.UserInterface; namespace osu.Game.Overlays.Profile.Sections { public abstract class PaginatedContainer : FillFlowContainer { - private readonly ProfileShowMoreButton moreButton; + private readonly ShowMoreButton moreButton; private readonly OsuSpriteText missingText; private APIRequest> retrievalRequest; private CancellationTokenSource loadCancellation; @@ -74,7 +75,7 @@ namespace osu.Game.Overlays.Profile.Sections RelativeSizeAxes = Axes.X, Spacing = new Vector2(0, 2), }, - moreButton = new ProfileShowMoreButton + moreButton = new ShowMoreButton { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, diff --git a/osu.Game/Overlays/Profile/Sections/ProfileShowMoreButton.cs b/osu.Game/Overlays/Profile/Sections/ProfileShowMoreButton.cs deleted file mode 100644 index 426ebeebe6..0000000000 --- a/osu.Game/Overlays/Profile/Sections/ProfileShowMoreButton.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.Allocation; -using osu.Game.Graphics.UserInterface; - -namespace osu.Game.Overlays.Profile.Sections -{ - public class ProfileShowMoreButton : ShowMoreButton - { - [BackgroundDependencyLoader] - private void load(OverlayColourProvider colourProvider) - { - IdleColour = colourProvider.Background2; - HoverColour = colourProvider.Background1; - ChevronIconColour = colourProvider.Foreground1; - } - } -} From 3ba8ec0fd75f73dda2db4d8bb855e7544a7f7aba Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Thu, 9 Jul 2020 03:40:14 +0300 Subject: [PATCH 0125/1782] Don't set null value to show front page --- .../Visual/Online/TestSceneNewsOverlay.cs | 16 ++++--- osu.Game/Overlays/News/NewsHeader.cs | 46 ++++++++++--------- osu.Game/Overlays/NewsOverlay.cs | 26 +++++------ 3 files changed, 45 insertions(+), 43 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneNewsOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneNewsOverlay.cs index f0dc309d01..e35ef4916b 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneNewsOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneNewsOverlay.cs @@ -7,19 +7,21 @@ namespace osu.Game.Tests.Visual.Online { public class TestSceneNewsOverlay : OsuTestScene { - private NewsOverlay news; - protected override bool UseOnlineAPI => true; protected override void LoadComplete() { base.LoadComplete(); - 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"); + NewsOverlay news; + Add(news = new NewsOverlay()); + + AddStep("Show", news.Show); + AddStep("Hide", news.Hide); + + AddStep("Show front page", () => news.ShowFrontPage()); + AddStep("Custom article", () => news.ShowArticle("Test Article 101")); + AddStep("Custom article", () => news.ShowArticle("Test Article 102")); } } } diff --git a/osu.Game/Overlays/News/NewsHeader.cs b/osu.Game/Overlays/News/NewsHeader.cs index 8214c71b3a..ee7991c0c6 100644 --- a/osu.Game/Overlays/News/NewsHeader.cs +++ b/osu.Game/Overlays/News/NewsHeader.cs @@ -3,44 +3,46 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; -using System; namespace osu.Game.Overlays.News { public class NewsHeader : BreadcrumbControlOverlayHeader { - private const string front_page_string = "frontpage"; + public const string FRONT_PAGE_STRING = "frontpage"; - public readonly Bindable Post = new Bindable(null); - - public Action ShowFrontPage; + public readonly Bindable Post = new Bindable(FRONT_PAGE_STRING); public NewsHeader() { - TabControl.AddItem(front_page_string); - - Current.ValueChanged += e => - { - if (e.NewValue == front_page_string) - ShowFrontPage?.Invoke(); - }; - - Post.ValueChanged += showPost; + TabControl.AddItem(FRONT_PAGE_STRING); + Current.Value = FRONT_PAGE_STRING; + Current.BindValueChanged(onCurrentChanged); + Post.BindValueChanged(onPostChanged, true); } - private void showPost(ValueChangedEvent e) - { - if (e.OldValue != null) - TabControl.RemoveItem(e.OldValue); + public void SetFrontPage() => Post.Value = FRONT_PAGE_STRING; - if (e.NewValue != null) + public void SetArticle(string slug) => Post.Value = slug; + + private void onCurrentChanged(ValueChangedEvent current) + { + if (current.NewValue == FRONT_PAGE_STRING) + Post.Value = FRONT_PAGE_STRING; + } + + private void onPostChanged(ValueChangedEvent post) + { + if (post.OldValue != FRONT_PAGE_STRING) + TabControl.RemoveItem(post.OldValue); + + if (post.NewValue != FRONT_PAGE_STRING) { - TabControl.AddItem(e.NewValue); - Current.Value = e.NewValue; + TabControl.AddItem(post.NewValue); + Current.Value = post.NewValue; } else { - Current.Value = front_page_string; + Current.Value = FRONT_PAGE_STRING; } } diff --git a/osu.Game/Overlays/NewsOverlay.cs b/osu.Game/Overlays/NewsOverlay.cs index 4cd83f83af..db989e71bf 100644 --- a/osu.Game/Overlays/NewsOverlay.cs +++ b/osu.Game/Overlays/NewsOverlay.cs @@ -15,10 +15,9 @@ namespace osu.Game.Overlays { public class NewsOverlay : FullscreenOverlay { - public readonly Bindable Current = new Bindable(null); - private Container content; private LoadingLayer loading; + private NewsHeader header; private OverlayScrollContainer scrollFlow; public NewsOverlay() @@ -29,8 +28,6 @@ namespace osu.Game.Overlays [BackgroundDependencyLoader] private void load() { - NewsHeader header; - Children = new Drawable[] { new Box @@ -49,10 +46,7 @@ namespace osu.Game.Overlays Direction = FillDirection.Vertical, Children = new Drawable[] { - header = new NewsHeader - { - ShowFrontPage = ShowFrontPage - }, + header = new NewsHeader(), content = new Container { RelativeSizeAxes = Axes.X, @@ -63,30 +57,34 @@ namespace osu.Game.Overlays }, loading = new LoadingLayer(content), }; - - header.Post.BindTo(Current); } protected override void LoadComplete() { base.LoadComplete(); - Current.BindValueChanged(onCurrentChanged, true); + header.Post.BindValueChanged(onPostChanged, true); } public void ShowFrontPage() { - Current.Value = null; + header.SetFrontPage(); + Show(); + } + + public void ShowArticle(string slug) + { + header.SetArticle(slug); Show(); } private CancellationTokenSource cancellationToken; - private void onCurrentChanged(ValueChangedEvent current) + private void onPostChanged(ValueChangedEvent post) { cancellationToken?.Cancel(); loading.Show(); - if (current.NewValue == null) + if (post.NewValue == NewsHeader.FRONT_PAGE_STRING) { LoadDisplay(new FrontPageDisplay()); return; From aeb664aca759e7061a6140b7c51dabc31fa26132 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Thu, 9 Jul 2020 04:00:53 +0300 Subject: [PATCH 0126/1782] Delete broken file --- .../News/Displays/FrontpageDisplay.cs | 119 ------------------ 1 file changed, 119 deletions(-) delete mode 100644 osu.Game/Overlays/News/Displays/FrontpageDisplay.cs diff --git a/osu.Game/Overlays/News/Displays/FrontpageDisplay.cs b/osu.Game/Overlays/News/Displays/FrontpageDisplay.cs deleted file mode 100644 index 386e0b0dca..0000000000 --- a/osu.Game/Overlays/News/Displays/FrontpageDisplay.cs +++ /dev/null @@ -1,119 +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.Threading; -using osu.Framework.Allocation; -using osu.Framework.Extensions.IEnumerableExtensions; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Game.Graphics.UserInterface; -using osu.Game.Online.API; -using osu.Game.Online.API.Requests; -using osuTK; - -namespace osu.Game.Overlays.News.Displays -{ - public class FrontPageDisplay : CompositeDrawable - { - [Resolved] - private IAPIProvider api { get; set; } - - private readonly FillFlowContainer content; - private readonly ShowMoreButton showMore; - - private GetNewsRequest request; - private Cursor lastCursor; - - public FrontPageDisplay() - { - RelativeSizeAxes = Axes.X; - AutoSizeAxes = Axes.Y; - Padding = new MarginPadding - { - Vertical = 20, - Left = 30, - Right = 50 - }; - - InternalChild = new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, - Spacing = new Vector2(0, 10), - Children = new Drawable[] - { - content = new FillFlowContainer - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, - Spacing = new Vector2(0, 10) - }, - showMore = new ShowMoreButton - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Margin = new MarginPadding - { - Top = 15 - }, - Action = fetchPage, - Alpha = 0 - } - } - }; - } - - [BackgroundDependencyLoader] - private void load() - { - fetchPage(); - } - - private void fetchPage() - { - request?.Cancel(); - - request = new GetNewsRequest(lastCursor); - request.Success += response => Schedule(() => createContent(response)); - api.PerformAsync(request); - } - - private CancellationTokenSource cancellationToken; - - private void createContent(GetNewsResponse response) - { - lastCursor = response.Cursor; - - var flow = new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, - Spacing = new Vector2(0, 10) - }; - - response.NewsPosts.ForEach(p => - { - flow.Add(new NewsCard(p)); - }); - - LoadComponentAsync(flow, loaded => - { - content.Add(loaded); - showMore.IsLoading = false; - showMore.Show(); - }, (cancellationToken = new CancellationTokenSource()).Token); - } - - protected override void Dispose(bool isDisposing) - { - request?.Cancel(); - cancellationToken?.Cancel(); - base.Dispose(isDisposing); - } - } -} From f663dd18033e9cfc83dbbaa652f862a4e7fb8099 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Thu, 9 Jul 2020 04:02:14 +0300 Subject: [PATCH 0127/1782] Fix incorrect file name --- .../News/Displays/FrontPageDisplay.cs | 115 ++++++++++++++++++ 1 file changed, 115 insertions(+) create mode 100644 osu.Game/Overlays/News/Displays/FrontPageDisplay.cs diff --git a/osu.Game/Overlays/News/Displays/FrontPageDisplay.cs b/osu.Game/Overlays/News/Displays/FrontPageDisplay.cs new file mode 100644 index 0000000000..67b5edfafd --- /dev/null +++ b/osu.Game/Overlays/News/Displays/FrontPageDisplay.cs @@ -0,0 +1,115 @@ +// 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.Threading; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Graphics.UserInterface; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests; +using osuTK; + +namespace osu.Game.Overlays.News.Displays +{ + public class FrontPageDisplay : CompositeDrawable + { + [Resolved] + private IAPIProvider api { get; set; } + + private readonly FillFlowContainer content; + private readonly ShowMoreButton showMore; + + private GetNewsRequest request; + private Cursor lastCursor; + + public FrontPageDisplay() + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + Padding = new MarginPadding + { + Vertical = 20, + Left = 30, + Right = 50 + }; + + InternalChild = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 10), + Children = new Drawable[] + { + content = new FillFlowContainer + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 10) + }, + showMore = new ShowMoreButton + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Margin = new MarginPadding + { + Top = 15 + }, + Action = fetchPage, + Alpha = 0 + } + } + }; + } + + [BackgroundDependencyLoader] + private void load() + { + fetchPage(); + } + + private void fetchPage() + { + request?.Cancel(); + + request = new GetNewsRequest(lastCursor); + request.Success += response => Schedule(() => createContent(response)); + api.PerformAsync(request); + } + + private CancellationTokenSource cancellationToken; + + private void createContent(GetNewsResponse response) + { + lastCursor = response.Cursor; + + var flow = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 10), + Children = response.NewsPosts.Select(p => new NewsCard(p)).ToList() + }; + + LoadComponentAsync(flow, loaded => + { + content.Add(loaded); + showMore.IsLoading = false; + showMore.Show(); + }, (cancellationToken = new CancellationTokenSource()).Token); + } + + protected override void Dispose(bool isDisposing) + { + request?.Cancel(); + cancellationToken?.Cancel(); + base.Dispose(isDisposing); + } + } +} From 598e48678e2e143b6671a9cd2c9f55dad36f3bff Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 12 Jul 2020 15:45:48 +0300 Subject: [PATCH 0128/1782] Refactor NewsHeader --- osu.Game/Overlays/News/NewsHeader.cs | 54 ++++++++++++++-------------- osu.Game/Overlays/NewsOverlay.cs | 19 ++++++---- 2 files changed, 41 insertions(+), 32 deletions(-) diff --git a/osu.Game/Overlays/News/NewsHeader.cs b/osu.Game/Overlays/News/NewsHeader.cs index ee7991c0c6..ddada2bdaf 100644 --- a/osu.Game/Overlays/News/NewsHeader.cs +++ b/osu.Game/Overlays/News/NewsHeader.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -8,41 +9,42 @@ namespace osu.Game.Overlays.News { public class NewsHeader : BreadcrumbControlOverlayHeader { - public const string FRONT_PAGE_STRING = "frontpage"; + private const string front_page_string = "frontpage"; - public readonly Bindable Post = new Bindable(FRONT_PAGE_STRING); + public Action ShowFrontPage; + + private readonly Bindable article = new Bindable(null); public NewsHeader() { - TabControl.AddItem(FRONT_PAGE_STRING); - Current.Value = FRONT_PAGE_STRING; - Current.BindValueChanged(onCurrentChanged); - Post.BindValueChanged(onPostChanged, true); - } + TabControl.AddItem(front_page_string); - public void SetFrontPage() => Post.Value = FRONT_PAGE_STRING; - - public void SetArticle(string slug) => Post.Value = slug; - - private void onCurrentChanged(ValueChangedEvent current) - { - if (current.NewValue == FRONT_PAGE_STRING) - Post.Value = FRONT_PAGE_STRING; - } - - private void onPostChanged(ValueChangedEvent post) - { - if (post.OldValue != FRONT_PAGE_STRING) - TabControl.RemoveItem(post.OldValue); - - if (post.NewValue != FRONT_PAGE_STRING) + Current.BindValueChanged(e => { - TabControl.AddItem(post.NewValue); - Current.Value = post.NewValue; + if (e.NewValue == front_page_string) + ShowFrontPage?.Invoke(); + }); + + article.BindValueChanged(onArticleChanged, true); + } + + public void SetFrontPage() => article.Value = null; + + public void SetArticle(string slug) => article.Value = slug; + + private void onArticleChanged(ValueChangedEvent e) + { + if (e.OldValue != null) + TabControl.RemoveItem(e.OldValue); + + if (e.NewValue != null) + { + TabControl.AddItem(e.NewValue); + Current.Value = e.NewValue; } else { - Current.Value = FRONT_PAGE_STRING; + Current.Value = front_page_string; } } diff --git a/osu.Game/Overlays/NewsOverlay.cs b/osu.Game/Overlays/NewsOverlay.cs index db989e71bf..a5687b77e2 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 readonly Bindable article = new Bindable(null); + private Container content; private LoadingLayer loading; private NewsHeader header; @@ -46,7 +48,10 @@ namespace osu.Game.Overlays Direction = FillDirection.Vertical, Children = new Drawable[] { - header = new NewsHeader(), + header = new NewsHeader + { + ShowFrontPage = ShowFrontPage + }, content = new Container { RelativeSizeAxes = Axes.X, @@ -62,34 +67,36 @@ namespace osu.Game.Overlays protected override void LoadComplete() { base.LoadComplete(); - header.Post.BindValueChanged(onPostChanged, true); + article.BindValueChanged(onArticleChanged, true); } public void ShowFrontPage() { - header.SetFrontPage(); + article.Value = null; Show(); } public void ShowArticle(string slug) { - header.SetArticle(slug); + article.Value = slug; Show(); } private CancellationTokenSource cancellationToken; - private void onPostChanged(ValueChangedEvent post) + private void onArticleChanged(ValueChangedEvent e) { cancellationToken?.Cancel(); loading.Show(); - if (post.NewValue == NewsHeader.FRONT_PAGE_STRING) + if (e.NewValue == null) { + header.SetFrontPage(); LoadDisplay(new FrontPageDisplay()); return; } + header.SetArticle(e.NewValue); LoadDisplay(Empty()); } From a72bb932661faa2442ceb9160d61b0d21e803790 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 12 Jul 2020 15:57:18 +0300 Subject: [PATCH 0129/1782] Add test scene for NewsHeader --- .../Visual/Online/TestSceneNewsHeader.cs | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 osu.Game.Tests/Visual/Online/TestSceneNewsHeader.cs diff --git a/osu.Game.Tests/Visual/Online/TestSceneNewsHeader.cs b/osu.Game.Tests/Visual/Online/TestSceneNewsHeader.cs new file mode 100644 index 0000000000..65e86e925d --- /dev/null +++ b/osu.Game.Tests/Visual/Online/TestSceneNewsHeader.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 NUnit.Framework; +using osu.Game.Overlays.News; +using osu.Framework.Graphics; +using osu.Game.Overlays; +using osu.Framework.Allocation; + +namespace osu.Game.Tests.Visual.Online +{ + public class TestSceneNewsHeader : OsuTestScene + { + [Cached] + private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple); + + private TestHeader header; + + [SetUp] + public void Setup() + { + Child = header = new TestHeader + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre + }; + } + + [Test] + public void TestControl() + { + AddAssert("Front page selected", () => header.Current.Value == "frontpage"); + AddAssert("1 tab total", () => header.TabCount == 1); + + AddStep("Set article 1", () => header.SetArticle("1")); + AddAssert("Article 1 selected", () => header.Current.Value == "1"); + AddAssert("2 tabs total", () => header.TabCount == 2); + + AddStep("Set article 2", () => header.SetArticle("2")); + AddAssert("Article 2 selected", () => header.Current.Value == "2"); + AddAssert("2 tabs total", () => header.TabCount == 2); + + AddStep("Set front page", () => header.SetFrontPage()); + AddAssert("Front page selected", () => header.Current.Value == "frontpage"); + AddAssert("1 tab total", () => header.TabCount == 1); + } + + private class TestHeader : NewsHeader + { + public int TabCount => TabControl.Items.Count; + } + } +} From 444701fdd0c0903346177900acef014755da297f Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 12 Jul 2020 16:13:48 +0300 Subject: [PATCH 0130/1782] Use dummy api for tests --- .../Visual/Online/TestSceneNewsHeader.cs | 4 +- .../Visual/Online/TestSceneNewsOverlay.cs | 64 +++++++++++++++---- 2 files changed, 53 insertions(+), 15 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneNewsHeader.cs b/osu.Game.Tests/Visual/Online/TestSceneNewsHeader.cs index 65e86e925d..78288bf6e4 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneNewsHeader.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneNewsHeader.cs @@ -17,14 +17,14 @@ namespace osu.Game.Tests.Visual.Online private TestHeader header; [SetUp] - public void Setup() + public void Setup() => Schedule(() => { Child = header = new TestHeader { Anchor = Anchor.Centre, Origin = Anchor.Centre }; - } + }); [Test] public void TestControl() diff --git a/osu.Game.Tests/Visual/Online/TestSceneNewsOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneNewsOverlay.cs index e35ef4916b..37d51c16d2 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneNewsOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneNewsOverlay.cs @@ -1,27 +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 NUnit.Framework; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests; +using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays; namespace osu.Game.Tests.Visual.Online { public class TestSceneNewsOverlay : OsuTestScene { - protected override bool UseOnlineAPI => true; + private DummyAPIAccess dummyAPI => (DummyAPIAccess)API; - protected override void LoadComplete() + private NewsOverlay news; + + [SetUp] + public void SetUp() => Schedule(() => Child = news = new NewsOverlay()); + + [Test] + public void TestRequest() { - base.LoadComplete(); - - NewsOverlay news; - Add(news = new NewsOverlay()); - - AddStep("Show", news.Show); - AddStep("Hide", news.Hide); - - AddStep("Show front page", () => news.ShowFrontPage()); - AddStep("Custom article", () => news.ShowArticle("Test Article 101")); - AddStep("Custom article", () => news.ShowArticle("Test Article 102")); + setUpNewsResponse(responseExample); + AddStep("Show", () => news.Show()); + AddStep("Show article", () => news.ShowArticle("article")); } + + private void setUpNewsResponse(GetNewsResponse r) + => AddStep("set up response", () => + { + dummyAPI.HandleRequest = request => + { + if (!(request is GetNewsRequest getNewsRequest)) + return; + + getNewsRequest.TriggerSuccess(r); + }; + }); + + private GetNewsResponse responseExample => new GetNewsResponse + { + NewsPosts = new[] + { + new APINewsPost + { + Title = "This post has an image which starts with \"/\" and has many authors!", + Preview = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.", + Author = "someone, someone1, someone2, someone3, someone4", + FirstImage = "/help/wiki/shared/news/banners/monthly-beatmapping-contest.png", + PublishedAt = DateTimeOffset.Now + }, + new APINewsPost + { + Title = "This post has a full-url image! (HTML entity: &)", + Preview = "boom (HTML entity: &)", + Author = "user (HTML entity: &)", + FirstImage = "https://assets.ppy.sh/artists/88/header.jpg", + PublishedAt = DateTimeOffset.Now + } + } + }; } } From 6df1b1d9ea19bfd65be8bba837c5c8b2feff38ad Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 16 Jul 2020 20:38:33 +0900 Subject: [PATCH 0131/1782] Add a background beatmap difficulty manager --- osu.Game/Beatmaps/BeatmapDifficultyManager.cs | 99 +++++++++++++++++++ osu.Game/OsuGameBase.cs | 1 + 2 files changed, 100 insertions(+) create mode 100644 osu.Game/Beatmaps/BeatmapDifficultyManager.cs diff --git a/osu.Game/Beatmaps/BeatmapDifficultyManager.cs b/osu.Game/Beatmaps/BeatmapDifficultyManager.cs new file mode 100644 index 0000000000..f09118a24a --- /dev/null +++ b/osu.Game/Beatmaps/BeatmapDifficultyManager.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 System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using JetBrains.Annotations; +using osu.Framework.Allocation; +using osu.Framework.Graphics.Containers; +using osu.Framework.Threading; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Mods; + +namespace osu.Game.Beatmaps +{ + public class BeatmapDifficultyManager : CompositeDrawable + { + // Too many simultaneous updates can lead to stutters. One thread seems to work fine for song select display purposes. + private readonly ThreadedTaskScheduler updateScheduler = new ThreadedTaskScheduler(1, nameof(BeatmapDifficultyManager)); + + private readonly TimedExpiryCache difficultyCache = new TimedExpiryCache { ExpiryTime = 60000 }; + private readonly BeatmapManager beatmapManager; + + public BeatmapDifficultyManager(BeatmapManager beatmapManager) + { + this.beatmapManager = beatmapManager; + } + + public Task GetDifficultyAsync([NotNull] BeatmapInfo beatmapInfo, [CanBeNull] RulesetInfo rulesetInfo = null, [CanBeNull] IReadOnlyList mods = null, + CancellationToken cancellationToken = default) + => Task.Factory.StartNew(() => GetDifficulty(beatmapInfo, rulesetInfo, mods), cancellationToken, + TaskCreationOptions.HideScheduler | TaskCreationOptions.RunContinuationsAsynchronously, + updateScheduler); + + public double GetDifficulty([NotNull] BeatmapInfo beatmapInfo, [CanBeNull] RulesetInfo rulesetInfo = null, [CanBeNull] IReadOnlyList mods = null) + { + // Difficulty can only be computed if the beatmap is locally available. + if (beatmapInfo.ID == 0) + return 0; + + // In the case that the user hasn't given us a ruleset, use the beatmap's default ruleset. + rulesetInfo ??= beatmapInfo.Ruleset; + + var key = new DifficultyCacheLookup(beatmapInfo, rulesetInfo, mods); + if (difficultyCache.TryGetValue(key, out var existing)) + return existing; + + try + { + var ruleset = rulesetInfo.CreateInstance(); + Debug.Assert(ruleset != null); + + var calculator = ruleset.CreateDifficultyCalculator(beatmapManager.GetWorkingBeatmap(beatmapInfo)); + var attributes = calculator.Calculate(mods?.ToArray() ?? Array.Empty()); + + difficultyCache.Add(key, attributes.StarRating); + return attributes.StarRating; + } + catch + { + difficultyCache.Add(key, 0); + return 0; + } + } + + private readonly struct DifficultyCacheLookup : IEquatable + { + private readonly BeatmapInfo beatmapInfo; + private readonly RulesetInfo rulesetInfo; + private readonly IReadOnlyList mods; + + public DifficultyCacheLookup(BeatmapInfo beatmapInfo, RulesetInfo rulesetInfo, IEnumerable mods) + { + this.beatmapInfo = beatmapInfo; + this.rulesetInfo = rulesetInfo; + this.mods = mods?.OrderBy(m => m.Acronym).ToArray() ?? Array.Empty(); + } + + public bool Equals(DifficultyCacheLookup other) + => beatmapInfo.Equals(other.beatmapInfo) + && mods.SequenceEqual(other.mods); + + public override int GetHashCode() + { + var hashCode = new HashCode(); + + hashCode.Add(beatmapInfo.Hash); + hashCode.Add(rulesetInfo.GetHashCode()); + foreach (var mod in mods) + hashCode.Add(mod.Acronym); + + return hashCode.ToHashCode(); + } + } + } +} diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index dd120937af..1e6631ffa0 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -199,6 +199,7 @@ namespace osu.Game ScoreManager.Undelete(getBeatmapScores(item), true); }); + dependencies.Cache(new BeatmapDifficultyManager(BeatmapManager)); dependencies.Cache(KeyBindingStore = new KeyBindingStore(contextFactory, RulesetStore)); dependencies.Cache(SettingsStore = new SettingsStore(contextFactory)); dependencies.Cache(RulesetConfigCache = new RulesetConfigCache(SettingsStore)); From 68d2888a8c16cd5e16c1f50f12e5d22279dfd2c4 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Thu, 16 Jul 2020 14:48:40 +0300 Subject: [PATCH 0132/1782] Add NewsOverlay to the game --- osu.Game.Tests/Visual/TestSceneOsuGame.cs | 1 + osu.Game/OsuGame.cs | 5 ++++- osu.Game/Overlays/Toolbar/Toolbar.cs | 1 + .../Overlays/Toolbar/ToolbarNewsButton.cs | 22 +++++++++++++++++++ 4 files changed, 28 insertions(+), 1 deletion(-) create mode 100644 osu.Game/Overlays/Toolbar/ToolbarNewsButton.cs diff --git a/osu.Game.Tests/Visual/TestSceneOsuGame.cs b/osu.Game.Tests/Visual/TestSceneOsuGame.cs index 22ae5257e7..b347c39c1e 100644 --- a/osu.Game.Tests/Visual/TestSceneOsuGame.cs +++ b/osu.Game.Tests/Visual/TestSceneOsuGame.cs @@ -44,6 +44,7 @@ namespace osu.Game.Tests.Visual typeof(NotificationOverlay), typeof(BeatmapListingOverlay), typeof(DashboardOverlay), + typeof(NewsOverlay), typeof(ChannelManager), typeof(ChatOverlay), typeof(SettingsOverlay), diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 618049e72c..84b32673d5 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -71,6 +71,8 @@ namespace osu.Game private DashboardOverlay dashboard; + private NewsOverlay news; + private UserProfileOverlay userProfile; private BeatmapSetOverlay beatmapSetOverlay; @@ -630,6 +632,7 @@ namespace osu.Game // overlay elements loadComponentSingleFile(beatmapListing = new BeatmapListingOverlay(), overlayContent.Add, true); loadComponentSingleFile(dashboard = new DashboardOverlay(), overlayContent.Add, true); + loadComponentSingleFile(news = new NewsOverlay(), overlayContent.Add, true); var rankingsOverlay = loadComponentSingleFile(new RankingsOverlay(), overlayContent.Add, true); loadComponentSingleFile(channelManager = new ChannelManager(), AddInternal, true); loadComponentSingleFile(chatOverlay = new ChatOverlay(), overlayContent.Add, true); @@ -687,7 +690,7 @@ namespace osu.Game } // ensure only one of these overlays are open at once. - var singleDisplayOverlays = new OverlayContainer[] { chatOverlay, dashboard, beatmapListing, changelogOverlay, rankingsOverlay }; + var singleDisplayOverlays = new OverlayContainer[] { chatOverlay, news, dashboard, beatmapListing, changelogOverlay, rankingsOverlay }; foreach (var overlay in singleDisplayOverlays) { diff --git a/osu.Game/Overlays/Toolbar/Toolbar.cs b/osu.Game/Overlays/Toolbar/Toolbar.cs index de08b79f57..5bdd86c671 100644 --- a/osu.Game/Overlays/Toolbar/Toolbar.cs +++ b/osu.Game/Overlays/Toolbar/Toolbar.cs @@ -69,6 +69,7 @@ namespace osu.Game.Overlays.Toolbar AutoSizeAxes = Axes.X, Children = new Drawable[] { + new ToolbarNewsButton(), new ToolbarChangelogButton(), new ToolbarRankingsButton(), new ToolbarBeatmapListingButton(), diff --git a/osu.Game/Overlays/Toolbar/ToolbarNewsButton.cs b/osu.Game/Overlays/Toolbar/ToolbarNewsButton.cs new file mode 100644 index 0000000000..e813a3f4cb --- /dev/null +++ b/osu.Game/Overlays/Toolbar/ToolbarNewsButton.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.Allocation; +using osu.Framework.Graphics.Sprites; + +namespace osu.Game.Overlays.Toolbar +{ + public class ToolbarNewsButton : ToolbarOverlayToggleButton + { + public ToolbarNewsButton() + { + Icon = FontAwesome.Solid.Newspaper; + } + + [BackgroundDependencyLoader(true)] + private void load(NewsOverlay news) + { + StateContainer = news; + } + } +} From dac98c8914c7c7f873fa26945bf2195fd72f271b Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Thu, 16 Jul 2020 14:55:02 +0300 Subject: [PATCH 0133/1782] Add shortcut for news overlay --- osu.Game/Input/Bindings/GlobalActionContainer.cs | 4 ++++ osu.Game/OsuGame.cs | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs index 6ae420b162..9f59551b94 100644 --- a/osu.Game/Input/Bindings/GlobalActionContainer.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs @@ -36,6 +36,7 @@ namespace osu.Game.Input.Bindings new KeyBinding(new[] { InputKey.Control, InputKey.O }, GlobalAction.ToggleSettings), new KeyBinding(new[] { InputKey.Control, InputKey.D }, GlobalAction.ToggleDirect), new KeyBinding(new[] { InputKey.Control, InputKey.N }, GlobalAction.ToggleNotifications), + new KeyBinding(new[] { InputKey.Control, InputKey.A }, GlobalAction.ToggleNews), new KeyBinding(InputKey.Escape, GlobalAction.Back), new KeyBinding(InputKey.ExtraMouseButton1, GlobalAction.Back), @@ -165,5 +166,8 @@ namespace osu.Game.Input.Bindings [Description("Pause")] PauseGameplay, + + [Description("Toggle news overlay")] + ToggleNews } } diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 84b32673d5..fc904cb09c 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -872,6 +872,10 @@ namespace osu.Game dashboard.ToggleVisibility(); return true; + case GlobalAction.ToggleNews: + news.ToggleVisibility(); + return true; + case GlobalAction.ResetInputSettings: var sensitivity = frameworkConfig.GetBindable(FrameworkSetting.CursorSensitivity); From 3191bb506fe41950ec8f3b25be5632782499479a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 16 Jul 2020 21:07:14 +0900 Subject: [PATCH 0134/1782] Improve asynchronous process --- osu.Game/Beatmaps/BeatmapDifficultyManager.cs | 83 ++++++++++++------- 1 file changed, 55 insertions(+), 28 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapDifficultyManager.cs b/osu.Game/Beatmaps/BeatmapDifficultyManager.cs index f09118a24a..02342e9595 100644 --- a/osu.Game/Beatmaps/BeatmapDifficultyManager.cs +++ b/osu.Game/Beatmaps/BeatmapDifficultyManager.cs @@ -29,32 +29,32 @@ namespace osu.Game.Beatmaps this.beatmapManager = beatmapManager; } - public Task GetDifficultyAsync([NotNull] BeatmapInfo beatmapInfo, [CanBeNull] RulesetInfo rulesetInfo = null, [CanBeNull] IReadOnlyList mods = null, - CancellationToken cancellationToken = default) - => Task.Factory.StartNew(() => GetDifficulty(beatmapInfo, rulesetInfo, mods), cancellationToken, - TaskCreationOptions.HideScheduler | TaskCreationOptions.RunContinuationsAsynchronously, - updateScheduler); + public async Task GetDifficultyAsync([NotNull] BeatmapInfo beatmapInfo, [CanBeNull] RulesetInfo rulesetInfo = null, [CanBeNull] IReadOnlyList mods = null, + CancellationToken cancellationToken = default) + { + if (tryGetGetExisting(beatmapInfo, rulesetInfo, mods, out var existing, out var key)) + return existing; + + return await Task.Factory.StartNew(() => getDifficulty(key), cancellationToken, TaskCreationOptions.HideScheduler | TaskCreationOptions.RunContinuationsAsynchronously, updateScheduler); + } public double GetDifficulty([NotNull] BeatmapInfo beatmapInfo, [CanBeNull] RulesetInfo rulesetInfo = null, [CanBeNull] IReadOnlyList mods = null) { - // Difficulty can only be computed if the beatmap is locally available. - if (beatmapInfo.ID == 0) - return 0; - - // In the case that the user hasn't given us a ruleset, use the beatmap's default ruleset. - rulesetInfo ??= beatmapInfo.Ruleset; - - var key = new DifficultyCacheLookup(beatmapInfo, rulesetInfo, mods); - if (difficultyCache.TryGetValue(key, out var existing)) + if (tryGetGetExisting(beatmapInfo, rulesetInfo, mods, out var existing, out var key)) return existing; + return getDifficulty(key); + } + + private double getDifficulty(in DifficultyCacheLookup key) + { try { - var ruleset = rulesetInfo.CreateInstance(); + var ruleset = key.RulesetInfo.CreateInstance(); Debug.Assert(ruleset != null); - var calculator = ruleset.CreateDifficultyCalculator(beatmapManager.GetWorkingBeatmap(beatmapInfo)); - var attributes = calculator.Calculate(mods?.ToArray() ?? Array.Empty()); + var calculator = ruleset.CreateDifficultyCalculator(beatmapManager.GetWorkingBeatmap(key.BeatmapInfo)); + var attributes = calculator.Calculate(key.Mods); difficultyCache.Add(key, attributes.StarRating); return attributes.StarRating; @@ -66,30 +66,57 @@ namespace osu.Game.Beatmaps } } + /// + /// Attempts to retrieve an existing difficulty for the combination. + /// + /// The . + /// The . + /// The s. + /// The existing difficulty value, if present. + /// The key that was used to perform this lookup. This can be further used to query . + /// Whether an existing difficulty was found. + private bool tryGetGetExisting(BeatmapInfo beatmapInfo, RulesetInfo rulesetInfo, IReadOnlyList mods, out double existingDifficulty, out DifficultyCacheLookup key) + { + // In the case that the user hasn't given us a ruleset, use the beatmap's default ruleset. + rulesetInfo ??= beatmapInfo.Ruleset; + + // Difficulty can only be computed if the beatmap is locally available. + if (beatmapInfo.ID == 0) + { + existingDifficulty = 0; + key = default; + + return true; + } + + key = new DifficultyCacheLookup(beatmapInfo, rulesetInfo, mods); + return difficultyCache.TryGetValue(key, out existingDifficulty); + } + private readonly struct DifficultyCacheLookup : IEquatable { - private readonly BeatmapInfo beatmapInfo; - private readonly RulesetInfo rulesetInfo; - private readonly IReadOnlyList mods; + public readonly BeatmapInfo BeatmapInfo; + public readonly RulesetInfo RulesetInfo; + public readonly Mod[] Mods; public DifficultyCacheLookup(BeatmapInfo beatmapInfo, RulesetInfo rulesetInfo, IEnumerable mods) { - this.beatmapInfo = beatmapInfo; - this.rulesetInfo = rulesetInfo; - this.mods = mods?.OrderBy(m => m.Acronym).ToArray() ?? Array.Empty(); + BeatmapInfo = beatmapInfo; + RulesetInfo = rulesetInfo; + Mods = mods?.OrderBy(m => m.Acronym).ToArray() ?? Array.Empty(); } public bool Equals(DifficultyCacheLookup other) - => beatmapInfo.Equals(other.beatmapInfo) - && mods.SequenceEqual(other.mods); + => BeatmapInfo.Equals(other.BeatmapInfo) + && Mods.SequenceEqual(other.Mods); public override int GetHashCode() { var hashCode = new HashCode(); - hashCode.Add(beatmapInfo.Hash); - hashCode.Add(rulesetInfo.GetHashCode()); - foreach (var mod in mods) + hashCode.Add(BeatmapInfo.Hash); + hashCode.Add(RulesetInfo.GetHashCode()); + foreach (var mod in Mods) hashCode.Add(mod.Acronym); return hashCode.ToHashCode(); From 24f14751ce77a98c3a520e3b66c25df05666c0c6 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 16 Jul 2020 21:08:08 +0900 Subject: [PATCH 0135/1782] Update beatmap details SR on ruleset/mod changes --- .../Screens/Select/Details/AdvancedStats.cs | 36 +++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Select/Details/AdvancedStats.cs b/osu.Game/Screens/Select/Details/AdvancedStats.cs index 02822ea608..c5fc3701f8 100644 --- a/osu.Game/Screens/Select/Details/AdvancedStats.cs +++ b/osu.Game/Screens/Select/Details/AdvancedStats.cs @@ -14,10 +14,13 @@ using osu.Framework.Bindables; using System.Collections.Generic; using osu.Game.Rulesets.Mods; using System.Linq; +using System.Threading; +using System.Threading.Tasks; using osu.Framework.Threading; using osu.Framework.Utils; using osu.Game.Configuration; using osu.Game.Overlays.Settings; +using osu.Game.Rulesets; namespace osu.Game.Screens.Select.Details { @@ -26,6 +29,12 @@ namespace osu.Game.Screens.Select.Details [Resolved] private IBindable> mods { get; set; } + [Resolved] + private IBindable ruleset { get; set; } + + [Resolved] + private BeatmapDifficultyManager difficultyManager { get; set; } + protected readonly StatisticRow FirstValue, HpDrain, Accuracy, ApproachRate; private readonly StatisticRow starDifficulty; @@ -71,6 +80,7 @@ namespace osu.Game.Screens.Select.Details { base.LoadComplete(); + ruleset.BindValueChanged(_ => updateStatistics()); mods.BindValueChanged(modsChanged, true); } @@ -132,11 +142,33 @@ namespace osu.Game.Screens.Select.Details break; } - starDifficulty.Value = ((float)(Beatmap?.StarDifficulty ?? 0), null); - HpDrain.Value = (baseDifficulty?.DrainRate ?? 0, adjustedDifficulty?.DrainRate); Accuracy.Value = (baseDifficulty?.OverallDifficulty ?? 0, adjustedDifficulty?.OverallDifficulty); ApproachRate.Value = (baseDifficulty?.ApproachRate ?? 0, adjustedDifficulty?.ApproachRate); + + updateStarDifficulty(); + } + + private CancellationTokenSource starDifficultyCancellationSource; + + private void updateStarDifficulty() + { + starDifficultyCancellationSource?.Cancel(); + + if (Beatmap == null) + return; + + var ourSource = starDifficultyCancellationSource = new CancellationTokenSource(); + + Task.WhenAll(difficultyManager.GetDifficultyAsync(Beatmap, ruleset.Value, cancellationToken: ourSource.Token), + difficultyManager.GetDifficultyAsync(Beatmap, ruleset.Value, mods.Value, ourSource.Token)).ContinueWith(t => + { + Schedule(() => + { + if (!ourSource.IsCancellationRequested) + starDifficulty.Value = ((float)t.Result[0], (float)t.Result[1]); + }); + }, ourSource.Token); } public class StatisticRow : Container, IHasAccentColour From 9a52058a7aa5a8aa153a8793c83d77b0b1d37b3f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 16 Jul 2020 21:08:24 +0900 Subject: [PATCH 0136/1782] Update carousel beatmap SR on mod/ruleset changes --- .../Carousel/DrawableCarouselBeatmap.cs | 45 ++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs index 3e4798a812..d4205a4b93 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs @@ -3,7 +3,9 @@ using System; using System.Collections.Generic; +using System.Threading; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; @@ -13,6 +15,7 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; +using osu.Framework.Threading; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; using osu.Game.Graphics; @@ -20,6 +23,8 @@ using osu.Game.Graphics.Backgrounds; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Overlays; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Mods; using osuTK; using osuTK.Graphics; @@ -41,6 +46,15 @@ namespace osu.Game.Screens.Select.Carousel [Resolved(CanBeNull = true)] private BeatmapSetOverlay beatmapOverlay { get; set; } + [Resolved] + private IBindable ruleset { get; set; } + + [Resolved] + private IBindable> mods { get; set; } + + [Resolved] + private BeatmapDifficultyManager difficultyManager { get; set; } + public DrawableCarouselBeatmap(CarouselBeatmap panel) : base(panel) { @@ -137,7 +151,6 @@ namespace osu.Game.Screens.Select.Carousel }, starCounter = new StarCounter { - Current = (float)beatmap.StarDifficulty, Scale = new Vector2(0.8f), } } @@ -147,6 +160,36 @@ namespace osu.Game.Screens.Select.Carousel } } }; + + ruleset.BindValueChanged(_ => refreshStarCounter()); + mods.BindValueChanged(_ => refreshStarCounter(), true); + } + + private ScheduledDelegate scheduledRefresh; + private CancellationTokenSource cancellationSource; + + private void refreshStarCounter() + { + scheduledRefresh?.Cancel(); + scheduledRefresh = null; + + cancellationSource?.Cancel(); + cancellationSource = null; + + // Only want to run the calculation when we become visible. + scheduledRefresh = Schedule(() => + { + var ourSource = cancellationSource = new CancellationTokenSource(); + difficultyManager.GetDifficultyAsync(beatmap, ruleset.Value, mods.Value, ourSource.Token).ContinueWith(t => + { + // We're currently on a random threadpool thread which we must exit. + Schedule(() => + { + if (!ourSource.IsCancellationRequested) + starCounter.Current = (float)t.Result; + }); + }, ourSource.Token); + }); } protected override void Selected() From 2d9909cdd89b658411b450ad6e6ee86a8ba67193 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Thu, 16 Jul 2020 15:18:01 +0300 Subject: [PATCH 0137/1782] Make news cards clickable --- .../Visual/Online/TestSceneNewsCard.cs | 7 +-- osu.Game/Overlays/News/NewsCard.cs | 50 ++++++++----------- 2 files changed, 26 insertions(+), 31 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneNewsCard.cs b/osu.Game.Tests/Visual/Online/TestSceneNewsCard.cs index 0446cadac9..17675bfbc0 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneNewsCard.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneNewsCard.cs @@ -31,15 +31,16 @@ namespace osu.Game.Tests.Visual.Online { new NewsCard(new APINewsPost { - Title = "This post has an image which starts with \"/\" and has many authors!", + Title = "This post has an image which starts with \"/\" and has many authors! (clickable)", Preview = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.", Author = "someone, someone1, someone2, someone3, someone4", FirstImage = "/help/wiki/shared/news/banners/monthly-beatmapping-contest.png", - PublishedAt = DateTimeOffset.Now + PublishedAt = DateTimeOffset.Now, + Slug = "2020-07-16-summer-theme-park-2020-voting-open" }), new NewsCard(new APINewsPost { - Title = "This post has a full-url image! (HTML entity: &)", + Title = "This post has a full-url image! (HTML entity: &) (non-clickable)", Preview = "boom (HTML entity: &)", Author = "user (HTML entity: &)", FirstImage = "https://assets.ppy.sh/artists/88/header.jpg", diff --git a/osu.Game/Overlays/News/NewsCard.cs b/osu.Game/Overlays/News/NewsCard.cs index 9c478a7c1d..38362038ae 100644 --- a/osu.Game/Overlays/News/NewsCard.cs +++ b/osu.Game/Overlays/News/NewsCard.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.Extensions.Color4Extensions; using osu.Framework.Graphics; @@ -10,18 +11,17 @@ using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; -using osu.Framework.Input.Events; +using osu.Framework.Platform; using osu.Game.Graphics; +using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; -using osu.Game.Graphics.UserInterface; using osu.Game.Online.API.Requests.Responses; namespace osu.Game.Overlays.News { - public class NewsCard : CompositeDrawable + public class NewsCard : OsuHoverContainer { - [Resolved] - private OverlayColourProvider colourProvider { get; set; } + protected override IEnumerable EffectTargets => new[] { background }; private readonly APINewsPost post; @@ -31,24 +31,28 @@ namespace osu.Game.Overlays.News public NewsCard(APINewsPost post) { this.post = post; - } - [BackgroundDependencyLoader] - private void load() - { RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; Masking = true; CornerRadius = 6; + } + + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider, GameHost host) + { + if (post.Slug != null) + { + TooltipText = "view in browser"; + Action = () => host.OpenUrlExternally("https://osu.ppy.sh/home/news/" + post.Slug); + } NewsBackground bg; - - InternalChildren = new Drawable[] + AddRange(new Drawable[] { background = new Box { - RelativeSizeAxes = Axes.Both, - Colour = colourProvider.Background4 + RelativeSizeAxes = Axes.Both }, new FillFlowContainer { @@ -104,9 +108,11 @@ namespace osu.Game.Overlays.News } } } - }, - new HoverClickSounds() - }; + } + }); + + IdleColour = colourProvider.Background4; + HoverColour = colourProvider.Background3; bg.OnLoadComplete += d => d.FadeIn(250, Easing.In); @@ -116,18 +122,6 @@ namespace osu.Game.Overlays.News main.AddText(post.Author, t => t.Font = OsuFont.GetFont(size: 12, weight: FontWeight.SemiBold)); } - protected override bool OnHover(HoverEvent e) - { - background.FadeColour(colourProvider.Background3, 200, Easing.OutQuint); - return true; - } - - protected override void OnHoverLost(HoverLostEvent e) - { - background.FadeColour(colourProvider.Background4, 200, Easing.OutQuint); - base.OnHoverLost(e); - } - [LongRunningLoad] private class NewsBackground : Sprite { From 939441ae408d5f4eb7ee61dba8da54e5e056d481 Mon Sep 17 00:00:00 2001 From: Lucas A Date: Thu, 16 Jul 2020 14:50:11 +0200 Subject: [PATCH 0138/1782] Disable the windows key only when in gameplay. --- osu.Desktop/Windows/GameplayWinKeyHandler.cs | 14 +++++++------- osu.Game/Configuration/SessionStatics.cs | 4 +++- osu.Game/Screens/Play/ScreenSuspensionHandler.cs | 13 ++++++++++++- 3 files changed, 22 insertions(+), 9 deletions(-) diff --git a/osu.Desktop/Windows/GameplayWinKeyHandler.cs b/osu.Desktop/Windows/GameplayWinKeyHandler.cs index 4f74a4f492..d5ef89c680 100644 --- a/osu.Desktop/Windows/GameplayWinKeyHandler.cs +++ b/osu.Desktop/Windows/GameplayWinKeyHandler.cs @@ -11,26 +11,26 @@ namespace osu.Desktop.Windows { public class GameplayWinKeyHandler : Component { - private Bindable allowScreenSuspension; private Bindable disableWinKey; + private Bindable disableWinKeySetting; private GameHost host; [BackgroundDependencyLoader] - private void load(GameHost host, OsuConfigManager config) + private void load(GameHost host, OsuConfigManager config, SessionStatics statics) { this.host = host; - allowScreenSuspension = host.AllowScreenSuspension.GetBoundCopy(); - allowScreenSuspension.ValueChanged += toggleWinKey; + disableWinKey = statics.GetBindable(Static.DisableWindowsKey); + disableWinKey.ValueChanged += toggleWinKey; - disableWinKey = config.GetBindable(OsuSetting.GameplayDisableWinKey); - disableWinKey.BindValueChanged(t => allowScreenSuspension.TriggerChange(), true); + disableWinKeySetting = config.GetBindable(OsuSetting.GameplayDisableWinKey); + disableWinKeySetting.BindValueChanged(t => disableWinKey.TriggerChange(), true); } private void toggleWinKey(ValueChangedEvent e) { - if (!e.NewValue && disableWinKey.Value) + if (e.NewValue && disableWinKeySetting.Value) host.InputThread.Scheduler.Add(WindowsKey.Disable); else host.InputThread.Scheduler.Add(WindowsKey.Enable); diff --git a/osu.Game/Configuration/SessionStatics.cs b/osu.Game/Configuration/SessionStatics.cs index 40b2adb867..7aad79b5ad 100644 --- a/osu.Game/Configuration/SessionStatics.cs +++ b/osu.Game/Configuration/SessionStatics.cs @@ -12,12 +12,14 @@ namespace osu.Game.Configuration { Set(Static.LoginOverlayDisplayed, false); Set(Static.MutedAudioNotificationShownOnce, false); + Set(Static.DisableWindowsKey, false); } } public enum Static { LoginOverlayDisplayed, - MutedAudioNotificationShownOnce + MutedAudioNotificationShownOnce, + DisableWindowsKey } } diff --git a/osu.Game/Screens/Play/ScreenSuspensionHandler.cs b/osu.Game/Screens/Play/ScreenSuspensionHandler.cs index 8585a5c309..c2c7f1ac41 100644 --- a/osu.Game/Screens/Play/ScreenSuspensionHandler.cs +++ b/osu.Game/Screens/Play/ScreenSuspensionHandler.cs @@ -8,6 +8,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Platform; +using osu.Game.Configuration; namespace osu.Game.Screens.Play { @@ -22,6 +23,9 @@ namespace osu.Game.Screens.Play [Resolved] private GameHost host { get; set; } + [Resolved] + private SessionStatics statics { get; set; } + public ScreenSuspensionHandler([NotNull] GameplayClockContainer gameplayClockContainer) { this.gameplayClockContainer = gameplayClockContainer ?? throw new ArgumentNullException(nameof(gameplayClockContainer)); @@ -36,7 +40,11 @@ namespace osu.Game.Screens.Play Debug.Assert(host.AllowScreenSuspension.Value); isPaused = gameplayClockContainer.IsPaused.GetBoundCopy(); - isPaused.BindValueChanged(paused => host.AllowScreenSuspension.Value = paused.NewValue, true); + isPaused.BindValueChanged(paused => + { + host.AllowScreenSuspension.Value = paused.NewValue; + statics.Set(Static.DisableWindowsKey, !paused.NewValue); + }, true); } protected override void Dispose(bool isDisposing) @@ -46,7 +54,10 @@ namespace osu.Game.Screens.Play isPaused?.UnbindAll(); if (host != null) + { host.AllowScreenSuspension.Value = true; + statics.Set(Static.DisableWindowsKey, false); + } } } } From 396ada7f39fb52a3301398c1cf8d17767da86bf6 Mon Sep 17 00:00:00 2001 From: Lucas A Date: Thu, 16 Jul 2020 15:03:25 +0200 Subject: [PATCH 0139/1782] Enable windows key when a replay is loaded. --- osu.Game/Screens/Play/Player.cs | 2 +- osu.Game/Screens/Play/ScreenSuspensionHandler.cs | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 541275cf55..e0721d55f7 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -181,7 +181,7 @@ namespace osu.Game.Screens.Play InternalChild = GameplayClockContainer = new GameplayClockContainer(Beatmap.Value, Mods.Value, DrawableRuleset.GameplayStartTime); AddInternal(gameplayBeatmap = new GameplayBeatmap(playableBeatmap)); - AddInternal(screenSuspension = new ScreenSuspensionHandler(GameplayClockContainer)); + AddInternal(screenSuspension = new ScreenSuspensionHandler(GameplayClockContainer, DrawableRuleset.HasReplayLoaded)); dependencies.CacheAs(gameplayBeatmap); diff --git a/osu.Game/Screens/Play/ScreenSuspensionHandler.cs b/osu.Game/Screens/Play/ScreenSuspensionHandler.cs index c2c7f1ac41..6865db5a5e 100644 --- a/osu.Game/Screens/Play/ScreenSuspensionHandler.cs +++ b/osu.Game/Screens/Play/ScreenSuspensionHandler.cs @@ -19,6 +19,7 @@ namespace osu.Game.Screens.Play { private readonly GameplayClockContainer gameplayClockContainer; private Bindable isPaused; + private readonly Bindable hasReplayLoaded; [Resolved] private GameHost host { get; set; } @@ -26,9 +27,10 @@ namespace osu.Game.Screens.Play [Resolved] private SessionStatics statics { get; set; } - public ScreenSuspensionHandler([NotNull] GameplayClockContainer gameplayClockContainer) + public ScreenSuspensionHandler([NotNull] GameplayClockContainer gameplayClockContainer, Bindable hasReplayLoaded) { this.gameplayClockContainer = gameplayClockContainer ?? throw new ArgumentNullException(nameof(gameplayClockContainer)); + this.hasReplayLoaded = hasReplayLoaded.GetBoundCopy(); } protected override void LoadComplete() @@ -43,8 +45,9 @@ namespace osu.Game.Screens.Play isPaused.BindValueChanged(paused => { host.AllowScreenSuspension.Value = paused.NewValue; - statics.Set(Static.DisableWindowsKey, !paused.NewValue); + statics.Set(Static.DisableWindowsKey, !paused.NewValue && !hasReplayLoaded.Value); }, true); + hasReplayLoaded.BindValueChanged(_ => isPaused.TriggerChange(), true); } protected override void Dispose(bool isDisposing) @@ -52,6 +55,7 @@ namespace osu.Game.Screens.Play base.Dispose(isDisposing); isPaused?.UnbindAll(); + hasReplayLoaded.UnbindAll(); if (host != null) { From f67b93936ffeb85bb86b0d27e4cd5af94148ea3b Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Thu, 16 Jul 2020 23:11:55 +0300 Subject: [PATCH 0140/1782] Remove shortcut --- osu.Game/Input/Bindings/GlobalActionContainer.cs | 4 ---- osu.Game/OsuGame.cs | 4 ---- 2 files changed, 8 deletions(-) diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs index 9f59551b94..6ae420b162 100644 --- a/osu.Game/Input/Bindings/GlobalActionContainer.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs @@ -36,7 +36,6 @@ namespace osu.Game.Input.Bindings new KeyBinding(new[] { InputKey.Control, InputKey.O }, GlobalAction.ToggleSettings), new KeyBinding(new[] { InputKey.Control, InputKey.D }, GlobalAction.ToggleDirect), new KeyBinding(new[] { InputKey.Control, InputKey.N }, GlobalAction.ToggleNotifications), - new KeyBinding(new[] { InputKey.Control, InputKey.A }, GlobalAction.ToggleNews), new KeyBinding(InputKey.Escape, GlobalAction.Back), new KeyBinding(InputKey.ExtraMouseButton1, GlobalAction.Back), @@ -166,8 +165,5 @@ namespace osu.Game.Input.Bindings [Description("Pause")] PauseGameplay, - - [Description("Toggle news overlay")] - ToggleNews } } diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index fc904cb09c..84b32673d5 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -872,10 +872,6 @@ namespace osu.Game dashboard.ToggleVisibility(); return true; - case GlobalAction.ToggleNews: - news.ToggleVisibility(); - return true; - case GlobalAction.ResetInputSettings: var sensitivity = frameworkConfig.GetBindable(FrameworkSetting.CursorSensitivity); From ab23e7dfd4faad3040a8e24eb114a16b6956c183 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Thu, 16 Jul 2020 23:14:51 +0300 Subject: [PATCH 0141/1782] Protect the NewsCard from clicks while hovering DateContainer --- osu.Game/Overlays/News/NewsCard.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Overlays/News/NewsCard.cs b/osu.Game/Overlays/News/NewsCard.cs index 38362038ae..201c3ce826 100644 --- a/osu.Game/Overlays/News/NewsCard.cs +++ b/osu.Game/Overlays/News/NewsCard.cs @@ -11,6 +11,7 @@ using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; +using osu.Framework.Input.Events; using osu.Framework.Platform; using osu.Game.Graphics; using osu.Game.Graphics.Containers; @@ -187,6 +188,8 @@ namespace osu.Game.Overlays.News } }; } + + protected override bool OnClick(ClickEvent e) => true; // Protects the NewsCard from clicks while hovering DateContainer } } } From c44ac9104f77d72d3917c47005a3c7f7b849c72e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 17 Jul 2020 14:19:43 +0900 Subject: [PATCH 0142/1782] Fix post-merge error --- .../Difficulty/Skills/Strain.cs | 95 ------------------- 1 file changed, 95 deletions(-) delete mode 100644 osu.Game.Rulesets.Taiko/Difficulty/Skills/Strain.cs diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Strain.cs b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Strain.cs deleted file mode 100644 index 2c1885ae1a..0000000000 --- a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Strain.cs +++ /dev/null @@ -1,95 +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.Game.Rulesets.Difficulty.Preprocessing; -using osu.Game.Rulesets.Difficulty.Skills; -using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing; -using osu.Game.Rulesets.Taiko.Objects; - -namespace osu.Game.Rulesets.Taiko.Difficulty.Skills -{ - public class Strain : Skill - { - private const double rhythm_change_base_threshold = 0.2; - private const double rhythm_change_base = 2.0; - - protected override double SkillMultiplier => 1; - protected override double StrainDecayBase => 0.3; - - private ColourSwitch lastColourSwitch = ColourSwitch.None; - - private int sameColourCount = 1; - - protected override double StrainValueOf(DifficultyHitObject current) - { - double addition = 1; - - // We get an extra addition if we are not a slider or spinner - if (current.LastObject is Hit && current.BaseObject is Hit && current.BaseObject.StartTime - current.LastObject.StartTime < 1000) - { - if (hasColourChange(current)) - addition += 0.75; - - if (hasRhythmChange(current)) - addition += 1; - } - else - { - lastColourSwitch = ColourSwitch.None; - sameColourCount = 1; - } - - double additionFactor = 1; - - // Scale the addition factor linearly from 0.4 to 1 for DeltaTime from 0 to 50 - if (current.DeltaTime < 50) - additionFactor = 0.4 + 0.6 * current.DeltaTime / 50; - - return additionFactor * addition; - } - - private bool hasRhythmChange(DifficultyHitObject current) - { - // We don't want a division by zero if some random mapper decides to put two HitObjects at the same time. - if (current.DeltaTime == 0 || Previous.Count == 0 || Previous[0].DeltaTime == 0) - return false; - - double timeElapsedRatio = Math.Max(Previous[0].DeltaTime / current.DeltaTime, current.DeltaTime / Previous[0].DeltaTime); - - if (timeElapsedRatio >= 8) - return false; - - double difference = Math.Log(timeElapsedRatio, rhythm_change_base) % 1.0; - - return difference > rhythm_change_base_threshold && difference < 1 - rhythm_change_base_threshold; - } - - private bool hasColourChange(DifficultyHitObject current) - { - var taikoCurrent = (TaikoDifficultyHitObject)current; - - if (!taikoCurrent.HasTypeChange) - { - sameColourCount++; - return false; - } - - var oldColourSwitch = lastColourSwitch; - var newColourSwitch = sameColourCount % 2 == 0 ? ColourSwitch.Even : ColourSwitch.Odd; - - lastColourSwitch = newColourSwitch; - sameColourCount = 1; - - // We only want a bonus if the parity of the color switch changes - return oldColourSwitch != ColourSwitch.None && oldColourSwitch != newColourSwitch; - } - - private enum ColourSwitch - { - None, - Even, - Odd - } - } -} From a39c4236c7d8bdaaa7e86f50de4eb8282c4e0999 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 17 Jul 2020 19:08:50 +0900 Subject: [PATCH 0143/1782] Fix multiple issues and standardise transforms --- osu.Game/Screens/Play/GameplayMenuOverlay.cs | 7 ++++--- osu.Game/Screens/Play/PauseOverlay.cs | 15 ++++++++++----- osu.Game/Skinning/SkinnableSound.cs | 6 ++++-- 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/osu.Game/Screens/Play/GameplayMenuOverlay.cs b/osu.Game/Screens/Play/GameplayMenuOverlay.cs index 6b37135c86..57403a0987 100644 --- a/osu.Game/Screens/Play/GameplayMenuOverlay.cs +++ b/osu.Game/Screens/Play/GameplayMenuOverlay.cs @@ -24,7 +24,8 @@ namespace osu.Game.Screens.Play { public abstract class GameplayMenuOverlay : OverlayContainer, IKeyBindingHandler { - private const int transition_duration = 200; + protected const int TRANSITION_DURATION = 200; + private const int button_height = 70; private const float background_alpha = 0.75f; @@ -156,8 +157,8 @@ namespace osu.Game.Screens.Play } } - protected override void PopIn() => this.FadeIn(transition_duration, Easing.In); - protected override void PopOut() => this.FadeOut(transition_duration, Easing.In); + protected override void PopIn() => this.FadeIn(TRANSITION_DURATION, Easing.In); + protected override void PopOut() => this.FadeOut(TRANSITION_DURATION, Easing.In); // Don't let mouse down events through the overlay or people can click circles while paused. protected override bool OnMouseDown(MouseDownEvent e) => true; diff --git a/osu.Game/Screens/Play/PauseOverlay.cs b/osu.Game/Screens/Play/PauseOverlay.cs index 7b3fba7ddf..e74585990a 100644 --- a/osu.Game/Screens/Play/PauseOverlay.cs +++ b/osu.Game/Screens/Play/PauseOverlay.cs @@ -16,6 +16,8 @@ namespace osu.Game.Screens.Play { public Action OnResume; + public override bool IsPresent => base.IsPresent || pauseLoop.IsPlaying; + public override string Header => "paused"; public override string Description => "you're not going to do what i think you're going to do, are ya?"; @@ -23,6 +25,8 @@ namespace osu.Game.Screens.Play protected override Action BackAction => () => InternalButtons.Children.First().Click(); + private const float minimum_volume = 0.0001f; + [BackgroundDependencyLoader] private void load(OsuColour colours) { @@ -34,18 +38,20 @@ namespace osu.Game.Screens.Play { Looping = true, }); + // PopIn is called before updating the skin, and when a sample is updated, its "playing" value is reset // the sample must be played again pauseLoop.OnSkinChanged += () => pauseLoop.Play(); + + // SkinnableSound only plays a sound if its aggregate volume is > 0, so the volume must be turned up before playing it + pauseLoop.VolumeTo(minimum_volume); } protected override void PopIn() { base.PopIn(); - //SkinnableSound only plays a sound if its aggregate volume is > 0, so the volume must be turned up before playing it - pauseLoop.VolumeTo(0.00001f); - pauseLoop.VolumeTo(1.0f, 400, Easing.InQuint); + pauseLoop.VolumeTo(1.0f, TRANSITION_DURATION, Easing.InQuint); pauseLoop.Play(); } @@ -53,8 +59,7 @@ namespace osu.Game.Screens.Play { base.PopOut(); - var transformSeq = pauseLoop.VolumeTo(0.0f, 190, Easing.OutQuad); - transformSeq.Finally(_ => pauseLoop.Stop()); + pauseLoop.VolumeTo(minimum_volume, TRANSITION_DURATION, Easing.OutQuad).Finally(_ => pauseLoop.Stop()); } } } diff --git a/osu.Game/Skinning/SkinnableSound.cs b/osu.Game/Skinning/SkinnableSound.cs index 24d6648273..fb27ba0550 100644 --- a/osu.Game/Skinning/SkinnableSound.cs +++ b/osu.Game/Skinning/SkinnableSound.cs @@ -45,6 +45,10 @@ namespace osu.Game.Skinning public BindableNumber Tempo => samplesContainer.Tempo; + public override bool IsPresent => Scheduler.HasPendingTasks || IsPlaying; + + public bool IsPlaying => samplesContainer.Any(s => s.Playing); + /// /// Smoothly adjusts over time. /// @@ -94,8 +98,6 @@ namespace osu.Game.Skinning public void Stop() => samplesContainer.ForEach(c => c.Stop()); - public override bool IsPresent => Scheduler.HasPendingTasks; - protected override void SkinChanged(ISkinSource skin, bool allowFallback) { var channels = hitSamples.Select(s => From 77143952a91da519665c0a13a19abe01fb97275a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 17 Jul 2020 19:17:48 +0900 Subject: [PATCH 0144/1782] Add test coverage --- .../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 1961a224c1..420bf29429 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs @@ -11,6 +11,7 @@ using osu.Game.Graphics.Containers; using osu.Game.Graphics.Cursor; using osu.Game.Rulesets; using osu.Game.Screens.Play; +using osu.Game.Skinning; using osuTK; using osuTK.Input; @@ -221,6 +222,31 @@ namespace osu.Game.Tests.Visual.Gameplay confirmExited(); } + [Test] + public void TestPauseSoundLoop() + { + AddStep("seek before gameplay", () => Player.GameplayClockContainer.Seek(-5000)); + + SkinnableSound getLoop() => Player.ChildrenOfType().FirstOrDefault()?.ChildrenOfType().FirstOrDefault(); + + pauseAndConfirm(); + AddAssert("loop is playing", () => getLoop().IsPlaying); + + resumeAndConfirm(); + AddUntilStep("loop is stopped", () => !getLoop().IsPlaying); + + AddUntilStep("pause again", () => + { + Player.Pause(); + return !Player.GameplayClockContainer.GameplayClock.IsRunning; + }); + + AddAssert("loop is playing", () => getLoop().IsPlaying); + + resumeAndConfirm(); + AddUntilStep("loop is stopped", () => !getLoop().IsPlaying); + } + private void pauseAndConfirm() { pause(); From 9857779d424a8caa066a25a6ee5c603150083dfb Mon Sep 17 00:00:00 2001 From: Fabian Date: Fri, 17 Jul 2020 16:12:01 +0200 Subject: [PATCH 0145/1782] Added slider for the grow/deflate mod --- osu.Game.Rulesets.Osu/Mods/OsuModDeflate.cs | 12 +++++++++++- osu.Game.Rulesets.Osu/Mods/OsuModGrow.cs | 12 +++++++++++- osu.Game.Rulesets.Osu/Mods/OsuModObjectScaleTween.cs | 4 ++-- 3 files changed, 24 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModDeflate.cs b/osu.Game.Rulesets.Osu/Mods/OsuModDeflate.cs index 73cb483ef0..60dbe16453 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModDeflate.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModDeflate.cs @@ -1,7 +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.Bindables; using osu.Framework.Graphics.Sprites; +using osu.Game.Configuration; namespace osu.Game.Rulesets.Osu.Mods { @@ -15,6 +17,14 @@ namespace osu.Game.Rulesets.Osu.Mods public override string Description => "Hit them at the right size!"; - protected override float StartScale => 2f; + [SettingSource("Starting size", "The object starting size")] + public override BindableNumber StartScale { get; } = new BindableFloat + { + MinValue = 1f, + MaxValue = 15f, + Default = 2f, + Value = 2f, + Precision = 0.1f, + }; } } diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModGrow.cs b/osu.Game.Rulesets.Osu/Mods/OsuModGrow.cs index f08d4e8f5e..8e8268f8cf 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModGrow.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModGrow.cs @@ -1,7 +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.Bindables; using osu.Framework.Graphics.Sprites; +using osu.Game.Configuration; namespace osu.Game.Rulesets.Osu.Mods { @@ -15,6 +17,14 @@ namespace osu.Game.Rulesets.Osu.Mods public override string Description => "Hit them at the right size!"; - protected override float StartScale => 0.5f; + [SettingSource("Starting size", "The object starting size")] + public override BindableNumber StartScale { get; } = new BindableFloat + { + MinValue = 0f, + MaxValue = 0.99f, + Default = 0.5f, + Value = 0.5f, + Precision = 0.01f, + }; } } diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModObjectScaleTween.cs b/osu.Game.Rulesets.Osu/Mods/OsuModObjectScaleTween.cs index 42ddddc4dd..06ba4cde4a 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModObjectScaleTween.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModObjectScaleTween.cs @@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Osu.Mods public override double ScoreMultiplier => 1; - protected virtual float StartScale => 1; + public abstract BindableNumber StartScale { get; } protected virtual float EndScale => 1; @@ -68,7 +68,7 @@ namespace osu.Game.Rulesets.Osu.Mods case DrawableHitCircle _: { using (drawable.BeginAbsoluteSequence(h.StartTime - h.TimePreempt)) - drawable.ScaleTo(StartScale).Then().ScaleTo(EndScale, h.TimePreempt, Easing.OutSine); + drawable.ScaleTo(StartScale.Value).Then().ScaleTo(EndScale, h.TimePreempt, Easing.OutSine); break; } } From a6cf77beae9236f2893068ad2c42b379c166749a Mon Sep 17 00:00:00 2001 From: Fabian Date: Fri, 17 Jul 2020 17:53:20 +0200 Subject: [PATCH 0146/1782] Clarified what the slider value is --- osu.Game.Rulesets.Osu/Mods/OsuModDeflate.cs | 2 +- osu.Game.Rulesets.Osu/Mods/OsuModGrow.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModDeflate.cs b/osu.Game.Rulesets.Osu/Mods/OsuModDeflate.cs index 60dbe16453..076fde08f8 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModDeflate.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModDeflate.cs @@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Osu.Mods public override string Description => "Hit them at the right size!"; - [SettingSource("Starting size", "The object starting size")] + [SettingSource("Starting size modifier", "The object starting size modifier")] public override BindableNumber StartScale { get; } = new BindableFloat { MinValue = 1f, diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModGrow.cs b/osu.Game.Rulesets.Osu/Mods/OsuModGrow.cs index 8e8268f8cf..5288bdd62c 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModGrow.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModGrow.cs @@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Osu.Mods public override string Description => "Hit them at the right size!"; - [SettingSource("Starting size", "The object starting size")] + [SettingSource("Starting size modifier", "The object starting size modifier")] public override BindableNumber StartScale { get; } = new BindableFloat { MinValue = 0f, From 0975610bf77ac5fd574e71fbe4f4301f6f95c952 Mon Sep 17 00:00:00 2001 From: Fabian Date: Sat, 18 Jul 2020 02:21:55 +0200 Subject: [PATCH 0147/1782] Increased maximum start modifier higher (15x -> 25x) Tried playing around with higher values and personally had quite fun when the circles covered the whole screen so I raised the max modifier to 25. Works best with an AR of <6. --- osu.Game.Rulesets.Osu/Mods/OsuModDeflate.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModDeflate.cs b/osu.Game.Rulesets.Osu/Mods/OsuModDeflate.cs index 076fde08f8..6302d47843 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModDeflate.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModDeflate.cs @@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Osu.Mods public override BindableNumber StartScale { get; } = new BindableFloat { MinValue = 1f, - MaxValue = 15f, + MaxValue = 25f, Default = 2f, Value = 2f, Precision = 0.1f, From 20096f9aea4fedd471f80c3c12d1d53647c2d70b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 18 Jul 2020 11:44:18 +0900 Subject: [PATCH 0148/1782] Remove remaining per-Update transform in OsuLogo to reduce allocations --- osu.Game/Screens/Menu/OsuLogo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Menu/OsuLogo.cs b/osu.Game/Screens/Menu/OsuLogo.cs index 089906c342..34d49685d2 100644 --- a/osu.Game/Screens/Menu/OsuLogo.cs +++ b/osu.Game/Screens/Menu/OsuLogo.cs @@ -330,7 +330,7 @@ namespace osu.Game.Screens.Menu if (Beatmap.Value.Track.IsRunning) { var maxAmplitude = lastBeatIndex >= 0 ? Beatmap.Value.Track.CurrentAmplitudes.Maximum : 0; - logoAmplitudeContainer.ScaleTo(1 - Math.Max(0, maxAmplitude - scale_adjust_cutoff) * 0.04f, 75, Easing.OutQuint); + logoAmplitudeContainer.Scale = new Vector2((float)Interpolation.Damp(logoAmplitudeContainer.Scale.X, 1 - Math.Max(0, maxAmplitude - scale_adjust_cutoff) * 0.04f, 0.5f, Time.Elapsed)); if (maxAmplitude > velocity_adjust_cutoff) triangles.Velocity = 1 + Math.Max(0, maxAmplitude - velocity_adjust_cutoff) * 50; From 81d95f8584b21f8b656ab522107a130acbe29941 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 18 Jul 2020 20:24:38 +0300 Subject: [PATCH 0149/1782] Implement UserBrickPanel component --- .../Visual/Online/TestSceneSocialOverlay.cs | 84 ------ .../Visual/Online/TestSceneUserPanel.cs | 13 + .../Dashboard/Friends/FriendDisplay.cs | 3 + .../OverlayPanelDisplayStyleControl.cs | 7 +- .../Sections/General/LoginSettings.cs | 2 +- osu.Game/Overlays/SocialOverlay.cs | 242 ------------------ osu.Game/Users/ExtendedUserPanel.cs | 148 +++++++++++ osu.Game/Users/UserBrickPanel.cs | 65 +++++ osu.Game/Users/UserGridPanel.cs | 2 +- osu.Game/Users/UserListPanel.cs | 2 +- osu.Game/Users/UserPanel.cs | 130 +--------- 11 files changed, 241 insertions(+), 457 deletions(-) delete mode 100644 osu.Game.Tests/Visual/Online/TestSceneSocialOverlay.cs delete mode 100644 osu.Game/Overlays/SocialOverlay.cs create mode 100644 osu.Game/Users/ExtendedUserPanel.cs create mode 100644 osu.Game/Users/UserBrickPanel.cs diff --git a/osu.Game.Tests/Visual/Online/TestSceneSocialOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneSocialOverlay.cs deleted file mode 100644 index 77e77d90c1..0000000000 --- a/osu.Game.Tests/Visual/Online/TestSceneSocialOverlay.cs +++ /dev/null @@ -1,84 +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 NUnit.Framework; -using osu.Game.Overlays; -using osu.Game.Users; - -namespace osu.Game.Tests.Visual.Online -{ - [TestFixture] - public class TestSceneSocialOverlay : OsuTestScene - { - protected override bool UseOnlineAPI => true; - - public TestSceneSocialOverlay() - { - SocialOverlay s = new SocialOverlay - { - Users = new[] - { - new User - { - Username = @"flyte", - Id = 3103765, - Country = new Country { FlagName = @"JP" }, - CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c1.jpg", - }, - new User - { - Username = @"Cookiezi", - Id = 124493, - Country = new Country { FlagName = @"KR" }, - CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c2.jpg", - }, - new User - { - Username = @"Angelsim", - Id = 1777162, - Country = new Country { FlagName = @"KR" }, - CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", - }, - new User - { - Username = @"Rafis", - Id = 2558286, - Country = new Country { FlagName = @"PL" }, - CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c4.jpg", - }, - new User - { - Username = @"hvick225", - Id = 50265, - Country = new Country { FlagName = @"TW" }, - CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c5.jpg", - }, - new User - { - Username = @"peppy", - Id = 2, - Country = new Country { FlagName = @"AU" }, - CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c6.jpg" - }, - new User - { - Username = @"filsdelama", - Id = 2831793, - Country = new Country { FlagName = @"FR" }, - CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c7.jpg" - }, - new User - { - Username = @"_index", - Id = 652457, - Country = new Country { FlagName = @"RU" }, - CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c8.jpg" - }, - }, - }; - Add(s); - - AddStep(@"toggle", s.ToggleVisibility); - } - } -} diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs b/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs index f763e50067..c2e9945c99 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs @@ -42,6 +42,19 @@ namespace osu.Game.Tests.Visual.Online Spacing = new Vector2(10f), Children = new Drawable[] { + new UserBrickPanel(new User + { + Username = @"flyte", + Id = 3103765, + CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c6.jpg" + }), + new UserBrickPanel(new User + { + Username = @"peppy", + Id = 2, + Colour = "99EB47", + CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", + }), flyte = new UserGridPanel(new User { Username = @"flyte", diff --git a/osu.Game/Overlays/Dashboard/Friends/FriendDisplay.cs b/osu.Game/Overlays/Dashboard/Friends/FriendDisplay.cs index 79fda99c73..41b25ee1a5 100644 --- a/osu.Game/Overlays/Dashboard/Friends/FriendDisplay.cs +++ b/osu.Game/Overlays/Dashboard/Friends/FriendDisplay.cs @@ -225,6 +225,9 @@ namespace osu.Game.Overlays.Dashboard.Friends case OverlayPanelDisplayStyle.List: return new UserListPanel(user); + + case OverlayPanelDisplayStyle.Brick: + return new UserBrickPanel(user); } } diff --git a/osu.Game/Overlays/OverlayPanelDisplayStyleControl.cs b/osu.Game/Overlays/OverlayPanelDisplayStyleControl.cs index 7269007b41..87b9d89d4d 100644 --- a/osu.Game/Overlays/OverlayPanelDisplayStyleControl.cs +++ b/osu.Game/Overlays/OverlayPanelDisplayStyleControl.cs @@ -34,6 +34,10 @@ namespace osu.Game.Overlays { Icon = FontAwesome.Solid.Bars }); + AddTabItem(new PanelDisplayTabItem(OverlayPanelDisplayStyle.Brick) + { + Icon = FontAwesome.Solid.Th + }); } protected override TabFillFlowContainer CreateTabFlow() => new TabFillFlowContainer @@ -96,6 +100,7 @@ namespace osu.Game.Overlays public enum OverlayPanelDisplayStyle { Card, - List + List, + Brick } } diff --git a/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs b/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs index 52b712a40e..34e5da4ef4 100644 --- a/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs @@ -33,7 +33,7 @@ namespace osu.Game.Overlays.Settings.Sections.General [Resolved] private OsuColour colours { get; set; } - private UserPanel panel; + private UserGridPanel panel; private UserDropdown dropdown; /// diff --git a/osu.Game/Overlays/SocialOverlay.cs b/osu.Game/Overlays/SocialOverlay.cs deleted file mode 100644 index 1b05142192..0000000000 --- a/osu.Game/Overlays/SocialOverlay.cs +++ /dev/null @@ -1,242 +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.Linq; -using osu.Framework.Bindables; -using osuTK; -using osuTK.Graphics; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Game.Graphics.UserInterface; -using osu.Game.Online.API; -using osu.Game.Online.API.Requests; -using osu.Game.Overlays.SearchableList; -using osu.Game.Overlays.Social; -using osu.Game.Users; -using System.Threading; -using osu.Framework.Allocation; -using osu.Framework.Extensions.Color4Extensions; -using osu.Framework.Threading; - -namespace osu.Game.Overlays -{ - public class SocialOverlay : SearchableListOverlay - { - private readonly LoadingSpinner loading; - private FillFlowContainer panels; - - protected override Color4 BackgroundColour => Color4Extensions.FromHex(@"60284b"); - protected override Color4 TrianglesColourLight => Color4Extensions.FromHex(@"672b51"); - protected override Color4 TrianglesColourDark => Color4Extensions.FromHex(@"5c2648"); - - protected override SearchableListHeader CreateHeader() => new Header(); - protected override SearchableListFilterControl CreateFilterControl() => new FilterControl(); - - private User[] users = Array.Empty(); - - public User[] Users - { - get => users; - set - { - if (users == value) - return; - - users = value ?? Array.Empty(); - - if (LoadState >= LoadState.Ready) - recreatePanels(); - } - } - - public SocialOverlay() - : base(OverlayColourScheme.Pink) - { - Add(loading = new LoadingSpinner()); - - Filter.Search.Current.ValueChanged += text => - { - if (!string.IsNullOrEmpty(text.NewValue)) - { - // force searching in players until searching for friends is supported - Header.Tabs.Current.Value = SocialTab.AllPlayers; - - if (Filter.Tabs.Current.Value != SocialSortCriteria.Rank) - Filter.Tabs.Current.Value = SocialSortCriteria.Rank; - } - }; - - Header.Tabs.Current.ValueChanged += _ => queueUpdate(); - Filter.Tabs.Current.ValueChanged += _ => onFilterUpdate(); - - Filter.DisplayStyleControl.DisplayStyle.ValueChanged += _ => recreatePanels(); - Filter.Dropdown.Current.ValueChanged += _ => recreatePanels(); - - currentQuery.BindTo(Filter.Search.Current); - currentQuery.ValueChanged += query => - { - queryChangedDebounce?.Cancel(); - - if (string.IsNullOrEmpty(query.NewValue)) - queueUpdate(); - else - queryChangedDebounce = Scheduler.AddDelayed(updateSearch, 500); - }; - } - - [BackgroundDependencyLoader] - private void load() - { - recreatePanels(); - } - - private APIRequest getUsersRequest; - - private readonly Bindable currentQuery = new Bindable(); - - private ScheduledDelegate queryChangedDebounce; - - private void queueUpdate() => Scheduler.AddOnce(updateSearch); - - private CancellationTokenSource loadCancellation; - - private void updateSearch() - { - queryChangedDebounce?.Cancel(); - - if (!IsLoaded) - return; - - Users = null; - clearPanels(); - getUsersRequest?.Cancel(); - - if (API?.IsLoggedIn != true) - return; - - switch (Header.Tabs.Current.Value) - { - case SocialTab.Friends: - var friendRequest = new GetFriendsRequest(); // TODO filter arguments? - friendRequest.Success += users => Users = users.ToArray(); - API.Queue(getUsersRequest = friendRequest); - break; - - default: - var userRequest = new GetUsersRequest(); // TODO filter arguments! - userRequest.Success += res => Users = res.Users.Select(r => r.User).ToArray(); - API.Queue(getUsersRequest = userRequest); - break; - } - } - - private void recreatePanels() - { - clearPanels(); - - if (Users == null) - { - loading.Hide(); - return; - } - - IEnumerable sortedUsers = Users; - - switch (Filter.Tabs.Current.Value) - { - case SocialSortCriteria.Location: - sortedUsers = sortedUsers.OrderBy(u => u.Country.FullName); - break; - - case SocialSortCriteria.Name: - sortedUsers = sortedUsers.OrderBy(u => u.Username); - break; - } - - if (Filter.Dropdown.Current.Value == SortDirection.Descending) - sortedUsers = sortedUsers.Reverse(); - - var newPanels = new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Spacing = new Vector2(10f), - Margin = new MarginPadding { Top = 10 }, - ChildrenEnumerable = sortedUsers.Select(u => - { - UserPanel panel; - - switch (Filter.DisplayStyleControl.DisplayStyle.Value) - { - case PanelDisplayStyle.Grid: - panel = new UserGridPanel(u) - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Width = 290, - }; - break; - - default: - panel = new UserListPanel(u); - break; - } - - panel.Status.BindTo(u.Status); - panel.Activity.BindTo(u.Activity); - return panel; - }) - }; - - LoadComponentAsync(newPanels, f => - { - if (panels != null) - ScrollFlow.Remove(panels); - - loading.Hide(); - ScrollFlow.Add(panels = newPanels); - }, (loadCancellation = new CancellationTokenSource()).Token); - } - - private void onFilterUpdate() - { - if (Filter.Tabs.Current.Value == SocialSortCriteria.Rank) - { - queueUpdate(); - return; - } - - recreatePanels(); - } - - private void clearPanels() - { - loading.Show(); - - loadCancellation?.Cancel(); - - if (panels != null) - { - panels.Expire(); - panels = null; - } - } - - public override void APIStateChanged(IAPIProvider api, APIState state) - { - switch (state) - { - case APIState.Online: - queueUpdate(); - break; - - default: - Users = null; - clearPanels(); - break; - } - } - } -} diff --git a/osu.Game/Users/ExtendedUserPanel.cs b/osu.Game/Users/ExtendedUserPanel.cs new file mode 100644 index 0000000000..5bd98b3fb7 --- /dev/null +++ b/osu.Game/Users/ExtendedUserPanel.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 osuTK; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Framework.Graphics.Sprites; +using osu.Game.Users.Drawables; +using osu.Framework.Input.Events; + +namespace osu.Game.Users +{ + public abstract class ExtendedUserPanel : UserPanel + { + public readonly Bindable Status = new Bindable(); + + public readonly IBindable Activity = new Bindable(); + + protected TextFlowContainer LastVisitMessage { get; private set; } + + private SpriteIcon statusIcon; + private OsuSpriteText statusMessage; + + public ExtendedUserPanel(User user) + : base(user) + { + } + + [BackgroundDependencyLoader] + private void load() + { + BorderColour = ColourProvider?.Light1 ?? Colours.GreyVioletLighter; + + Status.ValueChanged += status => displayStatus(status.NewValue, Activity.Value); + Activity.ValueChanged += activity => displayStatus(Status.Value, activity.NewValue); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + Status.TriggerChange(); + + // Colour should be applied immediately on first load. + statusIcon.FinishTransforms(); + } + + protected UpdateableAvatar CreateAvatar() => new UpdateableAvatar + { + User = User, + OpenOnClick = { Value = false } + }; + + protected UpdateableFlag CreateFlag() => new UpdateableFlag(User.Country) + { + Size = new Vector2(39, 26) + }; + + protected SpriteIcon CreateStatusIcon() => statusIcon = new SpriteIcon + { + Icon = FontAwesome.Regular.Circle, + Size = new Vector2(25) + }; + + protected FillFlowContainer CreateStatusMessage(bool rightAlignedChildren) + { + var statusContainer = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical + }; + + var alignment = rightAlignedChildren ? Anchor.CentreRight : Anchor.CentreLeft; + + statusContainer.Add(LastVisitMessage = new TextFlowContainer(t => t.Font = OsuFont.GetFont(size: 12, weight: FontWeight.SemiBold)).With(text => + { + text.Anchor = alignment; + text.Origin = alignment; + text.AutoSizeAxes = Axes.Both; + text.Alpha = 0; + + if (User.LastVisit.HasValue) + { + text.AddText(@"Last seen "); + text.AddText(new DrawableDate(User.LastVisit.Value, italic: false) + { + Shadow = false + }); + } + })); + + statusContainer.Add(statusMessage = new OsuSpriteText + { + Anchor = alignment, + Origin = alignment, + Font = OsuFont.GetFont(size: 14, weight: FontWeight.SemiBold) + }); + + return statusContainer; + } + + private void displayStatus(UserStatus status, UserActivity activity = null) + { + if (status != null) + { + LastVisitMessage.FadeTo(status is UserStatusOffline && User.LastVisit.HasValue ? 1 : 0); + + // Set status message based on activity (if we have one) and status is not offline + if (activity != null && !(status is UserStatusOffline)) + { + statusMessage.Text = activity.Status; + statusIcon.FadeColour(activity.GetAppropriateColour(Colours), 500, Easing.OutQuint); + return; + } + + // Otherwise use only status + statusMessage.Text = status.Message; + statusIcon.FadeColour(status.GetAppropriateColour(Colours), 500, Easing.OutQuint); + + return; + } + + // Fallback to web status if local one is null + if (User.IsOnline) + { + Status.Value = new UserStatusOnline(); + return; + } + + Status.Value = new UserStatusOffline(); + } + + protected override bool OnHover(HoverEvent e) + { + BorderThickness = 2; + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + BorderThickness = 0; + base.OnHoverLost(e); + } + } +} diff --git a/osu.Game/Users/UserBrickPanel.cs b/osu.Game/Users/UserBrickPanel.cs new file mode 100644 index 0000000000..f6eabc3b75 --- /dev/null +++ b/osu.Game/Users/UserBrickPanel.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 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.Graphics; +using osuTK; + +namespace osu.Game.Users +{ + public class UserBrickPanel : UserPanel + { + public UserBrickPanel(User user) + : base(user) + { + AutoSizeAxes = Axes.X; + Height = 23; + CornerRadius = 6; + } + + [BackgroundDependencyLoader] + private void load() + { + Background.FadeTo(0.3f); + } + + protected override Drawable CreateLayout() => new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(5, 0), + Margin = new MarginPadding + { + Horizontal = 5 + }, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Children = new Drawable[] + { + new CircularContainer + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Masking = true, + Width = 4, + Height = 13, + Child = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = string.IsNullOrEmpty(User.Colour) ? Color4Extensions.FromHex("0087ca") : Color4Extensions.FromHex(User.Colour) + } + }, + CreateUsername().With(u => + { + u.Anchor = Anchor.CentreLeft; + u.Origin = Anchor.CentreLeft; + u.Font = OsuFont.GetFont(size: 13, weight: FontWeight.Bold); + }) + } + }; + } +} diff --git a/osu.Game/Users/UserGridPanel.cs b/osu.Game/Users/UserGridPanel.cs index e62a834d6d..44dcbc305d 100644 --- a/osu.Game/Users/UserGridPanel.cs +++ b/osu.Game/Users/UserGridPanel.cs @@ -9,7 +9,7 @@ using osuTK; namespace osu.Game.Users { - public class UserGridPanel : UserPanel + public class UserGridPanel : ExtendedUserPanel { private const int margin = 10; diff --git a/osu.Game/Users/UserListPanel.cs b/osu.Game/Users/UserListPanel.cs index 1c3ae20577..9c95eff739 100644 --- a/osu.Game/Users/UserListPanel.cs +++ b/osu.Game/Users/UserListPanel.cs @@ -12,7 +12,7 @@ using osu.Game.Overlays.Profile.Header.Components; namespace osu.Game.Users { - public class UserListPanel : UserPanel + public class UserListPanel : ExtendedUserPanel { public UserListPanel(User user) : base(user) diff --git a/osu.Game/Users/UserPanel.cs b/osu.Game/Users/UserPanel.cs index 6f59f9e443..94c0c31cfc 100644 --- a/osu.Game/Users/UserPanel.cs +++ b/osu.Game/Users/UserPanel.cs @@ -2,9 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; -using osuTK; using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -14,11 +12,8 @@ using osu.Game.Overlays; using osu.Framework.Graphics.UserInterface; using osu.Game.Graphics.UserInterface; using osu.Framework.Graphics.Cursor; -using osu.Framework.Graphics.Sprites; using osu.Game.Graphics.Containers; -using osu.Game.Users.Drawables; using JetBrains.Annotations; -using osu.Framework.Input.Events; namespace osu.Game.Users { @@ -26,21 +21,12 @@ namespace osu.Game.Users { public readonly User User; - public readonly Bindable Status = new Bindable(); - - public readonly IBindable Activity = new Bindable(); - public new Action Action; protected Action ViewProfile { get; private set; } protected DelayedLoadUnloadWrapper Background { get; private set; } - protected TextFlowContainer LastVisitMessage { get; private set; } - - private SpriteIcon statusIcon; - private OsuSpriteText statusMessage; - protected UserPanel(User user) { if (user == null) @@ -53,23 +39,22 @@ namespace osu.Game.Users private UserProfileOverlay profileOverlay { get; set; } [Resolved(canBeNull: true)] - private OverlayColourProvider colourProvider { get; set; } + protected OverlayColourProvider ColourProvider { get; private set; } [Resolved] - private OsuColour colours { get; set; } + protected OsuColour Colours { get; private set; } [BackgroundDependencyLoader] private void load() { Masking = true; - BorderColour = colourProvider?.Light1 ?? colours.GreyVioletLighter; AddRange(new[] { new Box { RelativeSizeAxes = Axes.Both, - Colour = colourProvider?.Background5 ?? colours.Gray1 + Colour = ColourProvider?.Background5 ?? Colours.Gray1 }, Background = new DelayedLoadUnloadWrapper(() => new UserCoverBackground { @@ -86,9 +71,6 @@ namespace osu.Game.Users CreateLayout() }); - Status.ValueChanged += status => displayStatus(status.NewValue, Activity.Value); - Activity.ValueChanged += activity => displayStatus(Status.Value, activity.NewValue); - base.Action = ViewProfile = () => { Action?.Invoke(); @@ -96,41 +78,9 @@ namespace osu.Game.Users }; } - protected override void LoadComplete() - { - base.LoadComplete(); - Status.TriggerChange(); - - // Colour should be applied immediately on first load. - statusIcon.FinishTransforms(); - } - - protected override bool OnHover(HoverEvent e) - { - BorderThickness = 2; - return base.OnHover(e); - } - - protected override void OnHoverLost(HoverLostEvent e) - { - BorderThickness = 0; - base.OnHoverLost(e); - } - [NotNull] protected abstract Drawable CreateLayout(); - protected UpdateableAvatar CreateAvatar() => new UpdateableAvatar - { - User = User, - OpenOnClick = { Value = false } - }; - - protected UpdateableFlag CreateFlag() => new UpdateableFlag(User.Country) - { - Size = new Vector2(39, 26) - }; - protected OsuSpriteText CreateUsername() => new OsuSpriteText { Font = OsuFont.GetFont(size: 16, weight: FontWeight.Bold), @@ -138,80 +88,6 @@ namespace osu.Game.Users Text = User.Username, }; - protected SpriteIcon CreateStatusIcon() => statusIcon = new SpriteIcon - { - Icon = FontAwesome.Regular.Circle, - Size = new Vector2(25) - }; - - protected FillFlowContainer CreateStatusMessage(bool rightAlignedChildren) - { - var statusContainer = new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Vertical - }; - - var alignment = rightAlignedChildren ? Anchor.CentreRight : Anchor.CentreLeft; - - statusContainer.Add(LastVisitMessage = new TextFlowContainer(t => t.Font = OsuFont.GetFont(size: 12, weight: FontWeight.SemiBold)).With(text => - { - text.Anchor = alignment; - text.Origin = alignment; - text.AutoSizeAxes = Axes.Both; - text.Alpha = 0; - - if (User.LastVisit.HasValue) - { - text.AddText(@"Last seen "); - text.AddText(new DrawableDate(User.LastVisit.Value, italic: false) - { - Shadow = false - }); - } - })); - - statusContainer.Add(statusMessage = new OsuSpriteText - { - Anchor = alignment, - Origin = alignment, - Font = OsuFont.GetFont(size: 14, weight: FontWeight.SemiBold) - }); - - return statusContainer; - } - - private void displayStatus(UserStatus status, UserActivity activity = null) - { - if (status != null) - { - LastVisitMessage.FadeTo(status is UserStatusOffline && User.LastVisit.HasValue ? 1 : 0); - - // Set status message based on activity (if we have one) and status is not offline - if (activity != null && !(status is UserStatusOffline)) - { - statusMessage.Text = activity.Status; - statusIcon.FadeColour(activity.GetAppropriateColour(colours), 500, Easing.OutQuint); - return; - } - - // Otherwise use only status - statusMessage.Text = status.Message; - statusIcon.FadeColour(status.GetAppropriateColour(colours), 500, Easing.OutQuint); - - return; - } - - // Fallback to web status if local one is null - if (User.IsOnline) - { - Status.Value = new UserStatusOnline(); - return; - } - - Status.Value = new UserStatusOffline(); - } - public MenuItem[] ContextMenuItems => new MenuItem[] { new OsuMenuItem("View Profile", MenuItemType.Highlighted, ViewProfile), From 753b1f3401757cb6cd71b1cece3b2499a59f1328 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 18 Jul 2020 20:26:15 +0300 Subject: [PATCH 0150/1782] Make ctor protected --- osu.Game/Users/ExtendedUserPanel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Users/ExtendedUserPanel.cs b/osu.Game/Users/ExtendedUserPanel.cs index 5bd98b3fb7..2604815751 100644 --- a/osu.Game/Users/ExtendedUserPanel.cs +++ b/osu.Game/Users/ExtendedUserPanel.cs @@ -25,7 +25,7 @@ namespace osu.Game.Users private SpriteIcon statusIcon; private OsuSpriteText statusMessage; - public ExtendedUserPanel(User user) + protected ExtendedUserPanel(User user) : base(user) { } From 3e773fde27d165bd43558ba48d5685c3568d5a26 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 18 Jul 2020 23:15:22 +0300 Subject: [PATCH 0151/1782] Remove SocialOverlay component as never being used --- .../Visual/Online/TestSceneSocialOverlay.cs | 84 ------ osu.Game/Overlays/SocialOverlay.cs | 242 ------------------ 2 files changed, 326 deletions(-) delete mode 100644 osu.Game.Tests/Visual/Online/TestSceneSocialOverlay.cs delete mode 100644 osu.Game/Overlays/SocialOverlay.cs diff --git a/osu.Game.Tests/Visual/Online/TestSceneSocialOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneSocialOverlay.cs deleted file mode 100644 index 77e77d90c1..0000000000 --- a/osu.Game.Tests/Visual/Online/TestSceneSocialOverlay.cs +++ /dev/null @@ -1,84 +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 NUnit.Framework; -using osu.Game.Overlays; -using osu.Game.Users; - -namespace osu.Game.Tests.Visual.Online -{ - [TestFixture] - public class TestSceneSocialOverlay : OsuTestScene - { - protected override bool UseOnlineAPI => true; - - public TestSceneSocialOverlay() - { - SocialOverlay s = new SocialOverlay - { - Users = new[] - { - new User - { - Username = @"flyte", - Id = 3103765, - Country = new Country { FlagName = @"JP" }, - CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c1.jpg", - }, - new User - { - Username = @"Cookiezi", - Id = 124493, - Country = new Country { FlagName = @"KR" }, - CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c2.jpg", - }, - new User - { - Username = @"Angelsim", - Id = 1777162, - Country = new Country { FlagName = @"KR" }, - CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", - }, - new User - { - Username = @"Rafis", - Id = 2558286, - Country = new Country { FlagName = @"PL" }, - CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c4.jpg", - }, - new User - { - Username = @"hvick225", - Id = 50265, - Country = new Country { FlagName = @"TW" }, - CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c5.jpg", - }, - new User - { - Username = @"peppy", - Id = 2, - Country = new Country { FlagName = @"AU" }, - CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c6.jpg" - }, - new User - { - Username = @"filsdelama", - Id = 2831793, - Country = new Country { FlagName = @"FR" }, - CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c7.jpg" - }, - new User - { - Username = @"_index", - Id = 652457, - Country = new Country { FlagName = @"RU" }, - CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c8.jpg" - }, - }, - }; - Add(s); - - AddStep(@"toggle", s.ToggleVisibility); - } - } -} diff --git a/osu.Game/Overlays/SocialOverlay.cs b/osu.Game/Overlays/SocialOverlay.cs deleted file mode 100644 index 1b05142192..0000000000 --- a/osu.Game/Overlays/SocialOverlay.cs +++ /dev/null @@ -1,242 +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.Linq; -using osu.Framework.Bindables; -using osuTK; -using osuTK.Graphics; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Game.Graphics.UserInterface; -using osu.Game.Online.API; -using osu.Game.Online.API.Requests; -using osu.Game.Overlays.SearchableList; -using osu.Game.Overlays.Social; -using osu.Game.Users; -using System.Threading; -using osu.Framework.Allocation; -using osu.Framework.Extensions.Color4Extensions; -using osu.Framework.Threading; - -namespace osu.Game.Overlays -{ - public class SocialOverlay : SearchableListOverlay - { - private readonly LoadingSpinner loading; - private FillFlowContainer panels; - - protected override Color4 BackgroundColour => Color4Extensions.FromHex(@"60284b"); - protected override Color4 TrianglesColourLight => Color4Extensions.FromHex(@"672b51"); - protected override Color4 TrianglesColourDark => Color4Extensions.FromHex(@"5c2648"); - - protected override SearchableListHeader CreateHeader() => new Header(); - protected override SearchableListFilterControl CreateFilterControl() => new FilterControl(); - - private User[] users = Array.Empty(); - - public User[] Users - { - get => users; - set - { - if (users == value) - return; - - users = value ?? Array.Empty(); - - if (LoadState >= LoadState.Ready) - recreatePanels(); - } - } - - public SocialOverlay() - : base(OverlayColourScheme.Pink) - { - Add(loading = new LoadingSpinner()); - - Filter.Search.Current.ValueChanged += text => - { - if (!string.IsNullOrEmpty(text.NewValue)) - { - // force searching in players until searching for friends is supported - Header.Tabs.Current.Value = SocialTab.AllPlayers; - - if (Filter.Tabs.Current.Value != SocialSortCriteria.Rank) - Filter.Tabs.Current.Value = SocialSortCriteria.Rank; - } - }; - - Header.Tabs.Current.ValueChanged += _ => queueUpdate(); - Filter.Tabs.Current.ValueChanged += _ => onFilterUpdate(); - - Filter.DisplayStyleControl.DisplayStyle.ValueChanged += _ => recreatePanels(); - Filter.Dropdown.Current.ValueChanged += _ => recreatePanels(); - - currentQuery.BindTo(Filter.Search.Current); - currentQuery.ValueChanged += query => - { - queryChangedDebounce?.Cancel(); - - if (string.IsNullOrEmpty(query.NewValue)) - queueUpdate(); - else - queryChangedDebounce = Scheduler.AddDelayed(updateSearch, 500); - }; - } - - [BackgroundDependencyLoader] - private void load() - { - recreatePanels(); - } - - private APIRequest getUsersRequest; - - private readonly Bindable currentQuery = new Bindable(); - - private ScheduledDelegate queryChangedDebounce; - - private void queueUpdate() => Scheduler.AddOnce(updateSearch); - - private CancellationTokenSource loadCancellation; - - private void updateSearch() - { - queryChangedDebounce?.Cancel(); - - if (!IsLoaded) - return; - - Users = null; - clearPanels(); - getUsersRequest?.Cancel(); - - if (API?.IsLoggedIn != true) - return; - - switch (Header.Tabs.Current.Value) - { - case SocialTab.Friends: - var friendRequest = new GetFriendsRequest(); // TODO filter arguments? - friendRequest.Success += users => Users = users.ToArray(); - API.Queue(getUsersRequest = friendRequest); - break; - - default: - var userRequest = new GetUsersRequest(); // TODO filter arguments! - userRequest.Success += res => Users = res.Users.Select(r => r.User).ToArray(); - API.Queue(getUsersRequest = userRequest); - break; - } - } - - private void recreatePanels() - { - clearPanels(); - - if (Users == null) - { - loading.Hide(); - return; - } - - IEnumerable sortedUsers = Users; - - switch (Filter.Tabs.Current.Value) - { - case SocialSortCriteria.Location: - sortedUsers = sortedUsers.OrderBy(u => u.Country.FullName); - break; - - case SocialSortCriteria.Name: - sortedUsers = sortedUsers.OrderBy(u => u.Username); - break; - } - - if (Filter.Dropdown.Current.Value == SortDirection.Descending) - sortedUsers = sortedUsers.Reverse(); - - var newPanels = new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Spacing = new Vector2(10f), - Margin = new MarginPadding { Top = 10 }, - ChildrenEnumerable = sortedUsers.Select(u => - { - UserPanel panel; - - switch (Filter.DisplayStyleControl.DisplayStyle.Value) - { - case PanelDisplayStyle.Grid: - panel = new UserGridPanel(u) - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Width = 290, - }; - break; - - default: - panel = new UserListPanel(u); - break; - } - - panel.Status.BindTo(u.Status); - panel.Activity.BindTo(u.Activity); - return panel; - }) - }; - - LoadComponentAsync(newPanels, f => - { - if (panels != null) - ScrollFlow.Remove(panels); - - loading.Hide(); - ScrollFlow.Add(panels = newPanels); - }, (loadCancellation = new CancellationTokenSource()).Token); - } - - private void onFilterUpdate() - { - if (Filter.Tabs.Current.Value == SocialSortCriteria.Rank) - { - queueUpdate(); - return; - } - - recreatePanels(); - } - - private void clearPanels() - { - loading.Show(); - - loadCancellation?.Cancel(); - - if (panels != null) - { - panels.Expire(); - panels = null; - } - } - - public override void APIStateChanged(IAPIProvider api, APIState state) - { - switch (state) - { - case APIState.Online: - queueUpdate(); - break; - - default: - Users = null; - clearPanels(); - break; - } - } - } -} From a6562be3eb33d35ac6b47a2d19cdaee3f6cc52dd Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 18 Jul 2020 23:27:33 +0300 Subject: [PATCH 0152/1782] Remove unused classes from social namespace --- osu.Game/Overlays/Social/FilterControl.cs | 33 ----------- osu.Game/Overlays/Social/Header.cs | 67 ----------------------- 2 files changed, 100 deletions(-) delete mode 100644 osu.Game/Overlays/Social/FilterControl.cs delete mode 100644 osu.Game/Overlays/Social/Header.cs diff --git a/osu.Game/Overlays/Social/FilterControl.cs b/osu.Game/Overlays/Social/FilterControl.cs deleted file mode 100644 index 93fcc3c401..0000000000 --- a/osu.Game/Overlays/Social/FilterControl.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 osu.Framework.Extensions.Color4Extensions; -using osuTK.Graphics; -using osu.Framework.Graphics; -using osu.Game.Overlays.SearchableList; - -namespace osu.Game.Overlays.Social -{ - public class FilterControl : SearchableListFilterControl - { - protected override Color4 BackgroundColour => Color4Extensions.FromHex(@"47253a"); - protected override SocialSortCriteria DefaultTab => SocialSortCriteria.Rank; - protected override SortDirection DefaultCategory => SortDirection.Ascending; - - public FilterControl() - { - Tabs.Margin = new MarginPadding { Top = 10 }; - } - } - - public enum SocialSortCriteria - { - Rank, - Name, - Location, - //[Description("Time Zone")] - //TimeZone, - //[Description("World Map")] - //WorldMap, - } -} diff --git a/osu.Game/Overlays/Social/Header.cs b/osu.Game/Overlays/Social/Header.cs deleted file mode 100644 index 22e0fdcd56..0000000000 --- a/osu.Game/Overlays/Social/Header.cs +++ /dev/null @@ -1,67 +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.Overlays.SearchableList; -using osuTK.Graphics; -using osu.Framework.Graphics; -using osu.Game.Graphics.Sprites; -using osu.Framework.Graphics.Containers; -using osu.Game.Graphics; -using osu.Framework.Allocation; -using System.ComponentModel; -using osu.Framework.Extensions.Color4Extensions; -using osu.Framework.Graphics.Sprites; - -namespace osu.Game.Overlays.Social -{ - public class Header : SearchableListHeader - { - private OsuSpriteText browser; - - protected override Color4 BackgroundColour => Color4Extensions.FromHex(@"38202e"); - - protected override SocialTab DefaultTab => SocialTab.AllPlayers; - protected override IconUsage Icon => FontAwesome.Solid.Users; - - protected override Drawable CreateHeaderText() - { - return new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Children = new[] - { - new OsuSpriteText - { - Text = "social ", - Font = OsuFont.GetFont(size: 25), - }, - browser = new OsuSpriteText - { - Text = "browser", - Font = OsuFont.GetFont(size: 25, weight: FontWeight.Light), - }, - }, - }; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - browser.Colour = colours.Pink; - } - } - - public enum SocialTab - { - [Description("All Players")] - AllPlayers, - - [Description("Friends")] - Friends, - //[Description("Team Members")] - //TeamMembers, - //[Description("Chat Channels")] - //ChatChannels, - } -} From 56b0094d4373a48c6e135fdbf23b6e95aec07d5b Mon Sep 17 00:00:00 2001 From: Fabian Date: Sat, 18 Jul 2020 23:10:05 +0200 Subject: [PATCH 0153/1782] Update slider labels & descriptions --- osu.Game.Rulesets.Osu/Mods/OsuModDeflate.cs | 2 +- osu.Game.Rulesets.Osu/Mods/OsuModGrow.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModDeflate.cs b/osu.Game.Rulesets.Osu/Mods/OsuModDeflate.cs index 6302d47843..ee6a7815e2 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModDeflate.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModDeflate.cs @@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Osu.Mods public override string Description => "Hit them at the right size!"; - [SettingSource("Starting size modifier", "The object starting size modifier")] + [SettingSource("Starting Size", "The initial size multiplier applied to all objects.")] public override BindableNumber StartScale { get; } = new BindableFloat { MinValue = 1f, diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModGrow.cs b/osu.Game.Rulesets.Osu/Mods/OsuModGrow.cs index 5288bdd62c..182d6eeb4b 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModGrow.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModGrow.cs @@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Osu.Mods public override string Description => "Hit them at the right size!"; - [SettingSource("Starting size modifier", "The object starting size modifier")] + [SettingSource("Starting Size", "The initial size multiplier applied to all objects.")] public override BindableNumber StartScale { get; } = new BindableFloat { MinValue = 0f, From 2025e5418c841a386ba5599164c3c9d017c7814c Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 19 Jul 2020 04:10:35 +0300 Subject: [PATCH 0154/1782] Minor visual adjustments --- osu.Game/Users/UserBrickPanel.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Users/UserBrickPanel.cs b/osu.Game/Users/UserBrickPanel.cs index f6eabc3b75..9ca7768187 100644 --- a/osu.Game/Users/UserBrickPanel.cs +++ b/osu.Game/Users/UserBrickPanel.cs @@ -16,15 +16,14 @@ namespace osu.Game.Users public UserBrickPanel(User user) : base(user) { - AutoSizeAxes = Axes.X; - Height = 23; + AutoSizeAxes = Axes.Both; CornerRadius = 6; } [BackgroundDependencyLoader] private void load() { - Background.FadeTo(0.3f); + Background.FadeTo(0.2f); } protected override Drawable CreateLayout() => new FillFlowContainer @@ -34,7 +33,8 @@ namespace osu.Game.Users Spacing = new Vector2(5, 0), Margin = new MarginPadding { - Horizontal = 5 + Horizontal = 10, + Vertical = 3, }, Anchor = Anchor.Centre, Origin = Anchor.Centre, From 648e414c14ac0ade2821b91b7101f225a3b8a65f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 19 Jul 2020 11:04:33 +0900 Subject: [PATCH 0155/1782] Update InputHandlers in line with framework changes --- .../Replays/CatchFramedReplayInputHandler.cs | 15 ++++++--------- .../Replays/ManiaFramedReplayInputHandler.cs | 5 ++++- .../Replays/OsuFramedReplayInputHandler.cs | 12 ++++++------ .../Replays/TaikoFramedReplayInputHandler.cs | 5 ++++- .../Visual/Gameplay/TestSceneReplayRecorder.cs | 15 +++------------ .../Visual/Gameplay/TestSceneReplayRecording.cs | 15 +++------------ .../Rulesets/Replays/FramedReplayInputHandler.cs | 3 --- 7 files changed, 26 insertions(+), 44 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Replays/CatchFramedReplayInputHandler.cs b/osu.Game.Rulesets.Catch/Replays/CatchFramedReplayInputHandler.cs index f122588a2b..24c21fbc84 100644 --- a/osu.Game.Rulesets.Catch/Replays/CatchFramedReplayInputHandler.cs +++ b/osu.Game.Rulesets.Catch/Replays/CatchFramedReplayInputHandler.cs @@ -35,18 +35,15 @@ namespace osu.Game.Rulesets.Catch.Replays } } - public override List GetPendingInputs() + public override void GetPendingInputs(List input) { - if (!Position.HasValue) return new List(); + if (!Position.HasValue) return; - return new List + input.Add(new CatchReplayState { - new CatchReplayState - { - PressedActions = CurrentFrame?.Actions ?? new List(), - CatcherX = Position.Value - }, - }; + PressedActions = CurrentFrame?.Actions ?? new List(), + CatcherX = Position.Value + }); } public class CatchReplayState : ReplayState diff --git a/osu.Game.Rulesets.Mania/Replays/ManiaFramedReplayInputHandler.cs b/osu.Game.Rulesets.Mania/Replays/ManiaFramedReplayInputHandler.cs index 899718b77e..26c4ccf289 100644 --- a/osu.Game.Rulesets.Mania/Replays/ManiaFramedReplayInputHandler.cs +++ b/osu.Game.Rulesets.Mania/Replays/ManiaFramedReplayInputHandler.cs @@ -18,6 +18,9 @@ namespace osu.Game.Rulesets.Mania.Replays protected override bool IsImportant(ManiaReplayFrame frame) => frame.Actions.Any(); - public override List GetPendingInputs() => new List { new ReplayState { PressedActions = CurrentFrame?.Actions ?? new List() } }; + public override void GetPendingInputs(List input) + { + input.Add(new ReplayState { PressedActions = CurrentFrame?.Actions ?? new List() }); + } } } diff --git a/osu.Game.Rulesets.Osu/Replays/OsuFramedReplayInputHandler.cs b/osu.Game.Rulesets.Osu/Replays/OsuFramedReplayInputHandler.cs index b42e9ac187..5c803539c2 100644 --- a/osu.Game.Rulesets.Osu/Replays/OsuFramedReplayInputHandler.cs +++ b/osu.Game.Rulesets.Osu/Replays/OsuFramedReplayInputHandler.cs @@ -36,19 +36,19 @@ namespace osu.Game.Rulesets.Osu.Replays } } - public override List GetPendingInputs() + public override void GetPendingInputs(List input) { - return new List - { + input.Add( new MousePositionAbsoluteInput { Position = GamefieldToScreenSpace(Position ?? Vector2.Zero) - }, + }); + input.Add( new ReplayState { PressedActions = CurrentFrame?.Actions ?? new List() - } - }; + }); + ; } } } diff --git a/osu.Game.Rulesets.Taiko/Replays/TaikoFramedReplayInputHandler.cs b/osu.Game.Rulesets.Taiko/Replays/TaikoFramedReplayInputHandler.cs index 97337acc45..7361d4efa8 100644 --- a/osu.Game.Rulesets.Taiko/Replays/TaikoFramedReplayInputHandler.cs +++ b/osu.Game.Rulesets.Taiko/Replays/TaikoFramedReplayInputHandler.cs @@ -18,6 +18,9 @@ namespace osu.Game.Rulesets.Taiko.Replays protected override bool IsImportant(TaikoReplayFrame frame) => frame.Actions.Any(); - public override List GetPendingInputs() => new List { new ReplayState { PressedActions = CurrentFrame?.Actions ?? new List() } }; + public override void GetPendingInputs(List input) + { + input.Add(new ReplayState { PressedActions = CurrentFrame?.Actions ?? new List() }); + } } } diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs index c7455583e4..e473f49826 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs @@ -173,19 +173,10 @@ namespace osu.Game.Tests.Visual.Gameplay { } - public override List GetPendingInputs() + public override void GetPendingInputs(List inputs) { - return new List - { - new MousePositionAbsoluteInput - { - Position = GamefieldToScreenSpace(CurrentFrame?.Position ?? Vector2.Zero) - }, - new ReplayState - { - PressedActions = CurrentFrame?.Actions ?? new List() - } - }; + inputs.Add(new MousePositionAbsoluteInput { Position = GamefieldToScreenSpace(CurrentFrame?.Position ?? Vector2.Zero) }); + inputs.Add(new ReplayState { PressedActions = CurrentFrame?.Actions ?? new List() }); } } diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecording.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecording.cs index 7822f07957..e891ed617a 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecording.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecording.cs @@ -113,19 +113,10 @@ namespace osu.Game.Tests.Visual.Gameplay { } - public override List GetPendingInputs() + public override void GetPendingInputs(List inputs) { - return new List - { - new MousePositionAbsoluteInput - { - Position = GamefieldToScreenSpace(CurrentFrame?.Position ?? Vector2.Zero) - }, - new ReplayState - { - PressedActions = CurrentFrame?.Actions ?? new List() - } - }; + inputs.Add(new MousePositionAbsoluteInput { Position = GamefieldToScreenSpace(CurrentFrame?.Position ?? Vector2.Zero) }); + inputs.Add(new ReplayState { PressedActions = CurrentFrame?.Actions ?? new List() }); } } diff --git a/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs b/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs index 55d82c4083..cf5c88b8fd 100644 --- a/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs +++ b/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using JetBrains.Annotations; -using osu.Framework.Input.StateChanges; using osu.Game.Input.Handlers; using osu.Game.Replays; @@ -69,8 +68,6 @@ namespace osu.Game.Rulesets.Replays return true; } - public override List GetPendingInputs() => new List(); - private const double sixty_frame_time = 1000.0 / 60; protected virtual double AllowedImportantTimeSpan => sixty_frame_time * 1.2; From 72ace508b605dace2f6f3e684aa83fac3bae3c4c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 19 Jul 2020 11:37:10 +0900 Subject: [PATCH 0156/1782] Reduce memory allocations in MenuCursorContainer --- osu.Game/Graphics/Cursor/MenuCursorContainer.cs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/Cursor/MenuCursorContainer.cs b/osu.Game/Graphics/Cursor/MenuCursorContainer.cs index b7ea1ba56a..02bfb3fad6 100644 --- a/osu.Game/Graphics/Cursor/MenuCursorContainer.cs +++ b/osu.Game/Graphics/Cursor/MenuCursorContainer.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.Linq; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; @@ -55,7 +54,15 @@ namespace osu.Game.Graphics.Cursor return; } - var newTarget = inputManager.HoveredDrawables.OfType().FirstOrDefault(t => t.ProvidingUserCursor) ?? this; + IProvideCursor newTarget = this; + + foreach (var d in inputManager.HoveredDrawables) + { + if (!(d is IProvideCursor p) || !p.ProvidingUserCursor) continue; + + newTarget = p; + break; + } if (currentTarget == newTarget) return; From 55d921ef85629cd0b04687497793336abfda88aa Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 20 Jul 2020 15:19:17 +0900 Subject: [PATCH 0157/1782] Improve feel of animation --- .../TestSceneSpinner.cs | 2 +- .../Objects/Drawables/DrawableSpinner.cs | 32 ++++++++++++------- .../Drawables/Pieces/SpinnerBackground.cs | 10 +++--- .../Objects/Drawables/Pieces/SpinnerTicks.cs | 8 ++--- 4 files changed, 31 insertions(+), 21 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinner.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinner.cs index 8cb7f3f4b6..67afc45e32 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinner.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinner.cs @@ -36,7 +36,7 @@ namespace osu.Game.Rulesets.Osu.Tests private void testSingle(float circleSize, bool auto = false) { - var spinner = new Spinner { StartTime = Time.Current + 1000, EndTime = Time.Current + 4000 }; + var spinner = new Spinner { StartTime = Time.Current + 2000, EndTime = Time.Current + 5000 }; spinner.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty { CircleSize = circleSize }); diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index be6766509c..3dd98be6fb 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -93,7 +93,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { Background = new SpinnerBackground { - Alpha = 0.6f, + Alpha = 1f, Anchor = Anchor.Centre, Origin = Anchor.Centre, }, @@ -128,7 +128,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables Background.AccentColour = normalColour; - completeColour = colours.YellowLight.Opacity(0.75f); + completeColour = colours.YellowLight; Disc.AccentColour = fillColour; circle.Colour = colours.BlueDark; @@ -152,8 +152,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables Disc.FadeAccent(completeColour, duration); - Background.FadeAccent(completeColour, duration); - Background.FadeOut(duration); + Background.FadeAccent(completeColour.Darken(1), duration); circle.FadeColour(completeColour, duration); glow.FadeColour(completeColour, duration); @@ -204,14 +203,25 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { base.UpdateInitialTransforms(); - circleContainer.ScaleTo(Spinner.Scale * 0.3f); - circleContainer.ScaleTo(Spinner.Scale, HitObject.TimePreempt / 1.4f, Easing.OutQuint); + circleContainer.ScaleTo(0); + mainContainer.ScaleTo(0); - mainContainer - .ScaleTo(0) - .ScaleTo(Spinner.Scale * circle.DrawHeight / DrawHeight * 1.4f, HitObject.TimePreempt - 150, Easing.OutQuint) - .Then() - .ScaleTo(1, 500, Easing.OutQuint); + using (BeginDelayedSequence(HitObject.TimePreempt / 2, true)) + { + float phaseOneScale = Spinner.Scale * 0.8f; + + circleContainer.ScaleTo(phaseOneScale, HitObject.TimePreempt / 2f, Easing.OutQuint); + + mainContainer + .ScaleTo(phaseOneScale * circle.DrawHeight / DrawHeight * 1.4f, HitObject.TimePreempt / 2, Easing.OutElasticHalf) + .RotateTo(25, HitObject.TimePreempt + Spinner.Duration); + + using (BeginDelayedSequence(HitObject.TimePreempt / 2, true)) + { + circleContainer.ScaleTo(Spinner.Scale * 1.4f, 400, Easing.OutQuint); + mainContainer.ScaleTo(Spinner.Scale, 400, Easing.OutQuint); + } + } } protected override void UpdateStateTransforms(ArmedState state) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerBackground.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerBackground.cs index 77228e28af..bfb9a9e763 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerBackground.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerBackground.cs @@ -1,25 +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 osuTK.Graphics; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; +using osuTK.Graphics; namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces { public class SpinnerBackground : CircularContainer, IHasAccentColour { - protected Box Disc; + private readonly Box disc; public Color4 AccentColour { - get => Disc.Colour; + get => disc.Colour; set { - Disc.Colour = value; + disc.Colour = value; EdgeEffect = new EdgeEffectParameters { @@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces Children = new Drawable[] { - Disc = new Box + disc = new Box { Origin = Anchor.Centre, Anchor = Anchor.Centre, diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerTicks.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerTicks.cs index 676cefb236..0e7dafdbea 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerTicks.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerTicks.cs @@ -20,24 +20,24 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces Anchor = Anchor.Centre; RelativeSizeAxes = Axes.Both; - const float count = 18; + const float count = 8; for (float i = 0; i < count; i++) { Add(new Container { Colour = Color4.Black, - Alpha = 0.4f, + Alpha = 0.2f, EdgeEffect = new EdgeEffectParameters { Type = EdgeEffectType.Glow, - Radius = 10, + Radius = 20, Colour = Color4.Gray.Opacity(0.2f), }, RelativePositionAxes = Axes.Both, Masking = true, CornerRadius = 5, - Size = new Vector2(60, 10), + Size = new Vector2(65, 10), Origin = Anchor.Centre, Position = new Vector2( 0.5f + MathF.Sin(i / count * 2 * MathF.PI) / 2 * 0.86f, From 33e58bb7db992e24562ecf38774b09297b6b55cf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 20 Jul 2020 17:22:17 +0900 Subject: [PATCH 0158/1782] Fix sizing and colour not correct on hit --- .../Objects/Drawables/DrawableSpinner.cs | 55 +++++++++++-------- 1 file changed, 31 insertions(+), 24 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index 3dd98be6fb..b8a290a978 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -147,15 +147,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables if (Progress >= 1 && !Disc.Complete) { Disc.Complete = true; - - const float duration = 200; - - Disc.FadeAccent(completeColour, duration); - - Background.FadeAccent(completeColour.Darken(1), duration); - - circle.FadeColour(completeColour, duration); - glow.FadeColour(completeColour, duration); + transformFillColour(completeColour, 200); } if (userTriggered || Time.Current < Spinner.EndTime) @@ -208,18 +200,18 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables using (BeginDelayedSequence(HitObject.TimePreempt / 2, true)) { - float phaseOneScale = Spinner.Scale * 0.8f; + float phaseOneScale = Spinner.Scale * 0.7f; - circleContainer.ScaleTo(phaseOneScale, HitObject.TimePreempt / 2f, Easing.OutQuint); + circleContainer.ScaleTo(phaseOneScale, HitObject.TimePreempt / 4, Easing.OutQuint); mainContainer - .ScaleTo(phaseOneScale * circle.DrawHeight / DrawHeight * 1.4f, HitObject.TimePreempt / 2, Easing.OutElasticHalf) - .RotateTo(25, HitObject.TimePreempt + Spinner.Duration); + .ScaleTo(phaseOneScale * circle.DrawHeight / DrawHeight * 1.5f, HitObject.TimePreempt / 4, Easing.OutQuint) + .RotateTo((float)(25 * Spinner.Duration / 2000), HitObject.TimePreempt + Spinner.Duration); using (BeginDelayedSequence(HitObject.TimePreempt / 2, true)) { - circleContainer.ScaleTo(Spinner.Scale * 1.4f, 400, Easing.OutQuint); - mainContainer.ScaleTo(Spinner.Scale, 400, Easing.OutQuint); + circleContainer.ScaleTo(Spinner.Scale, 400, Easing.OutQuint); + mainContainer.ScaleTo(1, 400, Easing.OutQuint); } } } @@ -228,18 +220,33 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { base.UpdateStateTransforms(state); - var sequence = this.Delay(Spinner.Duration).FadeOut(160); - - switch (state) + using (BeginDelayedSequence(Spinner.Duration, true)) { - case ArmedState.Hit: - sequence.ScaleTo(Scale * 1.2f, 320, Easing.Out); - break; + this.FadeOut(160); - case ArmedState.Miss: - sequence.ScaleTo(Scale * 0.8f, 320, Easing.In); - break; + switch (state) + { + case ArmedState.Hit: + transformFillColour(completeColour, 0); + this.ScaleTo(Scale * 1.2f, 320, Easing.Out); + mainContainer.RotateTo(mainContainer.Rotation + 180, 320); + break; + + case ArmedState.Miss: + this.ScaleTo(Scale * 0.8f, 320, Easing.In); + break; + } } } + + private void transformFillColour(Colour4 colour, double duration) + { + Disc.FadeAccent(colour, duration); + + Background.FadeAccent(colour.Darken(1), duration); + + circle.FadeColour(colour, duration); + glow.FadeColour(colour, duration); + } } } From 4cbc176cb66c3c9616ccc613252c7eee20d9a97f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 20 Jul 2020 17:48:35 +0900 Subject: [PATCH 0159/1782] Add less fill and more transparency --- .../Objects/Drawables/DrawableSpinner.cs | 10 ++++-- .../Drawables/Pieces/SpinnerBackground.cs | 8 ++--- .../Objects/Drawables/Pieces/SpinnerTicks.cs | 34 ++++++++++++++----- 3 files changed, 36 insertions(+), 16 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index b8a290a978..fafb3ab69d 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -93,7 +93,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { Background = new SpinnerBackground { - Alpha = 1f, + Disc = + { + Alpha = 0f, + }, Anchor = Anchor.Centre, Origin = Anchor.Centre, }, @@ -125,10 +128,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables private void load(OsuColour colours) { normalColour = baseColour; + completeColour = colours.YellowLight; Background.AccentColour = normalColour; - - completeColour = colours.YellowLight; + Ticks.AccentColour = normalColour; Disc.AccentColour = fillColour; circle.Colour = colours.BlueDark; @@ -244,6 +247,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables Disc.FadeAccent(colour, duration); Background.FadeAccent(colour.Darken(1), duration); + Ticks.FadeAccent(colour, duration); circle.FadeColour(colour, duration); glow.FadeColour(colour, duration); diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerBackground.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerBackground.cs index bfb9a9e763..944354abca 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerBackground.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerBackground.cs @@ -12,14 +12,14 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces { public class SpinnerBackground : CircularContainer, IHasAccentColour { - private readonly Box disc; + public readonly Box Disc; public Color4 AccentColour { - get => disc.Colour; + get => Disc.Colour; set { - disc.Colour = value; + Disc.Colour = value; EdgeEffect = new EdgeEffectParameters { @@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces Children = new Drawable[] { - disc = new Box + Disc = new Box { Origin = Anchor.Centre, Anchor = Anchor.Centre, diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerTicks.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerTicks.cs index 0e7dafdbea..95bccbf2fc 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerTicks.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerTicks.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Linq; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -9,10 +10,11 @@ using osu.Framework.Graphics.Effects; using osuTK; using osuTK.Graphics; using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces { - public class SpinnerTicks : Container + public class SpinnerTicks : Container, IHasAccentColour { public SpinnerTicks() { @@ -26,14 +28,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces { Add(new Container { - Colour = Color4.Black, - Alpha = 0.2f, - EdgeEffect = new EdgeEffectParameters - { - Type = EdgeEffectType.Glow, - Radius = 20, - Colour = Color4.Gray.Opacity(0.2f), - }, + Alpha = 0.4f, + Blending = BlendingParameters.Additive, RelativePositionAxes = Axes.Both, Masking = true, CornerRadius = 5, @@ -54,5 +50,25 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces }); } } + + public Color4 AccentColour + { + get => Colour; + set + { + Colour = value; + + foreach (var c in Children.OfType()) + { + c.EdgeEffect = + new EdgeEffectParameters + { + Type = EdgeEffectType.Glow, + Radius = 20, + Colour = value.Opacity(0.8f), + }; + } + } + } } } From e06d3c5812e87037b94a756b1bba98cdcaf09d87 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 20 Jul 2020 17:52:59 +0900 Subject: [PATCH 0160/1782] Minor adjustments to tick clearance --- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs | 2 +- .../Objects/Drawables/Pieces/SpinnerTicks.cs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index fafb3ab69d..9c4608cbb1 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -208,7 +208,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables circleContainer.ScaleTo(phaseOneScale, HitObject.TimePreempt / 4, Easing.OutQuint); mainContainer - .ScaleTo(phaseOneScale * circle.DrawHeight / DrawHeight * 1.5f, HitObject.TimePreempt / 4, Easing.OutQuint) + .ScaleTo(phaseOneScale * circle.DrawHeight / DrawHeight * 1.6f, HitObject.TimePreempt / 4, Easing.OutQuint) .RotateTo((float)(25 * Spinner.Duration / 2000), HitObject.TimePreempt + Spinner.Duration); using (BeginDelayedSequence(HitObject.TimePreempt / 2, true)) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerTicks.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerTicks.cs index 95bccbf2fc..ba7e8eae6f 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerTicks.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerTicks.cs @@ -33,11 +33,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces RelativePositionAxes = Axes.Both, Masking = true, CornerRadius = 5, - Size = new Vector2(65, 10), + Size = new Vector2(60, 10), Origin = Anchor.Centre, Position = new Vector2( - 0.5f + MathF.Sin(i / count * 2 * MathF.PI) / 2 * 0.86f, - 0.5f + MathF.Cos(i / count * 2 * MathF.PI) / 2 * 0.86f + 0.5f + MathF.Sin(i / count * 2 * MathF.PI) / 2 * 0.83f, + 0.5f + MathF.Cos(i / count * 2 * MathF.PI) / 2 * 0.83f ), Rotation = -i / count * 360 + 90, Children = new[] From f044c06d089319841abc78a59c68096fd0a5a330 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 20 Jul 2020 22:26:58 +0900 Subject: [PATCH 0161/1782] Fix hold notes accepting presses during release lenience --- .../TestSceneHoldNoteInput.cs | 65 +++++++++++++++++-- .../Objects/Drawables/DrawableHoldNote.cs | 6 ++ 2 files changed, 66 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs index 0d13b85901..95072cf4f8 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.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 NUnit.Framework; using osu.Framework.Screens; using osu.Game.Beatmaps; @@ -10,6 +11,8 @@ using osu.Game.Replays; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Replays; +using osu.Game.Rulesets.Mania.Scoring; +using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; @@ -236,6 +239,53 @@ namespace osu.Game.Rulesets.Mania.Tests assertTailJudgement(HitResult.Meh); } + [Test] + public void TestMissReleaseAndHitSecondRelease() + { + var windows = new ManiaHitWindows(); + windows.SetDifficulty(10); + + var beatmap = new Beatmap + { + HitObjects = + { + new HoldNote + { + StartTime = 1000, + Duration = 500, + Column = 0, + }, + new HoldNote + { + StartTime = 1000 + 500 + windows.WindowFor(HitResult.Miss) + 10, + Duration = 500, + Column = 0, + }, + }, + BeatmapInfo = + { + BaseDifficulty = new BeatmapDifficulty + { + SliderTickRate = 4, + OverallDifficulty = 10, + }, + Ruleset = new ManiaRuleset().RulesetInfo + }, + }; + + performTest(new List + { + new ManiaReplayFrame(beatmap.HitObjects[1].StartTime, ManiaAction.Key1), + new ManiaReplayFrame(beatmap.HitObjects[1].GetEndTime()), + }, beatmap); + + AddAssert("first hold note missed", () => judgementResults.Where(j => beatmap.HitObjects[0].NestedHitObjects.Contains(j.HitObject)) + .All(j => j.Type == HitResult.Miss)); + + AddAssert("second hold note missed", () => judgementResults.Where(j => beatmap.HitObjects[1].NestedHitObjects.Contains(j.HitObject)) + .All(j => j.Type == HitResult.Perfect)); + } + private void assertHeadJudgement(HitResult result) => AddAssert($"head judged as {result}", () => judgementResults[0].Type == result); @@ -250,11 +300,11 @@ namespace osu.Game.Rulesets.Mania.Tests private ScoreAccessibleReplayPlayer currentPlayer; - private void performTest(List frames) + private void performTest(List frames, Beatmap beatmap = null) { - AddStep("load player", () => + if (beatmap == null) { - Beatmap.Value = CreateWorkingBeatmap(new Beatmap + beatmap = new Beatmap { HitObjects = { @@ -270,9 +320,14 @@ namespace osu.Game.Rulesets.Mania.Tests BaseDifficulty = new BeatmapDifficulty { SliderTickRate = 4 }, Ruleset = new ManiaRuleset().RulesetInfo }, - }); + }; - Beatmap.Value.Beatmap.ControlPointInfo.Add(0, new DifficultyControlPoint { SpeedMultiplier = 0.1f }); + beatmap.ControlPointInfo.Add(0, new DifficultyControlPoint { SpeedMultiplier = 0.1f }); + } + + AddStep("load player", () => + { + Beatmap.Value = CreateWorkingBeatmap(beatmap); var p = new ScoreAccessibleReplayPlayer(new Score { Replay = new Replay { Frames = frames } }); diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs index 2262bd2b7d..0c5289efe1 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs @@ -167,6 +167,12 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables if (action != Action.Value) return false; + // The tail has a lenience applied to it which is factored into the miss window (i.e. the miss judgement will be delayed). + // But the hold cannot ever be started within the late-lenience window, so we should skip trying to begin the hold during that time. + // Note: Unlike below, we use the tail's start time to determine the time offset. + if (Time.Current > Tail.HitObject.StartTime && !Tail.HitObject.HitWindows.CanBeHit(Time.Current - Tail.HitObject.StartTime)) + return false; + beginHoldAt(Time.Current - Head.HitObject.StartTime); Head.UpdateResult(); From 72722d4c721c304a8119e56a496a0d05173cfa20 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 20 Jul 2020 17:18:26 +0000 Subject: [PATCH 0162/1782] Bump Microsoft.Build.Traversal from 2.0.50 to 2.0.52 Bumps [Microsoft.Build.Traversal](https://github.com/Microsoft/MSBuildSdks) from 2.0.50 to 2.0.52. - [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.50...Microsoft.Build.Traversal.2.0.52) 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 9aa5b6192b..233a040d18 100644 --- a/global.json +++ b/global.json @@ -5,6 +5,6 @@ "version": "3.1.100" }, "msbuild-sdks": { - "Microsoft.Build.Traversal": "2.0.50" + "Microsoft.Build.Traversal": "2.0.52" } } \ No newline at end of file From f71ed47e6693f59c0bba83433d83d0f43c876833 Mon Sep 17 00:00:00 2001 From: Joehu Date: Mon, 20 Jul 2020 11:52:02 -0700 Subject: [PATCH 0163/1782] Fix focused textbox absorbing input when unfocused --- osu.Game/Graphics/UserInterface/FocusedTextBox.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Graphics/UserInterface/FocusedTextBox.cs b/osu.Game/Graphics/UserInterface/FocusedTextBox.cs index 8977f014b6..f77a3109c9 100644 --- a/osu.Game/Graphics/UserInterface/FocusedTextBox.cs +++ b/osu.Game/Graphics/UserInterface/FocusedTextBox.cs @@ -67,6 +67,8 @@ namespace osu.Game.Graphics.UserInterface public bool OnPressed(GlobalAction action) { + if (!HasFocus) return false; + if (action == GlobalAction.Back) { if (Text.Length > 0) From f48984920d5b489adba4afd4a8c3c9fedaceebe1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 21 Jul 2020 11:21:32 +0900 Subject: [PATCH 0164/1782] Change bonus volume logic to work --- .../Objects/Drawables/DrawableSpinnerTick.cs | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinnerTick.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinnerTick.cs index 6512a9526e..436994e480 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinnerTick.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinnerTick.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.Audio; -using osu.Framework.Bindables; using osu.Game.Rulesets.Osu.Judgements; using osu.Game.Rulesets.Scoring; @@ -10,8 +8,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { public class DrawableSpinnerTick : DrawableOsuHitObject { - private readonly BindableDouble bonusSampleVolume = new BindableDouble(); - private bool hasBonusPoints; /// @@ -25,8 +21,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { hasBonusPoints = value; - bonusSampleVolume.Value = value ? 1 : 0; - ((OsuSpinnerTickJudgement)Result.Judgement).HasBonusPoints = value; + Samples.Volume.Value = ((OsuSpinnerTickJudgement)Result.Judgement).HasBonusPoints ? 1 : 0; } } @@ -37,13 +32,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { } - protected override void LoadComplete() - { - base.LoadComplete(); - - Samples.AddAdjustment(AdjustableProperty.Volume, bonusSampleVolume); - } - /// /// Apply a judgement result. /// From 4dd40542d519ad2f5db5a529ec864b990a0ec697 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 21 Jul 2020 11:21:58 +0900 Subject: [PATCH 0165/1782] Rename rotation set method to match others --- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs | 2 +- .../Objects/Drawables/Pieces/SpinnerBonusComponent.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index b82b44f35b..2707453ab9 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -227,7 +227,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables Ticks.Rotation = Disc.Rotation; SpmCounter.SetRotation(Disc.CumulativeRotation); - bonusComponent.UpdateRotation(Disc.CumulativeRotation); + bonusComponent.SetRotation(Disc.CumulativeRotation); float relativeCircleScale = Spinner.Scale * circle.DrawHeight / mainContainer.DrawHeight; float targetScale = relativeCircleScale + (1 - relativeCircleScale) * Progress; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerBonusComponent.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerBonusComponent.cs index 5c96751b3a..c49c10b45c 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerBonusComponent.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerBonusComponent.cs @@ -40,7 +40,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces private int currentSpins; - public void UpdateRotation(double rotation) + public void SetRotation(double rotation) { if (ticks.Count == 0) return; From a8991bb8bfa69a62d9e9a753a7d8d8ba435d48bd Mon Sep 17 00:00:00 2001 From: Joehu Date: Mon, 20 Jul 2020 20:13:09 -0700 Subject: [PATCH 0166/1782] Fix keybind clear button always clearing first keybind regardless of target --- osu.Game/Overlays/KeyBinding/KeyBindingRow.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs b/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs index eafb4572ca..d58acc1ac4 100644 --- a/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs +++ b/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs @@ -187,7 +187,7 @@ namespace osu.Game.Overlays.KeyBinding if (bindTarget.IsHovered) finalise(); - else + else if (buttons.Any(b => b.IsHovered)) updateBindTarget(); } From 35ad409da6fd60f48f66b58003058ac9d2c5b360 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 21 Jul 2020 06:59:24 +0300 Subject: [PATCH 0167/1782] Fix spinner bonus ticks samples not actually playing --- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinnerTick.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinnerTick.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinnerTick.cs index 436994e480..d49766adda 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinnerTick.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinnerTick.cs @@ -21,7 +21,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { hasBonusPoints = value; - Samples.Volume.Value = ((OsuSpinnerTickJudgement)Result.Judgement).HasBonusPoints ? 1 : 0; + ((OsuSpinnerTickJudgement)Result.Judgement).HasBonusPoints = value; + Samples.Volume.Value = value ? 1 : 0; } } From c1442568b95548316e748e470c00c70a45bd5f9a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 21 Jul 2020 17:04:29 +0900 Subject: [PATCH 0168/1782] Make perfect mod ignore all non-combo-affecting hitobjects --- .../Mods/TestSceneCatchModPerfect.cs | 2 +- osu.Game.Rulesets.Catch/Mods/CatchModPerfect.cs | 6 ------ .../Skinning/TestSceneDrawableTaikoMascot.cs | 2 +- osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoSuddenDeath.cs | 4 ++-- .../Judgements/TaikoDrumRollJudgement.cs | 2 -- osu.Game.Rulesets.Taiko/Judgements/TaikoSwellJudgement.cs | 2 -- osu.Game/Rulesets/Mods/ModPerfect.cs | 1 + 7 files changed, 5 insertions(+), 14 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModPerfect.cs b/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModPerfect.cs index 3e06e78dba..c1b7214d72 100644 --- a/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModPerfect.cs +++ b/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModPerfect.cs @@ -50,7 +50,7 @@ namespace osu.Game.Rulesets.Catch.Tests.Mods public void TestDroplet(bool shouldMiss) => CreateHitObjectTest(new HitObjectTestData(new Droplet { StartTime = 1000 }), shouldMiss); // We only care about testing misses, hits are tested via JuiceStream - [TestCase(true)] + [TestCase(false)] public void TestTinyDroplet(bool shouldMiss) => CreateHitObjectTest(new HitObjectTestData(new TinyDroplet { StartTime = 1000 }), shouldMiss); } } diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModPerfect.cs b/osu.Game.Rulesets.Catch/Mods/CatchModPerfect.cs index e3391c47f1..fb92399102 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModPerfect.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModPerfect.cs @@ -1,17 +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.Catch.Judgements; -using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Catch.Mods { public class CatchModPerfect : ModPerfect { - protected override bool FailCondition(HealthProcessor healthProcessor, JudgementResult result) - => !(result.Judgement is CatchBananaJudgement) - && base.FailCondition(healthProcessor, result); } } diff --git a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableTaikoMascot.cs b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableTaikoMascot.cs index d200c44a02..cb6a0decde 100644 --- a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableTaikoMascot.cs +++ b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableTaikoMascot.cs @@ -111,7 +111,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning assertStateAfterResult(new JudgementResult(new Hit(), new TaikoJudgement()) { Type = HitResult.Great }, TaikoMascotAnimationState.Idle); assertStateAfterResult(new JudgementResult(new Hit(), new TaikoJudgement()) { Type = HitResult.Miss }, TaikoMascotAnimationState.Fail); - assertStateAfterResult(new JudgementResult(new DrumRoll(), new TaikoDrumRollJudgement()) { Type = HitResult.Great }, TaikoMascotAnimationState.Fail); + assertStateAfterResult(new JudgementResult(new DrumRoll(), new TaikoDrumRollJudgement()) { Type = HitResult.Great }, TaikoMascotAnimationState.Idle); assertStateAfterResult(new JudgementResult(new Hit(), new TaikoJudgement()) { Type = HitResult.Good }, TaikoMascotAnimationState.Idle); } diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoSuddenDeath.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoSuddenDeath.cs index aaa634648a..0be005e1c4 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoSuddenDeath.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoSuddenDeath.cs @@ -36,7 +36,7 @@ namespace osu.Game.Rulesets.Taiko.Tests }; [Test] - public void TestSpinnerDoesNotFail() + public void TestSpinnerDoesFail() { bool judged = false; AddStep("Setup judgements", () => @@ -45,7 +45,7 @@ namespace osu.Game.Rulesets.Taiko.Tests Player.ScoreProcessor.NewJudgement += b => judged = true; }); AddUntilStep("swell judged", () => judged); - AddAssert("not failed", () => !Player.HasFailed); + AddAssert("failed", () => Player.HasFailed); } } } diff --git a/osu.Game.Rulesets.Taiko/Judgements/TaikoDrumRollJudgement.cs b/osu.Game.Rulesets.Taiko/Judgements/TaikoDrumRollJudgement.cs index 604daa929f..0d91002f4b 100644 --- a/osu.Game.Rulesets.Taiko/Judgements/TaikoDrumRollJudgement.cs +++ b/osu.Game.Rulesets.Taiko/Judgements/TaikoDrumRollJudgement.cs @@ -7,8 +7,6 @@ namespace osu.Game.Rulesets.Taiko.Judgements { public class TaikoDrumRollJudgement : TaikoJudgement { - public override bool AffectsCombo => false; - protected override double HealthIncreaseFor(HitResult result) { // Drum rolls can be ignored with no health penalty diff --git a/osu.Game.Rulesets.Taiko/Judgements/TaikoSwellJudgement.cs b/osu.Game.Rulesets.Taiko/Judgements/TaikoSwellJudgement.cs index 29be5e0eac..4d61efd3ee 100644 --- a/osu.Game.Rulesets.Taiko/Judgements/TaikoSwellJudgement.cs +++ b/osu.Game.Rulesets.Taiko/Judgements/TaikoSwellJudgement.cs @@ -7,8 +7,6 @@ namespace osu.Game.Rulesets.Taiko.Judgements { public class TaikoSwellJudgement : TaikoJudgement { - public override bool AffectsCombo => false; - protected override double HealthIncreaseFor(HitResult result) { switch (result) diff --git a/osu.Game/Rulesets/Mods/ModPerfect.cs b/osu.Game/Rulesets/Mods/ModPerfect.cs index 7fe606d584..65f1a972ed 100644 --- a/osu.Game/Rulesets/Mods/ModPerfect.cs +++ b/osu.Game/Rulesets/Mods/ModPerfect.cs @@ -17,6 +17,7 @@ namespace osu.Game.Rulesets.Mods protected override bool FailCondition(HealthProcessor healthProcessor, JudgementResult result) => !(result.Judgement is IgnoreJudgement) + && result.Judgement.AffectsCombo && result.Type != result.Judgement.MaxResult; } } From 05102bc1baf00b4508bf57dfe0e749569944b8ec Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 21 Jul 2020 18:22:37 +0900 Subject: [PATCH 0169/1782] Split ticks up into bonus and non-bonus --- .../Judgements/OsuSpinnerTickJudgement.cs | 18 -------------- .../Objects/Drawables/DrawableSpinner.cs | 3 +++ .../Drawables/DrawableSpinnerBonusTick.cs | 13 ++++++++++ .../Objects/Drawables/DrawableSpinnerTick.cs | 19 --------------- .../Drawables/Pieces/SpinnerBonusComponent.cs | 5 +--- osu.Game.Rulesets.Osu/Objects/Spinner.cs | 19 ++++++++++----- .../Objects/SpinnerBonusTick.cs | 24 +++++++++++++++++++ osu.Game.Rulesets.Osu/Objects/SpinnerTick.cs | 15 +++++++----- 8 files changed, 63 insertions(+), 53 deletions(-) delete mode 100644 osu.Game.Rulesets.Osu/Judgements/OsuSpinnerTickJudgement.cs create mode 100644 osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinnerBonusTick.cs create mode 100644 osu.Game.Rulesets.Osu/Objects/SpinnerBonusTick.cs diff --git a/osu.Game.Rulesets.Osu/Judgements/OsuSpinnerTickJudgement.cs b/osu.Game.Rulesets.Osu/Judgements/OsuSpinnerTickJudgement.cs deleted file mode 100644 index f9cac7a2c1..0000000000 --- a/osu.Game.Rulesets.Osu/Judgements/OsuSpinnerTickJudgement.cs +++ /dev/null @@ -1,18 +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.Scoring; - -namespace osu.Game.Rulesets.Osu.Judgements -{ - public class OsuSpinnerTickJudgement : OsuJudgement - { - internal bool HasBonusPoints; - - public override bool AffectsCombo => false; - - protected override int NumericResultFor(HitResult result) => 100 + (HasBonusPoints ? 1000 : 0); - - protected override double HealthIncreaseFor(HitResult result) => 0; - } -} diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index 2707453ab9..531d16d1d1 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -157,6 +157,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { switch (hitObject) { + case SpinnerBonusTick bonusTick: + return new DrawableSpinnerBonusTick(bonusTick); + case SpinnerTick tick: return new DrawableSpinnerTick(tick); } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinnerBonusTick.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinnerBonusTick.cs new file mode 100644 index 0000000000..2e1c07c4c6 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinnerBonusTick.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.Rulesets.Osu.Objects.Drawables +{ + public class DrawableSpinnerBonusTick : DrawableSpinnerTick + { + public DrawableSpinnerBonusTick(SpinnerBonusTick spinnerTick) + : base(spinnerTick) + { + } + } +} diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinnerTick.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinnerTick.cs index d49766adda..5fb7653f5a 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinnerTick.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinnerTick.cs @@ -1,31 +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.Osu.Judgements; using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Osu.Objects.Drawables { public class DrawableSpinnerTick : DrawableOsuHitObject { - private bool hasBonusPoints; - - /// - /// Whether this judgement has a bonus of 1,000 points additional to the numeric result. - /// Set when a spin occured after the spinner has completed. - /// - public bool HasBonusPoints - { - get => hasBonusPoints; - internal set - { - hasBonusPoints = value; - - ((OsuSpinnerTickJudgement)Result.Judgement).HasBonusPoints = value; - Samples.Volume.Value = value ? 1 : 0; - } - } - public override bool DisplayResult => false; public DrawableSpinnerTick(SpinnerTick spinnerTick) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerBonusComponent.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerBonusComponent.cs index c49c10b45c..9a65247453 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerBonusComponent.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerBonusComponent.cs @@ -55,12 +55,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces var tick = ticks[currentSpins]; if (direction >= 0) - { - tick.HasBonusPoints = currentSpins > spinsRequired; tick.TriggerResult(true); - } - if (tick.HasBonusPoints) + if (tick is DrawableSpinnerBonusTick) { bonusCounter.Text = $"{1000 * (currentSpins - spinsRequired)}"; bonusCounter.FadeOutFromOne(1500); diff --git a/osu.Game.Rulesets.Osu/Objects/Spinner.cs b/osu.Game.Rulesets.Osu/Objects/Spinner.cs index 4c21d9cfde..1c30058d5d 100644 --- a/osu.Game.Rulesets.Osu/Objects/Spinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Spinner.cs @@ -3,13 +3,11 @@ using System; 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.Types; using osu.Game.Rulesets.Osu.Judgements; -using osu.Game.Rulesets.Osu.Replays; using osu.Game.Rulesets.Scoring; -using osuTK; namespace osu.Game.Rulesets.Osu.Objects { @@ -28,6 +26,8 @@ namespace osu.Game.Rulesets.Osu.Objects /// public int SpinsRequired { get; protected set; } = 1; + public int MaximumBonusSpins => SpinsRequired; + protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) { base.ApplyDefaultsToSelf(controlPointInfo, difficulty); @@ -42,9 +42,16 @@ namespace osu.Game.Rulesets.Osu.Objects { base.CreateNestedHitObjects(); - var maximumSpins = OsuAutoGeneratorBase.SPIN_RADIUS * (Duration / 1000) / MathHelper.TwoPi; - for (int i = 0; i < maximumSpins; i++) - AddNested(new SpinnerTick()); + int totalSpins = MaximumBonusSpins + SpinsRequired; + + for (int i = 0; i < totalSpins; i++) + { + double startTime = StartTime + (float)(i + 1) / totalSpins * Duration; + + AddNested(i < SpinsRequired + ? new SpinnerTick { StartTime = startTime } + : new SpinnerBonusTick { StartTime = startTime }); + } } public override Judgement CreateJudgement() => new OsuJudgement(); diff --git a/osu.Game.Rulesets.Osu/Objects/SpinnerBonusTick.cs b/osu.Game.Rulesets.Osu/Objects/SpinnerBonusTick.cs new file mode 100644 index 0000000000..84eb58c70b --- /dev/null +++ b/osu.Game.Rulesets.Osu/Objects/SpinnerBonusTick.cs @@ -0,0 +1,24 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Audio; +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Scoring; + +namespace osu.Game.Rulesets.Osu.Objects +{ + public class SpinnerBonusTick : SpinnerTick + { + public SpinnerBonusTick() + { + Samples.Add(new HitSampleInfo { Name = "spinnerbonus" }); + } + + public override Judgement CreateJudgement() => new OsuSpinnerBonusTickJudgement(); + + public class OsuSpinnerBonusTickJudgement : OsuSpinnerTickJudgement + { + protected override int NumericResultFor(HitResult result) => 1100; + } + } +} diff --git a/osu.Game.Rulesets.Osu/Objects/SpinnerTick.cs b/osu.Game.Rulesets.Osu/Objects/SpinnerTick.cs index 318e8e71a2..89ad45b267 100644 --- a/osu.Game.Rulesets.Osu/Objects/SpinnerTick.cs +++ b/osu.Game.Rulesets.Osu/Objects/SpinnerTick.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.Audio; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Osu.Judgements; using osu.Game.Rulesets.Scoring; @@ -10,13 +9,17 @@ namespace osu.Game.Rulesets.Osu.Objects { public class SpinnerTick : OsuHitObject { - public SpinnerTick() - { - Samples.Add(new HitSampleInfo { Name = "spinnerbonus" }); - } - public override Judgement CreateJudgement() => new OsuSpinnerTickJudgement(); protected override HitWindows CreateHitWindows() => HitWindows.Empty; + + public class OsuSpinnerTickJudgement : OsuJudgement + { + public override bool AffectsCombo => false; + + protected override int NumericResultFor(HitResult result) => 100; + + protected override double HealthIncreaseFor(HitResult result) => 0; + } } } From 947f4e0d4c5aac6609ef6cfdf5488256402c6376 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 21 Jul 2020 19:03:17 +0900 Subject: [PATCH 0170/1782] Move tick handling to DrawableSpinner itself --- .../Objects/Drawables/DrawableSpinner.cs | 42 ++++++++- .../Objects/Drawables/DrawableSpinnerTick.cs | 7 +- .../Drawables/Pieces/SpinnerBonusComponent.cs | 87 ------------------- .../Drawables/Pieces/SpinnerBonusDisplay.cs | 44 ++++++++++ 4 files changed, 86 insertions(+), 94 deletions(-) delete mode 100644 osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerBonusComponent.cs create mode 100644 osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerBonusDisplay.cs diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index 531d16d1d1..df6eb206da 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables public readonly SpinnerDisc Disc; public readonly SpinnerTicks Ticks; public readonly SpinnerSpmCounter SpmCounter; - private readonly SpinnerBonusComponent bonusComponent; + private readonly SpinnerBonusDisplay bonusDisplay; private readonly Container mainContainer; @@ -126,7 +126,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables Y = 120, Alpha = 0 }, - bonusComponent = new SpinnerBonusComponent(this, ticks) + bonusDisplay = new SpinnerBonusDisplay { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -199,6 +199,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables if (userTriggered || Time.Current < Spinner.EndTime) return; + // Trigger a miss result for remaining ticks to avoid infinite gameplay. + foreach (var tick in ticks.Where(t => !t.IsHit)) + tick.TriggerResult(HitResult.Miss); + ApplyResult(r => { if (Progress >= 1) @@ -230,7 +234,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables Ticks.Rotation = Disc.Rotation; SpmCounter.SetRotation(Disc.CumulativeRotation); - bonusComponent.SetRotation(Disc.CumulativeRotation); + + updateBonusScore(); float relativeCircleScale = Spinner.Scale * circle.DrawHeight / mainContainer.DrawHeight; float targetScale = relativeCircleScale + (1 - relativeCircleScale) * Progress; @@ -239,6 +244,37 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables symbol.Rotation = (float)Interpolation.Lerp(symbol.Rotation, Disc.Rotation / 2, Math.Clamp(Math.Abs(Time.Elapsed) / 40, 0, 1)); } + private int wholeSpins; + + private void updateBonusScore() + { + if (ticks.Count == 0) + return; + + int spins = (int)(Disc.CumulativeRotation / 360); + + while (wholeSpins != spins) + { + if (wholeSpins < spins) + { + var tick = ticks.FirstOrDefault(t => !t.IsHit); + + if (tick != null) + { + tick.TriggerResult(HitResult.Great); + if (tick is DrawableSpinnerBonusTick) + bonusDisplay.SetBonusCount(spins - Spinner.SpinsRequired); + } + + wholeSpins++; + } + else + { + wholeSpins--; + } + } + } + protected override void UpdateInitialTransforms() { base.UpdateInitialTransforms(); diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinnerTick.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinnerTick.cs index 5fb7653f5a..6c9570c381 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinnerTick.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinnerTick.cs @@ -17,11 +17,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables /// /// Apply a judgement result. /// - /// Whether to apply a result, otherwise. - internal void TriggerResult(bool hit) + /// Whether to apply a result, otherwise. + internal void TriggerResult(HitResult result) { - HitObject.StartTime = Time.Current; - ApplyResult(r => r.Type = hit ? HitResult.Great : HitResult.Miss); + ApplyResult(r => r.Type = result); } } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerBonusComponent.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerBonusComponent.cs deleted file mode 100644 index 9a65247453..0000000000 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerBonusComponent.cs +++ /dev/null @@ -1,87 +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.Linq; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; -using osu.Game.Rulesets.Judgements; -using osu.Game.Rulesets.Objects.Drawables; - -namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces -{ - /// - /// A component that tracks spinner spins and add bonus score for it. - /// - public class SpinnerBonusComponent : CompositeDrawable - { - private readonly DrawableSpinner drawableSpinner; - private readonly Container ticks; - private readonly OsuSpriteText bonusCounter; - - public SpinnerBonusComponent(DrawableSpinner drawableSpinner, Container ticks) - { - this.drawableSpinner = drawableSpinner; - this.ticks = ticks; - - drawableSpinner.OnNewResult += onNewResult; - - AutoSizeAxes = Axes.Both; - InternalChild = bonusCounter = new OsuSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Font = OsuFont.Numeric.With(size: 24), - Alpha = 0, - }; - } - - private int currentSpins; - - public void SetRotation(double rotation) - { - if (ticks.Count == 0) - return; - - int spinsRequired = ((Spinner)drawableSpinner.HitObject).SpinsRequired; - - int newSpins = Math.Clamp((int)(rotation / 360), 0, ticks.Count - 1); - int direction = Math.Sign(newSpins - currentSpins); - - while (currentSpins != newSpins) - { - var tick = ticks[currentSpins]; - - if (direction >= 0) - tick.TriggerResult(true); - - if (tick is DrawableSpinnerBonusTick) - { - bonusCounter.Text = $"{1000 * (currentSpins - spinsRequired)}"; - bonusCounter.FadeOutFromOne(1500); - bonusCounter.ScaleTo(1.5f).Then().ScaleTo(1f, 1000, Easing.OutQuint); - } - - currentSpins += direction; - } - } - - private void onNewResult(DrawableHitObject hitObject, JudgementResult result) - { - if (!result.HasResult || hitObject != drawableSpinner) - return; - - // Trigger a miss result for remaining ticks to avoid infinite gameplay. - foreach (var tick in ticks.Where(t => !t.IsHit)) - tick.TriggerResult(false); - } - - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - drawableSpinner.OnNewResult -= onNewResult; - } - } -} diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerBonusDisplay.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerBonusDisplay.cs new file mode 100644 index 0000000000..76d7f1843e --- /dev/null +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerBonusDisplay.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.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; + +namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces +{ + /// + /// A component that tracks spinner spins and add bonus score for it. + /// + public class SpinnerBonusDisplay : CompositeDrawable + { + private readonly OsuSpriteText bonusCounter; + + public SpinnerBonusDisplay() + { + AutoSizeAxes = Axes.Both; + + InternalChild = bonusCounter = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Font = OsuFont.Numeric.With(size: 24), + Alpha = 0, + }; + } + + private int displayedCount; + + public void SetBonusCount(int count) + { + if (displayedCount == count) + return; + + displayedCount = count; + bonusCounter.Text = $"{1000 * count}"; + bonusCounter.FadeOutFromOne(1500); + bonusCounter.ScaleTo(1.5f).Then().ScaleTo(1f, 1000, Easing.OutQuint); + } + } +} From 7f2ae694cc96e41175932d16353fa3e1c0a3e9ba Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 21 Jul 2020 19:21:30 +0900 Subject: [PATCH 0171/1782] Simplify rewind handling --- .../Objects/Drawables/DrawableSpinner.cs | 29 ++++++++++--------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index df6eb206da..a8ecb60038 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -253,25 +253,26 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables int spins = (int)(Disc.CumulativeRotation / 360); + if (spins < wholeSpins) + { + // rewinding, silently handle + wholeSpins = spins; + return; + } + while (wholeSpins != spins) { - if (wholeSpins < spins) - { - var tick = ticks.FirstOrDefault(t => !t.IsHit); + var tick = ticks.FirstOrDefault(t => !t.IsHit); - if (tick != null) - { - tick.TriggerResult(HitResult.Great); - if (tick is DrawableSpinnerBonusTick) - bonusDisplay.SetBonusCount(spins - Spinner.SpinsRequired); - } - - wholeSpins++; - } - else + // tick may be null if we've hit the spin limit. + if (tick != null) { - wholeSpins--; + tick.TriggerResult(HitResult.Great); + if (tick is DrawableSpinnerBonusTick) + bonusDisplay.SetBonusCount(spins - Spinner.SpinsRequired); } + + wholeSpins++; } } From a4680d7a8945ded3804b0a0b84500a0a47241e44 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 21 Jul 2020 19:22:42 +0900 Subject: [PATCH 0172/1782] Reduce test range as to not hit spin cat --- osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs index 0f1cbcd44c..6e277ff37e 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs @@ -164,13 +164,13 @@ namespace osu.Game.Rulesets.Osu.Tests { double estimatedSpm = 0; - addSeekStep(2500); + addSeekStep(1000); AddStep("retrieve spm", () => estimatedSpm = drawableSpinner.SpmCounter.SpinsPerMinute); - addSeekStep(5000); + addSeekStep(2000); AddAssert("spm still valid", () => Precision.AlmostEquals(drawableSpinner.SpmCounter.SpinsPerMinute, estimatedSpm, 1.0)); - addSeekStep(2500); + addSeekStep(1000); AddAssert("spm still valid", () => Precision.AlmostEquals(drawableSpinner.SpmCounter.SpinsPerMinute, estimatedSpm, 1.0)); } From 1560e1786a09475d4537bfc02b881d8bb2f422f3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 21 Jul 2020 19:48:44 +0900 Subject: [PATCH 0173/1782] Revert back to bool for application --- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs | 4 ++-- .../Objects/Drawables/DrawableSpinnerTick.cs | 7 ++----- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index a8ecb60038..ecf78efdd9 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -201,7 +201,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables // Trigger a miss result for remaining ticks to avoid infinite gameplay. foreach (var tick in ticks.Where(t => !t.IsHit)) - tick.TriggerResult(HitResult.Miss); + tick.TriggerResult(false); ApplyResult(r => { @@ -267,7 +267,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables // tick may be null if we've hit the spin limit. if (tick != null) { - tick.TriggerResult(HitResult.Great); + tick.TriggerResult(true); if (tick is DrawableSpinnerBonusTick) bonusDisplay.SetBonusCount(spins - Spinner.SpinsRequired); } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinnerTick.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinnerTick.cs index 6c9570c381..c390b673be 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinnerTick.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinnerTick.cs @@ -17,10 +17,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables /// /// Apply a judgement result. /// - /// Whether to apply a result, otherwise. - internal void TriggerResult(HitResult result) - { - ApplyResult(r => r.Type = result); - } + /// Whether this tick was reached. + internal void TriggerResult(bool hit) => ApplyResult(r => r.Type = hit ? r.Judgement.MaxResult : HitResult.Miss); } } From bc079fccf52d5d338609ca87249208a899343958 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 21 Jul 2020 19:52:16 +0900 Subject: [PATCH 0174/1782] Add health drain for spinner ticks --- osu.Game.Rulesets.Osu/Objects/SpinnerBonusTick.cs | 2 ++ osu.Game.Rulesets.Osu/Objects/SpinnerTick.cs | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Objects/SpinnerBonusTick.cs b/osu.Game.Rulesets.Osu/Objects/SpinnerBonusTick.cs index 84eb58c70b..6ca2d4d72d 100644 --- a/osu.Game.Rulesets.Osu/Objects/SpinnerBonusTick.cs +++ b/osu.Game.Rulesets.Osu/Objects/SpinnerBonusTick.cs @@ -19,6 +19,8 @@ namespace osu.Game.Rulesets.Osu.Objects public class OsuSpinnerBonusTickJudgement : OsuSpinnerTickJudgement { protected override int NumericResultFor(HitResult result) => 1100; + + protected override double HealthIncreaseFor(HitResult result) => base.HealthIncreaseFor(result) * 2; } } } diff --git a/osu.Game.Rulesets.Osu/Objects/SpinnerTick.cs b/osu.Game.Rulesets.Osu/Objects/SpinnerTick.cs index 89ad45b267..c81348fbbf 100644 --- a/osu.Game.Rulesets.Osu/Objects/SpinnerTick.cs +++ b/osu.Game.Rulesets.Osu/Objects/SpinnerTick.cs @@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Osu.Objects protected override int NumericResultFor(HitResult result) => 100; - protected override double HealthIncreaseFor(HitResult result) => 0; + protected override double HealthIncreaseFor(HitResult result) => result == MaxResult ? 0.6 * base.HealthIncreaseFor(result) : 0; } } } From 107b5ca4f2ab5ca29356aad95a852cb28aa4e856 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 21 Jul 2020 23:13:04 +0900 Subject: [PATCH 0175/1782] Add support for bindable retrieval --- osu.Game/Beatmaps/BeatmapDifficultyManager.cs | 186 ++++++++++++++++-- osu.Game/OsuGameBase.cs | 5 +- .../Carousel/DrawableCarouselBeatmap.cs | 59 ++---- .../Screens/Select/Details/AdvancedStats.cs | 25 ++- 4 files changed, 208 insertions(+), 67 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapDifficultyManager.cs b/osu.Game/Beatmaps/BeatmapDifficultyManager.cs index 02342e9595..379cb6aa63 100644 --- a/osu.Game/Beatmaps/BeatmapDifficultyManager.cs +++ b/osu.Game/Beatmaps/BeatmapDifficultyManager.cs @@ -9,7 +9,9 @@ using System.Threading; using System.Threading.Tasks; using JetBrains.Annotations; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics.Containers; +using osu.Framework.Lists; using osu.Framework.Threading; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; @@ -21,32 +23,154 @@ namespace osu.Game.Beatmaps // Too many simultaneous updates can lead to stutters. One thread seems to work fine for song select display purposes. private readonly ThreadedTaskScheduler updateScheduler = new ThreadedTaskScheduler(1, nameof(BeatmapDifficultyManager)); - private readonly TimedExpiryCache difficultyCache = new TimedExpiryCache { ExpiryTime = 60000 }; - private readonly BeatmapManager beatmapManager; + // A cache that keeps references to BeatmapInfos for 60sec. + private readonly TimedExpiryCache difficultyCache = new TimedExpiryCache { ExpiryTime = 60000 }; - public BeatmapDifficultyManager(BeatmapManager beatmapManager) + // All bindables that should be updated along with the current ruleset + mods. + private readonly LockedWeakList trackedBindables = new LockedWeakList(); + + [Resolved] + private BeatmapManager beatmapManager { get; set; } + + [Resolved] + private Bindable currentRuleset { get; set; } + + [Resolved] + private Bindable> currentMods { get; set; } + + protected override void LoadComplete() { - this.beatmapManager = beatmapManager; + base.LoadComplete(); + + currentRuleset.BindValueChanged(_ => updateTrackedBindables()); + currentMods.BindValueChanged(_ => updateTrackedBindables(), true); } - public async Task GetDifficultyAsync([NotNull] BeatmapInfo beatmapInfo, [CanBeNull] RulesetInfo rulesetInfo = null, [CanBeNull] IReadOnlyList mods = null, - CancellationToken cancellationToken = default) + /// + /// Retrieves an containing the star difficulty of a with a given and combination. + /// + /// + /// This will not update to follow the currently-selected ruleset and mods. + /// + /// The to get the difficulty of. + /// The to get the difficulty with. + /// The s to get the difficulty with. + /// An optional which stops updating the star difficulty for the given . + /// An that is updated to contain the star difficulty when it becomes available. + public IBindable GetUntrackedBindable([NotNull] BeatmapInfo beatmapInfo, [CanBeNull] RulesetInfo rulesetInfo = null, [CanBeNull] IReadOnlyList mods = null, + CancellationToken cancellationToken = default) + => createBindable(beatmapInfo, rulesetInfo, mods, cancellationToken); + + /// + /// Retrieves a containing the star difficulty of a that follows the user's currently-selected ruleset and mods. + /// + /// + /// Ensure to hold a local reference of the returned in order to receive value-changed events. + /// + /// The to get the difficulty of. + /// An optional which stops updating the star difficulty for the given . + /// An that is updated to contain the star difficulty when it becomes available, or when the currently-selected ruleset and mods change. + public IBindable GetTrackedBindable([NotNull] BeatmapInfo beatmapInfo, CancellationToken cancellationToken = default) + { + var bindable = createBindable(beatmapInfo, currentRuleset.Value, currentMods.Value, cancellationToken); + trackedBindables.Add(bindable); + return bindable; + } + + /// + /// Retrieves the difficulty of a . + /// + /// The to get the difficulty of. + /// The to get the difficulty with. + /// The s to get the difficulty with. + /// An optional which stops computing the star difficulty. + /// The . + public async Task GetDifficultyAsync([NotNull] BeatmapInfo beatmapInfo, [CanBeNull] RulesetInfo rulesetInfo = null, [CanBeNull] IReadOnlyList mods = null, + CancellationToken cancellationToken = default) { if (tryGetGetExisting(beatmapInfo, rulesetInfo, mods, out var existing, out var key)) return existing; - return await Task.Factory.StartNew(() => getDifficulty(key), cancellationToken, TaskCreationOptions.HideScheduler | TaskCreationOptions.RunContinuationsAsynchronously, updateScheduler); + return await Task.Factory.StartNew(() => computeDifficulty(key), cancellationToken, TaskCreationOptions.HideScheduler | TaskCreationOptions.RunContinuationsAsynchronously, updateScheduler); } - public double GetDifficulty([NotNull] BeatmapInfo beatmapInfo, [CanBeNull] RulesetInfo rulesetInfo = null, [CanBeNull] IReadOnlyList mods = null) + /// + /// Retrieves the difficulty of a . + /// + /// The to get the difficulty of. + /// The to get the difficulty with. + /// The s to get the difficulty with. + /// The . + public StarDifficulty GetDifficulty([NotNull] BeatmapInfo beatmapInfo, [CanBeNull] RulesetInfo rulesetInfo = null, [CanBeNull] IReadOnlyList mods = null) { if (tryGetGetExisting(beatmapInfo, rulesetInfo, mods, out var existing, out var key)) return existing; - return getDifficulty(key); + return computeDifficulty(key); } - private double getDifficulty(in DifficultyCacheLookup key) + private CancellationTokenSource trackedUpdateCancellationSource; + + /// + /// Updates all tracked using the current ruleset and mods. + /// + private void updateTrackedBindables() + { + trackedUpdateCancellationSource?.Cancel(); + trackedUpdateCancellationSource = new CancellationTokenSource(); + + foreach (var b in trackedBindables) + { + if (trackedUpdateCancellationSource.IsCancellationRequested) + break; + + using (var linkedSource = CancellationTokenSource.CreateLinkedTokenSource(trackedUpdateCancellationSource.Token, b.CancellationToken)) + updateBindable(b, currentRuleset.Value, currentMods.Value, linkedSource.Token); + } + } + + /// + /// Updates the value of a with a given ruleset + mods. + /// + /// The to update. + /// The to update with. + /// The s to update with. + /// A token that may be used to cancel this update. + private void updateBindable([NotNull] BindableStarDifficulty bindable, [CanBeNull] RulesetInfo rulesetInfo, [CanBeNull] IReadOnlyList mods, CancellationToken cancellationToken = default) + { + GetDifficultyAsync(bindable.Beatmap, rulesetInfo, mods, cancellationToken).ContinueWith(t => + { + // We're on a threadpool thread, but we should exit back to the update thread so consumers can safely handle value-changed events. + Schedule(() => + { + if (!cancellationToken.IsCancellationRequested) + bindable.Value = t.Result; + }); + }, cancellationToken); + } + + /// + /// Creates a new and triggers an initial value update. + /// + /// The that star difficulty should correspond to. + /// The initial to get the difficulty with. + /// The initial s to get the difficulty with. + /// An optional which stops updating the star difficulty for the given . + /// The . + private BindableStarDifficulty createBindable([NotNull] BeatmapInfo beatmapInfo, [CanBeNull] RulesetInfo initialRulesetInfo, [CanBeNull] IReadOnlyList initialMods, + CancellationToken cancellationToken) + { + var bindable = new BindableStarDifficulty(beatmapInfo, cancellationToken); + updateBindable(bindable, initialRulesetInfo, initialMods, cancellationToken); + return bindable; + } + + /// + /// Computes the difficulty defined by a key, and stores it to the timed cache. + /// + /// The that defines the computation parameters. + /// The . + private StarDifficulty computeDifficulty(in DifficultyCacheLookup key) { try { @@ -56,13 +180,17 @@ namespace osu.Game.Beatmaps var calculator = ruleset.CreateDifficultyCalculator(beatmapManager.GetWorkingBeatmap(key.BeatmapInfo)); var attributes = calculator.Calculate(key.Mods); - difficultyCache.Add(key, attributes.StarRating); - return attributes.StarRating; + var difficulty = new StarDifficulty(attributes.StarRating); + difficultyCache.Add(key, difficulty); + + return difficulty; } catch { - difficultyCache.Add(key, 0); - return 0; + var difficulty = new StarDifficulty(0); + difficultyCache.Add(key, difficulty); + + return difficulty; } } @@ -73,9 +201,9 @@ namespace osu.Game.Beatmaps /// The . /// The s. /// The existing difficulty value, if present. - /// The key that was used to perform this lookup. This can be further used to query . + /// The key that was used to perform this lookup. This can be further used to query . /// Whether an existing difficulty was found. - private bool tryGetGetExisting(BeatmapInfo beatmapInfo, RulesetInfo rulesetInfo, IReadOnlyList mods, out double existingDifficulty, out DifficultyCacheLookup key) + private bool tryGetGetExisting(BeatmapInfo beatmapInfo, RulesetInfo rulesetInfo, IReadOnlyList mods, out StarDifficulty existingDifficulty, out DifficultyCacheLookup key) { // In the case that the user hasn't given us a ruleset, use the beatmap's default ruleset. rulesetInfo ??= beatmapInfo.Ruleset; @@ -83,7 +211,7 @@ namespace osu.Game.Beatmaps // Difficulty can only be computed if the beatmap is locally available. if (beatmapInfo.ID == 0) { - existingDifficulty = 0; + existingDifficulty = new StarDifficulty(0); key = default; return true; @@ -122,5 +250,29 @@ namespace osu.Game.Beatmaps return hashCode.ToHashCode(); } } + + private class BindableStarDifficulty : Bindable + { + public readonly BeatmapInfo Beatmap; + public readonly CancellationToken CancellationToken; + + public BindableStarDifficulty(BeatmapInfo beatmap, CancellationToken cancellationToken) + { + Beatmap = beatmap; + CancellationToken = cancellationToken; + } + } + } + + public readonly struct StarDifficulty + { + public readonly double Stars; + + public StarDifficulty(double stars) + { + Stars = stars; + + // Todo: Add more members (BeatmapInfo.DifficultyRating? Attributes? Etc...) + } } } diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 1e6631ffa0..fe5c0704b7 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -199,7 +199,10 @@ namespace osu.Game ScoreManager.Undelete(getBeatmapScores(item), true); }); - dependencies.Cache(new BeatmapDifficultyManager(BeatmapManager)); + var difficultyManager = new BeatmapDifficultyManager(); + dependencies.Cache(difficultyManager); + AddInternal(difficultyManager); + dependencies.Cache(KeyBindingStore = new KeyBindingStore(contextFactory, RulesetStore)); dependencies.Cache(SettingsStore = new SettingsStore(contextFactory)); dependencies.Cache(RulesetConfigCache = new RulesetConfigCache(SettingsStore)); diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs index d4205a4b93..d5aeecae04 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs @@ -15,7 +15,6 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; -using osu.Framework.Threading; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; using osu.Game.Graphics; @@ -23,8 +22,6 @@ using osu.Game.Graphics.Backgrounds; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Overlays; -using osu.Game.Rulesets; -using osu.Game.Rulesets.Mods; using osuTK; using osuTK.Graphics; @@ -46,15 +43,12 @@ namespace osu.Game.Screens.Select.Carousel [Resolved(CanBeNull = true)] private BeatmapSetOverlay beatmapOverlay { get; set; } - [Resolved] - private IBindable ruleset { get; set; } - - [Resolved] - private IBindable> mods { get; set; } - [Resolved] private BeatmapDifficultyManager difficultyManager { get; set; } + private IBindable starDifficultyBindable; + private CancellationTokenSource starDifficultyCancellationSource; + public DrawableCarouselBeatmap(CarouselBeatmap panel) : base(panel) { @@ -160,36 +154,6 @@ namespace osu.Game.Screens.Select.Carousel } } }; - - ruleset.BindValueChanged(_ => refreshStarCounter()); - mods.BindValueChanged(_ => refreshStarCounter(), true); - } - - private ScheduledDelegate scheduledRefresh; - private CancellationTokenSource cancellationSource; - - private void refreshStarCounter() - { - scheduledRefresh?.Cancel(); - scheduledRefresh = null; - - cancellationSource?.Cancel(); - cancellationSource = null; - - // Only want to run the calculation when we become visible. - scheduledRefresh = Schedule(() => - { - var ourSource = cancellationSource = new CancellationTokenSource(); - difficultyManager.GetDifficultyAsync(beatmap, ruleset.Value, mods.Value, ourSource.Token).ContinueWith(t => - { - // We're currently on a random threadpool thread which we must exit. - Schedule(() => - { - if (!ourSource.IsCancellationRequested) - starCounter.Current = (float)t.Result; - }); - }, ourSource.Token); - }); } protected override void Selected() @@ -224,6 +188,17 @@ namespace osu.Game.Screens.Select.Carousel if (Item.State.Value != CarouselItemState.Collapsed && Alpha == 0) starCounter.ReplayAnimation(); + if (Item.State.Value == CarouselItemState.Collapsed) + starDifficultyCancellationSource?.Cancel(); + else + { + starDifficultyCancellationSource?.Cancel(); + + // We've potentially cancelled the computation above so a new bindable is required. + starDifficultyBindable = difficultyManager.GetTrackedBindable(beatmap, (starDifficultyCancellationSource = new CancellationTokenSource()).Token); + starDifficultyBindable.BindValueChanged(d => starCounter.Current = (float)d.NewValue.Stars, true); + } + base.ApplyState(); } @@ -248,5 +223,11 @@ namespace osu.Game.Screens.Select.Carousel return items.ToArray(); } } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + starDifficultyCancellationSource?.Cancel(); + } } } diff --git a/osu.Game/Screens/Select/Details/AdvancedStats.cs b/osu.Game/Screens/Select/Details/AdvancedStats.cs index c5fc3701f8..aefba397b9 100644 --- a/osu.Game/Screens/Select/Details/AdvancedStats.cs +++ b/osu.Game/Screens/Select/Details/AdvancedStats.cs @@ -15,7 +15,6 @@ using System.Collections.Generic; using osu.Game.Rulesets.Mods; using System.Linq; using System.Threading; -using System.Threading.Tasks; using osu.Framework.Threading; using osu.Framework.Utils; using osu.Game.Configuration; @@ -149,6 +148,8 @@ namespace osu.Game.Screens.Select.Details updateStarDifficulty(); } + private IBindable normalStarDifficulty; + private IBindable moddedStarDifficulty; private CancellationTokenSource starDifficultyCancellationSource; private void updateStarDifficulty() @@ -160,15 +161,19 @@ namespace osu.Game.Screens.Select.Details var ourSource = starDifficultyCancellationSource = new CancellationTokenSource(); - Task.WhenAll(difficultyManager.GetDifficultyAsync(Beatmap, ruleset.Value, cancellationToken: ourSource.Token), - difficultyManager.GetDifficultyAsync(Beatmap, ruleset.Value, mods.Value, ourSource.Token)).ContinueWith(t => - { - Schedule(() => - { - if (!ourSource.IsCancellationRequested) - starDifficulty.Value = ((float)t.Result[0], (float)t.Result[1]); - }); - }, ourSource.Token); + normalStarDifficulty = difficultyManager.GetUntrackedBindable(Beatmap, ruleset.Value, cancellationToken: ourSource.Token); + moddedStarDifficulty = difficultyManager.GetUntrackedBindable(Beatmap, ruleset.Value, mods.Value, ourSource.Token); + + normalStarDifficulty.BindValueChanged(_ => updateDisplay()); + moddedStarDifficulty.BindValueChanged(_ => updateDisplay(), true); + + void updateDisplay() => starDifficulty.Value = ((float)normalStarDifficulty.Value.Stars, (float)moddedStarDifficulty.Value.Stars); + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + starDifficultyCancellationSource?.Cancel(); } public class StatisticRow : Container, IHasAccentColour From 00e6217f60c9d1981e1eb16e1b21b39a70b844a9 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 21 Jul 2020 23:50:54 +0900 Subject: [PATCH 0176/1782] Don't store BeatmapInfo/RulesetInfo references, remove TimedExpiryCache --- osu.Game/Beatmaps/BeatmapDifficultyManager.cs | 49 +++++++++---------- 1 file changed, 24 insertions(+), 25 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapDifficultyManager.cs b/osu.Game/Beatmaps/BeatmapDifficultyManager.cs index 379cb6aa63..b469ca78fb 100644 --- a/osu.Game/Beatmaps/BeatmapDifficultyManager.cs +++ b/osu.Game/Beatmaps/BeatmapDifficultyManager.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Linq; @@ -24,7 +25,7 @@ namespace osu.Game.Beatmaps private readonly ThreadedTaskScheduler updateScheduler = new ThreadedTaskScheduler(1, nameof(BeatmapDifficultyManager)); // A cache that keeps references to BeatmapInfos for 60sec. - private readonly TimedExpiryCache difficultyCache = new TimedExpiryCache { ExpiryTime = 60000 }; + private readonly ConcurrentDictionary difficultyCache = new ConcurrentDictionary(); // All bindables that should be updated along with the current ruleset + mods. private readonly LockedWeakList trackedBindables = new LockedWeakList(); @@ -91,7 +92,8 @@ namespace osu.Game.Beatmaps if (tryGetGetExisting(beatmapInfo, rulesetInfo, mods, out var existing, out var key)) return existing; - return await Task.Factory.StartNew(() => computeDifficulty(key), cancellationToken, TaskCreationOptions.HideScheduler | TaskCreationOptions.RunContinuationsAsynchronously, updateScheduler); + return await Task.Factory.StartNew(() => computeDifficulty(key, beatmapInfo, rulesetInfo), cancellationToken, + TaskCreationOptions.HideScheduler | TaskCreationOptions.RunContinuationsAsynchronously, updateScheduler); } /// @@ -106,7 +108,7 @@ namespace osu.Game.Beatmaps if (tryGetGetExisting(beatmapInfo, rulesetInfo, mods, out var existing, out var key)) return existing; - return computeDifficulty(key); + return computeDifficulty(key, beatmapInfo, rulesetInfo); } private CancellationTokenSource trackedUpdateCancellationSource; @@ -169,28 +171,24 @@ namespace osu.Game.Beatmaps /// Computes the difficulty defined by a key, and stores it to the timed cache. /// /// The that defines the computation parameters. + /// The to compute the difficulty of. + /// The to compute the difficulty with. /// The . - private StarDifficulty computeDifficulty(in DifficultyCacheLookup key) + private StarDifficulty computeDifficulty(in DifficultyCacheLookup key, BeatmapInfo beatmapInfo, RulesetInfo rulesetInfo) { try { - var ruleset = key.RulesetInfo.CreateInstance(); + var ruleset = rulesetInfo.CreateInstance(); Debug.Assert(ruleset != null); - var calculator = ruleset.CreateDifficultyCalculator(beatmapManager.GetWorkingBeatmap(key.BeatmapInfo)); + var calculator = ruleset.CreateDifficultyCalculator(beatmapManager.GetWorkingBeatmap(beatmapInfo)); var attributes = calculator.Calculate(key.Mods); - var difficulty = new StarDifficulty(attributes.StarRating); - difficultyCache.Add(key, difficulty); - - return difficulty; + return difficultyCache[key] = new StarDifficulty(attributes.StarRating); } catch { - var difficulty = new StarDifficulty(0); - difficultyCache.Add(key, difficulty); - - return difficulty; + return difficultyCache[key] = new StarDifficulty(0); } } @@ -208,8 +206,8 @@ namespace osu.Game.Beatmaps // In the case that the user hasn't given us a ruleset, use the beatmap's default ruleset. rulesetInfo ??= beatmapInfo.Ruleset; - // Difficulty can only be computed if the beatmap is locally available. - if (beatmapInfo.ID == 0) + // Difficulty can only be computed if the beatmap and ruleset are locally available. + if (beatmapInfo.ID == 0 || rulesetInfo.ID == null) { existingDifficulty = new StarDifficulty(0); key = default; @@ -217,33 +215,34 @@ namespace osu.Game.Beatmaps return true; } - key = new DifficultyCacheLookup(beatmapInfo, rulesetInfo, mods); + key = new DifficultyCacheLookup(beatmapInfo.ID, rulesetInfo.ID.Value, mods); return difficultyCache.TryGetValue(key, out existingDifficulty); } private readonly struct DifficultyCacheLookup : IEquatable { - public readonly BeatmapInfo BeatmapInfo; - public readonly RulesetInfo RulesetInfo; + public readonly int BeatmapId; + public readonly int RulesetId; public readonly Mod[] Mods; - public DifficultyCacheLookup(BeatmapInfo beatmapInfo, RulesetInfo rulesetInfo, IEnumerable mods) + public DifficultyCacheLookup(int beatmapId, int rulesetId, IEnumerable mods) { - BeatmapInfo = beatmapInfo; - RulesetInfo = rulesetInfo; + BeatmapId = beatmapId; + RulesetId = rulesetId; Mods = mods?.OrderBy(m => m.Acronym).ToArray() ?? Array.Empty(); } public bool Equals(DifficultyCacheLookup other) - => BeatmapInfo.Equals(other.BeatmapInfo) + => BeatmapId == other.BeatmapId + && RulesetId == other.RulesetId && Mods.SequenceEqual(other.Mods); public override int GetHashCode() { var hashCode = new HashCode(); - hashCode.Add(BeatmapInfo.Hash); - hashCode.Add(RulesetInfo.GetHashCode()); + hashCode.Add(BeatmapId); + hashCode.Add(RulesetId); foreach (var mod in Mods) hashCode.Add(mod.Acronym); From e96f8f1cb652e4d312758b20414f74c01d144ca0 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 21 Jul 2020 20:02:22 +0300 Subject: [PATCH 0177/1782] Make content side padding adjustable for OverlayHeader --- .../UserInterface/TestSceneOverlayHeader.cs | 20 +++++++++++--- osu.Game/Overlays/OverlayHeader.cs | 27 ++++++++++++++----- osu.Game/Overlays/TabControlOverlayHeader.cs | 24 ++++++++++++++--- 3 files changed, 57 insertions(+), 14 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneOverlayHeader.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneOverlayHeader.cs index 60af5b37ef..01c13dbc97 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneOverlayHeader.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneOverlayHeader.cs @@ -36,11 +36,11 @@ namespace osu.Game.Tests.Visual.UserInterface } }); - addHeader("Orange OverlayHeader (no background)", new TestNoBackgroundHeader(), OverlayColourScheme.Orange); - addHeader("Blue OverlayHeader", new TestNoControlHeader(), OverlayColourScheme.Blue); + addHeader("Orange OverlayHeader (no background, 100 padding)", new TestNoBackgroundHeader(), OverlayColourScheme.Orange); + addHeader("Blue OverlayHeader (default 70 padding)", new TestNoControlHeader(), OverlayColourScheme.Blue); addHeader("Green TabControlOverlayHeader (string) with ruleset selector", new TestStringTabControlHeader(), OverlayColourScheme.Green); - addHeader("Pink TabControlOverlayHeader (enum)", new TestEnumTabControlHeader(), OverlayColourScheme.Pink); - addHeader("Red BreadcrumbControlOverlayHeader (no background)", new TestBreadcrumbControlHeader(), OverlayColourScheme.Red); + addHeader("Pink TabControlOverlayHeader (enum, 30 padding)", new TestEnumTabControlHeader(), OverlayColourScheme.Pink); + addHeader("Red BreadcrumbControlOverlayHeader (no background, 10 padding)", new TestBreadcrumbControlHeader(), OverlayColourScheme.Red); } private void addHeader(string name, OverlayHeader header, OverlayColourScheme colourScheme) @@ -86,6 +86,11 @@ namespace osu.Game.Tests.Visual.UserInterface private class TestNoBackgroundHeader : OverlayHeader { protected override OverlayTitle CreateTitle() => new TestTitle(); + + public TestNoBackgroundHeader() + { + ContentSidePadding = 100; + } } private class TestNoControlHeader : OverlayHeader @@ -112,6 +117,11 @@ namespace osu.Game.Tests.Visual.UserInterface private class TestEnumTabControlHeader : TabControlOverlayHeader { + public TestEnumTabControlHeader() + { + ContentSidePadding = 30; + } + protected override Drawable CreateBackground() => new OverlayHeaderBackground(@"Headers/rankings"); protected override OverlayTitle CreateTitle() => new TestTitle(); @@ -130,6 +140,8 @@ namespace osu.Game.Tests.Visual.UserInterface public TestBreadcrumbControlHeader() { + ContentSidePadding = 10; + TabControl.AddItem("tab1"); TabControl.AddItem("tab2"); TabControl.Current.Value = "tab2"; diff --git a/osu.Game/Overlays/OverlayHeader.cs b/osu.Game/Overlays/OverlayHeader.cs index dbc934bde9..c9b9e3b836 100644 --- a/osu.Game/Overlays/OverlayHeader.cs +++ b/osu.Game/Overlays/OverlayHeader.cs @@ -12,9 +12,26 @@ namespace osu.Game.Overlays { public abstract class OverlayHeader : Container { - public const int CONTENT_X_MARGIN = 50; + private float contentSidePadding; + + /// + /// Horizontal padding of the header content. + /// + protected float ContentSidePadding + { + get => contentSidePadding; + set + { + contentSidePadding = value; + content.Padding = new MarginPadding + { + Horizontal = value + }; + } + } private readonly Box titleBackground; + private readonly Container content; protected readonly FillFlowContainer HeaderInfo; @@ -50,14 +67,10 @@ namespace osu.Game.Overlays RelativeSizeAxes = Axes.Both, Colour = Color4.Gray, }, - new Container + content = new Container { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Padding = new MarginPadding - { - Horizontal = CONTENT_X_MARGIN, - }, Children = new[] { CreateTitle().With(title => @@ -79,6 +92,8 @@ namespace osu.Game.Overlays CreateContent() } }); + + ContentSidePadding = 70; } [BackgroundDependencyLoader] diff --git a/osu.Game/Overlays/TabControlOverlayHeader.cs b/osu.Game/Overlays/TabControlOverlayHeader.cs index e8e000f441..61605d9e9e 100644 --- a/osu.Game/Overlays/TabControlOverlayHeader.cs +++ b/osu.Game/Overlays/TabControlOverlayHeader.cs @@ -22,6 +22,7 @@ namespace osu.Game.Overlays protected OsuTabControl TabControl; private readonly Box controlBackground; + private readonly Container tabControlContainer; private readonly BindableWithCurrent current = new BindableWithCurrent(); public Bindable Current @@ -30,6 +31,16 @@ namespace osu.Game.Overlays set => current.Current = value; } + protected new float ContentSidePadding + { + get => base.ContentSidePadding; + set + { + base.ContentSidePadding = value; + tabControlContainer.Padding = new MarginPadding { Horizontal = value }; + } + } + protected TabControlOverlayHeader() { HeaderInfo.Add(new Container @@ -42,11 +53,16 @@ namespace osu.Game.Overlays { RelativeSizeAxes = Axes.Both, }, - TabControl = CreateTabControl().With(control => + tabControlContainer = new Container { - control.Margin = new MarginPadding { Left = CONTENT_X_MARGIN }; - control.Current = Current; - }) + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding { Horizontal = ContentSidePadding }, + Child = TabControl = CreateTabControl().With(control => + { + control.Current = Current; + }) + } } }); } From 0145ca09e5d8ebd98a85857c6fab121d7c112143 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 21 Jul 2020 20:11:10 +0300 Subject: [PATCH 0178/1782] Apply changes to overlays --- osu.Game/Overlays/OverlayHeader.cs | 2 +- osu.Game/Overlays/Profile/ProfileHeader.cs | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/OverlayHeader.cs b/osu.Game/Overlays/OverlayHeader.cs index c9b9e3b836..cc7f798c4a 100644 --- a/osu.Game/Overlays/OverlayHeader.cs +++ b/osu.Game/Overlays/OverlayHeader.cs @@ -93,7 +93,7 @@ namespace osu.Game.Overlays } }); - ContentSidePadding = 70; + ContentSidePadding = 50; } [BackgroundDependencyLoader] diff --git a/osu.Game/Overlays/Profile/ProfileHeader.cs b/osu.Game/Overlays/Profile/ProfileHeader.cs index 0161d91daa..2895fa0726 100644 --- a/osu.Game/Overlays/Profile/ProfileHeader.cs +++ b/osu.Game/Overlays/Profile/ProfileHeader.cs @@ -23,6 +23,8 @@ namespace osu.Game.Overlays.Profile public ProfileHeader() { + ContentSidePadding = 70; + User.ValueChanged += e => updateDisplay(e.NewValue); TabControl.AddItem("info"); From 0a71194ea69e07e516be8db5c9180d612459aec8 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 21 Jul 2020 22:46:08 +0300 Subject: [PATCH 0179/1782] Fix SpotlightSelector is a VisibilityContainer without a reason --- .../TestSceneRankingsSpotlightSelector.cs | 6 - .../Overlays/Rankings/SpotlightSelector.cs | 104 ++++++++---------- .../Overlays/Rankings/SpotlightsLayout.cs | 2 - 3 files changed, 45 insertions(+), 67 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneRankingsSpotlightSelector.cs b/osu.Game.Tests/Visual/Online/TestSceneRankingsSpotlightSelector.cs index 997db827f3..d60222fa0b 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneRankingsSpotlightSelector.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneRankingsSpotlightSelector.cs @@ -30,12 +30,6 @@ namespace osu.Game.Tests.Visual.Online Add(selector = new SpotlightSelector()); } - [Test] - public void TestVisibility() - { - AddStep("Toggle Visibility", selector.ToggleVisibility); - } - [Test] public void TestLocalSpotlights() { diff --git a/osu.Game/Overlays/Rankings/SpotlightSelector.cs b/osu.Game/Overlays/Rankings/SpotlightSelector.cs index f112c1ec43..422373d099 100644 --- a/osu.Game/Overlays/Rankings/SpotlightSelector.cs +++ b/osu.Game/Overlays/Rankings/SpotlightSelector.cs @@ -18,10 +18,8 @@ using osu.Game.Online.API.Requests; namespace osu.Game.Overlays.Rankings { - public class SpotlightSelector : VisibilityContainer, IHasCurrentValue + public class SpotlightSelector : CompositeDrawable, IHasCurrentValue { - private const int duration = 300; - private readonly BindableWithCurrent current = new BindableWithCurrent(); public readonly Bindable Sort = new Bindable(); @@ -37,10 +35,7 @@ namespace osu.Game.Overlays.Rankings set => dropdown.Items = value; } - protected override bool StartHidden => true; - private readonly Box background; - private readonly Container content; private readonly SpotlightsDropdown dropdown; private readonly InfoColumn startDateColumn; private readonly InfoColumn endDateColumn; @@ -51,73 +46,68 @@ namespace osu.Game.Overlays.Rankings { RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; - Add(content = new Container + InternalChildren = new Drawable[] { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Children = new Drawable[] + background = new Box { - background = new Box - { - RelativeSizeAxes = Axes.Both, - }, - new Container + RelativeSizeAxes = Axes.Both, + }, + new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding { Horizontal = UserProfileOverlay.CONTENT_X_MARGIN }, + Child = new FillFlowContainer { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Padding = new MarginPadding { Horizontal = UserProfileOverlay.CONTENT_X_MARGIN }, - Child = new FillFlowContainer + Direction = FillDirection.Vertical, + Children = new Drawable[] { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, - Children = new Drawable[] + new Container { - new Container - { - Margin = new MarginPadding { Vertical = 20 }, - RelativeSizeAxes = Axes.X, - Height = 40, - Depth = -float.MaxValue, - Child = dropdown = new SpotlightsDropdown - { - RelativeSizeAxes = Axes.X, - Current = Current - } - }, - new Container + Margin = new MarginPadding { Vertical = 20 }, + RelativeSizeAxes = Axes.X, + Height = 40, + Depth = -float.MaxValue, + Child = dropdown = new SpotlightsDropdown { RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Children = new Drawable[] + Current = Current + } + }, + new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Children = new Drawable[] + { + new FillFlowContainer { - new FillFlowContainer + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(10, 0), + Margin = new MarginPadding { Bottom = 5 }, + Children = new Drawable[] { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(10, 0), - Margin = new MarginPadding { Bottom = 5 }, - Children = new Drawable[] - { - startDateColumn = new InfoColumn(@"Start Date"), - endDateColumn = new InfoColumn(@"End Date"), - mapCountColumn = new InfoColumn(@"Map Count"), - participantsColumn = new InfoColumn(@"Participants") - } - }, - new RankingsSortTabControl - { - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, - Current = Sort + startDateColumn = new InfoColumn(@"Start Date"), + endDateColumn = new InfoColumn(@"End Date"), + mapCountColumn = new InfoColumn(@"Map Count"), + participantsColumn = new InfoColumn(@"Participants") } + }, + new RankingsSortTabControl + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Current = Sort } } } } } } - }); + }; } [BackgroundDependencyLoader] @@ -134,10 +124,6 @@ namespace osu.Game.Overlays.Rankings participantsColumn.Value = response.Spotlight.Participants?.ToString("N0"); } - protected override void PopIn() => content.FadeIn(duration, Easing.OutQuint); - - protected override void PopOut() => content.FadeOut(duration, Easing.OutQuint); - private string dateToString(DateTimeOffset date) => date.ToString("yyyy-MM-dd"); private class InfoColumn : FillFlowContainer diff --git a/osu.Game/Overlays/Rankings/SpotlightsLayout.cs b/osu.Game/Overlays/Rankings/SpotlightsLayout.cs index 0f9b07bf89..61339df76f 100644 --- a/osu.Game/Overlays/Rankings/SpotlightsLayout.cs +++ b/osu.Game/Overlays/Rankings/SpotlightsLayout.cs @@ -81,8 +81,6 @@ namespace osu.Game.Overlays.Rankings { base.LoadComplete(); - selector.Show(); - selectedSpotlight.BindValueChanged(_ => onSpotlightChanged()); sort.BindValueChanged(_ => onSpotlightChanged()); Ruleset.BindValueChanged(onRulesetChanged); From ad9492804a645ea851f815b23878e4ab98211f6c Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 21 Jul 2020 22:56:44 +0300 Subject: [PATCH 0180/1782] Apply suggestions --- osu.Game.Tests/Visual/UserInterface/TestSceneOverlayHeader.cs | 2 +- osu.Game/Overlays/Profile/ProfileHeader.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneOverlayHeader.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneOverlayHeader.cs index 01c13dbc97..2a76b8e265 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneOverlayHeader.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneOverlayHeader.cs @@ -37,7 +37,7 @@ namespace osu.Game.Tests.Visual.UserInterface }); addHeader("Orange OverlayHeader (no background, 100 padding)", new TestNoBackgroundHeader(), OverlayColourScheme.Orange); - addHeader("Blue OverlayHeader (default 70 padding)", new TestNoControlHeader(), OverlayColourScheme.Blue); + addHeader("Blue OverlayHeader (default 50 padding)", new TestNoControlHeader(), OverlayColourScheme.Blue); addHeader("Green TabControlOverlayHeader (string) with ruleset selector", new TestStringTabControlHeader(), OverlayColourScheme.Green); addHeader("Pink TabControlOverlayHeader (enum, 30 padding)", new TestEnumTabControlHeader(), OverlayColourScheme.Pink); addHeader("Red BreadcrumbControlOverlayHeader (no background, 10 padding)", new TestBreadcrumbControlHeader(), OverlayColourScheme.Red); diff --git a/osu.Game/Overlays/Profile/ProfileHeader.cs b/osu.Game/Overlays/Profile/ProfileHeader.cs index 2895fa0726..2e5f1071f2 100644 --- a/osu.Game/Overlays/Profile/ProfileHeader.cs +++ b/osu.Game/Overlays/Profile/ProfileHeader.cs @@ -23,7 +23,7 @@ namespace osu.Game.Overlays.Profile public ProfileHeader() { - ContentSidePadding = 70; + ContentSidePadding = UserProfileOverlay.CONTENT_X_MARGIN; User.ValueChanged += e => updateDisplay(e.NewValue); From cccb47e6e04e956dc4cfe73dfdff8c2bdc993526 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 22 Jul 2020 11:29:23 +0900 Subject: [PATCH 0181/1782] Add user cover background to expanded version of score panel --- osu.Game/Screens/Ranking/ScorePanel.cs | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/osu.Game/Screens/Ranking/ScorePanel.cs b/osu.Game/Screens/Ranking/ScorePanel.cs index 9633f5c533..5da432d5b2 100644 --- a/osu.Game/Screens/Ranking/ScorePanel.cs +++ b/osu.Game/Screens/Ranking/ScorePanel.cs @@ -13,6 +13,7 @@ using osu.Framework.Input.Events; using osu.Game.Scoring; using osu.Game.Screens.Ranking.Contracted; using osu.Game.Screens.Ranking.Expanded; +using osu.Game.Users; using osuTK; using osuTK.Graphics; @@ -142,7 +143,16 @@ namespace osu.Game.Screens.Ranking CornerRadius = 20, CornerExponent = 2.5f, Masking = true, - Child = middleLayerBackground = new Box { RelativeSizeAxes = Axes.Both } + Children = new[] + { + middleLayerBackground = new Box { RelativeSizeAxes = Axes.Both }, + new UserCoverBackground + { + RelativeSizeAxes = Axes.Both, + User = Score.User, + Colour = ColourInfo.GradientVertical(Color4.White.Opacity(0.5f), Color4Extensions.FromHex("#444").Opacity(0)) + }, + } }, middleLayerContentContainer = new Container { RelativeSizeAxes = Axes.Both } } @@ -155,18 +165,10 @@ namespace osu.Game.Screens.Ranking { base.LoadComplete(); - if (state == PanelState.Expanded) - { - topLayerBackground.FadeColour(expanded_top_layer_colour); - middleLayerBackground.FadeColour(expanded_middle_layer_colour); - } - else - { - topLayerBackground.FadeColour(contracted_top_layer_colour); - middleLayerBackground.FadeColour(contracted_middle_layer_colour); - } - updateState(); + + topLayerBackground.FinishTransforms(false, nameof(Colour)); + middleLayerBackground.FinishTransforms(false, nameof(Colour)); } private PanelState state = PanelState.Contracted; From aca4110e36d03568e1ca2ceadeaf3df42a41093e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 22 Jul 2020 12:47:53 +0900 Subject: [PATCH 0182/1782] Use existing star difficulty if non-local beatmap/ruleset --- osu.Game/Beatmaps/BeatmapDifficultyManager.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/BeatmapDifficultyManager.cs b/osu.Game/Beatmaps/BeatmapDifficultyManager.cs index b469ca78fb..d94e04a79b 100644 --- a/osu.Game/Beatmaps/BeatmapDifficultyManager.cs +++ b/osu.Game/Beatmaps/BeatmapDifficultyManager.cs @@ -209,7 +209,8 @@ namespace osu.Game.Beatmaps // Difficulty can only be computed if the beatmap and ruleset are locally available. if (beatmapInfo.ID == 0 || rulesetInfo.ID == null) { - existingDifficulty = new StarDifficulty(0); + // If not, fall back to the existing star difficulty (e.g. from an online source). + existingDifficulty = new StarDifficulty(beatmapInfo.StarDifficulty); key = default; return true; From 6b7f05740e51c77790295a0ba882c8b45d379bb2 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 22 Jul 2020 12:48:12 +0900 Subject: [PATCH 0183/1782] Fix potential missing ruleset --- osu.Game/Beatmaps/BeatmapDifficultyManager.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Beatmaps/BeatmapDifficultyManager.cs b/osu.Game/Beatmaps/BeatmapDifficultyManager.cs index d94e04a79b..a9f34acd14 100644 --- a/osu.Game/Beatmaps/BeatmapDifficultyManager.cs +++ b/osu.Game/Beatmaps/BeatmapDifficultyManager.cs @@ -176,6 +176,9 @@ namespace osu.Game.Beatmaps /// The . private StarDifficulty computeDifficulty(in DifficultyCacheLookup key, BeatmapInfo beatmapInfo, RulesetInfo rulesetInfo) { + // In the case that the user hasn't given us a ruleset, use the beatmap's default ruleset. + rulesetInfo ??= beatmapInfo.Ruleset; + try { var ruleset = rulesetInfo.CreateInstance(); From ac602846df9d6a6be5b70672b537fe126cd0274e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 22 Jul 2020 16:37:24 +0900 Subject: [PATCH 0184/1782] Expose balance and sample loading methods in DrawableHitObject --- .../Objects/Drawables/DrawableHitObject.cs | 29 ++++++++++++++----- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index b633cb0860..f275153ce3 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 if (Result == null) throw new InvalidOperationException($"{GetType().ReadableName()} must provide a {nameof(JudgementResult)} through {nameof(CreateResult)}."); - loadSamples(); + LoadSamples(); } protected override void LoadComplete() @@ -145,14 +145,14 @@ namespace osu.Game.Rulesets.Objects.Drawables } samplesBindable = HitObject.SamplesBindable.GetBoundCopy(); - samplesBindable.CollectionChanged += (_, __) => loadSamples(); + samplesBindable.CollectionChanged += (_, __) => LoadSamples(); apply(HitObject); updateState(ArmedState.Idle, true); } - private void loadSamples() + protected virtual void LoadSamples() { if (Samples != null) { @@ -353,17 +353,32 @@ namespace osu.Game.Rulesets.Objects.Drawables [Resolved(canBeNull: true)] private GameplayClock gameplayClock { get; set; } + /// + /// Calculate the position to be used for sample playback at a specified X position (0..1). + /// + /// The lookup X position. Generally should be . + /// + protected double CalculateSamplePlaybackBalance(double position) + { + const float balance_adjust_amount = 0.4f; + + return balance_adjust_amount * (userPositionalHitSounds.Value ? position - 0.5f : 0); + } + + /// + /// Whether samples should currently be playing. Will be false during seek operations. + /// + protected bool ShouldPlaySamples => gameplayClock?.IsSeeking != true; + /// /// Plays all the hit sounds for this . /// This is invoked automatically when this is hit. /// public virtual void PlaySamples() { - const float balance_adjust_amount = 0.4f; - - if (Samples != null && gameplayClock?.IsSeeking != true) + if (Samples != null && ShouldPlaySamples) { - Samples.Balance.Value = balance_adjust_amount * (userPositionalHitSounds.Value ? SamplePlaybackPosition - 0.5f : 0); + Samples.Balance.Value = CalculateSamplePlaybackBalance(SamplePlaybackPosition); Samples.Play(); } } From 3ed40d3a6b1fd14413920fc70208a71e4beebf99 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 22 Jul 2020 16:37:53 +0900 Subject: [PATCH 0185/1782] Fix SkinnableSounds not continuing playback on skin change --- osu.Game/Skinning/SkinnableSound.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game/Skinning/SkinnableSound.cs b/osu.Game/Skinning/SkinnableSound.cs index 24d6648273..49f9f01cff 100644 --- a/osu.Game/Skinning/SkinnableSound.cs +++ b/osu.Game/Skinning/SkinnableSound.cs @@ -98,6 +98,8 @@ namespace osu.Game.Skinning protected override void SkinChanged(ISkinSource skin, bool allowFallback) { + bool wasPlaying = samplesContainer.Any(s => s.Playing); + var channels = hitSamples.Select(s => { var ch = skin.GetSample(s); @@ -121,6 +123,9 @@ namespace osu.Game.Skinning }).Where(c => c != null); samplesContainer.ChildrenEnumerable = channels.Select(c => new DrawableSample(c)); + + if (wasPlaying) + Play(); } } } From 2126f6bffc9d613706e12c4ef153c1fb7cb567ab Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 22 Jul 2020 16:37:38 +0900 Subject: [PATCH 0186/1782] Add slider "sliding" sample support --- .../Objects/Drawables/DrawableSlider.cs | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index 72502c02cd..5059ec1231 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Linq; using osuTK; using osu.Framework.Graphics; using osu.Game.Rulesets.Objects.Drawables; @@ -11,6 +12,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Skinning; +using osu.Game.Rulesets.Osu.UI; using osu.Game.Rulesets.Scoring; using osuTK.Graphics; using osu.Game.Skinning; @@ -81,6 +83,41 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables foreach (var drawableHitObject in NestedHitObjects) drawableHitObject.AccentColour.Value = colour.NewValue; }, true); + + Tracking.BindValueChanged(updateSlidingSample); + } + + private SkinnableSound slidingSample; + + protected override void LoadSamples() + { + base.LoadSamples(); + + slidingSample?.Expire(); + + var firstSample = HitObject.Samples.FirstOrDefault(); + + if (firstSample != null) + { + var clone = HitObject.SampleControlPoint.ApplyTo(firstSample); + clone.Name = "sliderslide"; + + AddInternal(slidingSample = new SkinnableSound(clone) + { + Looping = true + }); + } + } + + private void updateSlidingSample(ValueChangedEvent tracking) + { + // note that samples will not start playing if exiting a seek operation in the middle of a slider. + // may be something we want to address at a later point, but not so easy to make happen right now + // (SkinnableSound would need to expose whether the sample is already playing and this logic would need to run in Update). + if (tracking.NewValue && ShouldPlaySamples) + slidingSample?.Play(); + else + slidingSample?.Stop(); } protected override void AddNestedHitObject(DrawableHitObject hitObject) @@ -156,6 +193,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables Tracking.Value = Ball.Tracking; + if (Tracking.Value && slidingSample != null) + // keep the sliding sample playing at the current tracking position + slidingSample.Balance.Value = CalculateSamplePlaybackBalance(Ball.X / OsuPlayfield.BASE_SIZE.X); + double completionProgress = Math.Clamp((Time.Current - slider.StartTime) / slider.Duration, 0, 1); Ball.UpdateProgress(completionProgress); From 0957c5f74ce0bdf1e3fc6524d18d0021780abb86 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 22 Jul 2020 18:29:50 +0900 Subject: [PATCH 0187/1782] Re-namespace multiplayer requests/responses --- .../Visual/Multiplayer/TestSceneTimeshiftResultsScreen.cs | 1 - .../Requests/Responses => Multiplayer}/APICreatedRoom.cs | 3 +-- osu.Game/Online/{API => Multiplayer}/APIPlaylistBeatmap.cs | 2 +- .../{API/Requests/Responses => Multiplayer}/APIScoreToken.cs | 2 +- .../{API/Requests => Multiplayer}/CreateRoomRequest.cs | 5 ++--- .../{API/Requests => Multiplayer}/CreateRoomScoreRequest.cs | 4 ++-- .../Requests => Multiplayer}/GetRoomPlaylistScoresRequest.cs | 3 ++- .../Online/{API/Requests => Multiplayer}/GetRoomRequest.cs | 4 ++-- .../{API/Requests => Multiplayer}/GetRoomScoresRequest.cs | 3 ++- .../Online/{API/Requests => Multiplayer}/GetRoomsRequest.cs | 4 ++-- .../Online/{API/Requests => Multiplayer}/JoinRoomRequest.cs | 4 ++-- .../Online/{API/Requests => Multiplayer}/PartRoomRequest.cs | 4 ++-- osu.Game/Online/{API => Multiplayer}/RoomScore.cs | 4 ++-- .../{API/Requests => Multiplayer}/SubmitRoomScoreRequest.cs | 3 ++- osu.Game/Screens/Multi/Match/Components/MatchLeaderboard.cs | 1 - osu.Game/Screens/Multi/Play/TimeshiftPlayer.cs | 1 - osu.Game/Screens/Multi/Ranking/TimeshiftResultsScreen.cs | 1 - osu.Game/Screens/Multi/RoomManager.cs | 1 - 18 files changed, 23 insertions(+), 27 deletions(-) rename osu.Game/Online/{API/Requests/Responses => Multiplayer}/APICreatedRoom.cs (78%) rename osu.Game/Online/{API => Multiplayer}/APIPlaylistBeatmap.cs (94%) rename osu.Game/Online/{API/Requests/Responses => Multiplayer}/APIScoreToken.cs (85%) rename osu.Game/Online/{API/Requests => Multiplayer}/CreateRoomRequest.cs (86%) rename osu.Game/Online/{API/Requests => Multiplayer}/CreateRoomScoreRequest.cs (90%) rename osu.Game/Online/{API/Requests => Multiplayer}/GetRoomPlaylistScoresRequest.cs (92%) rename osu.Game/Online/{API/Requests => Multiplayer}/GetRoomRequest.cs (84%) rename osu.Game/Online/{API/Requests => Multiplayer}/GetRoomScoresRequest.cs (89%) rename osu.Game/Online/{API/Requests => Multiplayer}/GetRoomsRequest.cs (94%) rename osu.Game/Online/{API/Requests => Multiplayer}/JoinRoomRequest.cs (90%) rename osu.Game/Online/{API/Requests => Multiplayer}/PartRoomRequest.cs (90%) rename osu.Game/Online/{API => Multiplayer}/RoomScore.cs (97%) rename osu.Game/Online/{API/Requests => Multiplayer}/SubmitRoomScoreRequest.cs (95%) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneTimeshiftResultsScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneTimeshiftResultsScreen.cs index 9fc7c336cb..0da1e11fee 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneTimeshiftResultsScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneTimeshiftResultsScreen.cs @@ -6,7 +6,6 @@ using System.Collections.Generic; using System.Threading.Tasks; using NUnit.Framework; using osu.Game.Online.API; -using osu.Game.Online.API.Requests; using osu.Game.Online.Multiplayer; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Scoring; diff --git a/osu.Game/Online/API/Requests/Responses/APICreatedRoom.cs b/osu.Game/Online/Multiplayer/APICreatedRoom.cs similarity index 78% rename from osu.Game/Online/API/Requests/Responses/APICreatedRoom.cs rename to osu.Game/Online/Multiplayer/APICreatedRoom.cs index a554101bc7..2a3bb39647 100644 --- a/osu.Game/Online/API/Requests/Responses/APICreatedRoom.cs +++ b/osu.Game/Online/Multiplayer/APICreatedRoom.cs @@ -2,9 +2,8 @@ // See the LICENCE file in the repository root for full licence text. using Newtonsoft.Json; -using osu.Game.Online.Multiplayer; -namespace osu.Game.Online.API.Requests.Responses +namespace osu.Game.Online.Multiplayer { public class APICreatedRoom : Room { diff --git a/osu.Game/Online/API/APIPlaylistBeatmap.cs b/osu.Game/Online/Multiplayer/APIPlaylistBeatmap.cs similarity index 94% rename from osu.Game/Online/API/APIPlaylistBeatmap.cs rename to osu.Game/Online/Multiplayer/APIPlaylistBeatmap.cs index 4f7786e880..98972ef36d 100644 --- a/osu.Game/Online/API/APIPlaylistBeatmap.cs +++ b/osu.Game/Online/Multiplayer/APIPlaylistBeatmap.cs @@ -6,7 +6,7 @@ using osu.Game.Beatmaps; using osu.Game.Online.API.Requests.Responses; using osu.Game.Rulesets; -namespace osu.Game.Online.API +namespace osu.Game.Online.Multiplayer { public class APIPlaylistBeatmap : APIBeatmap { diff --git a/osu.Game/Online/API/Requests/Responses/APIScoreToken.cs b/osu.Game/Online/Multiplayer/APIScoreToken.cs similarity index 85% rename from osu.Game/Online/API/Requests/Responses/APIScoreToken.cs rename to osu.Game/Online/Multiplayer/APIScoreToken.cs index 1d2465bedf..1f0063d94e 100644 --- a/osu.Game/Online/API/Requests/Responses/APIScoreToken.cs +++ b/osu.Game/Online/Multiplayer/APIScoreToken.cs @@ -3,7 +3,7 @@ using Newtonsoft.Json; -namespace osu.Game.Online.API.Requests.Responses +namespace osu.Game.Online.Multiplayer { public class APIScoreToken { diff --git a/osu.Game/Online/API/Requests/CreateRoomRequest.cs b/osu.Game/Online/Multiplayer/CreateRoomRequest.cs similarity index 86% rename from osu.Game/Online/API/Requests/CreateRoomRequest.cs rename to osu.Game/Online/Multiplayer/CreateRoomRequest.cs index c848c55cc6..dcb4ed51ea 100644 --- a/osu.Game/Online/API/Requests/CreateRoomRequest.cs +++ b/osu.Game/Online/Multiplayer/CreateRoomRequest.cs @@ -4,10 +4,9 @@ using System.Net.Http; using Newtonsoft.Json; using osu.Framework.IO.Network; -using osu.Game.Online.API.Requests.Responses; -using osu.Game.Online.Multiplayer; +using osu.Game.Online.API; -namespace osu.Game.Online.API.Requests +namespace osu.Game.Online.Multiplayer { public class CreateRoomRequest : APIRequest { diff --git a/osu.Game/Online/API/Requests/CreateRoomScoreRequest.cs b/osu.Game/Online/Multiplayer/CreateRoomScoreRequest.cs similarity index 90% rename from osu.Game/Online/API/Requests/CreateRoomScoreRequest.cs rename to osu.Game/Online/Multiplayer/CreateRoomScoreRequest.cs index e6246b4f1f..f973f96b37 100644 --- a/osu.Game/Online/API/Requests/CreateRoomScoreRequest.cs +++ b/osu.Game/Online/Multiplayer/CreateRoomScoreRequest.cs @@ -3,9 +3,9 @@ using System.Net.Http; using osu.Framework.IO.Network; -using osu.Game.Online.API.Requests.Responses; +using osu.Game.Online.API; -namespace osu.Game.Online.API.Requests +namespace osu.Game.Online.Multiplayer { public class CreateRoomScoreRequest : APIRequest { diff --git a/osu.Game/Online/API/Requests/GetRoomPlaylistScoresRequest.cs b/osu.Game/Online/Multiplayer/GetRoomPlaylistScoresRequest.cs similarity index 92% rename from osu.Game/Online/API/Requests/GetRoomPlaylistScoresRequest.cs rename to osu.Game/Online/Multiplayer/GetRoomPlaylistScoresRequest.cs index 38f852870b..833a761f42 100644 --- a/osu.Game/Online/API/Requests/GetRoomPlaylistScoresRequest.cs +++ b/osu.Game/Online/Multiplayer/GetRoomPlaylistScoresRequest.cs @@ -3,8 +3,9 @@ using System.Collections.Generic; using Newtonsoft.Json; +using osu.Game.Online.API; -namespace osu.Game.Online.API.Requests +namespace osu.Game.Online.Multiplayer { public class GetRoomPlaylistScoresRequest : APIRequest { diff --git a/osu.Game/Online/API/Requests/GetRoomRequest.cs b/osu.Game/Online/Multiplayer/GetRoomRequest.cs similarity index 84% rename from osu.Game/Online/API/Requests/GetRoomRequest.cs rename to osu.Game/Online/Multiplayer/GetRoomRequest.cs index 531e1857de..2907b49f1d 100644 --- a/osu.Game/Online/API/Requests/GetRoomRequest.cs +++ b/osu.Game/Online/Multiplayer/GetRoomRequest.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 osu.Game.Online.Multiplayer; +using osu.Game.Online.API; -namespace osu.Game.Online.API.Requests +namespace osu.Game.Online.Multiplayer { public class GetRoomRequest : APIRequest { diff --git a/osu.Game/Online/API/Requests/GetRoomScoresRequest.cs b/osu.Game/Online/Multiplayer/GetRoomScoresRequest.cs similarity index 89% rename from osu.Game/Online/API/Requests/GetRoomScoresRequest.cs rename to osu.Game/Online/Multiplayer/GetRoomScoresRequest.cs index eb53369d18..bc913030dd 100644 --- a/osu.Game/Online/API/Requests/GetRoomScoresRequest.cs +++ b/osu.Game/Online/Multiplayer/GetRoomScoresRequest.cs @@ -2,9 +2,10 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using osu.Game.Online.API; using osu.Game.Online.API.Requests.Responses; -namespace osu.Game.Online.API.Requests +namespace osu.Game.Online.Multiplayer { public class GetRoomScoresRequest : APIRequest> { diff --git a/osu.Game/Online/API/Requests/GetRoomsRequest.cs b/osu.Game/Online/Multiplayer/GetRoomsRequest.cs similarity index 94% rename from osu.Game/Online/API/Requests/GetRoomsRequest.cs rename to osu.Game/Online/Multiplayer/GetRoomsRequest.cs index c47ed20909..64e0386f77 100644 --- a/osu.Game/Online/API/Requests/GetRoomsRequest.cs +++ b/osu.Game/Online/Multiplayer/GetRoomsRequest.cs @@ -4,10 +4,10 @@ using System.Collections.Generic; using Humanizer; using osu.Framework.IO.Network; -using osu.Game.Online.Multiplayer; +using osu.Game.Online.API; using osu.Game.Screens.Multi.Lounge.Components; -namespace osu.Game.Online.API.Requests +namespace osu.Game.Online.Multiplayer { public class GetRoomsRequest : APIRequest> { diff --git a/osu.Game/Online/API/Requests/JoinRoomRequest.cs b/osu.Game/Online/Multiplayer/JoinRoomRequest.cs similarity index 90% rename from osu.Game/Online/API/Requests/JoinRoomRequest.cs rename to osu.Game/Online/Multiplayer/JoinRoomRequest.cs index b0808afa45..74375af856 100644 --- a/osu.Game/Online/API/Requests/JoinRoomRequest.cs +++ b/osu.Game/Online/Multiplayer/JoinRoomRequest.cs @@ -3,9 +3,9 @@ using System.Net.Http; using osu.Framework.IO.Network; -using osu.Game.Online.Multiplayer; +using osu.Game.Online.API; -namespace osu.Game.Online.API.Requests +namespace osu.Game.Online.Multiplayer { public class JoinRoomRequest : APIRequest { diff --git a/osu.Game/Online/API/Requests/PartRoomRequest.cs b/osu.Game/Online/Multiplayer/PartRoomRequest.cs similarity index 90% rename from osu.Game/Online/API/Requests/PartRoomRequest.cs rename to osu.Game/Online/Multiplayer/PartRoomRequest.cs index c988cd5c9e..54bb005d96 100644 --- a/osu.Game/Online/API/Requests/PartRoomRequest.cs +++ b/osu.Game/Online/Multiplayer/PartRoomRequest.cs @@ -3,9 +3,9 @@ using System.Net.Http; using osu.Framework.IO.Network; -using osu.Game.Online.Multiplayer; +using osu.Game.Online.API; -namespace osu.Game.Online.API.Requests +namespace osu.Game.Online.Multiplayer { public class PartRoomRequest : APIRequest { diff --git a/osu.Game/Online/API/RoomScore.cs b/osu.Game/Online/Multiplayer/RoomScore.cs similarity index 97% rename from osu.Game/Online/API/RoomScore.cs rename to osu.Game/Online/Multiplayer/RoomScore.cs index 3c7f8c9833..97f378856f 100644 --- a/osu.Game/Online/API/RoomScore.cs +++ b/osu.Game/Online/Multiplayer/RoomScore.cs @@ -6,13 +6,13 @@ using System.Collections.Generic; using System.Linq; using Newtonsoft.Json; using Newtonsoft.Json.Converters; -using osu.Game.Online.Multiplayer; +using osu.Game.Online.API; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; using osu.Game.Users; -namespace osu.Game.Online.API +namespace osu.Game.Online.Multiplayer { public class RoomScore { diff --git a/osu.Game/Online/API/Requests/SubmitRoomScoreRequest.cs b/osu.Game/Online/Multiplayer/SubmitRoomScoreRequest.cs similarity index 95% rename from osu.Game/Online/API/Requests/SubmitRoomScoreRequest.cs rename to osu.Game/Online/Multiplayer/SubmitRoomScoreRequest.cs index 8eb2952159..f725ea5dc9 100644 --- a/osu.Game/Online/API/Requests/SubmitRoomScoreRequest.cs +++ b/osu.Game/Online/Multiplayer/SubmitRoomScoreRequest.cs @@ -4,9 +4,10 @@ using System.Net.Http; using Newtonsoft.Json; using osu.Framework.IO.Network; +using osu.Game.Online.API; using osu.Game.Scoring; -namespace osu.Game.Online.API.Requests +namespace osu.Game.Online.Multiplayer { public class SubmitRoomScoreRequest : APIRequest { diff --git a/osu.Game/Screens/Multi/Match/Components/MatchLeaderboard.cs b/osu.Game/Screens/Multi/Match/Components/MatchLeaderboard.cs index 571bbde716..1afbf5c32a 100644 --- a/osu.Game/Screens/Multi/Match/Components/MatchLeaderboard.cs +++ b/osu.Game/Screens/Multi/Match/Components/MatchLeaderboard.cs @@ -6,7 +6,6 @@ using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Bindables; 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.Online.Multiplayer; diff --git a/osu.Game/Screens/Multi/Play/TimeshiftPlayer.cs b/osu.Game/Screens/Multi/Play/TimeshiftPlayer.cs index cf0197d26b..c2381fe219 100644 --- a/osu.Game/Screens/Multi/Play/TimeshiftPlayer.cs +++ b/osu.Game/Screens/Multi/Play/TimeshiftPlayer.cs @@ -10,7 +10,6 @@ using osu.Framework.Bindables; using osu.Framework.Logging; using osu.Framework.Screens; using osu.Game.Online.API; -using osu.Game.Online.API.Requests; using osu.Game.Online.Multiplayer; using osu.Game.Rulesets; using osu.Game.Scoring; diff --git a/osu.Game/Screens/Multi/Ranking/TimeshiftResultsScreen.cs b/osu.Game/Screens/Multi/Ranking/TimeshiftResultsScreen.cs index 5cafc974f1..f367d44347 100644 --- a/osu.Game/Screens/Multi/Ranking/TimeshiftResultsScreen.cs +++ b/osu.Game/Screens/Multi/Ranking/TimeshiftResultsScreen.cs @@ -9,7 +9,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osu.Game.Online.API; -using osu.Game.Online.API.Requests; using osu.Game.Online.Multiplayer; using osu.Game.Scoring; using osu.Game.Screens.Ranking; diff --git a/osu.Game/Screens/Multi/RoomManager.cs b/osu.Game/Screens/Multi/RoomManager.cs index 491be2e946..2a96fa536d 100644 --- a/osu.Game/Screens/Multi/RoomManager.cs +++ b/osu.Game/Screens/Multi/RoomManager.cs @@ -14,7 +14,6 @@ using osu.Framework.Logging; using osu.Game.Beatmaps; using osu.Game.Online; using osu.Game.Online.API; -using osu.Game.Online.API.Requests; using osu.Game.Online.Multiplayer; using osu.Game.Rulesets; using osu.Game.Screens.Multi.Lounge.Components; From e423630b7cbec15c0457089513ff0af85822591b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 22 Jul 2020 18:37:00 +0900 Subject: [PATCH 0188/1782] Rename RoomScore -> MultiplayerScore --- .../Visual/Multiplayer/TestSceneTimeshiftResultsScreen.cs | 4 ++-- osu.Game/Online/Multiplayer/GetRoomPlaylistScoresRequest.cs | 2 +- .../Online/Multiplayer/{RoomScore.cs => MultiplayerScore.cs} | 2 +- osu.Game/Online/Multiplayer/SubmitRoomScoreRequest.cs | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) rename osu.Game/Online/Multiplayer/{RoomScore.cs => MultiplayerScore.cs} (98%) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneTimeshiftResultsScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneTimeshiftResultsScreen.cs index 0da1e11fee..0023866124 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneTimeshiftResultsScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneTimeshiftResultsScreen.cs @@ -64,11 +64,11 @@ namespace osu.Game.Tests.Visual.Multiplayer private void bindHandler(double delay = 0) { - var roomScores = new List(); + var roomScores = new List(); for (int i = 0; i < 10; i++) { - roomScores.Add(new RoomScore + roomScores.Add(new MultiplayerScore { ID = i, Accuracy = 0.9 - 0.01 * i, diff --git a/osu.Game/Online/Multiplayer/GetRoomPlaylistScoresRequest.cs b/osu.Game/Online/Multiplayer/GetRoomPlaylistScoresRequest.cs index 833a761f42..3d3bd20ff3 100644 --- a/osu.Game/Online/Multiplayer/GetRoomPlaylistScoresRequest.cs +++ b/osu.Game/Online/Multiplayer/GetRoomPlaylistScoresRequest.cs @@ -24,6 +24,6 @@ namespace osu.Game.Online.Multiplayer public class RoomPlaylistScores { [JsonProperty("scores")] - public List Scores { get; set; } + public List Scores { get; set; } } } diff --git a/osu.Game/Online/Multiplayer/RoomScore.cs b/osu.Game/Online/Multiplayer/MultiplayerScore.cs similarity index 98% rename from osu.Game/Online/Multiplayer/RoomScore.cs rename to osu.Game/Online/Multiplayer/MultiplayerScore.cs index 97f378856f..3bbf19b11f 100644 --- a/osu.Game/Online/Multiplayer/RoomScore.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerScore.cs @@ -14,7 +14,7 @@ using osu.Game.Users; namespace osu.Game.Online.Multiplayer { - public class RoomScore + public class MultiplayerScore { [JsonProperty("id")] public int ID { get; set; } diff --git a/osu.Game/Online/Multiplayer/SubmitRoomScoreRequest.cs b/osu.Game/Online/Multiplayer/SubmitRoomScoreRequest.cs index f725ea5dc9..d31aef2ea5 100644 --- a/osu.Game/Online/Multiplayer/SubmitRoomScoreRequest.cs +++ b/osu.Game/Online/Multiplayer/SubmitRoomScoreRequest.cs @@ -9,7 +9,7 @@ using osu.Game.Scoring; namespace osu.Game.Online.Multiplayer { - public class SubmitRoomScoreRequest : APIRequest + public class SubmitRoomScoreRequest : APIRequest { private readonly int scoreId; private readonly int roomId; From d9633fee64a270364f618fc415d5d48d93a808e5 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 22 Jul 2020 18:47:09 +0900 Subject: [PATCH 0189/1782] Rename request --- .../Visual/Multiplayer/TestSceneTimeshiftResultsScreen.cs | 2 +- ...PlaylistScoresRequest.cs => IndexPlaylistScoresRequest.cs} | 4 ++-- osu.Game/Screens/Multi/Ranking/TimeshiftResultsScreen.cs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) rename osu.Game/Online/Multiplayer/{GetRoomPlaylistScoresRequest.cs => IndexPlaylistScoresRequest.cs} (82%) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneTimeshiftResultsScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneTimeshiftResultsScreen.cs index 0023866124..37d31264b6 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneTimeshiftResultsScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneTimeshiftResultsScreen.cs @@ -97,7 +97,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { switch (request) { - case GetRoomPlaylistScoresRequest r: + case IndexPlaylistScoresRequest r: if (delay == 0) success(); else diff --git a/osu.Game/Online/Multiplayer/GetRoomPlaylistScoresRequest.cs b/osu.Game/Online/Multiplayer/IndexPlaylistScoresRequest.cs similarity index 82% rename from osu.Game/Online/Multiplayer/GetRoomPlaylistScoresRequest.cs rename to osu.Game/Online/Multiplayer/IndexPlaylistScoresRequest.cs index 3d3bd20ff3..c435dc6030 100644 --- a/osu.Game/Online/Multiplayer/GetRoomPlaylistScoresRequest.cs +++ b/osu.Game/Online/Multiplayer/IndexPlaylistScoresRequest.cs @@ -7,12 +7,12 @@ using osu.Game.Online.API; namespace osu.Game.Online.Multiplayer { - public class GetRoomPlaylistScoresRequest : APIRequest + public class IndexPlaylistScoresRequest : APIRequest { private readonly int roomId; private readonly int playlistItemId; - public GetRoomPlaylistScoresRequest(int roomId, int playlistItemId) + public IndexPlaylistScoresRequest(int roomId, int playlistItemId) { this.roomId = roomId; this.playlistItemId = playlistItemId; diff --git a/osu.Game/Screens/Multi/Ranking/TimeshiftResultsScreen.cs b/osu.Game/Screens/Multi/Ranking/TimeshiftResultsScreen.cs index f367d44347..b90c7252c4 100644 --- a/osu.Game/Screens/Multi/Ranking/TimeshiftResultsScreen.cs +++ b/osu.Game/Screens/Multi/Ranking/TimeshiftResultsScreen.cs @@ -44,7 +44,7 @@ namespace osu.Game.Screens.Multi.Ranking protected override APIRequest FetchScores(Action> scoresCallback) { - var req = new GetRoomPlaylistScoresRequest(roomId, playlistItem.ID); + var req = new IndexPlaylistScoresRequest(roomId, playlistItem.ID); req.Success += r => { From ec33a6ea8791b6878912b26dd9209063a45f5719 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 22 Jul 2020 18:47:40 +0900 Subject: [PATCH 0190/1782] Add additional responses --- .../Online/Multiplayer/MultiplayerScores.cs | 39 +++++++++++++++++++ .../Multiplayer/MultiplayerScoresAround.cs | 25 ++++++++++++ .../Multiplayer/MultiplayerScoresSort.cs | 14 +++++++ 3 files changed, 78 insertions(+) create mode 100644 osu.Game/Online/Multiplayer/MultiplayerScores.cs create mode 100644 osu.Game/Online/Multiplayer/MultiplayerScoresAround.cs create mode 100644 osu.Game/Online/Multiplayer/MultiplayerScoresSort.cs diff --git a/osu.Game/Online/Multiplayer/MultiplayerScores.cs b/osu.Game/Online/Multiplayer/MultiplayerScores.cs new file mode 100644 index 0000000000..f944a8999c --- /dev/null +++ b/osu.Game/Online/Multiplayer/MultiplayerScores.cs @@ -0,0 +1,39 @@ +// 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; + +namespace osu.Game.Online.Multiplayer +{ + /// + /// An object which contains scores and related data for fetching next pages. + /// + public class MultiplayerScores + { + /// + /// To be used for fetching the next page. + /// + [JsonProperty("cursor")] + public Cursor Cursor { get; set; } + + /// + /// The scores. + /// + [JsonProperty("scores")] + public List Scores { get; set; } + + /// + /// The total scores in the playlist item. Only provided via . + /// + [JsonProperty("total")] + public int? TotalScores { get; set; } + + /// + /// The user's score, if any. Only provided via . + /// + [JsonProperty("user_score")] + public MultiplayerScore UserScore { get; set; } + } +} diff --git a/osu.Game/Online/Multiplayer/MultiplayerScoresAround.cs b/osu.Game/Online/Multiplayer/MultiplayerScoresAround.cs new file mode 100644 index 0000000000..e83cc1b753 --- /dev/null +++ b/osu.Game/Online/Multiplayer/MultiplayerScoresAround.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.Online.Multiplayer +{ + /// + /// An object which stores scores higher and lower than the user's score. + /// + public class MultiplayerScoresAround + { + /// + /// Scores sorted "higher" than the user's score, depending on the sorting order. + /// + [JsonProperty("higher")] + public MultiplayerScores Higher { get; set; } + + /// + /// Scores sorted "lower" than the user's score, depending on the sorting order. + /// + [JsonProperty("lower")] + public MultiplayerScores Lower { get; set; } + } +} diff --git a/osu.Game/Online/Multiplayer/MultiplayerScoresSort.cs b/osu.Game/Online/Multiplayer/MultiplayerScoresSort.cs new file mode 100644 index 0000000000..decb1c4dfe --- /dev/null +++ b/osu.Game/Online/Multiplayer/MultiplayerScoresSort.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.Online.Multiplayer +{ + /// + /// Sorting option for indexing multiplayer scores. + /// + public enum MultiplayerScoresSort + { + Ascending, + Descending + } +} From 634efe31f843f53a8a86ae01319705b748398449 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 22 Jul 2020 18:51:54 +0900 Subject: [PATCH 0191/1782] Inherit ResponseWithCursor --- osu.Game/Online/Multiplayer/MultiplayerScores.cs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/osu.Game/Online/Multiplayer/MultiplayerScores.cs b/osu.Game/Online/Multiplayer/MultiplayerScores.cs index f944a8999c..6f74fc8984 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerScores.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerScores.cs @@ -10,14 +10,8 @@ namespace osu.Game.Online.Multiplayer /// /// An object which contains scores and related data for fetching next pages. /// - public class MultiplayerScores + public class MultiplayerScores : ResponseWithCursor { - /// - /// To be used for fetching the next page. - /// - [JsonProperty("cursor")] - public Cursor Cursor { get; set; } - /// /// The scores. /// From c75955e3819f5b7c8ec3a77af1bce267f2008633 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 22 Jul 2020 18:52:25 +0900 Subject: [PATCH 0192/1782] Add responses to MultiplayerScore --- osu.Game/Online/Multiplayer/MultiplayerScore.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/osu.Game/Online/Multiplayer/MultiplayerScore.cs b/osu.Game/Online/Multiplayer/MultiplayerScore.cs index 3bbf19b11f..1793ba72ef 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerScore.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerScore.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using JetBrains.Annotations; using Newtonsoft.Json; using Newtonsoft.Json.Converters; using osu.Game.Online.API; @@ -47,6 +48,19 @@ namespace osu.Game.Online.Multiplayer [JsonProperty("ended_at")] public DateTimeOffset EndedAt { get; set; } + /// + /// The position of this score, starting at 1. + /// + [JsonProperty("position")] + public int? Position { get; set; } + + /// + /// Any scores in the room around this score. + /// + [JsonProperty("scores_around")] + [CanBeNull] + public MultiplayerScoresAround ScoresAround { get; set; } + public ScoreInfo CreateScoreInfo(PlaylistItem playlistItem) { var rulesetInstance = playlistItem.Ruleset.Value.CreateInstance(); From 334fb7d4753386c6d534efe27e80e421a3b8a94f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 22 Jul 2020 18:54:41 +0900 Subject: [PATCH 0193/1782] Add additional params to index request --- .../Multiplayer/IndexPlaylistScoresRequest.cs | 32 ++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/Multiplayer/IndexPlaylistScoresRequest.cs b/osu.Game/Online/Multiplayer/IndexPlaylistScoresRequest.cs index c435dc6030..b43614bf6c 100644 --- a/osu.Game/Online/Multiplayer/IndexPlaylistScoresRequest.cs +++ b/osu.Game/Online/Multiplayer/IndexPlaylistScoresRequest.cs @@ -3,19 +3,49 @@ using System.Collections.Generic; using Newtonsoft.Json; +using osu.Framework.IO.Network; +using osu.Game.Extensions; using osu.Game.Online.API; +using osu.Game.Online.API.Requests; namespace osu.Game.Online.Multiplayer { + /// + /// Returns a list of scores for the specified playlist item. + /// public class IndexPlaylistScoresRequest : APIRequest { private readonly int roomId; private readonly int playlistItemId; + private readonly Cursor cursor; + private readonly MultiplayerScoresSort? sort; - public IndexPlaylistScoresRequest(int roomId, int playlistItemId) + public IndexPlaylistScoresRequest(int roomId, int playlistItemId, Cursor cursor = null, MultiplayerScoresSort? sort = null) { this.roomId = roomId; this.playlistItemId = playlistItemId; + this.cursor = cursor; + this.sort = sort; + } + + protected override WebRequest CreateWebRequest() + { + var req = base.CreateWebRequest(); + + req.AddCursor(cursor); + + switch (sort) + { + case MultiplayerScoresSort.Ascending: + req.AddParameter("sort", "scores_asc"); + break; + + case MultiplayerScoresSort.Descending: + req.AddParameter("sort", "scores_desc"); + break; + } + + return req; } protected override string Target => $@"rooms/{roomId}/playlist/{playlistItemId}/scores"; From 53a9ac3c1aa160bd1ccd8a23aa3833bd5f014e9b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 22 Jul 2020 19:06:39 +0900 Subject: [PATCH 0194/1782] Fix slider ball rotation being applied to follow circle and specular layer --- .../Objects/Drawables/Pieces/SliderBall.cs | 18 ++++-------- .../Skinning/LegacySliderBall.cs | 28 +++++++++++++++++-- 2 files changed, 30 insertions(+), 16 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs index 395c76a233..b87e112d10 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs @@ -33,7 +33,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces private readonly Slider slider; private readonly Drawable followCircle; private readonly DrawableSlider drawableSlider; - private readonly CircularContainer ball; + private readonly Drawable ball; public SliderBall(Slider slider, DrawableSlider drawableSlider = null) { @@ -54,19 +54,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces Alpha = 0, Child = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SliderFollowCircle), _ => new DefaultFollowCircle()), }, - ball = new CircularContainer + ball = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SliderBall), _ => new DefaultSliderBall()) { - Masking = true, - RelativeSizeAxes = Axes.Both, - Origin = Anchor.Centre, Anchor = Anchor.Centre, - Alpha = 1, - Child = new Container - { - RelativeSizeAxes = Axes.Both, - Child = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SliderBall), _ => new DefaultSliderBall()), - } - } + Origin = Anchor.Centre, + }, }; } @@ -187,7 +179,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces return; Position = newPos; - Rotation = -90 + (float)(-Math.Atan2(diff.X, diff.Y) * 180 / Math.PI); + ball.Rotation = -90 + (float)(-Math.Atan2(diff.X, diff.Y) * 180 / Math.PI); lastPosition = newPos; } diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacySliderBall.cs b/osu.Game.Rulesets.Osu/Skinning/LegacySliderBall.cs index b4ed75d97c..0f586034d5 100644 --- a/osu.Game.Rulesets.Osu/Skinning/LegacySliderBall.cs +++ b/osu.Game.Rulesets.Osu/Skinning/LegacySliderBall.cs @@ -15,6 +15,9 @@ namespace osu.Game.Rulesets.Osu.Skinning { private readonly Drawable animationContent; + private Sprite layerNd; + private Sprite layerSpec; + public LegacySliderBall(Drawable animationContent) { this.animationContent = animationContent; @@ -29,18 +32,37 @@ namespace osu.Game.Rulesets.Osu.Skinning InternalChildren = new[] { - new Sprite + layerNd = new Sprite { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, Texture = skin.GetTexture("sliderb-nd"), Colour = new Color4(5, 5, 5, 255), }, - animationContent, - new Sprite + animationContent.With(d => { + d.Anchor = Anchor.Centre; + d.Origin = Anchor.Centre; + }), + layerSpec = new Sprite + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, Texture = skin.GetTexture("sliderb-spec"), Blending = BlendingParameters.Additive, }, }; } + + protected override void UpdateAfterChildren() + { + base.UpdateAfterChildren(); + + //undo rotation on layers which should not be rotated. + float appliedRotation = Parent.Rotation; + + layerNd.Rotation = -appliedRotation; + layerSpec.Rotation = -appliedRotation; + } } } From bd6a51f545a5121d98e57f3e8894094d3cb1e738 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 22 Jul 2020 19:30:10 +0900 Subject: [PATCH 0195/1782] Hide slider repeat judgements temporarily --- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs index 720ffcd51c..d79ecb7b4e 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs @@ -23,6 +23,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables private readonly Drawable scaleContainer; + public override bool DisplayResult => false; + public DrawableSliderRepeat(SliderRepeat sliderRepeat, DrawableSlider drawableSlider) : base(sliderRepeat) { From f8401a76a25a59706226eee625dc479d13116c10 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 22 Jul 2020 19:40:00 +0900 Subject: [PATCH 0196/1782] Use show/index requests in results screen --- .../ShowPlaylistUserScoreRequest.cs | 23 +++++++ .../Multi/Ranking/TimeshiftResultsScreen.cs | 63 +++++++++++++++++-- 2 files changed, 80 insertions(+), 6 deletions(-) create mode 100644 osu.Game/Online/Multiplayer/ShowPlaylistUserScoreRequest.cs diff --git a/osu.Game/Online/Multiplayer/ShowPlaylistUserScoreRequest.cs b/osu.Game/Online/Multiplayer/ShowPlaylistUserScoreRequest.cs new file mode 100644 index 0000000000..936b8bbe89 --- /dev/null +++ b/osu.Game/Online/Multiplayer/ShowPlaylistUserScoreRequest.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.Game.Online.API; + +namespace osu.Game.Online.Multiplayer +{ + public class ShowPlaylistUserScoreRequest : APIRequest + { + private readonly int roomId; + private readonly int playlistItemId; + private readonly long userId; + + public ShowPlaylistUserScoreRequest(int roomId, int playlistItemId, long userId) + { + this.roomId = roomId; + this.playlistItemId = playlistItemId; + this.userId = userId; + } + + protected override string Target => $"rooms/{roomId}/playlist/{playlistItemId}/scores/users/{userId}"; + } +} diff --git a/osu.Game/Screens/Multi/Ranking/TimeshiftResultsScreen.cs b/osu.Game/Screens/Multi/Ranking/TimeshiftResultsScreen.cs index b90c7252c4..47aab02b1a 100644 --- a/osu.Game/Screens/Multi/Ranking/TimeshiftResultsScreen.cs +++ b/osu.Game/Screens/Multi/Ranking/TimeshiftResultsScreen.cs @@ -9,6 +9,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osu.Game.Online.API; +using osu.Game.Online.API.Requests; using osu.Game.Online.Multiplayer; using osu.Game.Scoring; using osu.Game.Screens.Ranking; @@ -21,6 +22,11 @@ namespace osu.Game.Screens.Multi.Ranking private readonly PlaylistItem playlistItem; private LoadingSpinner loadingLayer; + private Cursor higherScoresCursor; + private Cursor lowerScoresCursor; + + [Resolved] + private IAPIProvider api { get; set; } public TimeshiftResultsScreen(ScoreInfo score, int roomId, PlaylistItem playlistItem, bool allowRetry = true) : base(score, allowRetry) @@ -44,17 +50,62 @@ namespace osu.Game.Screens.Multi.Ranking protected override APIRequest FetchScores(Action> scoresCallback) { - var req = new IndexPlaylistScoresRequest(roomId, playlistItem.ID); + // This performs two requests: + // 1. A request to show the user's score. + // 2. If (1) fails, a request to index the room. - req.Success += r => + var userScoreReq = new ShowPlaylistUserScoreRequest(roomId, playlistItem.ID, api.LocalUser.Value.Id); + + userScoreReq.Success += userScore => { - scoresCallback?.Invoke(r.Scores.Where(s => s.ID != Score?.OnlineScoreID).Select(s => s.CreateScoreInfo(playlistItem))); - loadingLayer.Hide(); + var allScores = new List { userScore }; + + if (userScore.ScoresAround?.Higher != null) + { + allScores.AddRange(userScore.ScoresAround.Higher.Scores); + higherScoresCursor = userScore.ScoresAround.Higher.Cursor; + } + + if (userScore.ScoresAround?.Lower != null) + { + allScores.AddRange(userScore.ScoresAround.Lower.Scores); + lowerScoresCursor = userScore.ScoresAround.Lower.Cursor; + } + + success(allScores); }; - req.Failure += _ => loadingLayer.Hide(); + userScoreReq.Failure += _ => + { + // Fallback to a normal index. + var indexReq = new IndexPlaylistScoresRequest(roomId, playlistItem.ID); + indexReq.Success += r => success(r.Scores); + indexReq.Failure += __ => loadingLayer.Hide(); + api.Queue(indexReq); + }; - return req; + return userScoreReq; + + void success(List scores) + { + var scoreInfos = new List(scores.Select(s => s.CreateScoreInfo(playlistItem))); + + // Select a score if we don't already have one selected. + // Note: This is done before the callback so that the panel list centres on the selected score before panels are added (eliminating initial scroll). + if (SelectedScore.Value == null) + { + Schedule(() => + { + // Prefer selecting the local user's score, or otherwise default to the first visible score. + SelectedScore.Value = scoreInfos.FirstOrDefault(s => s.User.Id == api.LocalUser.Value.Id) ?? scoreInfos.FirstOrDefault(); + }); + } + + // Invoke callback to add the scores. Exclude the user's current score which was added previously. + scoresCallback?.Invoke(scoreInfos.Where(s => s.ID != Score?.OnlineScoreID)); + + loadingLayer.Hide(); + } } } } From 798bf0503818856b48f150b4598c2e01f340fdaf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 22 Jul 2020 19:43:48 +0900 Subject: [PATCH 0197/1782] 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 71d4e5aacf..c0c75b8d71 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,6 +52,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 2f3d08c528..e8c333b6b1 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -24,7 +24,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 2bb3914c25..8d1b837995 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -80,7 +80,7 @@ - + From 2c62b23d859d46b1e4f3c21ea18e8c52a910b9c0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 22 Jul 2020 19:53:45 +0900 Subject: [PATCH 0198/1782] Update naming --- .../Replays/CatchFramedReplayInputHandler.cs | 4 ++-- .../Replays/ManiaFramedReplayInputHandler.cs | 4 ++-- .../Replays/OsuFramedReplayInputHandler.cs | 15 +++------------ .../Replays/TaikoFramedReplayInputHandler.cs | 4 ++-- .../Visual/Gameplay/TestSceneReplayRecorder.cs | 2 +- .../Visual/Gameplay/TestSceneReplayRecording.cs | 2 +- 6 files changed, 11 insertions(+), 20 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Replays/CatchFramedReplayInputHandler.cs b/osu.Game.Rulesets.Catch/Replays/CatchFramedReplayInputHandler.cs index 24c21fbc84..99d899db80 100644 --- a/osu.Game.Rulesets.Catch/Replays/CatchFramedReplayInputHandler.cs +++ b/osu.Game.Rulesets.Catch/Replays/CatchFramedReplayInputHandler.cs @@ -35,11 +35,11 @@ namespace osu.Game.Rulesets.Catch.Replays } } - public override void GetPendingInputs(List input) + public override void CollectPendingInputs(List inputs) { if (!Position.HasValue) return; - input.Add(new CatchReplayState + inputs.Add(new CatchReplayState { PressedActions = CurrentFrame?.Actions ?? new List(), CatcherX = Position.Value diff --git a/osu.Game.Rulesets.Mania/Replays/ManiaFramedReplayInputHandler.cs b/osu.Game.Rulesets.Mania/Replays/ManiaFramedReplayInputHandler.cs index 26c4ccf289..aa0c148caf 100644 --- a/osu.Game.Rulesets.Mania/Replays/ManiaFramedReplayInputHandler.cs +++ b/osu.Game.Rulesets.Mania/Replays/ManiaFramedReplayInputHandler.cs @@ -18,9 +18,9 @@ namespace osu.Game.Rulesets.Mania.Replays protected override bool IsImportant(ManiaReplayFrame frame) => frame.Actions.Any(); - public override void GetPendingInputs(List input) + public override void CollectPendingInputs(List inputs) { - input.Add(new ReplayState { PressedActions = CurrentFrame?.Actions ?? new List() }); + inputs.Add(new ReplayState { PressedActions = CurrentFrame?.Actions ?? new List() }); } } } diff --git a/osu.Game.Rulesets.Osu/Replays/OsuFramedReplayInputHandler.cs b/osu.Game.Rulesets.Osu/Replays/OsuFramedReplayInputHandler.cs index 5c803539c2..cf48dc053f 100644 --- a/osu.Game.Rulesets.Osu/Replays/OsuFramedReplayInputHandler.cs +++ b/osu.Game.Rulesets.Osu/Replays/OsuFramedReplayInputHandler.cs @@ -36,19 +36,10 @@ namespace osu.Game.Rulesets.Osu.Replays } } - public override void GetPendingInputs(List input) + public override void CollectPendingInputs(List inputs) { - input.Add( - new MousePositionAbsoluteInput - { - Position = GamefieldToScreenSpace(Position ?? Vector2.Zero) - }); - input.Add( - new ReplayState - { - PressedActions = CurrentFrame?.Actions ?? new List() - }); - ; + inputs.Add(new MousePositionAbsoluteInput { Position = GamefieldToScreenSpace(Position ?? Vector2.Zero) }); + inputs.Add(new ReplayState { PressedActions = CurrentFrame?.Actions ?? new List() }); } } } diff --git a/osu.Game.Rulesets.Taiko/Replays/TaikoFramedReplayInputHandler.cs b/osu.Game.Rulesets.Taiko/Replays/TaikoFramedReplayInputHandler.cs index 7361d4efa8..138e8f9785 100644 --- a/osu.Game.Rulesets.Taiko/Replays/TaikoFramedReplayInputHandler.cs +++ b/osu.Game.Rulesets.Taiko/Replays/TaikoFramedReplayInputHandler.cs @@ -18,9 +18,9 @@ namespace osu.Game.Rulesets.Taiko.Replays protected override bool IsImportant(TaikoReplayFrame frame) => frame.Actions.Any(); - public override void GetPendingInputs(List input) + public override void CollectPendingInputs(List inputs) { - input.Add(new ReplayState { PressedActions = CurrentFrame?.Actions ?? new List() }); + inputs.Add(new ReplayState { PressedActions = CurrentFrame?.Actions ?? new List() }); } } } diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs index e473f49826..bc1c10e59d 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs @@ -173,7 +173,7 @@ namespace osu.Game.Tests.Visual.Gameplay { } - public override void GetPendingInputs(List inputs) + public override void CollectPendingInputs(List inputs) { inputs.Add(new MousePositionAbsoluteInput { Position = GamefieldToScreenSpace(CurrentFrame?.Position ?? Vector2.Zero) }); inputs.Add(new ReplayState { PressedActions = CurrentFrame?.Actions ?? new List() }); diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecording.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecording.cs index e891ed617a..c0f99db85d 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecording.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecording.cs @@ -113,7 +113,7 @@ namespace osu.Game.Tests.Visual.Gameplay { } - public override void GetPendingInputs(List inputs) + public override void CollectPendingInputs(List inputs) { inputs.Add(new MousePositionAbsoluteInput { Position = GamefieldToScreenSpace(CurrentFrame?.Position ?? Vector2.Zero) }); inputs.Add(new ReplayState { PressedActions = CurrentFrame?.Actions ?? new List() }); From 568fb51ce239c8f2fbe26ab63d83ea1a508bb65e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 22 Jul 2020 20:24:42 +0900 Subject: [PATCH 0199/1782] Remove RoomPlaylistScores intermediate class --- .../Multiplayer/TestSceneTimeshiftResultsScreen.cs | 2 +- .../Online/Multiplayer/IndexPlaylistScoresRequest.cs | 10 +--------- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneTimeshiftResultsScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneTimeshiftResultsScreen.cs index 37d31264b6..44ca676c4f 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneTimeshiftResultsScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneTimeshiftResultsScreen.cs @@ -111,7 +111,7 @@ namespace osu.Game.Tests.Visual.Multiplayer void success() { - r.TriggerSuccess(new RoomPlaylistScores { Scores = roomScores }); + r.TriggerSuccess(new MultiplayerScores { Scores = roomScores }); roomsReceived = true; } diff --git a/osu.Game/Online/Multiplayer/IndexPlaylistScoresRequest.cs b/osu.Game/Online/Multiplayer/IndexPlaylistScoresRequest.cs index b43614bf6c..d23208d338 100644 --- a/osu.Game/Online/Multiplayer/IndexPlaylistScoresRequest.cs +++ b/osu.Game/Online/Multiplayer/IndexPlaylistScoresRequest.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 Newtonsoft.Json; using osu.Framework.IO.Network; using osu.Game.Extensions; using osu.Game.Online.API; @@ -13,7 +11,7 @@ namespace osu.Game.Online.Multiplayer /// /// Returns a list of scores for the specified playlist item. /// - public class IndexPlaylistScoresRequest : APIRequest + public class IndexPlaylistScoresRequest : APIRequest { private readonly int roomId; private readonly int playlistItemId; @@ -50,10 +48,4 @@ namespace osu.Game.Online.Multiplayer protected override string Target => $@"rooms/{roomId}/playlist/{playlistItemId}/scores"; } - - public class RoomPlaylistScores - { - [JsonProperty("scores")] - public List Scores { get; set; } - } } From b7790de66fbc2d11126fb0c0dadf17090c647aa0 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 22 Jul 2020 20:24:48 +0900 Subject: [PATCH 0200/1782] Fix incorrect sort param --- osu.Game/Online/Multiplayer/IndexPlaylistScoresRequest.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Online/Multiplayer/IndexPlaylistScoresRequest.cs b/osu.Game/Online/Multiplayer/IndexPlaylistScoresRequest.cs index d23208d338..7273c0eea6 100644 --- a/osu.Game/Online/Multiplayer/IndexPlaylistScoresRequest.cs +++ b/osu.Game/Online/Multiplayer/IndexPlaylistScoresRequest.cs @@ -35,11 +35,11 @@ namespace osu.Game.Online.Multiplayer switch (sort) { case MultiplayerScoresSort.Ascending: - req.AddParameter("sort", "scores_asc"); + req.AddParameter("sort", "score_asc"); break; case MultiplayerScoresSort.Descending: - req.AddParameter("sort", "scores_desc"); + req.AddParameter("sort", "score_desc"); break; } From 46ea775cfb36d15a3dc7a13098bd62c18cbb7987 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 22 Jul 2020 20:24:55 +0900 Subject: [PATCH 0201/1782] Implement paging --- .../Multi/Ranking/TimeshiftResultsScreen.cs | 84 +++++++++++++++---- osu.Game/Screens/Ranking/ResultsScreen.cs | 41 +++++++-- osu.Game/Screens/Ranking/ScorePanelList.cs | 4 + 3 files changed, 109 insertions(+), 20 deletions(-) diff --git a/osu.Game/Screens/Multi/Ranking/TimeshiftResultsScreen.cs b/osu.Game/Screens/Multi/Ranking/TimeshiftResultsScreen.cs index 47aab02b1a..75a61b92ee 100644 --- a/osu.Game/Screens/Multi/Ranking/TimeshiftResultsScreen.cs +++ b/osu.Game/Screens/Multi/Ranking/TimeshiftResultsScreen.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; @@ -72,40 +73,93 @@ namespace osu.Game.Screens.Multi.Ranking lowerScoresCursor = userScore.ScoresAround.Lower.Cursor; } - success(allScores); + performSuccessCallback(scoresCallback, allScores); }; userScoreReq.Failure += _ => { // Fallback to a normal index. var indexReq = new IndexPlaylistScoresRequest(roomId, playlistItem.ID); - indexReq.Success += r => success(r.Scores); + + indexReq.Success += r => + { + performSuccessCallback(scoresCallback, r.Scores); + lowerScoresCursor = r.Cursor; + }; + indexReq.Failure += __ => loadingLayer.Hide(); + api.Queue(indexReq); }; return userScoreReq; + } - void success(List scores) + protected override APIRequest FetchNextPage(int direction, Action> scoresCallback) + { + Debug.Assert(direction == 1 || direction == -1); + + Cursor cursor; + MultiplayerScoresSort sort; + + switch (direction) { - var scoreInfos = new List(scores.Select(s => s.CreateScoreInfo(playlistItem))); + case -1: + cursor = higherScoresCursor; + sort = MultiplayerScoresSort.Ascending; + break; - // Select a score if we don't already have one selected. - // Note: This is done before the callback so that the panel list centres on the selected score before panels are added (eliminating initial scroll). - if (SelectedScore.Value == null) + default: + cursor = lowerScoresCursor; + sort = MultiplayerScoresSort.Descending; + break; + } + + if (cursor == null) + return null; + + var indexReq = new IndexPlaylistScoresRequest(roomId, playlistItem.ID, cursor, sort); + + indexReq.Success += r => + { + switch (direction) { - Schedule(() => - { - // Prefer selecting the local user's score, or otherwise default to the first visible score. - SelectedScore.Value = scoreInfos.FirstOrDefault(s => s.User.Id == api.LocalUser.Value.Id) ?? scoreInfos.FirstOrDefault(); - }); + case -1: + higherScoresCursor = r.Cursor; + break; + + default: + lowerScoresCursor = r.Cursor; + break; } - // Invoke callback to add the scores. Exclude the user's current score which was added previously. - scoresCallback?.Invoke(scoreInfos.Where(s => s.ID != Score?.OnlineScoreID)); + performSuccessCallback(scoresCallback, r.Scores); + }; - loadingLayer.Hide(); + indexReq.Failure += _ => loadingLayer.Hide(); + + return indexReq; + } + + private void performSuccessCallback(Action> callback, List scores) + { + var scoreInfos = new List(scores.Select(s => s.CreateScoreInfo(playlistItem))); + + // Select a score if we don't already have one selected. + // Note: This is done before the callback so that the panel list centres on the selected score before panels are added (eliminating initial scroll). + if (SelectedScore.Value == null) + { + Schedule(() => + { + // Prefer selecting the local user's score, or otherwise default to the first visible score. + SelectedScore.Value = scoreInfos.FirstOrDefault(s => s.User.Id == api.LocalUser.Value.Id) ?? scoreInfos.FirstOrDefault(); + }); } + + // Invoke callback to add the scores. Exclude the user's current score which was added previously. + callback?.Invoke(scoreInfos.Where(s => s.ID != Score?.OnlineScoreID)); + + loadingLayer.Hide(); } } } diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index 44458d8c8e..c5512822b2 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -164,11 +164,7 @@ namespace osu.Game.Screens.Ranking { base.LoadComplete(); - var req = FetchScores(scores => Schedule(() => - { - foreach (var s in scores) - addScore(s); - })); + var req = FetchScores(fetchScoresCallback); if (req != null) api.Queue(req); @@ -176,6 +172,29 @@ namespace osu.Game.Screens.Ranking statisticsPanel.State.BindValueChanged(onStatisticsStateChanged, true); } + private APIRequest nextPageRequest; + + protected override void Update() + { + base.Update(); + + if (hasAnyScores && nextPageRequest == null) + { + if (scorePanelList.IsScrolledToStart) + nextPageRequest = FetchNextPage(-1, fetchScoresCallback); + else if (scorePanelList.IsScrolledToEnd) + nextPageRequest = FetchNextPage(1, fetchScoresCallback); + + if (nextPageRequest != null) + { + nextPageRequest.Success += () => nextPageRequest = null; + nextPageRequest.Failure += _ => nextPageRequest = null; + + api.Queue(nextPageRequest); + } + } + } + /// /// Performs a fetch/refresh of scores to be displayed. /// @@ -183,6 +202,18 @@ namespace osu.Game.Screens.Ranking /// An responsible for the fetch operation. This will be queued and performed automatically. protected virtual APIRequest FetchScores(Action> scoresCallback) => null; + protected virtual APIRequest FetchNextPage(int direction, Action> scoresCallback) => null; + + private bool hasAnyScores; + + private void fetchScoresCallback(IEnumerable scores) => Schedule(() => + { + foreach (var s in scores) + addScore(s); + + hasAnyScores = true; + }); + public override void OnEntering(IScreen last) { base.OnEntering(last); diff --git a/osu.Game/Screens/Ranking/ScorePanelList.cs b/osu.Game/Screens/Ranking/ScorePanelList.cs index 0f8bc82ac0..aba8314732 100644 --- a/osu.Game/Screens/Ranking/ScorePanelList.cs +++ b/osu.Game/Screens/Ranking/ScorePanelList.cs @@ -26,6 +26,10 @@ namespace osu.Game.Screens.Ranking /// private const float expanded_panel_spacing = 15; + public bool IsScrolledToStart => flow.Count > 0 && scroll.ScrollableExtent > 0 && scroll.Current <= 100; + + public bool IsScrolledToEnd => flow.Count > 0 && scroll.ScrollableExtent > 0 && scroll.IsScrolledToEnd(100); + /// /// An action to be invoked if a is clicked while in an expanded state. /// From 113fac84ddf195b10d1ae3a9b1cd1e437c94d0ba Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 22 Jul 2020 21:14:04 +0900 Subject: [PATCH 0202/1782] Fix circle container type --- osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs index b87e112d10..07dc6021c9 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs @@ -184,7 +184,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces lastPosition = newPos; } - private class FollowCircleContainer : Container + private class FollowCircleContainer : CircularContainer { public override bool HandlePositionalInput => true; } From 50f72ac9cb90074f647d76085b515d4ce8d9b45d Mon Sep 17 00:00:00 2001 From: jorolf Date: Wed, 22 Jul 2020 22:10:59 +0200 Subject: [PATCH 0203/1782] rename classes --- ...neHueAnimation.cs => TestSceneLogoAnimation.cs} | 10 +++++----- .../Sprites/{HueAnimation.cs => LogoAnimation.cs} | 14 +++++++------- osu.Game/Screens/Menu/IntroTriangles.cs | 6 +++--- 3 files changed, 15 insertions(+), 15 deletions(-) rename osu.Game.Tests/Visual/UserInterface/{TestSceneHueAnimation.cs => TestSceneLogoAnimation.cs} (85%) rename osu.Game/Graphics/Sprites/{HueAnimation.cs => LogoAnimation.cs} (79%) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneHueAnimation.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneLogoAnimation.cs similarity index 85% rename from osu.Game.Tests/Visual/UserInterface/TestSceneHueAnimation.cs rename to osu.Game.Tests/Visual/UserInterface/TestSceneLogoAnimation.cs index 9c5888d072..155d043bf9 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneHueAnimation.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneLogoAnimation.cs @@ -11,14 +11,14 @@ using osu.Game.Graphics.Sprites; namespace osu.Game.Tests.Visual.UserInterface { [TestFixture] - public class TestSceneHueAnimation : OsuTestScene + public class TestSceneLogoAnimation : OsuTestScene { [BackgroundDependencyLoader] private void load(LargeTextureStore textures) { - HueAnimation anim2; + LogoAnimation anim2; - Add(anim2 = new HueAnimation + Add(anim2 = new LogoAnimation { RelativeSizeAxes = Axes.Both, FillMode = FillMode.Fit, @@ -26,9 +26,9 @@ namespace osu.Game.Tests.Visual.UserInterface Colour = Colour4.White, }); - HueAnimation anim; + LogoAnimation anim; - Add(anim = new HueAnimation + Add(anim = new LogoAnimation { RelativeSizeAxes = Axes.Both, FillMode = FillMode.Fit, diff --git a/osu.Game/Graphics/Sprites/HueAnimation.cs b/osu.Game/Graphics/Sprites/LogoAnimation.cs similarity index 79% rename from osu.Game/Graphics/Sprites/HueAnimation.cs rename to osu.Game/Graphics/Sprites/LogoAnimation.cs index 8ad68ace05..b1383065fe 100644 --- a/osu.Game/Graphics/Sprites/HueAnimation.cs +++ b/osu.Game/Graphics/Sprites/LogoAnimation.cs @@ -11,13 +11,13 @@ using osu.Framework.Graphics.Textures; namespace osu.Game.Graphics.Sprites { - public class HueAnimation : Sprite + public class LogoAnimation : Sprite { [BackgroundDependencyLoader] private void load(ShaderManager shaders, TextureStore textures) { - TextureShader = shaders.Load(VertexShaderDescriptor.TEXTURE_2, @"HueAnimation"); - RoundedTextureShader = shaders.Load(VertexShaderDescriptor.TEXTURE_2, @"HueAnimation"); // Masking isn't supported for now + TextureShader = shaders.Load(VertexShaderDescriptor.TEXTURE_2, @"LogoAnimation"); + RoundedTextureShader = shaders.Load(VertexShaderDescriptor.TEXTURE_2, @"LogoAnimation"); // Masking isn't supported for now } private float animationProgress; @@ -36,15 +36,15 @@ namespace osu.Game.Graphics.Sprites public override bool IsPresent => true; - protected override DrawNode CreateDrawNode() => new HueAnimationDrawNode(this); + protected override DrawNode CreateDrawNode() => new LogoAnimationDrawNode(this); - private class HueAnimationDrawNode : SpriteDrawNode + private class LogoAnimationDrawNode : SpriteDrawNode { - private HueAnimation source => (HueAnimation)Source; + private LogoAnimation source => (LogoAnimation)Source; private float progress; - public HueAnimationDrawNode(HueAnimation source) + public LogoAnimationDrawNode(LogoAnimation source) : base(source) { } diff --git a/osu.Game/Screens/Menu/IntroTriangles.cs b/osu.Game/Screens/Menu/IntroTriangles.cs index b56ba6c8a4..a9ef20436f 100644 --- a/osu.Game/Screens/Menu/IntroTriangles.cs +++ b/osu.Game/Screens/Menu/IntroTriangles.cs @@ -260,7 +260,7 @@ namespace osu.Game.Screens.Menu private class LazerLogo : CompositeDrawable { - private HueAnimation highlight, background; + private LogoAnimation highlight, background; public float Progress { @@ -282,13 +282,13 @@ namespace osu.Game.Screens.Menu { InternalChildren = new Drawable[] { - highlight = new HueAnimation + highlight = new LogoAnimation { RelativeSizeAxes = Axes.Both, Texture = textures.Get(@"Intro/Triangles/logo-highlight"), Colour = Color4.White, }, - background = new HueAnimation + background = new LogoAnimation { RelativeSizeAxes = Axes.Both, Texture = textures.Get(@"Intro/Triangles/logo-background"), From ee05d5cb14b7d946a0335f9f7208b6213da6ed57 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 23 Jul 2020 09:06:15 +0900 Subject: [PATCH 0204/1782] Remove no-longer-necessary play trigger on skin change --- osu.Game/Screens/Play/PauseOverlay.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/osu.Game/Screens/Play/PauseOverlay.cs b/osu.Game/Screens/Play/PauseOverlay.cs index e74585990a..fa917cda32 100644 --- a/osu.Game/Screens/Play/PauseOverlay.cs +++ b/osu.Game/Screens/Play/PauseOverlay.cs @@ -39,10 +39,6 @@ namespace osu.Game.Screens.Play Looping = true, }); - // PopIn is called before updating the skin, and when a sample is updated, its "playing" value is reset - // the sample must be played again - pauseLoop.OnSkinChanged += () => pauseLoop.Play(); - // SkinnableSound only plays a sound if its aggregate volume is > 0, so the volume must be turned up before playing it pauseLoop.VolumeTo(minimum_volume); } From 4102dae999cb7f63294b033898885d50afbc799b Mon Sep 17 00:00:00 2001 From: Lucas A Date: Wed, 22 Jul 2020 21:45:27 +0200 Subject: [PATCH 0205/1782] Revert commit 939441ae --- osu.Desktop/Windows/GameplayWinKeyHandler.cs | 14 +++++++------- osu.Game/Configuration/SessionStatics.cs | 4 +--- osu.Game/Screens/Play/Player.cs | 2 +- .../Screens/Play/ScreenSuspensionHandler.cs | 19 ++----------------- 4 files changed, 11 insertions(+), 28 deletions(-) diff --git a/osu.Desktop/Windows/GameplayWinKeyHandler.cs b/osu.Desktop/Windows/GameplayWinKeyHandler.cs index d5ef89c680..4f74a4f492 100644 --- a/osu.Desktop/Windows/GameplayWinKeyHandler.cs +++ b/osu.Desktop/Windows/GameplayWinKeyHandler.cs @@ -11,26 +11,26 @@ namespace osu.Desktop.Windows { public class GameplayWinKeyHandler : Component { + private Bindable allowScreenSuspension; private Bindable disableWinKey; - private Bindable disableWinKeySetting; private GameHost host; [BackgroundDependencyLoader] - private void load(GameHost host, OsuConfigManager config, SessionStatics statics) + private void load(GameHost host, OsuConfigManager config) { this.host = host; - disableWinKey = statics.GetBindable(Static.DisableWindowsKey); - disableWinKey.ValueChanged += toggleWinKey; + allowScreenSuspension = host.AllowScreenSuspension.GetBoundCopy(); + allowScreenSuspension.ValueChanged += toggleWinKey; - disableWinKeySetting = config.GetBindable(OsuSetting.GameplayDisableWinKey); - disableWinKeySetting.BindValueChanged(t => disableWinKey.TriggerChange(), true); + disableWinKey = config.GetBindable(OsuSetting.GameplayDisableWinKey); + disableWinKey.BindValueChanged(t => allowScreenSuspension.TriggerChange(), true); } private void toggleWinKey(ValueChangedEvent e) { - if (e.NewValue && disableWinKeySetting.Value) + if (!e.NewValue && disableWinKey.Value) host.InputThread.Scheduler.Add(WindowsKey.Disable); else host.InputThread.Scheduler.Add(WindowsKey.Enable); diff --git a/osu.Game/Configuration/SessionStatics.cs b/osu.Game/Configuration/SessionStatics.cs index 7aad79b5ad..40b2adb867 100644 --- a/osu.Game/Configuration/SessionStatics.cs +++ b/osu.Game/Configuration/SessionStatics.cs @@ -12,14 +12,12 @@ namespace osu.Game.Configuration { Set(Static.LoginOverlayDisplayed, false); Set(Static.MutedAudioNotificationShownOnce, false); - Set(Static.DisableWindowsKey, false); } } public enum Static { LoginOverlayDisplayed, - MutedAudioNotificationShownOnce, - DisableWindowsKey + MutedAudioNotificationShownOnce } } diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index e0721d55f7..541275cf55 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -181,7 +181,7 @@ namespace osu.Game.Screens.Play InternalChild = GameplayClockContainer = new GameplayClockContainer(Beatmap.Value, Mods.Value, DrawableRuleset.GameplayStartTime); AddInternal(gameplayBeatmap = new GameplayBeatmap(playableBeatmap)); - AddInternal(screenSuspension = new ScreenSuspensionHandler(GameplayClockContainer, DrawableRuleset.HasReplayLoaded)); + AddInternal(screenSuspension = new ScreenSuspensionHandler(GameplayClockContainer)); dependencies.CacheAs(gameplayBeatmap); diff --git a/osu.Game/Screens/Play/ScreenSuspensionHandler.cs b/osu.Game/Screens/Play/ScreenSuspensionHandler.cs index 6865db5a5e..8585a5c309 100644 --- a/osu.Game/Screens/Play/ScreenSuspensionHandler.cs +++ b/osu.Game/Screens/Play/ScreenSuspensionHandler.cs @@ -8,7 +8,6 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Platform; -using osu.Game.Configuration; namespace osu.Game.Screens.Play { @@ -19,18 +18,13 @@ namespace osu.Game.Screens.Play { private readonly GameplayClockContainer gameplayClockContainer; private Bindable isPaused; - private readonly Bindable hasReplayLoaded; [Resolved] private GameHost host { get; set; } - [Resolved] - private SessionStatics statics { get; set; } - - public ScreenSuspensionHandler([NotNull] GameplayClockContainer gameplayClockContainer, Bindable hasReplayLoaded) + public ScreenSuspensionHandler([NotNull] GameplayClockContainer gameplayClockContainer) { this.gameplayClockContainer = gameplayClockContainer ?? throw new ArgumentNullException(nameof(gameplayClockContainer)); - this.hasReplayLoaded = hasReplayLoaded.GetBoundCopy(); } protected override void LoadComplete() @@ -42,12 +36,7 @@ namespace osu.Game.Screens.Play Debug.Assert(host.AllowScreenSuspension.Value); isPaused = gameplayClockContainer.IsPaused.GetBoundCopy(); - isPaused.BindValueChanged(paused => - { - host.AllowScreenSuspension.Value = paused.NewValue; - statics.Set(Static.DisableWindowsKey, !paused.NewValue && !hasReplayLoaded.Value); - }, true); - hasReplayLoaded.BindValueChanged(_ => isPaused.TriggerChange(), true); + isPaused.BindValueChanged(paused => host.AllowScreenSuspension.Value = paused.NewValue, true); } protected override void Dispose(bool isDisposing) @@ -55,13 +44,9 @@ namespace osu.Game.Screens.Play base.Dispose(isDisposing); isPaused?.UnbindAll(); - hasReplayLoaded.UnbindAll(); if (host != null) - { host.AllowScreenSuspension.Value = true; - statics.Set(Static.DisableWindowsKey, false); - } } } } From acff270e969cde58d12893fc891351d3d06afdbd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 23 Jul 2020 19:14:18 +0900 Subject: [PATCH 0206/1782] Fix failing test by moving slider closer --- osu.Game.Rulesets.Osu.Tests/TestSceneOutOfOrderHits.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneOutOfOrderHits.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneOutOfOrderHits.cs index c3b4d2625e..854626d362 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneOutOfOrderHits.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneOutOfOrderHits.cs @@ -223,7 +223,7 @@ namespace osu.Game.Rulesets.Osu.Tests const double time_slider = 1500; const double time_circle = 1510; Vector2 positionCircle = Vector2.Zero; - Vector2 positionSlider = new Vector2(80); + Vector2 positionSlider = new Vector2(30); var hitObjects = new List { From 5e6adfff99b1b348897ab4606aef7f910016560c Mon Sep 17 00:00:00 2001 From: Lucas A Date: Thu, 23 Jul 2020 12:45:14 +0200 Subject: [PATCH 0207/1782] Disable windows key only while in gameplay. --- osu.Desktop/OsuGameDesktop.cs | 2 +- osu.Desktop/Windows/GameplayWinKeyHandler.cs | 15 ++++++++++++++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/osu.Desktop/OsuGameDesktop.cs b/osu.Desktop/OsuGameDesktop.cs index d05a4af126..6eefee3b50 100644 --- a/osu.Desktop/OsuGameDesktop.cs +++ b/osu.Desktop/OsuGameDesktop.cs @@ -101,7 +101,7 @@ namespace osu.Desktop LoadComponentAsync(new DiscordRichPresence(), Add); if (RuntimeInfo.OS == RuntimeInfo.Platform.Windows) - LoadComponentAsync(new GameplayWinKeyHandler(), Add); + LoadComponentAsync(new GameplayWinKeyHandler(ScreenStack), Add); } protected override void ScreenChanged(IScreen lastScreen, IScreen newScreen) diff --git a/osu.Desktop/Windows/GameplayWinKeyHandler.cs b/osu.Desktop/Windows/GameplayWinKeyHandler.cs index 4f74a4f492..96154356d0 100644 --- a/osu.Desktop/Windows/GameplayWinKeyHandler.cs +++ b/osu.Desktop/Windows/GameplayWinKeyHandler.cs @@ -1,11 +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.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Platform; using osu.Game.Configuration; +using osu.Game.Screens; +using osu.Game.Screens.Play; namespace osu.Desktop.Windows { @@ -14,8 +17,16 @@ namespace osu.Desktop.Windows private Bindable allowScreenSuspension; private Bindable disableWinKey; + private readonly OsuScreenStack screenStack; private GameHost host; + private Type currentScreenType => screenStack.CurrentScreen?.GetType(); + + public GameplayWinKeyHandler(OsuScreenStack stack) + { + screenStack = stack; + } + [BackgroundDependencyLoader] private void load(GameHost host, OsuConfigManager config) { @@ -30,7 +41,9 @@ namespace osu.Desktop.Windows private void toggleWinKey(ValueChangedEvent e) { - if (!e.NewValue && disableWinKey.Value) + var isPlayer = typeof(Player).IsAssignableFrom(currentScreenType) && currentScreenType != typeof(ReplayPlayer); + + if (!e.NewValue && disableWinKey.Value && isPlayer) host.InputThread.Scheduler.Add(WindowsKey.Disable); else host.InputThread.Scheduler.Add(WindowsKey.Enable); From f883cb85d72ff2f98ec87a1f207e239b805e2c8b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 23 Jul 2020 21:24:31 +0900 Subject: [PATCH 0208/1782] Null out the sample too --- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index 5059ec1231..07f40f763b 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -94,6 +94,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables base.LoadSamples(); slidingSample?.Expire(); + slidingSample = null; var firstSample = HitObject.Samples.FirstOrDefault(); From d0b35d7b32895ff3f988c0f6e85fa86eaacaad0f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 23 Jul 2020 22:13:37 +0900 Subject: [PATCH 0209/1782] 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 c0c75b8d71..e5b0245dd0 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,6 +52,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index e8c333b6b1..5af28ae11a 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -24,7 +24,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 8d1b837995..4a94ec33d8 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -80,7 +80,7 @@ - + From 76284a0f018f2c8c3a502db24360081e0c1f5996 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 23 Jul 2020 23:18:43 +0900 Subject: [PATCH 0210/1782] Move cancellation out of condition --- .../Screens/Select/Carousel/DrawableCarouselBeatmap.cs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs index d5aeecae04..1b5b448e1f 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs @@ -188,12 +188,11 @@ namespace osu.Game.Screens.Select.Carousel if (Item.State.Value != CarouselItemState.Collapsed && Alpha == 0) starCounter.ReplayAnimation(); - if (Item.State.Value == CarouselItemState.Collapsed) - starDifficultyCancellationSource?.Cancel(); - else - { - starDifficultyCancellationSource?.Cancel(); + starDifficultyCancellationSource?.Cancel(); + // Only compute difficulty when the item is visible. + if (Item.State.Value != CarouselItemState.Collapsed) + { // We've potentially cancelled the computation above so a new bindable is required. starDifficultyBindable = difficultyManager.GetTrackedBindable(beatmap, (starDifficultyCancellationSource = new CancellationTokenSource()).Token); starDifficultyBindable.BindValueChanged(d => starCounter.Current = (float)d.NewValue.Stars, true); From f75f1231b7f2300b260e25e7dc8f2d4d273b2bc7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 24 Jul 2020 10:41:09 +0900 Subject: [PATCH 0211/1782] Invert conditional for readability --- osu.Game/Graphics/Cursor/MenuCursorContainer.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/osu.Game/Graphics/Cursor/MenuCursorContainer.cs b/osu.Game/Graphics/Cursor/MenuCursorContainer.cs index 02bfb3fad6..3015c44613 100644 --- a/osu.Game/Graphics/Cursor/MenuCursorContainer.cs +++ b/osu.Game/Graphics/Cursor/MenuCursorContainer.cs @@ -58,10 +58,11 @@ namespace osu.Game.Graphics.Cursor foreach (var d in inputManager.HoveredDrawables) { - if (!(d is IProvideCursor p) || !p.ProvidingUserCursor) continue; - - newTarget = p; - break; + if (d is IProvideCursor p && p.ProvidingUserCursor) + { + newTarget = p; + break; + } } if (currentTarget == newTarget) From 264bd7ced1c8a8caab663eebba2114bfefa766a9 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 24 Jul 2020 13:38:53 +0900 Subject: [PATCH 0212/1782] Apply general refactoring from review --- osu.Game/Beatmaps/BeatmapDifficultyManager.cs | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapDifficultyManager.cs b/osu.Game/Beatmaps/BeatmapDifficultyManager.cs index a9f34acd14..12d472e8c6 100644 --- a/osu.Game/Beatmaps/BeatmapDifficultyManager.cs +++ b/osu.Game/Beatmaps/BeatmapDifficultyManager.cs @@ -24,7 +24,7 @@ namespace osu.Game.Beatmaps // Too many simultaneous updates can lead to stutters. One thread seems to work fine for song select display purposes. private readonly ThreadedTaskScheduler updateScheduler = new ThreadedTaskScheduler(1, nameof(BeatmapDifficultyManager)); - // A cache that keeps references to BeatmapInfos for 60sec. + // A permanent cache to prevent re-computations. private readonly ConcurrentDictionary difficultyCache = new ConcurrentDictionary(); // All bindables that should be updated along with the current ruleset + mods. @@ -48,29 +48,29 @@ namespace osu.Game.Beatmaps } /// - /// Retrieves an containing the star difficulty of a with a given and combination. + /// Retrieves a bindable containing the star difficulty of a with a given and combination. /// /// - /// This will not update to follow the currently-selected ruleset and mods. + /// The bindable will not update to follow the currently-selected ruleset and mods. /// /// The to get the difficulty of. /// The to get the difficulty with. /// The s to get the difficulty with. /// An optional which stops updating the star difficulty for the given . - /// An that is updated to contain the star difficulty when it becomes available. + /// A bindable that is updated to contain the star difficulty when it becomes available. public IBindable GetUntrackedBindable([NotNull] BeatmapInfo beatmapInfo, [CanBeNull] RulesetInfo rulesetInfo = null, [CanBeNull] IReadOnlyList mods = null, CancellationToken cancellationToken = default) => createBindable(beatmapInfo, rulesetInfo, mods, cancellationToken); /// - /// Retrieves a containing the star difficulty of a that follows the user's currently-selected ruleset and mods. + /// Retrieves a bindable containing the star difficulty of a that follows the user's currently-selected ruleset and mods. /// /// - /// Ensure to hold a local reference of the returned in order to receive value-changed events. + /// Ensure to hold a local reference of the returned bindable in order to receive value-changed events. /// /// The to get the difficulty of. /// An optional which stops updating the star difficulty for the given . - /// An that is updated to contain the star difficulty when it becomes available, or when the currently-selected ruleset and mods change. + /// A bindable that is updated to contain the star difficulty when it becomes available, or when the currently-selected ruleset and mods change. public IBindable GetTrackedBindable([NotNull] BeatmapInfo beatmapInfo, CancellationToken cancellationToken = default) { var bindable = createBindable(beatmapInfo, currentRuleset.Value, currentMods.Value, cancellationToken); @@ -89,7 +89,7 @@ namespace osu.Game.Beatmaps public async Task GetDifficultyAsync([NotNull] BeatmapInfo beatmapInfo, [CanBeNull] RulesetInfo rulesetInfo = null, [CanBeNull] IReadOnlyList mods = null, CancellationToken cancellationToken = default) { - if (tryGetGetExisting(beatmapInfo, rulesetInfo, mods, out var existing, out var key)) + if (tryGetExisting(beatmapInfo, rulesetInfo, mods, out var existing, out var key)) return existing; return await Task.Factory.StartNew(() => computeDifficulty(key, beatmapInfo, rulesetInfo), cancellationToken, @@ -105,7 +105,7 @@ namespace osu.Game.Beatmaps /// The . public StarDifficulty GetDifficulty([NotNull] BeatmapInfo beatmapInfo, [CanBeNull] RulesetInfo rulesetInfo = null, [CanBeNull] IReadOnlyList mods = null) { - if (tryGetGetExisting(beatmapInfo, rulesetInfo, mods, out var existing, out var key)) + if (tryGetExisting(beatmapInfo, rulesetInfo, mods, out var existing, out var key)) return existing; return computeDifficulty(key, beatmapInfo, rulesetInfo); @@ -204,7 +204,7 @@ namespace osu.Game.Beatmaps /// The existing difficulty value, if present. /// The key that was used to perform this lookup. This can be further used to query . /// Whether an existing difficulty was found. - private bool tryGetGetExisting(BeatmapInfo beatmapInfo, RulesetInfo rulesetInfo, IReadOnlyList mods, out StarDifficulty existingDifficulty, out DifficultyCacheLookup key) + private bool tryGetExisting(BeatmapInfo beatmapInfo, RulesetInfo rulesetInfo, IReadOnlyList mods, out StarDifficulty existingDifficulty, out DifficultyCacheLookup key) { // In the case that the user hasn't given us a ruleset, use the beatmap's default ruleset. rulesetInfo ??= beatmapInfo.Ruleset; From de007cc1c63da3bd88ee52881a31f8cce91c2ec0 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 24 Jul 2020 13:40:01 +0900 Subject: [PATCH 0213/1782] Use IEnumerable mods instead of IReadOnlyList --- osu.Game/Beatmaps/BeatmapDifficultyManager.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapDifficultyManager.cs b/osu.Game/Beatmaps/BeatmapDifficultyManager.cs index 12d472e8c6..914874e210 100644 --- a/osu.Game/Beatmaps/BeatmapDifficultyManager.cs +++ b/osu.Game/Beatmaps/BeatmapDifficultyManager.cs @@ -58,7 +58,7 @@ namespace osu.Game.Beatmaps /// The s to get the difficulty with. /// An optional which stops updating the star difficulty for the given . /// A bindable that is updated to contain the star difficulty when it becomes available. - public IBindable GetUntrackedBindable([NotNull] BeatmapInfo beatmapInfo, [CanBeNull] RulesetInfo rulesetInfo = null, [CanBeNull] IReadOnlyList mods = null, + public IBindable GetUntrackedBindable([NotNull] BeatmapInfo beatmapInfo, [CanBeNull] RulesetInfo rulesetInfo = null, [CanBeNull] IEnumerable mods = null, CancellationToken cancellationToken = default) => createBindable(beatmapInfo, rulesetInfo, mods, cancellationToken); @@ -86,7 +86,7 @@ namespace osu.Game.Beatmaps /// The s to get the difficulty with. /// An optional which stops computing the star difficulty. /// The . - public async Task GetDifficultyAsync([NotNull] BeatmapInfo beatmapInfo, [CanBeNull] RulesetInfo rulesetInfo = null, [CanBeNull] IReadOnlyList mods = null, + public async Task GetDifficultyAsync([NotNull] BeatmapInfo beatmapInfo, [CanBeNull] RulesetInfo rulesetInfo = null, [CanBeNull] IEnumerable mods = null, CancellationToken cancellationToken = default) { if (tryGetExisting(beatmapInfo, rulesetInfo, mods, out var existing, out var key)) @@ -103,7 +103,7 @@ namespace osu.Game.Beatmaps /// The to get the difficulty with. /// The s to get the difficulty with. /// The . - public StarDifficulty GetDifficulty([NotNull] BeatmapInfo beatmapInfo, [CanBeNull] RulesetInfo rulesetInfo = null, [CanBeNull] IReadOnlyList mods = null) + public StarDifficulty GetDifficulty([NotNull] BeatmapInfo beatmapInfo, [CanBeNull] RulesetInfo rulesetInfo = null, [CanBeNull] IEnumerable mods = null) { if (tryGetExisting(beatmapInfo, rulesetInfo, mods, out var existing, out var key)) return existing; @@ -138,7 +138,7 @@ namespace osu.Game.Beatmaps /// The to update with. /// The s to update with. /// A token that may be used to cancel this update. - private void updateBindable([NotNull] BindableStarDifficulty bindable, [CanBeNull] RulesetInfo rulesetInfo, [CanBeNull] IReadOnlyList mods, CancellationToken cancellationToken = default) + private void updateBindable([NotNull] BindableStarDifficulty bindable, [CanBeNull] RulesetInfo rulesetInfo, [CanBeNull] IEnumerable mods, CancellationToken cancellationToken = default) { GetDifficultyAsync(bindable.Beatmap, rulesetInfo, mods, cancellationToken).ContinueWith(t => { @@ -159,7 +159,7 @@ namespace osu.Game.Beatmaps /// The initial s to get the difficulty with. /// An optional which stops updating the star difficulty for the given . /// The . - private BindableStarDifficulty createBindable([NotNull] BeatmapInfo beatmapInfo, [CanBeNull] RulesetInfo initialRulesetInfo, [CanBeNull] IReadOnlyList initialMods, + private BindableStarDifficulty createBindable([NotNull] BeatmapInfo beatmapInfo, [CanBeNull] RulesetInfo initialRulesetInfo, [CanBeNull] IEnumerable initialMods, CancellationToken cancellationToken) { var bindable = new BindableStarDifficulty(beatmapInfo, cancellationToken); @@ -204,7 +204,7 @@ namespace osu.Game.Beatmaps /// The existing difficulty value, if present. /// The key that was used to perform this lookup. This can be further used to query . /// Whether an existing difficulty was found. - private bool tryGetExisting(BeatmapInfo beatmapInfo, RulesetInfo rulesetInfo, IReadOnlyList mods, out StarDifficulty existingDifficulty, out DifficultyCacheLookup key) + private bool tryGetExisting(BeatmapInfo beatmapInfo, RulesetInfo rulesetInfo, IEnumerable mods, out StarDifficulty existingDifficulty, out DifficultyCacheLookup key) { // In the case that the user hasn't given us a ruleset, use the beatmap's default ruleset. rulesetInfo ??= beatmapInfo.Ruleset; From b10b99a6703c9a68d2f1da1b013a46460548a988 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 24 Jul 2020 13:52:43 +0900 Subject: [PATCH 0214/1782] Change method signatures to remove tracked/untracked --- osu.Game/Beatmaps/BeatmapDifficultyManager.cs | 37 +++++++++---------- .../Carousel/DrawableCarouselBeatmap.cs | 2 +- .../Screens/Select/Details/AdvancedStats.cs | 4 +- 3 files changed, 20 insertions(+), 23 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapDifficultyManager.cs b/osu.Game/Beatmaps/BeatmapDifficultyManager.cs index 914874e210..d86c0dd945 100644 --- a/osu.Game/Beatmaps/BeatmapDifficultyManager.cs +++ b/osu.Game/Beatmaps/BeatmapDifficultyManager.cs @@ -47,6 +47,19 @@ namespace osu.Game.Beatmaps currentMods.BindValueChanged(_ => updateTrackedBindables(), true); } + /// + /// Retrieves a bindable containing the star difficulty of a that follows the currently-selected ruleset and mods. + /// + /// The to get the difficulty of. + /// An optional which stops updating the star difficulty for the given . + /// A bindable that is updated to contain the star difficulty when it becomes available. + public IBindable GetBindableDifficulty([NotNull] BeatmapInfo beatmapInfo, CancellationToken cancellationToken = default) + { + var bindable = createBindable(beatmapInfo, currentRuleset.Value, currentMods.Value, cancellationToken); + trackedBindables.Add(bindable); + return bindable; + } + /// /// Retrieves a bindable containing the star difficulty of a with a given and combination. /// @@ -54,30 +67,14 @@ namespace osu.Game.Beatmaps /// The bindable will not update to follow the currently-selected ruleset and mods. /// /// The to get the difficulty of. - /// The to get the difficulty with. - /// The s to get the difficulty with. + /// The to get the difficulty with. If null, the difficulty will change along with the game-wide ruleset and mods. + /// The s to get the difficulty with. If null, the difficulty will change along with the game-wide mods. /// An optional which stops updating the star difficulty for the given . /// A bindable that is updated to contain the star difficulty when it becomes available. - public IBindable GetUntrackedBindable([NotNull] BeatmapInfo beatmapInfo, [CanBeNull] RulesetInfo rulesetInfo = null, [CanBeNull] IEnumerable mods = null, - CancellationToken cancellationToken = default) + public IBindable GetBindableDifficulty([NotNull] BeatmapInfo beatmapInfo, [NotNull] RulesetInfo rulesetInfo, [CanBeNull] IEnumerable mods, + CancellationToken cancellationToken = default) => createBindable(beatmapInfo, rulesetInfo, mods, cancellationToken); - /// - /// Retrieves a bindable containing the star difficulty of a that follows the user's currently-selected ruleset and mods. - /// - /// - /// Ensure to hold a local reference of the returned bindable in order to receive value-changed events. - /// - /// The to get the difficulty of. - /// An optional which stops updating the star difficulty for the given . - /// A bindable that is updated to contain the star difficulty when it becomes available, or when the currently-selected ruleset and mods change. - public IBindable GetTrackedBindable([NotNull] BeatmapInfo beatmapInfo, CancellationToken cancellationToken = default) - { - var bindable = createBindable(beatmapInfo, currentRuleset.Value, currentMods.Value, cancellationToken); - trackedBindables.Add(bindable); - return bindable; - } - /// /// Retrieves the difficulty of a . /// diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs index 1b5b448e1f..c559b4f8f5 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs @@ -194,7 +194,7 @@ namespace osu.Game.Screens.Select.Carousel if (Item.State.Value != CarouselItemState.Collapsed) { // We've potentially cancelled the computation above so a new bindable is required. - starDifficultyBindable = difficultyManager.GetTrackedBindable(beatmap, (starDifficultyCancellationSource = new CancellationTokenSource()).Token); + starDifficultyBindable = difficultyManager.GetBindableDifficulty(beatmap, (starDifficultyCancellationSource = new CancellationTokenSource()).Token); starDifficultyBindable.BindValueChanged(d => starCounter.Current = (float)d.NewValue.Stars, true); } diff --git a/osu.Game/Screens/Select/Details/AdvancedStats.cs b/osu.Game/Screens/Select/Details/AdvancedStats.cs index aefba397b9..1557a025ef 100644 --- a/osu.Game/Screens/Select/Details/AdvancedStats.cs +++ b/osu.Game/Screens/Select/Details/AdvancedStats.cs @@ -161,8 +161,8 @@ namespace osu.Game.Screens.Select.Details var ourSource = starDifficultyCancellationSource = new CancellationTokenSource(); - normalStarDifficulty = difficultyManager.GetUntrackedBindable(Beatmap, ruleset.Value, cancellationToken: ourSource.Token); - moddedStarDifficulty = difficultyManager.GetUntrackedBindable(Beatmap, ruleset.Value, mods.Value, ourSource.Token); + normalStarDifficulty = difficultyManager.GetBindableDifficulty(Beatmap, ruleset.Value, null, cancellationToken: ourSource.Token); + moddedStarDifficulty = difficultyManager.GetBindableDifficulty(Beatmap, ruleset.Value, mods.Value, ourSource.Token); normalStarDifficulty.BindValueChanged(_ => updateDisplay()); moddedStarDifficulty.BindValueChanged(_ => updateDisplay(), true); From 44b0aae20d753f707065d21cf9d25da7b52936c3 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 24 Jul 2020 13:54:47 +0900 Subject: [PATCH 0215/1782] Allow nullable ruleset, reword xmldoc --- osu.Game/Beatmaps/BeatmapDifficultyManager.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapDifficultyManager.cs b/osu.Game/Beatmaps/BeatmapDifficultyManager.cs index d86c0dd945..5e644fbf1c 100644 --- a/osu.Game/Beatmaps/BeatmapDifficultyManager.cs +++ b/osu.Game/Beatmaps/BeatmapDifficultyManager.cs @@ -67,11 +67,11 @@ namespace osu.Game.Beatmaps /// The bindable will not update to follow the currently-selected ruleset and mods. /// /// The to get the difficulty of. - /// The to get the difficulty with. If null, the difficulty will change along with the game-wide ruleset and mods. - /// The s to get the difficulty with. If null, the difficulty will change along with the game-wide mods. + /// The to get the difficulty with. If null, the 's ruleset is used. + /// The s to get the difficulty with. If null, no mods will be assumed. /// An optional which stops updating the star difficulty for the given . /// A bindable that is updated to contain the star difficulty when it becomes available. - public IBindable GetBindableDifficulty([NotNull] BeatmapInfo beatmapInfo, [NotNull] RulesetInfo rulesetInfo, [CanBeNull] IEnumerable mods, + public IBindable GetBindableDifficulty([NotNull] BeatmapInfo beatmapInfo, [CanBeNull] RulesetInfo rulesetInfo, [CanBeNull] IEnumerable mods, CancellationToken cancellationToken = default) => createBindable(beatmapInfo, rulesetInfo, mods, cancellationToken); From d093dc09f94a194eb8e1d936c7ccf36b3a1ff712 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 24 Jul 2020 14:10:05 +0900 Subject: [PATCH 0216/1782] Limit notification text length to avoid large error messages degrading performance --- 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 f4bb10340e..d6a07651e2 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -18,6 +18,7 @@ using osu.Game.Screens.Menu; using System.Linq; using System.Threading; using System.Threading.Tasks; +using Humanizer; using JetBrains.Annotations; using osu.Framework.Audio; using osu.Framework.Bindables; @@ -759,7 +760,7 @@ namespace osu.Game Schedule(() => notifications.Post(new SimpleNotification { Icon = entry.Level == LogLevel.Important ? FontAwesome.Solid.ExclamationCircle : FontAwesome.Solid.Bomb, - Text = entry.Message + (entry.Exception != null && IsDeployedBuild ? "\n\nThis error has been automatically reported to the devs." : string.Empty), + Text = entry.Message.Truncate(256) + (entry.Exception != null && IsDeployedBuild ? "\n\nThis error has been automatically reported to the devs." : string.Empty), })); } else if (recentLogCount == short_term_display_limit) From 4e0f16a45059996dd0ac01ef70cf6bace62b70a7 Mon Sep 17 00:00:00 2001 From: Poliwrath Date: Fri, 24 Jul 2020 02:00:18 -0400 Subject: [PATCH 0217/1782] Add JPEG screenshot quality setting --- osu.Game/Configuration/OsuConfigManager.cs | 2 ++ osu.Game/Graphics/ScreenshotManager.cs | 5 ++++- .../Overlays/Settings/Sections/Graphics/DetailSettings.cs | 5 +++++ 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 268328272c..a45f5994b7 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -106,6 +106,7 @@ namespace osu.Game.Configuration Set(OsuSetting.Version, string.Empty); Set(OsuSetting.ScreenshotFormat, ScreenshotFormat.Jpg); + Set(OsuSetting.ScreenshotJpegQuality, 75, 0, 100); Set(OsuSetting.ScreenshotCaptureMenuCursor, false); Set(OsuSetting.SongSelectRightMouseScroll, false); @@ -212,6 +213,7 @@ namespace osu.Game.Configuration ShowConvertedBeatmaps, Skin, ScreenshotFormat, + ScreenshotJpegQuality, ScreenshotCaptureMenuCursor, SongSelectRightMouseScroll, BeatmapSkins, diff --git a/osu.Game/Graphics/ScreenshotManager.cs b/osu.Game/Graphics/ScreenshotManager.cs index 9804aefce8..091e206a80 100644 --- a/osu.Game/Graphics/ScreenshotManager.cs +++ b/osu.Game/Graphics/ScreenshotManager.cs @@ -19,6 +19,7 @@ using osu.Game.Input.Bindings; using osu.Game.Overlays; using osu.Game.Overlays.Notifications; using SixLabors.ImageSharp; +using SixLabors.ImageSharp.Formats.Jpeg; namespace osu.Game.Graphics { @@ -33,6 +34,7 @@ namespace osu.Game.Graphics public IBindable CursorVisibility => cursorVisibility; private Bindable screenshotFormat; + private Bindable screenshotJpegQuality; private Bindable captureMenuCursor; [Resolved] @@ -51,6 +53,7 @@ namespace osu.Game.Graphics this.storage = storage.GetStorageForDirectory(@"screenshots"); screenshotFormat = config.GetBindable(OsuSetting.ScreenshotFormat); + screenshotJpegQuality = config.GetBindable(OsuSetting.ScreenshotJpegQuality); captureMenuCursor = config.GetBindable(OsuSetting.ScreenshotCaptureMenuCursor); shutter = audio.Samples.Get("UI/shutter"); @@ -119,7 +122,7 @@ namespace osu.Game.Graphics break; case ScreenshotFormat.Jpg: - image.SaveAsJpeg(stream); + image.SaveAsJpeg(stream, new JpegEncoder { Quality = screenshotJpegQuality.Value }); break; default: diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/DetailSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/DetailSettings.cs index 3089040f96..8b783fb104 100644 --- a/osu.Game/Overlays/Settings/Sections/Graphics/DetailSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Graphics/DetailSettings.cs @@ -31,6 +31,11 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics LabelText = "Screenshot format", Bindable = config.GetBindable(OsuSetting.ScreenshotFormat) }, + new SettingsSlider + { + LabelText = "JPEG Screenshot quality", + Bindable = config.GetBindable(OsuSetting.ScreenshotJpegQuality) + }, new SettingsCheckbox { LabelText = "Show menu cursor in screenshots", From 05235c70c53186c5b3bceca9d8ad3963463ee9ec Mon Sep 17 00:00:00 2001 From: Poliwrath Date: Fri, 24 Jul 2020 02:26:45 -0400 Subject: [PATCH 0218/1782] remove jpeg quality setting, use 92 for quality --- osu.Game/Configuration/OsuConfigManager.cs | 2 -- osu.Game/Graphics/ScreenshotManager.cs | 6 +++--- .../Overlays/Settings/Sections/Graphics/DetailSettings.cs | 5 ----- 3 files changed, 3 insertions(+), 10 deletions(-) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index a45f5994b7..268328272c 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -106,7 +106,6 @@ namespace osu.Game.Configuration Set(OsuSetting.Version, string.Empty); Set(OsuSetting.ScreenshotFormat, ScreenshotFormat.Jpg); - Set(OsuSetting.ScreenshotJpegQuality, 75, 0, 100); Set(OsuSetting.ScreenshotCaptureMenuCursor, false); Set(OsuSetting.SongSelectRightMouseScroll, false); @@ -213,7 +212,6 @@ namespace osu.Game.Configuration ShowConvertedBeatmaps, Skin, ScreenshotFormat, - ScreenshotJpegQuality, ScreenshotCaptureMenuCursor, SongSelectRightMouseScroll, BeatmapSkins, diff --git a/osu.Game/Graphics/ScreenshotManager.cs b/osu.Game/Graphics/ScreenshotManager.cs index 091e206a80..d1f6fd445e 100644 --- a/osu.Game/Graphics/ScreenshotManager.cs +++ b/osu.Game/Graphics/ScreenshotManager.cs @@ -34,7 +34,6 @@ namespace osu.Game.Graphics public IBindable CursorVisibility => cursorVisibility; private Bindable screenshotFormat; - private Bindable screenshotJpegQuality; private Bindable captureMenuCursor; [Resolved] @@ -53,7 +52,6 @@ namespace osu.Game.Graphics this.storage = storage.GetStorageForDirectory(@"screenshots"); screenshotFormat = config.GetBindable(OsuSetting.ScreenshotFormat); - screenshotJpegQuality = config.GetBindable(OsuSetting.ScreenshotJpegQuality); captureMenuCursor = config.GetBindable(OsuSetting.ScreenshotCaptureMenuCursor); shutter = audio.Samples.Get("UI/shutter"); @@ -122,7 +120,9 @@ namespace osu.Game.Graphics break; case ScreenshotFormat.Jpg: - image.SaveAsJpeg(stream, new JpegEncoder { Quality = screenshotJpegQuality.Value }); + const int jpeg_quality = 92; + + image.SaveAsJpeg(stream, new JpegEncoder { Quality = jpeg_quality }); break; default: diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/DetailSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/DetailSettings.cs index 8b783fb104..3089040f96 100644 --- a/osu.Game/Overlays/Settings/Sections/Graphics/DetailSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Graphics/DetailSettings.cs @@ -31,11 +31,6 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics LabelText = "Screenshot format", Bindable = config.GetBindable(OsuSetting.ScreenshotFormat) }, - new SettingsSlider - { - LabelText = "JPEG Screenshot quality", - Bindable = config.GetBindable(OsuSetting.ScreenshotJpegQuality) - }, new SettingsCheckbox { LabelText = "Show menu cursor in screenshots", From 877b985e900a1c4669e04dcb16f47f760232aafd Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 24 Jul 2020 16:11:28 +0900 Subject: [PATCH 0219/1782] Remove local cancellation token --- osu.Game/Screens/Select/Details/AdvancedStats.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Select/Details/AdvancedStats.cs b/osu.Game/Screens/Select/Details/AdvancedStats.cs index 1557a025ef..44c328187f 100644 --- a/osu.Game/Screens/Select/Details/AdvancedStats.cs +++ b/osu.Game/Screens/Select/Details/AdvancedStats.cs @@ -159,10 +159,10 @@ namespace osu.Game.Screens.Select.Details if (Beatmap == null) return; - var ourSource = starDifficultyCancellationSource = new CancellationTokenSource(); + starDifficultyCancellationSource = new CancellationTokenSource(); - normalStarDifficulty = difficultyManager.GetBindableDifficulty(Beatmap, ruleset.Value, null, cancellationToken: ourSource.Token); - moddedStarDifficulty = difficultyManager.GetBindableDifficulty(Beatmap, ruleset.Value, mods.Value, ourSource.Token); + normalStarDifficulty = difficultyManager.GetBindableDifficulty(Beatmap, ruleset.Value, null, starDifficultyCancellationSource.Token); + moddedStarDifficulty = difficultyManager.GetBindableDifficulty(Beatmap, ruleset.Value, mods.Value, starDifficultyCancellationSource.Token); normalStarDifficulty.BindValueChanged(_ => updateDisplay()); moddedStarDifficulty.BindValueChanged(_ => updateDisplay(), true); From dbe9180c55c4e4d6a8991b76aa48a9a4b5f46674 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 24 Jul 2020 16:38:48 +0900 Subject: [PATCH 0220/1782] Rename class and remove screen conditionals --- osu.Desktop/OsuGameDesktop.cs | 2 +- ...KeyHandler.cs => GameplayWinKeyBlocker.cs} | 23 +++++-------------- osu.Desktop/Windows/WindowsKey.cs | 2 +- 3 files changed, 8 insertions(+), 19 deletions(-) rename osu.Desktop/Windows/{GameplayWinKeyHandler.cs => GameplayWinKeyBlocker.cs} (55%) diff --git a/osu.Desktop/OsuGameDesktop.cs b/osu.Desktop/OsuGameDesktop.cs index 6eefee3b50..2079f136d2 100644 --- a/osu.Desktop/OsuGameDesktop.cs +++ b/osu.Desktop/OsuGameDesktop.cs @@ -101,7 +101,7 @@ namespace osu.Desktop LoadComponentAsync(new DiscordRichPresence(), Add); if (RuntimeInfo.OS == RuntimeInfo.Platform.Windows) - LoadComponentAsync(new GameplayWinKeyHandler(ScreenStack), Add); + LoadComponentAsync(new GameplayWinKeyBlocker(), Add); } protected override void ScreenChanged(IScreen lastScreen, IScreen newScreen) diff --git a/osu.Desktop/Windows/GameplayWinKeyHandler.cs b/osu.Desktop/Windows/GameplayWinKeyBlocker.cs similarity index 55% rename from osu.Desktop/Windows/GameplayWinKeyHandler.cs rename to osu.Desktop/Windows/GameplayWinKeyBlocker.cs index 96154356d0..86174ceb90 100644 --- a/osu.Desktop/Windows/GameplayWinKeyHandler.cs +++ b/osu.Desktop/Windows/GameplayWinKeyBlocker.cs @@ -1,49 +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 System; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Platform; using osu.Game.Configuration; -using osu.Game.Screens; -using osu.Game.Screens.Play; namespace osu.Desktop.Windows { - public class GameplayWinKeyHandler : Component + public class GameplayWinKeyBlocker : Component { private Bindable allowScreenSuspension; private Bindable disableWinKey; - private readonly OsuScreenStack screenStack; private GameHost host; - private Type currentScreenType => screenStack.CurrentScreen?.GetType(); - - public GameplayWinKeyHandler(OsuScreenStack stack) - { - screenStack = stack; - } - [BackgroundDependencyLoader] private void load(GameHost host, OsuConfigManager config) { this.host = host; allowScreenSuspension = host.AllowScreenSuspension.GetBoundCopy(); - allowScreenSuspension.ValueChanged += toggleWinKey; + allowScreenSuspension.BindValueChanged(_ => updateBlocking()); disableWinKey = config.GetBindable(OsuSetting.GameplayDisableWinKey); - disableWinKey.BindValueChanged(t => allowScreenSuspension.TriggerChange(), true); + disableWinKey.BindValueChanged(_ => updateBlocking(), true); } - private void toggleWinKey(ValueChangedEvent e) + private void updateBlocking() { - var isPlayer = typeof(Player).IsAssignableFrom(currentScreenType) && currentScreenType != typeof(ReplayPlayer); + bool shouldDisable = disableWinKey.Value && !allowScreenSuspension.Value; - if (!e.NewValue && disableWinKey.Value && isPlayer) + if (shouldDisable) host.InputThread.Scheduler.Add(WindowsKey.Disable); else host.InputThread.Scheduler.Add(WindowsKey.Enable); diff --git a/osu.Desktop/Windows/WindowsKey.cs b/osu.Desktop/Windows/WindowsKey.cs index 4a815b135e..f19d741107 100644 --- a/osu.Desktop/Windows/WindowsKey.cs +++ b/osu.Desktop/Windows/WindowsKey.cs @@ -21,7 +21,7 @@ namespace osu.Desktop.Windows private static IntPtr keyHook; [StructLayout(LayoutKind.Explicit)] - private struct KdDllHookStruct + private readonly struct KdDllHookStruct { [FieldOffset(0)] public readonly int VkCode; From 5f98195144d1068063bfd015d2501255afcd0a34 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 24 Jul 2020 18:16:36 +0900 Subject: [PATCH 0221/1782] Load nested hitobjects during map load --- osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index f275153ce3..581617b567 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -129,9 +129,9 @@ namespace osu.Game.Rulesets.Objects.Drawables LoadSamples(); } - protected override void LoadComplete() + protected override void LoadAsyncComplete() { - base.LoadComplete(); + base.LoadAsyncComplete(); HitObject.DefaultsApplied += onDefaultsApplied; @@ -148,6 +148,11 @@ namespace osu.Game.Rulesets.Objects.Drawables samplesBindable.CollectionChanged += (_, __) => LoadSamples(); apply(HitObject); + } + + protected override void LoadComplete() + { + base.LoadComplete(); updateState(ArmedState.Idle, true); } From 2b068298cc339f3134662331037569cf08b18cd3 Mon Sep 17 00:00:00 2001 From: bastoo0 <37190278+bastoo0@users.noreply.github.com> Date: Fri, 24 Jul 2020 12:01:23 +0200 Subject: [PATCH 0222/1782] Fix inconsistency between this and osu-performance The bonus value for HD is given twice here (probably a merge issue). The correct bonus is currently used on stable: https://github.com/ppy/osu-performance/blob/736515a0347ba909d5ac303df7051b600f6655be/src/performance/catch/CatchScore.cpp#L68 --- osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs index 2ee7cea645..d700f79e5b 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs @@ -78,7 +78,6 @@ namespace osu.Game.Rulesets.Catch.Difficulty if (mods.Any(m => m is ModHidden)) { - value *= 1.05 + 0.075 * (10.0 - Math.Min(10.0, Attributes.ApproachRate)); // 7.5% for each AR below 10 // Hiddens gives almost nothing on max approach rate, and more the lower it is if (approachRate <= 10.0) value *= 1.05 + 0.075 * (10.0 - approachRate); // 7.5% for each AR below 10 From 8f841b47e68ea251a6bd7ec832007d8e50220d11 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 24 Jul 2020 19:24:20 +0900 Subject: [PATCH 0223/1782] Cancel previous initial state computations --- .../Scrolling/ScrollingHitObjectContainer.cs | 43 +++++++++++++------ 1 file changed, 30 insertions(+), 13 deletions(-) diff --git a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs index 0dc3324559..bf64175468 100644 --- a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs @@ -2,11 +2,13 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Caching; using osu.Framework.Graphics; using osu.Framework.Layout; +using osu.Framework.Threading; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Types; using osuTK; @@ -17,7 +19,7 @@ namespace osu.Game.Rulesets.UI.Scrolling { private readonly IBindable timeRange = new BindableDouble(); private readonly IBindable direction = new Bindable(); - private readonly Dictionary hitObjectInitialStateCache = new Dictionary(); + private readonly Dictionary hitObjectInitialStateCache = new Dictionary(); [Resolved] private IScrollingInfo scrollingInfo { get; set; } @@ -175,10 +177,10 @@ namespace osu.Game.Rulesets.UI.Scrolling { // The cache may not exist if the hitobject state hasn't been computed yet (e.g. if the hitobject was added + defaults applied in the same frame). // In such a case, combinedObjCache will take care of updating the hitobject. - if (hitObjectInitialStateCache.TryGetValue(drawableObject, out var objCache)) + if (hitObjectInitialStateCache.TryGetValue(drawableObject, out var state)) { combinedObjCache.Invalidate(); - objCache.Invalidate(); + state.Cache.Invalidate(); } } @@ -190,8 +192,8 @@ namespace osu.Game.Rulesets.UI.Scrolling if (!layoutCache.IsValid) { - foreach (var cached in hitObjectInitialStateCache.Values) - cached.Invalidate(); + foreach (var state in hitObjectInitialStateCache.Values) + state.Cache.Invalidate(); combinedObjCache.Invalidate(); scrollingInfo.Algorithm.Reset(); @@ -215,16 +217,18 @@ namespace osu.Game.Rulesets.UI.Scrolling foreach (var obj in Objects) { - if (!hitObjectInitialStateCache.TryGetValue(obj, out var objCache)) - objCache = hitObjectInitialStateCache[obj] = new Cached(); + if (!hitObjectInitialStateCache.TryGetValue(obj, out var state)) + state = hitObjectInitialStateCache[obj] = new InitialState(new Cached()); - if (objCache.IsValid) + if (state.Cache.IsValid) continue; - computeLifetimeStartRecursive(obj); - computeInitialStateRecursive(obj); + state.ScheduledComputation?.Cancel(); + state.ScheduledComputation = computeInitialStateRecursive(obj); - objCache.Validate(); + computeLifetimeStartRecursive(obj); + + state.Cache.Validate(); } combinedObjCache.Validate(); @@ -267,8 +271,7 @@ namespace osu.Game.Rulesets.UI.Scrolling return scrollingInfo.Algorithm.GetDisplayStartTime(hitObject.HitObject.StartTime, originAdjustment, timeRange.Value, scrollLength); } - // Cant use AddOnce() since the delegate is re-constructed every invocation - private void computeInitialStateRecursive(DrawableHitObject hitObject) => hitObject.Schedule(() => + private ScheduledDelegate computeInitialStateRecursive(DrawableHitObject hitObject) => hitObject.Schedule(() => { if (hitObject.HitObject is IHasDuration e) { @@ -325,5 +328,19 @@ namespace osu.Game.Rulesets.UI.Scrolling break; } } + + private class InitialState + { + [NotNull] + public readonly Cached Cache; + + [CanBeNull] + public ScheduledDelegate ScheduledComputation; + + public InitialState(Cached cache) + { + Cache = cache; + } + } } } From eb84f2503657fe5c8332e18c20ac9b0281d45d87 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 24 Jul 2020 19:34:13 +0900 Subject: [PATCH 0224/1782] Adjust maximum spins to roughly match stable --- osu.Game.Rulesets.Osu/Objects/Spinner.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Spinner.cs b/osu.Game.Rulesets.Osu/Objects/Spinner.cs index 1c30058d5d..9699ab9502 100644 --- a/osu.Game.Rulesets.Osu/Objects/Spinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Spinner.cs @@ -26,7 +26,10 @@ namespace osu.Game.Rulesets.Osu.Objects /// public int SpinsRequired { get; protected set; } = 1; - public int MaximumBonusSpins => SpinsRequired; + /// + /// Number of spins available to give bonus, beyond . + /// + public int MaximumBonusSpins => (int)(SpinsRequired * 1.8f); // roughly matches stable protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) { From 82e4050fddb27908c282a5f307f62b22ef62940c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 24 Jul 2020 19:41:34 +0900 Subject: [PATCH 0225/1782] Fix xmldoc --- .../Objects/Drawables/Pieces/SpinnerBonusDisplay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerBonusDisplay.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerBonusDisplay.cs index 76d7f1843e..a8f5580735 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerBonusDisplay.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerBonusDisplay.cs @@ -9,7 +9,7 @@ using osu.Game.Graphics.Sprites; namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces { /// - /// A component that tracks spinner spins and add bonus score for it. + /// Shows incremental bonus score achieved for a spinner. /// public class SpinnerBonusDisplay : CompositeDrawable { From dd45f0bd40d7aad6def77ce95ed2d2013cd03082 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 24 Jul 2020 21:03:55 +0900 Subject: [PATCH 0226/1782] Adjust max spins to "match" stable --- osu.Game.Rulesets.Osu/Objects/Spinner.cs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Spinner.cs b/osu.Game.Rulesets.Osu/Objects/Spinner.cs index 9699ab9502..2c03e6eeac 100644 --- a/osu.Game.Rulesets.Osu/Objects/Spinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Spinner.cs @@ -29,16 +29,21 @@ namespace osu.Game.Rulesets.Osu.Objects /// /// Number of spins available to give bonus, beyond . /// - public int MaximumBonusSpins => (int)(SpinsRequired * 1.8f); // roughly matches stable + public int MaximumBonusSpins { get; protected set; } = 1; protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) { base.ApplyDefaultsToSelf(controlPointInfo, difficulty); - SpinsRequired = (int)(Duration / 1000 * BeatmapDifficulty.DifficultyRange(difficulty.OverallDifficulty, 3, 5, 7.5)); + double secondsDuration = Duration / 1000; // spinning doesn't match 1:1 with stable, so let's fudge them easier for the time being. - SpinsRequired = (int)Math.Max(1, SpinsRequired * 0.6); + double minimumRotationsPerSecond = 0.6 * BeatmapDifficulty.DifficultyRange(difficulty.OverallDifficulty, 3, 5, 7.5); + + const double maximum_rotations_per_second = 8; // close to 477rpm. + + SpinsRequired = (int)Math.Max(1, (secondsDuration * minimumRotationsPerSecond)); + MaximumBonusSpins = (int)(maximum_rotations_per_second / minimumRotationsPerSecond * secondsDuration); } protected override void CreateNestedHitObjects() From a6a7961af9c2788202055efff6a9e42cfd3a7344 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 24 Jul 2020 22:09:25 +0900 Subject: [PATCH 0227/1782] Change div to subtraction to fix calculation --- osu.Game.Rulesets.Osu/Objects/Spinner.cs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Spinner.cs b/osu.Game.Rulesets.Osu/Objects/Spinner.cs index 2c03e6eeac..619b49926e 100644 --- a/osu.Game.Rulesets.Osu/Objects/Spinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Spinner.cs @@ -35,15 +35,18 @@ namespace osu.Game.Rulesets.Osu.Objects { base.ApplyDefaultsToSelf(controlPointInfo, difficulty); + // spinning doesn't match 1:1 with stable, so let's fudge them easier for the time being. + const double stable_matching_fudge = 0.6; + + // close to 477rpm + const double maximum_rotations_per_second = 8; + double secondsDuration = Duration / 1000; - // spinning doesn't match 1:1 with stable, so let's fudge them easier for the time being. - double minimumRotationsPerSecond = 0.6 * BeatmapDifficulty.DifficultyRange(difficulty.OverallDifficulty, 3, 5, 7.5); - - const double maximum_rotations_per_second = 8; // close to 477rpm. + double minimumRotationsPerSecond = stable_matching_fudge * BeatmapDifficulty.DifficultyRange(difficulty.OverallDifficulty, 3, 5, 7.5); SpinsRequired = (int)Math.Max(1, (secondsDuration * minimumRotationsPerSecond)); - MaximumBonusSpins = (int)(maximum_rotations_per_second / minimumRotationsPerSecond * secondsDuration); + MaximumBonusSpins = (int)((maximum_rotations_per_second - minimumRotationsPerSecond) * secondsDuration); } protected override void CreateNestedHitObjects() From 7e5147761fad18ea1bd904c0d718af15d8c2cc42 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 25 Jul 2020 09:26:29 +0300 Subject: [PATCH 0228/1782] Use OverlayView for FrontPageDisplay --- .../News/Displays/FrontPageDisplay.cs | 46 +++++++++---------- 1 file changed, 21 insertions(+), 25 deletions(-) diff --git a/osu.Game/Overlays/News/Displays/FrontPageDisplay.cs b/osu.Game/Overlays/News/Displays/FrontPageDisplay.cs index 67b5edfafd..2c228e2f78 100644 --- a/osu.Game/Overlays/News/Displays/FrontPageDisplay.cs +++ b/osu.Game/Overlays/News/Displays/FrontPageDisplay.cs @@ -13,18 +13,18 @@ using osuTK; namespace osu.Game.Overlays.News.Displays { - public class FrontPageDisplay : CompositeDrawable + public class FrontPageDisplay : OverlayView { - [Resolved] - private IAPIProvider api { get; set; } + protected override APIRequest CreateRequest() => new GetNewsRequest(); - private readonly FillFlowContainer content; - private readonly ShowMoreButton showMore; + private FillFlowContainer content; + private ShowMoreButton showMore; - private GetNewsRequest request; + private GetNewsRequest olderPostsRequest; private Cursor lastCursor; - public FrontPageDisplay() + [BackgroundDependencyLoader] + private void load() { RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; @@ -60,32 +60,19 @@ namespace osu.Game.Overlays.News.Displays { Top = 15 }, - Action = fetchPage, + Action = fetchOlderPosts, Alpha = 0 } } }; } - [BackgroundDependencyLoader] - private void load() - { - fetchPage(); - } - - private void fetchPage() - { - request?.Cancel(); - - request = new GetNewsRequest(lastCursor); - request.Success += response => Schedule(() => createContent(response)); - api.PerformAsync(request); - } - private CancellationTokenSource cancellationToken; - private void createContent(GetNewsResponse response) + protected override void OnSuccess(GetNewsResponse response) { + cancellationToken?.Cancel(); + lastCursor = response.Cursor; var flow = new FillFlowContainer @@ -105,9 +92,18 @@ namespace osu.Game.Overlays.News.Displays }, (cancellationToken = new CancellationTokenSource()).Token); } + private void fetchOlderPosts() + { + olderPostsRequest?.Cancel(); + + olderPostsRequest = new GetNewsRequest(lastCursor); + olderPostsRequest.Success += response => Schedule(() => OnSuccess(response)); + API.PerformAsync(olderPostsRequest); + } + protected override void Dispose(bool isDisposing) { - request?.Cancel(); + olderPostsRequest?.Cancel(); cancellationToken?.Cancel(); base.Dispose(isDisposing); } From c6ae2f520e8a7c1ea2446acf401e8a352f57028b Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 25 Jul 2020 10:39:10 +0300 Subject: [PATCH 0229/1782] Fix FrontPageDisplay is adding more news posts on api state change --- osu.Game/Overlays/News/Displays/FrontPageDisplay.cs | 2 ++ osu.Game/Overlays/OverlayView.cs | 12 +++++++++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/News/Displays/FrontPageDisplay.cs b/osu.Game/Overlays/News/Displays/FrontPageDisplay.cs index 2c228e2f78..fbacf53bf6 100644 --- a/osu.Game/Overlays/News/Displays/FrontPageDisplay.cs +++ b/osu.Game/Overlays/News/Displays/FrontPageDisplay.cs @@ -15,6 +15,8 @@ namespace osu.Game.Overlays.News.Displays { public class FrontPageDisplay : OverlayView { + protected override bool PerformFetchOnApiStateChange => false; + protected override APIRequest CreateRequest() => new GetNewsRequest(); private FillFlowContainer content; diff --git a/osu.Game/Overlays/OverlayView.cs b/osu.Game/Overlays/OverlayView.cs index 3e2c54c726..f73ca3aa6e 100644 --- a/osu.Game/Overlays/OverlayView.cs +++ b/osu.Game/Overlays/OverlayView.cs @@ -18,6 +18,11 @@ namespace osu.Game.Overlays public abstract class OverlayView : CompositeDrawable, IOnlineComponent where T : class { + /// + /// Whether we should perform fetch on api state change to online (true by default). + /// + protected virtual bool PerformFetchOnApiStateChange => true; + [Resolved] protected IAPIProvider API { get; private set; } @@ -33,6 +38,10 @@ namespace osu.Game.Overlays { base.LoadComplete(); API.Register(this); + + // If property is true - fetch will be triggered automatically by APIStateChanged and if not - we need to manually call it. + if (!PerformFetchOnApiStateChange) + PerformFetch(); } /// @@ -64,7 +73,8 @@ namespace osu.Game.Overlays switch (state) { case APIState.Online: - PerformFetch(); + if (PerformFetchOnApiStateChange) + PerformFetch(); break; } } From 897ab4a9bb2de4b5ffd2cbb9510cd74c758cfc20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 25 Jul 2020 11:53:38 +0200 Subject: [PATCH 0230/1782] Add example test resources to demonstrate fail case --- .../Resources/special-skin/pippidonclear.png | Bin 0 -> 8462 bytes .../Resources/special-skin/pippidonfail.png | Bin 0 -> 10643 bytes .../Resources/special-skin/pippidonidle.png | Bin 0 -> 10090 bytes .../Resources/special-skin/pippidonkiai.png | Bin 0 -> 12368 bytes 4 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 osu.Game.Rulesets.Taiko.Tests/Resources/special-skin/pippidonclear.png create mode 100644 osu.Game.Rulesets.Taiko.Tests/Resources/special-skin/pippidonfail.png create mode 100644 osu.Game.Rulesets.Taiko.Tests/Resources/special-skin/pippidonidle.png create mode 100644 osu.Game.Rulesets.Taiko.Tests/Resources/special-skin/pippidonkiai.png diff --git a/osu.Game.Rulesets.Taiko.Tests/Resources/special-skin/pippidonclear.png b/osu.Game.Rulesets.Taiko.Tests/Resources/special-skin/pippidonclear.png new file mode 100644 index 0000000000000000000000000000000000000000..c5bcdbd3fc13b3ca847cfba5402c2db38cd962c7 GIT binary patch literal 8462 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 literal 0 HcmV?d00001 diff --git a/osu.Game.Rulesets.Taiko.Tests/Resources/special-skin/pippidonfail.png b/osu.Game.Rulesets.Taiko.Tests/Resources/special-skin/pippidonfail.png new file mode 100644 index 0000000000000000000000000000000000000000..39cf7372851ff962b02d9184fd9169faf06c702b GIT binary patch literal 10643 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 b?-&p%=c-}*c9uXxB}hdQfLfd!P#rCUH62?6Pr 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 literal 0 HcmV?d00001 diff --git a/osu.Game.Rulesets.Taiko.Tests/Resources/special-skin/pippidonkiai.png b/osu.Game.Rulesets.Taiko.Tests/Resources/special-skin/pippidonkiai.png new file mode 100644 index 0000000000000000000000000000000000000000..7de00b5390494b7406e7595de865a8d6a659adcb GIT binary patch literal 12368 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 k73Wz0B#q`)9?)YNC#MoxTnmB!20&4eQ Date: Sat, 25 Jul 2020 12:03:54 +0200 Subject: [PATCH 0231/1782] Add support for indexless mascot texture lookups --- .../Skinning/TaikoLegacySkinTransformer.cs | 5 +---- osu.Game.Rulesets.Taiko/UI/TaikoMascotAnimation.cs | 9 ++++++++- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacySkinTransformer.cs b/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacySkinTransformer.cs index 23d675cfb0..f032c5f485 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacySkinTransformer.cs @@ -91,10 +91,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning return null; case TaikoSkinComponents.Mascot: - if (GetTexture("pippidonclear0") != null) - return new DrawableTaikoMascot(); - - return null; + return new DrawableTaikoMascot(); } return Source.GetDrawableComponent(component); diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoMascotAnimation.cs b/osu.Game.Rulesets.Taiko/UI/TaikoMascotAnimation.cs index 6f25a5f662..9c76aea54c 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoMascotAnimation.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoMascotAnimation.cs @@ -128,6 +128,13 @@ namespace osu.Game.Rulesets.Taiko.UI } private static Texture getAnimationFrame(ISkin skin, TaikoMascotAnimationState state, int frameIndex) - => skin.GetTexture($"pippidon{state.ToString().ToLower()}{frameIndex}"); + { + var texture = skin.GetTexture($"pippidon{state.ToString().ToLower()}{frameIndex}"); + + if (frameIndex == 0 && texture == null) + texture = skin.GetTexture($"pippidon{state.ToString().ToLower()}"); + + return texture; + } } } From f7a330becd68780ddbb91543350eee87b15d15a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 25 Jul 2020 12:13:19 +0200 Subject: [PATCH 0232/1782] Fix tests failing due to not checking state early enough --- .../Skinning/TestSceneDrawableTaikoMascot.cs | 25 ++++++++++++++----- 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableTaikoMascot.cs b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableTaikoMascot.cs index cb6a0decde..47d8a5c012 100644 --- a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableTaikoMascot.cs +++ b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableTaikoMascot.cs @@ -8,6 +8,7 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; +using osu.Framework.Graphics.Animations; using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; @@ -36,6 +37,10 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning private TaikoScoreProcessor scoreProcessor; private IEnumerable mascots => this.ChildrenOfType(); + + private IEnumerable animatedMascots => + mascots.Where(mascot => mascot.ChildrenOfType().All(animation => animation.FrameCount > 0)); + private IEnumerable playfields => this.ChildrenOfType(); [SetUp] @@ -72,11 +77,11 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning AddStep("set clear state", () => mascots.ForEach(mascot => mascot.State.Value = TaikoMascotAnimationState.Clear)); AddStep("miss", () => mascots.ForEach(mascot => mascot.LastResult.Value = new JudgementResult(new Hit(), new TaikoJudgement()) { Type = HitResult.Miss })); - AddAssert("skins with animations remain in clear state", () => someMascotsIn(TaikoMascotAnimationState.Clear)); + AddAssert("skins with animations remain in clear state", () => animatedMascotsIn(TaikoMascotAnimationState.Clear)); AddUntilStep("state reverts to fail", () => allMascotsIn(TaikoMascotAnimationState.Fail)); AddStep("set clear state again", () => mascots.ForEach(mascot => mascot.State.Value = TaikoMascotAnimationState.Clear)); - AddAssert("skins with animations change to clear", () => someMascotsIn(TaikoMascotAnimationState.Clear)); + AddAssert("skins with animations change to clear", () => animatedMascotsIn(TaikoMascotAnimationState.Clear)); } [Test] @@ -186,10 +191,18 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning private void assertStateAfterResult(JudgementResult judgementResult, TaikoMascotAnimationState expectedState) { - AddStep($"{judgementResult.Type.ToString().ToLower()} result for {judgementResult.Judgement.GetType().Name.Humanize(LetterCasing.LowerCase)}", - () => applyNewResult(judgementResult)); + TaikoMascotAnimationState[] mascotStates = null; - AddAssert($"state is {expectedState.ToString().ToLower()}", () => allMascotsIn(expectedState)); + AddStep($"{judgementResult.Type.ToString().ToLower()} result for {judgementResult.Judgement.GetType().Name.Humanize(LetterCasing.LowerCase)}", + () => + { + applyNewResult(judgementResult); + // store the states as soon as possible, so that the delay between steps doesn't incorrectly fail the test + // due to not checking if the state changed quickly enough. + Schedule(() => mascotStates = animatedMascots.Select(mascot => mascot.State.Value).ToArray()); + }); + + AddAssert($"state is {expectedState.ToString().ToLower()}", () => mascotStates.All(state => state == expectedState)); } private void applyNewResult(JudgementResult judgementResult) @@ -211,6 +224,6 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning } private bool allMascotsIn(TaikoMascotAnimationState state) => mascots.All(d => d.State.Value == state); - private bool someMascotsIn(TaikoMascotAnimationState state) => mascots.Any(d => d.State.Value == state); + private bool animatedMascotsIn(TaikoMascotAnimationState state) => animatedMascots.Any(d => d.State.Value == state); } } From 788395f8bf24f1702697298c31a7435897c7e7f3 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 25 Jul 2020 15:08:06 +0300 Subject: [PATCH 0233/1782] Revert changes regarding FrontPageDisplay and OverlayView --- .../News/Displays/FrontPageDisplay.cs | 35 ++++++++++--------- osu.Game/Overlays/OverlayView.cs | 12 +------ 2 files changed, 19 insertions(+), 28 deletions(-) diff --git a/osu.Game/Overlays/News/Displays/FrontPageDisplay.cs b/osu.Game/Overlays/News/Displays/FrontPageDisplay.cs index fbacf53bf6..0f177f151a 100644 --- a/osu.Game/Overlays/News/Displays/FrontPageDisplay.cs +++ b/osu.Game/Overlays/News/Displays/FrontPageDisplay.cs @@ -13,16 +13,15 @@ using osuTK; namespace osu.Game.Overlays.News.Displays { - public class FrontPageDisplay : OverlayView + public class FrontPageDisplay : CompositeDrawable { - protected override bool PerformFetchOnApiStateChange => false; - - protected override APIRequest CreateRequest() => new GetNewsRequest(); + [Resolved] + private IAPIProvider api { get; set; } private FillFlowContainer content; private ShowMoreButton showMore; - private GetNewsRequest olderPostsRequest; + private GetNewsRequest request; private Cursor lastCursor; [BackgroundDependencyLoader] @@ -62,16 +61,27 @@ namespace osu.Game.Overlays.News.Displays { Top = 15 }, - Action = fetchOlderPosts, + Action = performFetch, Alpha = 0 } } }; + + performFetch(); + } + + private void performFetch() + { + request?.Cancel(); + + request = new GetNewsRequest(lastCursor); + request.Success += response => Schedule(() => onSuccess(response)); + api.PerformAsync(request); } private CancellationTokenSource cancellationToken; - protected override void OnSuccess(GetNewsResponse response) + private void onSuccess(GetNewsResponse response) { cancellationToken?.Cancel(); @@ -94,18 +104,9 @@ namespace osu.Game.Overlays.News.Displays }, (cancellationToken = new CancellationTokenSource()).Token); } - private void fetchOlderPosts() - { - olderPostsRequest?.Cancel(); - - olderPostsRequest = new GetNewsRequest(lastCursor); - olderPostsRequest.Success += response => Schedule(() => OnSuccess(response)); - API.PerformAsync(olderPostsRequest); - } - protected override void Dispose(bool isDisposing) { - olderPostsRequest?.Cancel(); + request?.Cancel(); cancellationToken?.Cancel(); base.Dispose(isDisposing); } diff --git a/osu.Game/Overlays/OverlayView.cs b/osu.Game/Overlays/OverlayView.cs index f73ca3aa6e..3e2c54c726 100644 --- a/osu.Game/Overlays/OverlayView.cs +++ b/osu.Game/Overlays/OverlayView.cs @@ -18,11 +18,6 @@ namespace osu.Game.Overlays public abstract class OverlayView : CompositeDrawable, IOnlineComponent where T : class { - /// - /// Whether we should perform fetch on api state change to online (true by default). - /// - protected virtual bool PerformFetchOnApiStateChange => true; - [Resolved] protected IAPIProvider API { get; private set; } @@ -38,10 +33,6 @@ namespace osu.Game.Overlays { base.LoadComplete(); API.Register(this); - - // If property is true - fetch will be triggered automatically by APIStateChanged and if not - we need to manually call it. - if (!PerformFetchOnApiStateChange) - PerformFetch(); } /// @@ -73,8 +64,7 @@ namespace osu.Game.Overlays switch (state) { case APIState.Online: - if (PerformFetchOnApiStateChange) - PerformFetch(); + PerformFetch(); break; } } From 648f9204f5c59a992080f7bd9d9777a4852ce7ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 26 Jul 2020 15:09:12 +0200 Subject: [PATCH 0234/1782] Add sample lifetime constraints for taiko --- .../Audio/DrumSampleContainer.cs | 64 +++++++++++++++++++ .../Audio/DrumSampleMapping.cs | 52 --------------- .../Skinning/LegacyInputDrum.cs | 4 +- osu.Game.Rulesets.Taiko/UI/InputDrum.cs | 10 +-- osu.Game/Skinning/SkinnableSound.cs | 3 + 5 files changed, 74 insertions(+), 59 deletions(-) create mode 100644 osu.Game.Rulesets.Taiko/Audio/DrumSampleContainer.cs delete mode 100644 osu.Game.Rulesets.Taiko/Audio/DrumSampleMapping.cs diff --git a/osu.Game.Rulesets.Taiko/Audio/DrumSampleContainer.cs b/osu.Game.Rulesets.Taiko/Audio/DrumSampleContainer.cs new file mode 100644 index 0000000000..7c39c040b1 --- /dev/null +++ b/osu.Game.Rulesets.Taiko/Audio/DrumSampleContainer.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 osu.Framework.Graphics.Containers; +using osu.Game.Audio; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Skinning; + +namespace osu.Game.Rulesets.Taiko.Audio +{ + /// + /// Stores samples for the input drum. + /// The lifetime of the samples is adjusted so that they are only alive during the appropriate sample control point. + /// + public class DrumSampleContainer : LifetimeManagementContainer + { + private readonly ControlPointInfo controlPoints; + private readonly Dictionary mappings = new Dictionary(); + + public DrumSampleContainer(ControlPointInfo controlPoints) + { + this.controlPoints = controlPoints; + + IReadOnlyList samplePoints = controlPoints.SamplePoints.Count == 0 ? new[] { controlPoints.SamplePointAt(double.MinValue) } : controlPoints.SamplePoints; + + for (int i = 0; i < samplePoints.Count; i++) + { + var samplePoint = samplePoints[i]; + + var centre = samplePoint.GetSampleInfo(); + var rim = samplePoint.GetSampleInfo(HitSampleInfo.HIT_CLAP); + + var lifetimeStart = i > 0 ? samplePoint.Time : double.MinValue; + var lifetimeEnd = i + 1 < samplePoints.Count ? samplePoints[i + 1].Time : double.MaxValue; + + mappings[samplePoint.Time] = new DrumSample + { + Centre = addSound(centre, lifetimeStart, lifetimeEnd), + Rim = addSound(rim, lifetimeStart, lifetimeEnd) + }; + } + } + + private SkinnableSound addSound(HitSampleInfo hitSampleInfo, double lifetimeStart, double lifetimeEnd) + { + var drawable = new SkinnableSound(hitSampleInfo) + { + LifetimeStart = lifetimeStart, + LifetimeEnd = lifetimeEnd + }; + AddInternal(drawable); + return drawable; + } + + public DrumSample SampleAt(double time) => mappings[controlPoints.SamplePointAt(time).Time]; + + public class DrumSample + { + public SkinnableSound Centre; + public SkinnableSound Rim; + } + } +} diff --git a/osu.Game.Rulesets.Taiko/Audio/DrumSampleMapping.cs b/osu.Game.Rulesets.Taiko/Audio/DrumSampleMapping.cs deleted file mode 100644 index c31b07344d..0000000000 --- a/osu.Game.Rulesets.Taiko/Audio/DrumSampleMapping.cs +++ /dev/null @@ -1,52 +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.Audio; -using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Skinning; - -namespace osu.Game.Rulesets.Taiko.Audio -{ - public class DrumSampleMapping - { - private readonly ControlPointInfo controlPoints; - private readonly Dictionary mappings = new Dictionary(); - - public readonly List Sounds = new List(); - - public DrumSampleMapping(ControlPointInfo controlPoints) - { - this.controlPoints = controlPoints; - - IEnumerable samplePoints = controlPoints.SamplePoints.Count == 0 ? new[] { controlPoints.SamplePointAt(double.MinValue) } : controlPoints.SamplePoints; - - foreach (var s in samplePoints) - { - var centre = s.GetSampleInfo(); - var rim = s.GetSampleInfo(HitSampleInfo.HIT_CLAP); - - mappings[s.Time] = new DrumSample - { - Centre = addSound(centre), - Rim = addSound(rim) - }; - } - } - - private SkinnableSound addSound(HitSampleInfo hitSampleInfo) - { - var drawable = new SkinnableSound(hitSampleInfo); - Sounds.Add(drawable); - return drawable; - } - - public DrumSample SampleAt(double time) => mappings[controlPoints.SamplePointAt(time).Time]; - - public class DrumSample - { - public SkinnableSound Centre; - public SkinnableSound Rim; - } - } -} diff --git a/osu.Game.Rulesets.Taiko/Skinning/LegacyInputDrum.cs b/osu.Game.Rulesets.Taiko/Skinning/LegacyInputDrum.cs index 81d645e294..b7b55b9ae0 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/LegacyInputDrum.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/LegacyInputDrum.cs @@ -111,7 +111,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning public readonly Sprite Centre; [Resolved] - private DrumSampleMapping sampleMappings { get; set; } + private DrumSampleContainer sampleContainer { get; set; } public LegacyHalfDrum(bool flipped) { @@ -143,7 +143,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning public bool OnPressed(TaikoAction action) { Drawable target = null; - var drumSample = sampleMappings.SampleAt(Time.Current); + var drumSample = sampleContainer.SampleAt(Time.Current); if (action == CentreAction) { diff --git a/osu.Game.Rulesets.Taiko/UI/InputDrum.cs b/osu.Game.Rulesets.Taiko/UI/InputDrum.cs index 06ccd45cb8..f76f3d851a 100644 --- a/osu.Game.Rulesets.Taiko/UI/InputDrum.cs +++ b/osu.Game.Rulesets.Taiko/UI/InputDrum.cs @@ -25,11 +25,11 @@ namespace osu.Game.Rulesets.Taiko.UI private const float middle_split = 0.025f; [Cached] - private DrumSampleMapping sampleMapping; + private DrumSampleContainer sampleContainer; public InputDrum(ControlPointInfo controlPoints) { - sampleMapping = new DrumSampleMapping(controlPoints); + sampleContainer = new DrumSampleContainer(controlPoints); RelativeSizeAxes = Axes.Both; } @@ -69,7 +69,7 @@ namespace osu.Game.Rulesets.Taiko.UI } }); - AddRangeInternal(sampleMapping.Sounds); + AddRangeInternal(sampleContainer.Sounds); } /// @@ -93,7 +93,7 @@ namespace osu.Game.Rulesets.Taiko.UI private readonly Sprite centreHit; [Resolved] - private DrumSampleMapping sampleMappings { get; set; } + private DrumSampleContainer sampleContainer { get; set; } public TaikoHalfDrum(bool flipped) { @@ -154,7 +154,7 @@ namespace osu.Game.Rulesets.Taiko.UI Drawable target = null; Drawable back = null; - var drumSample = sampleMappings.SampleAt(Time.Current); + var drumSample = sampleContainer.SampleAt(Time.Current); if (action == CentreAction) { diff --git a/osu.Game/Skinning/SkinnableSound.cs b/osu.Game/Skinning/SkinnableSound.cs index 49f9f01cff..fb9cab74c8 100644 --- a/osu.Game/Skinning/SkinnableSound.cs +++ b/osu.Game/Skinning/SkinnableSound.cs @@ -22,6 +22,9 @@ namespace osu.Game.Skinning [Resolved] private ISampleStore samples { get; set; } + public override bool RemoveWhenNotAlive => false; + public override bool RemoveCompletedTransforms => false; + public SkinnableSound(ISampleInfo hitSamples) : this(new[] { hitSamples }) { From 8e6a0493b4f972f2d577c91b0f8cecfd6a74ba8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 26 Jul 2020 15:15:01 +0200 Subject: [PATCH 0235/1782] Adjust InputDrum usage --- osu.Game.Rulesets.Taiko/UI/InputDrum.cs | 60 +++++++++++++------------ 1 file changed, 31 insertions(+), 29 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/UI/InputDrum.cs b/osu.Game.Rulesets.Taiko/UI/InputDrum.cs index f76f3d851a..5966b24b34 100644 --- a/osu.Game.Rulesets.Taiko/UI/InputDrum.cs +++ b/osu.Game.Rulesets.Taiko/UI/InputDrum.cs @@ -37,39 +37,41 @@ namespace osu.Game.Rulesets.Taiko.UI [BackgroundDependencyLoader] private void load() { - Child = new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.InputDrum), _ => new Container + Children = new Drawable[] { - RelativeSizeAxes = Axes.Both, - FillMode = FillMode.Fit, - Scale = new Vector2(0.9f), - Children = new Drawable[] + new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.InputDrum), _ => new Container { - new TaikoHalfDrum(false) + RelativeSizeAxes = Axes.Both, + FillMode = FillMode.Fit, + Scale = new Vector2(0.9f), + Children = new Drawable[] { - Name = "Left Half", - Anchor = Anchor.Centre, - Origin = Anchor.CentreRight, - RelativeSizeAxes = Axes.Both, - RelativePositionAxes = Axes.X, - X = -middle_split / 2, - RimAction = TaikoAction.LeftRim, - CentreAction = TaikoAction.LeftCentre - }, - new TaikoHalfDrum(true) - { - Name = "Right Half", - Anchor = Anchor.Centre, - Origin = Anchor.CentreLeft, - RelativeSizeAxes = Axes.Both, - RelativePositionAxes = Axes.X, - X = middle_split / 2, - RimAction = TaikoAction.RightRim, - CentreAction = TaikoAction.RightCentre + new TaikoHalfDrum(false) + { + Name = "Left Half", + Anchor = Anchor.Centre, + Origin = Anchor.CentreRight, + RelativeSizeAxes = Axes.Both, + RelativePositionAxes = Axes.X, + X = -middle_split / 2, + RimAction = TaikoAction.LeftRim, + CentreAction = TaikoAction.LeftCentre + }, + new TaikoHalfDrum(true) + { + Name = "Right Half", + Anchor = Anchor.Centre, + Origin = Anchor.CentreLeft, + RelativeSizeAxes = Axes.Both, + RelativePositionAxes = Axes.X, + X = middle_split / 2, + RimAction = TaikoAction.RightRim, + CentreAction = TaikoAction.RightCentre + } } - } - }); - - AddRangeInternal(sampleContainer.Sounds); + }), + sampleContainer + }; } /// From c78c346b627e7fa89ea99c44a521216812ed5012 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 27 Jul 2020 14:11:01 +0900 Subject: [PATCH 0236/1782] 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 e5b0245dd0..7e6f1469f5 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -51,7 +51,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 5af28ae11a..ab434def38 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 4a94ec33d8..618de5d19f 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -71,7 +71,7 @@ - + From 5b724d96597a8fabf89b8813fafc1ce0fd0a869e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 27 Jul 2020 15:10:32 +0900 Subject: [PATCH 0237/1782] Adjust damp base component to provide ample tweening --- osu.Game/Screens/Menu/OsuLogo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Menu/OsuLogo.cs b/osu.Game/Screens/Menu/OsuLogo.cs index 34d49685d2..f5e4b078da 100644 --- a/osu.Game/Screens/Menu/OsuLogo.cs +++ b/osu.Game/Screens/Menu/OsuLogo.cs @@ -330,7 +330,7 @@ namespace osu.Game.Screens.Menu if (Beatmap.Value.Track.IsRunning) { var maxAmplitude = lastBeatIndex >= 0 ? Beatmap.Value.Track.CurrentAmplitudes.Maximum : 0; - logoAmplitudeContainer.Scale = new Vector2((float)Interpolation.Damp(logoAmplitudeContainer.Scale.X, 1 - Math.Max(0, maxAmplitude - scale_adjust_cutoff) * 0.04f, 0.5f, Time.Elapsed)); + logoAmplitudeContainer.Scale = new Vector2((float)Interpolation.Damp(logoAmplitudeContainer.Scale.X, 1 - Math.Max(0, maxAmplitude - scale_adjust_cutoff) * 0.04f, 0.9f, Time.Elapsed)); if (maxAmplitude > velocity_adjust_cutoff) triangles.Velocity = 1 + Math.Max(0, maxAmplitude - velocity_adjust_cutoff) * 50; From 3257c1e9f21bed20047f47c98a6aed7dc4c2107c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 27 Jul 2020 16:02:52 +0900 Subject: [PATCH 0238/1782] Move interface exposing into region --- osu.Game/Skinning/SkinnableSound.cs | 76 +++++++++++++++-------------- 1 file changed, 40 insertions(+), 36 deletions(-) diff --git a/osu.Game/Skinning/SkinnableSound.cs b/osu.Game/Skinning/SkinnableSound.cs index fb9cab74c8..11d1011ed8 100644 --- a/osu.Game/Skinning/SkinnableSound.cs +++ b/osu.Game/Skinning/SkinnableSound.cs @@ -40,42 +40,6 @@ namespace osu.Game.Skinning private readonly AudioContainer samplesContainer; - public BindableNumber Volume => samplesContainer.Volume; - - public BindableNumber Balance => samplesContainer.Balance; - - public BindableNumber Frequency => samplesContainer.Frequency; - - public BindableNumber Tempo => samplesContainer.Tempo; - - /// - /// Smoothly adjusts over time. - /// - /// A to which further transforms can be added. - public TransformSequence VolumeTo(double newVolume, double duration = 0, Easing easing = Easing.None) => - samplesContainer.VolumeTo(newVolume, duration, easing); - - /// - /// Smoothly adjusts over time. - /// - /// A to which further transforms can be added. - public TransformSequence BalanceTo(double newBalance, double duration = 0, Easing easing = Easing.None) => - samplesContainer.BalanceTo(newBalance, duration, easing); - - /// - /// Smoothly adjusts over time. - /// - /// A to which further transforms can be added. - public TransformSequence FrequencyTo(double newFrequency, double duration = 0, Easing easing = Easing.None) => - samplesContainer.FrequencyTo(newFrequency, duration, easing); - - /// - /// Smoothly adjusts over time. - /// - /// A to which further transforms can be added. - public TransformSequence TempoTo(double newTempo, double duration = 0, Easing easing = Easing.None) => - samplesContainer.TempoTo(newTempo, duration, easing); - public bool Looping { get => looping; @@ -130,5 +94,45 @@ namespace osu.Game.Skinning if (wasPlaying) Play(); } + + #region Re-expose AudioContainer + + public BindableNumber Volume => samplesContainer.Volume; + + public BindableNumber Balance => samplesContainer.Balance; + + public BindableNumber Frequency => samplesContainer.Frequency; + + public BindableNumber Tempo => samplesContainer.Tempo; + + /// + /// Smoothly adjusts over time. + /// + /// A to which further transforms can be added. + public TransformSequence VolumeTo(double newVolume, double duration = 0, Easing easing = Easing.None) => + samplesContainer.VolumeTo(newVolume, duration, easing); + + /// + /// Smoothly adjusts over time. + /// + /// A to which further transforms can be added. + public TransformSequence BalanceTo(double newBalance, double duration = 0, Easing easing = Easing.None) => + samplesContainer.BalanceTo(newBalance, duration, easing); + + /// + /// Smoothly adjusts over time. + /// + /// A to which further transforms can be added. + public TransformSequence FrequencyTo(double newFrequency, double duration = 0, Easing easing = Easing.None) => + samplesContainer.FrequencyTo(newFrequency, duration, easing); + + /// + /// Smoothly adjusts over time. + /// + /// A to which further transforms can be added. + public TransformSequence TempoTo(double newTempo, double duration = 0, Easing easing = Easing.None) => + samplesContainer.TempoTo(newTempo, duration, easing); + + #endregion } } From 9889bfa0f3b8a50765ec611b2a54bae898e54dcb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 27 Jul 2020 16:15:49 +0900 Subject: [PATCH 0239/1782] Stop playing samples on pause, resume looping on unpause --- osu.Game/Skinning/SkinnableSound.cs | 60 ++++++++++++++++++++++++----- 1 file changed, 50 insertions(+), 10 deletions(-) diff --git a/osu.Game/Skinning/SkinnableSound.cs b/osu.Game/Skinning/SkinnableSound.cs index 11d1011ed8..f54eff51c2 100644 --- a/osu.Game/Skinning/SkinnableSound.cs +++ b/osu.Game/Skinning/SkinnableSound.cs @@ -12,6 +12,7 @@ using osu.Framework.Graphics.Audio; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Transforms; using osu.Game.Audio; +using osu.Game.Screens.Play; namespace osu.Game.Skinning { @@ -22,9 +23,13 @@ namespace osu.Game.Skinning [Resolved] private ISampleStore samples { get; set; } + private bool requestedPlaying; + public override bool RemoveWhenNotAlive => false; public override bool RemoveCompletedTransforms => false; + private readonly AudioContainer samplesContainer; + public SkinnableSound(ISampleInfo hitSamples) : this(new[] { hitSamples }) { @@ -36,9 +41,28 @@ namespace osu.Game.Skinning InternalChild = samplesContainer = new AudioContainer(); } - private bool looping; + private Bindable gameplayClockPaused; - private readonly AudioContainer samplesContainer; + [BackgroundDependencyLoader(true)] + private void load(GameplayClock gameplayClock) + { + // if in a gameplay context, pause sample playback when gameplay is paused. + gameplayClockPaused = gameplayClock?.IsPaused.GetBoundCopy(); + gameplayClockPaused?.BindValueChanged(paused => + { + if (requestedPlaying) + { + if (paused.NewValue) + stop(); + // it's not easy to know if a sample has finished playing (to end). + // to keep things simple only resume playing looping samples. + else if (Looping) + play(); + } + }); + } + + private bool looping; public bool Looping { @@ -53,20 +77,36 @@ namespace osu.Game.Skinning } } - public void Play() => samplesContainer.ForEach(c => + public void Play() { - if (c.AggregateVolume.Value > 0) - c.Play(); - }); + requestedPlaying = true; + play(); + } - public void Stop() => samplesContainer.ForEach(c => c.Stop()); + private void play() + { + samplesContainer.ForEach(c => + { + if (c.AggregateVolume.Value > 0) + c.Play(); + }); + } + + public void Stop() + { + requestedPlaying = false; + stop(); + } + + private void stop() + { + samplesContainer.ForEach(c => c.Stop()); + } public override bool IsPresent => Scheduler.HasPendingTasks; protected override void SkinChanged(ISkinSource skin, bool allowFallback) { - bool wasPlaying = samplesContainer.Any(s => s.Playing); - var channels = hitSamples.Select(s => { var ch = skin.GetSample(s); @@ -91,7 +131,7 @@ namespace osu.Game.Skinning samplesContainer.ChildrenEnumerable = channels.Select(c => new DrawableSample(c)); - if (wasPlaying) + if (requestedPlaying) Play(); } From 5e7237bf567cb5cf1539acb1d6ef05427a490b5a Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 27 Jul 2020 10:29:16 +0300 Subject: [PATCH 0240/1782] Fix incorrect default hitcircle font overlapping applied to legacy skins --- osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs index 3e5758ca01..95ef2d58b1 100644 --- a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs @@ -92,7 +92,7 @@ namespace osu.Game.Rulesets.Osu.Skinning case OsuSkinComponents.HitCircleText: var font = GetConfig(OsuSkinConfiguration.HitCirclePrefix)?.Value ?? "default"; - var overlap = GetConfig(OsuSkinConfiguration.HitCircleOverlap)?.Value ?? 0; + var overlap = GetConfig(OsuSkinConfiguration.HitCircleOverlap)?.Value ?? -2; return !hasFont(font) ? null From d8f4e044de943c0e26603ee1536f631b7db95036 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 27 Jul 2020 17:46:10 +0900 Subject: [PATCH 0241/1782] Add test coverage --- .../Gameplay/TestSceneSkinnableSound.cs | 87 +++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs new file mode 100644 index 0000000000..1128b17303 --- /dev/null +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs @@ -0,0 +1,87 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Audio; +using osu.Framework.Graphics.Containers; +using osu.Framework.Testing; +using osu.Framework.Timing; +using osu.Game.Audio; +using osu.Game.Screens.Play; +using osu.Game.Skinning; + +namespace osu.Game.Tests.Visual.Gameplay +{ + public class TestSceneSkinnableSound : OsuTestScene + { + [Cached] + private GameplayClock gameplayClock = new GameplayClock(new FramedClock()); + + private SkinnableSound skinnableSounds; + + [SetUp] + public void SetUp() + { + Children = new Drawable[] + { + new Container + { + Clock = gameplayClock, + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + skinnableSounds = new SkinnableSound(new SampleInfo("normal-sliderslide")) + { + Looping = true + } + } + }, + }; + } + + [Test] + public void TestStoppedSoundDoesntResumeAfterPause() + { + DrawableSample sample = null; + AddStep("start sample", () => + { + skinnableSounds.Play(); + sample = skinnableSounds.ChildrenOfType().First(); + }); + + AddUntilStep("wait for sample to start playing", () => sample.Playing); + + AddStep("stop sample", () => skinnableSounds.Stop()); + + AddUntilStep("wait for sample to stop playing", () => !sample.Playing); + + AddStep("pause gameplay clock", () => gameplayClock.IsPaused.Value = true); + AddStep("resume gameplay clock", () => gameplayClock.IsPaused.Value = false); + + AddWaitStep("wait a bit", 5); + AddAssert("sample not playing", () => !sample.Playing); + } + + [Test] + public void TestLoopingSoundResumesAfterPause() + { + DrawableSample sample = null; + AddStep("start sample", () => + { + skinnableSounds.Play(); + sample = skinnableSounds.ChildrenOfType().First(); + }); + + AddUntilStep("wait for sample to start playing", () => sample.Playing); + + AddStep("pause gameplay clock", () => gameplayClock.IsPaused.Value = true); + AddUntilStep("wait for sample to stop playing", () => !sample.Playing); + + AddStep("resume gameplay clock", () => gameplayClock.IsPaused.Value = false); + AddUntilStep("wait for sample to start playing", () => sample.Playing); + } + } +} From 12368ace3b421382420ed1ef41684f6f02a4dfcd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 27 Jul 2020 17:46:44 +0900 Subject: [PATCH 0242/1782] Rename variable --- .../Visual/Gameplay/TestSceneSkinnableSound.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs index 1128b17303..73704faa1f 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs @@ -20,7 +20,7 @@ namespace osu.Game.Tests.Visual.Gameplay [Cached] private GameplayClock gameplayClock = new GameplayClock(new FramedClock()); - private SkinnableSound skinnableSounds; + private SkinnableSound skinnableSound; [SetUp] public void SetUp() @@ -33,7 +33,7 @@ namespace osu.Game.Tests.Visual.Gameplay RelativeSizeAxes = Axes.Both, Children = new Drawable[] { - skinnableSounds = new SkinnableSound(new SampleInfo("normal-sliderslide")) + skinnableSound = new SkinnableSound(new SampleInfo("normal-sliderslide")) { Looping = true } @@ -48,13 +48,13 @@ namespace osu.Game.Tests.Visual.Gameplay DrawableSample sample = null; AddStep("start sample", () => { - skinnableSounds.Play(); - sample = skinnableSounds.ChildrenOfType().First(); + skinnableSound.Play(); + sample = skinnableSound.ChildrenOfType().First(); }); AddUntilStep("wait for sample to start playing", () => sample.Playing); - AddStep("stop sample", () => skinnableSounds.Stop()); + AddStep("stop sample", () => skinnableSound.Stop()); AddUntilStep("wait for sample to stop playing", () => !sample.Playing); @@ -71,8 +71,8 @@ namespace osu.Game.Tests.Visual.Gameplay DrawableSample sample = null; AddStep("start sample", () => { - skinnableSounds.Play(); - sample = skinnableSounds.ChildrenOfType().First(); + skinnableSound.Play(); + sample = skinnableSound.ChildrenOfType().First(); }); AddUntilStep("wait for sample to start playing", () => sample.Playing); From 5fd73795f629bcf4c48379410f8c7d538c4494ef Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 27 Jul 2020 18:02:14 +0900 Subject: [PATCH 0243/1782] Remove wait steps and add coverage of non-looping sounds --- .../Gameplay/TestSceneSkinnableSound.cs | 42 ++++++++++++------- 1 file changed, 28 insertions(+), 14 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs index 73704faa1f..5b7704122b 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs @@ -31,13 +31,7 @@ namespace osu.Game.Tests.Visual.Gameplay { Clock = gameplayClock, RelativeSizeAxes = Axes.Both, - Children = new Drawable[] - { - skinnableSound = new SkinnableSound(new SampleInfo("normal-sliderslide")) - { - Looping = true - } - } + Child = skinnableSound = new SkinnableSound(new SampleInfo("normal-sliderslide")) }, }; } @@ -46,27 +40,47 @@ namespace osu.Game.Tests.Visual.Gameplay public void TestStoppedSoundDoesntResumeAfterPause() { DrawableSample sample = null; - AddStep("start sample", () => + AddStep("start sample with looping", () => { + skinnableSound.Looping = true; skinnableSound.Play(); sample = skinnableSound.ChildrenOfType().First(); }); - AddUntilStep("wait for sample to start playing", () => sample.Playing); + AddAssert("sample playing", () => sample.Playing); AddStep("stop sample", () => skinnableSound.Stop()); - AddUntilStep("wait for sample to stop playing", () => !sample.Playing); + AddAssert("sample not playing", () => !sample.Playing); AddStep("pause gameplay clock", () => gameplayClock.IsPaused.Value = true); AddStep("resume gameplay clock", () => gameplayClock.IsPaused.Value = false); - AddWaitStep("wait a bit", 5); AddAssert("sample not playing", () => !sample.Playing); } [Test] public void TestLoopingSoundResumesAfterPause() + { + DrawableSample sample = null; + AddStep("start sample with looping", () => + { + skinnableSound.Looping = true; + skinnableSound.Play(); + sample = skinnableSound.ChildrenOfType().First(); + }); + + AddAssert("sample playing", () => sample.Playing); + + AddStep("pause gameplay clock", () => gameplayClock.IsPaused.Value = true); + AddAssert("sample not playing", () => !sample.Playing); + + AddStep("resume gameplay clock", () => gameplayClock.IsPaused.Value = false); + AddUntilStep("wait for sample to start playing", () => sample.Playing); + } + + [Test] + public void TestNonLoopingStopsWithPause() { DrawableSample sample = null; AddStep("start sample", () => @@ -75,13 +89,13 @@ namespace osu.Game.Tests.Visual.Gameplay sample = skinnableSound.ChildrenOfType().First(); }); - AddUntilStep("wait for sample to start playing", () => sample.Playing); + AddAssert("sample playing", () => sample.Playing); AddStep("pause gameplay clock", () => gameplayClock.IsPaused.Value = true); - AddUntilStep("wait for sample to stop playing", () => !sample.Playing); + AddAssert("sample not playing", () => !sample.Playing); AddStep("resume gameplay clock", () => gameplayClock.IsPaused.Value = false); - AddUntilStep("wait for sample to start playing", () => sample.Playing); + AddAssert("sample not playing", () => !sample.Playing); } } } From 10101d5b31da4499d99f4564fc83dbc709ec7f4b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 27 Jul 2020 18:06:14 +0900 Subject: [PATCH 0244/1782] Reduce spinner tick and bonus score --- osu.Game.Rulesets.Osu/Objects/SpinnerBonusTick.cs | 2 +- osu.Game.Rulesets.Osu/Objects/SpinnerTick.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/SpinnerBonusTick.cs b/osu.Game.Rulesets.Osu/Objects/SpinnerBonusTick.cs index 6ca2d4d72d..b59428e701 100644 --- a/osu.Game.Rulesets.Osu/Objects/SpinnerBonusTick.cs +++ b/osu.Game.Rulesets.Osu/Objects/SpinnerBonusTick.cs @@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Osu.Objects public class OsuSpinnerBonusTickJudgement : OsuSpinnerTickJudgement { - protected override int NumericResultFor(HitResult result) => 1100; + protected override int NumericResultFor(HitResult result) => 50; protected override double HealthIncreaseFor(HitResult result) => base.HealthIncreaseFor(result) * 2; } diff --git a/osu.Game.Rulesets.Osu/Objects/SpinnerTick.cs b/osu.Game.Rulesets.Osu/Objects/SpinnerTick.cs index c81348fbbf..346f949a4f 100644 --- a/osu.Game.Rulesets.Osu/Objects/SpinnerTick.cs +++ b/osu.Game.Rulesets.Osu/Objects/SpinnerTick.cs @@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Osu.Objects { public override bool AffectsCombo => false; - protected override int NumericResultFor(HitResult result) => 100; + protected override int NumericResultFor(HitResult result) => 10; protected override double HealthIncreaseFor(HitResult result) => result == MaxResult ? 0.6 * base.HealthIncreaseFor(result) : 0; } From 06c4fb717146b999ed2496c2dadfb48fe6e0b404 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 27 Jul 2020 18:40:53 +0900 Subject: [PATCH 0245/1782] Update bonus score spec in test --- 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 6e277ff37e..23b440ced2 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs @@ -142,7 +142,7 @@ namespace osu.Game.Rulesets.Osu.Tests { // multipled by 2 to nullify the score multiplier. (autoplay mod selected) var totalScore = ((ScoreExposedPlayer)Player).ScoreProcessor.TotalScore.Value * 2; - return totalScore == (int)(drawableSpinner.Disc.CumulativeRotation / 360) * 100; + return totalScore == (int)(drawableSpinner.Disc.CumulativeRotation / 360) * 10; }); addSeekStep(0); From 1f8abf2cf6a085fe99838dda46835e2c1eaaf107 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 27 Jul 2020 19:03:21 +0900 Subject: [PATCH 0246/1782] Fix headless tests --- osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs index 5b7704122b..9cfea8ec85 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs @@ -23,7 +23,7 @@ namespace osu.Game.Tests.Visual.Gameplay private SkinnableSound skinnableSound; [SetUp] - public void SetUp() + public void SetUp() => Schedule(() => { Children = new Drawable[] { @@ -34,7 +34,7 @@ namespace osu.Game.Tests.Visual.Gameplay Child = skinnableSound = new SkinnableSound(new SampleInfo("normal-sliderslide")) }, }; - } + }); [Test] public void TestStoppedSoundDoesntResumeAfterPause() From 33e8e0aa187ccbc8cdbdf1f5da8e6da5e4271c13 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 27 Jul 2020 19:05:31 +0900 Subject: [PATCH 0247/1782] Add back until steps so headless tests can better handle thread delays --- .../Visual/Gameplay/TestSceneSkinnableSound.cs | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs index 9cfea8ec85..81e5f32ee8 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs @@ -47,15 +47,16 @@ namespace osu.Game.Tests.Visual.Gameplay sample = skinnableSound.ChildrenOfType().First(); }); - AddAssert("sample playing", () => sample.Playing); + AddUntilStep("wait for sample to start playing", () => sample.Playing); AddStep("stop sample", () => skinnableSound.Stop()); - AddAssert("sample not playing", () => !sample.Playing); + AddUntilStep("wait for sample to stop playing", () => !sample.Playing); AddStep("pause gameplay clock", () => gameplayClock.IsPaused.Value = true); AddStep("resume gameplay clock", () => gameplayClock.IsPaused.Value = false); + AddWaitStep("wait a bit", 5); AddAssert("sample not playing", () => !sample.Playing); } @@ -70,13 +71,10 @@ namespace osu.Game.Tests.Visual.Gameplay sample = skinnableSound.ChildrenOfType().First(); }); - AddAssert("sample playing", () => sample.Playing); + AddUntilStep("wait for sample to start playing", () => sample.Playing); AddStep("pause gameplay clock", () => gameplayClock.IsPaused.Value = true); - AddAssert("sample not playing", () => !sample.Playing); - - AddStep("resume gameplay clock", () => gameplayClock.IsPaused.Value = false); - AddUntilStep("wait for sample to start playing", () => sample.Playing); + AddUntilStep("wait for sample to stop playing", () => !sample.Playing); } [Test] @@ -92,9 +90,12 @@ namespace osu.Game.Tests.Visual.Gameplay AddAssert("sample playing", () => sample.Playing); AddStep("pause gameplay clock", () => gameplayClock.IsPaused.Value = true); - AddAssert("sample not playing", () => !sample.Playing); + AddUntilStep("wait for sample to stop playing", () => !sample.Playing); AddStep("resume gameplay clock", () => gameplayClock.IsPaused.Value = false); + + AddAssert("sample not playing", () => !sample.Playing); + AddAssert("sample not playing", () => !sample.Playing); AddAssert("sample not playing", () => !sample.Playing); } } From bbc7d69524931143073058d6a20bcdb799482435 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 14 Jul 2020 22:51:30 +0200 Subject: [PATCH 0248/1782] Add failing test cases --- .../TestSceneDrawableJudgement.cs | 102 ++++++++++++------ .../Objects/Drawables/DrawableOsuJudgement.cs | 21 ++-- 2 files changed, 83 insertions(+), 40 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneDrawableJudgement.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneDrawableJudgement.cs index f08f994b07..4bb4619a1b 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneDrawableJudgement.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneDrawableJudgement.cs @@ -4,62 +4,104 @@ using System; using System.Collections.Generic; using System.Linq; +using NUnit.Framework; +using osu.Framework.Allocation; using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Pooling; +using osu.Framework.Testing; +using osu.Game.Configuration; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Rulesets.Scoring; +using osu.Game.Skinning; namespace osu.Game.Rulesets.Osu.Tests { public class TestSceneDrawableJudgement : OsuSkinnableTestScene { + [Resolved] + private OsuConfigManager config { get; set; } + + private readonly List> pools; + public TestSceneDrawableJudgement() { - var pools = new List>(); + pools = new List>(); foreach (HitResult result in Enum.GetValues(typeof(HitResult)).OfType().Skip(1)) + showResult(result); + } + + [Test] + public void TestHitLightingDisabled() + { + AddStep("hit lighting disabled", () => config.Set(OsuSetting.HitLighting, false)); + + showResult(HitResult.Great); + + AddUntilStep("judgements shown", () => this.ChildrenOfType().Any()); + AddAssert("hit lighting hidden", + () => this.ChildrenOfType().All(judgement => judgement.Lighting.Alpha == 0)); + } + + [Test] + public void TestHitLightingEnabled() + { + AddStep("hit lighting enabled", () => config.Set(OsuSetting.HitLighting, true)); + + showResult(HitResult.Great); + + AddUntilStep("judgements shown", () => this.ChildrenOfType().Any()); + AddAssert("hit lighting shown", + () => this.ChildrenOfType().All(judgement => judgement.Lighting.Alpha > 0)); + } + + private void showResult(HitResult result) + { + AddStep("Show " + result.GetDescription(), () => { - AddStep("Show " + result.GetDescription(), () => + int poolIndex = 0; + + SetContents(() => { - int poolIndex = 0; + DrawablePool pool; - SetContents(() => + if (poolIndex >= pools.Count) + pools.Add(pool = new DrawablePool(1)); + else { - DrawablePool pool; + pool = pools[poolIndex]; - if (poolIndex >= pools.Count) - pools.Add(pool = new DrawablePool(1)); - else + // We need to make sure neither the pool nor the judgement get disposed when new content is set, and they both share the same parent. + ((Container)pool.Parent).Clear(false); + } + + var container = new Container + { + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] { - pool = pools[poolIndex]; - - // We need to make sure neither the pool nor the judgement get disposed when new content is set, and they both share the same parent. - ((Container)pool.Parent).Clear(false); - } - - var container = new Container - { - RelativeSizeAxes = Axes.Both, - Children = new Drawable[] + pool, + pool.Get(j => j.Apply(new JudgementResult(new HitObject(), new Judgement()) { Type = result }, null)).With(j => { - pool, - pool.Get(j => j.Apply(new JudgementResult(new HitObject(), new Judgement()) { Type = result }, null)).With(j => - { - j.Anchor = Anchor.Centre; - j.Origin = Anchor.Centre; - }) - } - }; + j.Anchor = Anchor.Centre; + j.Origin = Anchor.Centre; + }) + } + }; - poolIndex++; - return container; - }); + poolIndex++; + return container; }); - } + }); + } + + private class TestDrawableOsuJudgement : DrawableOsuJudgement + { + public new SkinnableSprite Lighting => base.Lighting; } } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs index 1493ddfcf3..3e45d3509d 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs @@ -16,7 +16,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { public class DrawableOsuJudgement : DrawableJudgement { - private SkinnableSprite lighting; + protected SkinnableSprite Lighting; + private Bindable lightingColour; public DrawableOsuJudgement(JudgementResult result, DrawableHitObject judgedObject) @@ -33,7 +34,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { if (config.Get(OsuSetting.HitLighting)) { - AddInternal(lighting = new SkinnableSprite("lighting") + AddInternal(Lighting = new SkinnableSprite("lighting") { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -60,32 +61,32 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables lightingColour?.UnbindAll(); - if (lighting != null) + if (Lighting != null) { - lighting.ResetAnimation(); + Lighting.ResetAnimation(); if (JudgedObject != null) { lightingColour = JudgedObject.AccentColour.GetBoundCopy(); - lightingColour.BindValueChanged(colour => lighting.Colour = Result.Type == HitResult.Miss ? Color4.Transparent : colour.NewValue, true); + lightingColour.BindValueChanged(colour => Lighting.Colour = Result.Type == HitResult.Miss ? Color4.Transparent : colour.NewValue, true); } else { - lighting.Colour = Color4.White; + Lighting.Colour = Color4.White; } } } - protected override double FadeOutDelay => lighting == null ? base.FadeOutDelay : 1400; + protected override double FadeOutDelay => Lighting == null ? base.FadeOutDelay : 1400; protected override void ApplyHitAnimations() { - if (lighting != null) + if (Lighting != null) { JudgementBody.FadeIn().Delay(FadeInDuration).FadeOut(400); - lighting.ScaleTo(0.8f).ScaleTo(1.2f, 600, Easing.Out); - lighting.FadeIn(200).Then().Delay(200).FadeOut(1000); + Lighting.ScaleTo(0.8f).ScaleTo(1.2f, 600, Easing.Out); + Lighting.FadeIn(200).Then().Delay(200).FadeOut(1000); } JudgementText?.TransformSpacingTo(Vector2.Zero).Then().TransformSpacingTo(new Vector2(14, 0), 1800, Easing.OutQuint); From 21ae33e28487a4939ed01ac03159c9e321a8f6b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 26 Jul 2020 23:20:38 +0200 Subject: [PATCH 0249/1782] Determine whether to show lighting at prepare time --- .../Objects/Drawables/DrawableOsuJudgement.cs | 44 +++++++++---------- 1 file changed, 21 insertions(+), 23 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs index 3e45d3509d..8079ce4a3f 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs @@ -20,6 +20,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables private Bindable lightingColour; + [Resolved] + private OsuConfigManager config { get; set; } + public DrawableOsuJudgement(JudgementResult result, DrawableHitObject judgedObject) : base(result, judgedObject) { @@ -30,18 +33,16 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables } [BackgroundDependencyLoader] - private void load(OsuConfigManager config) + private void load() { - if (config.Get(OsuSetting.HitLighting)) + AddInternal(Lighting = new SkinnableSprite("lighting") { - AddInternal(Lighting = new SkinnableSprite("lighting") - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Blending = BlendingParameters.Additive, - Depth = float.MaxValue - }); - } + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Blending = BlendingParameters.Additive, + Depth = float.MaxValue, + Alpha = 0 + }); } public override void Apply(JudgementResult result, DrawableHitObject judgedObject) @@ -61,19 +62,16 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables lightingColour?.UnbindAll(); - if (Lighting != null) - { - Lighting.ResetAnimation(); + Lighting.ResetAnimation(); - if (JudgedObject != null) - { - lightingColour = JudgedObject.AccentColour.GetBoundCopy(); - lightingColour.BindValueChanged(colour => Lighting.Colour = Result.Type == HitResult.Miss ? Color4.Transparent : colour.NewValue, true); - } - else - { - Lighting.Colour = Color4.White; - } + if (JudgedObject != null) + { + lightingColour = JudgedObject.AccentColour.GetBoundCopy(); + lightingColour.BindValueChanged(colour => Lighting.Colour = Result.Type == HitResult.Miss ? Color4.Transparent : colour.NewValue, true); + } + else + { + Lighting.Colour = Color4.White; } } @@ -81,7 +79,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables protected override void ApplyHitAnimations() { - if (Lighting != null) + if (config.Get(OsuSetting.HitLighting)) { JudgementBody.FadeIn().Delay(FadeInDuration).FadeOut(400); From 5fc7039bf2c6b4a9878611beb72427aa18c40b10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 26 Jul 2020 23:22:31 +0200 Subject: [PATCH 0250/1782] Prevent DrawableJudgement from removing other children --- osu.Game/Rulesets/Judgements/DrawableJudgement.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs index 052aaa3c65..e085334649 100644 --- a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs +++ b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs @@ -130,11 +130,16 @@ namespace osu.Game.Rulesets.Judgements if (type == currentDrawableType) return; - InternalChild = JudgementBody = new Container + // sub-classes might have added their own children that would be removed here if .InternalChild was used. + if (JudgementBody != null) + RemoveInternal(JudgementBody); + + AddInternal(JudgementBody = new Container { Anchor = Anchor.Centre, Origin = Anchor.Centre, RelativeSizeAxes = Axes.Both, + Depth = -float.MaxValue, Child = bodyDrawable = new SkinnableDrawable(new GameplaySkinComponent(type), _ => JudgementText = new OsuSpriteText { Text = type.GetDescription().ToUpperInvariant(), @@ -142,7 +147,7 @@ namespace osu.Game.Rulesets.Judgements Colour = colours.ForHitResult(type), Scale = new Vector2(0.85f, 1), }, confineMode: ConfineMode.NoScaling) - }; + }); currentDrawableType = type; } From 7ad3101d082975980eb3990c9db1a2981211bc0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 26 Jul 2020 23:26:21 +0200 Subject: [PATCH 0251/1782] Bring back custom fade-out delay if hit lighting is on --- .../TestSceneDrawableJudgement.cs | 5 +++++ .../Objects/Drawables/DrawableOsuJudgement.cs | 13 +++++++++++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneDrawableJudgement.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneDrawableJudgement.cs index 4bb4619a1b..646f12f710 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneDrawableJudgement.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneDrawableJudgement.cs @@ -43,6 +43,8 @@ namespace osu.Game.Rulesets.Osu.Tests showResult(HitResult.Great); AddUntilStep("judgements shown", () => this.ChildrenOfType().Any()); + AddAssert("judgement body immediately visible", + () => this.ChildrenOfType().All(judgement => judgement.JudgementBody.Alpha == 1)); AddAssert("hit lighting hidden", () => this.ChildrenOfType().All(judgement => judgement.Lighting.Alpha == 0)); } @@ -55,6 +57,8 @@ namespace osu.Game.Rulesets.Osu.Tests showResult(HitResult.Great); AddUntilStep("judgements shown", () => this.ChildrenOfType().Any()); + AddAssert("judgement body not immediately visible", + () => this.ChildrenOfType().All(judgement => judgement.JudgementBody.Alpha > 0 && judgement.JudgementBody.Alpha < 1)); AddAssert("hit lighting shown", () => this.ChildrenOfType().All(judgement => judgement.Lighting.Alpha > 0)); } @@ -102,6 +106,7 @@ namespace osu.Game.Rulesets.Osu.Tests private class TestDrawableOsuJudgement : DrawableOsuJudgement { public new SkinnableSprite Lighting => base.Lighting; + public new Container JudgementBody => base.JudgementBody; } } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs index 8079ce4a3f..012d9f8878 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs @@ -75,17 +75,26 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables } } - protected override double FadeOutDelay => Lighting == null ? base.FadeOutDelay : 1400; + private double fadeOutDelay; + protected override double FadeOutDelay => fadeOutDelay; protected override void ApplyHitAnimations() { - if (config.Get(OsuSetting.HitLighting)) + bool hitLightingEnabled = config.Get(OsuSetting.HitLighting); + + if (hitLightingEnabled) { JudgementBody.FadeIn().Delay(FadeInDuration).FadeOut(400); Lighting.ScaleTo(0.8f).ScaleTo(1.2f, 600, Easing.Out); Lighting.FadeIn(200).Then().Delay(200).FadeOut(1000); } + else + { + JudgementBody.Alpha = 1; + } + + fadeOutDelay = hitLightingEnabled ? 1400 : base.FadeOutDelay; JudgementText?.TransformSpacingTo(Vector2.Zero).Then().TransformSpacingTo(new Vector2(14, 0), 1800, Easing.OutQuint); base.ApplyHitAnimations(); From abdbeafc8ad4f52274ae1b65f29034f2fe676f2d Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 27 Jul 2020 17:21:15 +0000 Subject: [PATCH 0252/1782] Bump Sentry from 2.1.4 to 2.1.5 Bumps [Sentry](https://github.com/getsentry/sentry-dotnet) from 2.1.4 to 2.1.5. - [Release notes](https://github.com/getsentry/sentry-dotnet/releases) - [Commits](https://github.com/getsentry/sentry-dotnet/compare/2.1.4...2.1.5) 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 ab434def38..7ebffc6d10 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -26,7 +26,7 @@ - + From 1b5a23311ef36d80566fb8e555d250f9eec072d4 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 28 Jul 2020 00:29:17 +0300 Subject: [PATCH 0253/1782] Update ChevronButton position/colour --- .../Comments/Buttons/ChevronButton.cs | 48 +++++++++++++++++++ osu.Game/Overlays/Comments/DrawableComment.cs | 32 ++++--------- .../Overlays/Comments/ShowChildrenButton.cs | 33 ------------- 3 files changed, 58 insertions(+), 55 deletions(-) create mode 100644 osu.Game/Overlays/Comments/Buttons/ChevronButton.cs delete mode 100644 osu.Game/Overlays/Comments/ShowChildrenButton.cs diff --git a/osu.Game/Overlays/Comments/Buttons/ChevronButton.cs b/osu.Game/Overlays/Comments/Buttons/ChevronButton.cs new file mode 100644 index 0000000000..48f34e8f59 --- /dev/null +++ b/osu.Game/Overlays/Comments/Buttons/ChevronButton.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 osu.Framework.Graphics; +using osu.Game.Graphics.Containers; +using osu.Framework.Bindables; +using osu.Framework.Graphics.Sprites; +using osuTK; +using osu.Framework.Allocation; + +namespace osu.Game.Overlays.Comments.Buttons +{ + public class ChevronButton : OsuHoverContainer + { + public readonly BindableBool Expanded = new BindableBool(true); + + private readonly SpriteIcon icon; + + public ChevronButton() + { + Size = new Vector2(40, 22); + Child = icon = new SpriteIcon + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(12), + }; + } + + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider) + { + IdleColour = HoverColour = colourProvider.Foreground1; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + Action = Expanded.Toggle; + Expanded.BindValueChanged(onExpandedChanged, true); + } + + private void onExpandedChanged(ValueChangedEvent expanded) + { + icon.Icon = expanded.NewValue ? FontAwesome.Solid.ChevronUp : FontAwesome.Solid.ChevronDown; + } + } +} diff --git a/osu.Game/Overlays/Comments/DrawableComment.cs b/osu.Game/Overlays/Comments/DrawableComment.cs index 3cdc0a0cbd..39ad60b61c 100644 --- a/osu.Game/Overlays/Comments/DrawableComment.cs +++ b/osu.Game/Overlays/Comments/DrawableComment.cs @@ -228,13 +228,19 @@ namespace osu.Game.Overlays.Comments }, } }, - chevronButton = new ChevronButton + new Container { + Size = new Vector2(70, 40), Anchor = Anchor.TopRight, Origin = Anchor.TopRight, - Margin = new MarginPadding { Right = 30, Top = margin }, - Expanded = { BindTarget = childrenExpanded }, - Alpha = 0 + Margin = new MarginPadding { Horizontal = 5 }, + Child = chevronButton = new ChevronButton + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Expanded = { BindTarget = childrenExpanded }, + Alpha = 0 + } } }; @@ -357,24 +363,6 @@ namespace osu.Game.Overlays.Comments showMoreButton.IsLoading = loadRepliesButton.IsLoading = false; } - private class ChevronButton : ShowChildrenButton - { - private readonly SpriteIcon icon; - - public ChevronButton() - { - 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 ShowMoreButton : GetCommentRepliesButton { [BackgroundDependencyLoader] diff --git a/osu.Game/Overlays/Comments/ShowChildrenButton.cs b/osu.Game/Overlays/Comments/ShowChildrenButton.cs deleted file mode 100644 index 5ec7c1d471..0000000000 --- a/osu.Game/Overlays/Comments/ShowChildrenButton.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 osu.Framework.Graphics; -using osu.Game.Graphics.Containers; -using osu.Framework.Bindables; -using osuTK.Graphics; -using osu.Game.Graphics; - -namespace osu.Game.Overlays.Comments -{ - public abstract class ShowChildrenButton : OsuHoverContainer - { - public readonly BindableBool Expanded = new BindableBool(true); - - protected ShowChildrenButton() - { - AutoSizeAxes = Axes.Both; - IdleColour = OsuColour.Gray(0.7f); - HoverColour = Color4.White; - } - - protected override void LoadComplete() - { - Action = Expanded.Toggle; - - Expanded.BindValueChanged(OnExpandedChanged, true); - base.LoadComplete(); - } - - protected abstract void OnExpandedChanged(ValueChangedEvent expanded); - } -} From 46d1de7fa7efdd2597c02b5431313c172d4d6169 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 28 Jul 2020 00:43:06 +0300 Subject: [PATCH 0254/1782] ShowMoreButton rework --- .../ShowMoreButton.cs} | 27 +++++++++++-------- osu.Game/Overlays/Comments/DrawableComment.cs | 13 --------- 2 files changed, 16 insertions(+), 24 deletions(-) rename osu.Game/Overlays/Comments/{GetCommentRepliesButton.cs => Buttons/ShowMoreButton.cs} (66%) diff --git a/osu.Game/Overlays/Comments/GetCommentRepliesButton.cs b/osu.Game/Overlays/Comments/Buttons/ShowMoreButton.cs similarity index 66% rename from osu.Game/Overlays/Comments/GetCommentRepliesButton.cs rename to osu.Game/Overlays/Comments/Buttons/ShowMoreButton.cs index a3817ba416..0f07a7141c 100644 --- a/osu.Game/Overlays/Comments/GetCommentRepliesButton.cs +++ b/osu.Game/Overlays/Comments/Buttons/ShowMoreButton.cs @@ -8,38 +8,43 @@ using osu.Framework.Graphics.Containers; using osu.Game.Graphics.Sprites; using System.Collections.Generic; using osuTK; +using osu.Framework.Allocation; -namespace osu.Game.Overlays.Comments +namespace osu.Game.Overlays.Comments.Buttons { - public abstract class GetCommentRepliesButton : LoadingButton + public class ShowMoreButton : LoadingButton { - private const int duration = 200; - protected override IEnumerable EffectTargets => new[] { text }; private OsuSpriteText text; - protected GetCommentRepliesButton() + public ShowMoreButton() { AutoSizeAxes = Axes.Both; + Margin = new MarginPadding { Vertical = 10, Left = 80 }; LoadingAnimationSize = new Vector2(8); } + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider) + { + IdleColour = colourProvider.Light2; + HoverColour = colourProvider.Light1; + } + protected override Drawable CreateContent() => new Container { AutoSizeAxes = Axes.Both, Child = text = new OsuSpriteText { AlwaysPresent = true, - Font = OsuFont.GetFont(size: 12, weight: FontWeight.Bold), - Text = GetText() + Font = OsuFont.GetFont(size: 12, weight: FontWeight.SemiBold), + Text = "show more" } }; - protected abstract string GetText(); + protected override void OnLoadStarted() => text.FadeOut(200, Easing.OutQuint); - protected override void OnLoadStarted() => text.FadeOut(duration, Easing.OutQuint); - - protected override void OnLoadFinished() => text.FadeIn(duration, Easing.OutQuint); + protected override void OnLoadFinished() => text.FadeIn(200, Easing.OutQuint); } } diff --git a/osu.Game/Overlays/Comments/DrawableComment.cs b/osu.Game/Overlays/Comments/DrawableComment.cs index 39ad60b61c..05959dbfd9 100644 --- a/osu.Game/Overlays/Comments/DrawableComment.cs +++ b/osu.Game/Overlays/Comments/DrawableComment.cs @@ -363,19 +363,6 @@ namespace osu.Game.Overlays.Comments showMoreButton.IsLoading = loadRepliesButton.IsLoading = false; } - private class ShowMoreButton : GetCommentRepliesButton - { - [BackgroundDependencyLoader] - private void load(OverlayColourProvider colourProvider) - { - Margin = new MarginPadding { Vertical = 10, Left = 80 }; - IdleColour = colourProvider.Light2; - HoverColour = colourProvider.Light1; - } - - protected override string GetText() => @"Show More"; - } - private class ParentUsername : FillFlowContainer, IHasTooltip { public string TooltipText => getParentMessage(); From 69691b373971f03eb00a589768cc1c67b39fcb7f Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 28 Jul 2020 00:53:51 +0300 Subject: [PATCH 0255/1782] Use DrawableDate to represent creation date --- osu.Game/Overlays/Comments/DrawableComment.cs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/osu.Game/Overlays/Comments/DrawableComment.cs b/osu.Game/Overlays/Comments/DrawableComment.cs index 05959dbfd9..07c3ba970f 100644 --- a/osu.Game/Overlays/Comments/DrawableComment.cs +++ b/osu.Game/Overlays/Comments/DrawableComment.cs @@ -58,7 +58,7 @@ namespace osu.Game.Overlays.Comments } [BackgroundDependencyLoader] - private void load() + private void load(OverlayColourProvider colourProvider) { LinkFlowContainer username; FillFlowContainer info; @@ -176,14 +176,12 @@ namespace osu.Game.Overlays.Comments Spacing = new Vector2(10, 0), Children = new Drawable[] { - new OsuSpriteText + new DrawableDate(Comment.CreatedAt, 12, false) { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, - Font = OsuFont.GetFont(size: 12), - Colour = OsuColour.Gray(0.7f), - Text = HumanizerUtils.Humanize(Comment.CreatedAt) - }, + Colour = colourProvider.Foreground1 + } } }, showRepliesButton = new ShowRepliesButton(Comment.RepliesCount) From 6737c57e334377dbbc170f1a8ae3748118503dbd Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 28 Jul 2020 01:10:13 +0300 Subject: [PATCH 0256/1782] Adjust colour of edit info --- osu.Game/Overlays/Comments/DrawableComment.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Comments/DrawableComment.cs b/osu.Game/Overlays/Comments/DrawableComment.cs index 07c3ba970f..e2ab72d62f 100644 --- a/osu.Game/Overlays/Comments/DrawableComment.cs +++ b/osu.Game/Overlays/Comments/DrawableComment.cs @@ -253,8 +253,9 @@ namespace osu.Game.Overlays.Comments { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, - Font = OsuFont.GetFont(size: 12), - Text = $@"edited {HumanizerUtils.Humanize(Comment.EditedAt.Value)} by {Comment.EditedUser.Username}" + Font = OsuFont.GetFont(size: 12, weight: FontWeight.Regular), + Text = $@"edited {HumanizerUtils.Humanize(Comment.EditedAt.Value)} by {Comment.EditedUser.Username}", + Colour = colourProvider.Foreground1 }); } From 2d502cebdab2fb489568ad3296565e8204a21c45 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 28 Jul 2020 02:36:25 +0300 Subject: [PATCH 0257/1782] Update DrawableComment layout --- .../Comments/Buttons/CommentRepliesButton.cs | 4 - .../Comments/Buttons/ShowMoreButton.cs | 1 - .../Comments/DeletedCommentsCounter.cs | 2 - osu.Game/Overlays/Comments/DrawableComment.cs | 167 ++++++++++-------- 4 files changed, 89 insertions(+), 85 deletions(-) diff --git a/osu.Game/Overlays/Comments/Buttons/CommentRepliesButton.cs b/osu.Game/Overlays/Comments/Buttons/CommentRepliesButton.cs index f7e0cb0a6c..53438ca421 100644 --- a/osu.Game/Overlays/Comments/Buttons/CommentRepliesButton.cs +++ b/osu.Game/Overlays/Comments/Buttons/CommentRepliesButton.cs @@ -32,10 +32,6 @@ namespace osu.Game.Overlays.Comments.Buttons protected CommentRepliesButton() { AutoSizeAxes = Axes.Both; - Margin = new MarginPadding - { - Vertical = 2 - }; InternalChildren = new Drawable[] { new CircularContainer diff --git a/osu.Game/Overlays/Comments/Buttons/ShowMoreButton.cs b/osu.Game/Overlays/Comments/Buttons/ShowMoreButton.cs index 0f07a7141c..2c363564d2 100644 --- a/osu.Game/Overlays/Comments/Buttons/ShowMoreButton.cs +++ b/osu.Game/Overlays/Comments/Buttons/ShowMoreButton.cs @@ -21,7 +21,6 @@ namespace osu.Game.Overlays.Comments.Buttons public ShowMoreButton() { AutoSizeAxes = Axes.Both; - Margin = new MarginPadding { Vertical = 10, Left = 80 }; LoadingAnimationSize = new Vector2(8); } diff --git a/osu.Game/Overlays/Comments/DeletedCommentsCounter.cs b/osu.Game/Overlays/Comments/DeletedCommentsCounter.cs index f22086bf23..56588ef0a8 100644 --- a/osu.Game/Overlays/Comments/DeletedCommentsCounter.cs +++ b/osu.Game/Overlays/Comments/DeletedCommentsCounter.cs @@ -23,8 +23,6 @@ namespace osu.Game.Overlays.Comments public DeletedCommentsCounter() { AutoSizeAxes = Axes.Both; - Margin = new MarginPadding { Vertical = 10, Left = 80 }; - InternalChild = new FillFlowContainer { AutoSizeAxes = Axes.Both, diff --git a/osu.Game/Overlays/Comments/DrawableComment.cs b/osu.Game/Overlays/Comments/DrawableComment.cs index e2ab72d62f..9c0a48ec29 100644 --- a/osu.Game/Overlays/Comments/DrawableComment.cs +++ b/osu.Game/Overlays/Comments/DrawableComment.cs @@ -28,7 +28,6 @@ namespace osu.Game.Overlays.Comments public class DrawableComment : CompositeDrawable { private const int avatar_size = 40; - private const int margin = 10; public Action RepliesRequested; @@ -70,25 +69,25 @@ namespace osu.Game.Overlays.Comments AutoSizeAxes = Axes.Y; InternalChildren = new Drawable[] { - new FillFlowContainer + new Container { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, - Children = new Drawable[] + Padding = getPadding(Comment.IsTopLevel), + Child = new FillFlowContainer { - new Container + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Children = new Drawable[] { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Padding = new MarginPadding(margin) { Left = margin + 5, Top = Comment.IsTopLevel ? 10 : 0 }, - Child = content = new GridContainer + content = new GridContainer { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, ColumnDimensions = new[] { - new Dimension(GridSizeMode.AutoSize), + new Dimension(GridSizeMode.Absolute, size: avatar_size + 10), new Dimension(), }, RowDimensions = new[] @@ -99,91 +98,84 @@ namespace osu.Game.Overlays.Comments { new Drawable[] { - new FillFlowContainer + new Container { - AutoSizeAxes = Axes.Both, - Margin = new MarginPadding { Horizontal = margin }, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(5, 0), + Size = new Vector2(avatar_size), Children = new Drawable[] { - new Container - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Width = 40, - AutoSizeAxes = Axes.Y, - Child = votePill = new VotePill(Comment) - { - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, - } - }, new UpdateableAvatar(Comment.User) { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, Size = new Vector2(avatar_size), Masking = true, CornerRadius = avatar_size / 2f, CornerExponent = 2, }, + votePill = new VotePill(Comment) + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreRight, + Margin = new MarginPadding + { + Right = 5 + } + } } }, new FillFlowContainer { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Spacing = new Vector2(0, 3), + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 4), + Margin = new MarginPadding + { + Vertical = 2 + }, Children = new Drawable[] { new FillFlowContainer { AutoSizeAxes = Axes.Both, Direction = FillDirection.Horizontal, - Spacing = new Vector2(7, 0), + Spacing = new Vector2(10, 0), Children = new Drawable[] { - username = new LinkFlowContainer(s => s.Font = OsuFont.GetFont(size: 14, weight: FontWeight.Bold, italics: true)) + username = new LinkFlowContainer(s => s.Font = OsuFont.GetFont(size: 14, weight: FontWeight.Bold)) { - AutoSizeAxes = Axes.Both, + AutoSizeAxes = Axes.Both }, new ParentUsername(Comment), new OsuSpriteText { Alpha = Comment.IsDeleted ? 1 : 0, - Font = OsuFont.GetFont(size: 14, weight: FontWeight.Bold, italics: true), - Text = @"deleted", + Font = OsuFont.GetFont(size: 14, weight: FontWeight.Bold), + Text = "deleted" } } }, message = new LinkFlowContainer(s => s.Font = OsuFont.GetFont(size: 14)) { RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Padding = new MarginPadding { Right = 40 } + AutoSizeAxes = Axes.Y }, - new FillFlowContainer + info = new FillFlowContainer { AutoSizeAxes = Axes.Both, - Direction = FillDirection.Vertical, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(10, 0), Children = new Drawable[] { - info = new FillFlowContainer + new DrawableDate(Comment.CreatedAt, 12, false) { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(10, 0), - Children = new Drawable[] - { - new DrawableDate(Comment.CreatedAt, 12, false) - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Colour = colourProvider.Foreground1 - } - } - }, + Colour = colourProvider.Foreground1 + } + } + }, + new Container + { + AutoSizeAxes = Axes.Both, + Children = new Drawable[] + { showRepliesButton = new ShowRepliesButton(Comment.RepliesCount) { Expanded = { BindTarget = childrenExpanded } @@ -198,32 +190,36 @@ namespace osu.Game.Overlays.Comments } } } - } - }, - childCommentsVisibilityContainer = new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, - Children = new Drawable[] + }, + childCommentsVisibilityContainer = new FillFlowContainer { - childCommentsContainer = new FillFlowContainer + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Padding = new MarginPadding { Left = 20 }, + Children = new Drawable[] { - Padding = new MarginPadding { Left = 20 }, - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical - }, - deletedCommentsCounter = new DeletedCommentsCounter - { - ShowDeleted = { BindTarget = ShowDeleted } - }, - showMoreButton = new ShowMoreButton - { - Action = () => RepliesRequested(this, ++currentPage) + childCommentsContainer = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical + }, + deletedCommentsCounter = new DeletedCommentsCounter + { + ShowDeleted = { BindTarget = ShowDeleted }, + Margin = new MarginPadding + { + Top = 10 + } + }, + showMoreButton = new ShowMoreButton + { + Action = () => RepliesRequested(this, ++currentPage) + } } - } - }, + }, + } } }, new Container @@ -251,8 +247,6 @@ namespace osu.Game.Overlays.Comments { info.Add(new OsuSpriteText { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, Font = OsuFont.GetFont(size: 12, weight: FontWeight.Regular), Text = $@"edited {HumanizerUtils.Humanize(Comment.EditedAt.Value)} by {Comment.EditedUser.Username}", Colour = colourProvider.Foreground1 @@ -362,6 +356,23 @@ namespace osu.Game.Overlays.Comments showMoreButton.IsLoading = loadRepliesButton.IsLoading = false; } + private MarginPadding getPadding(bool isTopLevel) + { + if (isTopLevel) + { + return new MarginPadding + { + Horizontal = 70, + Vertical = 15 + }; + } + + return new MarginPadding + { + Top = 10 + }; + } + private class ParentUsername : FillFlowContainer, IHasTooltip { public string TooltipText => getParentMessage(); From dc577aa6fa2bc0df944d84bfbb27085217929737 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 28 Jul 2020 11:22:58 +0900 Subject: [PATCH 0258/1782] Fix display of bonus score --- osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs | 2 +- .../Objects/Drawables/Pieces/SpinnerBonusDisplay.cs | 2 +- osu.Game.Rulesets.Osu/Objects/SpinnerBonusTick.cs | 4 +++- osu.Game.Rulesets.Osu/Objects/SpinnerTick.cs | 4 +++- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs index 23b440ced2..c36bec391f 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs @@ -142,7 +142,7 @@ namespace osu.Game.Rulesets.Osu.Tests { // multipled by 2 to nullify the score multiplier. (autoplay mod selected) var totalScore = ((ScoreExposedPlayer)Player).ScoreProcessor.TotalScore.Value * 2; - return totalScore == (int)(drawableSpinner.Disc.CumulativeRotation / 360) * 10; + return totalScore == (int)(drawableSpinner.Disc.CumulativeRotation / 360) * SpinnerTick.SCORE_PER_TICK; }); addSeekStep(0); diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerBonusDisplay.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerBonusDisplay.cs index a8f5580735..b499d7a92b 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerBonusDisplay.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerBonusDisplay.cs @@ -36,7 +36,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces return; displayedCount = count; - bonusCounter.Text = $"{1000 * count}"; + bonusCounter.Text = $"{SpinnerBonusTick.SCORE_PER_TICK * count}"; bonusCounter.FadeOutFromOne(1500); bonusCounter.ScaleTo(1.5f).Then().ScaleTo(1f, 1000, Easing.OutQuint); } diff --git a/osu.Game.Rulesets.Osu/Objects/SpinnerBonusTick.cs b/osu.Game.Rulesets.Osu/Objects/SpinnerBonusTick.cs index b59428e701..9c4b6f774f 100644 --- a/osu.Game.Rulesets.Osu/Objects/SpinnerBonusTick.cs +++ b/osu.Game.Rulesets.Osu/Objects/SpinnerBonusTick.cs @@ -9,6 +9,8 @@ namespace osu.Game.Rulesets.Osu.Objects { public class SpinnerBonusTick : SpinnerTick { + public new const int SCORE_PER_TICK = 50; + public SpinnerBonusTick() { Samples.Add(new HitSampleInfo { Name = "spinnerbonus" }); @@ -18,7 +20,7 @@ namespace osu.Game.Rulesets.Osu.Objects public class OsuSpinnerBonusTickJudgement : OsuSpinnerTickJudgement { - protected override int NumericResultFor(HitResult result) => 50; + protected override int NumericResultFor(HitResult result) => SCORE_PER_TICK; protected override double HealthIncreaseFor(HitResult result) => base.HealthIncreaseFor(result) * 2; } diff --git a/osu.Game.Rulesets.Osu/Objects/SpinnerTick.cs b/osu.Game.Rulesets.Osu/Objects/SpinnerTick.cs index 346f949a4f..de3ae27e55 100644 --- a/osu.Game.Rulesets.Osu/Objects/SpinnerTick.cs +++ b/osu.Game.Rulesets.Osu/Objects/SpinnerTick.cs @@ -9,6 +9,8 @@ namespace osu.Game.Rulesets.Osu.Objects { public class SpinnerTick : OsuHitObject { + public const int SCORE_PER_TICK = 10; + public override Judgement CreateJudgement() => new OsuSpinnerTickJudgement(); protected override HitWindows CreateHitWindows() => HitWindows.Empty; @@ -17,7 +19,7 @@ namespace osu.Game.Rulesets.Osu.Objects { public override bool AffectsCombo => false; - protected override int NumericResultFor(HitResult result) => 10; + protected override int NumericResultFor(HitResult result) => SCORE_PER_TICK; protected override double HealthIncreaseFor(HitResult result) => result == MaxResult ? 0.6 * base.HealthIncreaseFor(result) : 0; } From df3e2cc640ca60f5118927f43f67e71b1e0f69b4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 28 Jul 2020 12:08:15 +0900 Subject: [PATCH 0259/1782] Fix potential crash due to cross-thread TrackVirtualManual.Stop --- osu.Game/Tests/Visual/OsuTestScene.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Tests/Visual/OsuTestScene.cs b/osu.Game/Tests/Visual/OsuTestScene.cs index cb9ed40b00..866fc215d6 100644 --- a/osu.Game/Tests/Visual/OsuTestScene.cs +++ b/osu.Game/Tests/Visual/OsuTestScene.cs @@ -305,8 +305,10 @@ namespace osu.Game.Tests.Visual { double refTime = referenceClock.CurrentTime; - if (lastReferenceTime.HasValue) - accumulated += (refTime - lastReferenceTime.Value) * Rate; + double? lastRefTime = lastReferenceTime; + + if (lastRefTime != null) + accumulated += (refTime - lastRefTime.Value) * Rate; lastReferenceTime = refTime; } From a210deee9a3caa6cd20c841d808858f3e10331cf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 28 Jul 2020 12:16:01 +0900 Subject: [PATCH 0260/1782] Remove unnecessary depth setter --- osu.Game/Rulesets/Judgements/DrawableJudgement.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs index e085334649..d24c81536e 100644 --- a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs +++ b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs @@ -139,7 +139,6 @@ namespace osu.Game.Rulesets.Judgements Anchor = Anchor.Centre, Origin = Anchor.Centre, RelativeSizeAxes = Axes.Both, - Depth = -float.MaxValue, Child = bodyDrawable = new SkinnableDrawable(new GameplaySkinComponent(type), _ => JudgementText = new OsuSpriteText { Text = type.GetDescription().ToUpperInvariant(), From a99c6698b7350b2f4362761cbaf64e932e2232fc Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Tue, 28 Jul 2020 04:25:01 +0000 Subject: [PATCH 0261/1782] Bump SharpCompress from 0.25.1 to 0.26.0 Bumps [SharpCompress](https://github.com/adamhathcock/sharpcompress) from 0.25.1 to 0.26.0. - [Release notes](https://github.com/adamhathcock/sharpcompress/releases) - [Commits](https://github.com/adamhathcock/sharpcompress/compare/0.25.1...0.26) 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 7ebffc6d10..5ac54a853f 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -27,7 +27,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 618de5d19f..8b2d1346be 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -81,7 +81,7 @@ - + From 72c8f0737ef5d125879c044677183f04f259cf02 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 28 Jul 2020 14:18:14 +0900 Subject: [PATCH 0262/1782] Fix Autopilot mod incompatibility with WindUp/WindDown --- osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs index d75f4c70d7..2263e2b2f4 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs @@ -11,6 +11,7 @@ using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Replays; using osu.Game.Rulesets.UI; +using osu.Game.Screens.Play; namespace osu.Game.Rulesets.Osu.Mods { @@ -30,6 +31,8 @@ namespace osu.Game.Rulesets.Osu.Mods private OsuInputManager inputManager; + private GameplayClock gameplayClock; + private List replayFrames; private int currentFrame; @@ -38,7 +41,7 @@ namespace osu.Game.Rulesets.Osu.Mods { if (currentFrame == replayFrames.Count - 1) return; - double time = playfield.Time.Current; + double time = gameplayClock.CurrentTime; // 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). @@ -53,6 +56,8 @@ namespace osu.Game.Rulesets.Osu.Mods public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) { + gameplayClock = drawableRuleset.FrameStableClock; + // Grab the input manager to disable the user's cursor, and for future use inputManager = (OsuInputManager)drawableRuleset.KeyBindingInputManager; inputManager.AllowUserCursorMovement = false; From e795b1ea318a97ff6693526b78003c85fd231cd2 Mon Sep 17 00:00:00 2001 From: Joe Yuan Date: Tue, 28 Jul 2020 00:38:31 -0700 Subject: [PATCH 0263/1782] Failing effect displays vertically --- osu.Game/Screens/Play/HUD/FailingLayer.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/FailingLayer.cs b/osu.Game/Screens/Play/HUD/FailingLayer.cs index 84dbb35f68..847b8a53cf 100644 --- a/osu.Game/Screens/Play/HUD/FailingLayer.cs +++ b/osu.Game/Screens/Play/HUD/FailingLayer.cs @@ -29,7 +29,7 @@ namespace osu.Game.Screens.Play.HUD private const float max_alpha = 0.4f; private const int fade_time = 400; - private const float gradient_size = 0.3f; + private const float gradient_size = 0.2f; /// /// The threshold under which the current player life should be considered low and the layer should start fading in. @@ -56,16 +56,16 @@ namespace osu.Game.Screens.Play.HUD new Box { RelativeSizeAxes = Axes.Both, - Colour = ColourInfo.GradientVertical(Color4.White, Color4.White.Opacity(0)), - Height = gradient_size, + Colour = ColourInfo.GradientHorizontal(Color4.White, Color4.White.Opacity(0)), + Width = gradient_size, }, new Box { RelativeSizeAxes = Axes.Both, - Height = gradient_size, - Colour = ColourInfo.GradientVertical(Color4.White.Opacity(0), Color4.White), - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, + Width = gradient_size, + Colour = ColourInfo.GradientHorizontal(Color4.White.Opacity(0), Color4.White), + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, }, } }, From ff3cb6487d2d474ff8d9ec9c6f164ee47d6efe62 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 28 Jul 2020 16:52:07 +0900 Subject: [PATCH 0264/1782] Store all linked cancellation tokens --- osu.Game/Beatmaps/BeatmapDifficultyManager.cs | 33 ++++++++++++++++--- 1 file changed, 28 insertions(+), 5 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapDifficultyManager.cs b/osu.Game/Beatmaps/BeatmapDifficultyManager.cs index 5e644fbf1c..e625f6f96e 100644 --- a/osu.Game/Beatmaps/BeatmapDifficultyManager.cs +++ b/osu.Game/Beatmaps/BeatmapDifficultyManager.cs @@ -109,25 +109,42 @@ namespace osu.Game.Beatmaps } private CancellationTokenSource trackedUpdateCancellationSource; + private readonly List linkedCancellationSources = new List(); /// /// Updates all tracked using the current ruleset and mods. /// private void updateTrackedBindables() { - trackedUpdateCancellationSource?.Cancel(); + cancelTrackedBindableUpdate(); trackedUpdateCancellationSource = new CancellationTokenSource(); foreach (var b in trackedBindables) { - if (trackedUpdateCancellationSource.IsCancellationRequested) - break; + var linkedSource = CancellationTokenSource.CreateLinkedTokenSource(trackedUpdateCancellationSource.Token, b.CancellationToken); + linkedCancellationSources.Add(linkedSource); - using (var linkedSource = CancellationTokenSource.CreateLinkedTokenSource(trackedUpdateCancellationSource.Token, b.CancellationToken)) - updateBindable(b, currentRuleset.Value, currentMods.Value, linkedSource.Token); + updateBindable(b, currentRuleset.Value, currentMods.Value, linkedSource.Token); } } + /// + /// Cancels the existing update of all tracked via . + /// + private void cancelTrackedBindableUpdate() + { + trackedUpdateCancellationSource?.Cancel(); + trackedUpdateCancellationSource = null; + + foreach (var c in linkedCancellationSources) + { + c.Cancel(); + c.Dispose(); + } + + linkedCancellationSources.Clear(); + } + /// /// Updates the value of a with a given ruleset + mods. /// @@ -220,6 +237,12 @@ namespace osu.Game.Beatmaps return difficultyCache.TryGetValue(key, out existingDifficulty); } + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + cancelTrackedBindableUpdate(); + } + private readonly struct DifficultyCacheLookup : IEquatable { public readonly int BeatmapId; From 96f68a32518422f90abbdce672089bd5b5ee50f3 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 28 Jul 2020 16:52:19 +0900 Subject: [PATCH 0265/1782] Reorder method --- osu.Game/Beatmaps/BeatmapDifficultyManager.cs | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapDifficultyManager.cs b/osu.Game/Beatmaps/BeatmapDifficultyManager.cs index e625f6f96e..98a1462d99 100644 --- a/osu.Game/Beatmaps/BeatmapDifficultyManager.cs +++ b/osu.Game/Beatmaps/BeatmapDifficultyManager.cs @@ -145,6 +145,22 @@ namespace osu.Game.Beatmaps linkedCancellationSources.Clear(); } + /// + /// Creates a new and triggers an initial value update. + /// + /// The that star difficulty should correspond to. + /// The initial to get the difficulty with. + /// The initial s to get the difficulty with. + /// An optional which stops updating the star difficulty for the given . + /// The . + private BindableStarDifficulty createBindable([NotNull] BeatmapInfo beatmapInfo, [CanBeNull] RulesetInfo initialRulesetInfo, [CanBeNull] IEnumerable initialMods, + CancellationToken cancellationToken) + { + var bindable = new BindableStarDifficulty(beatmapInfo, cancellationToken); + updateBindable(bindable, initialRulesetInfo, initialMods, cancellationToken); + return bindable; + } + /// /// Updates the value of a with a given ruleset + mods. /// @@ -165,22 +181,6 @@ namespace osu.Game.Beatmaps }, cancellationToken); } - /// - /// Creates a new and triggers an initial value update. - /// - /// The that star difficulty should correspond to. - /// The initial to get the difficulty with. - /// The initial s to get the difficulty with. - /// An optional which stops updating the star difficulty for the given . - /// The . - private BindableStarDifficulty createBindable([NotNull] BeatmapInfo beatmapInfo, [CanBeNull] RulesetInfo initialRulesetInfo, [CanBeNull] IEnumerable initialMods, - CancellationToken cancellationToken) - { - var bindable = new BindableStarDifficulty(beatmapInfo, cancellationToken); - updateBindable(bindable, initialRulesetInfo, initialMods, cancellationToken); - return bindable; - } - /// /// Computes the difficulty defined by a key, and stores it to the timed cache. /// From ca434e82d961dac772296c6d335ff699efdcc1ac Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 28 Jul 2020 17:09:38 +0900 Subject: [PATCH 0266/1782] Fix test failures due to gameplay clock not being unpaused --- osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs index 81e5f32ee8..e0a1f947ec 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs @@ -25,6 +25,8 @@ namespace osu.Game.Tests.Visual.Gameplay [SetUp] public void SetUp() => Schedule(() => { + gameplayClock.IsPaused.Value = false; + Children = new Drawable[] { new Container @@ -42,9 +44,10 @@ namespace osu.Game.Tests.Visual.Gameplay DrawableSample sample = null; AddStep("start sample with looping", () => { + sample = skinnableSound.ChildrenOfType().First(); + skinnableSound.Looping = true; skinnableSound.Play(); - sample = skinnableSound.ChildrenOfType().First(); }); AddUntilStep("wait for sample to start playing", () => sample.Playing); From fa25f8aef9993c53f2ac72a9ed1ecd4446848b3b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 28 Jul 2020 17:23:35 +0900 Subject: [PATCH 0267/1782] Dispose update scheduler --- osu.Game/Beatmaps/BeatmapDifficultyManager.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Beatmaps/BeatmapDifficultyManager.cs b/osu.Game/Beatmaps/BeatmapDifficultyManager.cs index 98a1462d99..b3afd1d4fd 100644 --- a/osu.Game/Beatmaps/BeatmapDifficultyManager.cs +++ b/osu.Game/Beatmaps/BeatmapDifficultyManager.cs @@ -240,7 +240,9 @@ namespace osu.Game.Beatmaps protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); + cancelTrackedBindableUpdate(); + updateScheduler.Dispose(); } private readonly struct DifficultyCacheLookup : IEquatable From f7cd6e83aa81bc24ccce152e37028851e1af8ee0 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 28 Jul 2020 17:58:58 +0900 Subject: [PATCH 0268/1782] Adjust mania scoring to be 95% based on accuracy --- osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs b/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs index ba84c21845..4b2f643333 100644 --- a/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs +++ b/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs @@ -7,9 +7,9 @@ namespace osu.Game.Rulesets.Mania.Scoring { internal class ManiaScoreProcessor : ScoreProcessor { - protected override double DefaultAccuracyPortion => 0.8; + protected override double DefaultAccuracyPortion => 0.95; - protected override double DefaultComboPortion => 0.2; + protected override double DefaultComboPortion => 0.05; public override HitWindows CreateHitWindows() => new ManiaHitWindows(); } From 375dad087837ba6156be5ec629b0a370c19c7891 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 28 Jul 2020 17:59:52 +0900 Subject: [PATCH 0269/1782] Increase PERFECT from 320 to 350 score --- osu.Game.Rulesets.Mania/Judgements/ManiaJudgement.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania/Judgements/ManiaJudgement.cs b/osu.Game.Rulesets.Mania/Judgements/ManiaJudgement.cs index 53db676a54..53967ffa05 100644 --- a/osu.Game.Rulesets.Mania/Judgements/ManiaJudgement.cs +++ b/osu.Game.Rulesets.Mania/Judgements/ManiaJudgement.cs @@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Mania.Judgements return 300; case HitResult.Perfect: - return 320; + return 350; } } } From 54d2f2c8cd2e837d7b0e046f4bf8df91317eb802 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 28 Jul 2020 20:34:09 +0900 Subject: [PATCH 0270/1782] Delay loading of cover backgrounds in score panels --- .../Ranking/Contracted/ContractedPanelMiddleContent.cs | 5 ++++- osu.Game/Screens/Ranking/ScorePanel.cs | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Ranking/Contracted/ContractedPanelMiddleContent.cs b/osu.Game/Screens/Ranking/Contracted/ContractedPanelMiddleContent.cs index 8cd0e7025e..3ffb205d09 100644 --- a/osu.Game/Screens/Ranking/Contracted/ContractedPanelMiddleContent.cs +++ b/osu.Game/Screens/Ranking/Contracted/ContractedPanelMiddleContent.cs @@ -70,11 +70,14 @@ namespace osu.Game.Screens.Ranking.Contracted RelativeSizeAxes = Axes.Both, Colour = Color4Extensions.FromHex("444") }, - new UserCoverBackground + new DelayedLoadUnloadWrapper(() => new UserCoverBackground { RelativeSizeAxes = Axes.Both, User = score.User, Colour = ColourInfo.GradientVertical(Color4.White.Opacity(0.5f), Color4Extensions.FromHex("#444").Opacity(0)) + }, 300, 5000) + { + RelativeSizeAxes = Axes.Both }, new FillFlowContainer { diff --git a/osu.Game/Screens/Ranking/ScorePanel.cs b/osu.Game/Screens/Ranking/ScorePanel.cs index 5da432d5b2..7ac98604f4 100644 --- a/osu.Game/Screens/Ranking/ScorePanel.cs +++ b/osu.Game/Screens/Ranking/ScorePanel.cs @@ -146,11 +146,14 @@ namespace osu.Game.Screens.Ranking Children = new[] { middleLayerBackground = new Box { RelativeSizeAxes = Axes.Both }, - new UserCoverBackground + new DelayedLoadUnloadWrapper(() => new UserCoverBackground { RelativeSizeAxes = Axes.Both, User = Score.User, Colour = ColourInfo.GradientVertical(Color4.White.Opacity(0.5f), Color4Extensions.FromHex("#444").Opacity(0)) + }, 300, 5000) + { + RelativeSizeAxes = Axes.Both }, } }, From 42e88c53d75a03f37fe8699ca2512c0477fb8c75 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 28 Jul 2020 20:50:55 +0900 Subject: [PATCH 0271/1782] Embed behaviour into UserCoverBackground --- osu.Game/Overlays/Profile/ProfileHeader.cs | 7 ++++++- .../Contracted/ContractedPanelMiddleContent.cs | 5 +---- osu.Game/Screens/Ranking/ScorePanel.cs | 7 ++----- osu.Game/Users/UserCoverBackground.cs | 11 +++++++++++ osu.Game/Users/UserPanel.cs | 10 ++-------- 5 files changed, 22 insertions(+), 18 deletions(-) diff --git a/osu.Game/Overlays/Profile/ProfileHeader.cs b/osu.Game/Overlays/Profile/ProfileHeader.cs index 2e5f1071f2..55474c9d3e 100644 --- a/osu.Game/Overlays/Profile/ProfileHeader.cs +++ b/osu.Game/Overlays/Profile/ProfileHeader.cs @@ -41,7 +41,7 @@ namespace osu.Game.Overlays.Profile Masking = true, Children = new Drawable[] { - coverContainer = new UserCoverBackground + coverContainer = new ProfileCoverBackground { RelativeSizeAxes = Axes.Both, }, @@ -100,5 +100,10 @@ namespace osu.Game.Overlays.Profile IconTexture = "Icons/profile"; } } + + private class ProfileCoverBackground : UserCoverBackground + { + protected override double LoadDelay => 0; + } } } diff --git a/osu.Game/Screens/Ranking/Contracted/ContractedPanelMiddleContent.cs b/osu.Game/Screens/Ranking/Contracted/ContractedPanelMiddleContent.cs index 3ffb205d09..8cd0e7025e 100644 --- a/osu.Game/Screens/Ranking/Contracted/ContractedPanelMiddleContent.cs +++ b/osu.Game/Screens/Ranking/Contracted/ContractedPanelMiddleContent.cs @@ -70,14 +70,11 @@ namespace osu.Game.Screens.Ranking.Contracted RelativeSizeAxes = Axes.Both, Colour = Color4Extensions.FromHex("444") }, - new DelayedLoadUnloadWrapper(() => new UserCoverBackground + new UserCoverBackground { RelativeSizeAxes = Axes.Both, User = score.User, Colour = ColourInfo.GradientVertical(Color4.White.Opacity(0.5f), Color4Extensions.FromHex("#444").Opacity(0)) - }, 300, 5000) - { - RelativeSizeAxes = Axes.Both }, new FillFlowContainer { diff --git a/osu.Game/Screens/Ranking/ScorePanel.cs b/osu.Game/Screens/Ranking/ScorePanel.cs index 7ac98604f4..24d193e9a7 100644 --- a/osu.Game/Screens/Ranking/ScorePanel.cs +++ b/osu.Game/Screens/Ranking/ScorePanel.cs @@ -146,15 +146,12 @@ namespace osu.Game.Screens.Ranking Children = new[] { middleLayerBackground = new Box { RelativeSizeAxes = Axes.Both }, - new DelayedLoadUnloadWrapper(() => new UserCoverBackground + new UserCoverBackground { RelativeSizeAxes = Axes.Both, User = Score.User, Colour = ColourInfo.GradientVertical(Color4.White.Opacity(0.5f), Color4Extensions.FromHex("#444").Opacity(0)) - }, 300, 5000) - { - RelativeSizeAxes = Axes.Both - }, + } } }, middleLayerContentContainer = new Container { RelativeSizeAxes = Axes.Both } diff --git a/osu.Game/Users/UserCoverBackground.cs b/osu.Game/Users/UserCoverBackground.cs index 748d9bd939..34bbf6892e 100644 --- a/osu.Game/Users/UserCoverBackground.cs +++ b/osu.Game/Users/UserCoverBackground.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; @@ -23,6 +24,16 @@ namespace osu.Game.Users protected override Drawable CreateDrawable(User user) => new Cover(user); + protected override double LoadDelay => 300; + + /// + /// Delay before the background is unloaded while off-screen. + /// + protected virtual double UnloadDelay => 5000; + + protected override DelayedLoadWrapper CreateDelayedLoadWrapper(Func createContentFunc, double timeBeforeLoad) + => new DelayedLoadUnloadWrapper(createContentFunc, timeBeforeLoad, UnloadDelay); + [LongRunningLoad] private class Cover : CompositeDrawable { diff --git a/osu.Game/Users/UserPanel.cs b/osu.Game/Users/UserPanel.cs index 94c0c31cfc..57a87a713d 100644 --- a/osu.Game/Users/UserPanel.cs +++ b/osu.Game/Users/UserPanel.cs @@ -4,7 +4,6 @@ using System; 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.Sprites; @@ -25,7 +24,7 @@ namespace osu.Game.Users protected Action ViewProfile { get; private set; } - protected DelayedLoadUnloadWrapper Background { get; private set; } + protected Drawable Background { get; private set; } protected UserPanel(User user) { @@ -56,17 +55,12 @@ namespace osu.Game.Users RelativeSizeAxes = Axes.Both, Colour = ColourProvider?.Background5 ?? Colours.Gray1 }, - Background = new DelayedLoadUnloadWrapper(() => new UserCoverBackground + Background = new UserCoverBackground { RelativeSizeAxes = Axes.Both, Anchor = Anchor.Centre, Origin = Anchor.Centre, User = User, - }, 300, 5000) - { - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, - RelativeSizeAxes = Axes.Both, }, CreateLayout() }); From 9f6446d83685fb4d6052930129ae7b68f7781f50 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 28 Jul 2020 20:58:13 +0900 Subject: [PATCH 0272/1782] Add xmldocs + refactoring --- osu.Game/Screens/Ranking/ResultsScreen.cs | 17 +++++++++++------ osu.Game/Screens/Ranking/ScorePanelList.cs | 15 +++++++++++++-- 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index c5512822b2..254ab76f5b 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -50,6 +50,9 @@ namespace osu.Game.Screens.Ranking private ScorePanelList scorePanelList; private Container detachedPanelContainer; + private bool fetchedInitialScores; + private APIRequest nextPageRequest; + protected ResultsScreen(ScoreInfo score, bool allowRetry = true) { Score = score; @@ -172,13 +175,11 @@ namespace osu.Game.Screens.Ranking statisticsPanel.State.BindValueChanged(onStatisticsStateChanged, true); } - private APIRequest nextPageRequest; - protected override void Update() { base.Update(); - if (hasAnyScores && nextPageRequest == null) + if (fetchedInitialScores && nextPageRequest == null) { if (scorePanelList.IsScrolledToStart) nextPageRequest = FetchNextPage(-1, fetchScoresCallback); @@ -202,16 +203,20 @@ namespace osu.Game.Screens.Ranking /// An responsible for the fetch operation. This will be queued and performed automatically. protected virtual APIRequest FetchScores(Action> scoresCallback) => null; + /// + /// Performs a fetch of the next page of scores. This is invoked every frame until a non-null is returned. + /// + /// The fetch direction. -1 to fetch scores greater than the current start of the list, and 1 to fetch scores lower than the current end of the list. + /// 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 virtual APIRequest FetchNextPage(int direction, Action> scoresCallback) => null; - private bool hasAnyScores; - private void fetchScoresCallback(IEnumerable scores) => Schedule(() => { foreach (var s in scores) addScore(s); - hasAnyScores = true; + fetchedInitialScores = true; }); public override void OnEntering(IScreen last) diff --git a/osu.Game/Screens/Ranking/ScorePanelList.cs b/osu.Game/Screens/Ranking/ScorePanelList.cs index aba8314732..b2e1e91831 100644 --- a/osu.Game/Screens/Ranking/ScorePanelList.cs +++ b/osu.Game/Screens/Ranking/ScorePanelList.cs @@ -26,9 +26,20 @@ namespace osu.Game.Screens.Ranking /// private const float expanded_panel_spacing = 15; - public bool IsScrolledToStart => flow.Count > 0 && scroll.ScrollableExtent > 0 && scroll.Current <= 100; + /// + /// Minimum distance from either end point of the list that the list can be considered scrolled to the end point. + /// + private const float scroll_endpoint_distance = 100; - public bool IsScrolledToEnd => flow.Count > 0 && scroll.ScrollableExtent > 0 && scroll.IsScrolledToEnd(100); + /// + /// Whether this can be scrolled and is currently scrolled to the start. + /// + public bool IsScrolledToStart => flow.Count > 0 && scroll.ScrollableExtent > 0 && scroll.Current <= scroll_endpoint_distance; + + /// + /// Whether this can be scrolled and is currently scrolled to the end. + /// + public bool IsScrolledToEnd => flow.Count > 0 && scroll.ScrollableExtent > 0 && scroll.IsScrolledToEnd(scroll_endpoint_distance); /// /// An action to be invoked if a is clicked while in an expanded state. From db91d1de50e9ab92f7df1ec45abbea93696766af Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 28 Jul 2020 21:40:11 +0900 Subject: [PATCH 0273/1782] Use response params in next page request --- .../Multiplayer/IndexPlaylistScoresRequest.cs | 25 +++++----- .../Online/Multiplayer/IndexScoresParams.cs | 20 ++++++++ .../Online/Multiplayer/MultiplayerScores.cs | 6 +++ .../Multi/Ranking/TimeshiftResultsScreen.cs | 49 ++++++------------- 4 files changed, 53 insertions(+), 47 deletions(-) create mode 100644 osu.Game/Online/Multiplayer/IndexScoresParams.cs diff --git a/osu.Game/Online/Multiplayer/IndexPlaylistScoresRequest.cs b/osu.Game/Online/Multiplayer/IndexPlaylistScoresRequest.cs index 7273c0eea6..67793df344 100644 --- a/osu.Game/Online/Multiplayer/IndexPlaylistScoresRequest.cs +++ b/osu.Game/Online/Multiplayer/IndexPlaylistScoresRequest.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.IO.Network; using osu.Game.Extensions; using osu.Game.Online.API; @@ -16,31 +17,31 @@ namespace osu.Game.Online.Multiplayer private readonly int roomId; private readonly int playlistItemId; private readonly Cursor cursor; - private readonly MultiplayerScoresSort? sort; + private readonly IndexScoresParams indexParams; - public IndexPlaylistScoresRequest(int roomId, int playlistItemId, Cursor cursor = null, MultiplayerScoresSort? sort = null) + public IndexPlaylistScoresRequest(int roomId, int playlistItemId) { this.roomId = roomId; this.playlistItemId = playlistItemId; + } + + public IndexPlaylistScoresRequest(int roomId, int playlistItemId, [NotNull] Cursor cursor, [NotNull] IndexScoresParams indexParams) + : this(roomId, playlistItemId) + { this.cursor = cursor; - this.sort = sort; + this.indexParams = indexParams; } protected override WebRequest CreateWebRequest() { var req = base.CreateWebRequest(); - req.AddCursor(cursor); - - switch (sort) + if (cursor != null) { - case MultiplayerScoresSort.Ascending: - req.AddParameter("sort", "score_asc"); - break; + req.AddCursor(cursor); - case MultiplayerScoresSort.Descending: - req.AddParameter("sort", "score_desc"); - break; + foreach (var (key, value) in indexParams.Properties) + req.AddParameter(key, value.ToString()); } return req; diff --git a/osu.Game/Online/Multiplayer/IndexScoresParams.cs b/osu.Game/Online/Multiplayer/IndexScoresParams.cs new file mode 100644 index 0000000000..8160dfefaf --- /dev/null +++ b/osu.Game/Online/Multiplayer/IndexScoresParams.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.Collections.Generic; +using JetBrains.Annotations; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace osu.Game.Online.Multiplayer +{ + /// + /// A collection of parameters which should be passed to the index endpoint to fetch the next page. + /// + public class IndexScoresParams + { + [UsedImplicitly] + [JsonExtensionData] + public IDictionary Properties; + } +} diff --git a/osu.Game/Online/Multiplayer/MultiplayerScores.cs b/osu.Game/Online/Multiplayer/MultiplayerScores.cs index 6f74fc8984..2d0f98e032 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerScores.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerScores.cs @@ -29,5 +29,11 @@ namespace osu.Game.Online.Multiplayer /// [JsonProperty("user_score")] public MultiplayerScore UserScore { get; set; } + + /// + /// The parameters to be used to fetch the next page. + /// + [JsonProperty("params")] + public IndexScoresParams Params { get; set; } } } diff --git a/osu.Game/Screens/Multi/Ranking/TimeshiftResultsScreen.cs b/osu.Game/Screens/Multi/Ranking/TimeshiftResultsScreen.cs index 75a61b92ee..648bee385c 100644 --- a/osu.Game/Screens/Multi/Ranking/TimeshiftResultsScreen.cs +++ b/osu.Game/Screens/Multi/Ranking/TimeshiftResultsScreen.cs @@ -10,7 +10,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osu.Game.Online.API; -using osu.Game.Online.API.Requests; using osu.Game.Online.Multiplayer; using osu.Game.Scoring; using osu.Game.Screens.Ranking; @@ -23,8 +22,8 @@ namespace osu.Game.Screens.Multi.Ranking private readonly PlaylistItem playlistItem; private LoadingSpinner loadingLayer; - private Cursor higherScoresCursor; - private Cursor lowerScoresCursor; + private MultiplayerScores higherScores; + private MultiplayerScores lowerScores; [Resolved] private IAPIProvider api { get; set; } @@ -52,8 +51,8 @@ namespace osu.Game.Screens.Multi.Ranking protected override APIRequest FetchScores(Action> scoresCallback) { // This performs two requests: - // 1. A request to show the user's score. - // 2. If (1) fails, a request to index the room. + // 1. A request to show the user's score (and scores around). + // 2. If that fails, a request to index the room starting from the highest score. var userScoreReq = new ShowPlaylistUserScoreRequest(roomId, playlistItem.ID, api.LocalUser.Value.Id); @@ -64,13 +63,13 @@ namespace osu.Game.Screens.Multi.Ranking if (userScore.ScoresAround?.Higher != null) { allScores.AddRange(userScore.ScoresAround.Higher.Scores); - higherScoresCursor = userScore.ScoresAround.Higher.Cursor; + higherScores = userScore.ScoresAround.Higher; } if (userScore.ScoresAround?.Lower != null) { allScores.AddRange(userScore.ScoresAround.Lower.Scores); - lowerScoresCursor = userScore.ScoresAround.Lower.Cursor; + lowerScores = userScore.ScoresAround.Lower; } performSuccessCallback(scoresCallback, allScores); @@ -84,7 +83,7 @@ namespace osu.Game.Screens.Multi.Ranking indexReq.Success += r => { performSuccessCallback(scoresCallback, r.Scores); - lowerScoresCursor = r.Cursor; + lowerScores = r; }; indexReq.Failure += __ => loadingLayer.Hide(); @@ -99,39 +98,19 @@ namespace osu.Game.Screens.Multi.Ranking { Debug.Assert(direction == 1 || direction == -1); - Cursor cursor; - MultiplayerScoresSort sort; + MultiplayerScores pivot = direction == -1 ? higherScores : lowerScores; - switch (direction) - { - case -1: - cursor = higherScoresCursor; - sort = MultiplayerScoresSort.Ascending; - break; - - default: - cursor = lowerScoresCursor; - sort = MultiplayerScoresSort.Descending; - break; - } - - if (cursor == null) + if (pivot?.Cursor == null) return null; - var indexReq = new IndexPlaylistScoresRequest(roomId, playlistItem.ID, cursor, sort); + var indexReq = new IndexPlaylistScoresRequest(roomId, playlistItem.ID, pivot.Cursor, pivot.Params); indexReq.Success += r => { - switch (direction) - { - case -1: - higherScoresCursor = r.Cursor; - break; - - default: - lowerScoresCursor = r.Cursor; - break; - } + if (direction == -1) + higherScores = r; + else + lowerScores = r; performSuccessCallback(scoresCallback, r.Scores); }; From ccc377ae6af607215edc8756a1cd24881be427d1 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 28 Jul 2020 21:42:04 +0900 Subject: [PATCH 0274/1782] Remove unused enum --- .../Online/Multiplayer/MultiplayerScoresSort.cs | 14 -------------- 1 file changed, 14 deletions(-) delete mode 100644 osu.Game/Online/Multiplayer/MultiplayerScoresSort.cs diff --git a/osu.Game/Online/Multiplayer/MultiplayerScoresSort.cs b/osu.Game/Online/Multiplayer/MultiplayerScoresSort.cs deleted file mode 100644 index decb1c4dfe..0000000000 --- a/osu.Game/Online/Multiplayer/MultiplayerScoresSort.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. - -namespace osu.Game.Online.Multiplayer -{ - /// - /// Sorting option for indexing multiplayer scores. - /// - public enum MultiplayerScoresSort - { - Ascending, - Descending - } -} From a57b6bdc1817bec9274c92a3d879878707c355c1 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 29 Jul 2020 11:29:38 +0900 Subject: [PATCH 0275/1782] Remove cancellation of linked tokens --- osu.Game/Beatmaps/BeatmapDifficultyManager.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapDifficultyManager.cs b/osu.Game/Beatmaps/BeatmapDifficultyManager.cs index b3afd1d4fd..58b96b08b0 100644 --- a/osu.Game/Beatmaps/BeatmapDifficultyManager.cs +++ b/osu.Game/Beatmaps/BeatmapDifficultyManager.cs @@ -137,10 +137,7 @@ namespace osu.Game.Beatmaps trackedUpdateCancellationSource = null; foreach (var c in linkedCancellationSources) - { - c.Cancel(); c.Dispose(); - } linkedCancellationSources.Clear(); } From 46483622149904c9240f6f6e53ba4ef283edb07d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 29 Jul 2020 11:30:25 +0900 Subject: [PATCH 0276/1782] Safeguard against potential finalise-before-initialised --- osu.Game/Beatmaps/BeatmapDifficultyManager.cs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapDifficultyManager.cs b/osu.Game/Beatmaps/BeatmapDifficultyManager.cs index 58b96b08b0..b80b4e45ed 100644 --- a/osu.Game/Beatmaps/BeatmapDifficultyManager.cs +++ b/osu.Game/Beatmaps/BeatmapDifficultyManager.cs @@ -136,10 +136,13 @@ namespace osu.Game.Beatmaps trackedUpdateCancellationSource?.Cancel(); trackedUpdateCancellationSource = null; - foreach (var c in linkedCancellationSources) - c.Dispose(); + if (linkedCancellationSources != null) + { + foreach (var c in linkedCancellationSources) + c.Dispose(); - linkedCancellationSources.Clear(); + linkedCancellationSources.Clear(); + } } /// @@ -239,7 +242,7 @@ namespace osu.Game.Beatmaps base.Dispose(isDisposing); cancelTrackedBindableUpdate(); - updateScheduler.Dispose(); + updateScheduler?.Dispose(); } private readonly struct DifficultyCacheLookup : IEquatable From 6c7e806eacecd71ad073d160ac95dbcadaad8199 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 29 Jul 2020 12:39:18 +0900 Subject: [PATCH 0277/1782] Include executable hash when submitting multiplayer scores --- osu.Game/OsuGameBase.cs | 14 ++++++++++++++ osu.Game/Scoring/ScoreInfo.cs | 3 +++ osu.Game/Screens/Play/Player.cs | 1 + 3 files changed, 18 insertions(+) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index fe5c0704b7..964a7fdd35 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -11,6 +11,7 @@ using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Bindables; using osu.Framework.Development; +using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.IO.Stores; @@ -97,6 +98,11 @@ namespace osu.Game public virtual Version AssemblyVersion => Assembly.GetEntryAssembly()?.GetName().Version ?? new Version(); + /// + /// MD5 representation of the game executable. + /// + public string VersionHash { get; private set; } + public bool IsDeployedBuild => AssemblyVersion.Major > 0; public virtual string Version @@ -128,6 +134,14 @@ namespace osu.Game [BackgroundDependencyLoader] private void load() { + var assembly = Assembly.GetEntryAssembly(); + + if (assembly != null) + { + using (var str = File.OpenRead(assembly.Location)) + VersionHash = str.ComputeMD5Hash(); + } + Resources.AddStore(new DllResourceStore(OsuResources.ResourceAssembly)); dependencies.Cache(contextFactory = new DatabaseContextFactory(Storage)); diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index 84c0d5b54e..2cc065b5ad 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -51,6 +51,9 @@ namespace osu.Game.Scoring [NotMapped] public bool Passed { get; set; } = true; + [JsonProperty("version_hash")] + public string VersionHash { get; set; } + [JsonIgnore] public virtual RulesetInfo Ruleset { get; set; } diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 541275cf55..5df6cf42cb 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -465,6 +465,7 @@ namespace osu.Game.Screens.Play { Beatmap = Beatmap.Value.BeatmapInfo, Ruleset = rulesetInfo, + VersionHash = Game.VersionHash, Mods = Mods.Value.ToArray(), }; From d7fab98af0352c39d07a9bd8d3325aa373376e78 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 29 Jul 2020 06:39:23 +0300 Subject: [PATCH 0278/1782] Update comments container footer in line with web --- .../Overlays/Comments/CommentsContainer.cs | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/osu.Game/Overlays/Comments/CommentsContainer.cs b/osu.Game/Overlays/Comments/CommentsContainer.cs index f71808ba89..2a78748be6 100644 --- a/osu.Game/Overlays/Comments/CommentsContainer.cs +++ b/osu.Game/Overlays/Comments/CommentsContainer.cs @@ -78,21 +78,22 @@ namespace osu.Game.Overlays.Comments AutoSizeAxes = Axes.Y, Children = new Drawable[] { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = colourProvider.Background4 - }, new FillFlowContainer { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Direction = FillDirection.Vertical, + Margin = new MarginPadding { Bottom = 20 }, Children = new Drawable[] { deletedCommentsCounter = new DeletedCommentsCounter { - ShowDeleted = { BindTarget = ShowDeleted } + ShowDeleted = { BindTarget = ShowDeleted }, + Margin = new MarginPadding + { + Horizontal = 70, + Vertical = 10 + } }, new Container { @@ -102,7 +103,10 @@ namespace osu.Game.Overlays.Comments { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Margin = new MarginPadding(5), + Margin = new MarginPadding + { + Vertical = 10 + }, Action = getComments, IsLoading = true, } From 9e6d562872effe4ab8c763dd0a6db0b7f0be1ffe Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 29 Jul 2020 13:18:40 +0900 Subject: [PATCH 0279/1782] Send in initial score request instead --- osu.Game/Online/Multiplayer/CreateRoomScoreRequest.cs | 5 ++++- osu.Game/Scoring/ScoreInfo.cs | 3 --- osu.Game/Screens/Multi/Play/TimeshiftPlayer.cs | 2 +- osu.Game/Screens/Play/Player.cs | 1 - 4 files changed, 5 insertions(+), 6 deletions(-) diff --git a/osu.Game/Online/Multiplayer/CreateRoomScoreRequest.cs b/osu.Game/Online/Multiplayer/CreateRoomScoreRequest.cs index f973f96b37..2d99b12519 100644 --- a/osu.Game/Online/Multiplayer/CreateRoomScoreRequest.cs +++ b/osu.Game/Online/Multiplayer/CreateRoomScoreRequest.cs @@ -11,17 +11,20 @@ namespace osu.Game.Online.Multiplayer { private readonly int roomId; private readonly int playlistItemId; + private readonly string versionHash; - public CreateRoomScoreRequest(int roomId, int playlistItemId) + public CreateRoomScoreRequest(int roomId, int playlistItemId, string versionHash) { this.roomId = roomId; this.playlistItemId = playlistItemId; + this.versionHash = versionHash; } protected override WebRequest CreateWebRequest() { var req = base.CreateWebRequest(); req.Method = HttpMethod.Post; + req.AddParameter("version_hash", versionHash); return req; } diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index 2cc065b5ad..84c0d5b54e 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -51,9 +51,6 @@ namespace osu.Game.Scoring [NotMapped] public bool Passed { get; set; } = true; - [JsonProperty("version_hash")] - public string VersionHash { get; set; } - [JsonIgnore] public virtual RulesetInfo Ruleset { get; set; } diff --git a/osu.Game/Screens/Multi/Play/TimeshiftPlayer.cs b/osu.Game/Screens/Multi/Play/TimeshiftPlayer.cs index c2381fe219..da082692d7 100644 --- a/osu.Game/Screens/Multi/Play/TimeshiftPlayer.cs +++ b/osu.Game/Screens/Multi/Play/TimeshiftPlayer.cs @@ -58,7 +58,7 @@ namespace osu.Game.Screens.Multi.Play if (!playlistItem.RequiredMods.All(m => Mods.Value.Any(m.Equals))) throw new InvalidOperationException("Current Mods do not match PlaylistItem's RequiredMods"); - var req = new CreateRoomScoreRequest(roomId.Value ?? 0, playlistItem.ID); + var req = new CreateRoomScoreRequest(roomId.Value ?? 0, playlistItem.ID, Game.VersionHash); req.Success += r => token = r.ID; req.Failure += e => { diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 5df6cf42cb..541275cf55 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -465,7 +465,6 @@ namespace osu.Game.Screens.Play { Beatmap = Beatmap.Value.BeatmapInfo, Ruleset = rulesetInfo, - VersionHash = Game.VersionHash, Mods = Mods.Value.ToArray(), }; From c3c60334ec7b1806cd9cb7e99b39a0d9951c50a1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 29 Jul 2020 15:24:14 +0900 Subject: [PATCH 0280/1782] Add skinning support to spinner test scene --- .../TestSceneSpinner.cs | 40 +++++++------------ 1 file changed, 15 insertions(+), 25 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinner.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinner.cs index 67afc45e32..8e3a22bfdc 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinner.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinner.cs @@ -4,37 +4,32 @@ using System.Linq; 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.Mods; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; -using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Osu.Tests { [TestFixture] - public class TestSceneSpinner : OsuTestScene + public class TestSceneSpinner : OsuSkinnableTestScene { - private readonly Container content; - protected override Container Content => content; - private int depthIndex; public TestSceneSpinner() { - base.Content.Add(content = new OsuInputManager(new RulesetInfo { ID = 0 })); + // base.Content.Add(content = new OsuInputManager(new RulesetInfo { ID = 0 })); - AddStep("Miss Big", () => testSingle(2)); - AddStep("Miss Medium", () => testSingle(5)); - AddStep("Miss Small", () => testSingle(7)); - AddStep("Hit Big", () => testSingle(2, true)); - AddStep("Hit Medium", () => testSingle(5, true)); - AddStep("Hit Small", () => testSingle(7, true)); + AddStep("Miss Big", () => SetContents(() => testSingle(2))); + AddStep("Miss Medium", () => SetContents(() => testSingle(5))); + AddStep("Miss Small", () => SetContents(() => testSingle(7))); + AddStep("Hit Big", () => SetContents(() => testSingle(2, true))); + AddStep("Hit Medium", () => SetContents(() => testSingle(5, true))); + AddStep("Hit Small", () => SetContents(() => testSingle(7, true))); } - private void testSingle(float circleSize, bool auto = false) + private Drawable testSingle(float circleSize, bool auto = false) { var spinner = new Spinner { StartTime = Time.Current + 2000, EndTime = Time.Current + 5000 }; @@ -49,12 +44,12 @@ namespace osu.Game.Rulesets.Osu.Tests foreach (var mod in SelectedMods.Value.OfType()) mod.ApplyToDrawableHitObjects(new[] { drawable }); - Add(drawable); + return drawable; } private class TestDrawableSpinner : DrawableSpinner { - private bool auto; + private readonly bool auto; public TestDrawableSpinner(Spinner s, bool auto) : base(s) @@ -62,16 +57,11 @@ namespace osu.Game.Rulesets.Osu.Tests this.auto = auto; } - protected override void CheckForResult(bool userTriggered, double timeOffset) + protected override void Update() { - if (auto && !userTriggered && Time.Current > Spinner.StartTime + Spinner.Duration / 2 && Progress < 1) - { - // force completion only once to not break human interaction - Disc.CumulativeRotation = Spinner.SpinsRequired * 360; - auto = false; - } - - base.CheckForResult(userTriggered, timeOffset); + base.Update(); + if (auto) + Disc.Rotate((float)(Clock.ElapsedFrameTime * 3)); } } } From 0f1f4b2b5c97656e80650b4560bfe4d1adb3b740 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 29 Jul 2020 15:36:42 +0900 Subject: [PATCH 0281/1782] Add pooling for mania hit explosions --- .../Skinning/LegacyHitExplosion.cs | 7 ++- osu.Game.Rulesets.Mania/UI/Column.cs | 15 ++----- .../UI/DefaultHitExplosion.cs | 28 +++++------- osu.Game.Rulesets.Mania/UI/IHitExplosion.cs | 16 +++++++ .../UI/PoolableHitExplosion.cs | 44 +++++++++++++++++++ 5 files changed, 79 insertions(+), 31 deletions(-) create mode 100644 osu.Game.Rulesets.Mania/UI/IHitExplosion.cs create mode 100644 osu.Game.Rulesets.Mania/UI/PoolableHitExplosion.cs diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyHitExplosion.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyHitExplosion.cs index bc93bb2615..c2b39945c2 100644 --- a/osu.Game.Rulesets.Mania/Skinning/LegacyHitExplosion.cs +++ b/osu.Game.Rulesets.Mania/Skinning/LegacyHitExplosion.cs @@ -6,13 +6,14 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Animations; +using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Skinning; using osuTK; namespace osu.Game.Rulesets.Mania.Skinning { - public class LegacyHitExplosion : LegacyManiaColumnElement + public class LegacyHitExplosion : LegacyManiaColumnElement, IHitExplosion { private readonly IBindable direction = new Bindable(); @@ -62,10 +63,8 @@ namespace osu.Game.Rulesets.Mania.Skinning explosion.Anchor = direction.NewValue == ScrollingDirection.Up ? Anchor.TopCentre : Anchor.BottomCentre; } - protected override void LoadComplete() + public void Animate() { - base.LoadComplete(); - explosion?.FadeInFromZero(80) .Then().FadeOut(120); } diff --git a/osu.Game.Rulesets.Mania/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs index 642353bd0b..7ddac759db 100644 --- a/osu.Game.Rulesets.Mania/UI/Column.cs +++ b/osu.Game.Rulesets.Mania/UI/Column.cs @@ -9,9 +9,9 @@ using osu.Game.Graphics; using osu.Game.Rulesets.Objects.Drawables; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Graphics.Pooling; using osu.Framework.Input.Bindings; using osu.Game.Rulesets.Judgements; -using osu.Game.Rulesets.Mania.Objects.Drawables; using osu.Game.Rulesets.Mania.UI.Components; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Skinning; @@ -34,8 +34,8 @@ namespace osu.Game.Rulesets.Mania.UI public readonly Bindable Action = new Bindable(); public readonly ColumnHitObjectArea HitObjectArea; - internal readonly Container TopLevelContainer; + private readonly DrawablePool hitExplosionPool; public Container UnderlayElements => HitObjectArea.UnderlayElements; @@ -53,6 +53,7 @@ namespace osu.Game.Rulesets.Mania.UI InternalChildren = new[] { + hitExplosionPool = new DrawablePool(5), // For input purposes, the background is added at the highest depth, but is then proxied back below all other elements background.CreateProxy(), HitObjectArea = new ColumnHitObjectArea(Index, HitObjectContainer) { RelativeSizeAxes = Axes.Both }, @@ -108,15 +109,7 @@ namespace osu.Game.Rulesets.Mania.UI if (!result.IsHit || !judgedObject.DisplayResult || !DisplayJudgements.Value) return; - var explosion = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.HitExplosion, Index), _ => - new DefaultHitExplosion(judgedObject.AccentColour.Value, judgedObject is DrawableHoldNoteTick)) - { - RelativeSizeAxes = Axes.Both - }; - - HitObjectArea.Explosions.Add(explosion); - - explosion.Delay(200).Expire(true); + HitObjectArea.Explosions.Add(hitExplosionPool.Get()); } public bool OnPressed(ManiaAction action) diff --git a/osu.Game.Rulesets.Mania/UI/DefaultHitExplosion.cs b/osu.Game.Rulesets.Mania/UI/DefaultHitExplosion.cs index 7a047ed121..bac77b134c 100644 --- a/osu.Game.Rulesets.Mania/UI/DefaultHitExplosion.cs +++ b/osu.Game.Rulesets.Mania/UI/DefaultHitExplosion.cs @@ -15,7 +15,7 @@ using osuTK.Graphics; namespace osu.Game.Rulesets.Mania.UI { - public class DefaultHitExplosion : CompositeDrawable + public class DefaultHitExplosion : CompositeDrawable, IHitExplosion { public override bool RemoveWhenNotAlive => true; @@ -123,21 +123,6 @@ namespace osu.Game.Rulesets.Mania.UI direction.BindValueChanged(onDirectionChanged, true); } - protected override void LoadComplete() - { - const double duration = 200; - - base.LoadComplete(); - - 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); - } - private void onDirectionChanged(ValueChangedEvent direction) { if (direction.NewValue == ScrollingDirection.Up) @@ -151,5 +136,16 @@ namespace osu.Game.Rulesets.Mania.UI Y = -DefaultNotePiece.NOTE_HEIGHT / 2; } } + + public void Animate() + { + largeFaint + .ResizeTo(largeFaint.Size * new Vector2(5, 1), PoolableHitExplosion.DURATION, Easing.OutQuint) + .FadeOut(PoolableHitExplosion.DURATION * 2); + + mainGlow1.ScaleTo(1.4f, PoolableHitExplosion.DURATION, Easing.OutQuint); + + this.FadeOut(PoolableHitExplosion.DURATION, Easing.Out); + } } } diff --git a/osu.Game.Rulesets.Mania/UI/IHitExplosion.cs b/osu.Game.Rulesets.Mania/UI/IHitExplosion.cs new file mode 100644 index 0000000000..da1742e48b --- /dev/null +++ b/osu.Game.Rulesets.Mania/UI/IHitExplosion.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.Mania.UI +{ + /// + /// Common interface for all hit explosion bodies. + /// + public interface IHitExplosion + { + /// + /// Begins animating this . + /// + void Animate(); + } +} diff --git a/osu.Game.Rulesets.Mania/UI/PoolableHitExplosion.cs b/osu.Game.Rulesets.Mania/UI/PoolableHitExplosion.cs new file mode 100644 index 0000000000..43808f99a8 --- /dev/null +++ b/osu.Game.Rulesets.Mania/UI/PoolableHitExplosion.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.Pooling; +using osu.Game.Skinning; + +namespace osu.Game.Rulesets.Mania.UI +{ + public class PoolableHitExplosion : PoolableDrawable + { + public const double DURATION = 200; + + [Resolved] + private Column column { get; set; } + + private SkinnableDrawable skinnableExplosion; + + public PoolableHitExplosion() + { + RelativeSizeAxes = Axes.Both; + } + + [BackgroundDependencyLoader] + private void load() + { + InternalChild = skinnableExplosion = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.HitExplosion, column.Index), + _ => new DefaultHitExplosion(column.AccentColour, false /*todo */)) + { + RelativeSizeAxes = Axes.Both + }; + } + + protected override void PrepareForUse() + { + base.PrepareForUse(); + + (skinnableExplosion?.Drawable as IHitExplosion)?.Animate(); + + this.Delay(DURATION).Then().Expire(); + } + } +} From 7f2e554ad47a6e2ac9f3d691d1d128838a49546f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 29 Jul 2020 15:52:25 +0900 Subject: [PATCH 0282/1782] Fix animations not being reset --- .../Skinning/LegacyHitExplosion.cs | 2 ++ osu.Game.Rulesets.Mania/UI/DefaultHitExplosion.cs | 15 +++++++++++---- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyHitExplosion.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyHitExplosion.cs index c2b39945c2..c4fcffbc22 100644 --- a/osu.Game.Rulesets.Mania/Skinning/LegacyHitExplosion.cs +++ b/osu.Game.Rulesets.Mania/Skinning/LegacyHitExplosion.cs @@ -65,6 +65,8 @@ namespace osu.Game.Rulesets.Mania.Skinning public void Animate() { + (explosion as IFramedAnimation)?.GotoFrame(0); + explosion?.FadeInFromZero(80) .Then().FadeOut(120); } diff --git a/osu.Game.Rulesets.Mania/UI/DefaultHitExplosion.cs b/osu.Game.Rulesets.Mania/UI/DefaultHitExplosion.cs index bac77b134c..3007668117 100644 --- a/osu.Game.Rulesets.Mania/UI/DefaultHitExplosion.cs +++ b/osu.Game.Rulesets.Mania/UI/DefaultHitExplosion.cs @@ -17,6 +17,8 @@ namespace osu.Game.Rulesets.Mania.UI { public class DefaultHitExplosion : CompositeDrawable, IHitExplosion { + private const float default_large_faint_size = 0.8f; + public override bool RemoveWhenNotAlive => true; private readonly IBindable direction = new Bindable(); @@ -54,7 +56,7 @@ namespace osu.Game.Rulesets.Mania.UI RelativeSizeAxes = Axes.Both, Masking = true, // we want our size to be very small so the glow dominates it. - Size = new Vector2(0.8f), + Size = new Vector2(default_large_faint_size), Blending = BlendingParameters.Additive, EdgeEffect = new EdgeEffectParameters { @@ -140,12 +142,17 @@ namespace osu.Game.Rulesets.Mania.UI public void Animate() { largeFaint - .ResizeTo(largeFaint.Size * new Vector2(5, 1), PoolableHitExplosion.DURATION, Easing.OutQuint) + .ResizeTo(default_large_faint_size) + .Then() + .ResizeTo(default_large_faint_size * new Vector2(5, 1), PoolableHitExplosion.DURATION, Easing.OutQuint) .FadeOut(PoolableHitExplosion.DURATION * 2); - mainGlow1.ScaleTo(1.4f, PoolableHitExplosion.DURATION, Easing.OutQuint); + mainGlow1 + .ScaleTo(1) + .Then() + .ScaleTo(1.4f, PoolableHitExplosion.DURATION, Easing.OutQuint); - this.FadeOut(PoolableHitExplosion.DURATION, Easing.Out); + this.FadeOutFromOne(PoolableHitExplosion.DURATION, Easing.Out); } } } From 00821e7b653b58f55de16c9e9cfdb87b2846f85c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 29 Jul 2020 16:14:19 +0900 Subject: [PATCH 0283/1782] Re-implement support for small ticks --- .../Skinning/LegacyHitExplosion.cs | 3 +- osu.Game.Rulesets.Mania/UI/Column.cs | 2 +- .../UI/DefaultHitExplosion.cs | 43 +++++++++++-------- osu.Game.Rulesets.Mania/UI/IHitExplosion.cs | 5 ++- .../UI/PoolableHitExplosion.cs | 13 ++++-- 5 files changed, 41 insertions(+), 25 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyHitExplosion.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyHitExplosion.cs index c4fcffbc22..12747924de 100644 --- a/osu.Game.Rulesets.Mania/Skinning/LegacyHitExplosion.cs +++ b/osu.Game.Rulesets.Mania/Skinning/LegacyHitExplosion.cs @@ -6,6 +6,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Animations; +using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Skinning; @@ -63,7 +64,7 @@ namespace osu.Game.Rulesets.Mania.Skinning explosion.Anchor = direction.NewValue == ScrollingDirection.Up ? Anchor.TopCentre : Anchor.BottomCentre; } - public void Animate() + public void Animate(JudgementResult result) { (explosion as IFramedAnimation)?.GotoFrame(0); diff --git a/osu.Game.Rulesets.Mania/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs index 7ddac759db..255ce4c064 100644 --- a/osu.Game.Rulesets.Mania/UI/Column.cs +++ b/osu.Game.Rulesets.Mania/UI/Column.cs @@ -109,7 +109,7 @@ namespace osu.Game.Rulesets.Mania.UI if (!result.IsHit || !judgedObject.DisplayResult || !DisplayJudgements.Value) return; - HitObjectArea.Explosions.Add(hitExplosionPool.Get()); + HitObjectArea.Explosions.Add(hitExplosionPool.Get(e => e.Apply(result))); } public bool OnPressed(ManiaAction action) diff --git a/osu.Game.Rulesets.Mania/UI/DefaultHitExplosion.cs b/osu.Game.Rulesets.Mania/UI/DefaultHitExplosion.cs index 3007668117..225269cf48 100644 --- a/osu.Game.Rulesets.Mania/UI/DefaultHitExplosion.cs +++ b/osu.Game.Rulesets.Mania/UI/DefaultHitExplosion.cs @@ -8,6 +8,8 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Effects; using osu.Framework.Utils; +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Mania.Judgements; using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces; using osu.Game.Rulesets.UI.Scrolling; using osuTK; @@ -21,31 +23,30 @@ namespace osu.Game.Rulesets.Mania.UI public override bool RemoveWhenNotAlive => true; + [Resolved] + private Column column { get; set; } + private readonly IBindable direction = new Bindable(); - private readonly CircularContainer largeFaint; - private readonly CircularContainer mainGlow1; + private CircularContainer largeFaint; + private CircularContainer mainGlow1; - public DefaultHitExplosion(Color4 objectColour, bool isSmall = false) + public DefaultHitExplosion() { Origin = Anchor.Centre; RelativeSizeAxes = Axes.X; Height = DefaultNotePiece.NOTE_HEIGHT; + } - // scale roughly in-line with visual appearance of notes - Scale = new Vector2(1f, 0.6f); - - if (isSmall) - Scale *= 0.5f; - + [BackgroundDependencyLoader] + private void load(IScrollingInfo scrollingInfo) + { const float angle_variangle = 15; // should be less than 45 - const float roundness = 80; - const float initial_height = 10; - var colour = Interpolation.ValueAt(0.4f, objectColour, Color4.White, 0, 1); + var colour = Interpolation.ValueAt(0.4f, column.AccentColour, Color4.White, 0, 1); InternalChildren = new Drawable[] { @@ -61,7 +62,7 @@ namespace osu.Game.Rulesets.Mania.UI EdgeEffect = new EdgeEffectParameters { Type = EdgeEffectType.Glow, - Colour = Interpolation.ValueAt(0.1f, objectColour, Color4.White, 0, 1).Opacity(0.3f), + Colour = Interpolation.ValueAt(0.1f, column.AccentColour, Color4.White, 0, 1).Opacity(0.3f), Roundness = 160, Radius = 200, }, @@ -76,7 +77,7 @@ namespace osu.Game.Rulesets.Mania.UI EdgeEffect = new EdgeEffectParameters { Type = EdgeEffectType.Glow, - Colour = Interpolation.ValueAt(0.6f, objectColour, Color4.White, 0, 1), + Colour = Interpolation.ValueAt(0.6f, column.AccentColour, Color4.White, 0, 1), Roundness = 20, Radius = 50, }, @@ -116,11 +117,7 @@ namespace osu.Game.Rulesets.Mania.UI }, } }; - } - [BackgroundDependencyLoader] - private void load(IScrollingInfo scrollingInfo) - { direction.BindTo(scrollingInfo.Direction); direction.BindValueChanged(onDirectionChanged, true); } @@ -139,8 +136,16 @@ namespace osu.Game.Rulesets.Mania.UI } } - public void Animate() + public void Animate(JudgementResult result) { + // scale roughly in-line with visual appearance of notes + Vector2 scale = new Vector2(1, 0.6f); + + if (result.Judgement is HoldNoteTickJudgement) + scale *= 0.5f; + + this.ScaleTo(scale); + largeFaint .ResizeTo(default_large_faint_size) .Then() diff --git a/osu.Game.Rulesets.Mania/UI/IHitExplosion.cs b/osu.Game.Rulesets.Mania/UI/IHitExplosion.cs index da1742e48b..3252dcc276 100644 --- a/osu.Game.Rulesets.Mania/UI/IHitExplosion.cs +++ b/osu.Game.Rulesets.Mania/UI/IHitExplosion.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.Judgements; + namespace osu.Game.Rulesets.Mania.UI { /// @@ -11,6 +13,7 @@ namespace osu.Game.Rulesets.Mania.UI /// /// Begins animating this . /// - void Animate(); + /// The type of that caused this explosion. + void Animate(JudgementResult result); } } diff --git a/osu.Game.Rulesets.Mania/UI/PoolableHitExplosion.cs b/osu.Game.Rulesets.Mania/UI/PoolableHitExplosion.cs index 43808f99a8..64b7d7d550 100644 --- a/osu.Game.Rulesets.Mania/UI/PoolableHitExplosion.cs +++ b/osu.Game.Rulesets.Mania/UI/PoolableHitExplosion.cs @@ -4,6 +4,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Pooling; +using osu.Game.Rulesets.Judgements; using osu.Game.Skinning; namespace osu.Game.Rulesets.Mania.UI @@ -12,6 +13,8 @@ namespace osu.Game.Rulesets.Mania.UI { public const double DURATION = 200; + public JudgementResult Result { get; private set; } + [Resolved] private Column column { get; set; } @@ -25,18 +28,22 @@ namespace osu.Game.Rulesets.Mania.UI [BackgroundDependencyLoader] private void load() { - InternalChild = skinnableExplosion = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.HitExplosion, column.Index), - _ => new DefaultHitExplosion(column.AccentColour, false /*todo */)) + InternalChild = skinnableExplosion = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.HitExplosion, column.Index), _ => new DefaultHitExplosion()) { RelativeSizeAxes = Axes.Both }; } + public void Apply(JudgementResult result) + { + Result = result; + } + protected override void PrepareForUse() { base.PrepareForUse(); - (skinnableExplosion?.Drawable as IHitExplosion)?.Animate(); + (skinnableExplosion?.Drawable as IHitExplosion)?.Animate(Result); this.Delay(DURATION).Then().Expire(); } From d01d1ce3f1f6bd1b239eab98cf0ffb1a6abdb9bc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 29 Jul 2020 16:25:10 +0900 Subject: [PATCH 0284/1782] Add initial support for spinner disc skinning --- .../old-skin/spinner-approachcircle.png | Bin 0 -> 26350 bytes .../Resources/old-skin/spinner-background.png | Bin 0 -> 46103 bytes .../Resources/old-skin/spinner-circle.png | Bin 0 -> 166439 bytes .../Resources/old-skin/spinner-clear.png | Bin 0 -> 39074 bytes .../Resources/old-skin/spinner-metre.png | Bin 0 -> 14518 bytes .../Resources/old-skin/spinner-osu.png | Bin 0 -> 18585 bytes .../Resources/old-skin/spinner-spin.png | Bin 0 -> 21353 bytes .../Resources/old-skin/spinnerbonus.wav | Bin 0 -> 309536 bytes .../Resources/old-skin/spinnerspin.wav | Bin 0 -> 36868 bytes .../Objects/Drawables/DrawableSpinner.cs | 2 +- .../Objects/Drawables/Pieces/SpinnerDisc.cs | 30 ++++++++++++------ osu.Game.Rulesets.Osu/OsuSkinComponents.cs | 1 + .../Skinning/OsuLegacySkinTransformer.cs | 9 ++++++ 13 files changed, 31 insertions(+), 11 deletions(-) create mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/old-skin/spinner-approachcircle.png create mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/old-skin/spinner-background.png create mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/old-skin/spinner-circle.png create mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/old-skin/spinner-clear.png create mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/old-skin/spinner-metre.png create mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/old-skin/spinner-osu.png create mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/old-skin/spinner-spin.png create mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/old-skin/spinnerbonus.wav create mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/old-skin/spinnerspin.wav diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/spinner-approachcircle.png b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/spinner-approachcircle.png new file mode 100644 index 0000000000000000000000000000000000000000..3811e5050f4cc0404a5713b1b875ab33ac32243a GIT binary patch literal 26350 zcmY&=c|6qn_y7An8^a7^ZS0dROH&3BlD8$1l!O??n5d~pNeY$sScXJKqP=D=ZiGT} z+tmmqQp1&`Qbb8AgjA~UYjFGget-11k1Oxja?bOd^E}I|gFdU4ljSt!AP6FRdbs&Q z5X_VQktKkiB%#Lr;6J#{&fd-tbg9u6pZOmAzeen`z}U6Xn_~Bb#Y8~P;nC~}nrBql z<_O=2u<-bu4KsFD!!1Vnw7i;!a{JpwI*)n|19MAFalMH zsRtady1Q;vB`p$Xq@Iu+)~Kw^-4Z*jC!hU@(36Cl1iwwvs4R6Ouf*ag%^aVs+>-Q+~0~azYgJlyZ+TW!s%zo5$KStpfxj0l~f>y61|)sC-Z`; zDuY@(E%rBBs}#tkD0$&)(PEd$oMH^xnj{%`wvFLQ@*!%q_U+7Mjn#N=1zY^YKbiXsEk6Zw=CX7E&o#fvQ!Y?~uQokE5r+$j5YNx^zDbt2ky9z%rp6gsdo~v2gYm-#~Wju1e<;$Wj+Tsmd^i` zJu%-Mrb7sn9Y88XyVT%O3rYeW4SAhnVXOJpnJtg0vN-ydd*K~22wSv0OR{h};(J1z zKy?!2!zI;w73-sgW9v+QCD-$Mq65t+V{&g4yL{R?%NTMd1PHcfGO{pR$7K2G$(^lc zP@l`WIZW{vm*8&}Ox27Ap&GPMgkc#~QFP10a34&$cikZGUwWc>klB=zt9j-zWJ)%OzQyfWE=U2s$axhuC;RVKvCCQW*W3Wy6h{p%fiPsUsq?cXq(WEM6e8NIXp_DX zN1t-8TlJMnb6tH&VwRcXLppU1ZSl@hu+82vBeceHiX{CY-yGX1p_J=GWO-9;mA+r3 zXq|;MRC1R`{#J^}@AoO*p_lR565*X=so^6Bh&4wh(*iuRl9ezJh9hEA?egK#t@?dU zXbxNz3SEg*wO%L4^wX%!CiGMecsC+dcD_bCPr;V9aImrb^Ux7u#StX*rL`Q7F=MWM zV+yRYgV(D32+EQk8Gp`acoyDlTMR-=!oROsY`6gFhb~PS916L(s zGVPI;TonL||G?54KsQ*T|6 z^_wd;<}c$X5CkreD07*vnNuM1T9OdIq~!HCS$bAC@~rW`tKghzYS}#Wg@HJ3YSG2Q zOCvhpTCzF{#ZNAa;|Qve(HJM|rK!Tm?IoITVTGhsoSZd8&9{o&NlS{8^Ps2rt>IGr zaedY1FJ|5RU1nUSv7Vc9Q*?KW9x`(e-OdM zyEb77QxbwLnX@wwu2xlV&)b{UacI)U{-O208DzTNabYdfT#($NBYsOp^11UVA6$dE zQ`o+a-;(5?SdsExx#BQ~-C-XF?^p_SHNUD5f8VooOZ~hcC26r46l){2aKC6s-QlNR z|H72JWKQkj)Lcci)F@i2&b+&d*r=uk!Az_YrI%QUxIMxgWe*T~lA2uEa3nE0aw{YiZfWf93}M-pL4>a4_Ot-GyRloH zf2FHW2m*TdbL5rxUsjEYKwp?67p2jrPD8Lx^G>T732a*P7vgk_66g0Ih)|Eg264D_ zLjL&(CW~j|!E&f@&aaE$D2@=Z?CS4WCZ1tmZ{0W};1sE<%P6cbG6D<&XOO8hl_pH= zj(>gZ{cH>abKKzz3hS^>&IFa zS!NNTQ5)|>PUL6#{D>5iRV8lYz|qzo$kCvDp){)14`|oZX5t6wksO!kl}5uvop|7E zj`NZCKEs_*X{u~`%@8e<4Sss~R-{sg6>^azNyHUyd-y=viy;P&+9MWeCpx&N&c{iOk@PO;~&%GXpMCMa13|Ccd?p z7}rOPT-vJ~n?XE!R-{3zsfy*xq(dJ=_J#IJ?9_Qft!u0#{T?=hf$&op|iX!x96MCbU7=MlU z&E#maL}(n(`4A#^;jp?|s+}nLT4r%F>g}7fY+GPfGK`zK(7uJRP1Qqu%eA`_Ik>$) zoo@FeJwR2YX@>cy`x6oz*La^MPLQcAF1}`#vc}?Cf=UuUK_ygTM(lgDhIMu542YhB zvn6tVSzWH)v>)X{wfbd*df%r~o1lw&MzX_VlM}=<0Kqy$;2}n)SGH^s6da{mTP2&}{5j1l&Jsqx=yh3Q zD@<S>?OY~Dhd2$ z;%ZKmSiB{2zV8!5LCV?t(B6SzWt|zpjlq>?Dk{SaeEv@Hp5j`bW?;jek}3Hg@ev6U zLp{!2#rS`fh9Xbp1SZH7ZupvuWtH7$<=5}w-t7!fZRyH4`HEJ&loklJsy zD7I59Q%{n+e(z9cI*ZkjTM-CpVXY>;5SHjyzFox-ALkgntpV3i`92$ zdU@hrGhAoWUwVZ#DQm>&{yVsm+F>Ho;@V z&eYL4Mccm(yr?yywI7-|eBp#3Um^1B8!9$@v3%T4i54eq zk-eR_>ok+WeoQ0^)bx}k7i~?xpaF@2E-FhGnWsJ{Wi09)xKG))37Nl0r{?M&Rm$^2 zlPT?o(3WNU+&(N@k~*Q!N{sR=^>g0g(t>S?*4~VkpS_9w_|GlWCe}Ss(Hb*tH9m91 z2}FS#`J@+mrdV-sdx~*>6qQ!Dy#0pZI|u8_r#FdjmyO96$c3q9VSLSH$Gl@-0k!V| zHlbnt@nw1dId5sN~^J zi)xwP7OC1Lztw{~F^&efok1tEoWJZ=6sW1hd=87l7lUI!)!MZMf@J1mfgR3) z(0CfND1n$H)cCv@J6L^t9Vx@JzZKj>ax;07Tb#TX^KluPpdb*g4I(t{S(~C6uw6oU zI+xSG;}~2eQj70z<8++|86zMbND%NXvGsWGB0)&<5|KVczk`kPQz<^OU+|F7Wtxx= zGjfKHz*TEwV;ecDB%T~*|7u+1A{CB-7H8faoLYh+?K0xJa=EZ(*HX^Hexvs75}&67 z_15^AlaHF?eLl!sNz`aRlA9cNDy#+X0c6iHjog@ zB7hQ|YX(dX|M6LCiu&85kWWf8U@awYDD6um7N4w2&V=$u8#w)l{*3HnAuX_k#UW>v z^JXH;9h=bdB7rJuN#zu`?lk!cog4@?_$SjnN1lpzEd_qnFD1?{*yRh!=!>F&cTCAz z@lynP+E|Q@T2kXO=$Z3YNqNSYi^i4t>4#a(KF(zpi1}e8uX&TCDcKilS|S*KF8Gqg zn&)%(trBa_>8ww_y7-jq$&)Q;Vg@n58GRI1ojh^H6c+FTDcIfvL|slD`AYfjSk6JW z8PJd9VD2NLBPtIC zt_|x;B)xEit|WCdjAN@6*JLWL2A$59x!E_)&4X zw;+3aw+7x-hxXWs_R2=)xrxjN=i>;&Xx-Cf&8=L?ROWiSTP);shrRu0RAw4@=$&!+ z)M`qyu}uV_UXr^28jvokH9wlBtNdP}j zNq9Ix;`<-yQ+Pb;@}*Q3%MK*^*)2)w`a(8M%EnmFh}jtRwoFl0vb7H58vlcsn;^|Y z>xIMuWoWC>86H~#hbdAiDiMv%L+-)Wg{;Mxu^yw&DQsl2W=D!e8aw9Lm1NEB0)H~Pul3HZWeOzz-1RZheC(n=jF9ZOpr{G9`D}6H6-=HH3DZ<; ze8QQox|74kMFnE%rI*75kxSvDubq$a(E{OJuc_+{r%kPXM%6%PQXRy?@T(BZ+ zY{x~g0O4W`5^^1PS1{LDl4;~kXv-NPEK>a_jF5>b7F+n5N%imv=)ujZ+!u;kkNtR7 zw22mNYgHnV@wTjsvwQ{8H9e~PI~Dg@6M^mIp#Zz7O;Uz7u@p5f_H-m_w6jDJh|a2H ze~;f9yl^t_ccXqut#FQ=gDCntDtK1Gt*V}@n-D-8vd zw0@IX89@Pv`nHuow{%2{iKg)Oe$?=IWlAp7{w4HR_$`e8{d$(Y3 z6AL6jk86A)s}r=pwtbP*wa6(SMe3ia?Zz=$_&>| z*i9i9!uhLHbky^uN=wg#zr9@~xe}*AsORn#T+V5HrmJZGhGWhwMsF<-6ZuN3<{YRq zv-r|tGnr^YW5fC)Xes9zrc7IssOWlNw?0^8JQI#vZZ!nfF>(<6P$dZ};23Zuf4Sft zesYD0s#1Y`&&S$N%q@&T!PTGJj>wdzkDdw3Sm*)E3WOmH)cDjh;iAPRZ0C1nDGQ## zFV7>7TAx*}=$Vi0i_sZ7dv``4ytw{hvbT1@`3I%i^@50&g(mKw9K{8dik`qET(7f{ zrGuDQmU`qmtCB;@AA!Tz;X8+0ImH%o?zO|%-#h&jDmN}ffFyP)SducribF{m2eT)i5d20$o+rJ<@BQg4777G^k~f-LD&ATe--fx4fIC-!yE9ze$w-1X1(a8Hv3)KIA<8Y7Cj$cP$SQ_ld&rM_76~qcAXFMu#7tsa zegN7^pb$RB#%-c~G|>s+oEXc8cLi=~TkHUGQ%{ckH0PkD*RFyOX=O z@&5X(Piv*{35WakD^#Av4s{+_z*&eM{UOzz%IInAsKR^a(0fu@lu#v)5sbG@%3C$$ ztC>k|E`%H#LO-p&W0ueu2Vq^J zvcAX(7U*!!3$3x?o2>@g$QvvlV#!*y+d~q?ke&UH<{9yrAZDUF7Bt>bfCUFp3~l$5 z!Ufn~!%aT@kVCdcT<-@#g*Fg!0Uyc&lUY{@_1uQzbzA4gKgsi&-CLs`0K zKqRd-LE*l&L1FpW5>hXEm}zo^rEV@NPpC!h%B@TGNae%2NZrw5e$9fv^cm>_E)E`v zAT;W$GY>3i9l5>tesp@eM>Xmp5krrYJE z&gN-e$Ud!%a|~LCW=?<{h&)-C_4##*oS@n)KRdL^Fp1E%Dyks4V1u3z{FqQbJT5pL z4IEc3lOZ^FT(0X93$D=ifCWrF>(+frrM{D5i-hH0q`fi73At@NhV^ZLPJYHkUBp+| zvkhvOkU1K>^>$+8!rvsD$I{D`Q~6)b#R~Vai(}Mgz-uK(PTz9WT&9z-*oJcn_H*Y3thkWNQeP;VpZFNk=yeY% z9ouG+?*$U$<7R};TbS={eiHnPBHcprNsF##=$Uu|g{$Z9j=dyhe}$+vdh31Z887Y7P>w9Z zZb^T`#i#*3!YoKghhspBxg@$VQEmGh@C6DYA3mg!aSj`iUJymI zT53UitHuAkW4aFr7Ddl&lGSqYO^dtg=4mYx`=yIWyWIet=!<`BVgGE2bTxqjayILL zB7Gy`x?)PQM#0zJ)=e1kLQBfXFN`FD;0I18WTQF(c&##Qg^MMd7Ue60L%zt#2=cL! z)$xsIQkG61OF6%89Y+Iewr#I4Kg9yO3;^s~>!%lY_ zNkXXa;agD4r<6z2wtV;|)e*Zz+HytA(S3inZR4E4UeHEDkj_!Lnk76I(y&?`;!jOqFpmU!ATg=BF}lF?su45 z&>3t=xpT*v%6!ZHbS6o$B9irmjJ+~K24+*uMnckX4)VlUtyA^~-=bSTY$WmH)6Rol zURvv!mT6Qm;m${#&V6-jIYESz^U}x%Rpi@+5qgogd$ig(_fhNZFoq*ABaGcV6`?`7 zza`Hy6?dUR(L(UZJZn2!3-~rwNm&fiJw-4}nG{ASb2z6vzZ_+quUyItP!PnBZHlaE zn@wRH&^S<(w=gt{vL^MNATVH4%9{C(_~16UTc?2S4b&*irz{checWTuI%fE@^lq7 zL4@H#(Zix`L1YRtCL-;>3mOE$7PMBKuR<<)@bBye-a1mrlKyUj)ke^n{f^Jx+G<#oP!vLjCYU?Zk z{GH#X!udupyNoQ~L`jsKrHCSN8|D)$F0!+a+BPj5ddH_7G!rl}l}_ zH>Acr)^))~#>!6P*P=6lTsgPcR#H7k5Dn?$IY_fd5a+}L@5dp4M75T&kndzSl`UR=XJ_s!OxII z0tIZir+O76T16Gz)UZj#-TZ6UpZ%dxewzd5#BMDn@27sPc04D@RG#i|U1LDBPmP8S ztN}b^HU7yyK}D~zWV=!oq+0S+@Uptq-$M%XWT9jm5>h-yVe(!Y*OJsZL8Uml16~ke zYE_}5NI^swseIeZ+(&vcXU<+w95<|jtm~3a!b@hhij%tCoLFHvWp>K*m{jCWNo=Pw9 zue^K|V5!>6(B|T}24xpH_WdFVM^VXc*|ZVS-Va|G$r41eVNUPc`5?EJ9neIlBalo8x zS>AnFdU5~+S3K==)LsJp-$(qwf-M{0FOrl`5oBq`nkVHy&@=SM)o~sc*!GV%njpKT zxY)b#Qm18cG?Cn>(wZZPc)k(rhYT*tXuI(g5KU1zl~se6EZB@EwAozd-UtzRYIdH* zOZr-JTDxbAPxhnff?!MDFtm~ZzR)so1ZXJ)I)q;|39G9cv6YO#^! zs+13w{42^t(KoEMDYnaK@r@qT4;$AM3kwD%8S20C^kZbQ^G0FY?E zKt)va1c!`RT3yKf`pi6mp0(9)G%VKY`Lr7fpU@0Jni1cLMH#Nm%ub%|DuS7}3z#HY z1k$-^I4Lu%F2zIC>Yo4TfScXWB+0Qv4fAd$KSq%Yr zK=%gpmYbTjlD>RHJw{S5&v~F8xjih_XvXy!j%(hwqP^ABb!m2E!{xWsHVS&%{K01; zw)l#hzuw?twpg#I8^bv@Iq+fzwuTd$OI@3hz)L-fx!<1&BNq4{yNJP2ia}y@(2UdT zit8#~qwnlg&ckJ2%KkJ1oUFc(xL1Jt{pnOYq7?+DY(@!A}al(LK*O z(V&F^j4?Tp{EDAI-UEV16@q0F>Wqb^_D%rSCZ$x?SG$zBm$efM_|b}uovD;@p z>e_}3ZQF~X8yv*lTe>*dwT(Nh{Uz~Bz3P)PSWHiF(*Akf$uuRP}DK1kmePJJn{nej4}Lz z1ai^(-=>Q6Lunc&x9VkRXk!K7lDWt+zb|>Oza5fjj?hK!+8^oCOUbz>lKIuc=Ay`b z3Uu>>QPy8vo7tJ^dBE&WXTtlbL~E6c&MqyuGGb`~0-=*pNrVa|=jO`?tg=?w=S=SeV@30)$aJk&F3l@^S8d2= zBju}l!)Uu!153?a%MkN&Cxsi-{RxdXg4hs;-A@o1r|Kk)Y_^^~DXcBN_ddb=bg z7o1QOMwl>A9j478q}biQ2hNIoDLvgee2b>OvnzMF|vVj2EC z;Vb9nNAnB^)`cV54J{9l-W|$7N=yb#(cqx#HbeHdJ7u6NwYhWE>^D?=CmcD?I#T(& z8Fr*_zP`j#Gk)>GflQc&dH~FnkwzW_y+hGtYy`Fk8v?v?imHmIYJMWx^s)mt^i_#D&iqKIysA z%;3Kp+P!uXk(TMMN?2VKqQH69lHMDC8Yt!hfwKnTYD_#&>Ql zk`A9vdz!{F0K+_1qUPB=K`6}R)xFgiUJ#6*7O6r}rt@js>KEJ&PUW|>|J_h;Y9sQ{ z>Q;I|RlUuX22cY^erF7<{Who>?lY9kUFjmZD9eD_U&c41H#E0^!MhpVHJK6mo_I!U zWZW6e(_`|!SW ziH$}@rs$;2MT+{JsP5^xvwNsR&v&#b>W|-T+PR&8(RvANl9;o&oc%gY^PXo>zaEtW za1Lg3r>`#gn?0HA*R70QER3*Q=-Yt)qX4e39@NJPZ)^GtP;}R z&3ptp`4^|A-08VzPZzE{Ge&IXs$@|QCCsQ{tHNy#1eYC`H zH4cDkSzgA=fqPi`zt;s3OFD=k4ZRxxO6*JO0t{;pZ#5$9OO1Z1VaB&dWO9i{- zD^fRMDK-``-fBP?*-%j7ucI{~UG-Lx)Ge{UXSa}CWKTAr_NU!QtmK0CFFYIxSyI<| zdO&r4FVuE3XuRD2 z9mNK+c?@Z-O1O_q$!61p(v_t}CpFJXN75}fCc!~rLaV*1@aw{vkPLq`<2gyHC$zA= z30O81>8up_!0cX=A-JzUgZ7nrEkiV9{y*_Z0bku}rQ~#eFgP zi3bU#uhjulw$c=&5WNlD&@(dzs>Zxx@RjOgER}t50~DN#Ycp&_rZNpXih)@ttP8 zBS27qqNAWb9bv&hqzGgw%^Zc-ze_vBT$e=HLL+Mhno)H|0g<7EW=qxxKb4r+dGr& z$#E1}Dv~>b2#cz(PF60>d;4^;w{FQuwFzIpFJ7MfW~oKiMUR>Al*|R~NAtX;e8Dlr ziu=$dDuDL72JtGFhT(JYZtDMB;r9SiW1VMx!JU1QYTapYNV~LV2XED8Q-_a*#U7aT z&ICCCBFfDTP<)D(Rm^@62_|_*0CPAnjpRRSh;9Hl*6)Yb_U$K`ID!-aSRf5JO)TJf z|HX(}1Rm=#Wbbh@P&5$wNwu`iU%C7#64@}E_@9eThA&YM= zp~$KnZZ1#;G2|_v02hUZzUG9!u%bUpNUq*Tmz;K6)qq+a1y;2(P}Pz_amvDB8om7g z0H1m-6TT9jABe6oMZq|6nAQrmVHKNtX0Psas7vNv(&ne<0Y&kf4@iY$to>5n*PQp8 zvv(%yy2JYuNfs-?CSVv>b{#koE@kE4&$}KJ$EKO(C15O};l{tW;~X*HQy4SQ+c=ab}^o zbJ0v`0Gt>x!}+euXVbp`#z0KR04Dslwdp?ydKZgVCNC}TUkXBXkB>)Fxs5#bo7aXJ zLJeBn=Y7#Ib>%Vk%i|;vXc9<(hX-EupGhu&$p`nN`=T%)6IhWm?YhU*rOfwgO}4_k z88Fp~K}4(bC^sBvNI0z|-K0eI7Z@6UEar7=r2gk6Su8+-I44$;CLg1VuVacq*OeCO z>Io7YU`YSnc|e|@p4(VT_2B{VW)QuuV!qkf03KTYm3Mf?_Duv{Iq=L6Pxm__P><${ z@2>0y3wwCFX#*;o!3=)aMr6TM@ni4+iF z?6W1RVIA5T^Z#G0LgwkuJWHqtK&|Dq8c3!X!rkV;7X|hsBvu5nq1yh>o@3aRq!KUx zgBvc&NoSdixiC1`2L>fkX#H?*WzYYIO-RdKznDFlR(2mag8{X=(4tQbtCh_NSN?c$ z!Ywuni~&qOK#$uX&;A0x)+sF}^f=`5tPJdUC&FS)W{Q_`14JM$)^nK*_!US`O{9ku zK4ysD&he4J!y+J+aF>sHz4p8P<1t5Cp(yV%C zmv27~!n+p}yEjVRf~)5_j~%C>cIWax!jaUlS!o5e7{h^|_2@>aZ{Ja|0`o*al$znR zkl06!Bh*iJfad`-+4EQ#wNvVT)#{Qs8o%@4<_jR?x|~x)J~5_Pv;%os3+o1!{KvGk z1dkUEE0=y=D9T#?i6I^D4Rdf)?ASP|5TSzI`#+a6oXJi1*q;O#OH2+7dgfBH^MNr% zYg1l8dz%^;5%|Z{1;7`{(G%^25e&v_8Q$SFK&y69e5)&ONZV=SE{mAm=mKarJrp@k?CZkpUb8rUT~T2 zl?NYX{H&_X^ALqM|06nJvSgu%WK7tXQt;7T?M88C$^EP_tlDa=Q5dpXIMgE1P`dH_h zlV3T2E5?VTkpAE7y`IS%ji|KXYC8@0d4d_gg$6)2BM*u6T}B>~A4foz1gu1EdtbgM zt>uzNM(7E{CJXz=vj7wk2ENGu?3hW_szK(=ymz|~4KgK_Fo1|?~eGAH144$L?$ zeUUa(Qi+-_CFo&C%#VO_Z!EYV7-0c-uB;*HaVOP?3dhnr*n*3X1`hnkLlw}Ep|5!b zRilR1w?dtpV>)$tcdn^|5+%qswdedo^xgnC`V zX_dz`pe2wlyb|(icpaAYkTb8f=Ra#T7TaVQdK7h^lUPovV7Y3Z(q9IoyHkPFdF06+ z$##(Q#%;kxzG?X1xk%!Pg<>1!x&_M(Kps@kQ(1f=5ZnvsRmrT}d83cOQ%*8!EGfcQ z$qaJ0+Ng1&K-LlW$Am$~+&NB2mRJ^a<{k&t`zd+R%HM}p137~t$&}j#sxJRE(UZ^) zLd1{%>}CmcGYO8TdLF_3ROFlS&VkYaumV4Z`0(prgpZlGShQb=qffT+U6i_tt@U4c zMTY>Pd458Vc}3ab&M=l4my}x2ADGkS4j!|(m9Qs4K>;b-UvMOKUxFEtrQM58YZS)`v%5z`m6=n7~EhU4GS2~lQ4-gzyel{hZT@#M3Fn@0ox_F7==X*9(5H~=A?{f?b60{5PpFn=F{RkC1xT9^L zdbTb=BZ~`4^+DB5sr0L!=;49K=0p~a49F~!abymL5-FcEo2`plrli2=Lx?LNoB6FL64WEu7={P+g1lL8 zriw1fGKA<}p4XFlbU_K|n*){`alt|gH;@W^AqhN5k`r1*FR=7SXy?Bcz{+QWAK1}C zIr4MzVvsRAI3*W6b9+3qU$8U-WPKr3ej+lU*nvD_(cfA+t7fZYz2K?s5fjmTqX{41 z&Pu1q1Kg4@Lkf_pw8nYeQ%|VgJ+F3`V9`}UP^{#8@b>?qoQ4Bb4EWHsBv8{ zm_u?qos&ueeCp_29q_Th^*FLIV2=jP>P0=|^HfH(U+c>Gu(77cGw}~hOt0H}cVdAD zU~pMu@=W(T5y*?qiDQ9>0Jl5}K+$dqoVFLCjh819F4B!uPKaGR8&Y;))d}@QmqAn8 zD1!Rl2+KauGpDKsKYeb%(R+&4k&4Lk9#{xIZJ|15Ce8q8$FY`jQc2>Ra1Tk+Ie6zr zEzdd+sbLnWKTOi_d8e`6ijowsSR#+R!BemyIwoA6X}qX{w&kfVP>&oX)hinOOf6^u zt~ANBN*ferE2ylSg0MPIfy$2%mC`xZxtNn{Waw9uy(lpXfc$oY<{YD(N^Q0rX&A0D88*7LmW57F{Z z)hFMSRDW|6l^buN62SwWjMFX}SM1*#h&J6If4o2eRd7>HP*NUfbtKQg7nNfr&H)G4 zB-MxZeA8hX-{BRs8dkU`1IMp_o{e1p32e%+JdzH&KEy8!5QP%Ks~hX`GDapu@J1Xud@-o&y(!< zB}+dPYDHVn|IO>LE4b$ikn~-C9?bdLKbQDDB6sWE){t2eCzw!Y8JHJob4Msl^*Uaz zM^`&fioYYE=)JoQOi9i42FS}U!An~iRo6hTWLh$<-I#0hRZ|PHnc%e?by$l@SS!*b zg6Z{)Bc7pSJw@|;mMh7(gI-bL{hc%Nutb)IXmtep7&!m-;%KZKu~3!;qAAAyL$H#kO4WqK#|*XUYaj76#+$hJuJf3*z@^3tE6dZfw1c4M5z{E0VC~;pn3u zgqGA-;j{zFn(e#WmJKe->L$*B=0Oc!x2QXL0d~aogPt(rvpndCjVe(Gz@rfpdTLmr zrnDLGsDCdggZ*<(u+usojDNl3T$;(lFp2V~J!WKw3>~!M} zbTv{y_i2|MV%0lGBf}|B(fS!1S1~v2et6b|=Cltor~rNVUoHfuv2vn*Js2wbL1Ge; zA_S~94XKPY@x!4dZ&=HBI`P^LmHtfEb7bQnRb~*4PJqBVjfJ#=6gtfD>FNMqh^lC1TBH5(8)>k zdFVBS*-!Suv@9xBbr9d^g6yH`-a3`8KyL_!>+P4z={zIs5gTYn5N}kma|N<9fKp?h8QB zTuG2c?>$4TzO*6p>(Oh!c8>gijwrrHUWOXiiy+$_*m6_ZWy)?#IwK-m&o6yFgir zWfA9~!Bp@;p$i~?Tl~#nOj(U@avR>;XItxc9-V%IU+FX z0vurLUzN*l*UR`7a^w2X7*sul%9c5MT|=JNFaJD)4N`hHW{zlOUIdbLnBNw>Scbkf ztOkF9zGj(}ZIlHp=EP~o>Q2f4{G09~3p5hF2#rM#2mZb~9b$ZB22MRw6-H=l5jVX+ zK&KTgsQS(62?T}^0kWG#s7>gS)av#i-k}mKXbA4r@WuS(8`q61SaFw@kZeGyY zItR1{vX5?aa6+0T%O#d-9l-)mDM=U256?{`3*-$!XExNRinj=6N6-q`)D5?1O6)gk z@SUT6rkoR-T-ZTzYE0hJ^Sh&AspswhIpKN$(P19W3fZs#jP4q?{5kXxoff@mEn2We z+3@%Xq4er7uVV(A%sG`wRb!gSdH9HB&|mkVXH)QC9QWdlH_36$?(_bd8J5zpaA`IJ zBTh#0^yV=Si#JZ9hi;$gQ^mz~AgW6Dy-ny`U$?A8<{(~ByaCLeNQNATw2k6;t#gZ~ zh4z~8P*N=yr+Y+MH2;l3Z5EiVP>J8p9sHp#0ra|cqX(hpDP(UvLBfX1+k%m)=B*4Q zj(B*L+q9=7I3VYC6r>k~6>0Au0h40gg_~!ttcy1XCTaf|d*u=g`YEo;525VVAZM&v z>QgUML=Sz_z%uh!@0X>Uhy75$-Bx%N0_l|7C3V}Oz9-;c z&bZDiB=HX!W#9$R_DN3YSJsfHNQDlwa=3gIbb$(4iRvqb5!9;J4x+5o7P>gj?ss82 zc$ktAl4dsgpe$Or=yenr!KvY*ODWrsn-Tip-+F} z+&BQO1*V8EY@R&s_>zBSx-L9 zNDY^#-^{>&7DT++&k6c=kbAFrZL3tCV8Z2gKe=zG>G(^y`(e1uMg92$G*gf>Y_b&`1zx z&p}n&Q^?KDa-_nt88D|5`hsD`B8XnN(qsNHFho@^Be5K{s!5cWa-jKG3fkDb4mjs~ zbtL`v+d8Oq1ro7}D30`LA8vHB_4wcJpsBxcM;3KC-11k+Otg%L+g zv0IjJwL+T3rzDoSn~n`rzl6%l3rum|^eas8 zrds+&#l1G;k}8s@vR4o>hqKgKg=v!;V_#>^(b#(|3@SMP7l?S8B^`^tyIFJAe^jUF zrFZbbl#5{w78^caG4?2ZXnt|4*Zyf+7lEm%&$3`np3FJn)>Jj;jxgdCofEW}rt+u} zXgiQ#;nOZfK9)=okpFT^S zu3({e+9D3wL%U+HJpIcjARPGA4rzjAEddsMnA3TNEV#T3Ipy$zN^8E80ShjkHW0~A zXmpW(q_uCPo{63g$3B;V%1_D-&gD2a(-tT{{6?mK*lt1l#1wo`*t~xFR?f8F5y(p= zvb`rI)-(-+?fdS)hD$o)Sm??wfuiZpO;jD<=}f^#0Sv~!ARYfAr)+p~w>-1s4DL*8N!~N2UWbnp9wvX0b4dNT#;-ikWC;~XtQc~P9BbVvt?prOEJ2y z56=my#V{xM7%&fY?YYGAv!n0EIB5-zTmV|bob5iSBMRnU|HV6M&^E?{0h@5X1~$B0 zL9}n&0XxWVJJ%|jW=b>_@q%vd$8|n1djsc$Yg1eMIZ5?Ldr^5}3v%D!pM$XUbQu_( z_|C@L6_qniG%LJ*NfeAN(iFCkBICSm z8ygMT8h}-lS_OHDwFTwoR5XyN}13+U7>60OE&=aPBxlo}lTyID=LNtCr2GR0_q>QAt6?QkOy^_h1mC#-lb^)r zs1{&TJ+{7S7XMe(x%e~P{(tv~`B>-~Oz-mm9}dsH-6 z)W>TA-Q7>0VgXIMc+_~HE*3ej*i`s#NhfqNd!;WK zzU2p5#?uPnFRZTy%=uYfeE_eXA<$2$P+Y?5T4k?jzdS86?3_CU@=guKjwkF|MN~4(1Lpm zbSxUS@Qy}j&W6jWM-w2tT&BFfgOqE%l>36-!5Eqa^oDw~uepQUc|l4iwY`ot$_wK6 zP)%_g4Oyk#s1f{g+SUc=ZFk7QoDcXM$E0lbSypI zR!2J2R*q_XKB;`PCo)O97=G7yU)3;U-HNj24)}2c zPZ2cmvq9YvC^T+wMqbzUgKh?2n6M{`J}Ys>b9ZzWqdxCV2z@abKR=_DfuuUbb|s-U zz*=yWQdJH#MAxtzuVSv|QJOZ58e^hbJH(6psA*VsoG)FcC*NtTvBs6ey_tcIZ7=nveeC@R zr7DP}aNY$KhAko#Mw)TmMj_0Yo{20zj&(i&0tsrA_9<1xvP1Yjg=WF`%siBhw2FBu zH41D|nn`}JlKfa7_w_hLw(|gr&Le#xRFxxrtTaPK2GmF%CE)iw;gmg%U>fTNI__jN zl5<}29r{vPmb~Bp>c5D~u*B^ELP`XWLc{}q;JeFlW1S!O+mc7uUXV|^%UGw$PA4hk z&F-pl4B^KS{~wtN(NiA2E60mF4TJ;LbzlZ$Pw=`kG$U(x-6a2IMgXcNR&oMm`*5E^ zI6fnaigqp|f(Oxg18xalQ)Fv)FWbG5X;YJ^Vsaso(krh>G-aDUku&HK-G6~r^!_0t+0KWO%st% zyh2}$kk(SGAfjF|#yH@Gb=u@G*v!lY)^_lF?&+H=?aV)Xz&yK;_>S6OqQc%t{u0U0 zN@NiM=?{!zzH#NLDR41*3v*%wYWW5Mrk&_z>QW7g^hU;X-~-_7@H?uZ((PILmlyRj zz8wxkRuL7b^mNU?Z5V&Q(hG>Ct18M(tHcz%X?7J|@&WZfZqD%~?_uni;yI&Egp6s4SF-KJ1(Ce9=`0@!C9VOS^{us4ptT`N`zG;)73P_JFkafj z=lAxYiX?GV`jM~+b}uQuns@YU`ybo&H#W*;&A*_yMq-V(^3_UQlJ^B$%(KJ#EMTyA zz5dpi0XwwKi^ilKl?M*OQ~^IoX%m&byskZA`vt>FO_VRk3D+B(4)dcD<~XbQ7jeM%#6IEgI=%n!Pdb0 ztJ~0hJAnE*kx{4A^&Ro`IKJjWD-+;XhSA?CamJ^9y9bXPB9JkApQX=w-0)zz!x>8Z zwv;IP@@A2Q#mm7#aa!}%xO2Q&?w4h;>Ehnn$^&P+nJ zh09OPECzjWcL=KkyVU_!5Hrzinu5RA9~{LqTZw!JVz(ENZWLPo?B{aSXKLQSuzbuh zK;0;~1ie^?3@q~{i-7>Sx<-i5oE@PeKaX1*j>j-2)=?XE!$6`JjWx55E5^k60Q7_S zT2MJ{+-7pQ{ATK%OqjQ z6t^eHTXYnxES*MI*5^-Y${`K?9LoX|b8Caaof!QS8GO{P8Pz!NPYs2oesl9(xI(71 zbu0%+sW3i|Pw=P9MmSIr+vCVRxO(?b#20O!^BFx9?fFMiWs5wlkt{!70=w>7%JtK0 z+Db{fl8=*$?y~s$u(JjO_@W;g2{!QXeV0>yZ22iwE&nQRAb3VpV=bf};DSoU#B-M) z4TO9p@Giey;P$s~p4O->BRTtSmVS}POxvtb$x;9Y^eIK1-%7DRIRe$|mE5*BD0(HA z^8dRlLKv*lFV=}SzU~eBi5FLHRe3Su8rpyY=8>f}auW!456AG#HpFu~@b%$pykHf$ z!W6k|7WWV6Taw#wje4&n$Q%CVCIeWBg?6TZl;M&AxF*kaxP^p?!zTtT_wm6w^RmPf zz7`bGIl50dEZ?UUxXTnNd!YhP&6d0BoQrfd*>_S>QJq4rQNKU8EQiyH5@L)YfCU&^ z(j7QFvSpznL-7y59$?(@Z(x0*gVb}OaNxq--^|4taItVbd5?F0C9buHM-7(ar-B#T zFw~`{_KgN$)}VHOWMlXL8ys0015g;;0|Jaxz^Q7Zv6V9bWXf$6AJCwmy8IKjJQJKg zz=gC1YRhu`SUJXglqW1@>{f+S6--ICC?6|sYv)#TJjKeD!&tMXV20+^?gMBhW`Mjv zXL+Ay_j*^j!lOpWz7A9R^*-pYE&DNj{qEknQ6=*W@)+nG@q@*_?iEYY*zFXZSA)^? z4_#h(E>q#6fU)_ELdBDp={`iQ)c286Q#+Dyvlag@)SjiN!O+8ehvR@rGQ)PT!_Dxf zl(~u8NltHqxXSwWw_Va?rYI1&R%&7rAWL!N4?P4p36zqKj#0|6oG?ZXFQBq7HVZM z=**+&^Jf;)Kj`YXl1}=}ugAw?44^K=oy5e25(izg-*t^ugKfVl5jK@_04n1pjmBE< zBDwwAz-Qy=z)Cj~emj`{U38N_#r_Ie#|c-zLZH7t)-$p|7tf%aS;yEXKXEPZ;5;MyH!x% zUwpG;9+s1g&T4oacA9FwNzenW{Q~zuN%x2La6jk_?$vSaD`C#BRdkDjEX-TdGioW7 z0QqcNJz$UdLgpBL^2SW1X=fnR3L@DzV}~w&Q>u-&ck*y0;mjuAh!W#3icC=yPx;@w6`eV98irx^8h=-k#2BZp{z(4q|{#loO+CY zFzFp8+8o(60)L8PbgqVTE-H}4x0lJGJ^$PQ1WD5PM#G&9ODy2MjU&!DZstw3a1ZNs z?RTFdxzK~vmlgXkJE-gs{k+o|qeroIDUR-0#pBxm)t$D)|Z!!59(5i&OAi0^8Cv>M;FB&YIRe$6~}4vzSM zdf>~aEXfd=Jy1rl6rJxV3bA%w#TTfDtAmgUZyb3?FA0RlXn-y!!+r)0+lHt88D%7t z{g$pyUP&lEhybTz5lP`9>6x}wrs*EvUYlXYpk`g!UAKELkM`3Dol>^dr44?zwFmDj2G^q~B0( zVJZvbXsAIue7*g%)2DvHE*KrB7D7C>U27Bli<;N+I)CssOm1O6hA~r!nz>CYU+Xcz zUzG-K3*y_JLfNky<^#W97eDE;nNfH1N4vFC`x?7>>w0G)YMn!ZnAS^D6%d&@7gM%G zdgI1QT>6y?SG6E+iY{3x1u6-&Ysbs508>AkP-G-`H;T-9a(9ktZu_&0} zOD=j+$gBUCvTX%FUxcC|G0ans~VqAfgTmz#MG-~X{#LRu|>RuON;#8C1Q%+Gmi8u1RZFRo%yi0+sZ`g z08?RpwKCF`4^Sl^QX$Q|^4phc!KHTV4B2k3h;q5Zv|G`=6e)SPtcvtZGf>aLKX3R? zYM;3CTuR%a>J+Gq4QwpRM^m6B-DTLzcu3hMYct?BKvI8-bT2yjtaY4hT(NV!(Fy~a zGUZF<&Z`e)Mm)$JPKTnE5Vn|TpJ^M=$X4M;AsevVi5a88>#+!#SXMLJ#Ve_sy>0C@ zXg_F)8LKdoX?wxLC~mB498){c&)94;KYG^0%kEQK5caYKM%H5# z`T=s-fW79pRz2Ki=Kzucy~;b?qJ`^KZ$=oB50x(1IhfsP0@^A7X6EnSX9c;9P*PMavdszH#I=P+7b^@&I&}B zD^6FCe8a?#i&fn*pI<47+r7Dd8ZAs-{PmwbJf{Nd(il7nn!~8?{7jn!fUPX8LHKAh zS}I_l@yE3jmaM_T^|uR<+}Gf#N?ub20sA81r(p01FHgDC;#zBE3-Rm>sYF}l(y4Yc zv16eHCV~qRH@uNgrzd>5D7(kzzN{Wj0{q~{hiX?uP+2JLgg3}?=Wyxmn>>E5)QJ$( zN2CGQ36naYp-dIHFukM#M<%>wJVAx~62c#1uPs133}VljxK#$JwF0vB01fxr6+M>A zqtx31s4SVo@|2{jFf4PcUI%Ko2@f$`vlJ2g-2E;-<-9< zqwS>ujdkr6ybDxD{}gN8A?*rMJ6XY6BMCB92EjPD38h?TM1XcM_=p0K?DuQo5RC(Xh1^+9 zkL>a%N;tIyaXPNM7!_CIM;KCpvRh3+V0or;(=0Px5&{>a&GkEiFSeT%7g}ON{dQ@P z6X#BMCxZGZ8|G5rsR4%k2vLjLJ zpG<46-hmGGr#I|nHY}c{O!;D3CSyASi1Y?MK@fJl1ueT5;KuC-hQ&)e7=YyR{`{i_ zy=4;Ys=`Ye=+G=l@zLz)2OLT6e26Pdz;m1Ge8UUGZIx|&UC_RQvGs#-K<9HCds<=t@OMxG0?~uWI%|H@SqBah z|NXYQo3-W=oUO>!Jc3Uu$tliHh+b&{d9Y|{>`MnSwHz9>*(ojiz0R5-q7&kDjn;!T z=Ch-(qetGxTR*G8N+M&>EvPXXYHCttK|lkP>~|h3RT~lYC@0Z=QS^1tYt5f#1>+^d zs3{u@iT+fjcMTaczZH{z(>c3U;0GrNQqX-Af8#AvWWsI9*WWah+2`NQyfJGFCrqW^ z;iZXn5=~L(%=eGfnuwJ#KUc$t=`rSnU?SbXO?1w7gmIRDSYq~b_YqhRAG4SBW}2_f z$eMAnvZLP}6s$Gt6#gkr=`489qRt~&g`+_pZKT$xp`=jR(rvQZzu-tSWW_}z=>o$W zjFxp9Z{vD>SZp|G*;${?SV?i%Qvg$q+y4R+;K|?ML7(4}oOb}xjW@&h5~HRgP~Wp` zbOSu0GWyGGCFNZnmZV?+<81LzoYWuEg+42B7Cdj8sRy84!rLo(@BOvsL9@cqFfa|5 z>2(_-bzg`Jyg(h;Q9=J1O0%m6$<54A`IbZ6YA#gvtNwZnq#XOgic-h49FUnqLVk+O`P?X4JdYBi;r9|%S(RZ%SzX&Y{}t& z(By^B!ludvJyPDqv~7e;D8!MJ{xqXDc@~yIuEt0$S&@RX&go? z?yp5Pr&fpp!+0;vGyuoe56<0^)O@mOWT6yRpeVY6uNY{yS@6G*4?Z7={DhMb#{lP~ z-MmdsDU^3nW?2wr=R?M}8#?X^)R08rroR0LS1h!tq5AZXBkHLm22!?y<_v6Th#)Fc zzo^}}^577xuf_&02OsNxZRe$x4~{QM ztK=EYA5e3+>J-Buk?HPHM5zzqZ-}Iy)XX}RDwaupX$r^(pLFU042C&X*g8ji7-~H*5zBF0>A_Jg7`@bu_!BozcZ@v zIfrrLDX%tGRGQbUuKiNUi)5ibM!WAW_5HX&DiLaqbU**;E+*~SqSMlL3w#UUx^cHR zfSU7S=I=^>Mgt88VCvskm+$H^%Ue0#Q5Py28*jJwMtYpp3#GFAouZ7`VQ>h#8+;>V zbf2W-JE*&TRIFuqpBcCzeubJm@iA-P-}ga<#U8Zf%vnkrI<#H5AxvfWF;wCU{@t4) zsuF9sT$o$WYzwg&0kyn}AeGA&x$az?Yg&ch_#XgEO2YsC>HrDP>PN5NUV^w1ddV_B zx9FI*j2&p>qrQBU_l-7Qbt0p|Wanq|S=$l!_vtGl!kkD#)!%kNo*`i#bhhDlKB*Em zpcxHh>PJ~Y{o%Ht$dPCAPKhyaByDE@b1zMvZ!_m;(B+3TVexN$m_HAMmSkw{DW$Ms S&tJL__^tB%W98pWj^KYrw90@0 literal 0 HcmV?d00001 diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/spinner-background.png b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/spinner-background.png new file mode 100644 index 0000000000000000000000000000000000000000..d84eab2f15eb844ef0c37f3a0fbda07f7f4b4d16 GIT binary patch literal 46103 zcmY)WbyQUE_dkFhKtPl*5TrqnE&=IMlrHJ+?rs&a>;aU;DL|g@1G=^U{)`sb_1l_rs2+6ksvew}tJ|pTD^QAc{9I!@ zljm4fHpb{W`)@u^XnQ7NM<-@gs?uSr!oe`nCj@dO`qz4j;bEa$_xfH2F6PSKrR!t) zzoLX|Z;yh|9q9t!IE4*3Qx^6lu4T$62e=hUp>~Qln&VZalQ6;xr(2kB$KaO%;`NAV zb4;jQMp}}GFbkFYZ7a-FFk@7^K`i2Uh5ct0d*8*%QND^SQf3XK-R@ID<`wTd+SGyw zbIi zl*Skt8TT2Xn(DZ?xZPbRN?ge1*@J7}EUuJ+mE|h!{$v%jI6Rgi$W7Q>mjss-ni%=< z<7JL&yKC^tE9t(YVF{WZ3~1tB1Zv!_R)ZDq-M*D^XNJh>>G5&gdaM*4v;&c%ax%%n zOcDIM0a(G$SG<~*J*(KQt97&1jM2}t1ZLwcr{BJPyR@{#JIM?|I#P|_3dc5nu06-B zdF<;N|NG%GI2~5`Bw}ukD&kd5qvfas3+d0Eo{$b&P7_Eny@K7|&+nr2h*Yxa{w+G< zJEmXQp&CtscJ5zk58oOk?8(j2Jbo<8m>{SFjfg9bWu&DE^$`D5UONd3IM`Wv7|Ycm z-D3~0c*b9Mj3nho5mQWiOlYC9GCAuDyPNFv3)Dn?CXDT_N^@3>uj#!<|9qce8fQ`G zh*SB~@2Ws5&^wS6m6w~F``d=NtpU8@)tij;bp6H;|Add;KNlCLCJc8ChO)&}bf`W? zNBLgiIr}_(DC;XFrC6~*v3?pBZs0Vr;NCZi=Yxf`aO_W36Lrx4%$esYWG}00s7T>v zL~5m!pLZ~}RRpc??Wr&(_}ry}X2MM$CnO|%{L<`a_Cweb8qrc5Q!E@ylFyfV02lc* z+xN%3rS001&#CTyDNq2FZwqpW@LKgOz zu-Kfipx~3r8m|*Jb2WwsWHdBAFEE1nU%ZrvKsh${s)(&8xa>tbS>`UIvJ^I1+HhN> zO9(YS=CP`N46ofx)Dx5I4nf-TexZ|35k1=ab0a>b2a~JHdoZ1(XJWO9tW!l+SrYs%xcE=jE#*Y z$mjPmtqVdr`n91EO`B{XMga%if|O82-CC|&gQ6Syv&z9K6X(gP!Cw`FDr8)A)E95z zTS)*VFsvJTY#xq`h!gLU=D*Kl%@ zarbolb^#~RiSa|(UHm-G9=>Z&^*8mao6T4uO82Efz`R-zOz)RWvUWyK`L zy*d`ze2xy@}eJ7#Kna2V2}=z9)Djr18TATSYh%k?kuN4UQ%{1 znMb9z^}&Z%Pg+`9blwp`pHLXtA+;v)v0SY!{3X^K&7;pV`mMgm!$t35fw^< zbs7j&M}=zqKAu+tpX}l5&G~q>5}WUeTvwbw&l3qeL{>bj7udFX>BPT%BHg?2Jxy{a zR&iw2$}nnp^`t?xbfDX(v$J)oj`t_YBn6>BrUDB!NwvxWFOiltwZouymj^8*d4U@| zBMlBJFtA{Be%Qf=HI*K#6NV) zPtI}i;MAx2SgnJ?=t91Vg9WFz?OOt2@dBqoW-C1Xf|{jLZ=cG%@^KLcH`VR`E_Ua` zm(+n(8^uFYbA9^G`uGtV>V_nX!Q=Z+>^oY#I*dxgFXQRBHh-}9oicpaFVFL>4H7b@ zeJ;r{;@y?~_aJUoKl$$yOURD=!ac-2CaOjeiZkmwUcg+{Uy>Kv22#hLKM--SQQx%81uv z_9!W3p5Wd;8jxHzsM{~FLh$~dw_znRp1;R&j*4E8djPxcYsySM&X3Y;vug7YGA_wd zYrsSNajfR&g69Psp8Bk2SZ)x!ol&HVU06N2_`BvSCnt#-P0?Y+GjdmdL6LC8^+`TB z{UYo8{;hH6_y2P~j;9}=^j%Z-2u)deY{%q}4F9b28p#<6J)ha8il%QcE>rxvN?(_+ zUtngdsm@XUuN6TcCOE$P^n>5j`e-H&YK73*W@>rs#3$E@|AFkT-RX~1kp>MMjl#Xc zQ^Wt6ESQr#Apo;4a}KdE`9k|bZdTy9xG2%%S(#Q$LWVZHoj%R3?@zH$oN5`a-2lO} zx01tzjDqdA;;HKp%aPb>+BG{)TrCu;;z2YmaxbW3nc`$kgzw!%e{cey65E8(^S*Gr zlOFzsM~DDxpY>#Z^fZBOCCTY{6&)w@D!tvgPGFo;hsMWby5-tRt<91+oVoeiMHJKf zADBLw1&C3@evmHAhxxPRdAlbpEHk#5%j(K6VE)hCl#6E!@ZF<%3dF_k{$eykORekQ z;m5V#;lGQSy0?v*NKT)sL8;{7DX&Bbt(+zXrIV>u?`^hyt7}!Qfa4+S5CmO$2Bk?D zOl_Lt2an_z-bHQX;e6;>P9?kr$)cf(_|(5h2KWeg@+oj9=V68F0@(P}@lAO3V{{u7DpU($axWBF0}k$L#s zNl%P$sD!?7h?##s@mSH`#7Id$ID5cw+LW3tFh#T7mW+2&y>#E%M?HPJrsjK!$T5}E zMUXrZLHqw+Ca}85K$9{s7KpkuOW*tG&{#GX^y@+|L)Wmsc$Hpl*H@iGS8rVPjt@p@ zy6es_(!U$^PO?uIt<0(UHxG_)#Wzi@KYqA=6faN9_lNLua2WL={Yv?KDzMH`c;1Kh z-ulKy$N%0$K2XJ)bf@q7;}^kCyzwIboQ)!UKIZy^`<3{=Ke;jdwV#!vyMBD0ce2eP z6s{|_6&O`Ld^Y9z(;w3)wj8lT@C;>9fu5OJ#>SL$w(%J(#2L4`#Uc78;GCM={+8+g zxt4>+kn&-Kvp>-}m$08dMQBjwCVatOE-s0=1pt}gMh0?jO~tKX(3 zym9@@?GoTIuL8>a>)FihYFJ=(^Pk40`_GF;$ZemlXz zz3R(!uy7za)$HF{Rt_q0+Z1W!-txI|she`LwJ^#6H>+eGAY|8|*=}O2UH1M#UmySL z4@r)3$=7^(`SY)-zN4Q=?d{KO9(f4!bqwCJvy^n99r1rkF-^JMGD<6n()6DgjXj7nCb7zXP{AD-*YGVA1S5suD&x6Wwtz>nArI( zIPhs?dGQzb#^Lvt`;2^#Wp@v|>v`YiwOyLUN>nn6bPl7wdTcAq7VW8?J$nYSI>@RM zci#}p?XC$9)oF_yhce`Jz4q>@Zx!Klxz-C0(pgBl>q?|po0X|O|2vx?tscOo9w;5;{ zKgsW0sahbtV5gz>SMD3{3V99cK%6zWs@b1;|`>dh=IE!!>-Q zSbYdx^FMPs4t22gaGIN&li3fYq~kSC zSF~Mzkv!ya_FKd$-jBl@a>8+ z;}ngo;Xk)}WP@HFPA|<_UaId$>Hp`0_QTv!pyOdp>Sc=IdraNZpK&5Add35ZCnXd1(9Tj6t7lg zx{LIZ}m6b>Wv!nQE`C;91{VURWQk-sMYIVb{ zVGdx9`#T zVlq3iMQE@~Npp4jksBEoVI+%}h;h;yz}OZKCGE5wjZ_b-l3O0BmSAL|Qxg#~TZLsR zxKEx-azB2xfOUg`?S#bp3Qr;z0!=fvyxI?7a-YLDc^}aPfNV|Q!tOTQAZy1F<1qH|NzruHur5@2WfTyZ5%f=_% zMKrZ6b=XVH1$JEO3tcbr(OrL66V=G{%|1!wRPW#suXqtQGkG<=Q#40ukWR>VkNo@m zLf{*l{V=Ue&0mMVxj00Rnl!YBEFTvX-oU6EcudE~c+M^dB60Y&clGasiAkRE4cxqL z>rkY5T|T1Vqfr=v=;6eCS4q;s@rER|v3JED$J;DJj;1xE66Ht6txO;gH&7Y>3zC6& zhKQ9QYo>Gd4e`B}J_U)#vWD}Ua2wZh&H{V3a|?LUYLMa~MpU!mNeLWYDf?BTq2y$0 zalb&)P5bVN)KHtM&w=v*Q|rr1iktioI)jX8kVT&^E$M@BmIa`K1g#3p6fM#{ES~4a z~Yv1DB{%( zo&ev?&)3QQ(KANnkZx-IY|#_;kg6$nyg}k9PfuJg6ZMwU(tddJ_gwP*0aGV^{l#C>_c-4QuUq=|9L~=I z)8HuTaf){;o&R}jaP+%PZ~^vi_T`^~6j`V_Y0%d3Pq-Us+TUU{1}OxGM>nRa`mC&7 zMMVCPa?ZUr)OVNKxBWkJRAiBbywiH)uxy?KO++1c zA8z~aH;pIEP|Cov6{eH3py<^JFow^W*35#I-mE^b2OCq{_!syCaB+GWD7lWn4s6oJ zG6Bdh2fqB>uD1V3v}F$BrDDujMH5TpzrCM~CjXHR0-WT`eb;^5$@6%iBS28Sq)t!#b`L-{f6 zRF^~047rs!AUC5i!50>_v&9)E&XhfHYX&^ajk&Q538^;lPiX25w?C=;x+ zZcq!A!Hfkl$620_tK$H!PUdSd>+P4ddQWL=XNUZgcaIiQX3ptA>ESqE+U7)%@T#!n zcdjm<>V`$_;^^eEX7khc4&KVmt;h8%|A@aZqv?+Y*CNYKj9no@SpqOrhJKOKAM7GX ziI4M44#Ok~P$y|=zJpw9wD%rUQsRfc6cmP{tJxzpM$IxA+-idfSTPOy>kE`4bCYV$irExlZZ1ql7 z$JX(eJq+@~EkFKrjuiXOQ;NjjL2VLovafRABwM;xK=Yr}WU5+eZ?7aw?6tSo!j+8< zw>Z7;2Fcks825`6YQ?xEER=EK#=I8Am?^(?oAA9%5a*|6c0Qkw68;w-oF`mhU(CIchUZM6H`5B>MX1S#CygqMr-k=2r7jSS{Cf>iP{l?y z(|>9d9r4c}Sj|B?uKeieUPFBs4uoA^tuR#jv>3%}28h#~cWH;uVp2SeU>hB7HZ{}j zAQnE1tkJ+?e>6=Ui|b6--gqvid(p*>AHSy(9(34KOK@TB%KJ8lVs~R6WvrI~ss-sk zm_g=sPGVrAmpvEcA&s|4h%_LlYN8iDxw}&Oq8x{r23?5EYC@c&;Q8^39%Pbk0~`Jy zt9?= zG+pp9glI(f0DeV2S%7(LOiI@9oX3sgJY)0~ZL~LAQu5}vL5Bi~e_sUc#vN92;cd*NZempseloVGb<^OWsU>KL z>_SVHN4~^Z+-u-#J(lolXapJF^%urX0gwX+SxNR+7m?UL@EjY&t+P%4$c?tK{JS-~ z(w1t6hf8B3mIM{(B}ayPu z=WE7e1yn5ysyNlR5uAH84)*8Vw)7ufg^>ben3`~xEA{Y8$-pYX$>bfVkS41Z$rxPZ zF26)eC?tva(F_abzs&UDl@gdu_`cYtPJ~rkBp#kOTRq!{wr0kr^bIC(i#Z6BFG)geb!ET@@a_6`jBLaDB_opAvqXSk{mP;g+J6`R99r;NlHB+y9AEe*J4i zAI z#Haip%`Ii!LjN_jJt!RCxb!q7xh90(bJcP1TqwJt4xVxr9N2KP)N{40O|D$swNLXj z_@+uF#~#!MzaltacQZZY^>Da$LVWy^F~nWP&gRL`CzycePWnUjYEcPJSOgh$1%NE@KM zMPp+cHwg#H0^4Ny`~Y!um^+!MbMmEJnW(U^e7|Lwsmnc6^CzCp%*4eStbF7u24`=T zoYCZS&&3=E{*Ua&kNRaNv(#IW z0O}tL3oCUVi{c@RpIq?9#s=LpqvX}6z_%+1XKVyHgH&`H+RJVtH{%X}AY zjZ1GG@$RNk3yjj&}sG-hxH z@{9SN8GY{6Y;2=L!u;@y2Sjo_A5aw?amH^=xTogk%52H;c%46^mJiO(&IIK|1uY9aL+&PMkXN59vgA(ImR7Oc}8Jr&Ndo|}I zu;oB-Pj>gC^Z6;cK#r!$n~VEDmbm3a*X4u1U0gy8^9c^aM^_YiutVo`e#e((_%4iU z&qSV|Gu3Xvoc$0IyeNk8buLoEM3sQrfU5y=iO^lF;DRBB0?vY@zmWxM9{%+6lI5LS zZ!Z$&)~>58S=CN#$(v~Igns$W+<1R+ytt{=#=2H7Q0XG})e|sS_<(|}>DpLh*ipP5 z%WMALf$!~h1!_Mg&glyu61}{5hDiMF4ONgI3t+@-{mAH<+&So`ggvC?x$sVFiESOeQ>fqyIFS3KlgofTYYg)t+8gaHmR2lci&fI+`UV=3`43$=ynQL;PM5PCyPm>cg+wiWSI&+7=Elp+H<068P|` z{KZ0z?L*Ehtc=%VHNfNCMY+s%v9!_M?a$$xUxad$%P5zX1DgV_#A^fIZ-Q}RMcj1q zx4XRZt6%fA#T=sW@-iIeRzJqH3=G~1J*N@za?;QQ^*K>3xO&Smx$J3BnCZpkrK=z` zWbydt%8Eq@(O{4mPZYrdvO6mh7QLw~Ki#tW@HFmVRQ1j}50pKqp+GL-C>u#_Tra@4 z;;@|ac57nyug{?eNi(%j1E^m>J#G8n=*0}8=XG%#yFi1L^1>=iFkno=?}|7jT~(edDHg2`z<`G< z^))tR?~;{xwDQV=^|cz7pvc`18KTRGprV1gu3yx+c1agl`l`?kC7Ho?0T=cx-Y7wJ z3Ix*C9(ixU?O*b$vz&J&IN6REgeyuRg1P;1(GA8S(@NlP-u<(*G zB0)`vox>}}2n#S4J_Jf*vPdSH^r?x3YV++(qE|ihuU$`z6UH}a5@TVyEG7S7H{Z8_pFpyj6Z}vrwZ%ST6JO57yZs8-Zl4=UqZ& zejoAjnwRcdQ^ZE2^%0}pzY}4sD=rl;pu9`L1T^?$Qe7_DNX4hDtg5q%QZAC)_hS=JYL+h zjE57l&Lepta}iD;Pu$(tCpTcns6(`X3|)EJ_QHAL&tcqQM7b7YYeD4Qbr#45R-mUg zAk!zq_{r=_4p*P>#Bfkr;HX++vnpDXzx$|pVD(Pu%XKbbiK!32VRm-K5$k&=($Y>1 z_5PQa>pSU*{@>0H7Zj!om1?81FoR=Qpqh`I9P58EA8sGv;8Hp?obUy6pkMqFwe5{8 zage`v%k3XXQ2+NpNuMluR+iI~3oz_KTz!)Pq=xFuGpV7zP|`c$;+G*zQH}_#y|8gq;0(jie*@e|8Kyhc1oxDK-dpQS`gd8**&*~6hMUUPj^OObYdH~d>G zG~$^_3~{F16~h4-WJ@<+EiFCW{>cTC_tnXMG+NfV2izb4i)Rfq8)oElx1h`+jgMie zbskxGNZuf(1Zj&EDoz3VJQ`z5kt%lk&m_^(|97-TP0$V^oz0|X1}=L;uR~MBnJnQQ zuK=MVAd8&LkK!z7$$7yzwPrGq(?O9c$t7_)!ispaX;%68&doeZCmh&8cvIlewT@PGCjkeFiXEX7H=bw%87 z6MIrziglfBXoKdt6w;Wq&uxeeC*?5iMRw0R2?*Z>09aUGIFpn34%-~7sK-&V2>eTZ zb1WbM($0#x(I4p6Mr5E0x;s#(jmZBMLDY_5>3ccB;eK*j-a zh+FOG9~9K8!CL0gxjxnO6FnHazLTmZY!I;FZuXBwy(BvwGFh(h$RdWqYTCnOQYHI4 zlAG$$qzx6uWf?k3^kJqNC7NoGk0`YV3CLg`Mo4&idWnV>WjV-jwn(aloO*LNROswo znv?@>E@&zhJD4sGS?*eRL)dS7S6I58Xf-^P-^d^5GyBZQ0)e9Z-*v55tV{6ncDl~W z3v4&6Ys)yHcv`3)@~W(!pc{TFWygY3Wqsk0A|2E71f26pSQw>)5J*g(wl9YEj4d%F z!-&+NPf_oOFo6jPO>`%aNWS4C;M)z$_2J*pl=&^Ap}In+0jR}7Hl#xX{904x z88GF*&f-hS-<95FDARTeN;+&wh6gV&Zg4f#z3ak)f>&=0Ho{s`u2G=&eW}qVm!SV8 zrY3qwgp+)V0SOvHy$v(|Io~)LN48AOm7i0P`Gz}gnEmV1jA2ZY4<&|-%1Onr!NVN; z`36Cbk<&Z)F?_9kZ3}q4muwf>OeMTTF6w~6WWTr#Ek0j!%(sz^G_2EPg92O&KNAqO z4X#?fM=qDxcGWqmL150GZ24Oq5bGme_?@YrKgV18=2H+FT$6$WQs=jTA)nJB0+3^h zLb2kQkWUDv51-a>wNK-5e`u@5j@P=r;E6395U z3b=M$7QIGyMh;&;JD2^*gS-#51BrnGZ&y^m(%IiNp)>A9$xR-99>A@2Y~F=H#lgH` z;U>Zu88*c;M*Fpo6Kdt{!}i&bn(` zwoh+)`VWMqmRJNKCgj40)(q-gT8^|tQ<-irx($Hc<7q2QMoCZF%HKvq*TA`liK2}BJelDO>D ztjlHoSX>N1F7^#v5-e3^&Kuic=@^JT@iM#!xp)2E?flQT124d*6uEqfp;BiJ*~2%ytHBNNDTA zZWzO*6uNzEymff?9%)|U@c&;4l0rj61H>1)!UDBX@YVrzuoiN7r)1%u$j$xPSYntI zKh1=DdEHU9Su8W;O!>Z_06u(6c=#v}a%q}dX9MTA`FV8}P^kmXFh3B~hAxhMGgC6t zh`N+`6N};pnIpR!J2;2f!}J@9VKGs?EDYDVio@`iXhT0p6SUZp>?BhEWi&R?7R1Ty ztU-f;lNsXA5c;{xF%yW1HFvInm&?j@7g$iKE(Yyd&Lh@bX=rVIiB zp%a!dcBX55CHfQYQ3_2Vj~Z)e*G|h@p+!N?2N9pLLtEdS$fIAeCjF=k=FhR6Sy{CfS_zX&RJsMqER=}Ae_!xD%81U1`7 z=T|`z7k6fuuM<+NXz$T-DNz#YxaX_cT3Ja?9y8VvoESBn$IK;qDRR&Z(wrr#`QhC> z^7>A21|8Rg6XuBse+nG{QpLPPkJYSd{jCv{J!E0`3BxNNFqaBFxHN7!;gVB(64xI2 zVB((d{rFj9c9On`s))hq-l=3*{p3!LT32z>a%d+!>1a6a8IBk>G!ldzY|HGc&T?Uv zF4p?q9J_tO_^)!%*dNvsXZd7Hp7v+>4{zTZF{}vVN&CgNvB0NPs*G*_`riBV^K%D& z#OzZvd5Zr)+gxXu%-2^ir!OAJNV5|b#OR1F1UY_2a||XPB^z1Nn}eo`-my`vb$=fb zDTC^W^A2=GeA6mXxI2A*9c17Zs)df3;*UUuzFZo=93klZs=*3sn)+Dwb1)N3lHc$U zh7a4%6u&#}w zEOh>)wB`LY+wmT!S7*^8#|#nAkhJAt$P_WKb^OE|gWwBu{78~=p^C9>;MinZ&vJxx zZk#Lw@trtrh8QY77-t=OLrlg7WyGa5`G0u_MG`$X5h@gPB9o-Pz>~)=d>_g-|32$r z!3PN2I3L02bV<13uvCP4p+q@WOYUVN!BKEuhro=GOu zqe4Jg{t2Atif!Iu^O&jY-sWu}700RQ(Ip^tH?NSVaR=iX!f@E%gC^O9bmi ze(xh~*JS}{_!)!>67E}wzl-aJi!xX%bjiSggi~&mQ_Wm;Nq9(MO{j|>En>RlnhEb% z!UhFv!quNX9X;5o%(3pJh1BlnS~EIJ)$-5gf*=nc3|fx$(RW;R(Rd7*tk91TfuxwZ z$miLhzsvRn9g_f`4_5SgxmFA*5loAd^2ZAtprykO&JcJ$L^@}nkM$?wwWnyS6Mt1i z`W0sk2Ph3S3>OOl&Zj46MfwZ-D~;7I?XGTXm)E$E$#sR2I>$`Gzvw8xsjr|gwXLcB zSK={ZIKl5mo`ZUDY;a}Ir#I1kzWL|${W(p#9a#?RQ)Ov0Fbxg$W@Os=ImLWQ{KL-)1Q^88{WrWCSc~nWB35PLjLI^4DM>N`VG?H^iM= z$=$XRoxkq6E5pH&fa+Xwc@p_Q_XNRnRedp_T{*jZlZbUBSq}#rN)w(tB zk6HtI4m8ri(e0ny3-qka^~sFA{W&ob6&Yn@WDYEhbqJCzJfv z;u-i_|0djSkaYC*v&5I47ePU&`|2Dy-VnsEiV}FFP|_%B#Lu&9m4PSP(ON&1%xRTn zphVArOu!Fom{?>FGi_{Y;usLVW(-~{U6&9S-#sv)qs@NTeCQK|%}Z~d4s$*G3Cnih ztzY@^_VD1+d$MJYg51pxFB_!c#8CyibDJZM_nl&5V(V1dbPtNk zJez7r21;JQEp^pgpIl&xg_-gNU6!Zg#k4FNi=u92uD40lqZ0rds*C|mWn%}c@g+W? znEP>-0F2o&&!*6`>*&XC%l$a2+iaaY`+Z_caWU)1!_6iIDJiMcbs`Uy+JfmbQldE* zqUhH_4hRjlo{uu9Hzipw0s42aaN9jUtn}C1WgsJ`wEYwvL$u&C%qq=Hk%_6nksmm` zVVB+3MuUs}l{HDeie2C^DP;26bNDqnL>~LeT7S=8LY2R3As5sxZJl1AwNXW2d=*eH zup*mKD-s-d(?eHKl88w^$;jYW8;9a4KI*JVBg8Knl5%|A{*O$7+ zHlRnE4o$;=vZd1G-{|A2vUSV9oU&=hKk-XjY9J$QzZ~BdV-5p?6xhq`WOex)rzy7ePguSZe2i&L zfCtKwYKzY5B!?EEC6oMZHCaGMu$mvd+b~{5LP|<;-k(DuuEKJ`wwe;jb)Cui>x&2k zEh@U1!p^5s{`9{G$cIlL!TQgZPRB5qk(Ky%1pZv0V+ zk#$wa#uy4Mq+&aBD|A;d-HrmnMKOm-tOxDmU z?PFq~pQYyqTk^2`(bB(JB=H!pAj1`6egT2{?G)_GZ)D#u5)zd+?5u}_#B=;hxd0^1T>s}csYHxamSjyx@s;kwX~DG2v<%RUc-K&b21R-bk*EujT=o> zQN!xJsXt`Yivb0DD(VH)2_K8`1PG4Pk}dXf*Z(~jsQUNs-~JU>HpUU^Ey)|=wY4>H znSe8H+50GwSA|>m)GDh~F(CJZn-qeArNao-pO43b3KbnCTnpKN)+C2`b++}9WB!(( z_O);6Ix#rK4PEf(Pogd7(NIy_<&R2lRQu|e1L{PM)sshrfqJB4>x5u!qT^nHrqa`4 zX#w6^CPwwz^=_SY2#D|tqRphu?|XG+icqB9u~lv@6! zNZloZAf-=7mksI*6W0 z3_!s0DzWP5G?jy-euPapisMJAuCf`QDUs1AUYgB)I!o=Ln}!bd7JjS zbH@IPs&9#6&op=0W3NVM%L z@)w1i(cuenk&2oj6)>j{OMDHk5OO4!=*+xO-kGyPROX-d-J9;ZkYKm~`Gfy(UgYNI zQt^4xTBmzo8it7Ie(iyI{_h)SC3kazf12M}+=MHYqv>zI{C|%6&Qka0qy}!X2K%V9 zI`k`Bfv#TX&Ix$W) zZPD|2W!DX<9H@eVFV&w7?l?QCX4As+-;3ISKONpsuZtYc;G@{EM2il>PO< znt%|LN(zL$IgJ5leQKcIkXZOGu+Bt6)$oVu!p4krFO{%?e-r56e9^?v`h6S9iOcY$ zH=3S)evNTL5(kcZ8vs1=opjXU+a9Ei{^la*`6mK$EqMk0@6p%B4v_*=3#%g`^_YEB ztO7ZSK%_!J_F0Tt1?c$NBGmv35fYY07;@ie6t z1B-t4uUTk{=1_>$m12==ZB#@$9$%#?V}h-PG$;7}(zBH(18(%dH5}grA}@@_g-KR5lo_T z=o-HX_^8JXtZuYj472Qn4Xh@Uz%@T|eE}?CHo+7ycLr1~-7m4XI0ozEo&oRwb`d}O zIqc1YPbgE*!}Dg^+tC4*Hj5X49e-&nU?*@{;7#F1cynKlomvferl@Du6i-7~$kfuO zMyg7ur*#o^zn=lXuk_%xtE+2e2iI=f<}&gmPxvT1H3j~I65nwsQ6ege zXRxf+^|;$iHhX+`&Fu3(Foq0q5Ax$|Y+Cz^mfPrJB_+~O4tNcaRUrgPHdT7Oy^4*F z;%R-aUXQMdyBX@7ucfm=FLUmJj+op>%-}NI{bsV_a*;hS$jEg~TXyZzuzH2j-~!#e zeW$lBZ&sY;l#;B1(jf`I)WXNn2?37X$vwggj?JbD^ata1A;zLU*9#+hi)|+{o9g2m zdkDrXur&!-=|6(tlN(${IuNkFcKNW$e|1lPw{>)1-0(co$*YQtV~eDh{+t^P>rQ%2 zYEAp-46V%nPJUU%QF~QYa~4&dg6`Xw$C>_Dx9SU6wp?rQ)8zWmM_8bU@s+t3If6Rl zR@fR1fN8K7jhIpr{QAw#Mq6FN@T}n=ZTg_qQ@NLuz$4ksO-)~DQe;{^TsXaJ6QDlVW|qyehuvN0TJ z=KO$eT$K5jSk%oAhkDVq{^MDnNZ_7z)8qKHp9=YIG#fe7-jh6) zB>i^XX+NyP<_{a8n16L_Wt@+UPYlGl2A50qJXO!^gsj1(_W)dO1p@hx?eo}OS#-sl zQsjI#(Af=j{#?%x;jldbT5c)e!N$OYs}I}qwLlV+Ke%^3TO{2VtPnO=AYi=VM_n;A zCSi1EL=(Q-_QkW~OK!bP&u(_D0B60PP(iZM%QoOmYuzw}#>ep0zotA{LR0KyUVV$; ztyS#Hc+kH5#UvkrK!~%;JK9oY)QJDN%tj`E_b>I?74P2ZhlZdgWcL1-*oQ#ytirzR z6~~ra0YEZ())`>Za>#y7Gf?%N)S?qWe6~OV?%E?Mpf;eW6>~eNCc5Ju`^6p{UVXYQ z|41>L>r>;84lQlpOD;OO_~PO?*)G5Xr|{DnPKp&8-#PG^T|QXMc3@7n1cq31Yk1_i zdNU|R#rI4q*_L;OiiMJ*fAT-_xCf{LH$V)_caTcJUwPr`1NFXI+F4`1t5SV7^PSDcSH!Y565482wL`wq0Td zorE{08d-|OGd#M3{Qw8^E%Xij&AGsK=-01L@#R~{X7m_Z@3>M$kUk{)kc&{D6fY-B}B>ksS`D$@WtQk>>j8 z=AVYa5PSfH`e)($X7WjP`T=EMMe|BNjiSLm8*+w}*Z&X~h56r815==|_|xv;qc9vQ z2e4!CtoCEg8G8&D-|b11sV~@hlQ}T3Nq0Nwc4rsA3qT`vRh#7>PW0NTNYFWalbc|R z2&5YxH4%wFE!Qo`nUpFOK#@)GFi_fo+t_1QSe`ob1xYF-I)0*;n-z@B4??B6qhTz` zemlf#Mk}7L4V?JF9BgB!*b%fQHewgcOReBFL(|Dck+uyL5+m*`WM)9ffj(vY)RVF82qb){p|Ip{qVMX7->L1Eax6JsauDAG|Op5gY(N*%tae`{l>ZL7-w zP5521tev>3FH|cL7~+5l*%LHHRcesvlz)l115|!TY`B5vP@x}OIcZ21vH~e2T#MC z&isie^1bf-=?)cp>F1pieHjSs5wZdsveA=fOmb%V5SDA>BW7tFL z#FL4!>l(re zcCb{xbKvvpji?y?`nI2wi;K%xfzsEbi8K@{NNuQWd-VZTEZ7?xZyD{b>Cf~C*Z^rb zxNZnlP>>8ZN#8GXB)m<>UnsGC%g|m~`D~@#lQB)vX}W3^iT`a9x!c|jom*4p9`~%e z#eH#l?fegEeC%2;pm;P}2!s!YI?FWRoJOd6{5PDGRUC8EUVZIKVSD2>Ba@SBN9Q9_ zTPi>aPD5i{yk(?>UUCskl3!Xc)n*^KxJF602?|qFHHB=V_T6K_UbF+^)g-`n(&lN! zHQ%*4DP5XZ*UvH?9PG!^d(568hlDvHAdCl(;Y_gygj zjFTH_cp#$~{Fjm&p#iqbZ+cb-n>tJ8*FvWm%K)&@Du*31^7AQ;{X`sY8S;^Pa*bmE z6LZ+HCKv=wsw6y?Wuc& zoqHs_X4H5zxpi!2Tj&{kB@RC>4HroKzK;dAg+E&hYpyEm2**+bC=#88;>fK#^E|ig zDg5ClB^7aZ9*hM=V=?@&h#`g75MfuNHfKr-?72@TSSL{O6afDR0#Lajx^A*vFz3K0 zpuDjgw*?=M<@fNe&!?K;J_(8^*PRy<4LVw2Jh-A**@20Q1OF!@CHHq_w0S=q?E=N)f*jR z05xiVLgj=!KO`{A(+a`F{~)<h~CiU^F{ozt71G`#n5Op8{72o~+7osyq9P*FR0LkBB2BtVvrv@YJA~dl0!m3hqzg!w8Ug9O zOO1kni1c1V@0}1JA;}%S`{O?MFXWsvXJ*cxJ!`GKCT7!Fukk+~iNIhfaK)VCH?0UV zyP5m)A*X-xrga*%feptH0<{0b%?sA62c~~GL^(7^4Klp1lR+1u1~T^)&k&a|TkOth zCp`DN1HT^{lfPLcxz66f1#sr%j&5c-~xl;(kZHaQ6g$%C#(|UE4W_%j$7i496aYO ztILF^3%p!RSjJO~$N2Ve6R)NmnA;E@G226hgD*~xqwAGIa2#~RLY?B&5MK-!73#3; zM?F1cyQg(019S0Hg2WU_VJ6cHNTcs-yzqZ9}pR6)3@ie_x#z8<6i0>4&7>K{WFCai5|={-rE|v&bF!d%fZP zpSt}TS1{_GLv;_qysCMexJHP}lAiFUh%zDpDYusmMUTB@gh@X;a_(bBVMa3#V+Kgk z2S9jGcr5m^@HjJa&t!w6s7O>WAE-XeoP-ZQmDBN}3u%D*CXeqM*jz%u#8Am~4I5f! z=D}ZoL)_YOe`#^D+Ew2Yig|_jq?4l7K>cF{>={h0avBOB=e8Rhvqg2L8DceWf07%! za~WWzBAII-y3~p+8KRVZRe3U!-TTe6R6(;j4v|^QD>LK?A%hdEO{o(_u`8DW8)i@% zE4B(M7|1D%RDD(7ae?B+s&^6(AAo0c@RWK{DJKgG-{%zg@3{|7p?>jc-;;{ZWB|XT z)=#=(cJ>w%PA)z0F+(8nV_{NbV}%TOg`W`@I$e*DgxnF>$(23m0pFx(5|UFEACcO{%K}VT>ZPzK?L{3m`R)mO!lGiH!Am5#XGut*TjLYO z!h_Xae4>>;jX*W{ZpI9P>KDY^B2}sFhhc)MQ+Xd@pwW6!Q_F}p5+5PEH1exI&STJ& z4&||^*D?HqA94QIqY&R~UfsJ0=jMiu1}OtSt}E=sn-2W;fPcsYJEWE%@N+qu4mlGj@AfZ-g)=X3>lf}976f$Ls{WoId9dJf z|4DV+E#2h_OVz-!FLM)v79UFd3~%pSHOtn2Sjx zzkV^-abT|hdfQ=M8LRINQf$)h4BiQ5G9^6k%>`JMN+N`fL)2(iLvxkSHOxi26|xdfLk`Z7oM%0_}>8R`IBu z&h&9brpkHGQ6oc$3z0?OtyfsGO-p^c%UM}y)QD0m$6W=I zVHmiepn_ME_r5U7)P?WKd^Z$Cau$l#!Bq;1V0nX@vHU(&eS<3(8{Zc}h1wKcugm@K z+77CyAHnYXRuu=P5J=<5JI$ukQ#Axh}r^*TDB)L zX^rQq=5KR9_Q2l|UuXuYp0b#y84B-oY266X_-TLuzfYJ*%5&%ROid=HMN|AZsjq%w zeEiJt>1{?$Y?^h@2@bnBo#yKUrG95OC|a=!VhAn0Bg&nC;YClu4^!4|1Pexc;Huxv zeer0W5Cuaq@uDpVfNDb&+S)5m`RF3EVxDv09a80PfD*~z_CZ3`Bo7zl{@Gu!jv3VH z+Mit;^$a6{h*rH}lMtSeFZY)rs+if+zE&FcuXJiG4M!srQ~_5!9rc!-@!Qy2Htdpq zFJmCh7=_8{UQr9&Th{>-u?zzs(-_DpzOl6g^l$@rEPGi#raIf!NhTNj+5R%@3i0gh z16mzd&9jwwhZoUrK~PPjmUVM>F8jrQi_JR(I9BvV-#g4MTxPd9p;1n&-Kk4#=PWUIUJmv3{Z7$2|tG(j557Yl8G!(073?= z?b5l1DIAN#Z~t$%e2YF|I;p5=(h)Pdp}$PIB3L$GhuUkf>K!Xx_b&7-#ur$b5!8$N zFRl$>57GE?)IXghw`W))`0C4(X95CQk8V6Krnr#2vvtRp=fMxjG|G5+c;;g%cH(j5 z!EKcmT{?}Q%aChvpxm5y>aFoWF3&}0tg8jH@SS^Vi4Mrty3c&!Dyw+Qa{2llkg?0a zH6Y)I?Tp6X=3GX$m8?e#c znSuki9-x|mNNxnn`a~5Mg|N=3J8CW|)y~X#d|;rwqj0cagc+)f>@Y9X0zPB2v$G&= zbx>h;>VJO} zKDO|hDQd+*SX}5$#oI6cbwf!mEhBzGBJnm<|GfyftjF`Eqi?sb%w~ttsX58QcRAMD z)=ffihn8zvf-SQ*XVTkS+k4#jOj|oX`%i08Q)V$1pDG&45;;1G%FkRo^Fv@Ik} zLZ&PAw_m>c9p3h;hlBnOiEvtP)sE4}NbC-W1UkeCRs7?LC)&jm@IT(_DUNaw%QGcG4w3|Tw z57=fTrKMNdTW9)Z>gzY>Yy3+n$vj5xMY3qA*$+w2@Col*i6f282*Zbmm#xSkKUG%R zjO@A(wLP6~(f)_G;NCt@FROXy?RNgI)*x>i0+~o99C;jP5yw<|+vb^vq$U0vIBNC} z&0Ks{o1VW4{$FaqW$B!d@0G3h1%|SA#E;`JALt0FeOVF1dGV!ai2_|$edO1^z zi#L({o+2{%8ZC=~+9oE)c>r_eQiz!v$Mmh;=@bev%RN2=gGOWP41P#O$MVSbzS>lT zjmdv_Utv7`YN^RlF%>Ue8FIMDhB{W`vSM9^WJJeek)nTT3c=HOZ*wz3h9x3fP0G-F zRl&;IdUbcUX~lhbX@hiD6!uEDba|C8fgG3@Kpv?5Ri>@BIA-_eF?wFlKKLr`yB^Qj zANfNeL(1V}RXOc=co4fF<|Mf|;(TV62!&L@fqWG-FRGHPEJc3Z-jNZ`84bK?!!7YUEOhOroy`i zFZLq6TI&`Ud)=K=oi0N#K*wFVonpgPE}wYhp;La!eX7}C*xJW?-u~jh+qXg|m3D!& zaejW@W&^68p!HbRc!q;1#>JW@X}0)4oBOOmG4akgNY-5W@M|2AlMKfU@KV~Dp?T&q zuBI=x-LJ9+6tJpnca-*)UIEovW9ve@V`g;yk9YYMt|_I!lrE?!Q3FQMWVpKM%xir) z=w)3jd9FrbBAPi|=257)U22^hr_G+dU!r-PL0_dE1mdb=_aVDR*!kFd(%|2OQa~X7 z90upbE5=!-a;&)KQ%J`PP?KGJF#Lt-h@~XZRhk?Y-Rku}d67)Iyu68#N#)}l=5p25 z5s{7!XDN!N)l(sZo*7(Hv(sa!v~*8i(f(t7d|8xZarUEqZ8`$?-RP^$FxW72_J1y$RO7%=;U#}5& zl1Ak0>FFtXB#V`o*Tnt9QdYnR;0;fb5yS-Lpk74|iwxlYB9amo`Z`d}mYW$$n4vS;sKmgN43Gst4PF9V#|}<7vYZg9lxI9e46A#hBCUpwswxbzwYZRG&B}^GUfwuu#yKL%;8lR9Xmj>NQC1zW@ zCaXcA`c2jntTB=+`pJ8(XJXoY{eSEcqo_BRW4dDqCv+V5z7P(cG0Yu05Wf)>xE$KQ zx9vNxqmbTOp9rPK^Dd8b!^?BzZ$&(Lw=y+x7Ma{KcQx!c&!*S8lm(m|}f!BjP_P!E|R}H`wcS$-F(hocdVwg|Pa}W5|0gCoU$#A$av`B6p%fmfDk> zK~6H*E^F_vo=m31Z}6;yG%otD`(%BbouRM?6MU#F*0R_Mj%Y6afC3qA6`+(J@^&piqn;5k7 z*ANBsN0Q4u3cnQ5*}XD|K=>=jWGD(!5YHNBaf-!rqmA?i(Pk3m<)V+~=&xhwW5 z>`mU;mV%hEdJ}0d*!0lmvMqju1V>zb^&nJO#kolt0CF|)_PJV3Z){YtE{Av6g247; zLJ#PNh)GcR<})&%xFME!{N4-PNi?k8A^Dt{J?_L1MM!i~v?I^y$m#y8mLXJ!FY+BplqtgGte3T*XRHZ~!?9YwN^w@h)!87)D9= ziVRq`+M`p7*bXO0hzv!`9egnNc<1}+CjFkS^#okP$G&cVZAVEP>Oica-Y;`EYt^XX$K#sP ze%#hCvS5fMROZ>&HWF&#I2&wvOObn-=%jQQK^T%YJf3zq#>olB*7D`grpFx}i;Ill zsnzPAdO%b!;Y9T7KHgF;*>G^riqc`!QE1?*>r-)EJdG8NCY&$?=&CT)?{ds{o@dS# zb(c;rHJ_327t>XXJ?e!p{}4Rs_rdK8;29{eAMw!M9K+vQGI7HMsnM z?SEak&`YISx7=|ve%TV|N2it>;fI?tMB@}RDW;m-*3bWyNB&E zw{{W>#qMot#H>@9VR;v3>Hl+rk7o%B)p$cbPmG&O~aO@8cbDTm+sJ}GGjlwh`Yr;=4^6L^ALv38Ue}Z&#-SYWDa8!d${>ZwN(qVsXjQ3dN%FW6?*RKR0bQhcs$y zM$}8a-o#O$%G{P*V7o&c%>6ae-{FqzcyZ4j7fHO$V^KbqYvN#957fDBvNZX8f1k2+l}Ki`yJMjyh*`Dg1!keRJawBWOU0B>hYd%( z|4GYm8fB`cX7%3rDZX_sK~jt@%;n+X0a%pkkJ!eH6E0#}{n3<+fU0uFv*TYWatsy3 zqxSeWL~$T_yeVtTxzhHk!FEeRrFD5>G7{c8>0I5K9?Rp=Rd_UUb+?MJW^R2RpvnJv z9aUEH_3%(#w>--K1h?n-&?*AXLdZH@!z7re)e{X82>eFP&AoNY9!@{WYzQ65bHzcG z8N?0|%Z8c%Vr1Dr3Gi%@!}f5Hc3FkL`;= zp$V-i(c9F^=W;yDeuT=rIc^9=vdsL6!3}Xfr+s(ai4yDZ`UGB@k^u z&rG*Mt1CaDRk89OzQOh!BJdc^@=}ej7NoU^_OX~VszUtiiHZyHL+X8z8Y{3Q&FEn3 zO$Uf0=M}(c^qlh+x)iF_@k}?y?BR; zkcfyP)f6vYS8+6=RB=<3Rz>u|!p$LKbTz?Jug?UxS5Wc7yJ-^X{d;JQxIPn{M5$k> zY<70=K@sW&b$Ps9$NuO@|$U$y;W6;2OMOM$hZp zyyCn}pvB9T&_6^Ccn&}D%iA~_Z9Rt}PVLW4&a+Or4ol9pgAp4f52;!n>-IoI@| zf>h+8AIZxaX1(4&@<;#4QidhDOJgIK`3{>N+ZMe!s(mRng?}k!zeKWlkeF%@w7PVP zi^Xa@bT>L)?MN$#tW>d$GO4N3YI{BS2^3C_-dOqt8JUE(HtU!(pyrU4svdj}jd_MU zEV3GcELCJzEwsh{ zY^ZsSSB01@vsMa)mT)bu(MooMJhSw;jj4GVip(sgVoQ@f_5_P!Gpx9uM`&6 z&afRO0dg;rteK{`~U2AQ6ChXhMKc(PReR)CnY6JRntf0 zx;NzHD3Q+pc$Hf6syx&g;iq?c`{kLTKU@#0PFoR8n=b1t zShBs@S%zC=9zBXSFt^RWoJeEo+Q_o`bC*7&)1C4Vd1mqb<;p(G8R5@U#+Qx-UiHL{ z#PW|I0#zXoM4yQ?1@F>`%jBk)C(t<&@>)6a@jxxOwY_a=@hqLt;U{^LVAsRQ&DN!ffrYMSJP)-?*asK>Tn!Hb}R8 zw%K*#@N~8ir;C6668`i-L>G$`;;cKkdJTquSy(f9hV0mw|C^6&fgLXfaw4X26Pm|{ zpAtJ0q(>WvS`v`}E}$>cIZVu+-9S$M>IOT>6eSLt8irKw^z8?g&yaR1}OJ-`O8xNqbL<*(Z0$4H#W9%vnxKx1%=@H1>w zn^iV)&28KzEsvOO3vndmv1EsR)94cuz#d?BrklL?tVhCGSJ&1?eb=^c`hV4&HU(xL zwZ^rw@-(74zSlx8jY&38SfKWL*2ov9^~E9zg(lHL&<0UVRg0l!oKD{9XPLHsre~*Z z2drnW+`OD&?(Ul|uyfrdZ)eA~ZrQajy?sdZW?H4~4cV4}aYw1n_9HW$)s`N+Wp4x)G>T6fSP+rD84 z8&u&jUi5aiAL~O{+1qz&2iq&j+T%E!ln%HYQtj{hs1_X+zK)q~#HKmP5fdqVPR3G@ zL>D+K@31G z0D?O2O61m+5tH-Wo`g_SQ;(NJ%V%HIhbN^*WNGs-p1`*-KT7c)oo(?mV^Z1`9+?9^ zm&#R-S$k?_TZBsmnutx(_(4=F(Yd&_XS4^|blLUhl7~=!0=>#5ba@=DU{P^mrn39E;*Qzi4 z%th?f#<=Nu+-pV!{3(>rQ^|Pi>*>3UP~vJ1^ybZ->!BZedK!$RpoZ6f01d#M93qLB zD1?@*t!8q$M*1ZDiO& z;eADVJl56v2XZc6DGrhnN+g4KlKPG()fpm&b#&Qf!%%G*vH2>*!%`Rj-m-nv!=AXJ zM}VRm+sCWIkD1*YYIU*OlhbZ!%I9*({e1+KAlnrRpLAPWEFuDNLd4bi6pQl9mk0)= zU$|+=Wec_Lpfaudb@=86bkKf@FpBgg z+BnQ9pl6s%@8S2TDQ`2j+1qbH{04f60GuNdDD zT@|G%W^6V-NuTl{R>=2SPXcq9LrP@ege!_NS@48S~0)zQr%~_8{gqI&fYhw#Yr4EA9C|ox~y?*6c$c;!_Her zrD6Cy=v#G5ZFOrKo87sVYV1;DiOrEomCB^YBIMgba`+nqlkTCXDYe!p33KQk}Z& zXCEBjKboDgDJxsobMQ}poiT;*xAWHQe|CnbMU?xrsny!A%$<-1=ycHK|sJ2?2LakI#N%i%so`=eAC@^|r=ep`iI<}bgOzG&XOst+ZZmjMTjf;23M^=B*%fuKgU$8i z`I4iJ6wFs}Y|}8Sj&5S(h@PKW(%m*q*1-rvZ)YTP`1Q|fMxnM1;mn4(jc}5}N0OCe z*8)u^)+qaWdBd)L(|Rm^2Or-(L8GYP{jM1wgwVKL(%nDqVwYs)N&CunrW`-MmcNj)~)QdbNTx7!p$ zNIY$)H+jFHN<^!UW9whD;$AOI3>~_cF%cb-3EWm*j{?xhm4R$K18_!_!z6ySfRe=E ztcIN_Mw3cD3@t5Gy=rZBcU8GgS5&^W7uPO_Y(PGHsG3oQgHI|%mETsxU3cp!gE_FPjsVa@H$$qq` z`gi599FE_D;x~1-{1{}_DJ9O*Z568~bJO2b(ndyuadbIwj&$yXRQM|gC%HcTXX_y! z_7|L3L_zkgodt%3Cau(RK&)n@4OBjHZoR7RWraVFr;ElxU2SExW6S!X%{;AbS@Zj@ zKE$~}PiGg~e;9o(U4>ltEVX}Yhn0Hgy)LcCd&EU(=@Bo?xc)2|{d_LEfOnE;606L1NiJMl``*FcyovqY&cj2&4Q5OMrHxA{n!l~m zc}4PDH0oi?m+nkSZxg?8#5>S40CHwBnE7TvOZH`I9#_RS1TWM}IJzR+TL>j=GKh6K zr(>ItcHKR9?@!k69pnq8bSQbb**(*>{dc-FQA-rHGa}hCmkr&X_`YKx#0tNAuxCj+ zppFQLd-ZhsQ)w$1cb>USA*F%C&|(&`F85I}GS*k_YnbSKyO0g>oPQ36Ba?3N*%=Jf z2E-nBZ5Hv&&+HlJC7%r>!G#d&k7Gs=>Q&xhBs{hgS5{_b&R>Z(J%VZSjBuQL+vmZ{ zd3J6-O~TW@9(-U(s{VP!OQ`@N-HMHyi!8E{!2Fx zAMeVI<1^Uf#Lnl)Tfz{)GdT8c_I0y~A+vZjGR*%L#Va4K=8!K8zZ5p%n-gtS(KS{n zNb+z@5H&q!--*pa7gF}=;Wh7uNJ%8rRVgf$&3**Wm&Tr+1LRoD`MT|A0)Mi21SNU{ zY(P+i6QT#)XI>h%hoVh2T2UT97DJwm=^`=5#8U)}C7amx%3?&-zYa0gbyJ&+A-{EQ zq^)C2wR36@A1j?Lm@VRC`(CeA?KTB+;*k#OfO0&&#PhAE`=Ak(@eT6i$=d+s^PK)+ zR|VG>==n|*1*cIlh_beYQjk|g-^?xLi|rB1F4*@*p0rbU#XNXk0gbb#Uj*#RTT7yO z;X%OE6`$erLtWH+Ov4(AK=3AF@ZMr)|M*+cfA`=^;^8t?UO_Gm5@PiOVT33FY3^kF zc;H$a_>8__nDt5u?gf5X!)n_T#>o`E^E!X({7tVLT7#l>#;$MjH7ak{E7FM z0yj617(}k=qr(_t6Y=0!Z0;OKse(_25)Z-hm2)((zaFnDd9>}(B0S zU$&Ch!LRkbk*|pU`^beFHv<=^Pqd5)@*ceyU-dPk0G~i4d0g1#|Ho6S^X^& zko|Gr&~RBRIzET;jxG}MXsF>;z44m&%|~0P)9BOGjEodS8m;6Di)=IfB(kfa76&ps zIt-^KJV|u)$>bg8PM_eFKYskEX>K;M6~r1&nQZzvNq(X;A&A|?@Bi|jn+gUC7v2rp ztE0M&1I$SCQT$0MA>dYDdI`I>M!E#F>2YHKdQUQ9BfDt;?388Fj{#@;$a10iHj9~_ z_ZbmT4r5;Ox?Rn+FZ*GBCf|2_c%WyOzqyIITHKqTdSz;_&Rrl9u}JypRaV%W^Al&P zg8q93g*?0~_lrYD^U)=}n_PtC9{R(xPCfWqIac~~*WL^Z2W*sLeza3V>;hU8T0L=s z!j6r@FmoG8FrC@mfxHC}fQk38sfV7hKV#>fvXh^ytl+_O_eJA$@0|f|rtZFN zyY>>{9^06+fuO5-Pz7u|?2lflPhIRn&8SCpShOw9!=FjgUG4I)lSbZa{@is!%; zbskas#?$iaN#xf{L4L{EIE+rlHkL&I8ynx4rmOw2gy}wj`;(8w4BWm~%uW#?86<2l zM0I@Z73s=#A2RI|;Q9Fc=Ncr6`z-1qVTDZlanv8^*|r8zVXfl$flI%6Hx3gW!6KGe zDqHUgz)!KAmQ5s{vg9|z+w%&#mga@5cZd^u@pgsNWZ==XVLBrXLeNx@74LEp>5b{{ zc)xeMA4gHIEK7F8Aefs&e=Mtb?_t2MrxPdn6V~Hy*uJ0)XLB#Vr-$EqIBj>!>F#;; z;oO_wb6Ct4%Ye)2!+`fcvx0D7-ftzTcso!Pqh9GqOFK&FJ5=Sc!tDoTSqvK)WBYPx zBwyuRE6xCr?LrW)L}fJU>ih_qOPTQ+HP1y&3Ea@1Il!IUYQhW48sT1d`IcMCf zi{{m#*OVX@4%_HmDv>-yE-e^d{_37N$_7~0w!WpRSdR!Qi<}x}w<=<&(3;8var4(w z4*&RAd;)q^NgrpYUF*esssp)5(!ZdZv1>Qf17DM2BFmLHd{?IQil+iPNA~DOEydeG zHP~s@&YsPI&Vlo9s0;V>;p$kio-92 z0GvY~o}?T9c&{(heyls9z+n+exs9?}vi+pVd|t)gKRn*_>v?i*!{(OLi!8O{qlm!s0Jcr+Rjr75U-W+76-H zP z%BHOsJdqX=Ox8^`#oj1!r}k|`8Gh2xv)ZCZ$R~&0)l#%`C!ZLsG?p1ooHiua{pUJ* z@=>HxyyS;z-HZAwcfSxMYbcwFJ(Fs`F_b;}$-+WtJz6^Po~MKXq+|;fIAE!_=NwZt zQ`DP2{4ou~_axeH`X<9T`;&Ka?|memg$dQJ?tNjqbn^|-WH!_o<8!HM)269Lfa=sh za3wcXNx450JFirnSz7p#$ak`kQqB7z6Kv9KQjuBre$w@i6hSqV{&(?8U7Gc8l3)h- zbmkvF&^bF_n03AnU1&?_q|ByElBT|f6nMH6d2H(k7;m6Bxvr7`cZYI!!;vJ**~yM} zvl#I5k|MK&BVizY^M)P59L7cMW6=DMkE%~p>i4g=ygx%}^FNK(U;c5~C8JQ5x~&cf z7!?`h4jQ(PzgCfhceiJ`jRZTuI&aedF6~0Y5tXA3yLDaPi14Q#&RL_AE{-iiMirH`3w%y{`EX^whOFymhF=JkxfDxPW*%`R&ta@owh$u*{ItaYan!-W zra!~wY&g?p(EWz!Z5Q9c;+aOt-8x;XcJGB-QFUQNjU7wn0Fn(kKb+?K3UrP*wgtljNZDPlT)`Wme8UsAWqv(yF_{K1n_ZVY467w z^*?_#mzfk6FC}>o2;Y1a!~t6M0C1r!SVM2K>ER$kex3~y*Wz8W70I+bbuU*2ExotP zH@-=Jlr7+~@P$*@J9><@ipjm5+pLcnh!PsjX1o)P3wcj^K0?I@Cq32x6vOP}Mx$n- zLDVTVuBF?%g=+wz(P;~$Cx3$5yLF-nKyNa+`5|5}=@*VopMGr}axs%!_PWY^%L_=} zW@VwW@-85q7G@uAmM~r1>>kqM_2(m7EdB9^uD_LpV)s!zTkxx$jtSRMIBxIuV3vOU z35Z=E08;lX#Y&re)3}seXSM5)!9Lp6^+9|F@JufK`qfCxIQ z?nw4+VqfL)ZH2==MooXFY*Z&KyWH{R9+rZIFC*wZxBl$?B2B$qBH>mgfoMIc; zELB6vy=L3-Wj2=3fB_^i1EaoW6c@2ljaiX(h#0=q|HHMS=}#>S+oXhJ&LGW2jGTPh zbT0+VZrRoKLu{8bB|Vc?GT`-F>NlCj2S4U@l;Z_86}R z8+mW^us0B2f+>&m^gaJwX|wG|{tVa9B^sX0+vY>g5P+CXTc*j>Ewv3a*#&iPs4b`F z-L~ZKq4K|p)bjU4Z$IBW`Vv-dDd=_j6E=9g1{1f;0g82EZTmBu=0gH|j(<(sFt7^C zg$${TE)O>QE-YGk_t~*#uT4vzZh(9bFyi3s=hW0p$>U%AVw22c$vDaNsjKtH{4F)T z?%|AUK&-KsDDt(^ZGP;df$LB9?m#y{<<^W(cO+|kwtXh{nrq+m!3QuwJMCIxy+cEX zFrNqpdu~quG8AbeKh@BW-&2wcg%Fz2p~$&z+bPO`FNNcQ@S2cO~}{w z-%D3&Tu`gfX2|v;C!C-@tSndZ?m38}?vkeU^;v&>4lFIm=lHCR23fzuB_1RAg$2{i z){mPV-Y(OIW5#yes7hMgd);#>3PO>Vf*gP2lzx{otb>CHPE-VzvmwKw{lGG?pU1=%U z+AUgj5JI&_txkQgMsjMA4Qo;D@liUOBupS%@qtr~3qUU9cTG3Ps1%*=JaZ z{7E3%ioTMhdAfT7VIfGp^RH*JbO`5#KB_tC4O`5ohQ{17^r?Ayr?4#Zu$r=?wGMtH ztniK~y|tJ0Gsh(j3&Ce-#Cu|Mqshk7>Go2{ybCb4!hU`Xdrqo%4}k%`@W=_IYdd_9r#hq;V&wuzf zLDhxfn<}RJNykqTfree@fti+Mqr47j0w}^08#tGgvlLVG&uTzJOLe#|i@p^Rr)51}@w+-GBJIvLEAzHt!!C~6qU`GeC`yFB1>ga1}pz6`aE^z9TG-KRJo@EXa!WhQ<2}lW<;c**NcH`F^gXn ztrPH~aGv?`5X*P=i(?zmVZb?8tLrLoKhiBTI#{>m?g;yPUz0!znYYCxTgp=QFs1LW zNyQTOW*RgLwI)(^fDo4DgJ?*)-!4X39S>+KU|j_ck>6-k$>p(ccAjlykJz4Ne)+&w zPaTvO+GvXx#bx&tp@yY9fT`{^*$cpSzAanZ+S(eN4Zqi4)%bUrH$`c6C|42iNq54- zn0`L_^MUujRNg_)@TtDOzN8*2RU%I+Qa)zeAQLN+Mhi-W-!R58F=KEX$B35jviY^;esy>a! zv+I}59`-bVLp&yqS_fIH7IQBC?!~9$pP9K&_8tB{e05L29CvY#z5@Q%$*c9}Kww$^ z;GLyV9dz&>mJ$*`T4OpDwriJWcu{^!RzP5KJ_kpGki?Z><08uA6+mdhJp%G@{jcC2 z)9uLbe+=H34M^9?7_aU5d5#olAD*2rE}G-M!zEb&djYB>{^PdBWdwT~Q(nHrb3FRE z!~DXevv@RW7|GMqZ7CT02KcEj7y#zC_uoGyB_+Tz_KQWg^1(M7RtllI-0XE=eq&_e zqc)wCkF2wuVA=nVq6qXbGg|=V8I*h{I9Dv?)u4@csWUFx<~pNgWqgDBQBhIhQ442G zl7iV^yN8CJ8q$F#+h#pvtt*#=(HV~e&Kc-2(njOt)RSawXv1YVI5=b|D-J^`rC1)^ zqNJpx#+T+j`a3`WZQ^dGi}krA$fbiWK};Fm>d>;5-K`Wo)xX(3QR_fIh&nv^%m(TS z2n0gtFw!nH`^3B?(JAd=h6>rGIi|||`vUqaNvsga_P^T^8ip(5M{7|1q|lHMEp4m# zDORQ!Z!a$xA3pNQV^PxF$_A4CcTKriU%w312yQBDvzKF^*O^AgSB{y#U*PdK zMGNpE&~O|$NJ^Y{8yrQ)v$~_Y`+41@Hfr}dsQp-}F*S(U#+<8W*@a&|`-K^+l)7xo zj_+D>d%Sq9JRisns;D*`z($Ykl7lb_Hi*y)F$Ne0vZzex}5rRNt#5eJdZ{X>2B5 z^CG51U<>?nEDjt@-{WFi!3d*k%dg&Gp3uSVZs{oR-UoF?n}13k`6yXuD1(Y7SL{i? zpK<>aG&3-Zd7ZW?v=meSONj`ot&jdqSQPQgg5X;5`TG^J&l6HNfKPlnXEt&X&)x;f zNL(<=Ji|M8LT{^}Rt^Fqt^cCh`m+&lhU5b&L|Qs0Jv@MNlNGEBqE=J4<_c<9V4 z0cs%fVXJfobr_}54iRNo-@6y}Rx=5NW7mp3t3gsVa4w>Of`&gAd{ck7oOFYEH~Wf@ z<)|uXzO5A|SzBZvKQ!bKV7FZ9M^QP!SM|{JkbtB-xbd-Px6daH@CM%MpY9Z9$Z8OTPL z7g^`qKPd#KoVc=&j?=EgGJWAB5L;!nVb)r9Sntz|EBLHrP99&ipa$Nnd~Y>sUN9?O zL=WBJcGg9;i>{vVQW~y#QNFpAcL#J{ixxB)iThfDz3A2Hth)lxHL4IiXluRDlIjy) z0r1C%ZzGtt>GQP|F1o2qR6H=nbQKI`T_%C{wE%S$-8UL=pW_V_;xIp2;0P=mIO#_6&Q4BF8>7RR0_?#AnOJAQ{{U&Ih zfR82p|2kLcbX*Hw&ouj*3pjY4+#Qs(Ui`ww1#*G_V(lF8}KD{!O|t z*Tuu%V<{=F_(B&Iz_RRM0|)^Y>%}MSu!{zxEQ?S|4=gXfT8B`$@^P+TIB8s));;j{ zI6(dc?yj-x=L1iuk~@EB+LazW$@aatqiy6#3LV;8x~mZhfk-ICX^*0IR2i4rz^1{L z*~eCm0`0yBs_0|B!(Wqs2h%V&KzRdcP^xiFuQzP_7N&*ALmO0W^@FBRo;^nlolAJE+A>PU-0+X~yGzGz| zfW7PEYE5}cv+f@YUM-+M`>T1_x{J)oP65K_!-o{*JxeS-@bLsc_0n3g8f9L8_G<(I z22*(VWHyjfJ}zI=yZ?oPKoZ71zp zYrx3Nye4TXH0ajb*9Q~DFaG}-QwF2=cg>rFkg%YPQ{Fu=)uUC25m{GoQK!`6N6|l& zI9S^_T}Y=B6ml8FoWoibVHMY#K!M-Ce>X=ff6dIy03nmtsx3G45S+Zw^BD~N_WnL7 z6oHScvsU?bCB67g6!+c*tL_}d|4W~oH`xD|KAE)mByt7tI5n3+k$gVoj5w2WRs7la zk|*F}G$pfv%j)81Tl-k!N_Li?1KpiT<2ySyx7hkmo*;vg!lQ?c_{LBBwVhl4Sqhp9 z$>a>aVawiXG;@uryCPG~?(pK+_-GYwAje7n@R-~01Bz?+T5&$@aNs4+u#1&#eT!NG8Xn({lQSKbO-D&)6x zA&7vY-3sddpehr@`qBR>?90QU{=WF%2uY~yODIZ6lr6g=vPX6?vKK-m`;bufrKqeC zBKwjp#*%#}YZ&|3*D;ncGrv21zR&OZ{rBrHPafyq_g&6C=XK6`og-Sr4k6=Mdd*T5 zrA9UT&XOP)v}LV{qk_iY?`nwqcea2pAnNoDhIp$Z$JO*f0u*@Jlj4K;NYXiU!|xC{ z0B_12n(oP!nZ+{zT{A8<2h)q3JO{IjHw{F`q_!r>a;U@K=P8gtpwU-%IOlF|+DMk~ zcc4qhpQ#fADU)J!IK*{COQ{Xd}rq(RKoMH%u4w* zPx#ZIKLZ(3o!Qu%e-Pkex`^Cr=O;UFH-QfCfda6 z?EAwkF@skB`ajrNS!L?wJ&O7m{Po+X_rddl#(m5(()kQ$pn;q@*l_SOfGF$`zW@Jh zuZso-b+xt1_Tmx}*Ci;(pPltk;9@-<-Drik4j}+DA`KzF09%(Xxe%ipLAhEhFCsE~ zh~*Dq1jGiZ2E3O*YRd(=#x5@Tqj#=%Wo2bG(N$kEZv+PiTUkE}_YG5?NRs9K0TOb< z!PC@2Z|)yWQ0#Z>^PszP-g>OaTp7M@ro;bH{owFWAqB5B%uvs4UFh_`Jd8)-p`avn zE>7~==hc3|3F$SN#HgeU7sa2ctOg8kU%y^4FyIf7o8R@aw@0A>E|z5_`iL!=9hxk} z9_+m4j5|2X`~WafdWJqeFC@mW;vM8Z6%11IXo49PE`L8(p8|AYQ_r3%n2!OjuU8Id z(G4uD_}bs~j{wzG7rw0EQ&1ZHlOuR+&a?N*Z=}a@b_F1j0W?bTN(sOF22J3G?eVJ} zxIY8ytpe7&MX70qnZGp{G>vTGO36P}Ke2~u9|LQZtZ0iGh@L($qzog26#u@3w=%Ct zJ^hV&96=d!|4R7m*_t>R1A}XU(w_RUycG5RMVHrnR0i)q0T;ib#l&K;av%eNWy$tB zJJ;Sxk&z`&axKL5Cso$j^lZYG_24x8_YxdZWPb}cIiSSCBo-*Yw<>*FYo9~+R|{5$ z3ws+Myn1=e1YD#q;^D7Chv&u0Ytln$zzf3X&e!S>XEDRJu*6sQm25M9ZUxT_r%Z5O z{^y-T+2SzOyCFf~*QS+L*J;oAq@kLrpvK3m$erA@gKO#Yr{sWHd}?l3ljA&%--A(O z+6&NZoO{wYhoBk7Z8bTu4&t2;$|1+XXtH71z=aaFft%>{C?Y#8k!s<u?l?9^PLz#lrsWo%~;5YOAVm=U?0 zfJw#7pb(Tlv&G5jlR~reJH_CM1FA+D7zmUs_+{+{?%uSD_p#-x3x^7-|?_WY4snlF0%=RS)25hOKH{$YXuy-vn(gj8dSR3}Dft zZ#MvUhJ*Du)T)i20%~-ggHdC^#*XmRvFy?vE=(dEW$aB+VdTC#w`Ptl@m+`fz+?~~ zge4llZ{U_Jw>47g*r`G|C-~v6B2o>QNUkD4^@)h>d{pe;|3f+h%27=1b(r|Z2`^u) zWu2b#{Q>LU@wp>XfwRCoeBMth@Cq(CwcRSd;HWN*DhDPcr1OD#A1FOb5GOc1;{*Hi z=9%`y8I~cyvE;>7x@^-fJHM3S(=2dK@w{03W9HbBi)7c7}Duj*-4p;j?Q0;4g+LC zN}#1CmR6>J^mA!DnVhg;xPrh^&UXsPl3EO|Q$ny6Jz=p9AQl3Z>{q9LV*!gDu2EGk ziOgn$0_}8o%9wyMqW0iQun7^pLz2@gvcNaFS5mkZH`vi3**m^KTrZ~paqE~63Pk3y zZ!0TUj$YSIWF^ScLT&D~(*4Gq?k?64$}7aQ#UUseVB}d@a7z`;?PYsMaAUo=aYo+u z<@*)tydAoH}%d^7McgpH{8-2rUox0RH4YJ|-c{J%15OZK~&Pi{rb7ysOL z9eCFI^p}#vbFSHd+zO8>z`O7Uc+*e8#+}CnI9J0fgQ*FLIfSJpTLe<46x75uH#gtE z^Z6SGhW;b6uP?Y)B(pBqpHFmYX@1^0p9C`g$i#7$#;^6=rfp9Q%_?j$UHR(0(v76< zCBrS7|Lk!xtZF65Kfiam;QEgspsN~+>}`Y;!F*%%di#I|z!&kv_q@FU)JQo%MgKGf zfEIUxF1uExH`oFJLKJS53%bqu$s-Ky75z>-)+W3{>lrZVA;1eJ7K=_Ir3~e;J1mN$kG>>u69#G zn|B4nr4#6f(?&+Zg#F z6)@Lm%Sw!X3=tM52-XR7po?39zCe(oV*Y`7$)m52s(lWgTX%G#D5~Dmxb81&4U(Y` z4sMgjzKR+vG^(WBwbNVoWaYFb--(OwL7KIuF zEjb_1K=Ey+hqRCn^xQrUBq9SQ(Tn$tW-u}GR}(s))OzrXj+j~I z`lZU$)KsCQZXu+1iCQ=-9bJP(U;oV+sS(}6M;D;+QX8Sgb-ykoE~Vg0if5`i;qpQE zP?9h#`p^6EXJ4*!kvpY#oczDWKCTV(pRq4nv2Q8Lt2U~nG&tz;(S5k)GxDE66qzXA z%j+m=as!{@ZhpaXdHqH@&8N}u9Rxkyv5F1Msh>-SfJtY*Gj;=bLW*`9r7J}WV)yHw z3o5h2K~!eHMdpT1K&=~l5Z3j0l!1a3a1On+a-N}T03 z^-{Cb(kOO`luMknG5WoHWk`KARysYAA+=lxgyQMOHl%xGEnDi4NM<&#`jli7HU;y- zMDQ+FNU4EveewFLCGtmj+^oI^;1~!_wa-nZkVelc+Kj#KI<5iaJ%4~TkNYm13^cqF zY>c!%^7u+4fCPT}ho!b(S8*_M>QC=E-GVKm6uz=S1AUaIou9c>HedWYjym4<& zk-Qf0+D+g_H;xIp)n3UykfN^&p(AZ2P2odFvh;xW!(hQ?$(2v1ivNs+Bz!Nb|UBkapz0|nJt~_Q2S*CuHWGe;A zo-R>o4c~a;2wXC7J^%%dx^0$$-Ldk}vi$hQIZRpsLs`Uvf-njNUdD?4i!%Uel3!l; zGd0xIF5c243_4VlmnTH@3GtDlfO-IqFxQiQYf7Is)dhG$S)sB^_?vRivvTQlS%ko_&(fa+3l)(_)77*^tJM^eiXpT zH8eERZE+sbTIi}8k_+N6Y(k;{jMo1Z%b@vSJ>2RyX7?63)SqU}C>o&1(Q~kK!QKGd z|1EiNdfJ(V!o^TV{CUHkt_JU=Ic;j}`v6I%2mrhnFyW6qhSb1y=-5dvZ-txn%q%8Y zK|99iBypaf!nNykQ9C!t0-UPyo;!X?e8_tv5$EhC@DYTC{y8vKh3NiNwdmpImY{}& ziDZQz!laGM5Y(>>7yp1cOc`ia8qo^V9Zvyy`y!HGr0?qoT*Fb4l61|TMIeQ{_XOwz zUv46wL%sF?T$VEWfXtJ(uN5gib1%!recHcnx#&dL6;OTgo+~Y@mJTM!sdaS(48;bD)k4q zn!dB@dJ^{fHRrez9gVU|1Aiv`#fej;UM4p*TeMSr;{RT0u4&BihmRZSwMLMV*=ZiO z&U7n_oHDZx@A~sYcW>JEcSCSls(DYk^Pjb)ou#YKJMq2i8xNped(^#da9GA?2+uL| zh`#=`V#97=yQcKx!!68491po1f;hEP&kz%N>bHRvQcl7WzImKA5ka^v`MNY1;9(rm z_)u2()mki_I9wQ|H3ZPP4cU?$lCv?&ho@OJ8UzW2*Y9-z@>seeyl!{8`{^A%$U^%I zQRIZTy6)xYKwd+FS1O2P&dEyowd!kJ(^M{qEDY7< z9Da-isJkWqss(yXi7e$Bl?XSk3fb`ZBBgZKY!n6eFG1;(%?Ta6`O~>Of+S>t!1IyJ z^5x-iS!)VNe8P(hMk5pJXk}uO9OmLywiIm6UVx10agNqFo-E|(n`eChzl_EIeFd!D zT^2d9ORUe%lhk(b0p`B^MdYWsuu?qn_Y>Z;4HVq6msoM0{!AG<4uDXOd;%d~47br& zCdl_i5|{I@FHwda*#)-n=m5v9y+cq`YMIcT|<2aVPZH8@qW$uN9h+vDS&$|Mp4t z5A>~t2iE_@mroK~r|uT#d=6s223H%*0^ACgCmQ+12`&G2{&MAZ$)u5)7=)i0m^Esh zxGA~+_vO>*2xF*3d(Wunt?;o;#`d)l&600`;q)8alhdo5Z2j$b^EoZ%W(zmdHHP!*9`FE-RJw&H*;?J!p1+0yaYG z-%ym*(3i`=)5rS1IsZcB9zVwaVdgbqq*1)>oA3r|R7WQ<3Vp)lrimwnG0JtV#(+#f zLNw8Xn0#R`nt<=)G38FJ!KYJyb_5q{5T*v&5L&wv!+0yU56%xP6R_)|8lLt zKd$-{rCLXshe^RbS>$9dvH8jQuGcp=FR9YQ(q@_|`hmz5jgg3y{Slv!3oquANu%gPYR(guvZO?cB zfR_Q6LJ9&z_24HcPY$CkX(tB102mGWs4o3+re`JUW2wMz8Yu)|BEnfGlyG^;4R1E0>YP!|HWnEJ9(E%(!ZawfhSkDKyCk(;B^1` z*SS_1xKYncfnoQbGlBk1u)(N~7a*Q7g5>!Tplv*U|AhINFYe)^o7kn7H6PImQd0as zgLrTM`8|WnGED{5lh$K%mf^4Z8%z~rwzeKcPAwLLdYJAI^5>+D6yX;|GkqP0k0lNF z=AA->vI$NSb-#Xr*2&+KdO^Dt!yyoUVxUnpGGcyEI*dkp$fv`8h-S!-$8vJS+M3_a zteM!)&X!>~6Bxp4LX8I4K;*b}_(ha#$mTb~^Ga!Xd3gY9*KhDj7lGJEzuoCC-@_v0 zHppH0(8dWYyVqL<^kdcQ%*mN2GPZKx$%i%QxrmfY4_=T{49x$5CHKLoSqJ4B78CM| zAM0r{?Y2q`BVs#kl#evC^*;1)f&%EaDE7>4J@fSH5ulsC zUy|^(OB8Q?C!q4=G{>~>HuHtRL{2M)UgUxX=iW6CYnzV$t$uHu8wV8D+;y(n>T2UW zebCS)xWCZw21AiW{UpoFy&8XGr$CTk6V91YTpYt6Sm5|ydd*b+D67K&>2xOLfKsr= zYlWLO#YWxU&q*zlp9>z1-a1xzwH>d3y6%*MPxe9oFX_e<4gKl63UYlw=FrtQ68Og^ zl~NbzE+HgNL=3XKT}TFZf)s?O_zKTnC3l-XDkZXC4Ux7O`iazd++B zB&{2F=qY>KXd0gEwdV}OmrrE0aKSHYWMyQMyO*Tb4$0mvcB+#6TWo_hTdcaVf@ENwlH>#!WIscM6>`3s~=O z!`lwzu#;|ZVZ$brW)x&i`-?y?>#C`>t(8-FypD^CKtZ*jgL0o8C~ZN2p2aK+d<&WD0<86*65#Y1}2^Y1J3b6akmWgDlbr$A2lAAr-*77?L&?)MojIpP)Bww%N`G)>tu@Iwz%SL*>&CCjFEhT`w zq{2cWid3%4JSo#J4nZlb8wb*nP-*H6hjVSGUsBK8+;pZlsrSc@PDS#E^g(nOlDhI3 zqrX)$WXd}85!=J~xIWdT;dc77C2S*4Sax6QFOJX!_aY`jPD3<=$s()?X{gH_9Ll=N zOPLznd_LVW6$lXo`Sx{TEkd7QijN`#vU$pR_E>#o?vb1BdLZX zSgOZne#RmNbn~e{XYP>(Vo8#qU$k7K>t=I%%L2-ABG8?tU6|k5dBks+@%bVE!`wIk z>^JqHI!@P}{l*toN=O*i@XJ@B^^FNaB%N|ND($<2>SvaWLx~!nkGcMJ2Xwcg#vy&H zXZ0X=zH<9CiaF<&QQHFU*FZ3NIbnL*Tm@$z$-fVpJ(Uvd$~!e9CKkLcevAMBj*!?z zwlltFiKi;|oE=T88OhyU>aIA{M%|7eCAsOh#2EePe&`Dvw*&4$s97{mR}k;>TQ^g1 zbwz()RPh&>NM4FAFE1A@CG_@csoUI|awMFuY(C9`=*c+FG1iRWzF`H}(>~mWWX_P? z{C1eEMDe0xeKb9}+T5qtktRzr=s^WRCF|bL<0m556iUIPq3+fbawGihh1Wum`ZC>3 ze`E?Uv0p1Ds zCWqWgcs2to!QHXya{D1}&h@A}zhzwE<|TnigSqG((}|tC4Ssp$7GFiJpg^fZE{Rs8 zWK)fif`=n^Y0F$&YD%Z|?Sm@#9Xx6xH<>H(D8=^zrcRKq@w`FS{3vE>@-d_HUzXWZ z-_)3MsdqkJu+*N}rmFKzItXB_D26c)zixavuAqVInZS3}nf|F&;-QUr$1W!xcJ60c z6ZUA0GL@#tJqWT4oq_#*y&mSN##MXXxX=Fc{ZF~Lmc7tE$UTB2U#ItL^PXHIFU2fC zOT=u|#n}8fa#Qm+92Tghv+#_=C9T^EU3fGN_J>+fVp~eMY1drf- zb$Cl5+cx3F2F`;X3d9wEAv``)a&tVDzp%t0RaZO{Tg z$)G1EEC*NR?PuEp0qI{H5aylj3g=#MqoaG@)f+Ou}TFGRGW%qSSO6OFX zAw1T})rK_i&gF;n6piYph=pp~cpZa~I)QP9nNT!a1qSS1OB8YUPHqPx?}VB8f1$yY zABk;pO_-CGT`4BD45>icVPu9xV`|S;8FnsBk?;hNLnpXj1Tdn$wK4EXey4YfdOUNJ$9fBlSsico4!TjJ?LRg(=R$x<@$5H8k zNgSOC_gSX#llJbT>Y>8FQMDTn;pWw^Cf$jnJl%_#Z=B>g&5??HShM@!^F{O(u2g1e zp|Yp z!cRYxgkOfTrQjPpe$+*Bg+@kTC3ch!8ds2|IDeUB3I$`-y_H$EYS$m`^od4i??@B% zK9$9?kC(hjEE`t)s?H$9h&liIlt8aIaiJgX#WX;EP3>=89T~v^)JnxvYJ2#7j?(U} zq$2AYx$n&mA#dR%>f3ea?j|E;U*3O!Y0COa0c~(`nh{p@A)R3f05-)7|o?7M)F2=VXu4pmhl&WWRM zSyyu}QMo9Q|82u>D$gwn#PoOn;;wtSa-DySkaI6WkDqxF zKk@hk++C4M#l~_bhoU#0aof*+;Di0VoNft7mp29a^Tj@X)D6!Kxjf6f@HD_hw#TI% zJ01F-+`)yH6xNZFJ_(Xse1&wPXYvIlK!JbiDm^@n1gFy|;Ck*JFiCd5=Z! zj&)9nyeHur6wJyrYzp*cR`N^QKSNC(-hjWEMN&-HqmB3FL_+VF4 z#_W%g#4Z!qHtzKBq7Hr}MQ|`6;o*+q9bi zki#H;@9Aulmt>q})hv?z8=BapXwksRvRaVDm+nM9YGQl(k~=1D5XpyK8fYks5TAI@ zN;7BJzKV@L-+exSO_Qhwvn`J*#LS?6 zE_Ekh)~HB$;}^B+=Yg)2>@in;v9uaT`P*}e`%?3nA!op+7}1B8L5U;QIC!!>o8d;E zl>}$Ui&2Iy6=|*y$M?N;$E(hG0%)x;YNEqy>_E zF^KDXI{8pz7k9mztgDPbH7!*E&g@{!)Bo#L_BB5TdtCCvM!cGlhoOanRq(8)0)403 z+uOc^hs#|#Tc32M@-N~#hp-aqsvnUTU%!*5;UmUJ1@;ZGxcWdv9zuOSN1 zsD#xsn5%*w7I_Y-n09(9Cris~COSnfx59-xaYmIweJDoJ)Xsw=rwaUj<>On`tW;Ic z6@=>%9AioJ2UVz{z2c<#uYiLes2_G;2N2i&u1L{`)4InATUkuF%vR+>v8TR@!!%P+ z`RvtNz8sYlrb*e2ahKbGO?{p2JBWGZp-cOhy~fBI`&z(yj9Ief7$Zwdwh=bu*p=b0 zx}#W5L3|Hd7SpAip&gBHRZpWII9CP;9j?fOPoksSP+1iR{ZNQ+EIY29*zf7TAHfX< z|7LeL5tg64FD|7ggA8fthgza-8cLVey__JX#s5y~rOvT60sKE0fMEmXn956aXnAYV z`^7l|?V)`y7x;~_E?^#Hc<`ZRY6Cg2=_!*++j`qqvlF?`+(sPB(iWFH{o*lgxxE;h z)&}Uj@_UM`-R;H)6Fr93-Q8V(c$Oa!VtH-#zz4)e&MSeSLrPj&*Y3wr)3dsuOz4Bj zOHWTvZ*Ol@`sp$k|GyEX!@^iVmf^L5Zi|D#HX_cH_hgz z2h?sCk1GJ-1tqu`l-%fZadjsN8xzcOFS~nUl|p@Ot)BWWlO4qq3k&?wLJXpGG=J=f zEE`mxKpGANg5Rtp7S}LZYg!oZCx4I}&sz*bL@!kDMNBvJ(0CngrM@S@U@%@(BK3{y zD=Qrgdr1u)Ux>q%%%`NJu<%bJk$eO{pOo};_|Z<&jg=#ijo@n8VAHp7SCbz@7H~$h zM4W>b^qJN4v%S@DX(y^AI_NJd;l&3);(9VK>q!DH`^GPbd{ZRApa zw!_Q3rD)(`R|`ubLNZ_6>4OU1PVs8uEkRQgE*Wu?TZF0}@Km%(j)G9-cn!K`=Vt%m zqr_ymInbJ?mz^AI6|C6mcw(y#S-eJV>xKCz-=Se%QQ~Rq&)yhtf$BKo0C?fz&*v{4 zXD^$E_XUQc*2Os?q>k9%9sdx~%TLd(%tN3G$AT~Jv~iZ;NAJ`58jtuPx)!0A1|e=` zkemaD@oM24*}}~^@o>8C#qZ(MRwnZFV_K?Gq?<0t2a5zX&yqS)$W|qY%exvVG>9~H zI#T&szsyS_%25-XB;HBbl&Y~as((_cv?^AcRH~@Vk(KYAO_MBD|6UfNFg#18_jkvz zV%ECYplW)E4Fi-~xR~<1AM-3tVfkw4uV2p}m9k4hNUi(eegJ%rlR`yVd4e7Qp>`?T zaGZ&?!84P_BF3Qsd?1X9rcldO{lc|niTH2rp0pKpJ3j-G*6D9k z9_)SBej@u*11zqRL}A(<*hZAPcuqln6ST>$(_tx zhN35oufN}+skJ1^2o=P2?$F>BK1gQX3B4KHEiakw(iaGsTQsvo@G&=W@XwdLk1Y8{ zKz+a=pe9eLv>H3q$ZdI_n~1{XJE%w-^SB4I?4YpisvbcJz}v z(5%1Tkyx#!cQ)n?NQy+QSF84~FW*!-xKpLNNAw3)#a;r2sR^EUHxp1)VGpkczFPHf zIN=lR65|6Xu%iJ9{8}6--VWD%FLPbcRr=Ed&YMaD)uXd22G5Y+*V_(5a55=VEb914 zn^@_sgfgsVQcVwfmyR`du~NVrc7UN zX0(|QI{J|&PfIrYuAd6(bNnq)76~qh183)~HBnj6Pc3+L_BCioX|LvWU4p8e5#D1b z0umh3hfF5~dSCt$hs^OwU$2S0V~?*Yl46KB5ITm>4)Or;m?g*?(AZPb-FXj3VZ@SQ zs3v2*!TbS6?tW?iGTs^8!v4WS`T1mjie>-3m=kj!eLk*2n~Zf~{6B#)SR zQKE=$CW#ccx`zjE5xSS=guu(cX73BSa6y*NkWqXV00_r&M&f(QirMa#vX9U!>EvCX zxyY_9Lj}G(A3`Y5e5Rw?a{acWfi&a__u<%?6$gS)%yd203w*SN+t(`8S%ub|?lBRf zC7T+$)hLt_C)YR<{uob0Ol`ltzvEj+I7jUoQnb67W?D~J7yRdV2Nb^MsV02mn>@zf z_{!=AGGN^z^D9!U?8&z?*&iZ!aPofE_zku%Gq+Q$JLmZ(`xByz(;T4kA%P5CoW%c< z`cOS+TSfTp5&ShE&!9~HR=a6Vt9#0P`PFbJ?N;CSsnz^}H8RxtkO1UT1}d~_A7wi;WIxSZ>7focA;@Bi>`ac-dVMmTsumpE)`(NN260k>0+ zl)OFhtm>TRcoTt?c?Ig=c<8Aa5Ja1>5;=e%NivU)dHsL?G3T-CTVq2tKWYQXH!gD~ zi3RQHk&AxM%-qhdoSi7_A*-D0RmnAMcgNe07k8;d-M}k&bvcI4UB`$1nMNKbcLI@2 zD)9k|kz(_8D9C`xXU9!BPR*_`+kx@T`<{$w7K5kBbXW(me}V`D*xR>A_dspqwcGs@ z`!6HC1&5y<{+%Ll~!K&X>Z>}r#3o3(^UZY3fiuzT@>HxWv(-RcMgcpS%ePR&$wPaCxzUkiLq5p4F>*%i^g*S$o7n$> zydC4uZD*F%QZtrhu`?2{(-`SozoJyqRfD1EvkEt#N`~a4zNcuXp`I-aC3POXsB~=p z`O+um;64pLJ|K{z0q^UN;hoJf6UVbLlbY&3bE7-7mC&DIzZVzjI$Uo0^RRv2bR~o; zB121Kr7=GdSeEAelE3I3YBDw*evh3TBmdYHI1)fX_o=on{$Yl)-A0!cn2a!YMKZRG zN8dSxRWs~J#lstEzIau^% ztoB$1U`y|)bhWibn41!~BQiVQD=A5*J8h(hPy0tK)c$4NsRqo?*bRF>tY;Qc}@*Pj+=(F+q z;p?{%)JPx;yS0QLl#x;6!Lo)RQH79td|;MSgEGUG zA6*tK9ZtumMeh5iNiyEJaVot3^=4&)3e;heG2V*Uh4|Ck{d&hQ67QRr&7%H8Y5R){ zniI{?EBxj8lD;vik|GtTOA_2UNu#)^_tK%sV%aeQFG z<_fnFQa)VD?K%cCs?YcCUDCmABWFy@o^?_jC43$@wh!(g7>WBKzXQGP&o(T6&bI~3 z=U_AF!<=nPZ_7!w{WLF`-K<;OHw6bnHYisjDDW(!!s#~0#PMg9*3W__Ev*H}Kn26N z&F*ml9mwnAZl3Zljxa-+5e%(j8bMU#wE}<>YGP5YFU8HXavjAeJ#I14FB^WML)|`f zb+(2jd(KVUAg=~r6qdV*pJnp!m zBKALj8r>XIINv$$-?dh7&2N7J6`IkWGBs}S_jV-4C+!2Y)c=2# z*YfOMQ|eeKS({4<7!AE-eF;v@{8=z}H`25((E32bCHPffU>WxU`A`>&MI>BLpSJkt z*W4bC9Muw|N;o;Ck4zG#z3%~Lsp!F)KXG`v3i32+*?9cl0psa00}?)=y|2#~QIICb zN8t+h>BObvmceJyN2BNe<)?-@*!R2BW1(*l8C>YDvhOmZfSe6TCje92B)}C@nA-;y zVOd)QmSoz9oqk}Lv17`bdtw$n_^Nt=xbR9aG8b9M`1w4`A6DmP@>Y-6wI94}+&M2EpNOVFCMj5FACp#yR~k^7P`(-BlowVlyMxL^vsrove=j zMw6)AYErx`67b`*7Q7Nie|%}81PXJiiT1A8I`jAxvRo+`UWv3(Wr!NjKOTPB6jX5M+~NjLX0Y~jfVxquT|821J& zcC^~A)L;j+0f-{!$M$Nn}S?uW$Jx&9vzmdvN3 zC-QVAz7<}3ugWUpxi#ms$AF#x;OYv~@=v`|VqF#Jw{L;l_}ONpV`L>CnXQ$k)__>8 zhmaU;GPI{&t0${dt!lHYc9~;Wss_SYOi8&PWQrkI!*ddNY=*=22;5xS3qSJM(n0n+ zYQp07;!;zyGpIE`kv|tNE{Qh*^Sli{=eH`&yI)e`@3Nq^dV*HZj;pn#zMxt&ASokf zVhHk4{5^IeP(o>tuQIROF8HNM{yYci37b38r}t7c+v$CP9Tlk1Xfw*6 zFjumGS(C>UCARO`5XpOwSA9z#wf0#J&O06X_ve{v|`=Cq^uNTM=yb%x>@= zX|DWFp{Q&xj4137D`<%kXa&s0aorQUP^SC0&=v;KxC?JoTCakb>l6N zE@ynEtW&+QLJDnUi@G_tjykC-wPBqChAbcv!)5>0bAXERbJ7DA1z%FvS*8rbSk}|S z9@UFJGSpSkwbcaLFs0V@-x=l)=nF(+rI$IprL}ePU0&7W84t9xA=B$cq~fK^!A+Cf zXN(DUS%?tEvS@Qz(Oo@h49#4@0gTk7rY6YF%&vYJR zyTV)c1yO?}(Mnq`{jLLadpL(ZSjwBX!{sQb0IP+TJD*<4FArUP`KeTgqZm!IK}w55W%*i>E-vVqT_coBlLJ4r`Q~jaHFLO4gFVP z(8Xc9J9MOEJ0rV4FeIaBwX3@Jv(t1_q@t{++=II+9BfDYo8D=y<6xED&l^ZY@zc~J z2}!Dkxzb@R(X5rUO~Y0P?Luj>{Jg`UMEsEe5GA4%@<<@KO@U0T%-!p+cHK-p@ygPh z)Wro7mHFD&Iu8K*Xj%2I(oa(ynO;()3|@WR33Ot0}ITpkDH&ZlziQ zGj;I!MNY_j2tdwaAfG3CI0c&l9;+IVI$wf}2F60TGgN@ciJ}m+IP{E<#iKa7!nJ*+A$@PVT@7zk2v6%fIx4!(c8wV?Kr+!ll90N?y?@>Y4>vS ztIIwH;s)DN(0qQ6Fe*<*X_&$}*-+-nL;gb!Fk)5&$|y}@vgxjk`=af5$(V}TGFgw$ zL83*vwrT&OCw7*bUVZ*4BKs}!hr~C*q0PObC$t+RxAz*<_^5&%yMje@+y-B8KZIRn z`*Q3|Ykg@KKPDd!s_?C4bR)GJ*NM^HNHxxJMFDZ@`C2rNkDUbJc^{;nUgjVd0VLX}>gp9NW7rdAsUjs`5mu>Jd+nLg`@YLTnS4K{sM3*}4P zlraB(N^q)1>SWGpAf+AT|G>a!L6EE=goi%vKEEh`7)Mx%vp@b+8AnXNn2Oe65wf2 z@z@(qh{Qk_RxkPQGc?3&uEY(JIi%E&5qo=f2xwjSX`(%zgis)8(I#vZ5_ubn1b_>o= z%@=z*IDcup%9RPaT1VAG6EN2jHP8AM?lWIYi@`{f`!yj>DGv@*Y6n zWu~Zz?Me4CY#4idt)KY3{P=*VReyEt+80*IV6{h28gs zBb|uS8SSRIWyy1l$#PL9ltr;9<)IPs2`+QCMH)65p`Ui*FL*7onwzZ`>Iik;6Bwh2FFmvY0m`J&jZOqHltimjlW5Vq z#?*`{DR>yj}`^`>$&J$kweWV&mstzpPT>RY4ZQwJyp77hn+Nqq%6 zA=((>u6?Ko?QE8W>Z}qp8fp~;uT<+M^4?*%oK@DYi1|43RO)lgL8C^Gt0S&(kv75u z9T3=4ZzzDIsG(&l!cb;f)GtBgKwnq5nzzdSeO#DbS*TFf0)+rCif4kiF(*609)jvW z$YKQLS2$|>F$-$-!|vIPy`ezv{xhlY1)y6DMB=4l4?aCJI0 zVq2efx8yxeqjfUaWwP<31MspiKT@gk^2uX`N2MJp= zu8-~IONhU_o%vLmC+^xS!jLs!)vE6@!9P}6Nm)_m#ik7WzSdCh^3v^f>V4A~zlCx|9&~@$uP@+thrX+F$VS^VoMLGP?aD)U{IWfXbV!$x5o$ zp3!Q;wc&I@%Cv9Am~4i^!n9-FVYd2V+xUe1MAosEetgQ-<{LbLr1D!-gg$C@PPFg$ zgeWuw@<^DT(BeepnGAXDM2%|!=|u{OpJ8l1M$PVjG*aPBrAV_wK1;5ALs z=b})ckRwu1U2UD>gL$$*p)be2?O&^Ng) z)j=`DO+|uhai)@QrN1QR7;@8ww^>8r-WnFu)hWc;i5efE01<~JM0YqkG6pT4IMg3S z#%l&vCagdkR<-bXX*Xsl4buT*rAW|;T&bq)x{R9MqehuF8L_)T zLK~O5ZVS&C=nYas(*zeE2p6-yQvQaJH7d6MP?*aZbjZf%1E5Wy-eu5lLqtV%@qQ?@ zzNZg40N^w&lxOe{xU0A46E7qS1uJ(}b!M($ne4pyK_UE>t$bB0l3J%)t1n5!1FGy{L` zN!ceaW8J8l-&unPaj#mBC6te^HGX8o$E~WIe8q2Fpz3s{UUk60{b1v@m3jmxoE235 zS1gBTONVlEV0L*sZOy)jD8>ltoxJpWC5Sz=N_pPzJ3Bb-KE&GdbY*0HnoLAT{&)&^ z<8*309dGF|Y4x(-W!7x!wp#*{5Rm*G0_Fs;^p4}6ZW$W+U&R)_A#h^R@6W| zUCn9+cgK@~!pEJS8pWL3Ehs?;!Gyv^-8v>q6D53%78JRFGC;=MNp6Lc=!Md-GOy;C z6<0DR-!Wh>#ayH)+9_{IlIO+aLx!uMCsu_k1jfP=Eu_QT)(ne7F* zgS`1>$ZyfR!_0J=s#|7IS;+(S{5#f6->8OJ9S4lCApf z481sG1K&nJ|3HaO^H+UrvAKu^gv5y#m+|*9qXnGW7<#sY&NQ`TvoSi?2M6n78(Aq& zwDo;w8te>j)1th0k;%z?14KlHQrK&^OX;K?k5^Prrt?+I%*1#uGzjmLX=f{G|Bu{d+&i-l+3l;~j@LWSQ7+&St;a}pUE zbimExj6{j=D!hTi(6@<|DfRBf6BclL+L#aw-RUr6guVReDW9b%LGB()S2F9gPO-%M zDHYgQfB)0+S;y6mX2Ou0f!8StiXXav!Wl>YzsdfUgaV#R$oR)~LBab&sw0XoF={A6 zj&61iAkLX-xvVdV+sA#aGyr`6i${{)O3Kc!3;r^ZUKxiMW(=@eqdfx^8(OLxJX}7o zf?t@t{XROQHLB|2BSCvMqfJl`Q=G2g+t{|v)^%6Op#qFR4!qj&wl--Ae(rWk?2#di zWkuyViUEr~H%HvY-v-2Fc4ir{!|Nckq@wS~kMg}GS`2eOCL#^oKRzOroNRWwE;Yw<8xA)`&)F^67M*;%@S5S|?Aqma=18(oj@FYRD@>TK|6~?_|mg)=R$A~ut zJ@w^SM+m2VZ^ss0Lt;e?CrNz>zL5~%i=&PWg)>vDreh!7Ly9qGL3X^>hOLGIQb)t# zxXb1sy{AplhmEX7sV+{u6=zHf?v@!5OznPj!hCey;#U*1X0NTKp6fvYxAyCZ%J#co&aCA3@62U~1OVb-5g+U1vAM3UMKSM1}@5G4yNwvZ1KSJf}R>fxl)63ayKpY^!Nb|aDBhG&2 zVf`UHNoa;|l6&I3cZJO(1rQqRhe~rfGU4|akC_s6Dlp=D!?SHvi5fHibXsng zl_70lF<}uE>(^20rR#wMp^~D%WWjNQDPsrP!&<86ulnHRO<7ZP=(L+=Fgl{+kng6H zYOuPrY3G8JSMLoNk}1;jJ@5;vbgHfNO_NyCiX9Zh%8D>c9d_vsdFp11br*cR@y=n!L*k}KVkh= zta&M+RvRND2l*6lv2A0~_kWwGA%_7TH}L_LT!3luNc2Vl+)r8-NP^@>X$m(#4@xQb zBE3v?o`!bsk%EWKT4uvY%O6U|Zesb%9Oc;Nr+y6rdSg4JHc|CsvFf-->L-tH6C7{?4A zGw}NYsFR&oR7Gxa7!yurP2r+SfZLs--8K^SlAeYKi|fAXR~luFiLz0UQ^$=duJ(s0 ze5yCssw@&qlo6#S%0|QOl(=Dp0eZA=YHA4&a-lxrw&A%Uv&tCDyC_AD>d8_UvltH; zpJHqw4+ZxnDF%l?RGvbmAXuaM!%xEQC(>=l9-rPy4U`2s9Gxm7Yed6wt!gVc3#ZAk zKjouKj1~tRlKtpJE}2%08T!+nHk$mCW36l}n=a@)4dBno1_p<1HjW1q&%M}wZfett z(8pW1)4ODENDCv8N}j&hdXs(D61sIgCL4`-=J?(u{%ad3#$36><-gg~4UuAK2!Lzd z2n`)2dtER?;ydfG&Hhp~KRNUT*>g`G0m{1*G|IYwBy^>utZ>C12QhEFVzVk6EUoYl z)OLa18P)QG;ll9Y;(@J*=z?p9r@uaq#)xIU3#`IlB}&!PU}#z z@%E&c_bl0g9SPn6l)u;M8U^r>jVsfcov0tc0p6g>hksr<2u(CL3epvr#c@Z#T79@VhuUTe)M^I&&$oE5(H= zMHG_Oz|j{cWUB<~pfc`$$s6dj#t{6wdEE%Jr5cM-Iye1w#7)2<62SkYKtbWx9=*nI zm+dCfjkw{`azCnX<$A4cAoBPZfR+%k_xY>$pRxTl|%+T>epz;s?MYBzu!KfBY z@vR%$W!4to$0Fk;9vK}hg^X|G|dWT5m< zCxnUMK{)X)1b7>ezyR1;oX$2{k`TnC8VznhqYwI}9XSXJ8sW#7duo9p!UagvDB;Wu zF~%FOT8bNwoITx@WZChNEmC=>CdC``rqCXVz(2f~E+G7f7O|8}A=B1LJ?*Z|M6dh? zmL5uhk~YBJMfDEc?9x=YZAgV!e!4}qsHbYU^MlKW_tV_NR5hW3bTSQB%(Ul1Ab7e& zt*Sz+y7_B*m*y^5Er5Sau5nnK$0m2o3KKF$H%G>EfZgHz+)y@c<(9L3sj|1-5PT4` z=C>{seRyA!H|`A^Tes*Jm0eYP7o9^O|49cP+!W#Ae%;4C`uK%v7%aekP++oF%|GgF zf#GufX|TI|nj8$g=k=RYX8X9%_RA=`0@<1_ufBM{5j!#>HLMzHB?)zwr@1JxMj4G&F}MNQ2?;T#tcFpGDQktw`3^Ld z<(j^Zn@8rKdPLI6-Pim8{UC!R>6bfRN^1tngQO~g0BaXOv8 zhjx&JZgWQH+MGQrSthyp!02;r>TA&-vS; z!#|zPF(oPIkC>Y5-7k0qqgNL4o_@Cxw)Gtjzbk1CgBdT?iM2qyDd28#79`Hn7JS5D zUka<9B?`}qd2AN(%g+*&_zm<aU(~w|R4hADyvDELXVS?_|*ioc+)&c!bCng~IWl2%2g!NGkEz43=m# zjZpzoxPH7|n@8xEV9{I;XM_yTc@%pu3in@u4l^c|2ZA~>_Z(Qy!{UcB}ojY2`F#|@M zweg`mI#L7c#d%GNr2E&c5VZ7UOG4fnqXZr_`kJ!A3aa2*K9p%wfrNB4^9;fb1Drtk zAE`h_1A>>|Yz@rCmhTU5w}?&?0Ph)xzW{}C``F8whWU+o(<^{Qz9X0%TiEyDKR{YE z&{)qDL2TCpx);W~v)K0eS$yd0jvFZP@N4NJesMaYngW)fRhUn|2G6G8wX&ki)(wAA zt1)Nduu-Yq+yH}YLBNGTtFP;P&sH>RFFyuLVowP&**_Wm7(28~Bp`UX^pdon=3NT9 z&_xmM-)0)N*Rr4j3~&}5?yry}>>3-iuV6&6vMw5FYeAMIL@#}3D+TTA!^^9FcVnm` z>B6x!C1-~<<%1_fvU+3?)jk9TGWGU)e~|bgoXQU-$koaPG${9>sP>KHApw8*4u6^G zxYA7m8{~g4qK{u0IE}DVZnT``TKD;WiB`fFGyO#QSh^@NV^-yZJ5y0y<3lgMEZ%}_ zJ9CnGIOyP6%7<2D5b~;H=LA~GFTjHb0F-FK8P+$$ga{DuL9uhIgpYuG``C%^;qH{m z-ooQ!AFF`-3#_AzA)4apwJqP<3$L1Xq?eY(lIH${SV5U#8{5v%2wvA?Im`WknEb}4 z9*mKGM8d0(;9-$qZ`eoO?)=1Ud);b}r4l-nKR3Lhx>78Bw(q(t-w4-jZ5zQGO(``i zrc0Ale=ikme4Rqc!HeDs&0p!74LbG2w9UAN1+8xu zByokrs$gXKz9s>Y-?L%C%0HVJByGZzaQH$Ke$7%qrt{GlczT!HXW@i@$3q8DXAgf_ zbH?DSO_)|m_}{m{7s;)3;W<^W96OR?foI>3tvUUO}qYi9KgxV62yGq9Bs~cW=Ar)5YUed!amF1M%3+y%zV%tCfgPBfp!uo2bV<4E!F4anQ-Ye` ztCFvIrSQfC!$?N+dr1JFljwnG|0$0+(=57cgQrjU0!3&!`WE9~M;+op=rUxcM&7W> zxg(|}RZr(IrNh3+AZd|zx9K%i`d3?=pq9hU;BL*9*7q$P8nZ8ORtGW%>u1x)wb9;PR(_6 z13h241nVxta|!gpZ3PFy8E158&Z1qSprFaPxYq7v*y?O&w>J+|PdEp$0ME$D!2w5r=kG6A={qW~VhdBv(yFRXFO2 zk{*~+rVxN#AI6-V&RDn^RE43*oyA6|EdB{SP*gjSMS77y+fWPAB(3hMMVTi(pI04m zR(%r!C-Y-WkT!-U3l@yH&WGheff^u!cbQ5P-kEnTL;TzLz2IT?sN5(^ z|3#Bak!kS6Bul+$qDXBEP&Wow=&!E))_t(2$g(C3*fvZ6q#;;2RHk!bv7w>c+63yd zZJ{{i;vB%V&zp`M-zMO^5vKt#p|#Qy3(^jxzxgXA?@Y}SOGnd1l7D!`R@&V7o0Ut! z`*nk&^}UlwKkJ}N?mi&B@vfCZVLrp51$EdQX?iXRIcOR^`fN9T5ik}35rHn(RBpO- zL>1sGC>E+v_*`8foOQx7EwU8pV;+x}*P9;)P9U?eYkl7FQBlfoT6-#8Vaf|xC_Odle^KzfPksrUK%Bfh~epw0p) zTFJ94NlG~~V2iCT8Og5mggS75iD==JI+j=l^9}j{O7N7x-XG?;w3Cle2T+~3(<9FZ z@1&FGLwfWg;u8nZ%6wgl!qNEy0KtFSl}biim9$7jyWQ^lcOto`yLK;MZ%aGhJxyZ+ zf4S-ASj}t?XY5xW#Vo56P?-M0rFftHC3|69{q&eKwSC6V#=`@4=DLfxgdl<6_;g++ z=^rByZr3mDF&3vNqH0-6NBMCD!;t z-$h?1ZtY4``Y}aq^EhQrd5`5Ne`PJp$+g1*{u{nRlhJFZHU$;!?G^sQ7P^dj7Qfsk zGkiL1e-U_8s1~JRbLZ|&(=Yx9%j^^g6VA?i(R?#AdG0O3I|Q=mIJ0=zI5=c`_a;m( zL(~$*2=B;VsiQ8#;Y{T|^Y0-p=&4|z279tho*5md9{10rxIXoL{Ln3+j{6^&7&E6I z-;BJnS^S$ZhEknjBKU^f=kSg~b#-M88G_YJo*#;nW{tUMk-%9F!3y6ITD-&80#RDH zAe$?vKD5|jdE3K(7ubUO+}_rL>XAW+BABDozU@Ncgcj08geuHAIeY7xWDqPmWU3=a zU*}oC!K?c52lu-SUrc2I2LxAOxEG8zle?Ae%O46&yA4k&J zYGGs)R+^ISQpsn;!kW4dZL%fAcVh2GnP$1Av9y}Z7fczgELO2vBE&5;0jY!;N8Gq4 z1&iY4ULv(is_oFhFNY(lbKo{a2LauH;sxUHRm3N)tHW1s^w;6z-gJdB&*0N(O0ejb zP??b^-=-ELsZQ|c+A`td*+UOzb*8TWE}u0Rc%&Kc-uHc3^p1=3A~CjZS-;hrcb$e~ zFJC;Q|NPpczm}#?@U51cm?k;C`)*+PN!dr+X4~EsoDOW_47k?QRh&6?^M7JlXik?Y zwPRt<$F5|?#%_a^?6q{htW=FfmlVXNMcL1r_7?fT$Tz88o zAt9GhMDg5o#P~l(RL_<&Q;1np_74g9z-yMo%_hhpL#w_iwMdw6hrCR4WA7w3fx|Xt ztq8#ZaH8Eb4kX0{!Aib(-S6JOR$iyBqOnW`CiYBAStAUCL z%ZK#W%Ar_d68Nb~P^??2deK$5FAI7TL|a#%n?-5BM?oqS%e&?bzbJk8ZQ83I zE$2fwJkD1T7s}Nbqe@*qHI6CeTzxDy36vdD8!e(&csyv#*(M%6h*=lp;E)vCQNpP| z&2g%x2YKd4M^kYYLnare>5+bqOw3x$VI_!ZQYmrdEEH`o5Q(I;A@>XFrSQ*D5N}G= zL6IbZh$#}uWCd4>di{L4n^K(v67$4n9l+qvjx3zRkG^M`=*o&_uu$nReR{4lH)H&p z2ii;b30K^0{q1+oqqswVCd&T4;hjAQyHR|ldBVkdJgY8W<1BgFzY=4lDlyl~?mvku zi0Krbyh2<--8ymPrv`D3vSg+N&OcuUGM8Fm`xMMS#=PuGYwkiV_w`=W8vu6QLA^`K z))FNfV)zyF#K7-zHV{9psEC2hT{gUF+Wlj+5u0bzfARhz4{|*>1OoY>&43e#p7h#9 zC$>H%q*Yc`QZo=wVD}sDB zWNlYnCBNnX@`Rvu_}ByQix1+Wli0viY-~!8LeTAGZ+l*A2^?RQNu_Qvx#-qe8>WT? z{U-x^Eka%g)EuYX@K@niCl8XZwJdGC5LH|r)ZmEW8HFKzAADKU<@7C63jR>#N43Yo z9}!!|xD8J;e)`^BSYQB;=tAxTJc_ru79b;+@=Ao7PB)7654=A=%bUPw*U%0L(M5AW zAMz@;T#=(@QfnuZ$isrYFYG}Ve~bI%4QQ+P;QZ~4wM}W7d4K+MIwlz%smi<(1>;P` zm6ctxguj3-iUPeQ&qO#)d`Il)v6kN3L%-umi|6}bVwi6RDoL){y16p+z^v1d#g*!8s;{+URfGrk`0uyhHcUqW^M|_3^pptlKO^m6n$TIK z6Cf&!Q23h0BvZc`z>UtU{sJdwN1UW(_*TnPL_>+Vg4*Hg5NLYem8R*4a|tKqR>>gR zEil^);z-54MRl^pcXdV9&s1d&vUH;nRsRi|AI7^csw$C2+0f%qUIkUT9!qB*-!|^q z)&8Qw_*}7`EzX!ol9GP()b$P9_aVJxJ8u5@lnM51^S2!io$f8#XZzC}(LL^ijn+qh z0o(=_QlVRq_Ln5Z&D5Avim~PP;Z@{gTdLRh`8+D36*4V%O)`b-bD0!EsjRiP)e^s% z;R>6tg2FXyD%m{J6=gXRr+|U7B)4KzFmH}Kn~E+e0#(T3gK~K^4%!(!0YL;2`x}_9 z1z+#Jvi#wn{Rb9nLc&iza5CN+#sgkV&D|X9QPqD2ZA2-@@vA)a62Wg5HKe)h>*6}Y zNzKo=-0;7~{d_*aF_`E3lwYvyTI)vE#z!R0lR8R2yi-%+#Jkw+_o8Uu>SDFmL>)9n ze?La>)vh$hiL0qPVn~;m-EWj(Ao|IIkA*W_$v=<8K?C+Zjskfe z{RY{-^EfS^UbGZJS^ zhhn{9%+uAYKg(5mtIwa3s!AR|6TaR)hEwDBY>c*8nwlaCeD-`?ZU~QQ>$Etm9Lw3{ zf0h4csqj5xsTu%ReeAur1N>i9{y_Nj6pA6ZSHDbioRrvYSP1J*_$F?8iDpgp>)oZ) zK1S&deb>R6iSD5$vHXhk*=&g@q9;{4(XGq~m`fB7_yi{Ux@4uqs*`w%;{4V_H*v_@ zFIVk+!~hti?-Hg~nujx+aDwQ$ULTN<+Rp8tpvheZUaeP`SK{7eA6)JmYAj8+TL9Bd z&v6%2xtT%Smn)%FYwSWoPfgwdvG5A7?-GAC8r;5OM?prO*xv_w^1RG3C8my(Q&KKh zVq;-dc0zEsjXE3=)xiJwW3f#1+u;5F(x(Jb;Pw|H-jUba8B#dLLTNJo$+kAv(i9FQ zl4K52LY}-^>KG?O$$;-@=~_;H;i9TPs-5&0O_{pFl#g=URzhGjd3GTIO&DyyYT9#& z#eFxOCvWa56THM4FZk^I8J@_##-_Ar-zafMB3gZjL~L0qwVOI(8(R^GleTJEOV!tg zuCNO{{dq`EDtt!wL65$Ps})vxy)#4(0V^@V0kblBE(Vs78#>u61Rbffnsj93NAG{z5*KzwRHpec8Jsi%J9HW&K`9B;Rk? z1q0?_vr)ig+PRwz&rM?IZ8j86s8Dx+lYp{GVbh)`IRMJTdcmeOfdvxeZBA?(AA95R z{rg|P?ZSnf&w!%IItVG)RG_KG1{kd9hncY3{mPGhK_58qNqzf=r}O!>{+R2cVnRq9 zaxrPa&pUQ5<^@nOX9=30H__4}0473(BJu4{?stOm>*~{LOTj2W8m9s&;&CaneAyq_ zIMhfHz-_fxc@%()x?e~_)5!yoOKetZb>F#$dS zLe~v4-+rr(jvcL|H%}Vq^eGdar@hLFUGT%&NIEKJ1w!SHiU}4SW<=w4KOehRV6q(@ zfm*3RkMi0uW9wFc(oY?X3YD_+$U;NhIg(LeFv|Xp8qSXsFypHHmw9ip-JYUe42U)l zh?4s%y3M$S3(o7_E1$bboO=vBO}j`4HM$BpEiIk4mUcMZ!cz^eN1+h={j$}~v_?o@ zf3eUxxOdO12cbv^R81Z>6>x4|&{RH}n{e=7QU;lzWImcd`t|DQho0=4xFy}+KhWq` z&P>?bIshJ_!;h_C8dSjqWfSa-78h;6EHpa%*kN+s7qb=1qBze#BxIR>Cf0thkug9(l(ISGM#(dGK7&Qbb;2VHddZFg8tZXB{NQNZ#?lz3~I{y70Lq6}{?Hj70@FoWc zh2)|;dF^&(x*a6~-%T)i2X(W~)J+PTrjxg6L#F9?ZyMJ5x?``7aH?(UCX2>7_rY&7 zsq4w|MA&p_kEg)Bc)P_dTlC&b8`-*Xl_!-WSxFkW%;A|6W=e*{MwVl z<2w^j2;}bc)7AkNMNm}^D(1V6Z4tDPoTWX=?JhqBFkpvV%(4xcaI&i##J)z8h=QO(zvebE?uYt4w!#rZeb7Ri~Y0 zBcx7f7+vpZ8*IYhT315bO6$+gj+K?kojZ2Cd}v`|`oHq3rm9HaoQkFb%}oZG@P*_n z%K^|jA!4E(t*cL6XQ8E#2Az6#5 zNBO$!^ftYwl_H+?hET5`yj~M1i@uKvU*3Y9-$k zBYCA2x455i3-4d1|2h`<%!2lTyI4qbz>}(h7I3oeLeMjvFr)V>XN3fz^a>YBZ%@B; zzak-B&n63niS1KUP?0|Q|1ro^VRQwWYP1l~7s!zKP@i~JFrQUpaacW*W45y!OOWFRvR%coY~lW6XyNMTKs<0{H_i)^O| zK=SP{z51aG=LfrdHxXy@j*VtzIxP}%tdLOEhE1qWhsTrs8Z~-v!*840Zxbw#bUmFY zmqsTiCSN;z%PmK4J$G)$Kk$B=3N{sJs?jFMWcEex8$bFdiGlr}(YAkdM}B0KLgu=4 z`hncbH(03r9mgBO+){ zQKG73+^G=+$)y({0ib0qEHNt-f?FsQa%_-j!4NAJsxXz4tVdNkt3Lp9cRi6wR4OAA z)vBJmW&e-%oWHR187LA`_D2dd)o24`GW(*Tp&|72*s(8Y!*_f#IrIMU;o%YbID<^~ zWo_F4({%`yg8@5)%0b1veAz^EPN|<28#v<*gGYf#8CYx; zIHOjVi&g>$Et|yfxniGX%Ct%p*>_R9?*uI`D!__VgA)&^jmwl?7S1t}OL2&D{EK!1 ziUO6IG%R9g6u1HryfuZ82KTofVpQs+8Q6U$3ygx1E zy{`$u6Ru#0f+yE6-E?v`8*MBJg-+_b=|CsUlN@Y2Y_cMuWHO@}W?z4))X~3t*Q*CE zUzz#3^4e6OsYVQt$&^e8ivwgnF?#fi#^{qD>7IVLXK-+chS4FM-uB9Js2YcS%r{S3 zkomSr?Tcur{3@aLMYO6M3Vup#bb20!AX>6vb0dq{n*L-$YW0 z6PZ``HF5T@mMNMEgBBV14l?+cRpMleX<9Ky4_jW;YH?AxjB+cYum(YBtH%TixIk#d zr9k#OCtW~At`DWt%5>OpgEWoap(>rVvIJ$*38#bKW*d5?yNiT$Jwe-664KMVTIe0w zz5A5|SFcWgm4(q2Y%0)HBO+u%I2<4ojMy#6{JYxthd$UhbtpS9VD~Iv7s$MH$!r*~ z(_ZE0P5RjUoQbYMKITfZs=R8Qe5y6uwuq^i3^Laf9M=;(+u5JI)#pTkFAP-U`dhT( zn?!&}(R|-l_a~A7K2dlOg-scivK6S5IeuhSQ7o{JqMFnD<*fP@*&rVklOXW|rXXPT z+m4_MI?;XP1=oJx35-IDk~r8T4shBwNX{+SRfss@bmZi3La`9oA88$JhfUTaUDLWd zS62onckcY*ZL@RJf2WMy6=y0p#aZbdw)`k?*h(Q!TzRHJHb`0oFqcskWw+66PWFT|2hFaNFGc)Rz=&D$rCz5HeX#Chhh!a^m}y?Z5um z=;%0NkO>`u+FJC}@W(u1pm(61W2b zU`B-+QPol=7gMH}xdL|r2>1E%P6QLUYbn)O6i zu&F>(4HslWxEz3Tbad2$%r{@IZT~d1l%(JZJGymMFq1cKpaQka*^fB_DvU69ixW$ zQH0mahNNX)BNMicvQI>df@HTP2*@-6SVbZ4Pv)~}B_}lO81(|`pna(k?nKO4Akl{z zY_b-XHCy4Ns;1M%5(uhzGJ`X1I& zl0jx$jo86>{TS<4e%he^m{+be^Dzsn2$f2NhRS90F}=VsNa2a9D#oEs#s!-)5E!+H z2O`nrdMp@7n1;H@oDoXK<41~t9zeN>4+2SCP z;-V

Gk~*^-q;;x5EnoHUef!Db4# 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�r4vqggU&a{!Z;_c?}k>Xn?pcZ1606Q~R$ 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%0Adh;ycK}XpES)0?nv?hw6qc!^ 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 LNVHN!-~ayrzhB6! literal 0 HcmV?d00001 diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/spinner-clear.png b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/spinner-clear.png new file mode 100644 index 0000000000000000000000000000000000000000..c66f1c9309cce5f54b6345d84a5bb4efbec30a82 GIT binary patch literal 39074 zcmV)iK%&2iP)+s6$m6>!W&XPLJ|`40xm$XL%;@CEO*J4 z)n#>QrPXSCpK`zdbM84iXYSlKvnyHt!u|cu-f4Sh?kUfC{@t)Fi)Q#RJkHL?&i{#W ztK)W-o7<1FTpn94@BQrf|Lb2NSdCqVhu&&dEFuzbKX?=tr+AHZ~*pKr#w8U&a+z$UoWa2wI?ojf)d!v#o< zHxmhpo`a4K!#Lf}X>KW>jwMIu8gIr9|JiX)7PS*J4GDGw+(x-w&g}&Wre+A>%sYV5 z)LzR6{H~zQ{Xz#&!$(T=V*IAOr-a0myw66YKMrF4|096Yt5eBGyi#Yd%vx9 zyA?tO;sv0_g|6C&p5qQDk4|AGgk&IYZsu-Gnm^ocVvOj*bp1m&+NaPoMTu>X!_n zVHnQ$eg>v* zMPjj7#5B!Zb93{N`uh4?CnqO)I`!|(VUpP2WANw1wQ=T4B{G@JC_m0(4kQwZ9G`Nj zR4V5}2o%d|=hh%M{XQzzrIrBZXFvN{<1?T634_IfTsSs{=UI$ zc5-53A~G;A5L4fabv!gQB+lIJSjQIDGu|5=9nJISEv#u?+xfx4LGe01bL!M7i^p0# z2PtZ4X|e9R@4gFWokq_QXqqm&5xI4?Y}m2W+=-LifpK&+cOvO$21kRLCOTD|xnsdR zMZ-E685zM@d`We6wdhcO&uv%JbyaTfTM|sw*jOG5JILy2Omp&EGq4!maIgz_9`v~A z{L`QQQ~>APxpPehIHR?-)#R~`)YjIT{1Z$D&`2_wj56Ti^(Y@EgQv-$nOVJh^-+H9 zGM-DLyvd%yZ|3(1jX!5G;KsOB@gmMMfM)r%ECVg(YLw??mamz00B9Ar#Znz`SZ?}I zH5n`g8DnE(*1>}ZjlRA^q`kK6aUfuqUy zVB%oV1JeePiJ&jJ@8Z0XHS+qbJ8|N~0QZ@}?-BQ5p8JB(_w3Kqfzyv&nhwA;eL&OT z4oq_gwQwiKv0DKg(YaufV4Pr^+)SSE7=Q?$@xH;I#d8dh!Tlw!#e+aRt{99wFCy$_ zd55T#^El@nplV4>utJE3L5+NLYJR#OSJxLmU_TGw=yxvnaasS*OeMl|!~`H=ym|99 z0h$b$Q66hB%?N;$7jTs4fKgRd6=kr)djP-gjT<*!!Siet&!wIGe1gHy;{O4&v>1e| z`I=FlPa}Mu?j|KAt>%EPQDHRG|x>iAAl`?M;>bz zpTT1^04sk_9_-X*ty<@EZA2>sObP%^)8%=|YusrjfR86{El=(g4^)g7LxRr+cMf)I zfTh75jxz?(tNWsK*<&J7zjT+8!qg6G*u?l-VV;+Ab{YU<$Y4DsjS z*@6Gb@-;H}UL5=xi2hZ~hOQyoFLVdDZb+Qi;JHN@908hOn9#cu<}cpE?}o$#-~pI8 z=1C-A?vRfG%Z(J|d`Q96+}W~a3*B|sUDm2qtBjQ^S3(pv_&1uG%|SGPBj0yauC*xo zpBvabHW>gX)M`=Q6lxgMvfQr?M~)mB#zq5{$@eVISesy=I4kiS4PrY05{wh;l|P%~ zK7uMGuNuFf#TjZBem~2bBS1Fma*svpL#g4;1%O7U0hn&gz&Y^x^N0qRV)vFiSd{Op zhC3yX$;Xo|1%`jKdSX}8CKyIJ$FFCk zD5bYa?QvF~6{zZi;p_+J#MfI?yVr;Zc#;cX<{Xnh>#`8k`$a%1!P7biplLeB+4R{g zB0fM6w4nrntAX*w;^ijvn!H%mbFfP+>KHa+20F|W03E>70O0ZXV-pts!JVC*BaMxX z>zNsX1k3GtgRF^}ipK7K8Q>+|aq_+RdW90M`qXnG+7?riSnJv{ez@MEFRl$$x` ztA#Vf@}ozO8aPY96ymL9%Mq|zm$~9>rb|C~ZLn@+AmezgXdXf2<=+Lc6oyr92K2`2 zdvV_YL+FU1v*iAZ@jXMI3*f}>i(~!bIp0r=KO5tFOfOloq@Fjmc4pP%5V=8UFkU!= zVS;HwohDhR(0R%;#P&Q6s73e$HMID>e29C(IM~ZOo59(!R^8Ug)cQRa02-Y^&~z>= z*98PxuP8qzI)Jdfy}g;Ar+F|F08a+W2v10H22M=+S7$^#IWs1<>d@Vww>*Q3MkRpoz(biOy$CrZjg{ zJ3k&_cA3J?&0;#lFQM;5!($%QI1hLnswuusEl=WQe2vBY|MR#X=KB2p0V!G?OdKnEulSjE(p_wJ>*@$a?%0r`s6UR&+Ch6MuPwx`b;Na zs>L01=>sn?i2AT9(?i^!dJn)Tb=VLMMU$@qJQZ+akqd?dF&*H^kK3@&fweF)KsUN; z*RFZ|dL6T)d!BgWi7uW;lbbee>Sgfg;kncdcEpS;QCC-&Vs<*8&vThYy~OkIW&WNs zkWiU{0+7Y;0PuiWLN5>fB}8(tTmB#XmKf$d*f4)Kjwa`b-r_EO=iG4hvBoh^0|(6O zeg_E{m#u^p)YjH!ArA9e%k$=uXVN0n3Q%dN4G4|9|#qXEB%`$+2d z17PJ1Ce2Jc!F^xH_uPriL`np>CM@oTd=6bT#P)i|f?5D44uzaNUWhz`?=1jyYEAHO zn}igA`;la5Lc2$|%_8fiewzvPR&<)6NdY>)DW-u5#FGiY2>^|ONn$tV0ZH-TwJ}(B z^W>|7E|Wh8>JPw7@Z%%{W|AM*^7|WkGOp(TnP0{|*2%U)n|!+@fIcp*p%|=D*%X*g zYT`iN0VE0x^o4-f=u5PP5Z8bu;=?$LfD=0^zMIE(C$}DM2zvP8hv@?!_yEnDH?JLj!0_Do<~P4dM~)ono-=38w)eg7eXp%rwdx86n0<#2 zAAXGI-!We7gA6(Z(BwHNKojZ%SckzjVL(BHngD7gNJfy5`2Q66q`2Wen&ezWAJuc4 z?_lcfS|6a*$mVzSNh8%Bse9e;GM|OV0so#!=x_PAf!*bx7K2+C)Mwau`1Lr0W}N#= z03?1h8VC;V0RW07!BYWflJ7my-QC^5Oty}%HO7ytd7amBKQ#08Y8j-P;AY6{zoCqI z*v(@OYuX7u{B*tBUG^-H~1)$2nYEB0{qZNQAg#b-moE3gu&`DzV z!7hx61q(XBlV7XiL7L5vhj`~p^FYQKG+`hDSSI=XCZ4=E@c&#sL!f})v|6@za(j;3 zgL0P}mMyMun<1N?ILc=XX3|csss@#|pFD199b2Ow0J^V`aiuowi07wc4?g5}`W zU<`t|2zGeeZMPAN+_Yf90(#|@SMXjdv&^^hzg*6M`W>E&&++2#;l3Vc(1gw}1HCB& z54uT!4M3DZ1Ad2KoX}}TG2a0|3>pdk?i7FKc0R8o+X;WpIk4fOZ#T$>0}jsr#qB$i z6re%;9+zG$VCoroBEb3&qK6HM=M@-oj9*XkXOi59Y3{2Oug@yJ2ZTgd@fcKbzg7cC z83=25-FEW-%;Ns7e){RB=iGA3EvY?w_FNBrFZvy9fByXW!UtO#Z_7Da`TO9#u!he! z$+dWlyZ&{4{vu!dApeeGZIjS93dOcf~gI4+m%F>e1R7rz<|wR`0>W~V@8 z6}OK|z&KaOd>&Y|+j)_$#B(fMEaZlU2Ej79Kk2&bt`i0e^y%!`v+1?hUZXF3;S2R$U0oO7 zb=O^wGnnKVG-J@I!AcJ+Go&T}4RnHFnfzG*C&c!sl%^osrU07!{8Kz6x1Q%)$42&x z+%Dtx_t5o2Zwb`~d|RRGht8OP8w_6-T>oGQ0+HbZs;~rq4nC~_$SPi|i@B}gdvE79 zmp@ZeS64R+{RyuDIUivlgZm&DtTF)6k|j%o|0dcDsTLRTbr$gXdj4&Y7N6(#18zNX z-Rjb#F5M^zvWTZt3ZBt2*k%}!u1Hi@I!!Q53<6KuYMyKvi0ll8X^7(7=@3^Mc>pj_ zn2af&h_iUo-G-g)9D}BQyhVb*HzW&9XnN1Arb|Sh&F1#yav9Tf4j$ZhVDi=1*MA09 zNVsS4!WWIfAw~&;@*eVFDa6f53LYELjYv<;$1T z+O=zG)~s1VY{wbQsH3BUo_OL3dgPHu=-|PFboA)ai&%WW>D}*s_jh@YPDuAAVabPe z7@!N_g!LHuPRwZrji>@m01nUJPrd=5sUJ`Ye39Ef@$Yz8>4F`2l-aE@b_=3TqKnvd@8?jaa@__Z3I zl(YDUH}T^p{%rN`-Mg>gk8FZih3FtGfb)1j|MXkj9^v*)xr2?%ohq;O*~iW&XzJj} zeYu?(&;va2bKIXI9FTYT95VntuG*OtWjc&A>R_7}JZS1_4>UMl1MFnPiV)cw8ykuH zOo;PfobbI=!4nYYp$0j5^5jzJ+ZhB9jfBl#i03>%VcZaIHY}bSJfC5ahQ%BfXy^b_ z{5`k6!Js)syt(}v|E5;HkADGh!q5hT7NVh~+Yjy;1}x}f(+rr`^L_uO)Em31OyHAw zXfBp3$1R9&rrI%hxyoQM-AVl!dlTSW5718IXf0k+@Qko*LSoMD)L3fL6 z-;*kexCWkXUM)mgGLmv#`oicq!+>cK)08epQ-&aj=RDzJ+yRq3SsPkgTRV7X9)s%# z57-<`Xy`Kz9XfO+Q~M4)-@bi2+1>_k$Z-R=Mclq5DgUIV=hSsc=l$S5yM!nBLvYbR zWDDY|;iAJLSB@vHGow@`0O~Xf>1^|kHw$Pw`b;c7xFmrIfqgX%{ffuj5_HYHn^`#2wboqS4$d zue`DjVik5Nhmpi}mr}~-jmx97yfa31DT@ck{waea89I16N6+ueQSVSi`rn08hxrxB zIu-R@cRpshx<8j=9rsrXfC=#A&wU1XiDT9Fa?K zL&8EAxvTDtBAYH8~r>+FwMhB!;{Ig99fuMX3hzA?J?+H1vv#>NeUhYAmT_St9qc^>yL zOC9Eo2@GE_PB2ZlAt3}9!5Lti3?NDVANVC-I#c)6-eA(=Stg|u_K;Sm3~H@2s48jD z_@qTg&g6MsGjkpfwcZ;U*ngGR*I#o}z=;hQVk*|?W`4XuiOp)mQ636-4Auy?j&%rT zzJC3B!Oj7g5Zf_t(UvYjatrw=`6f6oar2ewmHbcp6e$0<26Li6e^%GKl;e7V1w z_w^6w>C9lBPWD*z!v1Ng>0mp7$IJav_vsdWUUSMepiK@kVD=D=o#wrBf^VIHCZ9u)9>saNw+4SPv3DfhJJfLLOv6Fr<&!TxI4(8|?AF+Yoy?Q_ZWWL3 zS)RNJ-gqXF*aH2}eQ8o@enYoBhnD+qG@Qw$b+!ph?TlH^+Z&4kA;n|WTx&Am85EH& z-|3|NS}ng8jgb7DK`r$L)u-W?N#e8B3?l6f2ATX>yZ8!UGtMo>{}ks9weD>U{k-2< z`?-CeN02wzXdSl>GJpCCJ@G`B*r0B{`DOs608JGhhzKKQMLCGHi1gG&Pv{8|;Rb6u z2m)Mqc&E z<^{-n`Fj(zbx$DJT*_c}JbQo7qmI{Sb>1GIsaQA*oR`n3 zqQK)x-UyEO>7n=0Wt77!! z|D2$2{3Mhfb-iXN@;u(p!2IXGTne0~Q2<86Z~&>H-Y7gD9JA}sYR^<w zci%1CXJGVDh6$NaMua0mG=~*h8k*pWBr*s=KgeSN-DVno$vhrQJykIL`~LB_)3mt5 zEJxYk-S0d)^O$MRYmd>Dt0DqCzxcpJkjM^Z`8>i?p&MsVtbzK6CB8Lq)xv!OAe9&n zkd)b-1PjL|bH#F#R?ahMPIHznTD^=GE#E**3!1opv3U(r{gQpOXi=;5s^rO$9kW3) zE5XuKi&JSJts)v0Pgu0D!=MGTqI4avz6qZ3e|S%VUfiFf?>(ELfB$h=_K#b*9pm;B za!=?}l)O3&>#RgnSAJ0t;i}}h9(qb%(8zK$2YLD6j=|3t@&p@##StP59ECB_cn6>4 z!Dv{!cCCTk3;thlK>pzme<(zCJjTRT1NF6!*Ug$=p&E*xhhnL4|ww(ZXl8wl&;gvXgnjYPHkj|bR5?}#$ zB=~hc^UO2iInQ6@m5e9`ssktHEW~sW21E%lm5Q?uur8fdh9Q%8OTfqY3{>W z(1*mJQ{sN*$4OMpc$E8o6A6ct3=3#E{=WzdXi=63GlvS(&CnL4nyvbo99l@Szh-W` zNpspvx^Y94e)Vl}`j4Mv=a@o+%hKAg;><6}m``Oco-P5dp?vo3N51KAQ?INhZ0c#S664O+a3WWbecuFQ`^UiW53HRHf|K__q!S9XO;$co2P?>2GTwr>; zXxHogV%}ks-nwNyI@Oo=^E6n;^XoLw%iQ}h^3Araf(UbbjsBs9tY*c<(k6I>}Jjj}Y zauqxP>7JgRmI$*?;h?SU zocR#CO+VJDM-2fu7x>*f^p5(yPZv0KQQb9#a#8LieiGw#QNuz(qMc{y4(TGty7K?jW-b`h)i*=tEx~^Yb)VA@%l9tAYH?^0g|}s)c1~1A@o=Z`s3q z?m;jow+>r>oM$O1*Dr-(z(q5U1&i*vGxiiMS`RL3*(`jO*E)vk?Jesg^x$XH^!aa2 z(7*gJP<)4{%-z!3?o6Pg*ryRjTrU?mb5vYnHG+0wxE)2AX~q#9 z#Nb(l5K0EbOBXL*yZ~9T;iH8Jju&5ik)C_*IpLN7y$W=lH(xc6?*CLbnM;h~(EKSy zk)_=t6&L2P{Ka&)=PVJ^bqt<=_mLF+`d^JzJYj3A9U@pza4Jzr=EeMwMTbw>D;%sF zeS}CBEMQCMZw*+|6Eb^^V4O(K0JaI2BnN1^B~LbdM0Op@bx}PYG*<+RnhEjKVKDQ~ z3vb!1-DFIUGs~&l$aCT>gW(foCkq<4@4!-9W2_A(8v4s?Gwhyi!Qnd z7ItJV6QGGRfD@nz@PwEy^K1$+T_(eTesi9mDre22$T<k& z@J60bL>h0@%vM=VOEBAdZrm}f3!b@M%C?RA;&|E-r^dWn!o79u(R58^q_V4BA0PP+fk zn#o+z;ut8$FZN4`Yue)`&U$Vi?R94Zc)tDW7`^wVc;#YJo#CC{x9By)J$)_Dsor6W z_8zl6383;o7#mzaRCEPWoAG3iD$qo@7(f%5lkie06qi7_mrm1-6YK6sz8zDQBLDQo zLI90keM8D-Lig;V4KqU9b&qOxRKe9=1Y~1>+!ZLqBN_;oVkATX>(_p^qw2z zK}Mp5@B)yY0Lf5#=P6>sgJZ!sd)oG7v2r97^En32EVF&&Yc4_$i0UlSAsd|K0-EHm zx3(Plxh&It9k(?Z^qcQY&~Lvp5oW3Wf>bkB$?;Oez%7eeDBuh><7wTYfD<_gk*WQ} zi4y=#i0v@|ClbT5$e!lOhUBn!K<|ki9XcZH^w4i2)D3P95KHH@OwtcOH$mo{qf$nT z+k;`LEeGd(;j!TIay1y1Z?nh>{7f?wmyg^TpK=c=_edsb>RJ%Tgh*x-&SFPa4bLN5 zGwt)FT>x;(k`DbQ+(NXlG!twSVSs3=I2qWcbYwO>6arp#Y^L7ZvSo|W*4Ac9_oj$L z&*>|ww>KG6_pG;|KG&P`^Oo0V(HF3kSFhutXVNgXlexoDTc>8^ZH9??lUI)|8#v*4 z0hJHJcr$%;cEFvEVNUMYv4d8vT9s^UY(&y*L?(i5=5#R~zRJkSZ^cH#FDJq0#`b8Oq0?r^1MYTCqX%fZM3-6p%R9nB!echAHi6(i2-vt{N(h69e5055ZERF;<>L@QS16> zK{QA5XO*zT^7-}3BIg2DhI(ui9tSl9EZw{*O7q&wS435(4RH-KQhZHUvE8*&kwL@b zQwAWxI1$OBqA(yfGGJ!r&YfF>v|s=?WbFadl<%A5UBAbRbru1uj3N=)a{!#0xNZco zZD(JiA5?R`<~Igz<5lRlq%H1cyKTUPib^*wI3x2Fcq-B-mO=@W{rYzu7mT;}A8t63!`t})x_U`orKj0o ztX#Dy6693_2h5Z#A{QX4>ub>}btM;(KumbDXKcNv&?Rf;mb`}xp+0chcv}^&;Uv%Y zcU~Kc{9G+J4KF>XQ7&-$`b(g83zXwWjvNtUIudxG)CH1xAZt6qD|v!dF__-S6ROVL z?Zgl}`_*gu>E65fcf%r`w@Pa`D*qwN#T|t#SBGe;3~KMz5Loh-4durMR96}P1`mg! z%8hl7jExE4ggXpkf?*s|i4u^q3pK=Xr~H{116_p0>m-Ym)opEUb^ILRpi89~vRL|F zMyNPjlGkJ~8X6iiIyI3ImjGS$92Ph^VVIN-7GuF-E7zUYQ7=cPZ;;d24Vz z0`2;S{M~|hF847>IC}BL7tfMOJ@PVY$dpxDQPn_%q;h{P^1Id*Ya${9_T3*phcrCg7(yH?7nyX;#Zh|VIrY!iNcP;Vj$1OgivJbmeRbqbkr{gph1%bL-h zCET!1X_$(J&`-k9y{dw8MDg>}{rMoP^MM^ZcJ!@Tvu60#TW{^S|Ni^?;39??b!7jN z`b|~H%r}R@x@G6y4arr+t_z&}(9b3U-OW-~X@BN5*RZLvh}!;}M#BC;-g1cQE;5g0 z6^Cmmwy6sfWv*c;t!`kJFVc;BAG0L6=yPG5zTg=R0h&eNxo_V-bN~MRP>zeF9vWyS z&=7XUJ8?COlb(4SA%db^wR{)VEgW&*$@FiIP>Dakm9=Qs!90EIM;ZE;|IE;_)Ak3T zNBX7PVs!hK7%k{9DITkA!XfX-H5(#j0cRv;&l@5ar)_1}(3z%D$~s3U457ln0)=nu z#*G_AGz&y^s7L0{pWndmU&M>`1W))w-1i4~6FbJ^aEw{%C5voTfaNn5{p7_Q-SeqY>h3LGd3z4$>36?8L4SXLhW_pkQgk(o+{p@EGnX%n zg#Ik7%C$mVHA-WvNwcUM4sl(uP1Ala8l}@iQEG2*5|(siibAR`L=QoH2kT@uI=}#W z?8J!^JGhSzHa0e%Vdi<37xg467*c6TV=r&KqG^xvu+W#n7Zf^ES@r~m4>))?`U58- zcgz9``eit@!YhTWa=_D%jh4J-PLTVnR(W3-MV!|iRnmc z6Gp#z4bQ;_1w7~ZsI3WRoU-|1K@F~B8l~HAiKZNHAV$5WFwHC%=}WOp=UUgI{-#@^ zD9Mu0TP}+R0Vl@pDF@?>1nD@#?159@dEb5anI})4H1_V@i}+n3rbE98aRv3x;nu(t zsfHi_oG&wDMs@F9ywO!L+Z=Z)jJ3hZJG8OFO)O78+M1q=b|kFPD8e+-J(3nqKr+kdDONbO4nW+5sVzx+n@gQ zr^31peJ271cvH$Cdp=@}xs9dM=?t8+k>ivBubf21MQ0>mX>M*7h9(x*qevbi%MnjS zCVL%e=ys#JR-Z~;fjj`y$vQ2o7%WS`GbH_(oUnunXK=)#v2mjQL5uphjq&S)!xp`= zH%Bk-%=xfg0||e}7#zpXoH;Yb<1ljFb=TD_TC^y^;~?@^k{zy?kj2A1kM%2g{>2P> zBaT!ma1KW#X!w56h#Ib$yh=7ev2Mrhh$16S;{)Tg>-8KRKAEHLvlgA{vuKpp{5W&m zGrV#24_iViM96LvZ!R4zwn1wJx2AfdqIFxOB3T&-ar1q2oYMl%srk#mDY^#EzYx`- z15=R`5Yf3I`}LJb7~prYKmBkuEndW5ojK{QgtVwsyb?ZQ7CpNo9|)TI@$oN>(*u7~ zMe{n!AAy?c@c&bfyozbFU`_S{PRR4Xq9|`1F(f}|wX^vMHOoorI(CMB@Po4=AOM~R z7him_c#N2SBtm)l<(IoyT%Taky$Wt$3~Z=wnU`U6etgex(e_Po1(bq7op7*Dv{)<# z=X&Vyko`REuSYZOBrHxbT}TpfDZXXA!BfvI;3BGzGuWLSV89!)L9ll)PyO;SK=5o| zUWnZzV3E8yhxv^6U)!Ifp^?gH3-{}8zV~5f;Hcw0xqJ8SNu*$dBLl)O5o#%QgMs?Z zHOnI0!EX$3Hq-}xuc~0``$5YvN|VZ#Boy}68H{X+_ zTdt0Ze7d37fXf{$o*$u2>!S3`c2AXCr$bEFb)25Hh|1BrfN>(zv+&=9y9zJX7z{#u z?Sw3cTCFljd+H9W4Z0hyKFKNu!c&r!n8i!pa1-+^I)2)skAJbeSn`*5XX%NTa`fKL z3KUG`Bx6v?COm-?qE6BVNi?C?>9aQvxN4cUTJqGXA?oQFn~I@^6&IBkQP$z|%P)tX zbF90&`!tL1-7LzBw7tBT^E@7TS*g#j41f}pq~?CAT&f}cK_kpyqiC?KBhf9~k$7BN z{cgRzB_c(29kxw7*#o9nrq?#DJD6vNCA#BXIof?FM?K6$28L`91R#!#0R;0l2r^UL zd7yF-tmNSP-p>6oj&d0&QG&tQv17*sybh$ZyAsor(#YVh?pY9_B@1E&G55R!XP~+% zoS$w=xkq>@@(j&rloR(mIufNXe`B&Dk$d}|9KHV!M(BfgC+U;-ri7I|ln&hSS{3t? zi&ppor(m5t-rYRku?R%1w3%X@Dm%D=h#@RKxWd3)1-mYixboWo%|(7m4sd+w_o`{m z+=K+o(Sm61srRy-O@~=|B5Qmfm|u1-T;2AgS?~QJN~Qgxle_4@>2_ zr5P`*DcW_i+s@VlQ5~*GsPl~=0kF-@n>Rzx*~_9MtmUKIw{P!RxNzY(e@-w?)BvZ_ zRB~>;86}wY&|a z`C@-dlu7vFKabN_{$rdjw1aQ|IquUj04I+HR3S)YlV{N#8&pg)4EgFew_Fip@TyF| ziOpk}Hw(DARi!Ee-Vma?p^2#OMwWRIYHo~pgMZ0a?WFO_wFg?Mou%si4E`LW-)?!A! z&A0kpUKl1=9G2ae(|+~S^AyNRaRsDWVcMCQog)a#?Efq z-{BXdBVw_0F0M&Z4C2So!^wp!z!K#|5M>3u@UC6EU^L1jA#_e2(0TG_Eh(xy<2fvH z_naG(iv=nbya00dvs!Es?`3;S4QdT z%j0yz=D7EF`@GiH8XU6^$qJPeWJI0_1mSgO+0+Bafi

gwHJqh_C+uPfbQVwbtWb4U7JjY|+!`wV!Jw4~R4RM3tCoJY`SUgY2 z`r(3M!Z@T7iSPvS)me9T#O(S$u{3kAl{~EQUOLLNWWwG|qHx)%$YPPB^$6z6{ZT^G#(=*9}g-N zkHR1X@evq{bZ1C?Mx|Bzf;K^j>8Qn$XliPD3s00stAVg2F}uy{{@_ z=dIsl32Vv1SVePh&%qph>!ERa^7#x)A(aDXG8LivS~CoA1b~1K0dNIT+pA%wj<4M} zMn_Is-hgR3k|RV4ZB~0Uka41iG1nn+0W_h@MmZX%5w%=V-7PD)(D5K@0%AIU+2wv0 z_{6VQ)BL$H35u>>_gM)(^UbLBph6WS*He~GKLsVd7P}3{+c^Qce5AfUzux`C z=F9YXUS^5I~ zRLT5RM|IWoe0qeQ)H%09=otrxXLg`=?90)zWiH~nTM1e1rTV&P5HS?_z*UkQj5kYQ z;tF7$ZW7ycffH;Kb);8PKnCfzU0+3}DrqQ|^iVP3_3#Nhd^}5k`PJ!-z#NE@Mx^Zj z6S~o`lPjskTl!|ebi@%_1qb0XXD!-uz#BMu;r6r0F6yZF_VxlWkzg0)U4_vi05sjk zQ;q33v)Nanv%dM}o8LP;Jp4FP24XS+JQ+OdP){2b5W#x&ge{w|=%CoFYvom+=h-o& z*%|csWJlct2G72Ma}J6!YHG?J~lD4 z62-wIIh##-0j66@ZnDFegUuOYXE3BIo=DfI%9fK$)r7zv)pF7~%PFW(Gvu_p%&k_nTuss9?j8NQ_E$ zh(YO>EiZxb6cy4;pxDp^6&K{1lueaMP%->d@Lg8-C|-hr9bUk?aDCgQQa zoUawun;8w9Xo7OeREp5=LsS;>UANazvMN#{+7^rG zRPY6p-#Wme>o*=8nRbV-TArYCrBT2R?^qb<2B;%BZA#eTW;y5i?7^h0)^n*t; zwEs|H?!mvcB6(jIwWo1JB8L0-2e=0H1p{YJZhR!MBoaX4q&`*qE+p>~u6=n=S`dju zLfm6S1cBf&11Oj$GLU)J>FA>#|TT<$Ggz=WxP7>0h~E1&fMxeJAu>*s9rU;#V_pb?VUdWmgR!zxtn5DoV}MoOh)sj!%*V*l{LUf*~NQm@{h7Y^ZD@znxMF!b`_zl5i zC2$f;8Ig-GzBtXV4J!kY8|#dQ&~G~G%oXW3(-crg`kE_i5C|Qp4{#gk@2+2yu1G<@b8n{1$rW)|;FpN(Mk&jdFZT}y>YK-r&|~-R-2*H(!zIZAc(UjW zJ*N(wW$*$*EW9DbQK1xvUJnrzCr+FgXZG34;5i?;K@mGIHkJMRY46^>A{?~3O69q-N712wtj5~lcN0S-Tc6qA^_@-$bYO2h*8e(JYlafOu42hfDnY4e$RM3bT9i9#m z#$01Iw^KIr)Rw}~NFZ<`lomC&qBq}sbK58%r%s*9cXV_pU1x~{d^E(~;p&DY+2(v- zF?~rwP9P8ch~+J|zqThsd-nTUOY)M1`07{Jtx1)=TTf2%VXPUT+=yx(zou@Pw;8kIV+ zF$tV*ky2p+)N%n#%b}j4g5nmzT+xIqm4O>>_nY@i7bgn_l`;gEV<#u*$4?dSCf}d)61-{5o0lj!*iUU~(!J4S3^o=^fbY@- z2xMW(Pu1{pW1B3VpINqSS^A=jE^1+E?f^?#qqViQ7L1|rn|4F4u%fz~MIFGo#_u2B zd3(Jrrjy~$dkk;E^2JvI#q>c62*khc>S|iPw0z*~>Yk*BepH^5VQp!0oWHeQwNkx^@3=K53GuTxK<3Y{^?7Z&DEk%qIRP!EyX_0&I zubB4;29RNv2}u|{QS2?7tEsU;CiZa4KJTaBJWwj>be+x$LrZH*W#W26!}N5VLgi3_ zakYOika6mStkG{R-}sgq>YS~BQv=P2VxM`b3g0B(JWVQxO;2A=c!R(b1Rf!}UK8FT z3l}botzEmeuDiSY)X}3yhmZw}iGl^tjN3Hd>5fz}<4npn-|wF{Ur`-SOg5aIo~Ngu z4U7~zNdZ}_Z@Hn40j>N=^Xje%8XU@%H@PAqCzs_0_x6*4;XoZH{0tGIo>#t`5Z8Ht zr|b8Dn&J7=r%#U_K74q987*Sfk;%UXc|hTIgaUn-Xvz%<6-r?!O2iUUHI>uWf(sVu zb!FrkB4`k4aY#8zMWk|wdoj(tiyqUd%Ovtoh$2P_$!t&}1DU|I-{Gs@yl8crjA+Cg z+I>KD&wlq{kLg&}g&JvENBO|n)0?54d&YyzKs~RK-Me@9_Vo1hLxRH|$x;^~zb(&o zx2g%=!0FI$Djkp070I(4YHM?pR;@@^G}y;ZWSH^!dPira;b6Vrhc;eTQ}*}gau#iU zv4ZeUqs+mih52pYMxFazuv>52T*qCRcKJ^%J@R(4D$L8yyC>d&p z=hm%T*Ko-tmsF$96c{E-c)&FY$EHo2gk?Qdy-Uu0(}Uz$4xi3K;+BEIz>Oj{N6y))Vs)r;fZrqao1K0?dd0G|ZT&6jZOff~ zT7|i{Yu~sB5alWcPDn$@Eju)vuN-*G{BC#x1biQQqFVjLAQE@^rB#KH>?skQ95reT zsm(dVZJ8X>J_gRb@Il5rSD=Z^V@T5oBCvJq*5f?BLvZOs&IbT0fD>#}x+s;c z%O{S}u~2Xe%9%$3V>rDtQ$#6~d_zM+p1hM5TQ1^8QG=@!fD__4CKFGp_P4y{Ez7_0 zjc;ThdgvkSzT!fNDB8DgA8p&Vjjq4`dTQ_30-hw*%~`L@Ya+T8S~VfZcTAK20bW(| zH61veew@1RrmMfE_G5Dny*cq_A;TTDBxr$ol2W zIEyPk_Vs4zr_Z@t%=!i@x2!ie$7$8_G(G#`aE1RLE8k8KY+D8PG*BTwtmkpr#Z`3C zDhZsX2AqalXr84BqY&F)qC-bdP)A3H(9c892@vJEEs7t${PN52Q9j5Zse&K!V446< zZkFGV4=biCwy7AWZ#mScbiCszV8&`ALgLMgtj8y9Bm~+^nl5`yAJd zLr%b~lVV?1ex{e~u*wOo=~WDiI3RrA$6SX1cIdCSE$n9FWfk>#kDqx&p$ZVJoedq)2)X);w zT#*cwpPk%YlXFraZKnLlrPM#tORHBe6@&l{1rVX1fA-mDk$voRRaF(TV2L0S9&-Vl zC{`-VbR{UBT!Vv3WfFNkRNcKuVZd!LK=sr7n>Vm zmlk5WT5>Sp065N^Ig{VDYgf=rwY-cbvgEB`oEtW5n6+xvs!N`F>Zt?w-FM#v@6rHk zVGu%YP@I*X2<($`&qJ$6wpSXa$3aQx94H|~tb%`C|6tY=B=oM8MuUZtWnx9tP;t@d zFyoK^-9dJU)8A2P6~n1b1mUbG3j@&P4MUVjR*8#IHiMrpbWx!Q@eiBM#p{?ErA(I| zw@P1AxeDa_d-jj}=nS*dnqSjN^kGL1mG|A^_s+nvmT}~UYwHVH)uwcst_g1)Lwk~X z`j^t(ci%1anF!#(7(MyqlR{^Yxy%Hl8>s^sBr^a`B+EgR9EQewMN3(QzwFA3wwD*E01e(L z;es>;mY@Fgr`-nG@(|mLzyd55z`1_?`VQV<*Yi%io0;a=;NW1pQpW&1!8}!dP<7vl zG#2|_IUMbBsvK~Dr)CtAI?i5ZP+g}3cM4PLziOx1wO}@v$+nK$XXU0#x_-2?vonHz zKsvFmt}YY)M*vO+Od)B>9GtVmMVOH`^ zy!$)f`OYwl<81&--l3Z$Xba%fGb3GkF_XV^TuQ%3BzSg6_4hs(@HD0#PG;V)(b|Z8 zhTE?>4N3@I<@>f!LIlMP__3-hk6JVdnR~Xw4LG4UL&pBq08Uk6LSvH!urx|5fEoyr zx9Fwq-Vp(P1G#CRs6k!b5n8w? zCX7Llkl>HJc=2LUVF5bM=H_Oo`#?f6%r*sZf_183kFtQ%BVtGoH(TgSuUA)5q{6Cx z`(F>VkifD$s)6TMxcwy+EOh7nh|f^z*U3!aWOMp6difuQ(QQtJ7S5hMyA2}f_U+sE zKKS5+!^||BQOPmy$T|Y$dXWOMU6rb_-PrE zzUhg+l_vTS`uhrE-FwbXZ)aRMKS`hX$ZTpT>$??b+6lDA`_t6hx18wULF(@A78^f` z9>TDR=RC%P43MW7D95-#R7Wa75mdsPCmi6XZ5)a^MD_Nd&aU*>cOX#jb&3j6BqMUQ z{xY|Z$OUmOjvn%{;5Rfx&)0%?15I*ao8-eloxs_~lcaO+-o3AX|NGwuXx76}1brvK z)1fLTR#8L^$Ir-1)id;*E%yFuz0Pg2z_R2qJd%5(!Sgn5UzIB@1A`k}yx={6YM;<= zLR5zjC#ty0s+J4Ag${$Ry0XJAAu<)}PNe|q^ca(UvS*(^Vdy@SAr>x#p{J6g(nKF~ z=OinNbsrp@-uQm)D~qRrBUq13bokf;xEcwad~0i~P&dt)Ge;z>kt(MX{N9Mn_AkVB zUJE$|oQ^dV3OKzOrxMjY%f&`{F)dtB*$rc2Y=1Q{fcX#=!tTZ-fFkDgH@RI+wuu*_ z`|4?o8_@f>rb}(~1CMxz5*LAdOfGFPI-+$=QM;{$!k-r9x@R(?b zS!eL9^ZPN4V#9FzDU(7mPgb41$68&`$`*a()af_8NZV{ljSu4oGcby5`Z7AoMD9&g z5rI`*GYrl39=}Oie^INPj?Sf|1E>Q$<=gxA`xAzpyGNPDWvO#^WxC2r6MYE%eFf6v z*m&hRbRW8Z1#P^%#=RE3WqGI#)YOiXZGKry4}9kty6?WN)Y;i7@+`mydG+embmf&- z3hxje&m#<$0L=-wBLO@SjR%(`_;cpqSUZD^Gf8&(C)eYe&2`k#UfJNDJT*yAJnifF zuBQ~!!BeFe`w_RFP{H!Anp?qngGwEzmCve5&|D{}Q~F$s>iq**I&jdJbqrH*X$%Sw z+4cXyJyjv}{mCbv96NL7Oci`Wc_&7uB*i#=jWYIzVUcS%j9^l{pXEpGikFIYPKo__ zx&V$1Ho_AkgC>!a6bVmw(EvCj$}q&Q3&RjMR|~$(&QGgX&6m1ew}2$W9Xv(-tvoYM zf5I>{oTI^^Ecql4@>eIt&GXu^A@udG$M^4BLDz4or+Cuzp1ER%JL5VO#{+0CqSJ@B z(Vu_ehk|7y+!8wa#d0Hq_TaF$(a^)fTro9H&n?6R7sGGe4dimI_DFu z>r^HTa0ZF%ZeZ!4K2!a0=HS7DlMIw`xE-;Wj)Wmoj1yUsg4l>Fb{82x#*y@{Wa78A z#;4t9kZzz)Z*7T9ZD3HF0MPOHv8M*;g_nGNc_Eqnkb!cX7Ye`=mQ`#VqSlhEx0Fyd z)cNd57?v)MQp>EQ*0q+$5IDe-Pa`bWZhbyDH^RU)#NSF8XR5-rT&_;GwkAs7{`xW| zUbU3)`%YI{U`VbsTWS$sq5B`)E+U1nsljN6Ijzn}hj{nhcf-FI=@Ug!KIk|BoD7=T zy1F{3Z1TuJR<5W{RBBOIz}e*20dr<2DjwW}hXeJS-Bj$zJfVSTR@-`_^0k=u`}I1! zPS>2g1}F)dvS^6MDoW*?9u9S)A6pCyntoz>R~DXn2e$7PXkG z#Q(%REymxxzlVPQeS7_G0dpiS{WK#ei40E#04MI9*$Q3VOx{=l^&v12S1o%5x!>s{{6?n zIQg+~NkXi8b#=9`N-PZHbUMKM17~ODOyc!}fxy}208PD?Ojh2U>0rtca*q@-9KfmS zODE;)F$tOzR2-I9a#UAG=sX>6DbT_gi9B=kzyO2{OWfd=BqkQ}a>^*8S$;pJl|ie> z=u`5`EKfxnBz}BJ&*3W zt3~K;eRZitp2#W+&6LY@p42TL6Fll4|M?`d%YrHhOf#p&_reIs^ZwE2o_lU$%a$!E zqzh#70MN|BC5bo69J$ZvvKglioW2QM;YLvDar|W9j(UQMV~(Bi$Cd-|^+dF8g)E$| z$T2y~qZ%;dx~+ac3an@Pj5AQ@=ga~W25$TSR9ue%(?IwnCKrq8Nb&*J30EY80t!E* z;o_2WFiy)V?O7*W`+es80;ihO0LX|;sV9I_is^7e5{4muzs>p2_$;zS3H|2EdI_A3 zwr+Py>(fWB)^sWqCJYUn({M=wJBMnctf~fNmn<*$fCTaCYyLXhn{RBO-~DJOU9!Ha zRATVb(ig#)W%sWEGenQvta7yTg#r5HA08?hr(zXPbU(i~$V{`hqoX6YapT5@Lx&EX z92y#eMP2AO>+9?D9)!dSVw^ho=%Tu(!C+Q|#`*4;ks!#O+vK}2fcFa=139$wh9WmMR0hv~WyKmm| z21vK(*7XxMb%P`zsPN(q_@ zKmnS&JyguvOXC)8dv#={=7{g+4t?;;+s}Re1{nUl0D9%}6n*j|o%Gi08^RF|OUrRk zB(5W=PFzBQjT@t9Cg}G+dnE9ZH8nLqWbho}xjwjS*RG+tbLX1K$9eMP$sPojAY!Pk ztu61f#!3?zYvq957$=&iqxs6^Re{Cdy^fPzlR=97GY-JaI>0UO0i5LXn$CI5GL0@X zdW2XMgH&o&4|BMMTb88BQi^lQxR4QE#jtr#K&UA40pIKenCF;n;t1h{eNw4%Bxu5# zj#Oe%?uTeHnM`m?bHAkdc`HKn&;Xb?qpuJHz5b#&RW~D6|6&Q8P(M7WX?^l8nij!{ z=~UMQJ@S+OkQ>1?7$AmWy_XT4-531K7Lu_{gmxxC*&NXnBVH~Q2b$aPIjo#i~RC5%- z3GmSRC*o&CE;2Y+L5#r*OwVnf+V$&v4{^V|n5MuJA*?K_MOFRu+S=MQFS5mOjE65E z&S;2z252HX{vEf$n!8@=+TgL2k~h~bb#DW5x{k>6_>|zZ|G+3^GGRA@X+{rKpy#Z_ zaUN;`h~htfte>vjSWEBy`BvJ@=g@!|82=_S2`V)*v-a;j)JvcEgToER%v!~IB#=w2I4@j^_P z1yMaLTR2ak8S)VKhk8XF!SHo0$LWp4o+M|`tnErN04j3|`b-?~=Q z@}wj%!j5^I3U0bqowl4>&8akXPcy1QGntK0S{zzc*KqshJ26 zDVfFU1qRK>A0MDQ?%o}E2_CDTa67?ZImBZAq*MgWs2!R04kpE+OmZz7t57Rxryf2LI7wVU9GguCQZE_o7ARR-c*mOFLa9e<8uBD|V*V@{e z52H{D64jmJx^K_d*G0T5R|Xuz!-2rzC8CqYAf3rb)cF=}zenB$q4txVDd!LsGL6^C zRud$u&%}fb!MIP8e>vMq)a=Bfy5+NJdWsN8{veD))`hzcL%8qsI8{9WPl)PdyC}hb zGwGEP3CE4sHBz$Cl>0D_lX9Q!mzP)d$TB6vg@BJ&c7(5Q1xxjGl4n#RGFNIGnsLz7 zW^Q&OPmes-Pq*K-JCu7B1Li{vmiu`u2cX}CQHVFjF*v=)^P!1)i;;70E_<%p*h@g}jT1u0T_T5I8F$qWvzncLiJ2}Yhs9q$wFklMcgnkpZ2vI$o&Cd6`z{M8< z7)Q0Kct!$eMqYl%Q^C7qXZb||%dNgTU5T*LtV%Nvn6|h+$Wp|AK75vb;okk>E^in+ zqtWPNJgyMYhoRfdWHK-ciBL*g!6Yd$(LD<(lrLH#O4VVE; zG(n0h2E#vS25Z$*9ISeUG##BWiea86uaO;!;!(&X$w&0nMnUpG--;Bt-0w zRXDU;B;c-=dcLT?C~#gD`HX|6!)MpYIDMJcOeE$RQaGw6}eW5)NCKxawHG*M|@&>5f zjwV>TKsK<<#Kc4v*|3zlX64G2ZiNgzRbf##C}ZG6)*O+{RfeNQWElm|sG%i`Qg+mk z;j_h9h4|c3zpS$La1o6KtE@(8`5OV4H_p(I`??~nT9K|egN%NJsW(LBM-7!DraSxA z7a7lYe#i24fmxBm(&qr(o6wYzNY(+8Ao6g^>Xx-qdT~8AUr|e`hL{5wb5w9aqEfOV zlcF3wHc57o((;Z>rt+b$L{v{EjhUNea6@PYb8^he?Ar{>QyIaz^Wd?0^CQ_b43cfA*ojHOULV@ z7c_9@f^8`HgbLvih3{f9QTGCjQ|QZ?IQS;CS&b6d5nXen9xKLa6adtbF$%7ub#)OM z7%0!!Q?Y*}m7nON(t1OlTXR^zba^eAj?gc>yOqB9Hz&dvQh}}jNoRPrXmCA3MzE77 zPYTd{?6Jqn-oc{I+iPlSn)9D^t;f->#FIlcQnyw|NK~(K{zi*2vOY=D0xRUD+E!mNULDrY&ieG3FmM4F(Qcs zN;CBIWQ3I&kFoK9wd3*NV3wYLDKMEuUIR`wkB}Wni0a6UM0O0N=dog0TASPYWLJ?w zmMdtYEoBMq+yA;;B>3p>&lCw{Zi*MU1wp#G@`}o}Pvs{1sI=aYFXMEArYoUUOFLim zxkU?;^x5B^L!bBq-$WkxXJoS4=b@*Z5zNn|Hm2n z{9hfXH^JddD6LwyOpUSA|J5x}AQmX9PqSP|IUopqB_1QkBb>m?O^xPp?2rgIDOah8 zoRV?tN@wotez#L|V)gtEe2ywWcXbH{R4hH@>yO6I^HdC^yl^v|Y}ey38QJ zoO&&84~tbyYD~tMx8K@Kpa07fbhg)5LYqUyd3a57PxLb_;4EUC9UUF!@#DuMH8nND z5jes<$5J}td3~dXgkeZ9PAGYK&OtS54Hwsu_5#mv`WFS3%0Dwjh0N}037W8g|D4}N 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 kWRgiHc{?~s(fh($ literal 0 HcmV?d00001 diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/spinner-spin.png b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/spinner-spin.png new file mode 100644 index 0000000000000000000000000000000000000000..98a9991c2f103308706ea3c52baf0b3e90ef5a11 GIT binary patch literal 21353 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

SjXN<|hNt%y;m$|%aMvI6 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 literal 0 HcmV?d00001 diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/spinnerbonus.wav b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/spinnerbonus.wav new file mode 100644 index 0000000000000000000000000000000000000000..5e583e77aa7441b968e4ebc8de47da1033206c29 GIT binary patch literal 309536 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

}JOkaT^af>To^nKaittEF$R(n9ODq-t4bDj? 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�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

942hnQeXd_w3}~ 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 background.AccentColour; - set => background.AccentColour = value; + get => accentColour; + set + { + accentColour = value; + if (background.Drawable is IHasAccentColour accent) + accent.AccentColour = value; + } } - private readonly SpinnerBackground background; + private readonly SkinnableDrawable background; private const float idle_alpha = 0.2f; private const float tracking_alpha = 0.4f; @@ -37,7 +45,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces Children = new Drawable[] { - background = new SpinnerBackground { Alpha = idle_alpha }, + background = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SpinnerDisc), _ => new SpinnerBackground { Alpha = idle_alpha }), }; } @@ -54,7 +62,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces tracking = value; - background.FadeTo(tracking ? tracking_alpha : idle_alpha, 100); + // todo: new default only + background.Drawable.FadeTo(tracking ? tracking_alpha : idle_alpha, 100); } } @@ -121,11 +130,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces if (Complete && updateCompleteTick()) { - background.FinishTransforms(false, nameof(Alpha)); - background - .FadeTo(tracking_alpha + 0.2f, 60, Easing.OutExpo) - .Then() - .FadeTo(tracking_alpha, 250, Easing.OutQuint); + // todo: new default only + background.Drawable.FinishTransforms(false, nameof(Alpha)); + background.Drawable + .FadeTo(tracking_alpha + 0.2f, 60, Easing.OutExpo) + .Then() + .FadeTo(tracking_alpha, 250, Easing.OutQuint); } Rotation = (float)Interpolation.Lerp(Rotation, currentRotation / 2, Math.Clamp(Math.Abs(Time.Elapsed) / 40, 0, 1)); diff --git a/osu.Game.Rulesets.Osu/OsuSkinComponents.cs b/osu.Game.Rulesets.Osu/OsuSkinComponents.cs index b2cdc8ccbf..e25b4a5efc 100644 --- a/osu.Game.Rulesets.Osu/OsuSkinComponents.cs +++ b/osu.Game.Rulesets.Osu/OsuSkinComponents.cs @@ -17,5 +17,6 @@ namespace osu.Game.Rulesets.Osu SliderFollowCircle, SliderBall, SliderBody, + SpinnerDisc } } diff --git a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs index 95ef2d58b1..c5b5598be7 100644 --- a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs @@ -4,6 +4,7 @@ using System; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; using osu.Game.Skinning; using osuTK; @@ -102,6 +103,14 @@ namespace osu.Game.Rulesets.Osu.Skinning Scale = new Vector2(0.8f), Spacing = new Vector2(-overlap, 0) }; + + case OsuSkinComponents.SpinnerDisc: + if (Source.GetTexture("spinner-background") != null) + return new Sprite { Texture = Source.GetTexture("spinner-circle") }; + else if (Source.GetTexture("spinner-top") != null) + return new Sprite { Texture = Source.GetTexture("spinner-top") }; + + return null; } return null; From e5ebd211569f8f0fe050142272c6e52ca64fb4b1 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 29 Jul 2020 16:25:17 +0900 Subject: [PATCH 0285/1782] Fix test scene and add pooling support --- .../Skinning/TestSceneHitExplosion.cs | 50 ++++++++++++------- 1 file changed, 33 insertions(+), 17 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneHitExplosion.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneHitExplosion.cs index a692c0b697..0c56f7bcf4 100644 --- a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneHitExplosion.cs +++ b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneHitExplosion.cs @@ -1,23 +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 System.Collections.Generic; using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; -using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Pooling; +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Mania.Judgements; using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces; using osu.Game.Rulesets.Mania.UI; -using osu.Game.Skinning; +using osu.Game.Rulesets.Objects; using osuTK; -using osuTK.Graphics; namespace osu.Game.Rulesets.Mania.Tests.Skinning { [TestFixture] public class TestSceneHitExplosion : ManiaSkinnableTestScene { + private readonly List> hitExplosionPools = new List>(); + public TestSceneHitExplosion() { int runcount = 0; @@ -29,28 +33,40 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning if (runcount % 15 > 12) return; - CreatedDrawables.OfType().ForEach(c => + int poolIndex = 0; + + foreach (var c in CreatedDrawables.OfType()) { - c.Add(new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.HitExplosion, 0), - _ => new DefaultHitExplosion((runcount / 15) % 2 == 0 ? new Color4(94, 0, 57, 255) : new Color4(6, 84, 0, 255), runcount % 6 != 0) - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - })); - }); + c.Add(hitExplosionPools[poolIndex].Get(e => + { + e.Apply(new JudgementResult(new HitObject(), runcount % 6 == 0 ? new HoldNoteTickJudgement() : new ManiaJudgement())); + + e.Anchor = Anchor.Centre; + e.Origin = Anchor.Centre; + })); + + poolIndex++; + } }, 100); } [BackgroundDependencyLoader] private void load() { - SetContents(() => new ColumnTestContainer(0, ManiaAction.Key1) + SetContents(() => { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativePositionAxes = Axes.Y, - Y = -0.25f, - Size = new Vector2(Column.COLUMN_WIDTH, DefaultNotePiece.NOTE_HEIGHT), + var pool = new DrawablePool(5); + hitExplosionPools.Add(pool); + + return new ColumnTestContainer(0, ManiaAction.Key1) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativePositionAxes = Axes.Y, + Y = -0.25f, + Size = new Vector2(Column.COLUMN_WIDTH, DefaultNotePiece.NOTE_HEIGHT), + Child = pool + }; }); } } From 5439099b7cb23253179c270b9f4ddc2002163ded Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 29 Jul 2020 10:34:09 +0300 Subject: [PATCH 0286/1782] Merge GlobalSkinConfiguration settings into the LegacySetting enum --- .../Gameplay/TestSceneHitObjectSamples.cs | 8 ++++---- .../Objects/Legacy/ConvertHitObjectParser.cs | 2 +- osu.Game/Skinning/GlobalSkinConfiguration.cs | 11 ----------- osu.Game/Skinning/LegacySkin.cs | 14 ++++---------- osu.Game/Skinning/LegacySkinConfiguration.cs | 2 ++ osu.Game/Skinning/LegacySkinExtensions.cs | 3 ++- osu.Game/Skinning/LegacySkinTransformer.cs | 3 ++- 7 files changed, 15 insertions(+), 28 deletions(-) delete mode 100644 osu.Game/Skinning/GlobalSkinConfiguration.cs diff --git a/osu.Game.Tests/Gameplay/TestSceneHitObjectSamples.cs b/osu.Game.Tests/Gameplay/TestSceneHitObjectSamples.cs index 737946e1e0..583400f579 100644 --- a/osu.Game.Tests/Gameplay/TestSceneHitObjectSamples.cs +++ b/osu.Game.Tests/Gameplay/TestSceneHitObjectSamples.cs @@ -6,9 +6,9 @@ using osu.Framework.IO.Stores; using osu.Framework.Testing; using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; -using osu.Game.Skinning; using osu.Game.Tests.Beatmaps; using osu.Game.Tests.Resources; +using static osu.Game.Skinning.LegacySkinConfiguration; namespace osu.Game.Tests.Gameplay { @@ -190,7 +190,7 @@ namespace osu.Game.Tests.Gameplay } ///

- /// Tests that when a custom sample bank is used, but is disabled, + /// Tests that when a custom sample bank is used, but is disabled, /// only the additional sound will be looked up. /// [Test] @@ -209,7 +209,7 @@ namespace osu.Game.Tests.Gameplay } /// - /// Tests that when a normal sample bank is used and is disabled, + /// Tests that when a normal sample bank is used and is disabled, /// the normal sound will be looked up anyway. /// [Test] @@ -226,6 +226,6 @@ namespace osu.Game.Tests.Gameplay } private void disableLayeredHitSounds() - => AddStep("set LayeredHitSounds to false", () => Skin.Configuration.ConfigDictionary[GlobalSkinConfiguration.LayeredHitSounds.ToString()] = "0"); + => AddStep("set LayeredHitSounds to false", () => Skin.Configuration.ConfigDictionary[LegacySetting.LayeredHitSounds.ToString()] = "0"); } } diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs index 77075b2abe..9afc0ecaf4 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs @@ -434,7 +434,7 @@ namespace osu.Game.Rulesets.Objects.Legacy ///
/// /// Layered hit samples are automatically added in all modes (except osu!mania), but can be disabled - /// using the skin config option. + /// using the skin config option. /// public bool IsLayered { get; set; } } diff --git a/osu.Game/Skinning/GlobalSkinConfiguration.cs b/osu.Game/Skinning/GlobalSkinConfiguration.cs deleted file mode 100644 index d405702ea5..0000000000 --- a/osu.Game/Skinning/GlobalSkinConfiguration.cs +++ /dev/null @@ -1,11 +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.Skinning -{ - public enum GlobalSkinConfiguration - { - AnimationFramerate, - LayeredHitSounds, - } -} diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 3bbeff9918..5843cde94d 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -120,15 +120,6 @@ namespace osu.Game.Skinning break; - case LegacySkinConfiguration.LegacySetting legacy: - switch (legacy) - { - case LegacySkinConfiguration.LegacySetting.Version: - return SkinUtils.As(new Bindable(Configuration.LegacyVersion ?? LegacySkinConfiguration.LATEST_VERSION)); - } - - break; - case SkinCustomColourLookup customColour: return SkinUtils.As(getCustomColour(Configuration, customColour.Lookup.ToString())); @@ -142,8 +133,11 @@ namespace osu.Game.Skinning break; + case LegacySkinConfiguration.LegacySetting s when s == LegacySkinConfiguration.LegacySetting.Version: + return SkinUtils.As(new Bindable(Configuration.LegacyVersion ?? LegacySkinConfiguration.LATEST_VERSION)); + default: - // handles lookups like GlobalSkinConfiguration + // handles lookups like some in LegacySkinConfiguration.LegacySetting try { diff --git a/osu.Game/Skinning/LegacySkinConfiguration.cs b/osu.Game/Skinning/LegacySkinConfiguration.cs index 027f5b8883..41b7aea34b 100644 --- a/osu.Game/Skinning/LegacySkinConfiguration.cs +++ b/osu.Game/Skinning/LegacySkinConfiguration.cs @@ -15,6 +15,8 @@ namespace osu.Game.Skinning public enum LegacySetting { Version, + AnimationFramerate, + LayeredHitSounds, } } } diff --git a/osu.Game/Skinning/LegacySkinExtensions.cs b/osu.Game/Skinning/LegacySkinExtensions.cs index 7cf41ef3c1..bb46dc8b9f 100644 --- a/osu.Game/Skinning/LegacySkinExtensions.cs +++ b/osu.Game/Skinning/LegacySkinExtensions.cs @@ -9,6 +9,7 @@ using osu.Framework.Graphics.Animations; using osu.Framework.Graphics.OpenGL.Textures; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; +using static osu.Game.Skinning.LegacySkinConfiguration; namespace osu.Game.Skinning { @@ -89,7 +90,7 @@ namespace osu.Game.Skinning { if (applyConfigFrameRate) { - var iniRate = source.GetConfig(GlobalSkinConfiguration.AnimationFramerate); + var iniRate = source.GetConfig(LegacySetting.AnimationFramerate); if (iniRate?.Value > 0) return 1000f / iniRate.Value; diff --git a/osu.Game/Skinning/LegacySkinTransformer.cs b/osu.Game/Skinning/LegacySkinTransformer.cs index 786056b932..ebc4757e75 100644 --- a/osu.Game/Skinning/LegacySkinTransformer.cs +++ b/osu.Game/Skinning/LegacySkinTransformer.cs @@ -8,6 +8,7 @@ using osu.Framework.Graphics.OpenGL.Textures; using osu.Framework.Graphics.Textures; using osu.Game.Audio; using osu.Game.Rulesets.Objects.Legacy; +using static osu.Game.Skinning.LegacySkinConfiguration; namespace osu.Game.Skinning { @@ -38,7 +39,7 @@ namespace osu.Game.Skinning if (!(sampleInfo is ConvertHitObjectParser.LegacyHitSampleInfo legacySample)) return Source.GetSample(sampleInfo); - var playLayeredHitSounds = GetConfig(GlobalSkinConfiguration.LayeredHitSounds); + var playLayeredHitSounds = GetConfig(LegacySetting.LayeredHitSounds); if (legacySample.IsLayered && playLayeredHitSounds?.Value == false) return new SampleChannelVirtual(); From e98154b43277b726e286216b139ab9923e7806da Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 29 Jul 2020 16:37:23 +0900 Subject: [PATCH 0287/1782] Add initial support for spinner background layer --- .../Objects/Drawables/DrawableSpinner.cs | 11 ++++++----- .../Objects/Drawables/Pieces/SpinnerDisc.cs | 2 +- .../Pieces/{SpinnerBackground.cs => SpinnerFill.cs} | 4 ++-- osu.Game.Rulesets.Osu/OsuSkinComponents.cs | 3 ++- .../Skinning/OsuLegacySkinTransformer.cs | 6 ++++++ 5 files changed, 17 insertions(+), 9 deletions(-) rename osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/{SpinnerBackground.cs => SpinnerFill.cs} (92%) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index ac0df0aef6..37601d9680 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -18,6 +18,7 @@ using osu.Game.Rulesets.Objects; using osu.Framework.Utils; using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Ranking; +using osu.Game.Skinning; namespace osu.Game.Rulesets.Osu.Objects.Drawables { @@ -34,7 +35,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables private readonly Container mainContainer; - public readonly SpinnerBackground Background; + public readonly SkinnableDrawable Background; private readonly Container circleContainer; private readonly CirclePiece circle; private readonly GlowPiece glow; @@ -96,7 +97,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables RelativeSizeAxes = Axes.Y, Children = new[] { - Background = new SpinnerBackground + Background = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SpinnerBackground), _ => new SpinnerFill { Disc = { @@ -104,7 +105,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables }, Anchor = Anchor.Centre, Origin = Anchor.Centre, - }, + }), Disc = new SpinnerDisc(Spinner) { Scale = Vector2.Zero, @@ -173,7 +174,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables normalColour = baseColour; completeColour = colours.YellowLight; - Background.AccentColour = normalColour; + if (Background.Drawable is IHasAccentColour accent) accent.AccentColour = normalColour; Ticks.AccentColour = normalColour; Disc.AccentColour = fillColour; @@ -328,7 +329,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { Disc.FadeAccent(colour, duration); - Background.FadeAccent(colour.Darken(1), duration); + (Background.Drawable as IHasAccentColour)?.FadeAccent(colour.Darken(1), duration); Ticks.FadeAccent(colour, duration); circle.FadeColour(colour, duration); diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs index 86588a8a9f..22a6fc391a 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs @@ -45,7 +45,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces Children = new Drawable[] { - background = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SpinnerDisc), _ => new SpinnerBackground { Alpha = idle_alpha }), + background = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SpinnerDisc), _ => new SpinnerFill { Alpha = idle_alpha }), }; } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerBackground.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerFill.cs similarity index 92% rename from osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerBackground.cs rename to osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerFill.cs index 944354abca..dbba1044ca 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerBackground.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerFill.cs @@ -10,7 +10,7 @@ using osuTK.Graphics; namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces { - public class SpinnerBackground : CircularContainer, IHasAccentColour + public class SpinnerFill : CircularContainer, IHasAccentColour { public readonly Box Disc; @@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces } } - public SpinnerBackground() + public SpinnerFill() { RelativeSizeAxes = Axes.Both; Masking = true; diff --git a/osu.Game.Rulesets.Osu/OsuSkinComponents.cs b/osu.Game.Rulesets.Osu/OsuSkinComponents.cs index e25b4a5efc..d72673f9ee 100644 --- a/osu.Game.Rulesets.Osu/OsuSkinComponents.cs +++ b/osu.Game.Rulesets.Osu/OsuSkinComponents.cs @@ -17,6 +17,7 @@ namespace osu.Game.Rulesets.Osu SliderFollowCircle, SliderBall, SliderBody, - SpinnerDisc + SpinnerDisc, + SpinnerBackground } } diff --git a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs index c5b5598be7..28eef603f4 100644 --- a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs @@ -111,6 +111,12 @@ namespace osu.Game.Rulesets.Osu.Skinning return new Sprite { Texture = Source.GetTexture("spinner-top") }; return null; + + case OsuSkinComponents.SpinnerBackground: + if (Source.GetTexture("spinner-background") != null) + return new Sprite { Texture = Source.GetTexture("spinner-background") }; + + return null; } return null; From 5df406a0352285c871ed8606e54fae1c03e07a11 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 29 Jul 2020 16:41:10 +0900 Subject: [PATCH 0288/1782] Add pooling for mania judgements --- .../UI/DrawableManiaJudgement.cs | 4 ++++ osu.Game.Rulesets.Mania/UI/Stage.cs | 16 ++++++++++------ 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Mania/UI/DrawableManiaJudgement.cs b/osu.Game.Rulesets.Mania/UI/DrawableManiaJudgement.cs index 8797f014df..d99f6cb8d3 100644 --- a/osu.Game.Rulesets.Mania/UI/DrawableManiaJudgement.cs +++ b/osu.Game.Rulesets.Mania/UI/DrawableManiaJudgement.cs @@ -15,6 +15,10 @@ namespace osu.Game.Rulesets.Mania.UI { } + public DrawableManiaJudgement() + { + } + [BackgroundDependencyLoader] private void load() { diff --git a/osu.Game.Rulesets.Mania/UI/Stage.cs b/osu.Game.Rulesets.Mania/UI/Stage.cs index faa04dea97..36780b0f80 100644 --- a/osu.Game.Rulesets.Mania/UI/Stage.cs +++ b/osu.Game.Rulesets.Mania/UI/Stage.cs @@ -6,6 +6,7 @@ using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Pooling; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Objects; @@ -33,8 +34,8 @@ namespace osu.Game.Rulesets.Mania.UI public IReadOnlyList Columns => columnFlow.Children; private readonly FillFlowContainer columnFlow; - public Container Judgements => judgements; private readonly JudgementContainer judgements; + private readonly DrawablePool judgementPool; private readonly Drawable barLineContainer; private readonly Container topLevelContainer; @@ -63,6 +64,7 @@ namespace osu.Game.Rulesets.Mania.UI InternalChildren = new Drawable[] { + judgementPool = new DrawablePool(2), new Container { Anchor = Anchor.TopCentre, @@ -208,12 +210,14 @@ namespace osu.Game.Rulesets.Mania.UI if (!judgedObject.DisplayResult || !DisplayJudgements.Value) return; - judgements.Clear(); - judgements.Add(new DrawableManiaJudgement(result, judgedObject) + judgements.Clear(false); + judgements.Add(judgementPool.Get(j => { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - }); + j.Apply(result, judgedObject); + + j.Anchor = Anchor.Centre; + j.Origin = Anchor.Centre; + })); } protected override void Update() From 1c00cf95d5f7cb838bd72f2dbf09fd4a845c1422 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 29 Jul 2020 16:55:42 +0900 Subject: [PATCH 0289/1782] Add initial support for spinner middle skinning --- .../Objects/Drawables/DrawableSpinner.cs | 35 ++++++++++++------- osu.Game.Rulesets.Osu/OsuSkinComponents.cs | 3 +- .../Skinning/OsuLegacySkinTransformer.cs | 8 +++++ 3 files changed, 32 insertions(+), 14 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index 37601d9680..d6914c7a69 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -13,6 +13,7 @@ using osu.Game.Graphics; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.Sprites; using osu.Game.Rulesets.Objects; using osu.Framework.Utils; @@ -36,11 +37,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables private readonly Container mainContainer; public readonly SkinnableDrawable Background; - private readonly Container circleContainer; - private readonly CirclePiece circle; - private readonly GlowPiece glow; + private readonly SkinnableDrawable circleContainer; + private CirclePiece circle; + private GlowPiece glow; - private readonly SpriteIcon symbol; + private SpriteIcon symbol; private readonly Color4 baseColour = Color4Extensions.FromHex(@"002c3c"); private readonly Color4 fillColour = Color4Extensions.FromHex(@"005b7c"); @@ -66,7 +67,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables InternalChildren = new Drawable[] { ticks = new Container(), - circleContainer = new Container + circleContainer = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SpinnerCentre), _ => new Container { AutoSizeAxes = Axes.Both, Anchor = Anchor.Centre, @@ -89,6 +90,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables Shadow = false, }, } + }) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, }, mainContainer = new AspectContainer { @@ -175,11 +180,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables completeColour = colours.YellowLight; if (Background.Drawable is IHasAccentColour accent) accent.AccentColour = normalColour; + Ticks.AccentColour = normalColour; Disc.AccentColour = fillColour; - circle.Colour = colours.BlueDark; - glow.Colour = colours.BlueDark; + if (circle != null) circle.Colour = colours.BlueDark; + if (glow != null) glow.Colour = colours.BlueDark; positionBindable.BindValueChanged(pos => Position = pos.NewValue); positionBindable.BindTo(HitObject.PositionBindable); @@ -224,6 +230,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables Disc.Tracking = OsuActionInputManager?.PressedActions.Any(x => x == OsuAction.LeftButton || x == OsuAction.RightButton) ?? false; } + private float relativeHeight => ToScreenSpace(new RectangleF(0, 0, OsuHitObject.OBJECT_RADIUS, OsuHitObject.OBJECT_RADIUS)).Height / mainContainer.DrawHeight; + protected override void UpdateAfterChildren() { base.UpdateAfterChildren(); @@ -231,18 +239,19 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables if (!SpmCounter.IsPresent && Disc.Tracking) SpmCounter.FadeIn(HitObject.TimeFadeIn); - circle.Rotation = Disc.Rotation; + if (circle != null) circle.Rotation = Disc.Rotation; Ticks.Rotation = Disc.Rotation; SpmCounter.SetRotation(Disc.CumulativeRotation); updateBonusScore(); - float relativeCircleScale = Spinner.Scale * circle.DrawHeight / mainContainer.DrawHeight; + float relativeCircleScale = Spinner.Scale * relativeHeight; float targetScale = relativeCircleScale + (1 - relativeCircleScale) * Progress; Disc.Scale = new Vector2((float)Interpolation.Lerp(Disc.Scale.X, targetScale, Math.Clamp(Math.Abs(Time.Elapsed) / 100, 0, 1))); - symbol.Rotation = (float)Interpolation.Lerp(symbol.Rotation, Disc.Rotation / 2, Math.Clamp(Math.Abs(Time.Elapsed) / 40, 0, 1)); + if (symbol != null) + symbol.Rotation = (float)Interpolation.Lerp(symbol.Rotation, Disc.Rotation / 2, Math.Clamp(Math.Abs(Time.Elapsed) / 40, 0, 1)); } private int wholeSpins; @@ -291,7 +300,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables circleContainer.ScaleTo(phaseOneScale, HitObject.TimePreempt / 4, Easing.OutQuint); mainContainer - .ScaleTo(phaseOneScale * circle.DrawHeight / DrawHeight * 1.6f, HitObject.TimePreempt / 4, Easing.OutQuint) + .ScaleTo(phaseOneScale * relativeHeight * 1.6f, HitObject.TimePreempt / 4, Easing.OutQuint) .RotateTo((float)(25 * Spinner.Duration / 2000), HitObject.TimePreempt + Spinner.Duration); using (BeginDelayedSequence(HitObject.TimePreempt / 2, true)) @@ -332,8 +341,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables (Background.Drawable as IHasAccentColour)?.FadeAccent(colour.Darken(1), duration); Ticks.FadeAccent(colour, duration); - circle.FadeColour(colour, duration); - glow.FadeColour(colour, duration); + circle?.FadeColour(colour, duration); + glow?.FadeColour(colour, duration); } } } diff --git a/osu.Game.Rulesets.Osu/OsuSkinComponents.cs b/osu.Game.Rulesets.Osu/OsuSkinComponents.cs index d72673f9ee..c05dbf6b16 100644 --- a/osu.Game.Rulesets.Osu/OsuSkinComponents.cs +++ b/osu.Game.Rulesets.Osu/OsuSkinComponents.cs @@ -18,6 +18,7 @@ namespace osu.Game.Rulesets.Osu SliderBall, SliderBody, SpinnerDisc, - SpinnerBackground + SpinnerBackground, + SpinnerCentre } } diff --git a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs index 28eef603f4..a5198d3448 100644 --- a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs @@ -117,6 +117,14 @@ namespace osu.Game.Rulesets.Osu.Skinning return new Sprite { Texture = Source.GetTexture("spinner-background") }; return null; + + case OsuSkinComponents.SpinnerCentre: + if (Source.GetTexture("spinner-background") != null) + return Drawable.Empty(); + else if (Source.GetTexture("spinner-top") != null) + return new Sprite { Texture = Source.GetTexture("spinner-middle2") }; + + return null; } return null; From 2cd6e89cb05ba3cea8854dad77c614c4fd95cb63 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 29 Jul 2020 18:02:12 +0900 Subject: [PATCH 0290/1782] Move default centre implementation out of DrawableSpinner --- .../Objects/Drawables/DefaultSpinnerCentre.cs | 83 +++++++++++++++++++ .../Objects/Drawables/DrawableSpinner.cs | 40 +-------- 2 files changed, 84 insertions(+), 39 deletions(-) create mode 100644 osu.Game.Rulesets.Osu/Objects/Drawables/DefaultSpinnerCentre.cs diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DefaultSpinnerCentre.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DefaultSpinnerCentre.cs new file mode 100644 index 0000000000..d07829fbac --- /dev/null +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DefaultSpinnerCentre.cs @@ -0,0 +1,83 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Utils; +using osu.Game.Graphics; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Rulesets.Osu.Objects.Drawables +{ + public class DefaultSpinnerCentre : CompositeDrawable + { + private DrawableSpinner spinner; + + private CirclePiece circle; + private GlowPiece glow; + private SpriteIcon symbol; + + [BackgroundDependencyLoader] + private void load(OsuColour colours, DrawableHitObject drawableHitObject) + { + spinner = (DrawableSpinner)drawableHitObject; + + AutoSizeAxes = Axes.Both; + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + InternalChildren = new Drawable[] + { + glow = new GlowPiece(), + circle = new CirclePiece + { + Position = Vector2.Zero, + Anchor = Anchor.Centre, + }, + new RingPiece(), + symbol = new SpriteIcon + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(48), + Icon = FontAwesome.Solid.Asterisk, + Shadow = false, + }, + }; + + drawableHitObject.State.BindValueChanged(val => + { + Color4 colour; + + switch (val.NewValue) + { + default: + colour = colours.BlueDark; + break; + + case ArmedState.Hit: + colour = colours.YellowLight; + break; + } + + circle.FadeColour(colour, 200); + glow.FadeColour(colour, 200); + }, true); + + FinishTransforms(true); + } + + protected override void Update() + { + base.Update(); + + circle.Rotation = spinner.Disc.Rotation; + symbol.Rotation = (float)Interpolation.Lerp(symbol.Rotation, spinner.Disc.Rotation / 2, Math.Clamp(Math.Abs(Time.Elapsed) / 40, 0, 1)); + } + } +} diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index d6914c7a69..b2844c79a2 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -14,7 +14,6 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics.Primitives; -using osu.Framework.Graphics.Sprites; using osu.Game.Rulesets.Objects; using osu.Framework.Utils; using osu.Game.Rulesets.Scoring; @@ -38,10 +37,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables public readonly SkinnableDrawable Background; private readonly SkinnableDrawable circleContainer; - private CirclePiece circle; - private GlowPiece glow; - - private SpriteIcon symbol; private readonly Color4 baseColour = Color4Extensions.FromHex(@"002c3c"); private readonly Color4 fillColour = Color4Extensions.FromHex(@"005b7c"); @@ -67,30 +62,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables InternalChildren = new Drawable[] { ticks = new Container(), - circleContainer = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SpinnerCentre), _ => new Container - { - AutoSizeAxes = Axes.Both, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Children = new Drawable[] - { - glow = new GlowPiece(), - circle = new CirclePiece - { - Position = Vector2.Zero, - Anchor = Anchor.Centre, - }, - new RingPiece(), - symbol = new SpriteIcon - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(48), - Icon = FontAwesome.Solid.Asterisk, - Shadow = false, - }, - } - }) + circleContainer = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SpinnerCentre), _ => new DefaultSpinnerCentre()) { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -184,9 +156,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables Ticks.AccentColour = normalColour; Disc.AccentColour = fillColour; - if (circle != null) circle.Colour = colours.BlueDark; - if (glow != null) glow.Colour = colours.BlueDark; - positionBindable.BindValueChanged(pos => Position = pos.NewValue); positionBindable.BindTo(HitObject.PositionBindable); } @@ -239,7 +208,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables if (!SpmCounter.IsPresent && Disc.Tracking) SpmCounter.FadeIn(HitObject.TimeFadeIn); - if (circle != null) circle.Rotation = Disc.Rotation; Ticks.Rotation = Disc.Rotation; SpmCounter.SetRotation(Disc.CumulativeRotation); @@ -249,9 +217,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables float relativeCircleScale = Spinner.Scale * relativeHeight; float targetScale = relativeCircleScale + (1 - relativeCircleScale) * Progress; Disc.Scale = new Vector2((float)Interpolation.Lerp(Disc.Scale.X, targetScale, Math.Clamp(Math.Abs(Time.Elapsed) / 100, 0, 1))); - - if (symbol != null) - symbol.Rotation = (float)Interpolation.Lerp(symbol.Rotation, Disc.Rotation / 2, Math.Clamp(Math.Abs(Time.Elapsed) / 40, 0, 1)); } private int wholeSpins; @@ -340,9 +305,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables (Background.Drawable as IHasAccentColour)?.FadeAccent(colour.Darken(1), duration); Ticks.FadeAccent(colour, duration); - - circle?.FadeColour(colour, duration); - glow?.FadeColour(colour, duration); } } } From 2a5e9fed4d3f8d3a1cb3ea44f34e30a739459df2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 29 Jul 2020 18:15:19 +0900 Subject: [PATCH 0291/1782] Move default background implementation out of DrawableSpinner --- .../Drawables/DefaultSpinnerBackground.cs | 44 +++++++++++++++++++ .../Objects/Drawables/DrawableSpinner.cs | 14 +----- 2 files changed, 45 insertions(+), 13 deletions(-) create mode 100644 osu.Game.Rulesets.Osu/Objects/Drawables/DefaultSpinnerBackground.cs diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DefaultSpinnerBackground.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DefaultSpinnerBackground.cs new file mode 100644 index 0000000000..be864b8c16 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DefaultSpinnerBackground.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.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Game.Graphics; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces; +using osuTK.Graphics; + +namespace osu.Game.Rulesets.Osu.Objects.Drawables +{ + public class DefaultSpinnerBackground : SpinnerFill + { + [BackgroundDependencyLoader] + private void load(OsuColour colours, DrawableHitObject drawableHitObject) + { + Disc.Alpha = 0; + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + + drawableHitObject.State.BindValueChanged(val => + { + Color4 colour; + + switch (val.NewValue) + { + default: + colour = colours.BlueDark; + break; + + case ArmedState.Hit: + colour = colours.YellowLight; + break; + } + + this.FadeAccent(colour.Darken(1), 200); + }, true); + + FinishTransforms(true); + } + } +} diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index b2844c79a2..a0c2a06ff8 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -74,15 +74,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables RelativeSizeAxes = Axes.Y, Children = new[] { - Background = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SpinnerBackground), _ => new SpinnerFill - { - Disc = - { - Alpha = 0f, - }, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - }), + Background = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SpinnerBackground), _ => new DefaultSpinnerBackground()), Disc = new SpinnerDisc(Spinner) { Scale = Vector2.Zero, @@ -151,8 +143,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables normalColour = baseColour; completeColour = colours.YellowLight; - if (Background.Drawable is IHasAccentColour accent) accent.AccentColour = normalColour; - Ticks.AccentColour = normalColour; Disc.AccentColour = fillColour; @@ -302,8 +292,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables private void transformFillColour(Colour4 colour, double duration) { Disc.FadeAccent(colour, duration); - - (Background.Drawable as IHasAccentColour)?.FadeAccent(colour.Darken(1), duration); Ticks.FadeAccent(colour, duration); } } From 4c00c11541c18a07d32e2332bfce515bce4d5c8c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 29 Jul 2020 20:53:14 +0900 Subject: [PATCH 0292/1782] Remove unnecessary change --- .../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 e6dd9f5084..b9d95a6ba6 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs @@ -72,7 +72,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 c1a4f2e6afd823b9e36c73f76f16ea4445da0a88 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 29 Jul 2020 20:53:50 +0900 Subject: [PATCH 0293/1782] Update expected SR in test --- .../TaikoDifficultyCalculatorTest.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Taiko.Tests/TaikoDifficultyCalculatorTest.cs b/osu.Game.Rulesets.Taiko.Tests/TaikoDifficultyCalculatorTest.cs index e7b6d8615b..2d51e82bc4 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TaikoDifficultyCalculatorTest.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TaikoDifficultyCalculatorTest.cs @@ -13,8 +13,8 @@ namespace osu.Game.Rulesets.Taiko.Tests { protected override string ResourceAssembly => "osu.Game.Rulesets.Taiko"; - [TestCase(2.9811338051242915d, "diffcalc-test")] - [TestCase(2.9811338051242915d, "diffcalc-test-strong")] + [TestCase(2.2905937546434592d, "diffcalc-test")] + [TestCase(2.2905937546434592d, "diffcalc-test-strong")] public void Test(double expected, string name) => base.Test(expected, name); From 023feaf438ca028ca0ee44d27d38d86408ed4c29 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 29 Jul 2020 20:01:01 +0900 Subject: [PATCH 0294/1782] Refactor to centralise implementation into a single component Turns out this is a better way forward. --- .../TestSceneSpinner.cs | 2 +- .../TestSceneSpinnerRotation.cs | 22 +-- osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs | 4 +- osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs | 4 +- osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs | 3 +- .../Drawables/DefaultSpinnerBackground.cs | 44 ----- .../Objects/Drawables/DrawableSpinner.cs | 144 +++------------ .../Drawables/Pieces/DefaultSpinnerDisc.cs | 170 ++++++++++++++++++ .../Objects/Drawables/Pieces/SpinnerFill.cs | 3 + ...innerDisc.cs => SpinnerRotationTracker.cs} | 85 ++------- .../Drawables/SpinnerBackgroundLayer.cs | 22 +++ ...SpinnerCentre.cs => SpinnerCentreLayer.cs} | 41 ++--- 12 files changed, 265 insertions(+), 279 deletions(-) delete mode 100644 osu.Game.Rulesets.Osu/Objects/Drawables/DefaultSpinnerBackground.cs create mode 100644 osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/DefaultSpinnerDisc.cs rename osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/{SpinnerDisc.cs => SpinnerRotationTracker.cs} (59%) create mode 100644 osu.Game.Rulesets.Osu/Objects/Drawables/SpinnerBackgroundLayer.cs rename osu.Game.Rulesets.Osu/Objects/Drawables/{DefaultSpinnerCentre.cs => SpinnerCentreLayer.cs} (67%) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinner.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinner.cs index 8e3a22bfdc..65b338882e 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinner.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinner.cs @@ -61,7 +61,7 @@ namespace osu.Game.Rulesets.Osu.Tests { base.Update(); if (auto) - Disc.Rotate((float)(Clock.ElapsedFrameTime * 3)); + RotationTracker.AddRotation((float)(Clock.ElapsedFrameTime * 3)); } } } diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs index c36bec391f..319d326a01 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs @@ -61,12 +61,12 @@ namespace osu.Game.Rulesets.Osu.Tests public void TestSpinnerRewindingRotation() { addSeekStep(5000); - AddAssert("is disc rotation not almost 0", () => !Precision.AlmostEquals(drawableSpinner.Disc.Rotation, 0, 100)); - AddAssert("is disc rotation absolute not almost 0", () => !Precision.AlmostEquals(drawableSpinner.Disc.CumulativeRotation, 0, 100)); + AddAssert("is disc rotation not almost 0", () => !Precision.AlmostEquals(drawableSpinner.RotationTracker.Rotation, 0, 100)); + AddAssert("is disc rotation absolute not almost 0", () => !Precision.AlmostEquals(drawableSpinner.RotationTracker.CumulativeRotation, 0, 100)); addSeekStep(0); - AddAssert("is disc rotation almost 0", () => Precision.AlmostEquals(drawableSpinner.Disc.Rotation, 0, 100)); - AddAssert("is disc rotation absolute almost 0", () => Precision.AlmostEquals(drawableSpinner.Disc.CumulativeRotation, 0, 100)); + AddAssert("is disc rotation almost 0", () => Precision.AlmostEquals(drawableSpinner.RotationTracker.Rotation, 0, 100)); + AddAssert("is disc rotation absolute almost 0", () => Precision.AlmostEquals(drawableSpinner.RotationTracker.CumulativeRotation, 0, 100)); } [Test] @@ -75,24 +75,24 @@ namespace osu.Game.Rulesets.Osu.Tests double finalAbsoluteDiscRotation = 0, finalRelativeDiscRotation = 0, finalSpinnerSymbolRotation = 0; addSeekStep(5000); - AddStep("retrieve disc relative rotation", () => finalRelativeDiscRotation = drawableSpinner.Disc.Rotation); - AddStep("retrieve disc absolute rotation", () => finalAbsoluteDiscRotation = drawableSpinner.Disc.CumulativeRotation); + AddStep("retrieve disc relative rotation", () => finalRelativeDiscRotation = drawableSpinner.RotationTracker.Rotation); + AddStep("retrieve disc absolute rotation", () => finalAbsoluteDiscRotation = drawableSpinner.RotationTracker.CumulativeRotation); AddStep("retrieve spinner symbol rotation", () => finalSpinnerSymbolRotation = spinnerSymbol.Rotation); addSeekStep(2500); AddUntilStep("disc rotation rewound", // we want to make sure that the rotation at time 2500 is in the same direction as at time 5000, but about half-way in. - () => Precision.AlmostEquals(drawableSpinner.Disc.Rotation, finalRelativeDiscRotation / 2, 100)); + () => Precision.AlmostEquals(drawableSpinner.RotationTracker.Rotation, finalRelativeDiscRotation / 2, 100)); AddUntilStep("symbol rotation rewound", () => Precision.AlmostEquals(spinnerSymbol.Rotation, finalSpinnerSymbolRotation / 2, 100)); addSeekStep(5000); AddAssert("is disc rotation almost same", - () => Precision.AlmostEquals(drawableSpinner.Disc.Rotation, finalRelativeDiscRotation, 100)); + () => Precision.AlmostEquals(drawableSpinner.RotationTracker.Rotation, finalRelativeDiscRotation, 100)); AddAssert("is symbol rotation almost same", () => Precision.AlmostEquals(spinnerSymbol.Rotation, finalSpinnerSymbolRotation, 100)); AddAssert("is disc rotation absolute almost same", - () => Precision.AlmostEquals(drawableSpinner.Disc.CumulativeRotation, finalAbsoluteDiscRotation, 100)); + () => Precision.AlmostEquals(drawableSpinner.RotationTracker.CumulativeRotation, finalAbsoluteDiscRotation, 100)); } [Test] @@ -115,7 +115,7 @@ namespace osu.Game.Rulesets.Osu.Tests addSeekStep(5000); - AddAssert("disc spin direction correct", () => clockwise ? drawableSpinner.Disc.Rotation > 0 : drawableSpinner.Disc.Rotation < 0); + AddAssert("disc spin direction correct", () => clockwise ? drawableSpinner.RotationTracker.Rotation > 0 : drawableSpinner.RotationTracker.Rotation < 0); AddAssert("spinner symbol direction correct", () => clockwise ? spinnerSymbol.Rotation > 0 : spinnerSymbol.Rotation < 0); } @@ -142,7 +142,7 @@ namespace osu.Game.Rulesets.Osu.Tests { // multipled by 2 to nullify the score multiplier. (autoplay mod selected) var totalScore = ((ScoreExposedPlayer)Player).ScoreProcessor.TotalScore.Value * 2; - return totalScore == (int)(drawableSpinner.Disc.CumulativeRotation / 360) * SpinnerTick.SCORE_PER_TICK; + return totalScore == (int)(drawableSpinner.RotationTracker.CumulativeRotation / 360) * SpinnerTick.SCORE_PER_TICK; }); addSeekStep(0); diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs index fdba03f260..08fd13915d 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs @@ -82,9 +82,7 @@ namespace osu.Game.Rulesets.Osu.Mods case DrawableSpinner spinner: // hide elements we don't care about. - spinner.Disc.Hide(); - spinner.Ticks.Hide(); - spinner.Background.Hide(); + // todo: hide background using (spinner.BeginAbsoluteSequence(fadeOutStartTime + longFadeDuration, true)) spinner.FadeOut(fadeOutDuration); diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs b/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs index 7b54baa99b..47d765fecd 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs @@ -40,8 +40,8 @@ namespace osu.Game.Rulesets.Osu.Mods { var spinner = (DrawableSpinner)drawable; - spinner.Disc.Tracking = true; - spinner.Disc.Rotate(MathUtils.RadiansToDegrees((float)spinner.Clock.ElapsedFrameTime * 0.03f)); + spinner.RotationTracker.Tracking = true; + spinner.RotationTracker.AddRotation(MathUtils.RadiansToDegrees((float)spinner.Clock.ElapsedFrameTime * 0.03f)); } } } diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs index 774f9cf58b..f209b315af 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs @@ -58,8 +58,7 @@ namespace osu.Game.Rulesets.Osu.Mods break; case DrawableSpinner spinner: - spinner.Disc.Hide(); - spinner.Background.Hide(); + //todo: hide background break; } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DefaultSpinnerBackground.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DefaultSpinnerBackground.cs deleted file mode 100644 index be864b8c16..0000000000 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DefaultSpinnerBackground.cs +++ /dev/null @@ -1,44 +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.Extensions.Color4Extensions; -using osu.Framework.Graphics; -using osu.Game.Graphics; -using osu.Game.Rulesets.Objects.Drawables; -using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces; -using osuTK.Graphics; - -namespace osu.Game.Rulesets.Osu.Objects.Drawables -{ - public class DefaultSpinnerBackground : SpinnerFill - { - [BackgroundDependencyLoader] - private void load(OsuColour colours, DrawableHitObject drawableHitObject) - { - Disc.Alpha = 0; - Anchor = Anchor.Centre; - Origin = Anchor.Centre; - - drawableHitObject.State.BindValueChanged(val => - { - Color4 colour; - - switch (val.NewValue) - { - default: - colour = colours.BlueDark; - break; - - case ArmedState.Hit: - colour = colours.YellowLight; - break; - } - - this.FadeAccent(colour.Darken(1), 200); - }, true); - - FinishTransforms(true); - } - } -} diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index a0c2a06ff8..11f1afafba 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -3,22 +3,19 @@ using System; using System.Linq; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Game.Rulesets.Objects.Drawables; -using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces; -using osuTK; -using osuTK.Graphics; -using osu.Game.Graphics; -using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Primitives; +using osu.Game.Graphics; using osu.Game.Rulesets.Objects; -using osu.Framework.Utils; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces; using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Ranking; using osu.Game.Skinning; +using osuTK; namespace osu.Game.Rulesets.Osu.Objects.Drawables { @@ -28,24 +25,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables private readonly Container ticks; - public readonly SpinnerDisc Disc; - public readonly SpinnerTicks Ticks; + public readonly SpinnerRotationTracker RotationTracker; public readonly SpinnerSpmCounter SpmCounter; private readonly SpinnerBonusDisplay bonusDisplay; - private readonly Container mainContainer; - - public readonly SkinnableDrawable Background; - private readonly SkinnableDrawable circleContainer; - - private readonly Color4 baseColour = Color4Extensions.FromHex(@"002c3c"); - private readonly Color4 fillColour = Color4Extensions.FromHex(@"005b7c"); - private readonly IBindable positionBindable = new Bindable(); - private Color4 normalColour; - private Color4 completeColour; - public DrawableSpinner(Spinner s) : base(s) { @@ -62,27 +47,17 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables InternalChildren = new Drawable[] { ticks = new Container(), - circleContainer = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SpinnerCentre), _ => new DefaultSpinnerCentre()) - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - }, - mainContainer = new AspectContainer + RotationTracker = new SpinnerRotationTracker(Spinner), + new AspectContainer { Anchor = Anchor.Centre, Origin = Anchor.Centre, RelativeSizeAxes = Axes.Y, - Children = new[] + Scale = new Vector2(Spinner.Scale), + Children = new Drawable[] { - Background = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SpinnerBackground), _ => new DefaultSpinnerBackground()), - Disc = new SpinnerDisc(Spinner) - { - Scale = Vector2.Zero, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - }, - circleContainer.CreateProxy(), - Ticks = new SpinnerTicks + new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SpinnerDisc), _ => new DefaultSpinnerDisc()), + RotationTracker = new SpinnerRotationTracker(Spinner) { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -117,6 +92,14 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables } } + protected override void UpdateStateTransforms(ArmedState state) + { + base.UpdateStateTransforms(state); + + using (BeginDelayedSequence(Spinner.Duration, true)) + this.FadeOut(160); + } + protected override void ClearNestedHitObjects() { base.ClearNestedHitObjects(); @@ -140,27 +123,17 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables [BackgroundDependencyLoader] private void load(OsuColour colours) { - normalColour = baseColour; - completeColour = colours.YellowLight; - - Ticks.AccentColour = normalColour; - Disc.AccentColour = fillColour; - positionBindable.BindValueChanged(pos => Position = pos.NewValue); positionBindable.BindTo(HitObject.PositionBindable); } - public float Progress => Math.Clamp(Disc.CumulativeRotation / 360 / Spinner.SpinsRequired, 0, 1); + public float Progress => Math.Clamp(RotationTracker.CumulativeRotation / 360 / Spinner.SpinsRequired, 0, 1); protected override void CheckForResult(bool userTriggered, double timeOffset) { if (Time.Current < HitObject.StartTime) return; - if (Progress >= 1 && !Disc.Complete) - { - Disc.Complete = true; - transformFillColour(completeColour, 200); - } + RotationTracker.Complete.Value = Progress >= 1; if (userTriggered || Time.Current < Spinner.EndTime) return; @@ -186,27 +159,20 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { base.Update(); if (HandleUserInput) - Disc.Tracking = OsuActionInputManager?.PressedActions.Any(x => x == OsuAction.LeftButton || x == OsuAction.RightButton) ?? false; + RotationTracker.Tracking = OsuActionInputManager?.PressedActions.Any(x => x == OsuAction.LeftButton || x == OsuAction.RightButton) ?? false; } - private float relativeHeight => ToScreenSpace(new RectangleF(0, 0, OsuHitObject.OBJECT_RADIUS, OsuHitObject.OBJECT_RADIUS)).Height / mainContainer.DrawHeight; + public float RelativeHeight => ToScreenSpace(new RectangleF(0, 0, 0, OsuHitObject.OBJECT_RADIUS * 2)).Height / DrawHeight; protected override void UpdateAfterChildren() { base.UpdateAfterChildren(); - if (!SpmCounter.IsPresent && Disc.Tracking) + if (!SpmCounter.IsPresent && RotationTracker.Tracking) SpmCounter.FadeIn(HitObject.TimeFadeIn); - - Ticks.Rotation = Disc.Rotation; - - SpmCounter.SetRotation(Disc.CumulativeRotation); + SpmCounter.SetRotation(RotationTracker.CumulativeRotation); updateBonusScore(); - - float relativeCircleScale = Spinner.Scale * relativeHeight; - float targetScale = relativeCircleScale + (1 - relativeCircleScale) * Progress; - Disc.Scale = new Vector2((float)Interpolation.Lerp(Disc.Scale.X, targetScale, Math.Clamp(Math.Abs(Time.Elapsed) / 100, 0, 1))); } private int wholeSpins; @@ -216,7 +182,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables if (ticks.Count == 0) return; - int spins = (int)(Disc.CumulativeRotation / 360); + int spins = (int)(RotationTracker.CumulativeRotation / 360); if (spins < wholeSpins) { @@ -240,59 +206,5 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables wholeSpins++; } } - - protected override void UpdateInitialTransforms() - { - base.UpdateInitialTransforms(); - - circleContainer.ScaleTo(0); - mainContainer.ScaleTo(0); - - using (BeginDelayedSequence(HitObject.TimePreempt / 2, true)) - { - float phaseOneScale = Spinner.Scale * 0.7f; - - circleContainer.ScaleTo(phaseOneScale, HitObject.TimePreempt / 4, Easing.OutQuint); - - mainContainer - .ScaleTo(phaseOneScale * relativeHeight * 1.6f, HitObject.TimePreempt / 4, Easing.OutQuint) - .RotateTo((float)(25 * Spinner.Duration / 2000), HitObject.TimePreempt + Spinner.Duration); - - using (BeginDelayedSequence(HitObject.TimePreempt / 2, true)) - { - circleContainer.ScaleTo(Spinner.Scale, 400, Easing.OutQuint); - mainContainer.ScaleTo(1, 400, Easing.OutQuint); - } - } - } - - protected override void UpdateStateTransforms(ArmedState state) - { - base.UpdateStateTransforms(state); - - using (BeginDelayedSequence(Spinner.Duration, true)) - { - this.FadeOut(160); - - switch (state) - { - case ArmedState.Hit: - transformFillColour(completeColour, 0); - this.ScaleTo(Scale * 1.2f, 320, Easing.Out); - mainContainer.RotateTo(mainContainer.Rotation + 180, 320); - break; - - case ArmedState.Miss: - this.ScaleTo(Scale * 0.8f, 320, Easing.In); - break; - } - } - } - - private void transformFillColour(Colour4 colour, double duration) - { - Disc.FadeAccent(colour, duration); - Ticks.FadeAccent(colour, duration); - } } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/DefaultSpinnerDisc.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/DefaultSpinnerDisc.cs new file mode 100644 index 0000000000..11cd73b995 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/DefaultSpinnerDisc.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 osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Utils; +using osu.Game.Graphics; +using osu.Game.Rulesets.Objects.Drawables; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces +{ + public class DefaultSpinnerDisc : CompositeDrawable + { + private DrawableSpinner drawableSpinner; + + private Spinner spinner; + + private const float idle_alpha = 0.2f; + private const float tracking_alpha = 0.4f; + + private Color4 normalColour; + private Color4 completeColour; + + private SpinnerTicks ticks; + + private int completeTick; + private SpinnerFill fill; + private Container mainContainer; + private SpinnerCentreLayer centre; + private SpinnerBackgroundLayer background; + + public DefaultSpinnerDisc() + { + RelativeSizeAxes = Axes.Both; + + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours, DrawableHitObject drawableHitObject) + { + drawableSpinner = (DrawableSpinner)drawableHitObject; + spinner = (Spinner)drawableSpinner.HitObject; + + normalColour = colours.BlueDark; + completeColour = colours.YellowLight; + + InternalChildren = new Drawable[] + { + mainContainer = new Container + { + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Children = new Drawable[] + { + background = new SpinnerBackgroundLayer(), + fill = new SpinnerFill + { + Alpha = idle_alpha, + AccentColour = normalColour + }, + ticks = new SpinnerTicks + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AccentColour = normalColour + }, + } + }, + centre = new SpinnerCentreLayer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + centre.ScaleTo(0); + mainContainer.ScaleTo(0); + this.ScaleTo(1); + + drawableSpinner.RotationTracker.Complete.BindValueChanged(complete => updateComplete(complete.NewValue, 200)); + drawableSpinner.State.BindValueChanged(updateStateTransforms, true); + } + + private void updateStateTransforms(ValueChangedEvent state) + { + using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimePreempt / 2, true)) + { + float phaseOneScale = spinner.Scale * 0.7f; + + centre.ScaleTo(phaseOneScale, spinner.TimePreempt / 4, Easing.OutQuint); + + mainContainer + .ScaleTo(phaseOneScale * drawableSpinner.RelativeHeight * 1.6f, spinner.TimePreempt / 4, Easing.OutQuint); + + this.RotateTo((float)(25 * spinner.Duration / 2000), spinner.TimePreempt + spinner.Duration); + + using (BeginDelayedSequence(spinner.TimePreempt / 2, true)) + { + centre.ScaleTo(spinner.Scale, spinner.TimePreempt / 2, Easing.OutQuint); + mainContainer.ScaleTo(1, spinner.TimePreempt / 2, Easing.OutQuint); + } + } + + // transforms we have from completing the spinner will be rolled back, so reapply immediately. + updateComplete(state.NewValue == ArmedState.Hit, 0); + + using (BeginDelayedSequence(spinner.Duration, true)) + { + switch (state.NewValue) + { + case ArmedState.Hit: + this.ScaleTo(Scale * 1.2f, 320, Easing.Out); + this.RotateTo(mainContainer.Rotation + 180, 320); + break; + + case ArmedState.Miss: + this.ScaleTo(Scale * 0.8f, 320, Easing.In); + break; + } + } + } + + private void updateComplete(bool complete, double duration) + { + var colour = complete ? completeColour : normalColour; + + ticks.FadeAccent(colour.Darken(1), duration); + fill.FadeAccent(colour.Darken(1), duration); + + background.FadeAccent(colour, duration); + centre.FadeAccent(colour, duration); + } + + private bool updateCompleteTick() => completeTick != (completeTick = (int)(drawableSpinner.RotationTracker.CumulativeRotation / 360)); + + protected override void Update() + { + base.Update(); + + if (drawableSpinner.RotationTracker.Complete.Value && updateCompleteTick()) + { + fill.FinishTransforms(false, nameof(Alpha)); + fill + .FadeTo(tracking_alpha + 0.2f, 60, Easing.OutExpo) + .Then() + .FadeTo(tracking_alpha, 250, Easing.OutQuint); + } + + float relativeCircleScale = spinner.Scale * drawableSpinner.RelativeHeight; + float targetScale = relativeCircleScale + (1 - relativeCircleScale) * drawableSpinner.Progress; + + fill.Scale = new Vector2((float)Interpolation.Lerp(fill.Scale.X, targetScale, Math.Clamp(Math.Abs(Time.Elapsed) / 100, 0, 1))); + mainContainer.Rotation = drawableSpinner.RotationTracker.Rotation; + } + } +} diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerFill.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerFill.cs index dbba1044ca..043bc5618c 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerFill.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerFill.cs @@ -36,6 +36,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces RelativeSizeAxes = Axes.Both; Masking = true; + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + Children = new Drawable[] { Disc = new Box diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerRotationTracker.cs similarity index 59% rename from osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs rename to osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerRotationTracker.cs index 22a6fc391a..968c2a6df5 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerRotationTracker.cs @@ -2,85 +2,33 @@ // See the LICENCE file in the repository root for full licence text. using System; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Events; -using osu.Game.Graphics; -using osuTK; -using osuTK.Graphics; using osu.Framework.Utils; -using osu.Game.Skinning; +using osuTK; namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces { - public class SpinnerDisc : CircularContainer, IHasAccentColour + public class SpinnerRotationTracker : CircularContainer { private readonly Spinner spinner; - private Color4 accentColour; - - public Color4 AccentColour - { - get => accentColour; - set - { - accentColour = value; - if (background.Drawable is IHasAccentColour accent) - accent.AccentColour = value; - } - } - - private readonly SkinnableDrawable background; - - private const float idle_alpha = 0.2f; - private const float tracking_alpha = 0.4f; - public override bool IsPresent => true; // handle input when hidden - public SpinnerDisc(Spinner s) + public SpinnerRotationTracker(Spinner s) { spinner = s; RelativeSizeAxes = Axes.Both; - - Children = new Drawable[] - { - background = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SpinnerDisc), _ => new SpinnerFill { Alpha = idle_alpha }), - }; } public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; - private bool tracking; + public bool Tracking { get; set; } - public bool Tracking - { - get => tracking; - set - { - if (value == tracking) return; - - tracking = value; - - // todo: new default only - background.Drawable.FadeTo(tracking ? tracking_alpha : idle_alpha, 100); - } - } - - private bool complete; - - public bool Complete - { - get => complete; - set - { - if (value == complete) return; - - complete = value; - - updateCompleteTick(); - } - } + public readonly BindableBool Complete = new BindableBool(); /// /// The total rotation performed on the spinner disc, disregarding the spin direction. @@ -93,7 +41,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces /// If the spinner is spun 360 degrees clockwise and then 360 degrees counter-clockwise, /// this property will return the value of 720 (as opposed to 0 for ). /// - public float CumulativeRotation; + public float CumulativeRotation { get; private set; } /// /// Whether currently in the correct time range to allow spinning. @@ -110,9 +58,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces private float lastAngle; private float currentRotation; - private int completeTick; - - private bool updateCompleteTick() => completeTick != (completeTick = (int)(CumulativeRotation / 360)); private bool rotationTransferred; @@ -123,21 +68,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces var delta = thisAngle - lastAngle; - if (tracking) - Rotate(delta); + if (Tracking) + AddRotation(delta); lastAngle = thisAngle; - if (Complete && updateCompleteTick()) - { - // todo: new default only - background.Drawable.FinishTransforms(false, nameof(Alpha)); - background.Drawable - .FadeTo(tracking_alpha + 0.2f, 60, Easing.OutExpo) - .Then() - .FadeTo(tracking_alpha, 250, Easing.OutQuint); - } - Rotation = (float)Interpolation.Lerp(Rotation, currentRotation / 2, Math.Clamp(Math.Abs(Time.Elapsed) / 40, 0, 1)); } @@ -148,7 +83,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces /// Will be a no-op if not a valid time to spin. /// /// The delta angle. - public void Rotate(float angle) + public void AddRotation(float angle) { if (!isSpinnableTime) return; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/SpinnerBackgroundLayer.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/SpinnerBackgroundLayer.cs new file mode 100644 index 0000000000..3cd2454706 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/SpinnerBackgroundLayer.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.Allocation; +using osu.Framework.Graphics; +using osu.Game.Graphics; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces; + +namespace osu.Game.Rulesets.Osu.Objects.Drawables +{ + public class SpinnerBackgroundLayer : SpinnerFill + { + [BackgroundDependencyLoader] + private void load(OsuColour colours, DrawableHitObject drawableHitObject) + { + Disc.Alpha = 0; + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + } + } +} diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DefaultSpinnerCentre.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/SpinnerCentreLayer.cs similarity index 67% rename from osu.Game.Rulesets.Osu/Objects/Drawables/DefaultSpinnerCentre.cs rename to osu.Game.Rulesets.Osu/Objects/Drawables/SpinnerCentreLayer.cs index d07829fbac..8803b92815 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DefaultSpinnerCentre.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/SpinnerCentreLayer.cs @@ -15,7 +15,7 @@ using osuTK.Graphics; namespace osu.Game.Rulesets.Osu.Objects.Drawables { - public class DefaultSpinnerCentre : CompositeDrawable + public class SpinnerCentreLayer : CompositeDrawable, IHasAccentColour { private DrawableSpinner spinner; @@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables private SpriteIcon symbol; [BackgroundDependencyLoader] - private void load(OsuColour colours, DrawableHitObject drawableHitObject) + private void load(DrawableHitObject drawableHitObject) { spinner = (DrawableSpinner)drawableHitObject; @@ -49,35 +49,26 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables Shadow = false, }, }; - - drawableHitObject.State.BindValueChanged(val => - { - Color4 colour; - - switch (val.NewValue) - { - default: - colour = colours.BlueDark; - break; - - case ArmedState.Hit: - colour = colours.YellowLight; - break; - } - - circle.FadeColour(colour, 200); - glow.FadeColour(colour, 200); - }, true); - - FinishTransforms(true); } protected override void Update() { base.Update(); + symbol.Rotation = (float)Interpolation.Lerp(symbol.Rotation, spinner.RotationTracker.Rotation / 2, Math.Clamp(Math.Abs(Time.Elapsed) / 40, 0, 1)); + } - circle.Rotation = spinner.Disc.Rotation; - symbol.Rotation = (float)Interpolation.Lerp(symbol.Rotation, spinner.Disc.Rotation / 2, Math.Clamp(Math.Abs(Time.Elapsed) / 40, 0, 1)); + private Color4 accentColour; + + public Color4 AccentColour + { + get => accentColour; + set + { + accentColour = value; + + circle.Colour = accentColour; + glow.Colour = accentColour; + } } } } From 2b71ffa2ed9a45ec87b38fcd63cb053f244f800e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 29 Jul 2020 22:31:18 +0900 Subject: [PATCH 0295/1782] Add back legacy implementations --- .../Objects/Drawables/DrawableSpinner.cs | 2 +- osu.Game.Rulesets.Osu/OsuSkinComponents.cs | 4 +- .../Skinning/LegacyNewStyleSpinner.cs | 80 +++++++++++++++++++ .../Skinning/LegacyOldStyleSpinner.cs | 64 +++++++++++++++ .../Skinning/OsuLegacySkinTransformer.cs | 25 ++---- 5 files changed, 151 insertions(+), 24 deletions(-) create mode 100644 osu.Game.Rulesets.Osu/Skinning/LegacyNewStyleSpinner.cs create mode 100644 osu.Game.Rulesets.Osu/Skinning/LegacyOldStyleSpinner.cs diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index 11f1afafba..f36ecd9dc4 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -56,7 +56,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables Scale = new Vector2(Spinner.Scale), Children = new Drawable[] { - new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SpinnerDisc), _ => new DefaultSpinnerDisc()), + new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SpinnerBody), _ => new DefaultSpinnerDisc()), RotationTracker = new SpinnerRotationTracker(Spinner) { Anchor = Anchor.Centre, diff --git a/osu.Game.Rulesets.Osu/OsuSkinComponents.cs b/osu.Game.Rulesets.Osu/OsuSkinComponents.cs index c05dbf6b16..5468764692 100644 --- a/osu.Game.Rulesets.Osu/OsuSkinComponents.cs +++ b/osu.Game.Rulesets.Osu/OsuSkinComponents.cs @@ -17,8 +17,6 @@ namespace osu.Game.Rulesets.Osu SliderFollowCircle, SliderBall, SliderBody, - SpinnerDisc, - SpinnerBackground, - SpinnerCentre + SpinnerBody } } diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacyNewStyleSpinner.cs b/osu.Game.Rulesets.Osu/Skinning/LegacyNewStyleSpinner.cs new file mode 100644 index 0000000000..65b8b979fc --- /dev/null +++ b/osu.Game.Rulesets.Osu/Skinning/LegacyNewStyleSpinner.cs @@ -0,0 +1,80 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Game.Skinning; + +namespace osu.Game.Rulesets.Osu.Skinning +{ + public class LegacyNewStyleSpinner : CompositeDrawable + { + private Sprite discBottom; + private Sprite discTop; + private Sprite spinningMiddle; + + private DrawableSpinner drawableSpinner; + + [BackgroundDependencyLoader] + private void load(ISkinSource source, DrawableHitObject drawableObject) + { + drawableSpinner = (DrawableSpinner)drawableObject; + + InternalChildren = new Drawable[] + { + discBottom = new Sprite + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Texture = source.GetTexture("spinner-bottom") + }, + discTop = new Sprite + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Texture = source.GetTexture("spinner-top") + }, + new Sprite + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Texture = source.GetTexture("spinner-middle") + }, + spinningMiddle = new Sprite + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Texture = source.GetTexture("spinner-middle2") + } + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + this.FadeOut(); + drawableSpinner.State.BindValueChanged(updateStateTransforms, true); + } + + private void updateStateTransforms(ValueChangedEvent state) + { + var spinner = drawableSpinner.HitObject; + + using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimePreempt / 2, true)) + this.FadeInFromZero(spinner.TimePreempt / 2); + } + + protected override void Update() + { + base.Update(); + spinningMiddle.Rotation = discTop.Rotation = drawableSpinner.RotationTracker.Rotation; + discBottom.Rotation = discTop.Rotation / 3; + } + } +} diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacyOldStyleSpinner.cs b/osu.Game.Rulesets.Osu/Skinning/LegacyOldStyleSpinner.cs new file mode 100644 index 0000000000..16d8664e66 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Skinning/LegacyOldStyleSpinner.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.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Game.Skinning; + +namespace osu.Game.Rulesets.Osu.Skinning +{ + public class LegacyOldStyleSpinner : CompositeDrawable + { + private DrawableSpinner drawableSpinner; + private Sprite disc; + + [BackgroundDependencyLoader] + private void load(ISkinSource source, DrawableHitObject drawableObject) + { + drawableSpinner = (DrawableSpinner)drawableObject; + + InternalChildren = new Drawable[] + { + new Sprite + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Texture = source.GetTexture("spinner-background") + }, + disc = new Sprite + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Texture = source.GetTexture("spinner-circle") + } + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + this.FadeOut(); + drawableSpinner.State.BindValueChanged(updateStateTransforms, true); + } + + private void updateStateTransforms(ValueChangedEvent state) + { + var spinner = drawableSpinner.HitObject; + + using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimePreempt / 2, true)) + this.FadeInFromZero(spinner.TimePreempt / 2); + } + + protected override void Update() + { + base.Update(); + disc.Rotation = drawableSpinner.RotationTracker.Rotation; + } + } +} diff --git a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs index a5198d3448..44f431d6f7 100644 --- a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs @@ -4,7 +4,6 @@ using System; using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Framework.Graphics.Sprites; using osu.Game.Skinning; using osuTK; @@ -104,25 +103,11 @@ namespace osu.Game.Rulesets.Osu.Skinning Spacing = new Vector2(-overlap, 0) }; - case OsuSkinComponents.SpinnerDisc: - if (Source.GetTexture("spinner-background") != null) - return new Sprite { Texture = Source.GetTexture("spinner-circle") }; - else if (Source.GetTexture("spinner-top") != null) - return new Sprite { Texture = Source.GetTexture("spinner-top") }; - - return null; - - case OsuSkinComponents.SpinnerBackground: - if (Source.GetTexture("spinner-background") != null) - return new Sprite { Texture = Source.GetTexture("spinner-background") }; - - return null; - - case OsuSkinComponents.SpinnerCentre: - if (Source.GetTexture("spinner-background") != null) - return Drawable.Empty(); - else if (Source.GetTexture("spinner-top") != null) - return new Sprite { Texture = Source.GetTexture("spinner-middle2") }; + case OsuSkinComponents.SpinnerBody: + if (Source.GetTexture("spinner-top") != null) + return new LegacyNewStyleSpinner(); + else if (Source.GetTexture("spinner-background") != null) + return new LegacyOldStyleSpinner(); return null; } From ca21f038e0c8bf8f2f2a619562d88af7feac50cb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 30 Jul 2020 10:35:48 +0900 Subject: [PATCH 0296/1782] Add xmldoc for legacy classes --- osu.Game.Rulesets.Osu/Skinning/LegacyNewStyleSpinner.cs | 4 ++++ osu.Game.Rulesets.Osu/Skinning/LegacyOldStyleSpinner.cs | 3 +++ 2 files changed, 7 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacyNewStyleSpinner.cs b/osu.Game.Rulesets.Osu/Skinning/LegacyNewStyleSpinner.cs index 65b8b979fc..2521bb1d76 100644 --- a/osu.Game.Rulesets.Osu/Skinning/LegacyNewStyleSpinner.cs +++ b/osu.Game.Rulesets.Osu/Skinning/LegacyNewStyleSpinner.cs @@ -12,6 +12,10 @@ using osu.Game.Skinning; namespace osu.Game.Rulesets.Osu.Skinning { + /// + /// Legacy skinned spinner with two main spinning layers, one fixed overlay and one final spinning overlay. + /// No background layer. + /// public class LegacyNewStyleSpinner : CompositeDrawable { private Sprite discBottom; diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacyOldStyleSpinner.cs b/osu.Game.Rulesets.Osu/Skinning/LegacyOldStyleSpinner.cs index 16d8664e66..e6a4064129 100644 --- a/osu.Game.Rulesets.Osu/Skinning/LegacyOldStyleSpinner.cs +++ b/osu.Game.Rulesets.Osu/Skinning/LegacyOldStyleSpinner.cs @@ -12,6 +12,9 @@ using osu.Game.Skinning; namespace osu.Game.Rulesets.Osu.Skinning { + /// + /// Legacy skinned spinner with one main spinning layer and a background layer. + /// public class LegacyOldStyleSpinner : CompositeDrawable { private DrawableSpinner drawableSpinner; From d4496eb9824ad8bbc14da66d00b14c037fc54c69 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Thu, 30 Jul 2020 04:51:09 +0300 Subject: [PATCH 0297/1782] Update ShowMoreButton in line with web --- .../Visual/Online/TestSceneShowMoreButton.cs | 20 ++--- .../Graphics/UserInterface/ShowMoreButton.cs | 84 ++++++++++++++----- .../Comments/Buttons/CommentRepliesButton.cs | 15 ++-- .../Comments/CommentsShowMoreButton.cs | 11 --- .../Profile/Sections/PaginatedContainer.cs | 5 +- .../Profile/Sections/ProfileShowMoreButton.cs | 19 ----- 6 files changed, 76 insertions(+), 78 deletions(-) delete mode 100644 osu.Game/Overlays/Profile/Sections/ProfileShowMoreButton.cs diff --git a/osu.Game.Tests/Visual/Online/TestSceneShowMoreButton.cs b/osu.Game.Tests/Visual/Online/TestSceneShowMoreButton.cs index 273f593c32..18ac415126 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneShowMoreButton.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneShowMoreButton.cs @@ -4,19 +4,22 @@ using osu.Framework.Graphics; using osu.Game.Graphics.UserInterface; using osu.Framework.Allocation; -using osu.Game.Graphics; +using osu.Game.Overlays; namespace osu.Game.Tests.Visual.Online { public class TestSceneShowMoreButton : OsuTestScene { + [Cached] + private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Green); + public TestSceneShowMoreButton() { - TestButton button = null; + ShowMoreButton button = null; int fireCount = 0; - Add(button = new TestButton + Add(button = new ShowMoreButton { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -46,16 +49,5 @@ 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; - } - } } } diff --git a/osu.Game/Graphics/UserInterface/ShowMoreButton.cs b/osu.Game/Graphics/UserInterface/ShowMoreButton.cs index c9cd9f1158..f4ab53d305 100644 --- a/osu.Game/Graphics/UserInterface/ShowMoreButton.cs +++ b/osu.Game/Graphics/UserInterface/ShowMoreButton.cs @@ -1,13 +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.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.Sprites; +using osu.Game.Overlays; using osuTK; -using osuTK.Graphics; using System.Collections.Generic; namespace osu.Game.Graphics.UserInterface @@ -16,14 +18,6 @@ namespace osu.Game.Graphics.UserInterface { private const int duration = 200; - private Color4 chevronIconColour; - - protected Color4 ChevronIconColour - { - get => chevronIconColour; - set => chevronIconColour = leftChevron.Colour = rightChevron.Colour = value; - } - public string Text { get => text.Text; @@ -32,22 +26,28 @@ namespace osu.Game.Graphics.UserInterface protected override IEnumerable EffectTargets => new[] { background }; - private ChevronIcon leftChevron; - private ChevronIcon rightChevron; + private ChevronIcon leftIcon; + private ChevronIcon rightIcon; private SpriteText text; private Box background; private FillFlowContainer textContainer; public ShowMoreButton() { - Height = 30; - Width = 140; + AutoSizeAxes = Axes.Both; + } + + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider) + { + IdleColour = colourProvider.Background2; + HoverColour = colourProvider.Background1; } protected override Drawable CreateContent() => new CircularContainer { Masking = true, - RelativeSizeAxes = Axes.Both, + AutoSizeAxes = Axes.Both, Children = new Drawable[] { background = new Box @@ -56,22 +56,36 @@ namespace osu.Game.Graphics.UserInterface }, textContainer = new FillFlowContainer { + AlwaysPresent = true, Anchor = Anchor.Centre, Origin = Anchor.Centre, AutoSizeAxes = Axes.Both, Direction = FillDirection.Horizontal, - Spacing = new Vector2(7), + Spacing = new Vector2(10), + Margin = new MarginPadding + { + Horizontal = 20, + Vertical = 5 + }, Children = new Drawable[] { - leftChevron = new ChevronIcon(), + leftIcon = new ChevronIcon + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, text = new OsuSpriteText { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Font = OsuFont.GetFont(size: 12, weight: FontWeight.Bold), + Font = OsuFont.GetFont(size: 12, weight: FontWeight.SemiBold), Text = "show more".ToUpper(), }, - rightChevron = new ChevronIcon(), + rightIcon = new ChevronIcon + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + } } } } @@ -81,17 +95,41 @@ namespace osu.Game.Graphics.UserInterface protected override void OnLoadFinished() => textContainer.FadeIn(duration, Easing.OutQuint); - private class ChevronIcon : SpriteIcon + protected override bool OnHover(HoverEvent e) { - private const int icon_size = 8; + base.OnHover(e); + leftIcon.FadeHoverColour(); + rightIcon.FadeHoverColour(); + return true; + } + + protected override void OnHoverLost(HoverLostEvent e) + { + base.OnHoverLost(e); + leftIcon.FadeIdleColour(); + rightIcon.FadeIdleColour(); + } + + public class ChevronIcon : SpriteIcon + { + [Resolved] + private OverlayColourProvider colourProvider { get; set; } public ChevronIcon() { - Anchor = Anchor.Centre; - Origin = Anchor.Centre; - Size = new Vector2(icon_size); + Size = new Vector2(7.5f); Icon = FontAwesome.Solid.ChevronDown; } + + [BackgroundDependencyLoader] + private void load() + { + Colour = colourProvider.Foreground1; + } + + public void FadeHoverColour() => this.FadeColour(colourProvider.Light1, 200, Easing.OutQuint); + + public void FadeIdleColour() => this.FadeColour(colourProvider.Foreground1, 200, Easing.OutQuint); } } } diff --git a/osu.Game/Overlays/Comments/Buttons/CommentRepliesButton.cs b/osu.Game/Overlays/Comments/Buttons/CommentRepliesButton.cs index 53438ca421..202f3ddd7b 100644 --- a/osu.Game/Overlays/Comments/Buttons/CommentRepliesButton.cs +++ b/osu.Game/Overlays/Comments/Buttons/CommentRepliesButton.cs @@ -5,12 +5,12 @@ 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.Sprites; using osu.Game.Graphics.UserInterface; using osuTK; +using static osu.Game.Graphics.UserInterface.ShowMoreButton; namespace osu.Game.Overlays.Comments.Buttons { @@ -25,7 +25,7 @@ namespace osu.Game.Overlays.Comments.Buttons [Resolved] private OverlayColourProvider colourProvider { get; set; } - private readonly SpriteIcon icon; + private readonly ChevronIcon icon; private readonly Box background; private readonly OsuSpriteText text; @@ -68,12 +68,10 @@ namespace osu.Game.Overlays.Comments.Buttons AlwaysPresent = true, Font = OsuFont.GetFont(size: 12, weight: FontWeight.SemiBold) }, - icon = new SpriteIcon + icon = new ChevronIcon { Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Size = new Vector2(7.5f), - Icon = FontAwesome.Solid.ChevronDown + Origin = Anchor.CentreLeft } } } @@ -88,7 +86,6 @@ namespace osu.Game.Overlays.Comments.Buttons private void load() { background.Colour = colourProvider.Background2; - icon.Colour = colourProvider.Foreground1; } protected void SetIconDirection(bool upwards) => icon.ScaleTo(new Vector2(1, upwards ? -1 : 1)); @@ -99,7 +96,7 @@ namespace osu.Game.Overlays.Comments.Buttons { base.OnHover(e); background.FadeColour(colourProvider.Background1, 200, Easing.OutQuint); - icon.FadeColour(colourProvider.Light1, 200, Easing.OutQuint); + icon.FadeHoverColour(); return true; } @@ -107,7 +104,7 @@ namespace osu.Game.Overlays.Comments.Buttons { base.OnHoverLost(e); background.FadeColour(colourProvider.Background2, 200, Easing.OutQuint); - icon.FadeColour(colourProvider.Foreground1, 200, Easing.OutQuint); + icon.FadeIdleColour(); } } } diff --git a/osu.Game/Overlays/Comments/CommentsShowMoreButton.cs b/osu.Game/Overlays/Comments/CommentsShowMoreButton.cs index d2ff7ecb1f..adf64eabb1 100644 --- a/osu.Game/Overlays/Comments/CommentsShowMoreButton.cs +++ b/osu.Game/Overlays/Comments/CommentsShowMoreButton.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.Bindables; using osu.Game.Graphics.UserInterface; @@ -11,16 +10,6 @@ namespace osu.Game.Overlays.Comments { public readonly BindableInt Current = new BindableInt(); - [BackgroundDependencyLoader] - private void load(OverlayColourProvider colourProvider) - { - Height = 20; - - IdleColour = colourProvider.Background2; - HoverColour = colourProvider.Background1; - ChevronIconColour = colourProvider.Foreground1; - } - protected override void LoadComplete() { Current.BindValueChanged(onCurrentChanged, true); diff --git a/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs b/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs index a30ff786fb..9720469548 100644 --- a/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs @@ -14,12 +14,13 @@ using osu.Game.Users; using System.Collections.Generic; using System.Linq; using System.Threading; +using osu.Game.Graphics.UserInterface; namespace osu.Game.Overlays.Profile.Sections { public abstract class PaginatedContainer : FillFlowContainer { - private readonly ProfileShowMoreButton moreButton; + private readonly ShowMoreButton moreButton; private readonly OsuSpriteText missingText; private APIRequest> retrievalRequest; private CancellationTokenSource loadCancellation; @@ -74,7 +75,7 @@ namespace osu.Game.Overlays.Profile.Sections RelativeSizeAxes = Axes.X, Spacing = new Vector2(0, 2), }, - moreButton = new ProfileShowMoreButton + moreButton = new ShowMoreButton { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, diff --git a/osu.Game/Overlays/Profile/Sections/ProfileShowMoreButton.cs b/osu.Game/Overlays/Profile/Sections/ProfileShowMoreButton.cs deleted file mode 100644 index 426ebeebe6..0000000000 --- a/osu.Game/Overlays/Profile/Sections/ProfileShowMoreButton.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.Allocation; -using osu.Game.Graphics.UserInterface; - -namespace osu.Game.Overlays.Profile.Sections -{ - public class ProfileShowMoreButton : ShowMoreButton - { - [BackgroundDependencyLoader] - private void load(OverlayColourProvider colourProvider) - { - IdleColour = colourProvider.Background2; - HoverColour = colourProvider.Background1; - ChevronIconColour = colourProvider.Foreground1; - } - } -} From 45ddc7a2e9f9d1b05404831c172ff479920031a5 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Thu, 30 Jul 2020 05:02:01 +0300 Subject: [PATCH 0298/1782] Rename ShowMoreButton in comments namespace to ShowMoreRepliesButton --- .../Buttons/{ShowMoreButton.cs => ShowMoreRepliesButton.cs} | 4 ++-- osu.Game/Overlays/Comments/DrawableComment.cs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) rename osu.Game/Overlays/Comments/Buttons/{ShowMoreButton.cs => ShowMoreRepliesButton.cs} (93%) diff --git a/osu.Game/Overlays/Comments/Buttons/ShowMoreButton.cs b/osu.Game/Overlays/Comments/Buttons/ShowMoreRepliesButton.cs similarity index 93% rename from osu.Game/Overlays/Comments/Buttons/ShowMoreButton.cs rename to osu.Game/Overlays/Comments/Buttons/ShowMoreRepliesButton.cs index 2c363564d2..c115a8bb8f 100644 --- a/osu.Game/Overlays/Comments/Buttons/ShowMoreButton.cs +++ b/osu.Game/Overlays/Comments/Buttons/ShowMoreRepliesButton.cs @@ -12,13 +12,13 @@ using osu.Framework.Allocation; namespace osu.Game.Overlays.Comments.Buttons { - public class ShowMoreButton : LoadingButton + public class ShowMoreRepliesButton : LoadingButton { protected override IEnumerable EffectTargets => new[] { text }; private OsuSpriteText text; - public ShowMoreButton() + public ShowMoreRepliesButton() { AutoSizeAxes = Axes.Both; LoadingAnimationSize = new Vector2(8); diff --git a/osu.Game/Overlays/Comments/DrawableComment.cs b/osu.Game/Overlays/Comments/DrawableComment.cs index 9c0a48ec29..31aa41e967 100644 --- a/osu.Game/Overlays/Comments/DrawableComment.cs +++ b/osu.Game/Overlays/Comments/DrawableComment.cs @@ -46,7 +46,7 @@ namespace osu.Game.Overlays.Comments private FillFlowContainer childCommentsVisibilityContainer; private FillFlowContainer childCommentsContainer; private LoadRepliesButton loadRepliesButton; - private ShowMoreButton showMoreButton; + private ShowMoreRepliesButton showMoreButton; private ShowRepliesButton showRepliesButton; private ChevronButton chevronButton; private DeletedCommentsCounter deletedCommentsCounter; @@ -213,7 +213,7 @@ namespace osu.Game.Overlays.Comments Top = 10 } }, - showMoreButton = new ShowMoreButton + showMoreButton = new ShowMoreRepliesButton { Action = () => RepliesRequested(this, ++currentPage) } From 64c7ae768641e22a51fffb0e2a828ebcba00eab9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 30 Jul 2020 11:25:49 +0900 Subject: [PATCH 0299/1782] Fix hit transforms not playing out correctly --- .../Objects/Drawables/Pieces/DefaultSpinnerDisc.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/DefaultSpinnerDisc.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/DefaultSpinnerDisc.cs index 11cd73b995..6a40069c5d 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/DefaultSpinnerDisc.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/DefaultSpinnerDisc.cs @@ -87,16 +87,16 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces { base.LoadComplete(); - centre.ScaleTo(0); - mainContainer.ScaleTo(0); - this.ScaleTo(1); - drawableSpinner.RotationTracker.Complete.BindValueChanged(complete => updateComplete(complete.NewValue, 200)); drawableSpinner.State.BindValueChanged(updateStateTransforms, true); } private void updateStateTransforms(ValueChangedEvent state) { + centre.ScaleTo(0); + mainContainer.ScaleTo(0); + this.ScaleTo(1); + using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimePreempt / 2, true)) { float phaseOneScale = spinner.Scale * 0.7f; From 54fee7e7160ec6931937f37c31898a5e554bc247 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 30 Jul 2020 11:50:13 +0900 Subject: [PATCH 0300/1782] Simplify and standardise scale for default display --- .../Objects/Drawables/DrawableSpinner.cs | 12 ---------- .../Drawables/Pieces/DefaultSpinnerDisc.cs | 22 +++++++++---------- .../Objects/Drawables/SpinnerCentreLayer.cs | 3 --- 3 files changed, 11 insertions(+), 26 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index f36ecd9dc4..52a1b4679b 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -7,7 +7,6 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Primitives; using osu.Game.Graphics; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; @@ -39,29 +38,20 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables RelativeSizeAxes = Axes.Both; - // we are slightly bigger than our parent, to clip the top and bottom of the circle - Height = 1.3f; - Spinner = s; InternalChildren = new Drawable[] { ticks = new Container(), - RotationTracker = new SpinnerRotationTracker(Spinner), new AspectContainer { Anchor = Anchor.Centre, Origin = Anchor.Centre, RelativeSizeAxes = Axes.Y, - Scale = new Vector2(Spinner.Scale), Children = new Drawable[] { new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SpinnerBody), _ => new DefaultSpinnerDisc()), RotationTracker = new SpinnerRotationTracker(Spinner) - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - }, } }, SpmCounter = new SpinnerSpmCounter @@ -162,8 +152,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables RotationTracker.Tracking = OsuActionInputManager?.PressedActions.Any(x => x == OsuAction.LeftButton || x == OsuAction.RightButton) ?? false; } - public float RelativeHeight => ToScreenSpace(new RectangleF(0, 0, 0, OsuHitObject.OBJECT_RADIUS * 2)).Height / DrawHeight; - protected override void UpdateAfterChildren() { base.UpdateAfterChildren(); diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/DefaultSpinnerDisc.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/DefaultSpinnerDisc.cs index 6a40069c5d..2644f425a0 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/DefaultSpinnerDisc.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/DefaultSpinnerDisc.cs @@ -39,6 +39,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces { RelativeSizeAxes = Axes.Both; + // we are slightly bigger than our parent, to clip the top and bottom of the circle + // this should probably be revisited when scaled spinners are a thing. + Scale = new Vector2(1.3f); + Anchor = Anchor.Centre; Origin = Anchor.Centre; } @@ -95,22 +99,18 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces { centre.ScaleTo(0); mainContainer.ScaleTo(0); - this.ScaleTo(1); using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimePreempt / 2, true)) { - float phaseOneScale = spinner.Scale * 0.7f; - - centre.ScaleTo(phaseOneScale, spinner.TimePreempt / 4, Easing.OutQuint); - - mainContainer - .ScaleTo(phaseOneScale * drawableSpinner.RelativeHeight * 1.6f, spinner.TimePreempt / 4, Easing.OutQuint); - + // constant ambient rotation to give the spinner "spinning" character. this.RotateTo((float)(25 * spinner.Duration / 2000), spinner.TimePreempt + spinner.Duration); + centre.ScaleTo(0.3f, spinner.TimePreempt / 4, Easing.OutQuint); + mainContainer.ScaleTo(0.2f, spinner.TimePreempt / 4, Easing.OutQuint); + using (BeginDelayedSequence(spinner.TimePreempt / 2, true)) { - centre.ScaleTo(spinner.Scale, spinner.TimePreempt / 2, Easing.OutQuint); + centre.ScaleTo(0.5f, spinner.TimePreempt / 2, Easing.OutQuint); mainContainer.ScaleTo(1, spinner.TimePreempt / 2, Easing.OutQuint); } } @@ -160,8 +160,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces .FadeTo(tracking_alpha, 250, Easing.OutQuint); } - float relativeCircleScale = spinner.Scale * drawableSpinner.RelativeHeight; - float targetScale = relativeCircleScale + (1 - relativeCircleScale) * drawableSpinner.Progress; + const float initial_scale = 0.2f; + float targetScale = initial_scale + (1 - initial_scale) * drawableSpinner.Progress; fill.Scale = new Vector2((float)Interpolation.Lerp(fill.Scale.X, targetScale, Math.Clamp(Math.Abs(Time.Elapsed) / 100, 0, 1))); mainContainer.Rotation = drawableSpinner.RotationTracker.Rotation; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/SpinnerCentreLayer.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/SpinnerCentreLayer.cs index 8803b92815..b62ce822f0 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/SpinnerCentreLayer.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/SpinnerCentreLayer.cs @@ -28,9 +28,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { spinner = (DrawableSpinner)drawableHitObject; - AutoSizeAxes = Axes.Both; - Anchor = Anchor.Centre; - Origin = Anchor.Centre; InternalChildren = new Drawable[] { glow = new GlowPiece(), From 4d822742e83a8892838b7ddf105f5f4c6c472858 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 30 Jul 2020 12:05:19 +0900 Subject: [PATCH 0301/1782] Add scale and tint to new legacy style spinner --- .../Skinning/LegacyNewStyleSpinner.cs | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacyNewStyleSpinner.cs b/osu.Game.Rulesets.Osu/Skinning/LegacyNewStyleSpinner.cs index 2521bb1d76..618cb0e9ad 100644 --- a/osu.Game.Rulesets.Osu/Skinning/LegacyNewStyleSpinner.cs +++ b/osu.Game.Rulesets.Osu/Skinning/LegacyNewStyleSpinner.cs @@ -6,9 +6,13 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; +using osu.Framework.Utils; using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Skinning; +using osuTK; +using osuTK.Graphics; namespace osu.Game.Rulesets.Osu.Skinning { @@ -21,14 +25,19 @@ namespace osu.Game.Rulesets.Osu.Skinning private Sprite discBottom; private Sprite discTop; private Sprite spinningMiddle; + private Sprite fixedMiddle; private DrawableSpinner drawableSpinner; + private const float final_scale = 0.625f; + [BackgroundDependencyLoader] private void load(ISkinSource source, DrawableHitObject drawableObject) { drawableSpinner = (DrawableSpinner)drawableObject; + Scale = new Vector2(final_scale); + InternalChildren = new Drawable[] { discBottom = new Sprite @@ -43,7 +52,7 @@ namespace osu.Game.Rulesets.Osu.Skinning Origin = Anchor.Centre, Texture = source.GetTexture("spinner-top") }, - new Sprite + fixedMiddle = new Sprite { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -68,10 +77,14 @@ namespace osu.Game.Rulesets.Osu.Skinning private void updateStateTransforms(ValueChangedEvent state) { - var spinner = drawableSpinner.HitObject; + var spinner = (Spinner)drawableSpinner.HitObject; using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimePreempt / 2, true)) this.FadeInFromZero(spinner.TimePreempt / 2); + + fixedMiddle.FadeColour(Color4.White); + using (BeginAbsoluteSequence(spinner.StartTime, true)) + fixedMiddle.FadeColour(Color4.Red, spinner.Duration); } protected override void Update() @@ -79,6 +92,9 @@ namespace osu.Game.Rulesets.Osu.Skinning base.Update(); spinningMiddle.Rotation = discTop.Rotation = drawableSpinner.RotationTracker.Rotation; discBottom.Rotation = discTop.Rotation / 3; + + Scale = new Vector2(final_scale * 0.8f + + (float)Interpolation.ApplyEasing(Easing.Out, drawableSpinner.Progress) * (final_scale * 0.2f)); } } } From d2b3fe1e7b3412dd82082c3ffe818972a52f47e9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 30 Jul 2020 12:08:04 +0900 Subject: [PATCH 0302/1782] Add scale to old legacy spinner --- osu.Game.Rulesets.Osu/Skinning/LegacyOldStyleSpinner.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacyOldStyleSpinner.cs b/osu.Game.Rulesets.Osu/Skinning/LegacyOldStyleSpinner.cs index e6a4064129..02b8c7eef1 100644 --- a/osu.Game.Rulesets.Osu/Skinning/LegacyOldStyleSpinner.cs +++ b/osu.Game.Rulesets.Osu/Skinning/LegacyOldStyleSpinner.cs @@ -9,6 +9,7 @@ using osu.Framework.Graphics.Sprites; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Skinning; +using osuTK; namespace osu.Game.Rulesets.Osu.Skinning { @@ -25,6 +26,8 @@ namespace osu.Game.Rulesets.Osu.Skinning { drawableSpinner = (DrawableSpinner)drawableObject; + Scale = new Vector2(0.625f); + InternalChildren = new Drawable[] { new Sprite From 743d165319cba9784ee2700524999c59298e1880 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 30 Jul 2020 12:32:19 +0900 Subject: [PATCH 0303/1782] Add old style spin metre --- .../Skinning/LegacyOldStyleSpinner.cs | 50 +++++++++++++++++-- 1 file changed, 45 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacyOldStyleSpinner.cs b/osu.Game.Rulesets.Osu/Skinning/LegacyOldStyleSpinner.cs index 02b8c7eef1..9b1f2b31e3 100644 --- a/osu.Game.Rulesets.Osu/Skinning/LegacyOldStyleSpinner.cs +++ b/osu.Game.Rulesets.Osu/Skinning/LegacyOldStyleSpinner.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.Framework.Graphics.Sprites; +using osu.Framework.Utils; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Skinning; @@ -20,37 +22,59 @@ namespace osu.Game.Rulesets.Osu.Skinning { private DrawableSpinner drawableSpinner; private Sprite disc; + private Container metre; [BackgroundDependencyLoader] private void load(ISkinSource source, DrawableHitObject drawableObject) { drawableSpinner = (DrawableSpinner)drawableObject; - Scale = new Vector2(0.625f); + RelativeSizeAxes = Axes.Both; InternalChildren = new Drawable[] { new Sprite { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Texture = source.GetTexture("spinner-background") + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + Texture = source.GetTexture("spinner-background"), + Y = 20, + Scale = new Vector2(0.625f) }, disc = new Sprite { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Texture = source.GetTexture("spinner-circle") + Texture = source.GetTexture("spinner-circle"), + Scale = new Vector2(0.625f) + }, + metre = new Container + { + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + Y = 20, + Masking = true, + Child = new Sprite + { + Texture = source.GetTexture("spinner-metre"), + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + }, + Scale = new Vector2(0.625f) } }; } + private Vector2 metreFinalSize; + protected override void LoadComplete() { base.LoadComplete(); this.FadeOut(); drawableSpinner.State.BindValueChanged(updateStateTransforms, true); + + metreFinalSize = metre.Size = metre.Child.Size; } private void updateStateTransforms(ValueChangedEvent state) @@ -65,6 +89,22 @@ namespace osu.Game.Rulesets.Osu.Skinning { base.Update(); disc.Rotation = drawableSpinner.RotationTracker.Rotation; + metre.Height = getMetreHeight(drawableSpinner.Progress); + } + + private const int total_bars = 10; + + private float getMetreHeight(float progress) + { + progress = Math.Min(99, progress * 100); + + int barCount = (int)progress / 10; + + // todo: add SpinnerNoBlink support + if (RNG.NextBool(((int)progress % 10) / 10f)) + barCount++; + + return (float)barCount / total_bars * metreFinalSize.Y; } } } From 19fb350cd81d1ed3567f967a4a3d1f1d00943454 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 30 Jul 2020 12:50:27 +0900 Subject: [PATCH 0304/1782] Move offset and scale to constant --- .../Skinning/LegacyOldStyleSpinner.cs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacyOldStyleSpinner.cs b/osu.Game.Rulesets.Osu/Skinning/LegacyOldStyleSpinner.cs index 9b1f2b31e3..0ae1d8f683 100644 --- a/osu.Game.Rulesets.Osu/Skinning/LegacyOldStyleSpinner.cs +++ b/osu.Game.Rulesets.Osu/Skinning/LegacyOldStyleSpinner.cs @@ -24,6 +24,10 @@ namespace osu.Game.Rulesets.Osu.Skinning private Sprite disc; private Container metre; + private const float background_y_offset = 20; + + private const float sprite_scale = 1 / 1.6f; + [BackgroundDependencyLoader] private void load(ISkinSource source, DrawableHitObject drawableObject) { @@ -38,21 +42,21 @@ namespace osu.Game.Rulesets.Osu.Skinning Anchor = Anchor.BottomCentre, Origin = Anchor.BottomCentre, Texture = source.GetTexture("spinner-background"), - Y = 20, - Scale = new Vector2(0.625f) + Y = background_y_offset, + Scale = new Vector2(sprite_scale) }, disc = new Sprite { Anchor = Anchor.Centre, Origin = Anchor.Centre, Texture = source.GetTexture("spinner-circle"), - Scale = new Vector2(0.625f) + Scale = new Vector2(sprite_scale) }, metre = new Container { Anchor = Anchor.BottomCentre, Origin = Anchor.BottomCentre, - Y = 20, + Y = background_y_offset, Masking = true, Child = new Sprite { From c1085d49d3c41b88233cf530e41774065161f527 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 30 Jul 2020 12:55:34 +0900 Subject: [PATCH 0305/1782] Add more xmldoc --- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index 52a1b4679b..f65570a3d5 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -117,6 +117,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables positionBindable.BindTo(HitObject.PositionBindable); } + /// + /// The completion progress of this spinner from 0..1 (clamped). + /// public float Progress => Math.Clamp(RotationTracker.CumulativeRotation / 360 / Spinner.SpinsRequired, 0, 1); protected override void CheckForResult(bool userTriggered, double timeOffset) From 41c0f7557ac2a3bcc46174d811ead37d432a17fd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 30 Jul 2020 12:56:30 +0900 Subject: [PATCH 0306/1782] Remove traceable spinner reference for now --- osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs index f209b315af..977485ba66 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs @@ -57,9 +57,7 @@ namespace osu.Game.Rulesets.Osu.Mods applySliderState(slider); break; - case DrawableSpinner spinner: - //todo: hide background - break; + //todo: hide spinner background somehow } } From ec0d7760afeca015e7ed8ce488a27bedcb6cc20f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 30 Jul 2020 13:06:53 +0900 Subject: [PATCH 0307/1782] Move todo? --- 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 977485ba66..d7582f3196 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs @@ -43,6 +43,8 @@ namespace osu.Game.Rulesets.Osu.Mods var h = drawableOsu.HitObject; + //todo: expose and hide spinner background somehow + switch (drawable) { case DrawableHitCircle circle: @@ -56,8 +58,6 @@ namespace osu.Game.Rulesets.Osu.Mods slider.Body.OnSkinChanged += () => applySliderState(slider); applySliderState(slider); break; - - //todo: hide spinner background somehow } } From 6473bf503b91bdc6d8b8acb474530e7125862152 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 30 Jul 2020 07:09:40 +0300 Subject: [PATCH 0308/1782] Remove use of `case when` --- osu.Game/Skinning/LegacySkin.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 5843cde94d..d98f8aba83 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -133,8 +133,14 @@ namespace osu.Game.Skinning break; - case LegacySkinConfiguration.LegacySetting s when s == LegacySkinConfiguration.LegacySetting.Version: - return SkinUtils.As(new Bindable(Configuration.LegacyVersion ?? LegacySkinConfiguration.LATEST_VERSION)); + case LegacySkinConfiguration.LegacySetting legacy: + switch (legacy) + { + case LegacySkinConfiguration.LegacySetting.Version: + return SkinUtils.As(new Bindable(Configuration.LegacyVersion ?? LegacySkinConfiguration.LATEST_VERSION)); + } + + goto default; default: // handles lookups like some in LegacySkinConfiguration.LegacySetting From e5991d6e1487cd3f9a3596494fc732a0f36a5155 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 30 Jul 2020 13:49:04 +0900 Subject: [PATCH 0309/1782] Change method structure for hover/unhover state setting (shouldn't be called "Fade") --- osu.Game/Graphics/UserInterface/ShowMoreButton.cs | 13 ++++++------- .../Comments/Buttons/CommentRepliesButton.cs | 4 ++-- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/ShowMoreButton.cs b/osu.Game/Graphics/UserInterface/ShowMoreButton.cs index f4ab53d305..924c7913f3 100644 --- a/osu.Game/Graphics/UserInterface/ShowMoreButton.cs +++ b/osu.Game/Graphics/UserInterface/ShowMoreButton.cs @@ -98,16 +98,16 @@ namespace osu.Game.Graphics.UserInterface protected override bool OnHover(HoverEvent e) { base.OnHover(e); - leftIcon.FadeHoverColour(); - rightIcon.FadeHoverColour(); + leftIcon.SetHoveredState(true); + rightIcon.SetHoveredState(true); return true; } protected override void OnHoverLost(HoverLostEvent e) { base.OnHoverLost(e); - leftIcon.FadeIdleColour(); - rightIcon.FadeIdleColour(); + leftIcon.SetHoveredState(false); + rightIcon.SetHoveredState(false); } public class ChevronIcon : SpriteIcon @@ -127,9 +127,8 @@ namespace osu.Game.Graphics.UserInterface Colour = colourProvider.Foreground1; } - public void FadeHoverColour() => this.FadeColour(colourProvider.Light1, 200, Easing.OutQuint); - - public void FadeIdleColour() => this.FadeColour(colourProvider.Foreground1, 200, Easing.OutQuint); + public void SetHoveredState(bool hovered) => + this.FadeColour(hovered ? colourProvider.Light1 : colourProvider.Foreground1, 200, Easing.OutQuint); } } } diff --git a/osu.Game/Overlays/Comments/Buttons/CommentRepliesButton.cs b/osu.Game/Overlays/Comments/Buttons/CommentRepliesButton.cs index 202f3ddd7b..57bf2af4d2 100644 --- a/osu.Game/Overlays/Comments/Buttons/CommentRepliesButton.cs +++ b/osu.Game/Overlays/Comments/Buttons/CommentRepliesButton.cs @@ -96,7 +96,7 @@ namespace osu.Game.Overlays.Comments.Buttons { base.OnHover(e); background.FadeColour(colourProvider.Background1, 200, Easing.OutQuint); - icon.FadeHoverColour(); + icon.SetHoveredState(true); return true; } @@ -104,7 +104,7 @@ namespace osu.Game.Overlays.Comments.Buttons { base.OnHoverLost(e); background.FadeColour(colourProvider.Background2, 200, Easing.OutQuint); - icon.FadeIdleColour(); + icon.SetHoveredState(false); } } } From 7071f9a3af6fd281cf76c5aacef45959df5c3e87 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 30 Jul 2020 14:24:21 +0900 Subject: [PATCH 0310/1782] 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 7e6f1469f5..61314bdc10 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,6 +52,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 5ac54a853f..d6aeca1f53 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -24,7 +24,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 8b2d1346be..9b8d70ab6d 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -80,7 +80,7 @@ - + From d8bb52800fe5e351dd730cd2d37f707b1a447c3b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 30 Jul 2020 14:31:05 +0900 Subject: [PATCH 0311/1782] Update framework again (github deploy failed) --- 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 61314bdc10..0d951f58e6 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,6 +52,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index d6aeca1f53..92e7080fed 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -24,7 +24,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 9b8d70ab6d..973c1d5b89 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -80,7 +80,7 @@ - + From 1dfd2112c60d2f468a74675f383b4125334d11cc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 30 Jul 2020 15:32:08 +0900 Subject: [PATCH 0312/1782] Source hash from osu.Game.dll rather than executable --- osu.Game/OsuGameBase.cs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 964a7fdd35..278f2d849f 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -134,13 +134,8 @@ namespace osu.Game [BackgroundDependencyLoader] private void load() { - var assembly = Assembly.GetEntryAssembly(); - - if (assembly != null) - { - using (var str = File.OpenRead(assembly.Location)) - VersionHash = str.ComputeMD5Hash(); - } + using (var str = File.OpenRead(typeof(OsuGameBase).Assembly.Location)) + VersionHash = str.ComputeMD5Hash(); Resources.AddStore(new DllResourceStore(OsuResources.ResourceAssembly)); From cf697cc276e7aa466d068566b4250009291efdd7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 30 Jul 2020 15:32:40 +0900 Subject: [PATCH 0313/1782] Update framework again (fix audio component disposal issue) --- 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 0d951f58e6..0ac766926c 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,6 +52,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 92e7080fed..1ececa448c 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -24,7 +24,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 973c1d5b89..ef5ba10d17 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -80,7 +80,7 @@ - + From 6b9102b2a43665b82c92bcb3a3a643e8d23acce9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 30 Jul 2020 17:58:49 +0900 Subject: [PATCH 0314/1782] Add osu!catch banana catching sounds --- osu.Game.Rulesets.Catch/Objects/Banana.cs | 21 +++++++++++++++++++ .../Objects/BananaShower.cs | 12 ++++++++--- .../Objects/Drawables/DrawableBanana.cs | 7 +++++++ 3 files changed, 37 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Objects/Banana.cs b/osu.Game.Rulesets.Catch/Objects/Banana.cs index 0b3d1d23e0..4ecfb7b16d 100644 --- a/osu.Game.Rulesets.Catch/Objects/Banana.cs +++ b/osu.Game.Rulesets.Catch/Objects/Banana.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 osu.Game.Audio; using osu.Game.Rulesets.Catch.Judgements; using osu.Game.Rulesets.Judgements; @@ -8,8 +10,27 @@ namespace osu.Game.Rulesets.Catch.Objects { public class Banana : Fruit { + /// + /// Index of banana in current shower. + /// + public int BananaIndex; + public override FruitVisualRepresentation VisualRepresentation => FruitVisualRepresentation.Banana; public override Judgement CreateJudgement() => new CatchBananaJudgement(); + + private static readonly List samples = new List { new BananaHitSampleInfo() }; + + public Banana() + { + Samples = samples; + } + + private class BananaHitSampleInfo : HitSampleInfo + { + private static string[] lookupNames { get; } = { "metronomelow", "catch-banana" }; + + public override IEnumerable LookupNames => lookupNames; + } } } diff --git a/osu.Game.Rulesets.Catch/Objects/BananaShower.cs b/osu.Game.Rulesets.Catch/Objects/BananaShower.cs index 04a995c77e..89c51459a6 100644 --- a/osu.Game.Rulesets.Catch/Objects/BananaShower.cs +++ b/osu.Game.Rulesets.Catch/Objects/BananaShower.cs @@ -30,15 +30,21 @@ namespace osu.Game.Rulesets.Catch.Objects if (spacing <= 0) return; - for (double i = StartTime; i <= EndTime; i += spacing) + double time = StartTime; + int i = 0; + + while (time <= EndTime) { cancellationToken.ThrowIfCancellationRequested(); AddNested(new Banana { - Samples = Samples, - StartTime = i + StartTime = time, + BananaIndex = i, }); + + time += spacing; + i++; } } diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableBanana.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableBanana.cs index 01b76ceed9..a865984d45 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableBanana.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableBanana.cs @@ -40,6 +40,13 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables float getRandomAngle() => 180 * (RNG.NextSingle() * 2 - 1); } + public override void PlaySamples() + { + base.PlaySamples(); + if (Samples != null) + Samples.Frequency.Value = 0.77f + ((Banana)HitObject).BananaIndex * 0.006f; + } + private Color4 getBananaColour() { switch (RNG.Next(0, 3)) From 38a4bdf068c7b727e046a5b6d6119d6b3339f51b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 30 Jul 2020 19:34:59 +0900 Subject: [PATCH 0315/1782] Add spinner spin sample support --- .../Objects/Drawables/DrawableSpinner.cs | 62 ++++++++++++++++++- .../Pieces/SpinnerRotationTracker.cs | 9 ++- 2 files changed, 69 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index f65570a3d5..68516bedf8 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -70,6 +70,58 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables }; } + private Bindable isSpinning; + + protected override void LoadComplete() + { + base.LoadComplete(); + + isSpinning = RotationTracker.IsSpinning.GetBoundCopy(); + isSpinning.BindValueChanged(updateSpinningSample); + } + + private SkinnableSound spinningSample; + + private const float minimum_volume = 0.0001f; + + protected override void LoadSamples() + { + base.LoadSamples(); + + spinningSample?.Expire(); + spinningSample = null; + + var firstSample = HitObject.Samples.FirstOrDefault(); + + if (firstSample != null) + { + var clone = HitObject.SampleControlPoint.ApplyTo(firstSample); + clone.Name = "spinnerspin"; + + AddInternal(spinningSample = new SkinnableSound(clone) + { + Volume = { Value = minimum_volume }, + Looping = true, + }); + } + } + + private void updateSpinningSample(ValueChangedEvent tracking) + { + // note that samples will not start playing if exiting a seek operation in the middle of a spinner. + // may be something we want to address at a later point, but not so easy to make happen right now + // (SkinnableSound would need to expose whether the sample is already playing and this logic would need to run in Update). + if (tracking.NewValue && ShouldPlaySamples) + { + spinningSample?.Play(); + spinningSample?.VolumeTo(1, 200); + } + else + { + spinningSample?.VolumeTo(minimum_volume, 200).Finally(_ => spinningSample.Stop()); + } + } + protected override void AddNestedHitObject(DrawableHitObject hitObject) { base.AddNestedHitObject(hitObject); @@ -88,6 +140,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables using (BeginDelayedSequence(Spinner.Duration, true)) this.FadeOut(160); + + // skin change does a rewind of transforms, which will stop the spinning sound from playing if it's currently in playback. + isSpinning?.TriggerChange(); } protected override void ClearNestedHitObjects() @@ -151,8 +206,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables protected override void Update() { base.Update(); + if (HandleUserInput) - RotationTracker.Tracking = OsuActionInputManager?.PressedActions.Any(x => x == OsuAction.LeftButton || x == OsuAction.RightButton) ?? false; + RotationTracker.Tracking = !Result.HasResult && (OsuActionInputManager?.PressedActions.Any(x => x == OsuAction.LeftButton || x == OsuAction.RightButton) ?? false); + + if (spinningSample != null) + // todo: implement SpinnerFrequencyModulate + spinningSample.Frequency.Value = 0.5f + Progress; } protected override void UpdateAfterChildren() diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerRotationTracker.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerRotationTracker.cs index 968c2a6df5..0cc6c842f4 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerRotationTracker.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerRotationTracker.cs @@ -43,6 +43,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces /// public float CumulativeRotation { get; private set; } + /// + /// Whether the spinning is spinning at a reasonable speed to be considered visually spinning. + /// + public readonly BindableBool IsSpinning = new BindableBool(); + /// /// Whether currently in the correct time range to allow spinning. /// @@ -73,7 +78,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces lastAngle = thisAngle; - Rotation = (float)Interpolation.Lerp(Rotation, currentRotation / 2, Math.Clamp(Math.Abs(Time.Elapsed) / 40, 0, 1)); + IsSpinning.Value = isSpinnableTime && Math.Abs(currentRotation / 2 - Rotation) > 5f; + + Rotation = (float)Interpolation.Damp(Rotation, currentRotation / 2, 0.99, Math.Abs(Time.Elapsed)); } /// From 23ab6f8f946739bb788cc480f894b57611e78647 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 30 Jul 2020 21:10:13 +0900 Subject: [PATCH 0316/1782] Fix dynamic compilation loading wrong ruleset versions --- osu.Game/Rulesets/RulesetStore.cs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/osu.Game/Rulesets/RulesetStore.cs b/osu.Game/Rulesets/RulesetStore.cs index 58a2ba056e..837796287a 100644 --- a/osu.Game/Rulesets/RulesetStore.cs +++ b/osu.Game/Rulesets/RulesetStore.cs @@ -65,11 +65,15 @@ namespace osu.Game.Rulesets // the requesting assembly may be located out of the executable's base directory, thus requiring manual resolving of its dependencies. // this attempts resolving the ruleset dependencies on game core and framework assemblies by returning assemblies with the same assembly name // already loaded in the AppDomain. - foreach (var curAsm in AppDomain.CurrentDomain.GetAssemblies()) - { - if (asm.Name.Equals(curAsm.GetName().Name, StringComparison.Ordinal)) - return curAsm; - } + var domainAssembly = AppDomain.CurrentDomain.GetAssemblies() + // Given name is always going to be equally-or-more qualified than the assembly name. + .Where(a => args.Name.Contains(a.GetName().Name, StringComparison.Ordinal)) + // Pick the greatest assembly version. + .OrderBy(a => a.GetName().Version) + .LastOrDefault(); + + if (domainAssembly != null) + return domainAssembly; return loadedAssemblies.Keys.FirstOrDefault(a => a.FullName == asm.FullName); } From 5af45bcdcc008b8bc61d3919037b6cd150532893 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 30 Jul 2020 20:10:41 +0200 Subject: [PATCH 0317/1782] Expand tests to cover non-bank sample lookups --- osu.Game.Tests/Gameplay/TestSceneHitObjectSamples.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game.Tests/Gameplay/TestSceneHitObjectSamples.cs b/osu.Game.Tests/Gameplay/TestSceneHitObjectSamples.cs index 737946e1e0..a70b08a0d3 100644 --- a/osu.Game.Tests/Gameplay/TestSceneHitObjectSamples.cs +++ b/osu.Game.Tests/Gameplay/TestSceneHitObjectSamples.cs @@ -67,9 +67,11 @@ namespace osu.Game.Tests.Gameplay /// Tests that a hitobject which provides a custom sample set of 2 retrieves the following samples from the beatmap skin: /// normal-hitnormal2 /// normal-hitnormal + /// hitnormal /// [TestCase("normal-hitnormal2")] [TestCase("normal-hitnormal")] + [TestCase("hitnormal")] public void TestDefaultCustomSampleFromBeatmap(string expectedSample) { SetupSkins(expectedSample, expectedSample); @@ -83,9 +85,11 @@ namespace osu.Game.Tests.Gameplay /// Tests that a hitobject which provides a custom sample set of 2 retrieves the following samples from the user skin when the beatmap does not contain the sample: /// normal-hitnormal2 /// normal-hitnormal + /// hitnormal /// [TestCase("normal-hitnormal2")] [TestCase("normal-hitnormal")] + [TestCase("hitnormal")] public void TestDefaultCustomSampleFromUserSkinFallback(string expectedSample) { SetupSkins(string.Empty, expectedSample); @@ -145,6 +149,7 @@ namespace osu.Game.Tests.Gameplay /// [TestCase("normal-hitnormal2")] [TestCase("normal-hitnormal")] + [TestCase("hitnormal")] public void TestControlPointCustomSampleFromBeatmap(string sampleName) { SetupSkins(sampleName, sampleName); From 566c5310bf954c1b7b5b71dceb63d4a904d81162 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 30 Jul 2020 21:34:57 +0200 Subject: [PATCH 0318/1782] Add test coverage for taiko sample lookups --- ...o-hitobject-beatmap-custom-sample-bank.osu | 10 ++++ .../TestSceneTaikoHitObjectSamples.cs | 52 +++++++++++++++++++ 2 files changed, 62 insertions(+) create mode 100644 osu.Game.Rulesets.Taiko.Tests/Resources/SampleLookups/taiko-hitobject-beatmap-custom-sample-bank.osu create mode 100644 osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoHitObjectSamples.cs diff --git a/osu.Game.Rulesets.Taiko.Tests/Resources/SampleLookups/taiko-hitobject-beatmap-custom-sample-bank.osu b/osu.Game.Rulesets.Taiko.Tests/Resources/SampleLookups/taiko-hitobject-beatmap-custom-sample-bank.osu new file mode 100644 index 0000000000..f9755782c2 --- /dev/null +++ b/osu.Game.Rulesets.Taiko.Tests/Resources/SampleLookups/taiko-hitobject-beatmap-custom-sample-bank.osu @@ -0,0 +1,10 @@ +osu file format v14 + +[General] +Mode: 1 + +[TimingPoints] +0,300,4,1,2,100,1,0 + +[HitObjects] +444,320,1000,5,0,0:0:0:0: diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoHitObjectSamples.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoHitObjectSamples.cs new file mode 100644 index 0000000000..7089ea6619 --- /dev/null +++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoHitObjectSamples.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.Reflection; +using NUnit.Framework; +using osu.Framework.IO.Stores; +using osu.Game.Tests.Beatmaps; + +namespace osu.Game.Rulesets.Taiko.Tests +{ + public class TestSceneTaikoHitObjectSamples : HitObjectSampleTest + { + protected override Ruleset CreatePlayerRuleset() => new TaikoRuleset(); + + protected override IResourceStore Resources => new DllResourceStore(Assembly.GetAssembly(typeof(TestSceneTaikoHitObjectSamples))); + + [TestCase("taiko-normal-hitnormal")] + [TestCase("normal-hitnormal")] + [TestCase("hitnormal")] + public void TestDefaultCustomSampleFromBeatmap(string expectedSample) + { + SetupSkins(expectedSample, expectedSample); + + CreateTestWithBeatmap("taiko-hitobject-beatmap-custom-sample-bank.osu"); + + AssertBeatmapLookup(expectedSample); + } + + [TestCase("taiko-normal-hitnormal")] + [TestCase("normal-hitnormal")] + [TestCase("hitnormal")] + public void TestDefaultCustomSampleFromUserSkinFallback(string expectedSample) + { + SetupSkins(string.Empty, expectedSample); + + CreateTestWithBeatmap("taiko-hitobject-beatmap-custom-sample-bank.osu"); + + AssertUserLookup(expectedSample); + } + + [TestCase("taiko-normal-hitnormal2")] + [TestCase("normal-hitnormal2")] + public void TestUserSkinLookupIgnoresSampleBank(string unwantedSample) + { + SetupSkins(string.Empty, unwantedSample); + + CreateTestWithBeatmap("taiko-hitobject-beatmap-custom-sample-bank.osu"); + + AssertNoLookup(unwantedSample); + } + } +} From 2df5fafea0ed7a537eb51e2a830f851702f90dc3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 30 Jul 2020 21:39:45 +0200 Subject: [PATCH 0319/1782] Add failing test case --- .../Gameplay/TestSceneHitObjectSamples.cs | 24 +++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Gameplay/TestSceneHitObjectSamples.cs b/osu.Game.Tests/Gameplay/TestSceneHitObjectSamples.cs index a70b08a0d3..c3acc2ebe7 100644 --- a/osu.Game.Tests/Gameplay/TestSceneHitObjectSamples.cs +++ b/osu.Game.Tests/Gameplay/TestSceneHitObjectSamples.cs @@ -82,12 +82,11 @@ namespace osu.Game.Tests.Gameplay } /// - /// Tests that a hitobject which provides a custom sample set of 2 retrieves the following samples from the user skin when the beatmap does not contain the sample: - /// normal-hitnormal2 + /// Tests that a hitobject which provides a custom sample set of 2 retrieves the following samples from the user skin + /// (ignoring the custom sample set index) when the beatmap skin does not contain the sample: /// normal-hitnormal /// hitnormal /// - [TestCase("normal-hitnormal2")] [TestCase("normal-hitnormal")] [TestCase("hitnormal")] public void TestDefaultCustomSampleFromUserSkinFallback(string expectedSample) @@ -99,6 +98,23 @@ namespace osu.Game.Tests.Gameplay AssertUserLookup(expectedSample); } + /// + /// Tests that a hitobject which provides a custom sample set of 2 does not retrieve a normal-hitnormal2 sample from the user skin + /// if the beatmap skin does not contain the sample. + /// User skins in stable ignore the custom sample set index when performing lookups. + /// + [Test] + public void TestUserSkinLookupIgnoresSampleBank() + { + const string unwanted_sample = "normal-hitnormal2"; + + SetupSkins(string.Empty, unwanted_sample); + + CreateTestWithBeatmap("hitobject-beatmap-custom-sample.osu"); + + AssertNoLookup(unwanted_sample); + } + /// /// Tests that a hitobject which provides a sample file retrieves the sample file from the beatmap skin. /// @@ -183,7 +199,7 @@ namespace osu.Game.Tests.Gameplay string[] expectedSamples = { "normal-hitnormal2", - "normal-hitwhistle2" + "normal-hitwhistle" // user skin lookups ignore custom sample set index }; SetupSkins(expectedSamples[0], expectedSamples[1]); From 2bb436fd3c6dd6a6d172c462ed264ae8bc963faf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 28 Jul 2020 23:52:09 +0200 Subject: [PATCH 0320/1782] Do not use custom sample banks outside of beatmap skin --- osu.Game/Skinning/LegacyBeatmapSkin.cs | 1 + osu.Game/Skinning/LegacySkin.cs | 26 +++++++++++++++++++++++++- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/osu.Game/Skinning/LegacyBeatmapSkin.cs b/osu.Game/Skinning/LegacyBeatmapSkin.cs index 87bca856a3..d647bc4a2d 100644 --- a/osu.Game/Skinning/LegacyBeatmapSkin.cs +++ b/osu.Game/Skinning/LegacyBeatmapSkin.cs @@ -14,6 +14,7 @@ namespace osu.Game.Skinning public class LegacyBeatmapSkin : LegacySkin { protected override bool AllowManiaSkin => false; + protected override bool UseCustomSampleBanks => true; public LegacyBeatmapSkin(BeatmapInfo beatmap, IResourceStore storage, AudioManager audioManager) : base(createSkinInfo(beatmap), new LegacySkinResourceStore(beatmap.BeatmapSet, storage), audioManager, beatmap.Path) diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 3bbeff9918..187d601812 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -38,6 +38,12 @@ namespace osu.Game.Skinning protected virtual bool AllowManiaSkin => hasKeyTexture.Value; + /// + /// Whether this skin can use samples with a custom bank (custom sample set in stable terminology). + /// Added in order to match sample lookup logic from stable (in stable, only the beatmap skin could use samples with a custom sample bank). + /// + protected virtual bool UseCustomSampleBanks => false; + public new LegacySkinConfiguration Configuration { get => base.Configuration as LegacySkinConfiguration; @@ -337,7 +343,12 @@ namespace osu.Game.Skinning public override SampleChannel GetSample(ISampleInfo sampleInfo) { - foreach (var lookup in sampleInfo.LookupNames) + var lookupNames = sampleInfo.LookupNames; + + if (sampleInfo is HitSampleInfo hitSample) + lookupNames = getLegacyLookupNames(hitSample); + + foreach (var lookup in lookupNames) { var sample = Samples?.Get(lookup); @@ -361,5 +372,18 @@ namespace osu.Game.Skinning string lastPiece = componentName.Split('/').Last(); yield return componentName.StartsWith("Gameplay/taiko/") ? "taiko-" + lastPiece : lastPiece; } + + private IEnumerable getLegacyLookupNames(HitSampleInfo hitSample) + { + var lookupNames = hitSample.LookupNames; + + if (!UseCustomSampleBanks && !string.IsNullOrEmpty(hitSample.Suffix)) + // for compatibility with stable, exclude the lookup names with the custom sample bank suffix, if they are not valid for use in this skin. + // using .EndsWith() is intentional as it ensures parity in all edge cases + // (see LegacyTaikoSampleInfo for an example of one - prioritising the taiko prefix should still apply, but the sample bank should not). + lookupNames = hitSample.LookupNames.Where(name => !name.EndsWith(hitSample.Suffix)); + + return lookupNames; + } } } From 971eafde2b05e51231208eff01f326664f6a7a4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 30 Jul 2020 22:07:07 +0200 Subject: [PATCH 0321/1782] Move fallback to non-bank samples to centralise hackery --- osu.Game/Skinning/LegacySkin.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 187d601812..fc04383a64 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -356,10 +356,6 @@ namespace osu.Game.Skinning return sample; } - if (sampleInfo is HitSampleInfo hsi) - // Try fallback to non-bank samples. - return Samples?.Get(hsi.Name); - return null; } @@ -383,6 +379,11 @@ namespace osu.Game.Skinning // (see LegacyTaikoSampleInfo for an example of one - prioritising the taiko prefix should still apply, but the sample bank should not). lookupNames = hitSample.LookupNames.Where(name => !name.EndsWith(hitSample.Suffix)); + // also for compatibility, try falling back to non-bank samples (so-called "universal" samples) as the last resort. + // going forward specifying banks shall always be required, even for elements that wouldn't require it on stable, + // which is why this is done locally here. + lookupNames = lookupNames.Append(hitSample.Name); + return lookupNames; } } From 8e49256a5c28bac5d93d765c2efc4833e62bc165 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 31 Jul 2020 09:03:29 +0900 Subject: [PATCH 0322/1782] Rename and split up statement to make more legible --- .../Drawables/Pieces/DefaultSpinnerDisc.cs | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/DefaultSpinnerDisc.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/DefaultSpinnerDisc.cs index 2644f425a0..7f65a8c022 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/DefaultSpinnerDisc.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/DefaultSpinnerDisc.cs @@ -29,7 +29,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces private SpinnerTicks ticks; - private int completeTick; + private int wholeRotationCount; + private SpinnerFill fill; private Container mainContainer; private SpinnerCentreLayer centre; @@ -145,13 +146,24 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces centre.FadeAccent(colour, duration); } - private bool updateCompleteTick() => completeTick != (completeTick = (int)(drawableSpinner.RotationTracker.CumulativeRotation / 360)); + private bool checkNewRotationCount + { + get + { + int rotations = (int)(drawableSpinner.RotationTracker.CumulativeRotation / 360); + + if (wholeRotationCount == rotations) return false; + + wholeRotationCount = rotations; + return true; + } + } protected override void Update() { base.Update(); - if (drawableSpinner.RotationTracker.Complete.Value && updateCompleteTick()) + if (drawableSpinner.RotationTracker.Complete.Value && checkNewRotationCount) { fill.FinishTransforms(false, nameof(Alpha)); fill From cd570433f4aa1779d7bed92aac36e5fd368b6546 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 31 Jul 2020 09:04:20 +0900 Subject: [PATCH 0323/1782] Move private methods to bottom of class --- .../Drawables/Pieces/DefaultSpinnerDisc.cs | 39 ++++++++++--------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/DefaultSpinnerDisc.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/DefaultSpinnerDisc.cs index 7f65a8c022..81dea8d1c1 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/DefaultSpinnerDisc.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/DefaultSpinnerDisc.cs @@ -96,6 +96,26 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces drawableSpinner.State.BindValueChanged(updateStateTransforms, true); } + protected override void Update() + { + base.Update(); + + if (drawableSpinner.RotationTracker.Complete.Value && checkNewRotationCount) + { + fill.FinishTransforms(false, nameof(Alpha)); + fill + .FadeTo(tracking_alpha + 0.2f, 60, Easing.OutExpo) + .Then() + .FadeTo(tracking_alpha, 250, Easing.OutQuint); + } + + const float initial_scale = 0.2f; + float targetScale = initial_scale + (1 - initial_scale) * drawableSpinner.Progress; + + fill.Scale = new Vector2((float)Interpolation.Lerp(fill.Scale.X, targetScale, Math.Clamp(Math.Abs(Time.Elapsed) / 100, 0, 1))); + mainContainer.Rotation = drawableSpinner.RotationTracker.Rotation; + } + private void updateStateTransforms(ValueChangedEvent state) { centre.ScaleTo(0); @@ -159,24 +179,5 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces } } - protected override void Update() - { - base.Update(); - - if (drawableSpinner.RotationTracker.Complete.Value && checkNewRotationCount) - { - fill.FinishTransforms(false, nameof(Alpha)); - fill - .FadeTo(tracking_alpha + 0.2f, 60, Easing.OutExpo) - .Then() - .FadeTo(tracking_alpha, 250, Easing.OutQuint); - } - - const float initial_scale = 0.2f; - float targetScale = initial_scale + (1 - initial_scale) * drawableSpinner.Progress; - - fill.Scale = new Vector2((float)Interpolation.Lerp(fill.Scale.X, targetScale, Math.Clamp(Math.Abs(Time.Elapsed) / 100, 0, 1))); - mainContainer.Rotation = drawableSpinner.RotationTracker.Rotation; - } } } From 86784e30ad4cea26f205370fc5c2781ce9802e6e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 31 Jul 2020 09:54:30 +0900 Subject: [PATCH 0324/1782] Fix spacing --- .../Objects/Drawables/Pieces/DefaultSpinnerDisc.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/DefaultSpinnerDisc.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/DefaultSpinnerDisc.cs index 81dea8d1c1..79bfeedd07 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/DefaultSpinnerDisc.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/DefaultSpinnerDisc.cs @@ -178,6 +178,5 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces return true; } } - } } From fb74195d83be6b7361740e3584732a52c8795622 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 31 Jul 2020 10:43:54 +0900 Subject: [PATCH 0325/1782] Move InputManager implementation to base skinnable test scene class --- .../OsuSkinnableTestScene.cs | 15 +++++++++++++++ osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs | 14 -------------- osu.Game.Rulesets.Osu.Tests/TestSceneSpinner.cs | 2 -- 3 files changed, 15 insertions(+), 16 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/OsuSkinnableTestScene.cs b/osu.Game.Rulesets.Osu.Tests/OsuSkinnableTestScene.cs index a0a38fc47b..cad98185ce 100644 --- a/osu.Game.Rulesets.Osu.Tests/OsuSkinnableTestScene.cs +++ b/osu.Game.Rulesets.Osu.Tests/OsuSkinnableTestScene.cs @@ -1,12 +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.Graphics; +using osu.Framework.Graphics.Containers; using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Osu.Tests { public abstract class OsuSkinnableTestScene : SkinnableTestScene { + private Container content; + + protected override Container Content + { + get + { + if (content == null) + base.Content.Add(content = new OsuInputManager(new RulesetInfo { ID = 0 })); + + return content; + } + } + protected override Ruleset CreateRulesetForSkinProvider() => new OsuRuleset(); } } diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs index a9404f665a..6a689a1f80 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; @@ -26,19 +25,6 @@ namespace osu.Game.Rulesets.Osu.Tests [TestFixture] public class TestSceneSlider : OsuSkinnableTestScene { - 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() diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinner.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinner.cs index 65b338882e..b57561f3e1 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinner.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinner.cs @@ -19,8 +19,6 @@ namespace osu.Game.Rulesets.Osu.Tests public TestSceneSpinner() { - // base.Content.Add(content = new OsuInputManager(new RulesetInfo { ID = 0 })); - AddStep("Miss Big", () => SetContents(() => testSingle(2))); AddStep("Miss Medium", () => SetContents(() => testSingle(5))); AddStep("Miss Small", () => SetContents(() => testSingle(7))); From 6452d62249f1252034fd7c8d85c3929e692c1336 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 31 Jul 2020 12:52:03 +0900 Subject: [PATCH 0326/1782] 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 0ac766926c..13b4b6ebbb 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -51,7 +51,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 1ececa448c..745555e0e2 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 ef5ba10d17..f1080f0c8b 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -71,7 +71,7 @@ - + From 186b452331437ba41ef07d2559b1b28314d89933 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 31 Jul 2020 14:48:56 +0900 Subject: [PATCH 0327/1782] Apply common multiplication refactor --- osu.Game.Rulesets.Osu/Skinning/LegacyNewStyleSpinner.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacyNewStyleSpinner.cs b/osu.Game.Rulesets.Osu/Skinning/LegacyNewStyleSpinner.cs index 618cb0e9ad..72bc3ddc9a 100644 --- a/osu.Game.Rulesets.Osu/Skinning/LegacyNewStyleSpinner.cs +++ b/osu.Game.Rulesets.Osu/Skinning/LegacyNewStyleSpinner.cs @@ -93,8 +93,7 @@ namespace osu.Game.Rulesets.Osu.Skinning spinningMiddle.Rotation = discTop.Rotation = drawableSpinner.RotationTracker.Rotation; discBottom.Rotation = discTop.Rotation / 3; - Scale = new Vector2(final_scale * 0.8f - + (float)Interpolation.ApplyEasing(Easing.Out, drawableSpinner.Progress) * (final_scale * 0.2f)); + Scale = new Vector2(final_scale * (0.8f + (float)Interpolation.ApplyEasing(Easing.Out, drawableSpinner.Progress) * 0.2f)); } } } From 62ba214dad3d9cd99b760a1bed13e04ea78bd97a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 31 Jul 2020 16:21:47 +0900 Subject: [PATCH 0328/1782] Use OrderByDescending --- 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 837796287a..dd43092c0d 100644 --- a/osu.Game/Rulesets/RulesetStore.cs +++ b/osu.Game/Rulesets/RulesetStore.cs @@ -69,8 +69,8 @@ namespace osu.Game.Rulesets // Given name is always going to be equally-or-more qualified than the assembly name. .Where(a => args.Name.Contains(a.GetName().Name, StringComparison.Ordinal)) // Pick the greatest assembly version. - .OrderBy(a => a.GetName().Version) - .LastOrDefault(); + .OrderByDescending(a => a.GetName().Version) + .FirstOrDefault(); if (domainAssembly != null) return domainAssembly; From 88e179d8aa684a1ef8b9971f74cad059f20e6ce3 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 31 Jul 2020 17:40:58 +0900 Subject: [PATCH 0329/1782] Split out index-only response --- .../TestSceneTimeshiftResultsScreen.cs | 2 +- .../Multiplayer/IndexPlaylistScoresRequest.cs | 2 +- .../Multiplayer/IndexedMultiplayerScores.cs | 27 +++++++++++++++++++ .../Online/Multiplayer/MultiplayerScores.cs | 12 --------- 4 files changed, 29 insertions(+), 14 deletions(-) create mode 100644 osu.Game/Online/Multiplayer/IndexedMultiplayerScores.cs diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneTimeshiftResultsScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneTimeshiftResultsScreen.cs index 44ca676c4f..8b6d6694c3 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneTimeshiftResultsScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneTimeshiftResultsScreen.cs @@ -111,7 +111,7 @@ namespace osu.Game.Tests.Visual.Multiplayer void success() { - r.TriggerSuccess(new MultiplayerScores { Scores = roomScores }); + r.TriggerSuccess(new IndexedMultiplayerScores { Scores = roomScores }); roomsReceived = true; } diff --git a/osu.Game/Online/Multiplayer/IndexPlaylistScoresRequest.cs b/osu.Game/Online/Multiplayer/IndexPlaylistScoresRequest.cs index 67793df344..91f24933e1 100644 --- a/osu.Game/Online/Multiplayer/IndexPlaylistScoresRequest.cs +++ b/osu.Game/Online/Multiplayer/IndexPlaylistScoresRequest.cs @@ -12,7 +12,7 @@ namespace osu.Game.Online.Multiplayer /// /// Returns a list of scores for the specified playlist item. /// - public class IndexPlaylistScoresRequest : APIRequest + public class IndexPlaylistScoresRequest : APIRequest { private readonly int roomId; private readonly int playlistItemId; diff --git a/osu.Game/Online/Multiplayer/IndexedMultiplayerScores.cs b/osu.Game/Online/Multiplayer/IndexedMultiplayerScores.cs new file mode 100644 index 0000000000..e237b7e3fb --- /dev/null +++ b/osu.Game/Online/Multiplayer/IndexedMultiplayerScores.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 JetBrains.Annotations; +using Newtonsoft.Json; + +namespace osu.Game.Online.Multiplayer +{ + /// + /// A object returned via a . + /// + public class IndexedMultiplayerScores : MultiplayerScores + { + /// + /// The total scores in the playlist item. + /// + [JsonProperty("total")] + public int? TotalScores { get; set; } + + /// + /// The user's score, if any. + /// + [JsonProperty("user_score")] + [CanBeNull] + public MultiplayerScore UserScore { get; set; } + } +} diff --git a/osu.Game/Online/Multiplayer/MultiplayerScores.cs b/osu.Game/Online/Multiplayer/MultiplayerScores.cs index 2d0f98e032..8b8dd9e48d 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerScores.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerScores.cs @@ -18,18 +18,6 @@ namespace osu.Game.Online.Multiplayer [JsonProperty("scores")] public List Scores { get; set; } - /// - /// The total scores in the playlist item. Only provided via . - /// - [JsonProperty("total")] - public int? TotalScores { get; set; } - - /// - /// The user's score, if any. Only provided via . - /// - [JsonProperty("user_score")] - public MultiplayerScore UserScore { get; set; } - /// /// The parameters to be used to fetch the next page. /// From eadef53e68cd1612f8bb0b0a6d7260f5631ad07f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 31 Jul 2020 17:43:40 +0900 Subject: [PATCH 0330/1782] Add more annotations --- osu.Game/Online/Multiplayer/MultiplayerScoresAround.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Online/Multiplayer/MultiplayerScoresAround.cs b/osu.Game/Online/Multiplayer/MultiplayerScoresAround.cs index e83cc1b753..2ac62d0300 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerScoresAround.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerScoresAround.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 Newtonsoft.Json; namespace osu.Game.Online.Multiplayer @@ -14,12 +15,14 @@ namespace osu.Game.Online.Multiplayer /// Scores sorted "higher" than the user's score, depending on the sorting order. ///
[JsonProperty("higher")] + [CanBeNull] public MultiplayerScores Higher { get; set; } /// /// Scores sorted "lower" than the user's score, depending on the sorting order. /// [JsonProperty("lower")] + [CanBeNull] public MultiplayerScores Lower { get; set; } } } From 6d728d27fc7d150c313ec442b810772f174995cf Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 31 Jul 2020 17:58:48 +0900 Subject: [PATCH 0331/1782] Cleanup/consolidate indexing request --- .../Multi/Ranking/TimeshiftResultsScreen.cs | 52 +++++++++++-------- 1 file changed, 30 insertions(+), 22 deletions(-) diff --git a/osu.Game/Screens/Multi/Ranking/TimeshiftResultsScreen.cs b/osu.Game/Screens/Multi/Ranking/TimeshiftResultsScreen.cs index 648bee385c..0ef4953e83 100644 --- a/osu.Game/Screens/Multi/Ranking/TimeshiftResultsScreen.cs +++ b/osu.Game/Screens/Multi/Ranking/TimeshiftResultsScreen.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -75,21 +76,8 @@ namespace osu.Game.Screens.Multi.Ranking performSuccessCallback(scoresCallback, allScores); }; - userScoreReq.Failure += _ => - { - // Fallback to a normal index. - var indexReq = new IndexPlaylistScoresRequest(roomId, playlistItem.ID); - - indexReq.Success += r => - { - performSuccessCallback(scoresCallback, r.Scores); - lowerScores = r; - }; - - indexReq.Failure += __ => loadingLayer.Hide(); - - api.Queue(indexReq); - }; + // On failure, fallback to a normal index. + userScoreReq.Failure += _ => api.Queue(createIndexRequest(scoresCallback)); return userScoreReq; } @@ -103,16 +91,30 @@ namespace osu.Game.Screens.Multi.Ranking if (pivot?.Cursor == null) return null; - var indexReq = new IndexPlaylistScoresRequest(roomId, playlistItem.ID, pivot.Cursor, pivot.Params); + return createIndexRequest(scoresCallback, pivot); + } + + /// + /// Creates a with an optional score pivot. + /// + /// Does not queue the request. + /// The callback to perform with the resulting scores. + /// An optional score pivot to retrieve scores around. Can be null to retrieve scores from the highest score. + /// The indexing . + private APIRequest createIndexRequest(Action> scoresCallback, [CanBeNull] MultiplayerScores pivot = null) + { + var indexReq = pivot != null + ? new IndexPlaylistScoresRequest(roomId, playlistItem.ID, pivot.Cursor, pivot.Params) + : new IndexPlaylistScoresRequest(roomId, playlistItem.ID); indexReq.Success += r => { - if (direction == -1) - higherScores = r; - else + if (pivot == lowerScores) lowerScores = r; + else + higherScores = r; - performSuccessCallback(scoresCallback, r.Scores); + performSuccessCallback(scoresCallback, r.Scores, pivot); }; indexReq.Failure += _ => loadingLayer.Hide(); @@ -120,7 +122,13 @@ namespace osu.Game.Screens.Multi.Ranking return indexReq; } - private void performSuccessCallback(Action> callback, List scores) + /// + /// Transforms returned into s, ensure the is put into a sane state, and invokes a given success callback. + /// + /// The callback to invoke with the final s. + /// The s that were retrieved from s. + /// An optional pivot around which the scores were retrieved. + private void performSuccessCallback([NotNull] Action> callback, [NotNull] List scores, [CanBeNull] MultiplayerScores pivot = null) { var scoreInfos = new List(scores.Select(s => s.CreateScoreInfo(playlistItem))); @@ -136,7 +144,7 @@ namespace osu.Game.Screens.Multi.Ranking } // Invoke callback to add the scores. Exclude the user's current score which was added previously. - callback?.Invoke(scoreInfos.Where(s => s.ID != Score?.OnlineScoreID)); + callback.Invoke(scoreInfos.Where(s => s.ID != Score?.OnlineScoreID)); loadingLayer.Hide(); } From 9966d4f3b3b5da3c364157ab38dbadd0343152ae Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 31 Jul 2020 19:57:05 +0900 Subject: [PATCH 0332/1782] Add more loading spinners --- .../Multi/Ranking/TimeshiftResultsScreen.cs | 81 ++++++++++++++++--- osu.Game/Screens/Ranking/ResultsScreen.cs | 30 +++---- osu.Game/Screens/Ranking/ScorePanelList.cs | 10 +++ 3 files changed, 97 insertions(+), 24 deletions(-) diff --git a/osu.Game/Screens/Multi/Ranking/TimeshiftResultsScreen.cs b/osu.Game/Screens/Multi/Ranking/TimeshiftResultsScreen.cs index 0ef4953e83..87de9fd72a 100644 --- a/osu.Game/Screens/Multi/Ranking/TimeshiftResultsScreen.cs +++ b/osu.Game/Screens/Multi/Ranking/TimeshiftResultsScreen.cs @@ -22,7 +22,9 @@ namespace osu.Game.Screens.Multi.Ranking private readonly int roomId; private readonly PlaylistItem playlistItem; - private LoadingSpinner loadingLayer; + private LoadingSpinner leftLoadingLayer; + private LoadingSpinner centreLoadingLayer; + private LoadingSpinner rightLoadingLayer; private MultiplayerScores higherScores; private MultiplayerScores lowerScores; @@ -39,13 +41,29 @@ namespace osu.Game.Screens.Multi.Ranking [BackgroundDependencyLoader] private void load() { - AddInternal(loadingLayer = new LoadingLayer + AddInternal(new Container { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - X = -10, - State = { Value = Score == null ? Visibility.Visible : Visibility.Hidden }, - Padding = new MarginPadding { Bottom = TwoLayerButton.SIZE_EXTENDED.Y } + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Bottom = TwoLayerButton.SIZE_EXTENDED.Y }, + Children = new Drawable[] + { + leftLoadingLayer = new PanelListLoadingSpinner(ScorePanelList) + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.Centre, + }, + centreLoadingLayer = new PanelListLoadingSpinner(ScorePanelList) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + State = { Value = Score == null ? Visibility.Visible : Visibility.Hidden }, + }, + rightLoadingLayer = new PanelListLoadingSpinner(ScorePanelList) + { + Anchor = Anchor.CentreRight, + Origin = Anchor.Centre, + }, + } }); } @@ -91,6 +109,11 @@ namespace osu.Game.Screens.Multi.Ranking if (pivot?.Cursor == null) return null; + if (pivot == higherScores) + leftLoadingLayer.Show(); + else + rightLoadingLayer.Show(); + return createIndexRequest(scoresCallback, pivot); } @@ -114,10 +137,10 @@ namespace osu.Game.Screens.Multi.Ranking else higherScores = r; - performSuccessCallback(scoresCallback, r.Scores, pivot); + performSuccessCallback(scoresCallback, r.Scores, r); }; - indexReq.Failure += _ => loadingLayer.Hide(); + indexReq.Failure += _ => hideLoadingSpinners(pivot); return indexReq; } @@ -146,7 +169,45 @@ namespace osu.Game.Screens.Multi.Ranking // Invoke callback to add the scores. Exclude the user's current score which was added previously. callback.Invoke(scoreInfos.Where(s => s.ID != Score?.OnlineScoreID)); - loadingLayer.Hide(); + hideLoadingSpinners(pivot); + } + + private void hideLoadingSpinners([CanBeNull] MultiplayerScores pivot = null) + { + centreLoadingLayer.Hide(); + + if (pivot == lowerScores) + rightLoadingLayer.Hide(); + else if (pivot == higherScores) + leftLoadingLayer.Hide(); + } + + private class PanelListLoadingSpinner : LoadingSpinner + { + private readonly ScorePanelList list; + + /// + /// Creates a new . + /// + /// The list to track. + /// Whether the spinner should have a surrounding black box for visibility. + public PanelListLoadingSpinner(ScorePanelList list, bool withBox = true) + : base(withBox) + { + this.list = list; + } + + protected override void Update() + { + base.Update(); + + float panelOffset = list.DrawWidth / 2 - ScorePanel.EXPANDED_WIDTH; + + if ((Anchor & Anchor.x0) > 0) + X = (float)(panelOffset - list.Current); + else if ((Anchor & Anchor.x2) > 0) + X = (float)(list.ScrollableExtent - list.Current - panelOffset); + } } } } diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index 254ab76f5b..2506a63cc0 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -37,7 +37,8 @@ namespace osu.Game.Screens.Ranking public readonly Bindable SelectedScore = new Bindable(); public readonly ScoreInfo Score; - private readonly bool allowRetry; + + protected ScorePanelList ScorePanelList { get; private set; } [Resolved(CanBeNull = true)] private Player player { get; set; } @@ -47,12 +48,13 @@ namespace osu.Game.Screens.Ranking private StatisticsPanel statisticsPanel; private Drawable bottomPanel; - private ScorePanelList scorePanelList; private Container detachedPanelContainer; private bool fetchedInitialScores; private APIRequest nextPageRequest; + private readonly bool allowRetry; + protected ResultsScreen(ScoreInfo score, bool allowRetry = true) { Score = score; @@ -87,7 +89,7 @@ namespace osu.Game.Screens.Ranking RelativeSizeAxes = Axes.Both, Score = { BindTarget = SelectedScore } }, - scorePanelList = new ScorePanelList + ScorePanelList = new ScorePanelList { RelativeSizeAxes = Axes.Both, SelectedScore = { BindTarget = SelectedScore }, @@ -145,7 +147,7 @@ namespace osu.Game.Screens.Ranking }; if (Score != null) - scorePanelList.AddScore(Score); + ScorePanelList.AddScore(Score); if (player != null && allowRetry) { @@ -181,9 +183,9 @@ namespace osu.Game.Screens.Ranking if (fetchedInitialScores && nextPageRequest == null) { - if (scorePanelList.IsScrolledToStart) + if (ScorePanelList.IsScrolledToStart) nextPageRequest = FetchNextPage(-1, fetchScoresCallback); - else if (scorePanelList.IsScrolledToEnd) + else if (ScorePanelList.IsScrolledToEnd) nextPageRequest = FetchNextPage(1, fetchScoresCallback); if (nextPageRequest != null) @@ -249,7 +251,7 @@ namespace osu.Game.Screens.Ranking private void addScore(ScoreInfo score) { - var panel = scorePanelList.AddScore(score); + var panel = ScorePanelList.AddScore(score); if (detachedPanel != null) panel.Alpha = 0; @@ -262,11 +264,11 @@ namespace osu.Game.Screens.Ranking if (state.NewValue == Visibility.Visible) { // Detach the panel in its original location, and move into the desired location in the local container. - var expandedPanel = scorePanelList.GetPanelForScore(SelectedScore.Value); + var expandedPanel = ScorePanelList.GetPanelForScore(SelectedScore.Value); var screenSpacePos = expandedPanel.ScreenSpaceDrawQuad.TopLeft; // Detach and move into the local container. - scorePanelList.Detach(expandedPanel); + ScorePanelList.Detach(expandedPanel); detachedPanelContainer.Add(expandedPanel); // Move into its original location in the local container first, then to the final location. @@ -276,9 +278,9 @@ namespace osu.Game.Screens.Ranking .MoveTo(new Vector2(StatisticsPanel.SIDE_PADDING, origLocation.Y), 150, Easing.OutQuint); // Hide contracted panels. - foreach (var contracted in scorePanelList.GetScorePanels().Where(p => p.State == PanelState.Contracted)) + foreach (var contracted in ScorePanelList.GetScorePanels().Where(p => p.State == PanelState.Contracted)) contracted.FadeOut(150, Easing.OutQuint); - scorePanelList.HandleInput = false; + ScorePanelList.HandleInput = false; // Dim background. Background.FadeTo(0.1f, 150); @@ -291,7 +293,7 @@ namespace osu.Game.Screens.Ranking // Remove from the local container and re-attach. detachedPanelContainer.Remove(detachedPanel); - scorePanelList.Attach(detachedPanel); + ScorePanelList.Attach(detachedPanel); // Move into its original location in the attached container first, then to the final location. var origLocation = detachedPanel.Parent.ToLocalSpace(screenSpacePos); @@ -300,9 +302,9 @@ namespace osu.Game.Screens.Ranking .MoveTo(new Vector2(0, origLocation.Y), 150, Easing.OutQuint); // Show contracted panels. - foreach (var contracted in scorePanelList.GetScorePanels().Where(p => p.State == PanelState.Contracted)) + foreach (var contracted in ScorePanelList.GetScorePanels().Where(p => p.State == PanelState.Contracted)) contracted.FadeIn(150, Easing.OutQuint); - scorePanelList.HandleInput = true; + ScorePanelList.HandleInput = true; // Un-dim background. Background.FadeTo(0.5f, 150); diff --git a/osu.Game/Screens/Ranking/ScorePanelList.cs b/osu.Game/Screens/Ranking/ScorePanelList.cs index b2e1e91831..10bd99c8ce 100644 --- a/osu.Game/Screens/Ranking/ScorePanelList.cs +++ b/osu.Game/Screens/Ranking/ScorePanelList.cs @@ -41,6 +41,16 @@ namespace osu.Game.Screens.Ranking ///
public bool IsScrolledToEnd => flow.Count > 0 && scroll.ScrollableExtent > 0 && scroll.IsScrolledToEnd(scroll_endpoint_distance); + /// + /// The current scroll position. + /// + public double Current => scroll.Current; + + /// + /// The scrollable extent. + /// + public double ScrollableExtent => scroll.ScrollableExtent; + /// /// An action to be invoked if a is clicked while in an expanded state. /// From 4d2a677080beaed2a64c6ece28c663cf4d9f3aff Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 31 Jul 2020 20:33:18 +0900 Subject: [PATCH 0333/1782] Fix next track starting before previous one is paused Closes #9651. --- osu.Game/Overlays/MusicController.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index 546f7a1ec4..212d4d4850 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -262,7 +262,11 @@ namespace osu.Game.Overlays { if (beatmap is Bindable working) working.Value = beatmaps.GetWorkingBeatmap(playable.Beatmaps.First(), beatmap.Value); - beatmap.Value.Track.Restart(); + + // if not scheduled, the previously track will be stopped one frame later (see ScheduleAfterChildren logic in GameBase). + // we probably want to move this to a central method for switching to a new working beatmap in the future. + Schedule(() => beatmap.Value.Track.Restart()); + return true; } From 8e8a11bb72f558c0fb8144390bdab7e5d928ea73 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 31 Jul 2020 20:55:26 +0900 Subject: [PATCH 0334/1782] Add APIRequest.TriggerFailure() for testing --- osu.Game/Online/API/APIRequest.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/API/APIRequest.cs b/osu.Game/Online/API/APIRequest.cs index 2115326cc2..6912d9b629 100644 --- a/osu.Game/Online/API/APIRequest.cs +++ b/osu.Game/Online/API/APIRequest.cs @@ -136,6 +136,11 @@ namespace osu.Game.Online.API Success?.Invoke(); } + internal void TriggerFailure(Exception e) + { + Failure?.Invoke(e); + } + public void Cancel() => Fail(new OperationCanceledException(@"Request cancelled")); public void Fail(Exception e) @@ -166,7 +171,7 @@ namespace osu.Game.Online.API } Logger.Log($@"Failing request {this} ({e})", LoggingTarget.Network); - pendingFailure = () => Failure?.Invoke(e); + pendingFailure = () => TriggerFailure(e); checkAndScheduleFailure(); } From 2b77f99f56af81771980d77e8e5e57f02ff60f59 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 31 Jul 2020 20:55:44 +0900 Subject: [PATCH 0335/1782] Initialise some response parameters --- osu.Game/Online/API/Requests/Cursor.cs | 2 +- osu.Game/Online/Multiplayer/IndexScoresParams.cs | 2 +- osu.Game/Online/Multiplayer/MultiplayerScores.cs | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Online/API/Requests/Cursor.cs b/osu.Game/Online/API/Requests/Cursor.cs index f21445ca32..3de8db770c 100644 --- a/osu.Game/Online/API/Requests/Cursor.cs +++ b/osu.Game/Online/API/Requests/Cursor.cs @@ -15,6 +15,6 @@ namespace osu.Game.Online.API.Requests { [UsedImplicitly] [JsonExtensionData] - public IDictionary Properties; + public IDictionary Properties { get; set; } = new Dictionary(); } } diff --git a/osu.Game/Online/Multiplayer/IndexScoresParams.cs b/osu.Game/Online/Multiplayer/IndexScoresParams.cs index 8160dfefaf..a511e9a780 100644 --- a/osu.Game/Online/Multiplayer/IndexScoresParams.cs +++ b/osu.Game/Online/Multiplayer/IndexScoresParams.cs @@ -15,6 +15,6 @@ namespace osu.Game.Online.Multiplayer { [UsedImplicitly] [JsonExtensionData] - public IDictionary Properties; + public IDictionary Properties { get; set; } = new Dictionary(); } } diff --git a/osu.Game/Online/Multiplayer/MultiplayerScores.cs b/osu.Game/Online/Multiplayer/MultiplayerScores.cs index 8b8dd9e48d..7b9dcff828 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerScores.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerScores.cs @@ -16,12 +16,12 @@ namespace osu.Game.Online.Multiplayer /// The scores. ///
[JsonProperty("scores")] - public List Scores { get; set; } + public List Scores { get; set; } = new List(); /// /// The parameters to be used to fetch the next page. /// [JsonProperty("params")] - public IndexScoresParams Params { get; set; } + public IndexScoresParams Params { get; set; } = new IndexScoresParams(); } } From a4a4c8761241eabdf82a35fe02686aa5453c1d6e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 31 Jul 2020 21:21:48 +0900 Subject: [PATCH 0336/1782] Fix incorrect score id being used --- osu.Game/Screens/Multi/Ranking/TimeshiftResultsScreen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Multi/Ranking/TimeshiftResultsScreen.cs b/osu.Game/Screens/Multi/Ranking/TimeshiftResultsScreen.cs index 87de9fd72a..b0cf63a7a9 100644 --- a/osu.Game/Screens/Multi/Ranking/TimeshiftResultsScreen.cs +++ b/osu.Game/Screens/Multi/Ranking/TimeshiftResultsScreen.cs @@ -167,7 +167,7 @@ namespace osu.Game.Screens.Multi.Ranking } // Invoke callback to add the scores. Exclude the user's current score which was added previously. - callback.Invoke(scoreInfos.Where(s => s.ID != Score?.OnlineScoreID)); + callback.Invoke(scoreInfos.Where(s => s.OnlineScoreID != Score?.OnlineScoreID)); hideLoadingSpinners(pivot); } From 17018ffa8b616fe8da85c7acce17ad3744c9c018 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 31 Jul 2020 21:33:04 +0900 Subject: [PATCH 0337/1782] Fix potentially triggering new requests too early --- osu.Game/Screens/Ranking/ResultsScreen.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index 2506a63cc0..c95cf1066e 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -190,8 +190,9 @@ namespace osu.Game.Screens.Ranking if (nextPageRequest != null) { - nextPageRequest.Success += () => nextPageRequest = null; - nextPageRequest.Failure += _ => nextPageRequest = null; + // Scheduled after children to give the list a chance to update its scroll position and not potentially trigger a second request too early. + nextPageRequest.Success += () => ScheduleAfterChildren(() => nextPageRequest = null); + nextPageRequest.Failure += _ => ScheduleAfterChildren(() => nextPageRequest = null); api.Queue(nextPageRequest); } From f1e721e396988d5410b32fa3402b612f59dca23d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 31 Jul 2020 21:39:50 +0900 Subject: [PATCH 0338/1782] Rewrite test scene and add more tests --- .../TestSceneTimeshiftResultsScreen.cs | 344 +++++++++++++++--- .../Multiplayer/IndexPlaylistScoresRequest.cs | 31 +- .../Multi/Ranking/TimeshiftResultsScreen.cs | 23 +- 3 files changed, 329 insertions(+), 69 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneTimeshiftResultsScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneTimeshiftResultsScreen.cs index 8b6d6694c3..628d08a314 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneTimeshiftResultsScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneTimeshiftResultsScreen.cs @@ -3,14 +3,24 @@ using System; using System.Collections.Generic; +using System.Linq; +using System.Net; using System.Threading.Tasks; +using JetBrains.Annotations; +using Newtonsoft.Json.Linq; using NUnit.Framework; +using osu.Framework.Graphics.Containers; +using osu.Framework.Testing; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.UserInterface; using osu.Game.Online.API; +using osu.Game.Online.API.Requests; using osu.Game.Online.Multiplayer; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; using osu.Game.Screens.Multi.Ranking; +using osu.Game.Screens.Ranking; using osu.Game.Tests.Beatmaps; using osu.Game.Users; @@ -18,43 +28,134 @@ namespace osu.Game.Tests.Visual.Multiplayer { public class TestSceneTimeshiftResultsScreen : ScreenTestScene { - private bool roomsReceived; + private const int scores_per_result = 10; + + private TestResultsScreen resultsScreen; + private int currentScoreId; + private bool requestComplete; [SetUp] public void Setup() => Schedule(() => { - roomsReceived = false; + currentScoreId = 0; + requestComplete = false; bindHandler(); }); [Test] - public void TestShowResultsWithScore() + public void TestShowWithUserScore() { - createResults(new TestScoreInfo(new OsuRuleset().RulesetInfo)); - AddWaitStep("wait for display", 5); + ScoreInfo userScore = null; + + AddStep("bind user score info handler", () => + { + userScore = new TestScoreInfo(new OsuRuleset().RulesetInfo) { OnlineScoreID = currentScoreId++ }; + bindHandler(userScore: userScore); + }); + + createResults(() => userScore); + waitForDisplay(); + + AddAssert("user score selected", () => this.ChildrenOfType().Single(p => p.Score.OnlineScoreID == userScore.OnlineScoreID).State == PanelState.Expanded); } [Test] - public void TestShowResultsNullScore() + public void TestShowNullUserScore() { - createResults(null); - AddWaitStep("wait for display", 5); + createResults(); + waitForDisplay(); + + AddAssert("top score selected", () => this.ChildrenOfType().OrderByDescending(p => p.Score.TotalScore).First().State == PanelState.Expanded); } [Test] - public void TestShowResultsNullScoreWithDelay() + public void TestShowUserScoreWithDelay() + { + ScoreInfo userScore = null; + + AddStep("bind user score info handler", () => + { + userScore = new TestScoreInfo(new OsuRuleset().RulesetInfo) { OnlineScoreID = currentScoreId++ }; + bindHandler(3000, userScore); + }); + + createResults(() => userScore); + waitForDisplay(); + + AddAssert("more than 1 panel displayed", () => this.ChildrenOfType().Count() > 1); + AddAssert("user score selected", () => this.ChildrenOfType().Single(p => p.Score.OnlineScoreID == userScore.OnlineScoreID).State == PanelState.Expanded); + } + + [Test] + public void TestShowNullUserScoreWithDelay() { AddStep("bind delayed handler", () => bindHandler(3000)); - createResults(null); - AddUntilStep("wait for rooms to be received", () => roomsReceived); - AddWaitStep("wait for display", 5); + + createResults(); + waitForDisplay(); + + AddAssert("top score selected", () => this.ChildrenOfType().OrderByDescending(p => p.Score.TotalScore).First().State == PanelState.Expanded); } - private void createResults(ScoreInfo score) + [Test] + public void TestFetchWhenScrolledToTheRight() + { + createResults(); + waitForDisplay(); + + AddStep("bind delayed handler", () => bindHandler(3000)); + + for (int i = 0; i < 2; i++) + { + int beforePanelCount = 0; + + AddStep("get panel count", () => beforePanelCount = this.ChildrenOfType().Count()); + AddStep("scroll to right", () => resultsScreen.ScorePanelList.ChildrenOfType().Single().ScrollToEnd(false)); + + AddAssert("right loading spinner shown", () => resultsScreen.RightSpinner.State.Value == Visibility.Visible); + waitForDisplay(); + + AddAssert($"count increased by {scores_per_result}", () => this.ChildrenOfType().Count() == beforePanelCount + scores_per_result); + AddAssert("right loading spinner hidden", () => resultsScreen.RightSpinner.State.Value == Visibility.Hidden); + } + } + + [Test] + public void TestFetchWhenScrolledToTheLeft() + { + ScoreInfo userScore = null; + + AddStep("bind user score info handler", () => + { + userScore = new TestScoreInfo(new OsuRuleset().RulesetInfo) { OnlineScoreID = currentScoreId++ }; + bindHandler(userScore: userScore); + }); + + createResults(() => userScore); + waitForDisplay(); + + AddStep("bind delayed handler", () => bindHandler(3000)); + + for (int i = 0; i < 2; i++) + { + int beforePanelCount = 0; + + AddStep("get panel count", () => beforePanelCount = this.ChildrenOfType().Count()); + AddStep("scroll to left", () => resultsScreen.ScorePanelList.ChildrenOfType().Single().ScrollToStart(false)); + + AddAssert("left loading spinner shown", () => resultsScreen.LeftSpinner.State.Value == Visibility.Visible); + waitForDisplay(); + + AddAssert($"count increased by {scores_per_result}", () => this.ChildrenOfType().Count() == beforePanelCount + scores_per_result); + AddAssert("left loading spinner hidden", () => resultsScreen.LeftSpinner.State.Value == Visibility.Hidden); + } + } + + private void createResults(Func getScore = null) { AddStep("load results", () => { - LoadScreen(new TimeshiftResultsScreen(score, 1, new PlaylistItem + LoadScreen(resultsScreen = new TestResultsScreen(getScore?.Invoke(), 1, new PlaylistItem { Beatmap = { Value = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo }, Ruleset = { Value = new OsuRuleset().RulesetInfo } @@ -62,62 +163,213 @@ namespace osu.Game.Tests.Visual.Multiplayer }); } - private void bindHandler(double delay = 0) + private void waitForDisplay() { - var roomScores = new List(); + AddUntilStep("wait for request to complete", () => requestComplete); + AddWaitStep("wait for display", 5); + } - for (int i = 0; i < 10; i++) + private void bindHandler(double delay = 0, ScoreInfo userScore = null, bool failRequests = false) => ((DummyAPIAccess)API).HandleRequest = request => + { + requestComplete = false; + + if (failRequests) { - roomScores.Add(new MultiplayerScore + triggerFail(request, delay); + return; + } + + switch (request) + { + case ShowPlaylistUserScoreRequest s: + if (userScore == null) + triggerFail(s, delay); + else + triggerSuccess(s, createUserResponse(userScore), delay); + break; + + case IndexPlaylistScoresRequest i: + triggerSuccess(i, createIndexResponse(i), delay); + break; + } + }; + + private void triggerSuccess(APIRequest req, T result, double delay) + where T : class + { + if (delay == 0) + success(); + else + { + Task.Run(async () => { - ID = i, - Accuracy = 0.9 - 0.01 * i, - EndedAt = DateTimeOffset.Now.Subtract(TimeSpan.FromHours(i)), + await Task.Delay(TimeSpan.FromMilliseconds(delay)); + Schedule(success); + }); + } + + void success() + { + requestComplete = true; + req.TriggerSuccess(result); + } + } + + private void triggerFail(APIRequest req, double delay) + { + if (delay == 0) + fail(); + else + { + Task.Run(async () => + { + await Task.Delay(TimeSpan.FromMilliseconds(delay)); + Schedule(fail); + }); + } + + void fail() + { + requestComplete = true; + req.TriggerFailure(new WebException("Failed.")); + } + } + + private MultiplayerScore createUserResponse([NotNull] ScoreInfo userScore) + { + var multiplayerUserScore = new MultiplayerScore + { + ID = (int)(userScore.OnlineScoreID ?? currentScoreId++), + Accuracy = userScore.Accuracy, + EndedAt = userScore.Date, + Passed = userScore.Passed, + Rank = userScore.Rank, + MaxCombo = userScore.MaxCombo, + TotalScore = userScore.TotalScore, + User = userScore.User, + Statistics = userScore.Statistics, + ScoresAround = new MultiplayerScoresAround + { + Higher = new MultiplayerScores(), + Lower = new MultiplayerScores() + } + }; + + for (int i = 1; i <= scores_per_result; i++) + { + multiplayerUserScore.ScoresAround.Lower.Scores.Add(new MultiplayerScore + { + ID = currentScoreId++, + Accuracy = userScore.Accuracy, + EndedAt = userScore.Date, Passed = true, - Rank = ScoreRank.B, - MaxCombo = 999, - TotalScore = 999999 - i * 1000, + Rank = userScore.Rank, + MaxCombo = userScore.MaxCombo, + TotalScore = userScore.TotalScore - i, User = new User { Id = 2, Username = $"peppy{i}", CoverUrl = "https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", }, - Statistics = + Statistics = userScore.Statistics + }); + + multiplayerUserScore.ScoresAround.Higher.Scores.Add(new MultiplayerScore + { + ID = currentScoreId++, + Accuracy = userScore.Accuracy, + EndedAt = userScore.Date, + Passed = true, + Rank = userScore.Rank, + MaxCombo = userScore.MaxCombo, + TotalScore = userScore.TotalScore + i, + User = new User + { + Id = 2, + Username = $"peppy{i}", + CoverUrl = "https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", + }, + Statistics = userScore.Statistics + }); + } + + addCursor(multiplayerUserScore.ScoresAround.Lower); + addCursor(multiplayerUserScore.ScoresAround.Higher); + + return multiplayerUserScore; + } + + private IndexedMultiplayerScores createIndexResponse(IndexPlaylistScoresRequest req) + { + var result = new IndexedMultiplayerScores(); + + long startTotalScore = req.Cursor?.Properties["total_score"].ToObject() ?? 1000000; + string sort = req.IndexParams?.Properties["sort"].ToObject() ?? "score_desc"; + + for (int i = 1; i <= scores_per_result; i++) + { + result.Scores.Add(new MultiplayerScore + { + ID = currentScoreId++, + Accuracy = 1, + EndedAt = DateTimeOffset.Now, + Passed = true, + Rank = ScoreRank.X, + MaxCombo = 1000, + TotalScore = startTotalScore + (sort == "score_asc" ? i : -i), + User = new User + { + Id = 2, + Username = $"peppy{i}", + CoverUrl = "https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", + }, + Statistics = new Dictionary { { HitResult.Miss, 1 }, { HitResult.Meh, 50 }, { HitResult.Good, 100 }, - { HitResult.Great, 300 }, + { HitResult.Great, 300 } } }); } - ((DummyAPIAccess)API).HandleRequest = request => + addCursor(result); + + return result; + } + + private void addCursor(MultiplayerScores scores) + { + scores.Cursor = new Cursor { - switch (request) + Properties = new Dictionary { - case IndexPlaylistScoresRequest r: - if (delay == 0) - success(); - else - { - Task.Run(async () => - { - await Task.Delay(TimeSpan.FromMilliseconds(delay)); - Schedule(success); - }); - } + { "total_score", JToken.FromObject(scores.Scores[^1].TotalScore) }, + { "score_id", JToken.FromObject(scores.Scores[^1].ID) }, + } + }; - void success() - { - r.TriggerSuccess(new IndexedMultiplayerScores { Scores = roomScores }); - roomsReceived = true; - } - - break; + scores.Params = new IndexScoresParams + { + Properties = new Dictionary + { + { "sort", JToken.FromObject(scores.Scores[^1].TotalScore > scores.Scores[^2].TotalScore ? "score_asc" : "score_desc") } } }; } + + private class TestResultsScreen : TimeshiftResultsScreen + { + public new LoadingSpinner LeftSpinner => base.LeftSpinner; + public new LoadingSpinner CentreSpinner => base.CentreSpinner; + public new LoadingSpinner RightSpinner => base.RightSpinner; + public new ScorePanelList ScorePanelList => base.ScorePanelList; + + public TestResultsScreen(ScoreInfo score, int roomId, PlaylistItem playlistItem, bool allowRetry = true) + : base(score, roomId, playlistItem, allowRetry) + { + } + } } } diff --git a/osu.Game/Online/Multiplayer/IndexPlaylistScoresRequest.cs b/osu.Game/Online/Multiplayer/IndexPlaylistScoresRequest.cs index 91f24933e1..684d0aecd8 100644 --- a/osu.Game/Online/Multiplayer/IndexPlaylistScoresRequest.cs +++ b/osu.Game/Online/Multiplayer/IndexPlaylistScoresRequest.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 JetBrains.Annotations; using osu.Framework.IO.Network; using osu.Game.Extensions; @@ -14,39 +15,45 @@ namespace osu.Game.Online.Multiplayer ///
public class IndexPlaylistScoresRequest : APIRequest { - private readonly int roomId; - private readonly int playlistItemId; - private readonly Cursor cursor; - private readonly IndexScoresParams indexParams; + public readonly int RoomId; + public readonly int PlaylistItemId; + + [CanBeNull] + public readonly Cursor Cursor; + + [CanBeNull] + public readonly IndexScoresParams IndexParams; public IndexPlaylistScoresRequest(int roomId, int playlistItemId) { - this.roomId = roomId; - this.playlistItemId = playlistItemId; + RoomId = roomId; + PlaylistItemId = playlistItemId; } public IndexPlaylistScoresRequest(int roomId, int playlistItemId, [NotNull] Cursor cursor, [NotNull] IndexScoresParams indexParams) : this(roomId, playlistItemId) { - this.cursor = cursor; - this.indexParams = indexParams; + Cursor = cursor; + IndexParams = indexParams; } protected override WebRequest CreateWebRequest() { var req = base.CreateWebRequest(); - if (cursor != null) + if (Cursor != null) { - req.AddCursor(cursor); + Debug.Assert(IndexParams != null); - foreach (var (key, value) in indexParams.Properties) + req.AddCursor(Cursor); + + foreach (var (key, value) in IndexParams.Properties) req.AddParameter(key, value.ToString()); } return req; } - protected override string Target => $@"rooms/{roomId}/playlist/{playlistItemId}/scores"; + protected override string Target => $@"rooms/{RoomId}/playlist/{PlaylistItemId}/scores"; } } diff --git a/osu.Game/Screens/Multi/Ranking/TimeshiftResultsScreen.cs b/osu.Game/Screens/Multi/Ranking/TimeshiftResultsScreen.cs index b0cf63a7a9..e212bd4a82 100644 --- a/osu.Game/Screens/Multi/Ranking/TimeshiftResultsScreen.cs +++ b/osu.Game/Screens/Multi/Ranking/TimeshiftResultsScreen.cs @@ -22,9 +22,10 @@ namespace osu.Game.Screens.Multi.Ranking private readonly int roomId; private readonly PlaylistItem playlistItem; - private LoadingSpinner leftLoadingLayer; - private LoadingSpinner centreLoadingLayer; - private LoadingSpinner rightLoadingLayer; + protected LoadingSpinner LeftSpinner { get; private set; } + protected LoadingSpinner CentreSpinner { get; private set; } + protected LoadingSpinner RightSpinner { get; private set; } + private MultiplayerScores higherScores; private MultiplayerScores lowerScores; @@ -47,18 +48,18 @@ namespace osu.Game.Screens.Multi.Ranking Padding = new MarginPadding { Bottom = TwoLayerButton.SIZE_EXTENDED.Y }, Children = new Drawable[] { - leftLoadingLayer = new PanelListLoadingSpinner(ScorePanelList) + LeftSpinner = new PanelListLoadingSpinner(ScorePanelList) { Anchor = Anchor.CentreLeft, Origin = Anchor.Centre, }, - centreLoadingLayer = new PanelListLoadingSpinner(ScorePanelList) + CentreSpinner = new PanelListLoadingSpinner(ScorePanelList) { Anchor = Anchor.Centre, Origin = Anchor.Centre, State = { Value = Score == null ? Visibility.Visible : Visibility.Hidden }, }, - rightLoadingLayer = new PanelListLoadingSpinner(ScorePanelList) + RightSpinner = new PanelListLoadingSpinner(ScorePanelList) { Anchor = Anchor.CentreRight, Origin = Anchor.Centre, @@ -110,9 +111,9 @@ namespace osu.Game.Screens.Multi.Ranking return null; if (pivot == higherScores) - leftLoadingLayer.Show(); + LeftSpinner.Show(); else - rightLoadingLayer.Show(); + RightSpinner.Show(); return createIndexRequest(scoresCallback, pivot); } @@ -174,12 +175,12 @@ namespace osu.Game.Screens.Multi.Ranking private void hideLoadingSpinners([CanBeNull] MultiplayerScores pivot = null) { - centreLoadingLayer.Hide(); + CentreSpinner.Hide(); if (pivot == lowerScores) - rightLoadingLayer.Hide(); + RightSpinner.Hide(); else if (pivot == higherScores) - leftLoadingLayer.Hide(); + LeftSpinner.Hide(); } private class PanelListLoadingSpinner : LoadingSpinner From e8f75a78e8ce4d820d72d05efacc6cec70f51264 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 31 Jul 2020 22:02:12 +0900 Subject: [PATCH 0339/1782] Also fix second instance of same execution --- osu.Game/Overlays/MusicController.cs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index 212d4d4850..a990f9a6ab 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -236,8 +236,8 @@ namespace osu.Game.Overlays { if (beatmap is Bindable working) working.Value = beatmaps.GetWorkingBeatmap(playable.Beatmaps.First(), beatmap.Value); - beatmap.Value.Track.Restart(); + restartTrack(); return PreviousTrackResult.Previous; } @@ -263,16 +263,20 @@ namespace osu.Game.Overlays if (beatmap is Bindable working) working.Value = beatmaps.GetWorkingBeatmap(playable.Beatmaps.First(), beatmap.Value); - // if not scheduled, the previously track will be stopped one frame later (see ScheduleAfterChildren logic in GameBase). - // we probably want to move this to a central method for switching to a new working beatmap in the future. - Schedule(() => beatmap.Value.Track.Restart()); - + restartTrack(); return true; } return false; } + private void restartTrack() + { + // if not scheduled, the previously track will be stopped one frame later (see ScheduleAfterChildren logic in GameBase). + // we probably want to move this to a central method for switching to a new working beatmap in the future. + Schedule(() => beatmap.Value.Track.Restart()); + } + private WorkingBeatmap current; private TrackChangeDirection? queuedDirection; From b361761d869950fd802df2ae551e6819037233da Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 28 Jul 2020 22:08:10 +0900 Subject: [PATCH 0340/1782] Add position display in contracted score panels --- .../Online/Multiplayer/MultiplayerScore.cs | 3 +- osu.Game/Scoring/ScoreInfo.cs | 7 ++++ .../Contracted/ContractedPanelTopContent.cs | 37 +++++++++++++++++++ osu.Game/Screens/Ranking/ScorePanel.cs | 1 + osu.Game/Tests/TestScoreInfo.cs | 2 + 5 files changed, 49 insertions(+), 1 deletion(-) create mode 100644 osu.Game/Screens/Ranking/Contracted/ContractedPanelTopContent.cs diff --git a/osu.Game/Online/Multiplayer/MultiplayerScore.cs b/osu.Game/Online/Multiplayer/MultiplayerScore.cs index 1793ba72ef..8191003aad 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerScore.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerScore.cs @@ -80,7 +80,8 @@ namespace osu.Game.Online.Multiplayer Date = EndedAt, Hash = string.Empty, // todo: temporary? Rank = Rank, - Mods = Mods?.Select(m => m.ToMod(rulesetInstance)).ToArray() ?? Array.Empty() + Mods = Mods?.Select(m => m.ToMod(rulesetInstance)).ToArray() ?? Array.Empty(), + Position = Position, }; return scoreInfo; diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index 84c0d5b54e..efcf1737c9 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -179,6 +179,13 @@ namespace osu.Game.Scoring [JsonIgnore] public bool DeletePending { get; set; } + /// + /// The position of this score, starting at 1. + /// + [NotMapped] + [JsonProperty("position")] + public int? Position { get; set; } + [Serializable] protected class DeserializedMod : IMod { diff --git a/osu.Game/Screens/Ranking/Contracted/ContractedPanelTopContent.cs b/osu.Game/Screens/Ranking/Contracted/ContractedPanelTopContent.cs new file mode 100644 index 0000000000..0935ee7fb2 --- /dev/null +++ b/osu.Game/Screens/Ranking/Contracted/ContractedPanelTopContent.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.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Scoring; + +namespace osu.Game.Screens.Ranking.Contracted +{ + public class ContractedPanelTopContent : CompositeDrawable + { + private readonly ScoreInfo score; + + public ContractedPanelTopContent(ScoreInfo score) + { + this.score = score; + + RelativeSizeAxes = Axes.Both; + } + + [BackgroundDependencyLoader] + private void load() + { + InternalChild = new OsuSpriteText + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Y = 6, + Text = score.Position != null ? $"#{score.Position}" : string.Empty, + Font = OsuFont.GetFont(size: 18, weight: FontWeight.Bold) + }; + } + } +} diff --git a/osu.Game/Screens/Ranking/ScorePanel.cs b/osu.Game/Screens/Ranking/ScorePanel.cs index 5da432d5b2..b32da805e4 100644 --- a/osu.Game/Screens/Ranking/ScorePanel.cs +++ b/osu.Game/Screens/Ranking/ScorePanel.cs @@ -213,6 +213,7 @@ namespace osu.Game.Screens.Ranking topLayerBackground.FadeColour(contracted_top_layer_colour, resize_duration, Easing.OutQuint); middleLayerBackground.FadeColour(contracted_middle_layer_colour, resize_duration, Easing.OutQuint); + topLayerContentContainer.Add(middleLayerContent = new ContractedPanelTopContent(Score).With(d => d.Alpha = 0)); middleLayerContentContainer.Add(topLayerContent = new ContractedPanelMiddleContent(Score).With(d => d.Alpha = 0)); break; } diff --git a/osu.Game/Tests/TestScoreInfo.cs b/osu.Game/Tests/TestScoreInfo.cs index 1193a29d70..31cced6ce4 100644 --- a/osu.Game/Tests/TestScoreInfo.cs +++ b/osu.Game/Tests/TestScoreInfo.cs @@ -37,6 +37,8 @@ namespace osu.Game.Tests Statistics[HitResult.Meh] = 50; Statistics[HitResult.Good] = 100; Statistics[HitResult.Great] = 300; + + Position = 1; } private class TestModHardRock : ModHardRock From 4f3795486d34f0db6e97a04b53ba23d5e1be8048 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 31 Jul 2020 22:36:37 +0900 Subject: [PATCH 0341/1782] Post-process responses to populate positions --- .../TestSceneTimeshiftResultsScreen.cs | 1 + .../Multi/Ranking/TimeshiftResultsScreen.cs | 36 +++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneTimeshiftResultsScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneTimeshiftResultsScreen.cs index 628d08a314..03fd2b968c 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneTimeshiftResultsScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneTimeshiftResultsScreen.cs @@ -244,6 +244,7 @@ namespace osu.Game.Tests.Visual.Multiplayer EndedAt = userScore.Date, Passed = userScore.Passed, Rank = userScore.Rank, + Position = 200, MaxCombo = userScore.MaxCombo, TotalScore = userScore.TotalScore, User = userScore.User, diff --git a/osu.Game/Screens/Multi/Ranking/TimeshiftResultsScreen.cs b/osu.Game/Screens/Multi/Ranking/TimeshiftResultsScreen.cs index e212bd4a82..232f368fb3 100644 --- a/osu.Game/Screens/Multi/Ranking/TimeshiftResultsScreen.cs +++ b/osu.Game/Screens/Multi/Ranking/TimeshiftResultsScreen.cs @@ -84,12 +84,18 @@ namespace osu.Game.Screens.Multi.Ranking { allScores.AddRange(userScore.ScoresAround.Higher.Scores); higherScores = userScore.ScoresAround.Higher; + + Debug.Assert(userScore.Position != null); + setPositions(higherScores, userScore.Position.Value, -1); } if (userScore.ScoresAround?.Lower != null) { allScores.AddRange(userScore.ScoresAround.Lower.Scores); lowerScores = userScore.ScoresAround.Lower; + + Debug.Assert(userScore.Position != null); + setPositions(lowerScores, userScore.Position.Value, 1); } performSuccessCallback(scoresCallback, allScores); @@ -134,9 +140,15 @@ namespace osu.Game.Screens.Multi.Ranking indexReq.Success += r => { if (pivot == lowerScores) + { lowerScores = r; + setPositions(r, pivot, 1); + } else + { higherScores = r; + setPositions(r, pivot, -1); + } performSuccessCallback(scoresCallback, r.Scores, r); }; @@ -183,6 +195,30 @@ namespace osu.Game.Screens.Multi.Ranking LeftSpinner.Hide(); } + /// + /// Applies positions to all s from a given pivot. + /// + /// The to set positions on. + /// The pivot. + /// The amount to increment the pivot position by for each in . + private void setPositions([NotNull] MultiplayerScores scores, [CanBeNull] MultiplayerScores pivot, int increment) + => setPositions(scores, pivot?.Scores[^1].Position ?? 0, increment); + + /// + /// Applies positions to all s from a given pivot. + /// + /// The to set positions on. + /// The pivot position. + /// The amount to increment the pivot position by for each in . + private void setPositions([NotNull] MultiplayerScores scores, int pivotPosition, int increment) + { + foreach (var s in scores.Scores) + { + pivotPosition += increment; + s.Position = pivotPosition; + } + } + private class PanelListLoadingSpinner : LoadingSpinner { private readonly ScorePanelList list; From 308f8bf9bf1b5eb7a929d8f1a19c0746d908bbed Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 31 Jul 2020 23:11:42 +0900 Subject: [PATCH 0342/1782] Fix inverted naming --- osu.Game/Screens/Ranking/ScorePanel.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Ranking/ScorePanel.cs b/osu.Game/Screens/Ranking/ScorePanel.cs index ae55f6e0ae..1904da7094 100644 --- a/osu.Game/Screens/Ranking/ScorePanel.cs +++ b/osu.Game/Screens/Ranking/ScorePanel.cs @@ -203,8 +203,8 @@ namespace osu.Game.Screens.Ranking topLayerBackground.FadeColour(expanded_top_layer_colour, resize_duration, Easing.OutQuint); middleLayerBackground.FadeColour(expanded_middle_layer_colour, resize_duration, Easing.OutQuint); - topLayerContentContainer.Add(middleLayerContent = new ExpandedPanelTopContent(Score.User).With(d => d.Alpha = 0)); - middleLayerContentContainer.Add(topLayerContent = new ExpandedPanelMiddleContent(Score).With(d => d.Alpha = 0)); + topLayerContentContainer.Add(topLayerContent = new ExpandedPanelTopContent(Score.User).With(d => d.Alpha = 0)); + middleLayerContentContainer.Add(middleLayerContent = new ExpandedPanelMiddleContent(Score).With(d => d.Alpha = 0)); break; case PanelState.Contracted: @@ -213,8 +213,8 @@ namespace osu.Game.Screens.Ranking topLayerBackground.FadeColour(contracted_top_layer_colour, resize_duration, Easing.OutQuint); middleLayerBackground.FadeColour(contracted_middle_layer_colour, resize_duration, Easing.OutQuint); - topLayerContentContainer.Add(middleLayerContent = new ContractedPanelTopContent(Score).With(d => d.Alpha = 0)); - middleLayerContentContainer.Add(topLayerContent = new ContractedPanelMiddleContent(Score).With(d => d.Alpha = 0)); + topLayerContentContainer.Add(topLayerContent = new ContractedPanelTopContent(Score).With(d => d.Alpha = 0)); + middleLayerContentContainer.Add(middleLayerContent = new ContractedPanelMiddleContent(Score).With(d => d.Alpha = 0)); break; } From 04b71a0c7c3fb0420dccfb645944d4687dd4588d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 31 Jul 2020 23:16:55 +0900 Subject: [PATCH 0343/1782] Adjust xmldoc --- osu.Game/Screens/Multi/Ranking/TimeshiftResultsScreen.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Multi/Ranking/TimeshiftResultsScreen.cs b/osu.Game/Screens/Multi/Ranking/TimeshiftResultsScreen.cs index 232f368fb3..8da6a530a8 100644 --- a/osu.Game/Screens/Multi/Ranking/TimeshiftResultsScreen.cs +++ b/osu.Game/Screens/Multi/Ranking/TimeshiftResultsScreen.cs @@ -196,7 +196,7 @@ namespace osu.Game.Screens.Multi.Ranking } /// - /// Applies positions to all s from a given pivot. + /// Applies positions to all s referenced to a given pivot. /// /// The to set positions on. /// The pivot. @@ -205,7 +205,7 @@ namespace osu.Game.Screens.Multi.Ranking => setPositions(scores, pivot?.Scores[^1].Position ?? 0, increment); /// - /// Applies positions to all s from a given pivot. + /// Applies positions to all s referenced to a given pivot. /// /// The to set positions on. /// The pivot position. From 9e244be4890aacc1ff0c1534fcd860817e507eae Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 1 Aug 2020 00:05:04 +0900 Subject: [PATCH 0344/1782] Use better conditional for choosing which spinner type to use Co-authored-by: Dan Balasescu --- osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs index 44f431d6f7..81d1d05b66 100644 --- a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs @@ -104,9 +104,11 @@ namespace osu.Game.Rulesets.Osu.Skinning }; case OsuSkinComponents.SpinnerBody: - if (Source.GetTexture("spinner-top") != null) + bool hasBackground = Source.GetTexture("spinner-background") != null; + + if (Source.GetTexture("spinner-top") != null && !hasBackground) return new LegacyNewStyleSpinner(); - else if (Source.GetTexture("spinner-background") != null) + else if (hasBackground) return new LegacyOldStyleSpinner(); return null; From bb01ee5be953d6a241830f8a08e3a029298f9e25 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 1 Aug 2020 00:27:00 +0900 Subject: [PATCH 0345/1782] Fix trackign alpha not being applied --- .../Drawables/Pieces/DefaultSpinnerDisc.cs | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/DefaultSpinnerDisc.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/DefaultSpinnerDisc.cs index 79bfeedd07..eba5d869c0 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/DefaultSpinnerDisc.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/DefaultSpinnerDisc.cs @@ -100,13 +100,20 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces { base.Update(); - if (drawableSpinner.RotationTracker.Complete.Value && checkNewRotationCount) + if (drawableSpinner.RotationTracker.Complete.Value) { - fill.FinishTransforms(false, nameof(Alpha)); - fill - .FadeTo(tracking_alpha + 0.2f, 60, Easing.OutExpo) - .Then() - .FadeTo(tracking_alpha, 250, Easing.OutQuint); + if (checkNewRotationCount) + { + fill.FinishTransforms(false, nameof(Alpha)); + fill + .FadeTo(tracking_alpha + 0.2f, 60, Easing.OutExpo) + .Then() + .FadeTo(tracking_alpha, 250, Easing.OutQuint); + } + } + else + { + fill.Alpha = (float)Interpolation.Damp(fill.Alpha, drawableSpinner.RotationTracker.Tracking ? tracking_alpha : idle_alpha, 0.98f, (float)Clock.ElapsedFrameTime); } const float initial_scale = 0.2f; From 180afff80515bc09a0833d1acb45a3bac243a958 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 1 Aug 2020 00:39:04 +0900 Subject: [PATCH 0346/1782] Ensure damp is always positive exponent --- .../Objects/Drawables/Pieces/DefaultSpinnerDisc.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/DefaultSpinnerDisc.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/DefaultSpinnerDisc.cs index eba5d869c0..dfb692eba9 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/DefaultSpinnerDisc.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/DefaultSpinnerDisc.cs @@ -113,7 +113,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces } else { - fill.Alpha = (float)Interpolation.Damp(fill.Alpha, drawableSpinner.RotationTracker.Tracking ? tracking_alpha : idle_alpha, 0.98f, (float)Clock.ElapsedFrameTime); + fill.Alpha = (float)Interpolation.Damp(fill.Alpha, drawableSpinner.RotationTracker.Tracking ? tracking_alpha : idle_alpha, 0.98f, (float)Math.Abs(Clock.ElapsedFrameTime)); } const float initial_scale = 0.2f; From 74f70136fdf80faa47d53772c7c4f386d5e6c3e1 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 1 Aug 2020 06:00:24 +0300 Subject: [PATCH 0347/1782] Implement DashboardBeatmapPanel component --- .../TestSceneDashboardBeatmapPanel.cs | 52 ++++++ .../Dashboard/DashboardBeatmapPanel.cs | 159 ++++++++++++++++++ 2 files changed, 211 insertions(+) create mode 100644 osu.Game.Tests/Visual/UserInterface/TestSceneDashboardBeatmapPanel.cs create mode 100644 osu.Game/Overlays/Dashboard/Dashboard/DashboardBeatmapPanel.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneDashboardBeatmapPanel.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneDashboardBeatmapPanel.cs new file mode 100644 index 0000000000..a61cd37513 --- /dev/null +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneDashboardBeatmapPanel.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 osu.Framework.Graphics.Containers; +using osu.Framework.Graphics; +using osu.Game.Overlays.Dashboard.Dashboard; +using osu.Game.Beatmaps; +using osu.Game.Overlays; +using osu.Framework.Allocation; +using osu.Game.Users; + +namespace osu.Game.Tests.Visual.UserInterface +{ + public class TestSceneDashboardBeatmapPanel : OsuTestScene + { + [Cached] + private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple); + + public TestSceneDashboardBeatmapPanel() + { + Add(new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AutoSizeAxes = Axes.Y, + Width = 300, + Child = new DashboardBeatmapPanel(beatmap_set) + }); + } + + private static readonly BeatmapSetInfo beatmap_set = new BeatmapSetInfo + { + Metadata = new BeatmapMetadata + { + Title = "Very Long Title (TV size) [TATOE]", + Artist = "This artist has a really long name how is it possible", + Author = new User + { + Username = "author", + Id = 100 + } + }, + OnlineInfo = new BeatmapSetOnlineInfo + { + Covers = new BeatmapSetOnlineCovers + { + Cover = "https://assets.ppy.sh/beatmaps/1189904/covers/cover.jpg?1595456608", + } + } + }; + } +} diff --git a/osu.Game/Overlays/Dashboard/Dashboard/DashboardBeatmapPanel.cs b/osu.Game/Overlays/Dashboard/Dashboard/DashboardBeatmapPanel.cs new file mode 100644 index 0000000000..84cb5ae46a --- /dev/null +++ b/osu.Game/Overlays/Dashboard/Dashboard/DashboardBeatmapPanel.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 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.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.Dashboard.Dashboard +{ + public class DashboardBeatmapPanel : OsuClickableContainer + { + [Resolved] + private OverlayColourProvider colourProvider { get; set; } + + [Resolved(canBeNull: true)] + private BeatmapSetOverlay beatmapOverlay { get; set; } + + private readonly BeatmapSetInfo setInfo; + + private Box background; + private SpriteIcon chevron; + + public DashboardBeatmapPanel(BeatmapSetInfo setInfo) + { + this.setInfo = setInfo; + } + + [BackgroundDependencyLoader] + private void load() + { + RelativeSizeAxes = Axes.X; + Height = 60; + Children = new Drawable[] + { + background = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colourProvider.Background3, + Alpha = 0 + }, + new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Horizontal = 10 }, + Child = new GridContainer + { + RelativeSizeAxes = Axes.Both, + ColumnDimensions = new[] + { + new Dimension(GridSizeMode.Absolute, 70), + new Dimension(), + new Dimension(GridSizeMode.AutoSize) + }, + RowDimensions = new[] + { + new Dimension() + }, + Content = new[] + { + new Drawable[] + { + new Container + { + RelativeSizeAxes = Axes.Both, + Masking = true, + CornerRadius = 6, + Child = new UpdateableBeatmapSetCover + { + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + BeatmapSet = setInfo + } + }, + new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Horizontal = 10 }, + Child = new FillFlowContainer + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + new OsuSpriteText + { + RelativeSizeAxes = Axes.X, + Truncate = true, + Font = OsuFont.GetFont(weight: FontWeight.SemiBold), + Text = setInfo.Metadata.Title + }, + new OsuSpriteText + { + RelativeSizeAxes = Axes.X, + Truncate = true, + Font = OsuFont.GetFont(size: 12, weight: FontWeight.SemiBold), + Text = setInfo.Metadata.Artist + }, + new LinkFlowContainer(f => f.Font = OsuFont.GetFont(size: 10, weight: FontWeight.SemiBold)) + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y + }.With(c => + { + c.AddText("by "); + c.AddUserLink(setInfo.Metadata.Author); + }) + } + } + }, + chevron = new SpriteIcon + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Size = new Vector2(16), + Icon = FontAwesome.Solid.ChevronRight, + Colour = colourProvider.Foreground1 + } + } + } + } + } + }; + + Action = () => + { + if (setInfo.OnlineBeatmapSetID.HasValue) + beatmapOverlay?.FetchAndShowBeatmapSet(setInfo.OnlineBeatmapSetID.Value); + }; + } + + protected override bool OnHover(HoverEvent e) + { + base.OnHover(e); + background.FadeIn(200, Easing.OutQuint); + chevron.FadeColour(colourProvider.Light1, 200, Easing.OutQuint); + return true; + } + + protected override void OnHoverLost(HoverLostEvent e) + { + base.OnHoverLost(e); + background.FadeOut(200, Easing.OutQuint); + chevron.FadeColour(colourProvider.Foreground1, 200, Easing.OutQuint); + } + } +} From ce47a34991ce574265aeaf37134c3d9f6de02cee Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 1 Aug 2020 06:14:24 +0300 Subject: [PATCH 0348/1782] Implement DashboardNewBeatmapPanel component --- .../TestSceneDashboardBeatmapPanel.cs | 8 ++- .../Dashboard/DashboardBeatmapPanel.cs | 56 +++++++++++-------- .../Dashboard/DashboardNewBeatmapPanel.cs | 22 ++++++++ 3 files changed, 61 insertions(+), 25 deletions(-) create mode 100644 osu.Game/Overlays/Dashboard/Dashboard/DashboardNewBeatmapPanel.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneDashboardBeatmapPanel.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneDashboardBeatmapPanel.cs index a61cd37513..5f1af012db 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneDashboardBeatmapPanel.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneDashboardBeatmapPanel.cs @@ -8,6 +8,7 @@ using osu.Game.Beatmaps; using osu.Game.Overlays; using osu.Framework.Allocation; using osu.Game.Users; +using System; namespace osu.Game.Tests.Visual.UserInterface { @@ -24,7 +25,7 @@ namespace osu.Game.Tests.Visual.UserInterface Origin = Anchor.Centre, AutoSizeAxes = Axes.Y, Width = 300, - Child = new DashboardBeatmapPanel(beatmap_set) + Child = new DashboardNewBeatmapPanel(beatmap_set) }); } @@ -33,7 +34,7 @@ namespace osu.Game.Tests.Visual.UserInterface Metadata = new BeatmapMetadata { Title = "Very Long Title (TV size) [TATOE]", - Artist = "This artist has a really long name how is it possible", + Artist = "This artist has a really long name how is this possible", Author = new User { Username = "author", @@ -45,7 +46,8 @@ namespace osu.Game.Tests.Visual.UserInterface Covers = new BeatmapSetOnlineCovers { Cover = "https://assets.ppy.sh/beatmaps/1189904/covers/cover.jpg?1595456608", - } + }, + Ranked = DateTimeOffset.Now } }; } diff --git a/osu.Game/Overlays/Dashboard/Dashboard/DashboardBeatmapPanel.cs b/osu.Game/Overlays/Dashboard/Dashboard/DashboardBeatmapPanel.cs index 84cb5ae46a..30b0086b8a 100644 --- a/osu.Game/Overlays/Dashboard/Dashboard/DashboardBeatmapPanel.cs +++ b/osu.Game/Overlays/Dashboard/Dashboard/DashboardBeatmapPanel.cs @@ -16,22 +16,22 @@ using osuTK; namespace osu.Game.Overlays.Dashboard.Dashboard { - public class DashboardBeatmapPanel : OsuClickableContainer + public abstract class DashboardBeatmapPanel : OsuClickableContainer { [Resolved] - private OverlayColourProvider colourProvider { get; set; } + protected OverlayColourProvider ColourProvider { get; private set; } [Resolved(canBeNull: true)] private BeatmapSetOverlay beatmapOverlay { get; set; } - private readonly BeatmapSetInfo setInfo; + protected readonly BeatmapSetInfo SetInfo; private Box background; private SpriteIcon chevron; - public DashboardBeatmapPanel(BeatmapSetInfo setInfo) + protected DashboardBeatmapPanel(BeatmapSetInfo setInfo) { - this.setInfo = setInfo; + SetInfo = setInfo; } [BackgroundDependencyLoader] @@ -44,7 +44,7 @@ namespace osu.Game.Overlays.Dashboard.Dashboard background = new Box { RelativeSizeAxes = Axes.Both, - Colour = colourProvider.Background3, + Colour = ColourProvider.Background3, Alpha = 0 }, new Container @@ -78,7 +78,7 @@ namespace osu.Game.Overlays.Dashboard.Dashboard RelativeSizeAxes = Axes.Both, Anchor = Anchor.Centre, Origin = Anchor.Centre, - BeatmapSet = setInfo + BeatmapSet = SetInfo } }, new Container @@ -99,24 +99,34 @@ namespace osu.Game.Overlays.Dashboard.Dashboard RelativeSizeAxes = Axes.X, Truncate = true, Font = OsuFont.GetFont(weight: FontWeight.SemiBold), - Text = setInfo.Metadata.Title + Text = SetInfo.Metadata.Title }, new OsuSpriteText { RelativeSizeAxes = Axes.X, Truncate = true, Font = OsuFont.GetFont(size: 12, weight: FontWeight.SemiBold), - Text = setInfo.Metadata.Artist + Text = SetInfo.Metadata.Artist }, - new LinkFlowContainer(f => f.Font = OsuFont.GetFont(size: 10, weight: FontWeight.SemiBold)) + new FillFlowContainer { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y - }.With(c => - { - c.AddText("by "); - c.AddUserLink(setInfo.Metadata.Author); - }) + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(5, 0), + Margin = new MarginPadding { Top = 2 }, + Children = new Drawable[] + { + new LinkFlowContainer(f => f.Font = OsuFont.GetFont(size: 10, weight: FontWeight.SemiBold)) + { + AutoSizeAxes = Axes.Both + }.With(c => + { + c.AddText("by "); + c.AddUserLink(SetInfo.Metadata.Author); + }), + CreateInfo() + } + } } } }, @@ -126,7 +136,7 @@ namespace osu.Game.Overlays.Dashboard.Dashboard Origin = Anchor.CentreRight, Size = new Vector2(16), Icon = FontAwesome.Solid.ChevronRight, - Colour = colourProvider.Foreground1 + Colour = ColourProvider.Foreground1 } } } @@ -136,16 +146,18 @@ namespace osu.Game.Overlays.Dashboard.Dashboard Action = () => { - if (setInfo.OnlineBeatmapSetID.HasValue) - beatmapOverlay?.FetchAndShowBeatmapSet(setInfo.OnlineBeatmapSetID.Value); + if (SetInfo.OnlineBeatmapSetID.HasValue) + beatmapOverlay?.FetchAndShowBeatmapSet(SetInfo.OnlineBeatmapSetID.Value); }; } + protected abstract Drawable CreateInfo(); + protected override bool OnHover(HoverEvent e) { base.OnHover(e); background.FadeIn(200, Easing.OutQuint); - chevron.FadeColour(colourProvider.Light1, 200, Easing.OutQuint); + chevron.FadeColour(ColourProvider.Light1, 200, Easing.OutQuint); return true; } @@ -153,7 +165,7 @@ namespace osu.Game.Overlays.Dashboard.Dashboard { base.OnHoverLost(e); background.FadeOut(200, Easing.OutQuint); - chevron.FadeColour(colourProvider.Foreground1, 200, Easing.OutQuint); + chevron.FadeColour(ColourProvider.Foreground1, 200, Easing.OutQuint); } } } diff --git a/osu.Game/Overlays/Dashboard/Dashboard/DashboardNewBeatmapPanel.cs b/osu.Game/Overlays/Dashboard/Dashboard/DashboardNewBeatmapPanel.cs new file mode 100644 index 0000000000..6d2ec7ae37 --- /dev/null +++ b/osu.Game/Overlays/Dashboard/Dashboard/DashboardNewBeatmapPanel.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.Graphics; +using osu.Game.Beatmaps; +using osu.Game.Graphics; + +namespace osu.Game.Overlays.Dashboard.Dashboard +{ + public class DashboardNewBeatmapPanel : DashboardBeatmapPanel + { + public DashboardNewBeatmapPanel(BeatmapSetInfo setInfo) + : base(setInfo) + { + } + + protected override Drawable CreateInfo() => new DrawableDate(SetInfo.OnlineInfo.Ranked.Value, 10, false) + { + Colour = ColourProvider.Foreground1 + }; + } +} From 7624804edfacb185dc456c18eb32648287613665 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 1 Aug 2020 06:23:06 +0300 Subject: [PATCH 0349/1782] Implement DashboardPopularBeatmapPanel component --- .../TestSceneDashboardBeatmapPanel.cs | 35 ++++++++++++++-- .../Dashboard/DashboardBeatmapPanel.cs | 2 +- .../Dashboard/DashboardPopularBeatmapPanel.cs | 42 +++++++++++++++++++ 3 files changed, 75 insertions(+), 4 deletions(-) create mode 100644 osu.Game/Overlays/Dashboard/Dashboard/DashboardPopularBeatmapPanel.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneDashboardBeatmapPanel.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneDashboardBeatmapPanel.cs index 5f1af012db..10682490ae 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneDashboardBeatmapPanel.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneDashboardBeatmapPanel.cs @@ -9,6 +9,7 @@ using osu.Game.Overlays; using osu.Framework.Allocation; using osu.Game.Users; using System; +using osuTK; namespace osu.Game.Tests.Visual.UserInterface { @@ -19,17 +20,23 @@ namespace osu.Game.Tests.Visual.UserInterface public TestSceneDashboardBeatmapPanel() { - Add(new Container + Add(new FillFlowContainer { Anchor = Anchor.Centre, Origin = Anchor.Centre, AutoSizeAxes = Axes.Y, Width = 300, - Child = new DashboardNewBeatmapPanel(beatmap_set) + Spacing = new Vector2(0, 10), + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + new DashboardNewBeatmapPanel(new_beatmap_set), + new DashboardPopularBeatmapPanel(popular_beatmap_set) + } }); } - private static readonly BeatmapSetInfo beatmap_set = new BeatmapSetInfo + private static readonly BeatmapSetInfo new_beatmap_set = new BeatmapSetInfo { Metadata = new BeatmapMetadata { @@ -50,5 +57,27 @@ namespace osu.Game.Tests.Visual.UserInterface Ranked = DateTimeOffset.Now } }; + + private static readonly BeatmapSetInfo popular_beatmap_set = new BeatmapSetInfo + { + Metadata = new BeatmapMetadata + { + Title = "Title", + Artist = "Artist", + Author = new User + { + Username = "author", + Id = 100 + } + }, + OnlineInfo = new BeatmapSetOnlineInfo + { + Covers = new BeatmapSetOnlineCovers + { + Cover = "https://assets.ppy.sh/beatmaps/1189904/covers/cover.jpg?1595456608", + }, + FavouriteCount = 100 + } + }; } } diff --git a/osu.Game/Overlays/Dashboard/Dashboard/DashboardBeatmapPanel.cs b/osu.Game/Overlays/Dashboard/Dashboard/DashboardBeatmapPanel.cs index 30b0086b8a..fc70d9d40a 100644 --- a/osu.Game/Overlays/Dashboard/Dashboard/DashboardBeatmapPanel.cs +++ b/osu.Game/Overlays/Dashboard/Dashboard/DashboardBeatmapPanel.cs @@ -112,7 +112,7 @@ namespace osu.Game.Overlays.Dashboard.Dashboard { AutoSizeAxes = Axes.Both, Direction = FillDirection.Horizontal, - Spacing = new Vector2(5, 0), + Spacing = new Vector2(3, 0), Margin = new MarginPadding { Top = 2 }, Children = new Drawable[] { diff --git a/osu.Game/Overlays/Dashboard/Dashboard/DashboardPopularBeatmapPanel.cs b/osu.Game/Overlays/Dashboard/Dashboard/DashboardPopularBeatmapPanel.cs new file mode 100644 index 0000000000..04bb261dce --- /dev/null +++ b/osu.Game/Overlays/Dashboard/Dashboard/DashboardPopularBeatmapPanel.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.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Game.Beatmaps; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osuTK; + +namespace osu.Game.Overlays.Dashboard.Dashboard +{ + public class DashboardPopularBeatmapPanel : DashboardBeatmapPanel + { + public DashboardPopularBeatmapPanel(BeatmapSetInfo setInfo) + : base(setInfo) + { + } + + protected override Drawable CreateInfo() => new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(3, 0), + Colour = ColourProvider.Foreground1, + Children = new Drawable[] + { + new SpriteIcon + { + Size = new Vector2(10), + Icon = FontAwesome.Solid.Heart + }, + new OsuSpriteText + { + Font = OsuFont.GetFont(size: 10), + Text = SetInfo.OnlineInfo.FavouriteCount.ToString() + } + } + }; + } +} From b5f688e63aeb9168cb4d73c651889dab270871a3 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 1 Aug 2020 07:04:39 +0300 Subject: [PATCH 0350/1782] Implement DashboardBeatmapListing component --- .../TestSceneDashboardBeatmapListing.cs | 144 ++++++++++++++++++ .../TestSceneDashboardBeatmapPanel.cs | 83 ---------- .../Dashboard/DashboardBeatmapListing.cs | 43 ++++++ .../Dashboard/DashboardBeatmapPanel.cs | 2 +- .../Dashboard/DashboardNewBeatmapPanel.cs | 3 +- .../Dashboard/DrawableBeatmapsList.cs | 57 +++++++ .../Dashboard/DrawableNewBeatmapsList.cs | 20 +++ .../Dashboard/DrawablePopularBeatmapsList.cs | 20 +++ 8 files changed, 287 insertions(+), 85 deletions(-) create mode 100644 osu.Game.Tests/Visual/UserInterface/TestSceneDashboardBeatmapListing.cs delete mode 100644 osu.Game.Tests/Visual/UserInterface/TestSceneDashboardBeatmapPanel.cs create mode 100644 osu.Game/Overlays/Dashboard/Dashboard/DashboardBeatmapListing.cs create mode 100644 osu.Game/Overlays/Dashboard/Dashboard/DrawableBeatmapsList.cs create mode 100644 osu.Game/Overlays/Dashboard/Dashboard/DrawableNewBeatmapsList.cs create mode 100644 osu.Game/Overlays/Dashboard/Dashboard/DrawablePopularBeatmapsList.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneDashboardBeatmapListing.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneDashboardBeatmapListing.cs new file mode 100644 index 0000000000..be0cc5187e --- /dev/null +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneDashboardBeatmapListing.cs @@ -0,0 +1,144 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics; +using osu.Game.Overlays.Dashboard.Dashboard; +using osu.Game.Beatmaps; +using osu.Game.Overlays; +using osu.Framework.Allocation; +using osu.Game.Users; +using System; +using osu.Framework.Graphics.Shapes; +using System.Collections.Generic; + +namespace osu.Game.Tests.Visual.UserInterface +{ + public class TestSceneDashboardBeatmapListing : OsuTestScene + { + [Cached] + private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple); + + private readonly Container content; + + public TestSceneDashboardBeatmapListing() + { + Add(content = new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AutoSizeAxes = Axes.Y, + Width = 300, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colourProvider.Background4 + }, + new DashboardBeatmapListing(new_beatmaps, popular_beatmaps) + } + }); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + AddStep("Set width to 500", () => content.ResizeWidthTo(500, 500)); + AddStep("Set width to 300", () => content.ResizeWidthTo(300, 500)); + } + + private static readonly List new_beatmaps = new List + { + new BeatmapSetInfo + { + Metadata = new BeatmapMetadata + { + Title = "Very Long Title (TV size) [TATOE]", + Artist = "This artist has a really long name how is this possible", + Author = new User + { + Username = "author", + Id = 100 + } + }, + OnlineInfo = new BeatmapSetOnlineInfo + { + Covers = new BeatmapSetOnlineCovers + { + Cover = "https://assets.ppy.sh/beatmaps/1189904/covers/cover.jpg?1595456608", + }, + Ranked = DateTimeOffset.Now + } + }, + new BeatmapSetInfo + { + Metadata = new BeatmapMetadata + { + Title = "Very Long Title (TV size) [TATOE]", + Artist = "This artist has a really long name how is this possible", + Author = new User + { + Username = "author", + Id = 100 + } + }, + OnlineInfo = new BeatmapSetOnlineInfo + { + Covers = new BeatmapSetOnlineCovers + { + Cover = "https://assets.ppy.sh/beatmaps/1189904/covers/cover.jpg?1595456608", + }, + Ranked = DateTimeOffset.MinValue + } + } + }; + + private static readonly List popular_beatmaps = new List + { + new BeatmapSetInfo + { + Metadata = new BeatmapMetadata + { + Title = "Title", + Artist = "Artist", + Author = new User + { + Username = "author", + Id = 100 + } + }, + OnlineInfo = new BeatmapSetOnlineInfo + { + Covers = new BeatmapSetOnlineCovers + { + Cover = "https://assets.ppy.sh/beatmaps/1079428/covers/cover.jpg?1595295586", + }, + FavouriteCount = 100 + } + }, + new BeatmapSetInfo + { + Metadata = new BeatmapMetadata + { + Title = "Title 2", + Artist = "Artist 2", + Author = new User + { + Username = "someone", + Id = 100 + } + }, + OnlineInfo = new BeatmapSetOnlineInfo + { + Covers = new BeatmapSetOnlineCovers + { + Cover = "https://assets.ppy.sh/beatmaps/1079428/covers/cover.jpg?1595295586", + }, + FavouriteCount = 10 + } + } + }; + } +} diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneDashboardBeatmapPanel.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneDashboardBeatmapPanel.cs deleted file mode 100644 index 10682490ae..0000000000 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneDashboardBeatmapPanel.cs +++ /dev/null @@ -1,83 +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.Overlays.Dashboard.Dashboard; -using osu.Game.Beatmaps; -using osu.Game.Overlays; -using osu.Framework.Allocation; -using osu.Game.Users; -using System; -using osuTK; - -namespace osu.Game.Tests.Visual.UserInterface -{ - public class TestSceneDashboardBeatmapPanel : OsuTestScene - { - [Cached] - private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple); - - public TestSceneDashboardBeatmapPanel() - { - Add(new FillFlowContainer - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - AutoSizeAxes = Axes.Y, - Width = 300, - Spacing = new Vector2(0, 10), - Direction = FillDirection.Vertical, - Children = new Drawable[] - { - new DashboardNewBeatmapPanel(new_beatmap_set), - new DashboardPopularBeatmapPanel(popular_beatmap_set) - } - }); - } - - private static readonly BeatmapSetInfo new_beatmap_set = new BeatmapSetInfo - { - Metadata = new BeatmapMetadata - { - Title = "Very Long Title (TV size) [TATOE]", - Artist = "This artist has a really long name how is this possible", - Author = new User - { - Username = "author", - Id = 100 - } - }, - OnlineInfo = new BeatmapSetOnlineInfo - { - Covers = new BeatmapSetOnlineCovers - { - Cover = "https://assets.ppy.sh/beatmaps/1189904/covers/cover.jpg?1595456608", - }, - Ranked = DateTimeOffset.Now - } - }; - - private static readonly BeatmapSetInfo popular_beatmap_set = new BeatmapSetInfo - { - Metadata = new BeatmapMetadata - { - Title = "Title", - Artist = "Artist", - Author = new User - { - Username = "author", - Id = 100 - } - }, - OnlineInfo = new BeatmapSetOnlineInfo - { - Covers = new BeatmapSetOnlineCovers - { - Cover = "https://assets.ppy.sh/beatmaps/1189904/covers/cover.jpg?1595456608", - }, - FavouriteCount = 100 - } - }; - } -} diff --git a/osu.Game/Overlays/Dashboard/Dashboard/DashboardBeatmapListing.cs b/osu.Game/Overlays/Dashboard/Dashboard/DashboardBeatmapListing.cs new file mode 100644 index 0000000000..26c892174d --- /dev/null +++ b/osu.Game/Overlays/Dashboard/Dashboard/DashboardBeatmapListing.cs @@ -0,0 +1,43 @@ +// 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.Beatmaps; +using osuTK; + +namespace osu.Game.Overlays.Dashboard.Dashboard +{ + public class DashboardBeatmapListing : CompositeDrawable + { + private readonly List newBeatmaps; + private readonly List popularBeatmaps; + + public DashboardBeatmapListing(List newBeatmaps, List popularBeatmaps) + { + this.newBeatmaps = newBeatmaps; + this.popularBeatmaps = popularBeatmaps; + } + + [BackgroundDependencyLoader] + private void load() + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + InternalChild = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 10), + Children = new DrawableBeatmapsList[] + { + new DrawableNewBeatmapsList(newBeatmaps), + new DrawablePopularBeatmapsList(popularBeatmaps) + } + }; + } + } +} diff --git a/osu.Game/Overlays/Dashboard/Dashboard/DashboardBeatmapPanel.cs b/osu.Game/Overlays/Dashboard/Dashboard/DashboardBeatmapPanel.cs index fc70d9d40a..bb35ddd07c 100644 --- a/osu.Game/Overlays/Dashboard/Dashboard/DashboardBeatmapPanel.cs +++ b/osu.Game/Overlays/Dashboard/Dashboard/DashboardBeatmapPanel.cs @@ -114,7 +114,7 @@ namespace osu.Game.Overlays.Dashboard.Dashboard Direction = FillDirection.Horizontal, Spacing = new Vector2(3, 0), Margin = new MarginPadding { Top = 2 }, - Children = new Drawable[] + Children = new[] { new LinkFlowContainer(f => f.Font = OsuFont.GetFont(size: 10, weight: FontWeight.SemiBold)) { diff --git a/osu.Game/Overlays/Dashboard/Dashboard/DashboardNewBeatmapPanel.cs b/osu.Game/Overlays/Dashboard/Dashboard/DashboardNewBeatmapPanel.cs index 6d2ec7ae37..c4782b733d 100644 --- a/osu.Game/Overlays/Dashboard/Dashboard/DashboardNewBeatmapPanel.cs +++ b/osu.Game/Overlays/Dashboard/Dashboard/DashboardNewBeatmapPanel.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.Beatmaps; using osu.Game.Graphics; @@ -14,7 +15,7 @@ namespace osu.Game.Overlays.Dashboard.Dashboard { } - protected override Drawable CreateInfo() => new DrawableDate(SetInfo.OnlineInfo.Ranked.Value, 10, false) + protected override Drawable CreateInfo() => new DrawableDate(SetInfo.OnlineInfo.Ranked ?? DateTimeOffset.Now, 10, false) { Colour = ColourProvider.Foreground1 }; diff --git a/osu.Game/Overlays/Dashboard/Dashboard/DrawableBeatmapsList.cs b/osu.Game/Overlays/Dashboard/Dashboard/DrawableBeatmapsList.cs new file mode 100644 index 0000000000..dfe483a962 --- /dev/null +++ b/osu.Game/Overlays/Dashboard/Dashboard/DrawableBeatmapsList.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 System.Collections.Generic; +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Beatmaps; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osuTK; + +namespace osu.Game.Overlays.Dashboard.Dashboard +{ + public abstract class DrawableBeatmapsList : CompositeDrawable + { + private readonly List beatmaps; + + protected DrawableBeatmapsList(List beatmaps) + { + this.beatmaps = beatmaps; + } + + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider) + { + FillFlowContainer flow; + + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + InternalChild = flow = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 10), + Children = new Drawable[] + { + new OsuSpriteText + { + Font = OsuFont.GetFont(size: 16, weight: FontWeight.Bold), + Colour = colourProvider.Light1, + Text = CreateTitle(), + Padding = new MarginPadding { Left = 10 } + } + } + }; + + flow.AddRange(beatmaps.Select(CreateBeatmapPanel)); + } + + protected abstract string CreateTitle(); + + protected abstract DashboardBeatmapPanel CreateBeatmapPanel(BeatmapSetInfo setInfo); + } +} diff --git a/osu.Game/Overlays/Dashboard/Dashboard/DrawableNewBeatmapsList.cs b/osu.Game/Overlays/Dashboard/Dashboard/DrawableNewBeatmapsList.cs new file mode 100644 index 0000000000..e89495dc99 --- /dev/null +++ b/osu.Game/Overlays/Dashboard/Dashboard/DrawableNewBeatmapsList.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.Collections.Generic; +using osu.Game.Beatmaps; + +namespace osu.Game.Overlays.Dashboard.Dashboard +{ + public class DrawableNewBeatmapsList : DrawableBeatmapsList + { + public DrawableNewBeatmapsList(List beatmaps) + : base(beatmaps) + { + } + + protected override DashboardBeatmapPanel CreateBeatmapPanel(BeatmapSetInfo setInfo) => new DashboardNewBeatmapPanel(setInfo); + + protected override string CreateTitle() => "New Ranked Beatmaps"; + } +} diff --git a/osu.Game/Overlays/Dashboard/Dashboard/DrawablePopularBeatmapsList.cs b/osu.Game/Overlays/Dashboard/Dashboard/DrawablePopularBeatmapsList.cs new file mode 100644 index 0000000000..8076e86ed0 --- /dev/null +++ b/osu.Game/Overlays/Dashboard/Dashboard/DrawablePopularBeatmapsList.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.Collections.Generic; +using osu.Game.Beatmaps; + +namespace osu.Game.Overlays.Dashboard.Dashboard +{ + public class DrawablePopularBeatmapsList : DrawableBeatmapsList + { + public DrawablePopularBeatmapsList(List beatmaps) + : base(beatmaps) + { + } + + protected override DashboardBeatmapPanel CreateBeatmapPanel(BeatmapSetInfo setInfo) => new DashboardPopularBeatmapPanel(setInfo); + + protected override string CreateTitle() => "Popular Beatmaps"; + } +} From 5b1e3e86220e7ba3b8024fa9ff6cfc035e34a82c Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 1 Aug 2020 09:11:53 +0300 Subject: [PATCH 0351/1782] Remove redundant FillFlowContainer from DashboardBeatmapPanel --- .../Dashboard/DashboardBeatmapPanel.cs | 29 +++++++------------ 1 file changed, 11 insertions(+), 18 deletions(-) diff --git a/osu.Game/Overlays/Dashboard/Dashboard/DashboardBeatmapPanel.cs b/osu.Game/Overlays/Dashboard/Dashboard/DashboardBeatmapPanel.cs index bb35ddd07c..61324fdc7f 100644 --- a/osu.Game/Overlays/Dashboard/Dashboard/DashboardBeatmapPanel.cs +++ b/osu.Game/Overlays/Dashboard/Dashboard/DashboardBeatmapPanel.cs @@ -108,25 +108,18 @@ namespace osu.Game.Overlays.Dashboard.Dashboard Font = OsuFont.GetFont(size: 12, weight: FontWeight.SemiBold), Text = SetInfo.Metadata.Artist }, - new FillFlowContainer + new LinkFlowContainer(f => f.Font = OsuFont.GetFont(size: 10, weight: FontWeight.SemiBold)) { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(3, 0), - Margin = new MarginPadding { Top = 2 }, - Children = new[] - { - new LinkFlowContainer(f => f.Font = OsuFont.GetFont(size: 10, weight: FontWeight.SemiBold)) - { - AutoSizeAxes = Axes.Both - }.With(c => - { - c.AddText("by "); - c.AddUserLink(SetInfo.Metadata.Author); - }), - CreateInfo() - } - } + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + Spacing = new Vector2(3), + Margin = new MarginPadding { Top = 2 } + }.With(c => + { + c.AddText("by"); + c.AddUserLink(SetInfo.Metadata.Author); + c.AddArbitraryDrawable(CreateInfo()); + }) } } }, From 2190e6443a0265ba4d4db20a44b207b3a63a4760 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 1 Aug 2020 10:02:46 +0300 Subject: [PATCH 0352/1782] Apply height constraints to all settings dropdown --- osu.Game/Overlays/Settings/Sections/SkinSection.cs | 2 -- osu.Game/Overlays/Settings/SettingsDropdown.cs | 2 ++ 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/SkinSection.cs b/osu.Game/Overlays/Settings/Sections/SkinSection.cs index 04390a1193..596d3a9801 100644 --- a/osu.Game/Overlays/Settings/Sections/SkinSection.cs +++ b/osu.Game/Overlays/Settings/Sections/SkinSection.cs @@ -116,8 +116,6 @@ 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); } } diff --git a/osu.Game/Overlays/Settings/SettingsDropdown.cs b/osu.Game/Overlays/Settings/SettingsDropdown.cs index 167061f485..1175ddaab8 100644 --- a/osu.Game/Overlays/Settings/SettingsDropdown.cs +++ b/osu.Game/Overlays/Settings/SettingsDropdown.cs @@ -38,6 +38,8 @@ namespace osu.Game.Overlays.Settings Margin = new MarginPadding { Top = 5 }; RelativeSizeAxes = Axes.X; } + + protected override DropdownMenu CreateMenu() => base.CreateMenu().With(m => m.MaxHeight = 200); } } } From 5f52701273a8b41205bcd7a1c24fd02bb658560c Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 1 Aug 2020 10:11:34 +0300 Subject: [PATCH 0353/1782] Remove no longer necessary custom dropdown --- .../Ladder/Components/LadderEditorSettings.cs | 2 +- .../Components/LadderSettingsDropdown.cs | 26 ------------------- .../Ladder/Components/SettingsTeamDropdown.cs | 3 ++- 3 files changed, 3 insertions(+), 28 deletions(-) delete mode 100644 osu.Game.Tournament/Screens/Ladder/Components/LadderSettingsDropdown.cs diff --git a/osu.Game.Tournament/Screens/Ladder/Components/LadderEditorSettings.cs b/osu.Game.Tournament/Screens/Ladder/Components/LadderEditorSettings.cs index 4aea7ff4c0..fa530ea2c4 100644 --- a/osu.Game.Tournament/Screens/Ladder/Components/LadderEditorSettings.cs +++ b/osu.Game.Tournament/Screens/Ladder/Components/LadderEditorSettings.cs @@ -81,7 +81,7 @@ namespace osu.Game.Tournament.Screens.Ladder.Components { } - private class SettingsRoundDropdown : LadderSettingsDropdown + private class SettingsRoundDropdown : SettingsDropdown { public SettingsRoundDropdown(BindableList rounds) { diff --git a/osu.Game.Tournament/Screens/Ladder/Components/LadderSettingsDropdown.cs b/osu.Game.Tournament/Screens/Ladder/Components/LadderSettingsDropdown.cs deleted file mode 100644 index 347e4d91e0..0000000000 --- a/osu.Game.Tournament/Screens/Ladder/Components/LadderSettingsDropdown.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. - -using osu.Game.Graphics.UserInterface; -using osu.Game.Overlays.Settings; - -namespace osu.Game.Tournament.Screens.Ladder.Components -{ - public class LadderSettingsDropdown : SettingsDropdown - { - protected override OsuDropdown CreateDropdown() => new DropdownControl(); - - private new class DropdownControl : SettingsDropdown.DropdownControl - { - protected override DropdownMenu CreateMenu() => new Menu(); - - private new class Menu : OsuDropdownMenu - { - public Menu() - { - MaxHeight = 200; - } - } - } - } -} diff --git a/osu.Game.Tournament/Screens/Ladder/Components/SettingsTeamDropdown.cs b/osu.Game.Tournament/Screens/Ladder/Components/SettingsTeamDropdown.cs index a630e51e44..6604e3a313 100644 --- a/osu.Game.Tournament/Screens/Ladder/Components/SettingsTeamDropdown.cs +++ b/osu.Game.Tournament/Screens/Ladder/Components/SettingsTeamDropdown.cs @@ -6,11 +6,12 @@ using System.Collections.Specialized; using System.Linq; using osu.Framework.Bindables; using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Game.Overlays.Settings; using osu.Game.Tournament.Models; namespace osu.Game.Tournament.Screens.Ladder.Components { - public class SettingsTeamDropdown : LadderSettingsDropdown + public class SettingsTeamDropdown : SettingsDropdown { public SettingsTeamDropdown(BindableList teams) { From 45225646684d9f28b19650b3fffaa3d9791695d6 Mon Sep 17 00:00:00 2001 From: Lucas A Date: Sat, 1 Aug 2020 19:44:30 +0200 Subject: [PATCH 0354/1782] Add GameplayDisableOverlays setting. --- osu.Game/Configuration/OsuConfigManager.cs | 4 +++- .../Overlays/Settings/Sections/Gameplay/GeneralSettings.cs | 5 +++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index a8a8794320..44c0fbde82 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -100,6 +100,7 @@ namespace osu.Game.Configuration Set(OsuSetting.IncreaseFirstObjectVisibility, true); Set(OsuSetting.GameplayDisableWinKey, true); + Set(OsuSetting.GameplayDisableOverlayActivation, true); // Update Set(OsuSetting.ReleaseStream, ReleaseStream.Lazer); @@ -231,6 +232,7 @@ namespace osu.Game.Configuration UIHoldActivationDelay, HitLighting, MenuBackgroundSource, - GameplayDisableWinKey + GameplayDisableWinKey, + GameplayDisableOverlayActivation } } diff --git a/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs index 0149e6c3a6..c2e668fe68 100644 --- a/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs @@ -77,6 +77,11 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay { LabelText = "Score display mode", Bindable = config.GetBindable(OsuSetting.ScoreDisplayMode) + }, + new SettingsCheckbox + { + LabelText = "Disable overlays during gameplay", + Bindable = config.GetBindable(OsuSetting.GameplayDisableOverlayActivation) } }; From f4128083312843d9594c1b96d212914a43fee60b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 2 Aug 2020 12:57:15 +0200 Subject: [PATCH 0355/1782] Check rotation with bigger tolerance to account for damp --- .../TestSceneSpinnerRotation.cs | 44 ++++++++++++++----- 1 file changed, 33 insertions(+), 11 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs index 319d326a01..e4d89fb402 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.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 NUnit.Framework; @@ -60,39 +61,60 @@ namespace osu.Game.Rulesets.Osu.Tests [Test] public void TestSpinnerRewindingRotation() { + double trackerRotationTolerance = 0; + addSeekStep(5000); + AddStep("calculate rotation tolerance", () => + { + trackerRotationTolerance = Math.Abs(drawableSpinner.RotationTracker.Rotation * 0.1f); + }); AddAssert("is disc rotation not almost 0", () => !Precision.AlmostEquals(drawableSpinner.RotationTracker.Rotation, 0, 100)); AddAssert("is disc rotation absolute not almost 0", () => !Precision.AlmostEquals(drawableSpinner.RotationTracker.CumulativeRotation, 0, 100)); addSeekStep(0); - AddAssert("is disc rotation almost 0", () => Precision.AlmostEquals(drawableSpinner.RotationTracker.Rotation, 0, 100)); + AddAssert("is disc rotation almost 0", () => Precision.AlmostEquals(drawableSpinner.RotationTracker.Rotation, 0, trackerRotationTolerance)); AddAssert("is disc rotation absolute almost 0", () => Precision.AlmostEquals(drawableSpinner.RotationTracker.CumulativeRotation, 0, 100)); } [Test] public void TestSpinnerMiddleRewindingRotation() { - double finalAbsoluteDiscRotation = 0, finalRelativeDiscRotation = 0, finalSpinnerSymbolRotation = 0; + double finalCumulativeTrackerRotation = 0; + double finalTrackerRotation = 0, trackerRotationTolerance = 0; + double finalSpinnerSymbolRotation = 0, spinnerSymbolRotationTolerance = 0; addSeekStep(5000); - AddStep("retrieve disc relative rotation", () => finalRelativeDiscRotation = drawableSpinner.RotationTracker.Rotation); - AddStep("retrieve disc absolute rotation", () => finalAbsoluteDiscRotation = drawableSpinner.RotationTracker.CumulativeRotation); - AddStep("retrieve spinner symbol rotation", () => finalSpinnerSymbolRotation = spinnerSymbol.Rotation); + AddStep("retrieve disc rotation", () => + { + finalTrackerRotation = drawableSpinner.RotationTracker.Rotation; + trackerRotationTolerance = Math.Abs(finalTrackerRotation * 0.05f); + }); + AddStep("retrieve spinner symbol rotation", () => + { + finalSpinnerSymbolRotation = spinnerSymbol.Rotation; + spinnerSymbolRotationTolerance = Math.Abs(finalSpinnerSymbolRotation * 0.05f); + }); + AddStep("retrieve cumulative disc rotation", () => finalCumulativeTrackerRotation = drawableSpinner.RotationTracker.CumulativeRotation); addSeekStep(2500); AddUntilStep("disc rotation rewound", // we want to make sure that the rotation at time 2500 is in the same direction as at time 5000, but about half-way in. - () => Precision.AlmostEquals(drawableSpinner.RotationTracker.Rotation, finalRelativeDiscRotation / 2, 100)); + // due to the exponential damping applied we're allowing a larger margin of error of about 10% + // (5% relative to the final rotation value, but we're half-way through the spin). + () => Precision.AlmostEquals(drawableSpinner.RotationTracker.Rotation, finalTrackerRotation / 2, trackerRotationTolerance)); AddUntilStep("symbol rotation rewound", - () => Precision.AlmostEquals(spinnerSymbol.Rotation, finalSpinnerSymbolRotation / 2, 100)); + () => Precision.AlmostEquals(spinnerSymbol.Rotation, finalSpinnerSymbolRotation / 2, spinnerSymbolRotationTolerance)); + AddAssert("is cumulative rotation rewound", + // cumulative rotation is not damped, so we're treating it as the "ground truth" and allowing a comparatively smaller margin of error. + () => Precision.AlmostEquals(drawableSpinner.RotationTracker.CumulativeRotation, finalCumulativeTrackerRotation / 2, 100)); addSeekStep(5000); AddAssert("is disc rotation almost same", - () => Precision.AlmostEquals(drawableSpinner.RotationTracker.Rotation, finalRelativeDiscRotation, 100)); + () => Precision.AlmostEquals(drawableSpinner.RotationTracker.Rotation, finalTrackerRotation, trackerRotationTolerance)); AddAssert("is symbol rotation almost same", - () => Precision.AlmostEquals(spinnerSymbol.Rotation, finalSpinnerSymbolRotation, 100)); - AddAssert("is disc rotation absolute almost same", - () => Precision.AlmostEquals(drawableSpinner.RotationTracker.CumulativeRotation, finalAbsoluteDiscRotation, 100)); + () => Precision.AlmostEquals(spinnerSymbol.Rotation, finalSpinnerSymbolRotation, spinnerSymbolRotationTolerance)); + AddAssert("is cumulative rotation almost same", + () => Precision.AlmostEquals(drawableSpinner.RotationTracker.CumulativeRotation, finalCumulativeTrackerRotation, 100)); } [Test] From efb08aeed32b1787e79b7ebd0e7501a874cf559c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 2 Aug 2020 14:54:41 +0200 Subject: [PATCH 0356/1782] Switch unnecessary wait steps to asserts --- osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs index e4d89fb402..b46964e8b7 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs @@ -97,12 +97,12 @@ namespace osu.Game.Rulesets.Osu.Tests AddStep("retrieve cumulative disc rotation", () => finalCumulativeTrackerRotation = drawableSpinner.RotationTracker.CumulativeRotation); addSeekStep(2500); - AddUntilStep("disc rotation rewound", + AddAssert("disc rotation rewound", // we want to make sure that the rotation at time 2500 is in the same direction as at time 5000, but about half-way in. // due to the exponential damping applied we're allowing a larger margin of error of about 10% // (5% relative to the final rotation value, but we're half-way through the spin). () => Precision.AlmostEquals(drawableSpinner.RotationTracker.Rotation, finalTrackerRotation / 2, trackerRotationTolerance)); - AddUntilStep("symbol rotation rewound", + AddAssert("symbol rotation rewound", () => Precision.AlmostEquals(spinnerSymbol.Rotation, finalSpinnerSymbolRotation / 2, spinnerSymbolRotationTolerance)); AddAssert("is cumulative rotation rewound", // cumulative rotation is not damped, so we're treating it as the "ground truth" and allowing a comparatively smaller margin of error. From 3e5c3e256d3f0747afd8f89f05e622eba1d035ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 2 Aug 2020 19:46:29 +0200 Subject: [PATCH 0357/1782] Extract method for performing generic config lookup --- osu.Game/Skinning/LegacySkin.cs | 46 ++++++++++++++++++--------------- 1 file changed, 25 insertions(+), 21 deletions(-) diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index d98f8aba83..d1acc51bed 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -143,27 +143,7 @@ namespace osu.Game.Skinning goto default; default: - // handles lookups like some in LegacySkinConfiguration.LegacySetting - - try - { - 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(); - if (val != null) - bindable.Parse(val); - return bindable; - } - } - catch - { - } - - break; + return genericLookup(lookup); } return null; @@ -286,6 +266,30 @@ namespace osu.Game.Skinning private IBindable getManiaImage(LegacyManiaSkinConfiguration source, string lookup) => source.ImageLookups.TryGetValue(lookup, out var image) ? new Bindable(image) : null; + [CanBeNull] + private IBindable genericLookup(TLookup lookup) + { + try + { + 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(); + if (val != null) + bindable.Parse(val); + return bindable; + } + } + catch + { + } + + return null; + } + public override Drawable GetDrawableComponent(ISkinComponent component) { switch (component) From ca7545917c85d5b5cf58ab54ec49aaab4c17d52f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 2 Aug 2020 19:50:17 +0200 Subject: [PATCH 0358/1782] Extract method for performing legacy lookups --- osu.Game/Skinning/LegacySkin.cs | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index d1acc51bed..4e470e13b5 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -134,13 +134,7 @@ namespace osu.Game.Skinning break; case LegacySkinConfiguration.LegacySetting legacy: - switch (legacy) - { - case LegacySkinConfiguration.LegacySetting.Version: - return SkinUtils.As(new Bindable(Configuration.LegacyVersion ?? LegacySkinConfiguration.LATEST_VERSION)); - } - - goto default; + return legacySettingLookup(legacy); default: return genericLookup(lookup); @@ -266,6 +260,19 @@ namespace osu.Game.Skinning private IBindable getManiaImage(LegacyManiaSkinConfiguration source, string lookup) => source.ImageLookups.TryGetValue(lookup, out var image) ? new Bindable(image) : null; + [CanBeNull] + private IBindable legacySettingLookup(LegacySkinConfiguration.LegacySetting legacySetting) + { + switch (legacySetting) + { + case LegacySkinConfiguration.LegacySetting.Version: + return SkinUtils.As(new Bindable(Configuration.LegacyVersion ?? LegacySkinConfiguration.LATEST_VERSION)); + + default: + return genericLookup(legacySetting); + } + } + [CanBeNull] private IBindable genericLookup(TLookup lookup) { From ca57c70961e51cceed04404706c59a8601fa29b9 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 2 Aug 2020 21:33:14 +0300 Subject: [PATCH 0359/1782] Naming adjustments --- .../Dashboard/Dashboard/DashboardBeatmapListing.cs | 8 ++++---- .../Overlays/Dashboard/Dashboard/DashboardBeatmapPanel.cs | 8 ++++---- .../{DrawableBeatmapsList.cs => DrawableBeatmapList.cs} | 4 ++-- ...awableNewBeatmapsList.cs => DrawableNewBeatmapList.cs} | 4 ++-- ...pularBeatmapsList.cs => DrawablePopularBeatmapList.cs} | 4 ++-- 5 files changed, 14 insertions(+), 14 deletions(-) rename osu.Game/Overlays/Dashboard/Dashboard/{DrawableBeatmapsList.cs => DrawableBeatmapList.cs} (92%) rename osu.Game/Overlays/Dashboard/Dashboard/{DrawableNewBeatmapsList.cs => DrawableNewBeatmapList.cs} (80%) rename osu.Game/Overlays/Dashboard/Dashboard/{DrawablePopularBeatmapsList.cs => DrawablePopularBeatmapList.cs} (79%) diff --git a/osu.Game/Overlays/Dashboard/Dashboard/DashboardBeatmapListing.cs b/osu.Game/Overlays/Dashboard/Dashboard/DashboardBeatmapListing.cs index 26c892174d..a8c6ab7ba2 100644 --- a/osu.Game/Overlays/Dashboard/Dashboard/DashboardBeatmapListing.cs +++ b/osu.Game/Overlays/Dashboard/Dashboard/DashboardBeatmapListing.cs @@ -26,16 +26,16 @@ namespace osu.Game.Overlays.Dashboard.Dashboard { RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; - InternalChild = new FillFlowContainer + InternalChild = new FillFlowContainer { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Direction = FillDirection.Vertical, Spacing = new Vector2(0, 10), - Children = new DrawableBeatmapsList[] + Children = new DrawableBeatmapList[] { - new DrawableNewBeatmapsList(newBeatmaps), - new DrawablePopularBeatmapsList(popularBeatmaps) + new DrawableNewBeatmapList(newBeatmaps), + new DrawablePopularBeatmapList(popularBeatmaps) } }; } diff --git a/osu.Game/Overlays/Dashboard/Dashboard/DashboardBeatmapPanel.cs b/osu.Game/Overlays/Dashboard/Dashboard/DashboardBeatmapPanel.cs index 61324fdc7f..45fa56a177 100644 --- a/osu.Game/Overlays/Dashboard/Dashboard/DashboardBeatmapPanel.cs +++ b/osu.Game/Overlays/Dashboard/Dashboard/DashboardBeatmapPanel.cs @@ -26,7 +26,7 @@ namespace osu.Game.Overlays.Dashboard.Dashboard protected readonly BeatmapSetInfo SetInfo; - private Box background; + private Box hoverBackground; private SpriteIcon chevron; protected DashboardBeatmapPanel(BeatmapSetInfo setInfo) @@ -41,7 +41,7 @@ namespace osu.Game.Overlays.Dashboard.Dashboard Height = 60; Children = new Drawable[] { - background = new Box + hoverBackground = new Box { RelativeSizeAxes = Axes.Both, Colour = ColourProvider.Background3, @@ -149,7 +149,7 @@ namespace osu.Game.Overlays.Dashboard.Dashboard protected override bool OnHover(HoverEvent e) { base.OnHover(e); - background.FadeIn(200, Easing.OutQuint); + hoverBackground.FadeIn(200, Easing.OutQuint); chevron.FadeColour(ColourProvider.Light1, 200, Easing.OutQuint); return true; } @@ -157,7 +157,7 @@ namespace osu.Game.Overlays.Dashboard.Dashboard protected override void OnHoverLost(HoverLostEvent e) { base.OnHoverLost(e); - background.FadeOut(200, Easing.OutQuint); + hoverBackground.FadeOut(200, Easing.OutQuint); chevron.FadeColour(ColourProvider.Foreground1, 200, Easing.OutQuint); } } diff --git a/osu.Game/Overlays/Dashboard/Dashboard/DrawableBeatmapsList.cs b/osu.Game/Overlays/Dashboard/Dashboard/DrawableBeatmapList.cs similarity index 92% rename from osu.Game/Overlays/Dashboard/Dashboard/DrawableBeatmapsList.cs rename to osu.Game/Overlays/Dashboard/Dashboard/DrawableBeatmapList.cs index dfe483a962..7dd969863a 100644 --- a/osu.Game/Overlays/Dashboard/Dashboard/DrawableBeatmapsList.cs +++ b/osu.Game/Overlays/Dashboard/Dashboard/DrawableBeatmapList.cs @@ -13,11 +13,11 @@ using osuTK; namespace osu.Game.Overlays.Dashboard.Dashboard { - public abstract class DrawableBeatmapsList : CompositeDrawable + public abstract class DrawableBeatmapList : CompositeDrawable { private readonly List beatmaps; - protected DrawableBeatmapsList(List beatmaps) + protected DrawableBeatmapList(List beatmaps) { this.beatmaps = beatmaps; } diff --git a/osu.Game/Overlays/Dashboard/Dashboard/DrawableNewBeatmapsList.cs b/osu.Game/Overlays/Dashboard/Dashboard/DrawableNewBeatmapList.cs similarity index 80% rename from osu.Game/Overlays/Dashboard/Dashboard/DrawableNewBeatmapsList.cs rename to osu.Game/Overlays/Dashboard/Dashboard/DrawableNewBeatmapList.cs index e89495dc99..3ed42cc4bb 100644 --- a/osu.Game/Overlays/Dashboard/Dashboard/DrawableNewBeatmapsList.cs +++ b/osu.Game/Overlays/Dashboard/Dashboard/DrawableNewBeatmapList.cs @@ -6,9 +6,9 @@ using osu.Game.Beatmaps; namespace osu.Game.Overlays.Dashboard.Dashboard { - public class DrawableNewBeatmapsList : DrawableBeatmapsList + public class DrawableNewBeatmapList : DrawableBeatmapList { - public DrawableNewBeatmapsList(List beatmaps) + public DrawableNewBeatmapList(List beatmaps) : base(beatmaps) { } diff --git a/osu.Game/Overlays/Dashboard/Dashboard/DrawablePopularBeatmapsList.cs b/osu.Game/Overlays/Dashboard/Dashboard/DrawablePopularBeatmapList.cs similarity index 79% rename from osu.Game/Overlays/Dashboard/Dashboard/DrawablePopularBeatmapsList.cs rename to osu.Game/Overlays/Dashboard/Dashboard/DrawablePopularBeatmapList.cs index 8076e86ed0..ee6c3e6d77 100644 --- a/osu.Game/Overlays/Dashboard/Dashboard/DrawablePopularBeatmapsList.cs +++ b/osu.Game/Overlays/Dashboard/Dashboard/DrawablePopularBeatmapList.cs @@ -6,9 +6,9 @@ using osu.Game.Beatmaps; namespace osu.Game.Overlays.Dashboard.Dashboard { - public class DrawablePopularBeatmapsList : DrawableBeatmapsList + public class DrawablePopularBeatmapList : DrawableBeatmapList { - public DrawablePopularBeatmapsList(List beatmaps) + public DrawablePopularBeatmapList(List beatmaps) : base(beatmaps) { } From 7d83cdbf1c18fdaa9d25d866ac62a3d292a5e795 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 2 Aug 2020 21:35:24 +0300 Subject: [PATCH 0360/1782] Make title in DrawableBeatmapList a property --- osu.Game/Overlays/Dashboard/Dashboard/DrawableBeatmapList.cs | 4 ++-- .../Overlays/Dashboard/Dashboard/DrawableNewBeatmapList.cs | 2 +- .../Dashboard/Dashboard/DrawablePopularBeatmapList.cs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/Dashboard/Dashboard/DrawableBeatmapList.cs b/osu.Game/Overlays/Dashboard/Dashboard/DrawableBeatmapList.cs index 7dd969863a..4837d587fd 100644 --- a/osu.Game/Overlays/Dashboard/Dashboard/DrawableBeatmapList.cs +++ b/osu.Game/Overlays/Dashboard/Dashboard/DrawableBeatmapList.cs @@ -41,7 +41,7 @@ namespace osu.Game.Overlays.Dashboard.Dashboard { Font = OsuFont.GetFont(size: 16, weight: FontWeight.Bold), Colour = colourProvider.Light1, - Text = CreateTitle(), + Text = Title, Padding = new MarginPadding { Left = 10 } } } @@ -50,7 +50,7 @@ namespace osu.Game.Overlays.Dashboard.Dashboard flow.AddRange(beatmaps.Select(CreateBeatmapPanel)); } - protected abstract string CreateTitle(); + protected abstract string Title { get; } protected abstract DashboardBeatmapPanel CreateBeatmapPanel(BeatmapSetInfo setInfo); } diff --git a/osu.Game/Overlays/Dashboard/Dashboard/DrawableNewBeatmapList.cs b/osu.Game/Overlays/Dashboard/Dashboard/DrawableNewBeatmapList.cs index 3ed42cc4bb..9856f6ae3e 100644 --- a/osu.Game/Overlays/Dashboard/Dashboard/DrawableNewBeatmapList.cs +++ b/osu.Game/Overlays/Dashboard/Dashboard/DrawableNewBeatmapList.cs @@ -15,6 +15,6 @@ namespace osu.Game.Overlays.Dashboard.Dashboard protected override DashboardBeatmapPanel CreateBeatmapPanel(BeatmapSetInfo setInfo) => new DashboardNewBeatmapPanel(setInfo); - protected override string CreateTitle() => "New Ranked Beatmaps"; + protected override string Title => "New Ranked Beatmaps"; } } diff --git a/osu.Game/Overlays/Dashboard/Dashboard/DrawablePopularBeatmapList.cs b/osu.Game/Overlays/Dashboard/Dashboard/DrawablePopularBeatmapList.cs index ee6c3e6d77..294a75c48a 100644 --- a/osu.Game/Overlays/Dashboard/Dashboard/DrawablePopularBeatmapList.cs +++ b/osu.Game/Overlays/Dashboard/Dashboard/DrawablePopularBeatmapList.cs @@ -15,6 +15,6 @@ namespace osu.Game.Overlays.Dashboard.Dashboard protected override DashboardBeatmapPanel CreateBeatmapPanel(BeatmapSetInfo setInfo) => new DashboardPopularBeatmapPanel(setInfo); - protected override string CreateTitle() => "Popular Beatmaps"; + protected override string Title => "Popular Beatmaps"; } } From bddc61756a3a40f5885b449cd72178b4fd8c2c7b Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 2 Aug 2020 21:44:34 +0300 Subject: [PATCH 0361/1782] Rework padding --- .../TestSceneDashboardBeatmapListing.cs | 8 +++++++- .../Dashboard/Dashboard/DashboardBeatmapPanel.cs | 12 ++++++++---- .../Dashboard/Dashboard/DrawableBeatmapList.cs | 3 +-- 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneDashboardBeatmapListing.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneDashboardBeatmapListing.cs index be0cc5187e..c5714dae46 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneDashboardBeatmapListing.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneDashboardBeatmapListing.cs @@ -36,7 +36,13 @@ namespace osu.Game.Tests.Visual.UserInterface RelativeSizeAxes = Axes.Both, Colour = colourProvider.Background4 }, - new DashboardBeatmapListing(new_beatmaps, popular_beatmaps) + new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding { Horizontal = 10 }, + Child = new DashboardBeatmapListing(new_beatmaps, popular_beatmaps) + } } }); } diff --git a/osu.Game/Overlays/Dashboard/Dashboard/DashboardBeatmapPanel.cs b/osu.Game/Overlays/Dashboard/Dashboard/DashboardBeatmapPanel.cs index 45fa56a177..d74cdf4414 100644 --- a/osu.Game/Overlays/Dashboard/Dashboard/DashboardBeatmapPanel.cs +++ b/osu.Game/Overlays/Dashboard/Dashboard/DashboardBeatmapPanel.cs @@ -41,16 +41,20 @@ namespace osu.Game.Overlays.Dashboard.Dashboard Height = 60; Children = new Drawable[] { - hoverBackground = new Box + new Container { RelativeSizeAxes = Axes.Both, - Colour = ColourProvider.Background3, - Alpha = 0 + Padding = new MarginPadding { Horizontal = -10 }, + Child = hoverBackground = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = ColourProvider.Background3, + Alpha = 0 + } }, new Container { RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Horizontal = 10 }, Child = new GridContainer { RelativeSizeAxes = Axes.Both, diff --git a/osu.Game/Overlays/Dashboard/Dashboard/DrawableBeatmapList.cs b/osu.Game/Overlays/Dashboard/Dashboard/DrawableBeatmapList.cs index 4837d587fd..6e42881de7 100644 --- a/osu.Game/Overlays/Dashboard/Dashboard/DrawableBeatmapList.cs +++ b/osu.Game/Overlays/Dashboard/Dashboard/DrawableBeatmapList.cs @@ -41,8 +41,7 @@ namespace osu.Game.Overlays.Dashboard.Dashboard { Font = OsuFont.GetFont(size: 16, weight: FontWeight.Bold), Colour = colourProvider.Light1, - Text = Title, - Padding = new MarginPadding { Left = 10 } + Text = Title } } }; From dc559093cdc519e600d7304e6f14bdcc06ea91aa Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 2 Aug 2020 21:47:09 +0300 Subject: [PATCH 0362/1782] Rename namespace from Dashboard to Home --- .../Visual/UserInterface/TestSceneDashboardBeatmapListing.cs | 2 +- .../Dashboard/{Dashboard => Home}/DashboardBeatmapListing.cs | 2 +- .../Dashboard/{Dashboard => Home}/DashboardBeatmapPanel.cs | 2 +- .../Dashboard/{Dashboard => Home}/DashboardNewBeatmapPanel.cs | 2 +- .../{Dashboard => Home}/DashboardPopularBeatmapPanel.cs | 2 +- .../Dashboard/{Dashboard => Home}/DrawableBeatmapList.cs | 2 +- .../Dashboard/{Dashboard => Home}/DrawableNewBeatmapList.cs | 2 +- .../Dashboard/{Dashboard => Home}/DrawablePopularBeatmapList.cs | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) rename osu.Game/Overlays/Dashboard/{Dashboard => Home}/DashboardBeatmapListing.cs (96%) rename osu.Game/Overlays/Dashboard/{Dashboard => Home}/DashboardBeatmapPanel.cs (99%) rename osu.Game/Overlays/Dashboard/{Dashboard => Home}/DashboardNewBeatmapPanel.cs (93%) rename osu.Game/Overlays/Dashboard/{Dashboard => Home}/DashboardPopularBeatmapPanel.cs (96%) rename osu.Game/Overlays/Dashboard/{Dashboard => Home}/DrawableBeatmapList.cs (97%) rename osu.Game/Overlays/Dashboard/{Dashboard => Home}/DrawableNewBeatmapList.cs (92%) rename osu.Game/Overlays/Dashboard/{Dashboard => Home}/DrawablePopularBeatmapList.cs (92%) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneDashboardBeatmapListing.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneDashboardBeatmapListing.cs index c5714dae46..c51204eaba 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneDashboardBeatmapListing.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneDashboardBeatmapListing.cs @@ -3,7 +3,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics; -using osu.Game.Overlays.Dashboard.Dashboard; +using osu.Game.Overlays.Dashboard.Home; using osu.Game.Beatmaps; using osu.Game.Overlays; using osu.Framework.Allocation; diff --git a/osu.Game/Overlays/Dashboard/Dashboard/DashboardBeatmapListing.cs b/osu.Game/Overlays/Dashboard/Home/DashboardBeatmapListing.cs similarity index 96% rename from osu.Game/Overlays/Dashboard/Dashboard/DashboardBeatmapListing.cs rename to osu.Game/Overlays/Dashboard/Home/DashboardBeatmapListing.cs index a8c6ab7ba2..4d96825353 100644 --- a/osu.Game/Overlays/Dashboard/Dashboard/DashboardBeatmapListing.cs +++ b/osu.Game/Overlays/Dashboard/Home/DashboardBeatmapListing.cs @@ -8,7 +8,7 @@ using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps; using osuTK; -namespace osu.Game.Overlays.Dashboard.Dashboard +namespace osu.Game.Overlays.Dashboard.Home { public class DashboardBeatmapListing : CompositeDrawable { diff --git a/osu.Game/Overlays/Dashboard/Dashboard/DashboardBeatmapPanel.cs b/osu.Game/Overlays/Dashboard/Home/DashboardBeatmapPanel.cs similarity index 99% rename from osu.Game/Overlays/Dashboard/Dashboard/DashboardBeatmapPanel.cs rename to osu.Game/Overlays/Dashboard/Home/DashboardBeatmapPanel.cs index d74cdf4414..0b660c16af 100644 --- a/osu.Game/Overlays/Dashboard/Dashboard/DashboardBeatmapPanel.cs +++ b/osu.Game/Overlays/Dashboard/Home/DashboardBeatmapPanel.cs @@ -14,7 +14,7 @@ using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osuTK; -namespace osu.Game.Overlays.Dashboard.Dashboard +namespace osu.Game.Overlays.Dashboard.Home { public abstract class DashboardBeatmapPanel : OsuClickableContainer { diff --git a/osu.Game/Overlays/Dashboard/Dashboard/DashboardNewBeatmapPanel.cs b/osu.Game/Overlays/Dashboard/Home/DashboardNewBeatmapPanel.cs similarity index 93% rename from osu.Game/Overlays/Dashboard/Dashboard/DashboardNewBeatmapPanel.cs rename to osu.Game/Overlays/Dashboard/Home/DashboardNewBeatmapPanel.cs index c4782b733d..b212eaf20a 100644 --- a/osu.Game/Overlays/Dashboard/Dashboard/DashboardNewBeatmapPanel.cs +++ b/osu.Game/Overlays/Dashboard/Home/DashboardNewBeatmapPanel.cs @@ -6,7 +6,7 @@ using osu.Framework.Graphics; using osu.Game.Beatmaps; using osu.Game.Graphics; -namespace osu.Game.Overlays.Dashboard.Dashboard +namespace osu.Game.Overlays.Dashboard.Home { public class DashboardNewBeatmapPanel : DashboardBeatmapPanel { diff --git a/osu.Game/Overlays/Dashboard/Dashboard/DashboardPopularBeatmapPanel.cs b/osu.Game/Overlays/Dashboard/Home/DashboardPopularBeatmapPanel.cs similarity index 96% rename from osu.Game/Overlays/Dashboard/Dashboard/DashboardPopularBeatmapPanel.cs rename to osu.Game/Overlays/Dashboard/Home/DashboardPopularBeatmapPanel.cs index 04bb261dce..2fb5617796 100644 --- a/osu.Game/Overlays/Dashboard/Dashboard/DashboardPopularBeatmapPanel.cs +++ b/osu.Game/Overlays/Dashboard/Home/DashboardPopularBeatmapPanel.cs @@ -9,7 +9,7 @@ using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osuTK; -namespace osu.Game.Overlays.Dashboard.Dashboard +namespace osu.Game.Overlays.Dashboard.Home { public class DashboardPopularBeatmapPanel : DashboardBeatmapPanel { diff --git a/osu.Game/Overlays/Dashboard/Dashboard/DrawableBeatmapList.cs b/osu.Game/Overlays/Dashboard/Home/DrawableBeatmapList.cs similarity index 97% rename from osu.Game/Overlays/Dashboard/Dashboard/DrawableBeatmapList.cs rename to osu.Game/Overlays/Dashboard/Home/DrawableBeatmapList.cs index 6e42881de7..f6535b7db3 100644 --- a/osu.Game/Overlays/Dashboard/Dashboard/DrawableBeatmapList.cs +++ b/osu.Game/Overlays/Dashboard/Home/DrawableBeatmapList.cs @@ -11,7 +11,7 @@ using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osuTK; -namespace osu.Game.Overlays.Dashboard.Dashboard +namespace osu.Game.Overlays.Dashboard.Home { public abstract class DrawableBeatmapList : CompositeDrawable { diff --git a/osu.Game/Overlays/Dashboard/Dashboard/DrawableNewBeatmapList.cs b/osu.Game/Overlays/Dashboard/Home/DrawableNewBeatmapList.cs similarity index 92% rename from osu.Game/Overlays/Dashboard/Dashboard/DrawableNewBeatmapList.cs rename to osu.Game/Overlays/Dashboard/Home/DrawableNewBeatmapList.cs index 9856f6ae3e..75e8ca336d 100644 --- a/osu.Game/Overlays/Dashboard/Dashboard/DrawableNewBeatmapList.cs +++ b/osu.Game/Overlays/Dashboard/Home/DrawableNewBeatmapList.cs @@ -4,7 +4,7 @@ using System.Collections.Generic; using osu.Game.Beatmaps; -namespace osu.Game.Overlays.Dashboard.Dashboard +namespace osu.Game.Overlays.Dashboard.Home { public class DrawableNewBeatmapList : DrawableBeatmapList { diff --git a/osu.Game/Overlays/Dashboard/Dashboard/DrawablePopularBeatmapList.cs b/osu.Game/Overlays/Dashboard/Home/DrawablePopularBeatmapList.cs similarity index 92% rename from osu.Game/Overlays/Dashboard/Dashboard/DrawablePopularBeatmapList.cs rename to osu.Game/Overlays/Dashboard/Home/DrawablePopularBeatmapList.cs index 294a75c48a..90bd00008c 100644 --- a/osu.Game/Overlays/Dashboard/Dashboard/DrawablePopularBeatmapList.cs +++ b/osu.Game/Overlays/Dashboard/Home/DrawablePopularBeatmapList.cs @@ -4,7 +4,7 @@ using System.Collections.Generic; using osu.Game.Beatmaps; -namespace osu.Game.Overlays.Dashboard.Dashboard +namespace osu.Game.Overlays.Dashboard.Home { public class DrawablePopularBeatmapList : DrawableBeatmapList { From b96e32b0bbd741373689ad7755b54d576d87dd56 Mon Sep 17 00:00:00 2001 From: Joehu Date: Sun, 2 Aug 2020 12:26:09 -0700 Subject: [PATCH 0363/1782] Add xmldoc for updateBindTarget --- osu.Game/Overlays/KeyBinding/KeyBindingRow.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs b/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs index d58acc1ac4..e5b246807c 100644 --- a/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs +++ b/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs @@ -320,6 +320,9 @@ namespace osu.Game.Overlays.KeyBinding base.OnFocusLost(e); } + /// + /// Updates the bind target to the currently hovered key button or the first if clicked anywhere else. + /// private void updateBindTarget() { if (bindTarget != null) bindTarget.IsBinding = false; From f1ba576438eb84e9303619fb6bec9f54ad3c5a1c Mon Sep 17 00:00:00 2001 From: Lucas A Date: Sun, 2 Aug 2020 21:34:35 +0200 Subject: [PATCH 0364/1782] Disable overlay activation when in gameplay. --- osu.Game/Screens/Play/Player.cs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 541275cf55..24c27fde8d 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -63,6 +63,8 @@ namespace osu.Game.Screens.Play private Bindable mouseWheelDisabled; + private Bindable gameplayOverlaysDisabled; + private readonly Bindable storyboardReplacesBackground = new Bindable(); public int RestartCount; @@ -77,6 +79,9 @@ namespace osu.Game.Screens.Play [Resolved] private IAPIProvider api { get; set; } + [Resolved] + private OsuGame game { get; set; } + private SampleChannel sampleRestart; public BreakOverlay BreakOverlay; @@ -165,6 +170,7 @@ namespace osu.Game.Screens.Play sampleRestart = audio.Samples.Get(@"Gameplay/restart"); mouseWheelDisabled = config.GetBindable(OsuSetting.MouseDisableWheel); + gameplayOverlaysDisabled = config.GetBindable(OsuSetting.GameplayDisableOverlayActivation); DrawableRuleset = ruleset.CreateDrawableRulesetWith(playableBeatmap, Mods.Value); @@ -197,6 +203,13 @@ namespace osu.Game.Screens.Play skipOverlay.Hide(); } + gameplayOverlaysDisabled.ValueChanged += disabled => + { + game.OverlayActivationMode.Value = disabled.NewValue && !DrawableRuleset.IsPaused.Value ? OverlayActivation.Disabled : OverlayActivation.All; + }; + DrawableRuleset.IsPaused.BindValueChanged(_ => gameplayOverlaysDisabled.TriggerChange()); + + DrawableRuleset.HasReplayLoaded.BindValueChanged(_ => updatePauseOnFocusLostState(), true); // bind clock into components that require it @@ -627,6 +640,8 @@ namespace osu.Game.Screens.Play foreach (var mod in Mods.Value.OfType()) mod.ApplyToHUD(HUDOverlay); + + gameplayOverlaysDisabled.TriggerChange(); } public override void OnSuspending(IScreen next) From ba77fa2945475ab3d6eb8092f384c4500f1a6f51 Mon Sep 17 00:00:00 2001 From: Joehu Date: Sun, 2 Aug 2020 12:41:35 -0700 Subject: [PATCH 0365/1782] Add test for clear button --- .../Settings/TestSceneKeyBindingPanel.cs | 40 +++++++++++++++++++ osu.Game/Overlays/KeyBinding/KeyBindingRow.cs | 14 +++---- 2 files changed, 47 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs b/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs index 3d335995ac..e7a1cab8eb 100644 --- a/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs +++ b/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs @@ -64,5 +64,45 @@ namespace osu.Game.Tests.Visual.Settings }, 0, true); }); } + + [Test] + public void TestClearButtonOnBindings() + { + KeyBindingRow backBindingRow = null; + + AddStep("click back binding row", () => + { + backBindingRow = panel.ChildrenOfType().ElementAt(10); + InputManager.MoveMouseTo(backBindingRow); + InputManager.Click(MouseButton.Left); + }); + + clickClearButton(); + + AddAssert("first binding cleared", () => string.IsNullOrEmpty(backBindingRow.Buttons.First().Text.Text)); + + AddStep("click second binding", () => + { + var target = backBindingRow.Buttons.ElementAt(1); + + InputManager.MoveMouseTo(target); + InputManager.Click(MouseButton.Left); + }); + + clickClearButton(); + + AddAssert("second binding cleared", () => string.IsNullOrEmpty(backBindingRow.Buttons.ElementAt(1).Text.Text)); + + void clickClearButton() + { + AddStep("click clear button", () => + { + var clearButton = backBindingRow.ChildrenOfType().Single(); + + InputManager.MoveMouseTo(clearButton); + InputManager.Click(MouseButton.Left); + }); + } + } } } diff --git a/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs b/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs index e5b246807c..44b5fe09f4 100644 --- a/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs +++ b/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs @@ -50,7 +50,7 @@ namespace osu.Game.Overlays.KeyBinding private OsuSpriteText text; private Drawable pressAKey; - private FillFlowContainer buttons; + public FillFlowContainer Buttons; public IEnumerable FilterTerms => bindings.Select(b => b.KeyCombination.ReadableString()).Prepend((string)text.Text); @@ -93,7 +93,7 @@ namespace osu.Game.Overlays.KeyBinding Text = action.GetDescription(), Margin = new MarginPadding(padding), }, - buttons = new FillFlowContainer + Buttons = new FillFlowContainer { AutoSizeAxes = Axes.Both, Anchor = Anchor.TopRight, @@ -116,7 +116,7 @@ namespace osu.Game.Overlays.KeyBinding }; foreach (var b in bindings) - buttons.Add(new KeyButton(b)); + Buttons.Add(new KeyButton(b)); } public void RestoreDefaults() @@ -125,7 +125,7 @@ namespace osu.Game.Overlays.KeyBinding foreach (var d in Defaults) { - var button = buttons[i++]; + var button = Buttons[i++]; button.UpdateKeyCombination(d); store.Update(button.KeyBinding); } @@ -187,7 +187,7 @@ namespace osu.Game.Overlays.KeyBinding if (bindTarget.IsHovered) finalise(); - else if (buttons.Any(b => b.IsHovered)) + else if (Buttons.Any(b => b.IsHovered)) updateBindTarget(); } @@ -326,7 +326,7 @@ namespace osu.Game.Overlays.KeyBinding private void updateBindTarget() { if (bindTarget != null) bindTarget.IsBinding = false; - bindTarget = buttons.FirstOrDefault(b => b.IsHovered) ?? buttons.FirstOrDefault(); + bindTarget = Buttons.FirstOrDefault(b => b.IsHovered) ?? Buttons.FirstOrDefault(); if (bindTarget != null) bindTarget.IsBinding = true; } @@ -357,7 +357,7 @@ namespace osu.Game.Overlays.KeyBinding } } - private class KeyButton : Container + public class KeyButton : Container { public readonly Framework.Input.Bindings.KeyBinding KeyBinding; From d49e54deb60b3cfee754697c446b9b4ea21cfb2a Mon Sep 17 00:00:00 2001 From: Joehu Date: Sun, 2 Aug 2020 12:47:23 -0700 Subject: [PATCH 0366/1782] Add failing test for another regressing behavior --- .../Settings/TestSceneKeyBindingPanel.cs | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs b/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs index e7a1cab8eb..96075d56d1 100644 --- a/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs +++ b/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs @@ -104,5 +104,37 @@ namespace osu.Game.Tests.Visual.Settings }); } } + + [Test] + public void TestClickRowSelectsFirstBinding() + { + KeyBindingRow backBindingRow = null; + + AddStep("click back binding row", () => + { + backBindingRow = panel.ChildrenOfType().ElementAt(10); + InputManager.MoveMouseTo(backBindingRow); + InputManager.Click(MouseButton.Left); + }); + + AddAssert("first binding selected", () => backBindingRow.Buttons.First().IsBinding); + + AddStep("click second binding", () => + { + var target = backBindingRow.Buttons.ElementAt(1); + + InputManager.MoveMouseTo(target); + InputManager.Click(MouseButton.Left); + }); + + AddStep("click back binding row", () => + { + backBindingRow = panel.ChildrenOfType().ElementAt(10); + InputManager.MoveMouseTo(backBindingRow); + InputManager.Click(MouseButton.Left); + }); + + AddAssert("first binding selected", () => backBindingRow.Buttons.First().IsBinding); + } } } From 7aafc018ad384f5e1d10437519df539a93f4a91b Mon Sep 17 00:00:00 2001 From: Joehu Date: Sun, 2 Aug 2020 12:52:12 -0700 Subject: [PATCH 0367/1782] Prevent updating bind target when hovering cancel and clear buttons instead --- osu.Game/Overlays/KeyBinding/KeyBindingRow.cs | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs b/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs index 44b5fe09f4..a7394579bd 100644 --- a/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs +++ b/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs @@ -48,7 +48,7 @@ namespace osu.Game.Overlays.KeyBinding public bool FilteringActive { get; set; } private OsuSpriteText text; - private Drawable pressAKey; + private FillFlowContainer cancelAndClearButtons; public FillFlowContainer Buttons; @@ -80,7 +80,7 @@ namespace osu.Game.Overlays.KeyBinding Hollow = true, }; - Children = new[] + Children = new Drawable[] { new Box { @@ -99,7 +99,7 @@ namespace osu.Game.Overlays.KeyBinding Anchor = Anchor.TopRight, Origin = Anchor.TopRight }, - pressAKey = new FillFlowContainer + cancelAndClearButtons = new FillFlowContainer { AutoSizeAxes = Axes.Both, Padding = new MarginPadding(padding) { Top = height + padding * 2 }, @@ -187,7 +187,8 @@ namespace osu.Game.Overlays.KeyBinding if (bindTarget.IsHovered) finalise(); - else if (Buttons.Any(b => b.IsHovered)) + // prevent updating bind target before clear button's action + else if (!cancelAndClearButtons.Any(b => b.IsHovered)) updateBindTarget(); } @@ -298,8 +299,8 @@ namespace osu.Game.Overlays.KeyBinding if (HasFocus) GetContainingInputManager().ChangeFocus(null); - pressAKey.FadeOut(300, Easing.OutQuint); - pressAKey.BypassAutoSizeAxes |= Axes.Y; + cancelAndClearButtons.FadeOut(300, Easing.OutQuint); + cancelAndClearButtons.BypassAutoSizeAxes |= Axes.Y; } protected override void OnFocus(FocusEvent e) @@ -307,8 +308,8 @@ namespace osu.Game.Overlays.KeyBinding AutoSizeDuration = 500; AutoSizeEasing = Easing.OutQuint; - pressAKey.FadeIn(300, Easing.OutQuint); - pressAKey.BypassAutoSizeAxes &= ~Axes.Y; + cancelAndClearButtons.FadeIn(300, Easing.OutQuint); + cancelAndClearButtons.BypassAutoSizeAxes &= ~Axes.Y; updateBindTarget(); base.OnFocus(e); From fe97d472dfafa30cdd7686074191d6b04815446f Mon Sep 17 00:00:00 2001 From: Lucas A Date: Sun, 2 Aug 2020 21:53:13 +0200 Subject: [PATCH 0368/1782] Enable back overlays when a replay is loaded. --- osu.Game/Screens/Play/Player.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 24c27fde8d..9d4a4741d5 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -205,10 +205,13 @@ namespace osu.Game.Screens.Play gameplayOverlaysDisabled.ValueChanged += disabled => { - game.OverlayActivationMode.Value = disabled.NewValue && !DrawableRuleset.IsPaused.Value ? OverlayActivation.Disabled : OverlayActivation.All; + if (DrawableRuleset.HasReplayLoaded.Value) + game.OverlayActivationMode.Value = OverlayActivation.UserTriggered; + else + game.OverlayActivationMode.Value = disabled.NewValue && !DrawableRuleset.IsPaused.Value ? OverlayActivation.Disabled : OverlayActivation.UserTriggered; }; DrawableRuleset.IsPaused.BindValueChanged(_ => gameplayOverlaysDisabled.TriggerChange()); - + DrawableRuleset.HasReplayLoaded.BindValueChanged(_ => gameplayOverlaysDisabled.TriggerChange()); DrawableRuleset.HasReplayLoaded.BindValueChanged(_ => updatePauseOnFocusLostState(), true); From 435c9de8b9d500890fb649809f38d6263fca2a89 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 3 Aug 2020 15:25:23 +0900 Subject: [PATCH 0369/1782] Re-privatise buttons --- .../Visual/Settings/TestSceneKeyBindingPanel.cs | 12 ++++++------ osu.Game/Overlays/KeyBinding/KeyBindingRow.cs | 11 +++++------ 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs b/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs index 96075d56d1..e06b3a8a7e 100644 --- a/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs +++ b/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs @@ -79,11 +79,11 @@ namespace osu.Game.Tests.Visual.Settings clickClearButton(); - AddAssert("first binding cleared", () => string.IsNullOrEmpty(backBindingRow.Buttons.First().Text.Text)); + AddAssert("first binding cleared", () => string.IsNullOrEmpty(backBindingRow.ChildrenOfType().First().Text.Text)); AddStep("click second binding", () => { - var target = backBindingRow.Buttons.ElementAt(1); + var target = backBindingRow.ChildrenOfType().ElementAt(1); InputManager.MoveMouseTo(target); InputManager.Click(MouseButton.Left); @@ -91,7 +91,7 @@ namespace osu.Game.Tests.Visual.Settings clickClearButton(); - AddAssert("second binding cleared", () => string.IsNullOrEmpty(backBindingRow.Buttons.ElementAt(1).Text.Text)); + AddAssert("second binding cleared", () => string.IsNullOrEmpty(backBindingRow.ChildrenOfType().ElementAt(1).Text.Text)); void clickClearButton() { @@ -117,11 +117,11 @@ namespace osu.Game.Tests.Visual.Settings InputManager.Click(MouseButton.Left); }); - AddAssert("first binding selected", () => backBindingRow.Buttons.First().IsBinding); + AddAssert("first binding selected", () => backBindingRow.ChildrenOfType().First().IsBinding); AddStep("click second binding", () => { - var target = backBindingRow.Buttons.ElementAt(1); + var target = backBindingRow.ChildrenOfType().ElementAt(1); InputManager.MoveMouseTo(target); InputManager.Click(MouseButton.Left); @@ -134,7 +134,7 @@ namespace osu.Game.Tests.Visual.Settings InputManager.Click(MouseButton.Left); }); - AddAssert("first binding selected", () => backBindingRow.Buttons.First().IsBinding); + AddAssert("first binding selected", () => backBindingRow.ChildrenOfType().First().IsBinding); } } } diff --git a/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs b/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs index a7394579bd..b808d49fa2 100644 --- a/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs +++ b/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs @@ -49,8 +49,7 @@ namespace osu.Game.Overlays.KeyBinding private OsuSpriteText text; private FillFlowContainer cancelAndClearButtons; - - public FillFlowContainer Buttons; + private FillFlowContainer buttons; public IEnumerable FilterTerms => bindings.Select(b => b.KeyCombination.ReadableString()).Prepend((string)text.Text); @@ -93,7 +92,7 @@ namespace osu.Game.Overlays.KeyBinding Text = action.GetDescription(), Margin = new MarginPadding(padding), }, - Buttons = new FillFlowContainer + buttons = new FillFlowContainer { AutoSizeAxes = Axes.Both, Anchor = Anchor.TopRight, @@ -116,7 +115,7 @@ namespace osu.Game.Overlays.KeyBinding }; foreach (var b in bindings) - Buttons.Add(new KeyButton(b)); + buttons.Add(new KeyButton(b)); } public void RestoreDefaults() @@ -125,7 +124,7 @@ namespace osu.Game.Overlays.KeyBinding foreach (var d in Defaults) { - var button = Buttons[i++]; + var button = buttons[i++]; button.UpdateKeyCombination(d); store.Update(button.KeyBinding); } @@ -327,7 +326,7 @@ namespace osu.Game.Overlays.KeyBinding private void updateBindTarget() { if (bindTarget != null) bindTarget.IsBinding = false; - bindTarget = Buttons.FirstOrDefault(b => b.IsHovered) ?? Buttons.FirstOrDefault(); + bindTarget = buttons.FirstOrDefault(b => b.IsHovered) ?? buttons.FirstOrDefault(); if (bindTarget != null) bindTarget.IsBinding = true; } From 630322ff85a5c08a1b71e2a35404386fabd483e7 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 3 Aug 2020 09:55:06 +0300 Subject: [PATCH 0370/1782] Adjust font weights in line with web --- osu.Game/Overlays/Dashboard/Home/DashboardBeatmapPanel.cs | 6 +++--- .../Overlays/Dashboard/Home/DashboardPopularBeatmapPanel.cs | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/Dashboard/Home/DashboardBeatmapPanel.cs b/osu.Game/Overlays/Dashboard/Home/DashboardBeatmapPanel.cs index 0b660c16af..3badea155d 100644 --- a/osu.Game/Overlays/Dashboard/Home/DashboardBeatmapPanel.cs +++ b/osu.Game/Overlays/Dashboard/Home/DashboardBeatmapPanel.cs @@ -102,17 +102,17 @@ namespace osu.Game.Overlays.Dashboard.Home { RelativeSizeAxes = Axes.X, Truncate = true, - Font = OsuFont.GetFont(weight: FontWeight.SemiBold), + Font = OsuFont.GetFont(weight: FontWeight.Regular), Text = SetInfo.Metadata.Title }, new OsuSpriteText { RelativeSizeAxes = Axes.X, Truncate = true, - Font = OsuFont.GetFont(size: 12, weight: FontWeight.SemiBold), + Font = OsuFont.GetFont(size: 12, weight: FontWeight.Regular), Text = SetInfo.Metadata.Artist }, - new LinkFlowContainer(f => f.Font = OsuFont.GetFont(size: 10, weight: FontWeight.SemiBold)) + new LinkFlowContainer(f => f.Font = OsuFont.GetFont(size: 10, weight: FontWeight.Regular)) { AutoSizeAxes = Axes.Y, RelativeSizeAxes = Axes.X, diff --git a/osu.Game/Overlays/Dashboard/Home/DashboardPopularBeatmapPanel.cs b/osu.Game/Overlays/Dashboard/Home/DashboardPopularBeatmapPanel.cs index 2fb5617796..e9066c0657 100644 --- a/osu.Game/Overlays/Dashboard/Home/DashboardPopularBeatmapPanel.cs +++ b/osu.Game/Overlays/Dashboard/Home/DashboardPopularBeatmapPanel.cs @@ -33,7 +33,7 @@ namespace osu.Game.Overlays.Dashboard.Home }, new OsuSpriteText { - Font = OsuFont.GetFont(size: 10), + Font = OsuFont.GetFont(size: 10, weight: FontWeight.Regular), Text = SetInfo.OnlineInfo.FavouriteCount.ToString() } } From af320e4a619784cd979c1eba9af93a9e29ee97d8 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 3 Aug 2020 10:03:42 +0300 Subject: [PATCH 0371/1782] Fix NewsOverlay running request on startup --- osu.Game/Overlays/NewsOverlay.cs | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/NewsOverlay.cs b/osu.Game/Overlays/NewsOverlay.cs index a5687b77e2..f8666e22c5 100644 --- a/osu.Game/Overlays/NewsOverlay.cs +++ b/osu.Game/Overlays/NewsOverlay.cs @@ -67,7 +67,26 @@ namespace osu.Game.Overlays protected override void LoadComplete() { base.LoadComplete(); - article.BindValueChanged(onArticleChanged, true); + article.BindValueChanged(onArticleChanged); + } + + private bool displayUpdateRequired = true; + + protected override void PopIn() + { + base.PopIn(); + + if (displayUpdateRequired) + { + article.TriggerChange(); + displayUpdateRequired = false; + } + } + + protected override void PopOutComplete() + { + base.PopOutComplete(); + displayUpdateRequired = true; } public void ShowFrontPage() From f812767c95428a679d6e5646fc9c6cc5b608ed17 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 3 Aug 2020 18:48:10 +0900 Subject: [PATCH 0372/1782] Add fallback hash generation to fix android startup crash --- osu.Game/OsuGameBase.cs | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 278f2d849f..98f60d52d3 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -36,6 +36,7 @@ using osu.Game.Rulesets.Mods; using osu.Game.Scoring; using osu.Game.Skinning; using osuTK.Input; +using RuntimeInfo = osu.Framework.RuntimeInfo; namespace osu.Game { @@ -134,8 +135,17 @@ namespace osu.Game [BackgroundDependencyLoader] private void load() { - using (var str = File.OpenRead(typeof(OsuGameBase).Assembly.Location)) - VersionHash = str.ComputeMD5Hash(); + try + { + using (var str = File.OpenRead(typeof(OsuGameBase).Assembly.Location)) + VersionHash = str.ComputeMD5Hash(); + } + catch + { + // special case for android builds, which can't read DLLs from a packed apk. + // should eventually be handled in a better way. + VersionHash = $"{Version}-{RuntimeInfo.OS}".ComputeMD5Hash(); + } Resources.AddStore(new DllResourceStore(OsuResources.ResourceAssembly)); From c48648ea2ac014f40281f7b0f313949b7d454fda Mon Sep 17 00:00:00 2001 From: Sebastian Krajewski Date: Mon, 3 Aug 2020 12:59:15 +0200 Subject: [PATCH 0373/1782] Add playfield shift like in osu-stable --- 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 9c8be868b0..93d88f3690 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfieldAdjustmentContainer.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfieldAdjustmentContainer.cs @@ -55,6 +55,7 @@ namespace osu.Game.Rulesets.Osu.UI // Scale = 819.2 / 512 // Scale = 1.6 Scale = new Vector2(Parent.ChildSize.X / OsuPlayfield.BASE_SIZE.X); + Position = new Vector2(0, 8 * Parent.ChildSize.Y / OsuPlayfield.BASE_SIZE.Y); // Size = 0.625 Size = Vector2.Divide(Vector2.One, Scale); } From 675f618b288cc1e75fed0a152c7908e61e77e9d4 Mon Sep 17 00:00:00 2001 From: Sebastian Krajewski Date: Mon, 3 Aug 2020 14:18:45 +0200 Subject: [PATCH 0374/1782] Apply playfield's offset only in play mode --- osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs | 2 +- .../UI/OsuPlayfieldAdjustmentContainer.cs | 12 +++++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs b/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs index b4d51d11c9..e6e37fd58e 100644 --- a/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs @@ -35,7 +35,7 @@ namespace osu.Game.Rulesets.Osu.UI protected override PassThroughInputManager CreateInputManager() => new OsuInputManager(Ruleset.RulesetInfo); - public override PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => new OsuPlayfieldAdjustmentContainer(); + public override PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => new OsuPlayfieldAdjustmentContainer { PlayfieldShift = true }; protected override ResumeOverlay CreateResumeOverlay() => new OsuResumeOverlay(); diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfieldAdjustmentContainer.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfieldAdjustmentContainer.cs index 93d88f3690..700601dbfd 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfieldAdjustmentContainer.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfieldAdjustmentContainer.cs @@ -15,6 +15,14 @@ namespace osu.Game.Rulesets.Osu.UI private const float playfield_size_adjust = 0.8f; + /// + /// Whether an osu-stable playfield offset should be applied (8 osu!pixels) + /// + public bool PlayfieldShift + { + set => ((ScalingContainer)content).PlayfieldShift = value; + } + public OsuPlayfieldAdjustmentContainer() { Anchor = Anchor.Centre; @@ -39,6 +47,8 @@ namespace osu.Game.Rulesets.Osu.UI ///
private class ScalingContainer : Container { + internal bool PlayfieldShift { get; set; } + protected override void Update() { base.Update(); @@ -55,7 +65,7 @@ namespace osu.Game.Rulesets.Osu.UI // Scale = 819.2 / 512 // Scale = 1.6 Scale = new Vector2(Parent.ChildSize.X / OsuPlayfield.BASE_SIZE.X); - Position = new Vector2(0, 8 * Parent.ChildSize.Y / OsuPlayfield.BASE_SIZE.Y); + Position = new Vector2(0, (PlayfieldShift ? 8f : 0f) * Parent.ChildSize.Y / OsuPlayfield.BASE_SIZE.Y); // Size = 0.625 Size = Vector2.Divide(Vector2.One, Scale); } From 4d6f60edaf8b49a95ede7b5a97fdcca16dfd13ff Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 3 Aug 2020 22:41:22 +0900 Subject: [PATCH 0375/1782] Fix multiplayer match select forcing playback even when user paused --- osu.Game/Screens/Multi/Multiplayer.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Multi/Multiplayer.cs b/osu.Game/Screens/Multi/Multiplayer.cs index 269eab5772..4912df17b1 100644 --- a/osu.Game/Screens/Multi/Multiplayer.cs +++ b/osu.Game/Screens/Multi/Multiplayer.cs @@ -17,6 +17,7 @@ using osu.Game.Graphics.UserInterface; using osu.Game.Input; using osu.Game.Online.API; using osu.Game.Online.Multiplayer; +using osu.Game.Overlays; using osu.Game.Screens.Menu; using osu.Game.Screens.Multi.Components; using osu.Game.Screens.Multi.Lounge; @@ -50,6 +51,9 @@ namespace osu.Game.Screens.Multi [Cached] private readonly Bindable currentFilter = new Bindable(new FilterCriteria()); + [Resolved] + private MusicController music { get; set; } + [Cached(Type = typeof(IRoomManager))] private RoomManager roomManager; @@ -346,8 +350,7 @@ namespace osu.Game.Screens.Multi track.RestartPoint = Beatmap.Value.Metadata.PreviewTime; track.Looping = true; - if (!track.IsRunning) - track.Restart(); + music.EnsurePlayingSomething(); } } else From 25ebb8619d2cbaa6fc232ed742d33da91b80d703 Mon Sep 17 00:00:00 2001 From: Lucas A Date: Mon, 3 Aug 2020 16:00:10 +0200 Subject: [PATCH 0376/1782] Add tests. --- .../Gameplay/TestSceneOverlayActivation.cs | 54 +++++++++++++++++++ osu.Game/Screens/Play/Player.cs | 11 ++-- 2 files changed, 62 insertions(+), 3 deletions(-) create mode 100644 osu.Game.Tests/Visual/Gameplay/TestSceneOverlayActivation.cs diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneOverlayActivation.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneOverlayActivation.cs new file mode 100644 index 0000000000..107a3a2a4b --- /dev/null +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneOverlayActivation.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 NUnit.Framework; +using osu.Framework.Allocation; +using osu.Game.Configuration; +using osu.Game.Overlays; +using osu.Game.Rulesets; + +namespace osu.Game.Tests.Visual.Gameplay +{ + public class TestSceneOverlayActivation : OsuPlayerTestScene + { + private OverlayTestPlayer testPlayer; + + [Resolved] + private OsuConfigManager mng { get; set; } + + public override void SetUpSteps() + { + AddStep("disable overlay activation during gameplay", () => mng.Set(OsuSetting.GameplayDisableOverlayActivation, true)); + base.SetUpSteps(); + } + + [Test] + public void TestGameplayOverlayActivationSetting() + { + AddAssert("activation mode is disabled", () => testPlayer.OverlayActivationMode == OverlayActivation.Disabled); + } + + [Test] + public void TestGameplayOverlayActivationPaused() + { + AddUntilStep("activation mode is disabled", () => testPlayer.OverlayActivationMode == OverlayActivation.Disabled); + AddStep("pause gameplay", () => testPlayer.Pause()); + AddUntilStep("activation mode is user triggered", () => testPlayer.OverlayActivationMode == OverlayActivation.UserTriggered); + } + + [Test] + public void TestGameplayOverlayActivationReplayLoaded() + { + AddAssert("activation mode is disabled", () => testPlayer.OverlayActivationMode == OverlayActivation.Disabled); + AddStep("load a replay", () => testPlayer.DrawableRuleset.HasReplayLoaded.Value = true); + AddAssert("activation mode is user triggered", () => testPlayer.OverlayActivationMode == OverlayActivation.UserTriggered); + } + + protected override TestPlayer CreatePlayer(Ruleset ruleset) => testPlayer = new OverlayTestPlayer(); + + private class OverlayTestPlayer : TestPlayer + { + public new OverlayActivation OverlayActivationMode => base.OverlayActivationMode.Value; + } + } +} diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 9d4a4741d5..45a9b442be 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -79,7 +79,7 @@ namespace osu.Game.Screens.Play [Resolved] private IAPIProvider api { get; set; } - [Resolved] + [Resolved(CanBeNull = true)] private OsuGame game { get; set; } private SampleChannel sampleRestart; @@ -90,6 +90,8 @@ namespace osu.Game.Screens.Play private SkipOverlay skipOverlay; + protected readonly Bindable OverlayActivationMode = new Bindable(OverlayActivation.Disabled); + protected ScoreProcessor ScoreProcessor { get; private set; } protected HealthProcessor HealthProcessor { get; private set; } @@ -203,12 +205,15 @@ namespace osu.Game.Screens.Play skipOverlay.Hide(); } + if (game != null) + OverlayActivationMode.BindTo(game.OverlayActivationMode); + gameplayOverlaysDisabled.ValueChanged += disabled => { if (DrawableRuleset.HasReplayLoaded.Value) - game.OverlayActivationMode.Value = OverlayActivation.UserTriggered; + OverlayActivationMode.Value = OverlayActivation.UserTriggered; else - game.OverlayActivationMode.Value = disabled.NewValue && !DrawableRuleset.IsPaused.Value ? OverlayActivation.Disabled : OverlayActivation.UserTriggered; + OverlayActivationMode.Value = disabled.NewValue && !DrawableRuleset.IsPaused.Value ? OverlayActivation.Disabled : OverlayActivation.UserTriggered; }; DrawableRuleset.IsPaused.BindValueChanged(_ => gameplayOverlaysDisabled.TriggerChange()); DrawableRuleset.HasReplayLoaded.BindValueChanged(_ => gameplayOverlaysDisabled.TriggerChange()); From 9d10658e3c8cd5d5f0b7d24018401c30fdbec246 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 3 Aug 2020 20:14:17 +0300 Subject: [PATCH 0377/1782] Allow providing custom sprite text for RollingCounter --- .../Gameplay/Components/MatchScoreDisplay.cs | 18 ++++-- .../UserInterface/PercentageCounter.cs | 9 ++- .../Graphics/UserInterface/RollingCounter.cs | 61 +++++++++++++------ .../Graphics/UserInterface/ScoreCounter.cs | 9 ++- .../Expanded/Statistics/AccuracyStatistic.cs | 15 +++-- .../Expanded/Statistics/CounterStatistic.cs | 9 ++- .../Ranking/Expanded/TotalScoreCounter.cs | 17 ++++-- 7 files changed, 97 insertions(+), 41 deletions(-) diff --git a/osu.Game.Tournament/Screens/Gameplay/Components/MatchScoreDisplay.cs b/osu.Game.Tournament/Screens/Gameplay/Components/MatchScoreDisplay.cs index 2e7484542a..25417921bc 100644 --- a/osu.Game.Tournament/Screens/Gameplay/Components/MatchScoreDisplay.cs +++ b/osu.Game.Tournament/Screens/Gameplay/Components/MatchScoreDisplay.cs @@ -8,6 +8,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Tournament.IPC; using osu.Game.Tournament.Models; @@ -127,21 +128,28 @@ namespace osu.Game.Tournament.Screens.Gameplay.Components private class MatchScoreCounter : ScoreCounter { + private OsuSpriteText displayedSpriteText; + public MatchScoreCounter() { Margin = new MarginPadding { Top = bar_height, Horizontal = 10 }; - - Winning = false; - - DisplayedCountSpriteText.Spacing = new Vector2(-6); } public bool Winning { - set => DisplayedCountSpriteText.Font = value + set => displayedSpriteText.Font = value ? OsuFont.Torus.With(weight: FontWeight.Bold, size: 50, fixedWidth: true) : OsuFont.Torus.With(weight: FontWeight.Regular, size: 40, fixedWidth: true); } + + protected override OsuSpriteText CreateSpriteText() + { + displayedSpriteText = base.CreateSpriteText(); + displayedSpriteText.Spacing = new Vector2(-6); + Winning = false; + + return displayedSpriteText; + } } } } diff --git a/osu.Game/Graphics/UserInterface/PercentageCounter.cs b/osu.Game/Graphics/UserInterface/PercentageCounter.cs index 940c9808ce..9b31935eee 100644 --- a/osu.Game/Graphics/UserInterface/PercentageCounter.cs +++ b/osu.Game/Graphics/UserInterface/PercentageCounter.cs @@ -3,6 +3,7 @@ using System; using osu.Framework.Allocation; +using osu.Game.Graphics.Sprites; using osu.Game.Utils; namespace osu.Game.Graphics.UserInterface @@ -23,7 +24,6 @@ namespace osu.Game.Graphics.UserInterface public PercentageCounter() { - DisplayedCountSpriteText.Font = DisplayedCountSpriteText.Font.With(fixedWidth: true); Current.Value = DisplayedCount = 1.0f; } @@ -37,6 +37,13 @@ namespace osu.Game.Graphics.UserInterface return Math.Abs(currentValue - newValue) * RollingDuration * 100.0f; } + protected override OsuSpriteText CreateSpriteText() + { + var spriteText = base.CreateSpriteText(); + spriteText.Font = spriteText.Font.With(fixedWidth: true); + return spriteText; + } + public override void Increment(double amount) { Current.Value += amount; diff --git a/osu.Game/Graphics/UserInterface/RollingCounter.cs b/osu.Game/Graphics/UserInterface/RollingCounter.cs index cd244ed7e6..76bb4bf69d 100644 --- a/osu.Game/Graphics/UserInterface/RollingCounter.cs +++ b/osu.Game/Graphics/UserInterface/RollingCounter.cs @@ -7,6 +7,7 @@ using osu.Framework.Graphics.Sprites; using osu.Game.Graphics.Sprites; using System; using System.Collections.Generic; +using osu.Framework.Allocation; using osu.Framework.Bindables; using osuTK.Graphics; @@ -20,7 +21,7 @@ namespace osu.Game.Graphics.UserInterface /// public Bindable Current = new Bindable(); - protected SpriteText DisplayedCountSpriteText; + private SpriteText displayedCountSpriteText; /// public void Stop() { - var track = current?.Track; - IsUserPaused = true; - if (track?.IsRunning == true) - track.Stop(); + if (drawableTrack?.IsRunning == true) + drawableTrack.Stop(); } /// @@ -196,9 +235,7 @@ namespace osu.Game.Overlays /// Whether the operation was successful. public bool TogglePause() { - var track = current?.Track; - - if (track?.IsRunning == true) + if (drawableTrack?.IsRunning == true) Stop(); else Play(); @@ -220,7 +257,7 @@ namespace osu.Game.Overlays if (beatmap.Disabled) return PreviousTrackResult.None; - var currentTrackPosition = current?.Track.CurrentTime; + var currentTrackPosition = drawableTrack?.CurrentTime; if (currentTrackPosition >= restart_cutoff_point) { @@ -274,7 +311,7 @@ namespace osu.Game.Overlays { // if not scheduled, the previously track will be stopped one frame later (see ScheduleAfterChildren logic in GameBase). // we probably want to move this to a central method for switching to a new working beatmap in the future. - Schedule(() => beatmap.Value.Track.Restart()); + Schedule(() => drawableTrack?.Restart()); } private WorkingBeatmap current; @@ -307,6 +344,14 @@ namespace osu.Game.Overlays } current = beatmap.NewValue; + + drawableTrack?.Expire(); + drawableTrack = null; + track = null; + + if (current != null) + trackContainer.Add(drawableTrack = new DrawableTrack(track = current.GetRealTrack())); + TrackChanged?.Invoke(current, direction); ResetTrackAdjustments(); @@ -334,16 +379,15 @@ namespace osu.Game.Overlays public void ResetTrackAdjustments() { - var track = current?.Track; - if (track == null) + if (drawableTrack == null) return; - track.ResetSpeedAdjustments(); + drawableTrack.ResetSpeedAdjustments(); if (allowRateAdjustments) { foreach (var mod in mods.Value.OfType()) - mod.ApplyToTrack(track); + mod.ApplyToTrack(drawableTrack); } } @@ -394,6 +438,133 @@ namespace osu.Game.Overlays { } } + + private class TrackContainer : AudioContainer + { + } + + #region ITrack + + /// + /// The volume of this component. + /// + public BindableNumber Volume => drawableTrack?.Volume; // Todo: Bad + + /// + /// The playback balance of this sample (-1 .. 1 where 0 is centered) + /// + public BindableNumber Balance => drawableTrack?.Balance; // Todo: Bad + + /// + /// Rate at which the component is played back (affects pitch). 1 is 100% playback speed, or default frequency. + /// + public BindableNumber Frequency => drawableTrack?.Frequency; // Todo: Bad + + /// + /// Rate at which the component is played back (does not affect pitch). 1 is 100% playback speed. + /// + public BindableNumber Tempo => drawableTrack?.Tempo; // Todo: Bad + + public IBindable AggregateVolume => drawableTrack?.AggregateVolume; // Todo: Bad + + public IBindable AggregateBalance => drawableTrack?.AggregateBalance; // Todo: Bad + + public IBindable AggregateFrequency => drawableTrack?.AggregateFrequency; // Todo: Bad + + public IBindable AggregateTempo => drawableTrack?.AggregateTempo; // Todo: Bad + + /// + /// Overall playback rate (1 is 100%, -1 is reversed at 100%). + /// + public double Rate => AggregateFrequency.Value * AggregateTempo.Value; + + event Action ITrack.Completed + { + add + { + if (drawableTrack != null) + drawableTrack.Completed += value; + } + remove + { + if (drawableTrack != null) + drawableTrack.Completed -= value; + } + } + + event Action ITrack.Failed + { + add + { + if (drawableTrack != null) + drawableTrack.Failed += value; + } + remove + { + if (drawableTrack != null) + drawableTrack.Failed -= value; + } + } + + public bool Looping + { + get => drawableTrack?.Looping ?? false; + set + { + if (drawableTrack != null) + drawableTrack.Looping = value; + } + } + + public bool IsDummyDevice => drawableTrack?.IsDummyDevice ?? true; + + public double RestartPoint + { + get => drawableTrack?.RestartPoint ?? 0; + set + { + if (drawableTrack != null) + drawableTrack.RestartPoint = value; + } + } + + double ITrack.CurrentTime => CurrentTrackTime; + + double ITrack.Length + { + get => TrackLength; + set + { + if (drawableTrack != null) + drawableTrack.Length = value; + } + } + + public int? Bitrate => drawableTrack?.Bitrate; + + bool ITrack.IsRunning => IsPlaying; + + public bool IsReversed => drawableTrack?.IsReversed ?? false; + + public bool HasCompleted => drawableTrack?.HasCompleted ?? false; + + void ITrack.Reset() => drawableTrack?.Reset(); + + void ITrack.Restart() => Play(true); + + void ITrack.ResetSpeedAdjustments() => ResetTrackAdjustments(); + + bool ITrack.Seek(double seek) + { + SeekTo(seek); + return true; + } + + void ITrack.Start() => Play(); + + public ChannelAmplitudes CurrentAmplitudes => drawableTrack?.CurrentAmplitudes ?? ChannelAmplitudes.Empty; + + #endregion } public enum TrackChangeDirection diff --git a/osu.Game/Overlays/NowPlayingOverlay.cs b/osu.Game/Overlays/NowPlayingOverlay.cs index ebb4a96d14..fde6a52fee 100644 --- a/osu.Game/Overlays/NowPlayingOverlay.cs +++ b/osu.Game/Overlays/NowPlayingOverlay.cs @@ -234,14 +234,12 @@ namespace osu.Game.Overlays pendingBeatmapSwitch = null; } - var track = beatmap.Value?.TrackLoaded ?? false ? beatmap.Value.Track : null; - - if (track?.IsDummyDevice == false) + if (musicController.IsDummyDevice == false) { - progressBar.EndTime = track.Length; - progressBar.CurrentTime = track.CurrentTime; + progressBar.EndTime = musicController.TrackLength; + progressBar.CurrentTime = musicController.CurrentTrackTime; - playButton.Icon = track.IsRunning ? FontAwesome.Regular.PauseCircle : FontAwesome.Regular.PlayCircle; + playButton.Icon = musicController.IsPlaying ? FontAwesome.Regular.PauseCircle : FontAwesome.Regular.PlayCircle; } else { diff --git a/osu.Game/Rulesets/Mods/IApplicableToTrack.cs b/osu.Game/Rulesets/Mods/IApplicableToTrack.cs index 4d6d958e82..9b840cea08 100644 --- a/osu.Game/Rulesets/Mods/IApplicableToTrack.cs +++ b/osu.Game/Rulesets/Mods/IApplicableToTrack.cs @@ -10,6 +10,6 @@ namespace osu.Game.Rulesets.Mods /// public interface IApplicableToTrack : IApplicableMod { - void ApplyToTrack(Track track); + void ApplyToTrack(ITrack track); } } diff --git a/osu.Game/Rulesets/Mods/ModDaycore.cs b/osu.Game/Rulesets/Mods/ModDaycore.cs index bd98e735e5..9cefeb3340 100644 --- a/osu.Game/Rulesets/Mods/ModDaycore.cs +++ b/osu.Game/Rulesets/Mods/ModDaycore.cs @@ -27,11 +27,11 @@ namespace osu.Game.Rulesets.Mods }, true); } - public override void ApplyToTrack(Track track) + public override void ApplyToTrack(ITrack track) { // base.ApplyToTrack() intentionally not called (different tempo adjustment is applied) - track.AddAdjustment(AdjustableProperty.Frequency, freqAdjust); - track.AddAdjustment(AdjustableProperty.Tempo, tempoAdjust); + (track as Track)?.AddAdjustment(AdjustableProperty.Frequency, freqAdjust); + (track as Track)?.AddAdjustment(AdjustableProperty.Tempo, tempoAdjust); } } } diff --git a/osu.Game/Rulesets/Mods/ModNightcore.cs b/osu.Game/Rulesets/Mods/ModNightcore.cs index ed8eb2fb66..b34affa77f 100644 --- a/osu.Game/Rulesets/Mods/ModNightcore.cs +++ b/osu.Game/Rulesets/Mods/ModNightcore.cs @@ -38,11 +38,11 @@ namespace osu.Game.Rulesets.Mods }, true); } - public override void ApplyToTrack(Track track) + public override void ApplyToTrack(ITrack track) { // base.ApplyToTrack() intentionally not called (different tempo adjustment is applied) - track.AddAdjustment(AdjustableProperty.Frequency, freqAdjust); - track.AddAdjustment(AdjustableProperty.Tempo, tempoAdjust); + (track as Track)?.AddAdjustment(AdjustableProperty.Frequency, freqAdjust); + (track as Track)?.AddAdjustment(AdjustableProperty.Tempo, tempoAdjust); } public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) diff --git a/osu.Game/Rulesets/Mods/ModRateAdjust.cs b/osu.Game/Rulesets/Mods/ModRateAdjust.cs index 874384686f..ee1280da39 100644 --- a/osu.Game/Rulesets/Mods/ModRateAdjust.cs +++ b/osu.Game/Rulesets/Mods/ModRateAdjust.cs @@ -12,9 +12,9 @@ namespace osu.Game.Rulesets.Mods { public abstract BindableNumber SpeedChange { get; } - public virtual void ApplyToTrack(Track track) + public virtual void ApplyToTrack(ITrack track) { - track.AddAdjustment(AdjustableProperty.Tempo, SpeedChange); + (track as Track)?.AddAdjustment(AdjustableProperty.Tempo, SpeedChange); } public virtual void ApplyToSample(SampleChannel sample) diff --git a/osu.Game/Rulesets/Mods/ModTimeRamp.cs b/osu.Game/Rulesets/Mods/ModTimeRamp.cs index 839d97f04e..0257e241b8 100644 --- a/osu.Game/Rulesets/Mods/ModTimeRamp.cs +++ b/osu.Game/Rulesets/Mods/ModTimeRamp.cs @@ -51,9 +51,9 @@ namespace osu.Game.Rulesets.Mods AdjustPitch.BindValueChanged(applyPitchAdjustment); } - public void ApplyToTrack(Track track) + public void ApplyToTrack(ITrack track) { - this.track = track; + this.track = track as Track; FinalRate.TriggerChange(); AdjustPitch.TriggerChange(); diff --git a/osu.Game/Screens/Edit/Components/BottomBarContainer.cs b/osu.Game/Screens/Edit/Components/BottomBarContainer.cs index cb5078a479..8d8c59b2ee 100644 --- a/osu.Game/Screens/Edit/Components/BottomBarContainer.cs +++ b/osu.Game/Screens/Edit/Components/BottomBarContainer.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.Track; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -18,7 +17,6 @@ namespace osu.Game.Screens.Edit.Components private const float contents_padding = 15; protected readonly IBindable Beatmap = new Bindable(); - protected Track Track => Beatmap.Value.Track; private readonly Drawable background; private readonly Container content; diff --git a/osu.Game/Screens/Edit/Components/PlaybackControl.cs b/osu.Game/Screens/Edit/Components/PlaybackControl.cs index 59b3d1c565..0a9b4f06a7 100644 --- a/osu.Game/Screens/Edit/Components/PlaybackControl.cs +++ b/osu.Game/Screens/Edit/Components/PlaybackControl.cs @@ -16,6 +16,7 @@ using osu.Framework.Input.Events; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; +using osu.Game.Overlays; using osuTK.Input; namespace osu.Game.Screens.Edit.Components @@ -27,6 +28,9 @@ namespace osu.Game.Screens.Edit.Components [Resolved] private EditorClock editorClock { get; set; } + [Resolved] + private MusicController musicController { get; set; } + private readonly BindableNumber tempo = new BindableDouble(1); [BackgroundDependencyLoader] @@ -62,12 +66,12 @@ namespace osu.Game.Screens.Edit.Components } }; - Track?.AddAdjustment(AdjustableProperty.Tempo, tempo); + musicController.AddAdjustment(AdjustableProperty.Tempo, tempo); } protected override void Dispose(bool isDisposing) { - Track?.RemoveAdjustment(AdjustableProperty.Tempo, tempo); + musicController?.RemoveAdjustment(AdjustableProperty.Tempo, tempo); base.Dispose(isDisposing); } 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 9e9ac93d23..a353f79ef4 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/MarkerPart.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/MarkerPart.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Diagnostics; using osuTK; using osu.Framework.Allocation; using osu.Framework.Graphics; @@ -58,7 +59,9 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts return; float markerPos = Math.Clamp(ToLocalSpace(screenPosition).X, 0, DrawWidth); - editorClock.SeekTo(markerPos / DrawWidth * editorClock.TrackLength); + + Debug.Assert(editorClock.TrackLength != null); + editorClock.SeekTo(markerPos / DrawWidth * editorClock.TrackLength.Value); }); } 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 4a7c3f26bc..446f7fdf88 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/TimelinePart.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/TimelinePart.cs @@ -8,6 +8,7 @@ using osuTK; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps; +using osu.Game.Overlays; namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts { @@ -26,6 +27,9 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts protected override Container Content => content; + [Resolved] + private MusicController musicController { get; set; } + public TimelinePart(Container content = null) { AddInternal(this.content = content ?? new Container { RelativeSizeAxes = Axes.Both }); @@ -46,14 +50,14 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts private void updateRelativeChildSize() { // the track may not be loaded completely (only has a length once it is). - if (!Beatmap.Value.Track.IsLoaded) + if (!musicController.TrackLoaded) { content.RelativeChildSize = Vector2.One; Schedule(updateRelativeChildSize); return; } - content.RelativeChildSize = new Vector2((float)Math.Max(1, Beatmap.Value.Track.Length), 1); + content.RelativeChildSize = new Vector2((float)Math.Max(1, musicController.TrackLength), 1); } protected virtual void LoadBeatmap(WorkingBeatmap beatmap) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs index 717d60b4f3..f35e5defd8 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs @@ -3,7 +3,6 @@ using System; using osu.Framework.Allocation; -using osu.Framework.Audio.Track; using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; @@ -11,6 +10,7 @@ using osu.Framework.Graphics.Audio; using osu.Framework.Input.Events; using osu.Game.Beatmaps; using osu.Game.Graphics; +using osu.Game.Overlays; using osu.Game.Rulesets.Edit; using osuTK; @@ -26,6 +26,9 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline [Resolved] private EditorClock editorClock { get; set; } + [Resolved] + private MusicController musicController { get; set; } + public Timeline() { ZoomDuration = 200; @@ -57,18 +60,21 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline Beatmap.BindValueChanged(b => { waveform.Waveform = b.NewValue.Waveform; - track = b.NewValue.Track; - if (track.Length > 0) + // Todo: Wrong. + Schedule(() => { - MaxZoom = getZoomLevelForVisibleMilliseconds(500); - MinZoom = getZoomLevelForVisibleMilliseconds(10000); - Zoom = getZoomLevelForVisibleMilliseconds(2000); - } + if (musicController.TrackLength > 0) + { + MaxZoom = getZoomLevelForVisibleMilliseconds(500); + MinZoom = getZoomLevelForVisibleMilliseconds(10000); + Zoom = getZoomLevelForVisibleMilliseconds(2000); + } + }); }, true); } - private float getZoomLevelForVisibleMilliseconds(double milliseconds) => (float)(track.Length / milliseconds); + private float getZoomLevelForVisibleMilliseconds(double milliseconds) => (float)(musicController.TrackLength / milliseconds); /// /// The timeline's scroll position in the last frame. @@ -90,8 +96,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline /// private bool trackWasPlaying; - private Track track; - protected override void Update() { base.Update(); @@ -129,18 +133,18 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private void seekTrackToCurrent() { - if (!track.IsLoaded) + if (!musicController.TrackLoaded) return; - editorClock.Seek(Current / Content.DrawWidth * track.Length); + editorClock.Seek(Current / Content.DrawWidth * musicController.TrackLength); } private void scrollToTrackTime() { - if (!track.IsLoaded || track.Length == 0) + if (!musicController.TrackLoaded || musicController.TrackLength == 0) return; - ScrollTo((float)(editorClock.CurrentTime / track.Length) * Content.DrawWidth, false); + ScrollTo((float)(editorClock.CurrentTime / musicController.TrackLength) * Content.DrawWidth, false); } protected override bool OnMouseDown(MouseDownEvent e) @@ -184,7 +188,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline new SnapResult(screenSpacePosition, beatSnapProvider.SnapTime(getTimeFromPosition(Content.ToLocalSpace(screenSpacePosition)))); private double getTimeFromPosition(Vector2 localPosition) => - (localPosition.X / Content.DrawWidth) * track.Length; + (localPosition.X / Content.DrawWidth) * musicController.TrackLength; public float GetBeatSnapDistanceAt(double referenceTime) => throw new NotImplementedException(); diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs index 36ee976bf7..a833b354ed 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs @@ -3,10 +3,9 @@ using System.Linq; using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Game.Beatmaps; using osu.Game.Graphics; +using osu.Game.Overlays; using osu.Game.Screens.Edit.Components.Timelines.Summary.Parts; using osu.Game.Screens.Edit.Components.Timelines.Summary.Visualisations; @@ -18,7 +17,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private EditorBeatmap beatmap { get; set; } [Resolved] - private Bindable working { get; set; } + private MusicController musicController { get; set; } [Resolved] private BindableBeatDivisor beatDivisor { get; set; } @@ -44,7 +43,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline for (var i = 0; i < beatmap.ControlPointInfo.TimingPoints.Count; i++) { var point = beatmap.ControlPointInfo.TimingPoints[i]; - var until = i + 1 < beatmap.ControlPointInfo.TimingPoints.Count ? beatmap.ControlPointInfo.TimingPoints[i + 1].Time : working.Value.Track.Length; + var until = i + 1 < beatmap.ControlPointInfo.TimingPoints.Count ? beatmap.ControlPointInfo.TimingPoints[i + 1].Time : musicController.TrackLength; int beat = 0; diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index d92f3922c3..756b03d049 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -28,6 +28,7 @@ using osu.Framework.Logging; using osu.Game.Beatmaps; using osu.Game.Graphics.Cursor; using osu.Game.Input.Bindings; +using osu.Game.Overlays; using osu.Game.Rulesets.Edit; using osu.Game.Screens.Edit.Compose; using osu.Game.Screens.Edit.Setup; @@ -53,6 +54,9 @@ namespace osu.Game.Screens.Edit [Resolved] private BeatmapManager beatmapManager { get; set; } + [Resolved] + private MusicController musicController { get; set; } + private Box bottomBackground; private Container screenContainer; @@ -79,7 +83,7 @@ namespace osu.Game.Screens.Edit 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(); + var sourceClock = musicController.GetTrackClock() ?? new StopwatchClock(); clock = new EditorClock(Beatmap.Value, beatDivisor) { IsCoupled = false }; clock.ChangeSource(sourceClock); @@ -346,7 +350,7 @@ namespace osu.Game.Screens.Edit private void resetTrack(bool seekToStart = false) { - Beatmap.Value.Track?.Stop(); + musicController.Stop(); if (seekToStart) { diff --git a/osu.Game/Screens/Edit/EditorClock.cs b/osu.Game/Screens/Edit/EditorClock.cs index d4d0feb813..4cb24f90a6 100644 --- a/osu.Game/Screens/Edit/EditorClock.cs +++ b/osu.Game/Screens/Edit/EditorClock.cs @@ -2,13 +2,16 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Diagnostics; using System.Linq; +using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Transforms; using osu.Framework.Utils; using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Overlays; namespace osu.Game.Screens.Edit { @@ -17,7 +20,7 @@ namespace osu.Game.Screens.Edit /// public class EditorClock : Component, IFrameBasedClock, IAdjustableClock, ISourceChangeableClock { - public readonly double TrackLength; + public double? TrackLength { get; private set; } public ControlPointInfo ControlPointInfo; @@ -25,12 +28,15 @@ namespace osu.Game.Screens.Edit private readonly DecoupleableInterpolatingFramedClock underlyingClock; + [Resolved] + private MusicController musicController { get; set; } + public EditorClock(WorkingBeatmap beatmap, BindableBeatDivisor beatDivisor) - : this(beatmap.Beatmap.ControlPointInfo, beatmap.Track.Length, beatDivisor) + : this(beatmap.Beatmap.ControlPointInfo, null, beatDivisor) { } - public EditorClock(ControlPointInfo controlPointInfo, double trackLength, BindableBeatDivisor beatDivisor) + public EditorClock(ControlPointInfo controlPointInfo, double? trackLength, BindableBeatDivisor beatDivisor) { this.beatDivisor = beatDivisor; @@ -45,6 +51,13 @@ namespace osu.Game.Screens.Edit { } + [BackgroundDependencyLoader] + private void load() + { + // Todo: What. + TrackLength ??= musicController.TrackLength; + } + /// /// Seek to the closest snappable beat from a time. /// @@ -135,7 +148,8 @@ namespace osu.Game.Screens.Edit seekTime = timingPoint.Time; // Ensure the sought point is within the boundaries - seekTime = Math.Clamp(seekTime, 0, TrackLength); + Debug.Assert(TrackLength != null); + seekTime = Math.Clamp(seekTime, 0, TrackLength.Value); SeekTo(seekTime); } diff --git a/osu.Game/Screens/Menu/IntroScreen.cs b/osu.Game/Screens/Menu/IntroScreen.cs index 5f91aaad15..7da5df2723 100644 --- a/osu.Game/Screens/Menu/IntroScreen.cs +++ b/osu.Game/Screens/Menu/IntroScreen.cs @@ -12,6 +12,7 @@ using osu.Framework.Screens; using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.IO.Archives; +using osu.Game.Overlays; using osu.Game.Screens.Backgrounds; using osu.Game.Skinning; using osuTK; @@ -43,7 +44,8 @@ namespace osu.Game.Screens.Menu private WorkingBeatmap initialBeatmap; - protected Track Track => initialBeatmap?.Track; + [Resolved] + protected MusicController MusicController { get; private set; } private readonly BindableDouble exitingVolumeFade = new BindableDouble(1); @@ -111,7 +113,7 @@ namespace osu.Game.Screens.Menu if (setInfo != null) { initialBeatmap = beatmaps.GetWorkingBeatmap(setInfo.Beatmaps[0]); - UsingThemedIntro = !(Track is TrackVirtual); + UsingThemedIntro = !MusicController.IsDummyDevice; } return UsingThemedIntro; @@ -150,7 +152,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 Menu. if (UsingThemedIntro) - Track.Restart(); + MusicController.Play(true); } protected override void LogoArriving(OsuLogo logo, bool resuming) diff --git a/osu.Game/Screens/Menu/IntroTriangles.cs b/osu.Game/Screens/Menu/IntroTriangles.cs index a9ef20436f..86a6fa3802 100644 --- a/osu.Game/Screens/Menu/IntroTriangles.cs +++ b/osu.Game/Screens/Menu/IntroTriangles.cs @@ -59,7 +59,7 @@ namespace osu.Game.Screens.Menu LoadComponentAsync(new TrianglesIntroSequence(logo, background) { RelativeSizeAxes = Axes.Both, - Clock = new FramedClock(UsingThemedIntro ? Track : null), + Clock = new FramedClock(UsingThemedIntro ? MusicController.GetTrackClock() : null), LoadMenu = LoadMenu }, t => { diff --git a/osu.Game/Screens/Menu/IntroWelcome.cs b/osu.Game/Screens/Menu/IntroWelcome.cs index bf42e36e8c..62cada577d 100644 --- a/osu.Game/Screens/Menu/IntroWelcome.cs +++ b/osu.Game/Screens/Menu/IntroWelcome.cs @@ -11,6 +11,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; +using osu.Game.Overlays; using osu.Game.Screens.Backgrounds; using osuTK.Graphics; @@ -30,6 +31,9 @@ namespace osu.Game.Screens.Menu Alpha = 0, }; + [Resolved] + private MusicController musicController { get; set; } + private BackgroundScreenDefault background; [BackgroundDependencyLoader] @@ -40,7 +44,7 @@ namespace osu.Game.Screens.Menu pianoReverb = audio.Samples.Get(@"Intro/Welcome/welcome_piano"); - Track.Looping = true; + musicController.Looping = true; } protected override void LogoArriving(OsuLogo logo, bool resuming) diff --git a/osu.Game/Screens/Menu/LogoVisualisation.cs b/osu.Game/Screens/Menu/LogoVisualisation.cs index ebbb19636c..349654165f 100644 --- a/osu.Game/Screens/Menu/LogoVisualisation.cs +++ b/osu.Game/Screens/Menu/LogoVisualisation.cs @@ -20,6 +20,7 @@ using osu.Framework.Audio; using osu.Framework.Audio.Track; using osu.Framework.Bindables; using osu.Framework.Utils; +using osu.Game.Overlays; namespace osu.Game.Screens.Menu { @@ -74,6 +75,9 @@ namespace osu.Game.Screens.Menu /// public float Magnitude { get; set; } = 1; + [Resolved] + private MusicController musicController { get; set; } + private readonly float[] frequencyAmplitudes = new float[256]; private IShader shader; @@ -103,15 +107,15 @@ namespace osu.Game.Screens.Menu private void updateAmplitudes() { - var effect = beatmap.Value.BeatmapLoaded && beatmap.Value.TrackLoaded - ? beatmap.Value.Beatmap?.ControlPointInfo.EffectPointAt(beatmap.Value.Track.CurrentTime) + var effect = beatmap.Value.BeatmapLoaded && musicController.TrackLoaded + ? beatmap.Value.Beatmap?.ControlPointInfo.EffectPointAt(musicController.CurrentTrackTime) : null; for (int i = 0; i < temporalAmplitudes.Length; i++) temporalAmplitudes[i] = 0; - if (beatmap.Value.TrackLoaded) - addAmplitudesFromSource(beatmap.Value.Track); + if (musicController.TrackLoaded) + addAmplitudesFromSource(musicController); foreach (var source in amplitudeSources) addAmplitudesFromSource(source); diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index 57252d557e..c422f57332 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -5,7 +5,6 @@ using System.Linq; using osuTK; using osuTK.Graphics; using osu.Framework.Allocation; -using osu.Framework.Audio.Track; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Platform; @@ -62,8 +61,6 @@ namespace osu.Game.Screens.Menu protected override BackgroundScreen CreateBackground() => background; - internal Track Track { get; private set; } - private Bindable holdDelay; private Bindable loginDisplayed; @@ -172,20 +169,23 @@ namespace osu.Game.Screens.Menu [Resolved] private Storage storage { get; set; } + [Resolved] + private MusicController musicController { get; set; } + public override void OnEntering(IScreen last) { base.OnEntering(last); buttons.FadeInFromZero(500); - Track = Beatmap.Value.Track; var metadata = Beatmap.Value.Metadata; - if (last is IntroScreen && Track != null) + if (last is IntroScreen && musicController.TrackLoaded) { - if (!Track.IsRunning) + // Todo: Wrong. + if (!musicController.IsPlaying) { - Track.Seek(metadata.PreviewTime != -1 ? metadata.PreviewTime : 0.4f * Track.Length); - Track.Start(); + musicController.SeekTo(metadata.PreviewTime != -1 ? metadata.PreviewTime : 0.4f * musicController.TrackLength); + musicController.Play(); } } diff --git a/osu.Game/Screens/Menu/OsuLogo.cs b/osu.Game/Screens/Menu/OsuLogo.cs index f5e4b078da..1feb2481c3 100644 --- a/osu.Game/Screens/Menu/OsuLogo.cs +++ b/osu.Game/Screens/Menu/OsuLogo.cs @@ -17,6 +17,7 @@ using osu.Framework.Utils; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics.Backgrounds; using osu.Game.Graphics.Containers; +using osu.Game.Overlays; using osuTK; using osuTK.Graphics; using osuTK.Input; @@ -46,7 +47,6 @@ namespace osu.Game.Screens.Menu private SampleChannel sampleBeat; private readonly Container colourAndTriangles; - private readonly Triangles triangles; /// @@ -319,6 +319,9 @@ namespace osu.Game.Screens.Menu intro.Delay(length + fade).FadeOut(); } + [Resolved] + private MusicController musicController { get; set; } + protected override void Update() { base.Update(); @@ -327,9 +330,9 @@ namespace osu.Game.Screens.Menu const float velocity_adjust_cutoff = 0.98f; const float paused_velocity = 0.5f; - if (Beatmap.Value.Track.IsRunning) + if (musicController.IsPlaying) { - var maxAmplitude = lastBeatIndex >= 0 ? Beatmap.Value.Track.CurrentAmplitudes.Maximum : 0; + var maxAmplitude = lastBeatIndex >= 0 ? musicController.CurrentAmplitudes.Maximum : 0; logoAmplitudeContainer.Scale = new Vector2((float)Interpolation.Damp(logoAmplitudeContainer.Scale.X, 1 - Math.Max(0, maxAmplitude - scale_adjust_cutoff) * 0.04f, 0.9f, Time.Elapsed)); if (maxAmplitude > velocity_adjust_cutoff) diff --git a/osu.Game/Screens/Multi/Match/Components/ReadyButton.cs b/osu.Game/Screens/Multi/Match/Components/ReadyButton.cs index a64f24dd7e..032c8d5ca9 100644 --- a/osu.Game/Screens/Multi/Match/Components/ReadyButton.cs +++ b/osu.Game/Screens/Multi/Match/Components/ReadyButton.cs @@ -10,6 +10,7 @@ using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; using osu.Game.Online.Multiplayer; +using osu.Game.Overlays; namespace osu.Game.Screens.Multi.Match.Components { @@ -26,6 +27,9 @@ namespace osu.Game.Screens.Multi.Match.Components [Resolved] private BeatmapManager beatmaps { get; set; } + [Resolved] + private MusicController musicController { get; set; } + private bool hasBeatmap; public ReadyButton() @@ -100,7 +104,7 @@ namespace osu.Game.Screens.Multi.Match.Components return; } - bool hasEnoughTime = DateTimeOffset.UtcNow.AddSeconds(30).AddMilliseconds(gameBeatmap.Value.Track.Length) < endDate.Value; + bool hasEnoughTime = DateTimeOffset.UtcNow.AddSeconds(30).AddMilliseconds(musicController.TrackLength) < endDate.Value; Enabled.Value = hasBeatmap && hasEnoughTime; } diff --git a/osu.Game/Screens/Multi/Multiplayer.cs b/osu.Game/Screens/Multi/Multiplayer.cs index 4912df17b1..2d74434c76 100644 --- a/osu.Game/Screens/Multi/Multiplayer.cs +++ b/osu.Game/Screens/Multi/Multiplayer.cs @@ -52,7 +52,7 @@ namespace osu.Game.Screens.Multi private readonly Bindable currentFilter = new Bindable(new FilterCriteria()); [Resolved] - private MusicController music { get; set; } + private MusicController musicController { get; set; } [Cached(Type = typeof(IRoomManager))] private RoomManager roomManager; @@ -343,15 +343,9 @@ namespace osu.Game.Screens.Multi { if (screenStack.CurrentScreen is MatchSubScreen) { - var track = Beatmap.Value?.Track; - - if (track != null) - { - track.RestartPoint = Beatmap.Value.Metadata.PreviewTime; - track.Looping = true; - - music.EnsurePlayingSomething(); - } + musicController.RestartPoint = Beatmap.Value.Metadata.PreviewTime; + musicController.Looping = true; + musicController.EnsurePlayingSomething(); } else { @@ -361,13 +355,8 @@ namespace osu.Game.Screens.Multi private void cancelLooping() { - var track = Beatmap?.Value?.Track; - - if (track != null) - { - track.Looping = false; - track.RestartPoint = 0; - } + musicController.Looping = false; + musicController.RestartPoint = 0; } protected override void Dispose(bool isDisposing) diff --git a/osu.Game/Screens/Play/FailAnimation.cs b/osu.Game/Screens/Play/FailAnimation.cs index 54c644c999..0e0ef8c675 100644 --- a/osu.Game/Screens/Play/FailAnimation.cs +++ b/osu.Game/Screens/Play/FailAnimation.cs @@ -8,10 +8,10 @@ using System; using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Audio.Sample; -using osu.Framework.Audio.Track; using osu.Framework.Graphics; using osu.Framework.Utils; using osu.Game.Beatmaps; +using osu.Game.Overlays; using osu.Game.Rulesets.Objects.Drawables; using osuTK; using osuTK.Graphics; @@ -30,12 +30,13 @@ namespace osu.Game.Screens.Play private readonly BindableDouble trackFreq = new BindableDouble(1); - private Track track; - private const float duration = 2500; private SampleChannel failSample; + [Resolved] + private MusicController musicController { get; set; } + public FailAnimation(DrawableRuleset drawableRuleset) { this.drawableRuleset = drawableRuleset; @@ -44,7 +45,6 @@ namespace osu.Game.Screens.Play [BackgroundDependencyLoader] private void load(AudioManager audio, IBindable beatmap) { - track = beatmap.Value.Track; failSample = audio.Samples.Get(@"Gameplay/failsound"); } @@ -68,7 +68,7 @@ namespace osu.Game.Screens.Play Expire(); }); - track.AddAdjustment(AdjustableProperty.Frequency, trackFreq); + musicController.AddAdjustment(AdjustableProperty.Frequency, trackFreq); applyToPlayfield(drawableRuleset.Playfield); drawableRuleset.Playfield.HitObjectContainer.FlashColour(Color4.Red, 500); @@ -107,7 +107,7 @@ namespace osu.Game.Screens.Play protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); - track?.RemoveAdjustment(AdjustableProperty.Frequency, trackFreq); + musicController?.RemoveAdjustment(AdjustableProperty.Frequency, trackFreq); } } } diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index 0653373c91..cf4678ab29 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -8,13 +8,13 @@ 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; using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Configuration; +using osu.Game.Overlays; using osu.Game.Rulesets.Mods; namespace osu.Game.Screens.Play @@ -27,10 +27,8 @@ namespace osu.Game.Screens.Play private readonly WorkingBeatmap beatmap; private readonly IReadOnlyList mods; - /// - /// The 's track. - /// - private Track track; + [Resolved] + private MusicController musicController { get; set; } public readonly BindableBool IsPaused = new BindableBool(); @@ -72,8 +70,6 @@ namespace osu.Game.Screens.Play RelativeSizeAxes = Axes.Both; - track = beatmap.Track; - adjustableClock = new DecoupleableInterpolatingFramedClock { IsCoupled = false }; // Lazer's audio timings in general doesn't match stable. This is the result of user testing, albeit limited. @@ -125,15 +121,15 @@ namespace osu.Game.Screens.Play { // The Reset() call below causes speed adjustments to be reset in an async context, leading to deadlocks. // The deadlock can be prevented by resetting the track synchronously before entering the async context. - track.ResetSpeedAdjustments(); + musicController.Reset(); Task.Run(() => { - track.Reset(); + musicController.Reset(); Schedule(() => { - adjustableClock.ChangeSource(track); + adjustableClock.ChangeSource(musicController.GetTrackClock()); updateRate(); if (!IsPaused.Value) @@ -194,20 +190,6 @@ 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 (track != beatmap.Track) - return; - - removeSourceClockAdjustments(); - - track = new TrackVirtual(beatmap.Track.Length); - adjustableClock.ChangeSource(track); - } - protected override void Update() { if (!IsPaused.Value) @@ -220,31 +202,23 @@ namespace osu.Game.Screens.Play private void updateRate() { - if (track == null) return; - speedAdjustmentsApplied = true; - track.ResetSpeedAdjustments(); - - track.AddAdjustment(AdjustableProperty.Frequency, pauseFreqAdjust); - track.AddAdjustment(AdjustableProperty.Tempo, UserPlaybackRate); - - foreach (var mod in mods.OfType()) - mod.ApplyToTrack(track); + musicController.ResetTrackAdjustments(); + musicController.AddAdjustment(AdjustableProperty.Frequency, pauseFreqAdjust); + musicController.AddAdjustment(AdjustableProperty.Tempo, UserPlaybackRate); } protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); - removeSourceClockAdjustments(); - track = null; } private void removeSourceClockAdjustments() { if (speedAdjustmentsApplied) { - track.ResetSpeedAdjustments(); + musicController.ResetTrackAdjustments(); speedAdjustmentsApplied = false; } } diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 74a5ee8309..9ba2732920 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -4,7 +4,6 @@ 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.Graphics.Containers; @@ -561,8 +560,11 @@ namespace osu.Game.Screens.Select BeatmapDetails.Refresh(); - Beatmap.Value.Track.Looping = true; - music?.ResetTrackAdjustments(); + if (music != null) + { + music.Looping = true; + music.ResetTrackAdjustments(); + } if (Beatmap != null && !Beatmap.Value.BeatmapSetInfo.DeletePending) { @@ -586,8 +588,8 @@ namespace osu.Game.Screens.Select BeatmapOptions.Hide(); - if (Beatmap.Value.Track != null) - Beatmap.Value.Track.Looping = false; + if (music != null) + music.Looping = false; this.ScaleTo(1.1f, 250, Easing.InSine); @@ -608,8 +610,8 @@ namespace osu.Game.Screens.Select FilterControl.Deactivate(); - if (Beatmap.Value.Track != null) - Beatmap.Value.Track.Looping = false; + if (music != null) + music.Looping = false; return false; } @@ -650,28 +652,18 @@ namespace osu.Game.Screens.Select BeatmapDetails.Beatmap = beatmap; - if (beatmap.Track != null) - beatmap.Track.Looping = true; + if (music != null) + music.Looping = false; } - 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 && (music?.IsUserPaused != true || isNewTrack)) - music?.Play(true); - - lastTrack.SetTarget(track); + music.RestartPoint = Beatmap.Value.Metadata.PreviewTime; + music.EnsurePlayingSomething(); } private void carouselBeatmapsLoaded() diff --git a/osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs b/osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs index cdf9170701..ee04142035 100644 --- a/osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs +++ b/osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs @@ -27,8 +27,6 @@ namespace osu.Game.Tests.Beatmaps this.storyboard = storyboard; } - public override bool TrackLoaded => true; - public override bool BeatmapLoaded => true; protected override IBeatmap GetBeatmap() => beatmap; diff --git a/osu.Game/Tests/Visual/EditorClockTestScene.cs b/osu.Game/Tests/Visual/EditorClockTestScene.cs index f0ec638fc9..5226a49def 100644 --- a/osu.Game/Tests/Visual/EditorClockTestScene.cs +++ b/osu.Game/Tests/Visual/EditorClockTestScene.cs @@ -44,7 +44,7 @@ namespace osu.Game.Tests.Visual private void beatmapChanged(ValueChangedEvent e) { Clock.ControlPointInfo = e.NewValue.Beatmap.ControlPointInfo; - Clock.ChangeSource((IAdjustableClock)e.NewValue.Track ?? new StopwatchClock()); + Clock.ChangeSource(MusicController.GetTrackClock() ?? new StopwatchClock()); Clock.ProcessFrame(); } diff --git a/osu.Game/Tests/Visual/OsuTestScene.cs b/osu.Game/Tests/Visual/OsuTestScene.cs index 866fc215d6..e968f7e675 100644 --- a/osu.Game/Tests/Visual/OsuTestScene.cs +++ b/osu.Game/Tests/Visual/OsuTestScene.cs @@ -20,6 +20,7 @@ using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Database; using osu.Game.Online.API; +using osu.Game.Overlays; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.UI; @@ -135,6 +136,9 @@ namespace osu.Game.Tests.Visual [Resolved] protected AudioManager Audio { get; private set; } + [Resolved] + protected MusicController MusicController { get; private set; } + /// /// Creates the ruleset to be used for this test scene. /// @@ -164,8 +168,8 @@ namespace osu.Game.Tests.Visual rulesetDependencies?.Dispose(); - if (Beatmap?.Value.TrackLoaded == true) - Beatmap.Value.Track.Stop(); + if (MusicController?.TrackLoaded == true) + MusicController.Stop(); if (contextFactory.IsValueCreated) contextFactory.Value.ResetDatabase(); diff --git a/osu.Game/Tests/Visual/RateAdjustedBeatmapTestScene.cs b/osu.Game/Tests/Visual/RateAdjustedBeatmapTestScene.cs index ad24ffc7b8..e7cb461d7b 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.Tempo.Value = Clock.Rate; + MusicController.Tempo.Value = Clock.Rate; } } } From 5c05fe3988ad5ba9c92e908628f2d11def1c9376 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 5 Aug 2020 21:10:38 +0900 Subject: [PATCH 0406/1782] Expose track from MusicController --- .../TestSceneHoldNoteInput.cs | 2 +- .../TestSceneOutOfOrderHits.cs | 2 +- .../TestSceneSliderInput.cs | 2 +- .../Skins/TestSceneBeatmapSkinResources.cs | 2 +- .../Visual/Editing/TimelineTestScene.cs | 6 +- .../Visual/Gameplay/TestScenePause.cs | 2 +- .../Visual/Gameplay/TestScenePlayerLoader.cs | 6 +- .../Visual/Gameplay/TestSceneStoryboard.cs | 8 +- .../Visual/Menus/TestSceneIntroWelcome.cs | 2 +- .../Navigation/TestSceneScreenNavigation.cs | 13 +- .../TestSceneBeatSyncedContainer.cs | 3 +- .../TestSceneNowPlayingOverlay.cs | 4 +- .../Containers/BeatSyncedContainer.cs | 14 +- osu.Game/Overlays/MusicController.cs | 190 ++---------------- osu.Game/Overlays/NowPlayingOverlay.cs | 12 +- osu.Game/Rulesets/Mods/IApplicableToTrack.cs | 3 +- osu.Game/Rulesets/Mods/ModDaycore.cs | 7 +- osu.Game/Rulesets/Mods/ModNightcore.cs | 6 +- osu.Game/Rulesets/Mods/ModRateAdjust.cs | 4 +- osu.Game/Rulesets/Mods/ModTimeRamp.cs | 10 +- .../Edit/Components/PlaybackControl.cs | 4 +- .../Timelines/Summary/Parts/TimelinePart.cs | 4 +- .../Compose/Components/Timeline/Timeline.cs | 31 +-- .../Timeline/TimelineTickDisplay.cs | 3 +- osu.Game/Screens/Edit/Editor.cs | 2 +- osu.Game/Screens/Edit/EditorClock.cs | 2 +- osu.Game/Screens/Menu/IntroScreen.cs | 4 +- osu.Game/Screens/Menu/IntroTriangles.cs | 2 +- osu.Game/Screens/Menu/IntroWelcome.cs | 3 +- osu.Game/Screens/Menu/LogoVisualisation.cs | 5 +- osu.Game/Screens/Menu/MainMenu.cs | 10 +- osu.Game/Screens/Menu/OsuLogo.cs | 4 +- .../Multi/Match/Components/ReadyButton.cs | 2 +- osu.Game/Screens/Multi/Multiplayer.cs | 17 +- osu.Game/Screens/Play/FailAnimation.cs | 13 +- .../Screens/Play/GameplayClockContainer.cs | 44 +++- osu.Game/Screens/Select/SongSelect.cs | 33 ++- osu.Game/Tests/Visual/EditorClockTestScene.cs | 2 +- .../Visual/RateAdjustedBeatmapTestScene.cs | 4 +- 39 files changed, 204 insertions(+), 283 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs index c3e0c277a0..98669efb10 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs @@ -343,7 +343,7 @@ namespace osu.Game.Rulesets.Mania.Tests judgementResults = new List(); }); - AddUntilStep("Beatmap at 0", () => MusicController.CurrentTrackTime == 0); + AddUntilStep("Beatmap at 0", () => MusicController.CurrentTrack?.CurrentTime == 0); AddUntilStep("Wait until player is loaded", () => currentPlayer.IsCurrentScreen()); AddUntilStep("Wait for completion", () => currentPlayer.ScoreProcessor.HasCompleted.Value); } diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneOutOfOrderHits.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneOutOfOrderHits.cs index dc7e59b40d..e5be778527 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneOutOfOrderHits.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneOutOfOrderHits.cs @@ -385,7 +385,7 @@ namespace osu.Game.Rulesets.Osu.Tests judgementResults = new List(); }); - AddUntilStep("Beatmap at 0", () => MusicController.CurrentTrackTime == 0); + AddUntilStep("Beatmap at 0", () => MusicController.CurrentTrack?.CurrentTime == 0); AddUntilStep("Wait until player is loaded", () => currentPlayer.IsCurrentScreen()); AddUntilStep("Wait for completion", () => currentPlayer.ScoreProcessor.HasCompleted.Value); } diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs index 2dffcfeabb..c9d13d3976 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs @@ -366,7 +366,7 @@ namespace osu.Game.Rulesets.Osu.Tests judgementResults = new List(); }); - AddUntilStep("Beatmap at 0", () => MusicController.CurrentTrackTime == 0); + AddUntilStep("Beatmap at 0", () => MusicController.CurrentTrack?.CurrentTime == 0); AddUntilStep("Wait until player is loaded", () => currentPlayer.IsCurrentScreen()); AddUntilStep("Wait for completion", () => currentPlayer.ScoreProcessor.HasCompleted.Value); } diff --git a/osu.Game.Tests/Skins/TestSceneBeatmapSkinResources.cs b/osu.Game.Tests/Skins/TestSceneBeatmapSkinResources.cs index 1a19326ac4..08a4e27ff7 100644 --- a/osu.Game.Tests/Skins/TestSceneBeatmapSkinResources.cs +++ b/osu.Game.Tests/Skins/TestSceneBeatmapSkinResources.cs @@ -31,6 +31,6 @@ namespace osu.Game.Tests.Skins public void TestRetrieveOggSample() => AddAssert("sample is non-null", () => beatmap.Skin.GetSample(new SampleInfo("sample")) != null); [Test] - public void TestRetrieveOggTrack() => AddAssert("track is non-null", () => !MusicController.IsDummyDevice); + public void TestRetrieveOggTrack() => AddAssert("track is non-null", () => MusicController.CurrentTrack?.IsDummyDevice == false); } } diff --git a/osu.Game.Tests/Visual/Editing/TimelineTestScene.cs b/osu.Game.Tests/Visual/Editing/TimelineTestScene.cs index 5d6136d9fb..4113bdddf8 100644 --- a/osu.Game.Tests/Visual/Editing/TimelineTestScene.cs +++ b/osu.Game.Tests/Visual/Editing/TimelineTestScene.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.Allocation; using osu.Framework.Audio; using osu.Framework.Graphics; @@ -94,7 +95,10 @@ namespace osu.Game.Tests.Visual.Editing base.Update(); if (musicController.TrackLoaded) - marker.X = (float)(editorClock.CurrentTime / musicController.TrackLength); + { + Debug.Assert(musicController.CurrentTrack != null); + marker.X = (float)(editorClock.CurrentTime / musicController.CurrentTrack.Length); + } } } diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs index 8f14de1578..e500b451f0 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs @@ -288,7 +288,7 @@ namespace osu.Game.Tests.Visual.Gameplay private void confirmNoTrackAdjustments() { - AddAssert("track has no adjustments", () => MusicController.AggregateFrequency.Value == 1); + AddAssert("track has no adjustments", () => MusicController.CurrentTrack?.AggregateFrequency.Value == 1); } private void restart() => AddStep("restart", () => Player.Restart()); diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs index 2f86db1b25..c72ab7d3d1 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 Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo); foreach (var mod in SelectedMods.Value.OfType()) - mod.ApplyToTrack(MusicController); + mod.ApplyToTrack(MusicController.CurrentTrack); InputManager.Child = container = new TestPlayerLoaderContainer( loader = new TestPlayerLoader(() => @@ -77,12 +77,12 @@ namespace osu.Game.Tests.Visual.Gameplay { AddStep("load dummy beatmap", () => ResetPlayer(false, () => SelectedMods.Value = new[] { new OsuModNightcore() })); AddUntilStep("wait for current", () => loader.IsCurrentScreen()); - AddAssert("mod rate applied", () => MusicController.Rate != 1); + AddAssert("mod rate applied", () => MusicController.CurrentTrack?.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", () => MusicController.Rate != 1); + AddAssert("mod rate still applied", () => MusicController.CurrentTrack?.Rate != 1); } [Test] diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboard.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboard.cs index a4b558fce2..c7a012a03f 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboard.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboard.cs @@ -87,9 +87,9 @@ namespace osu.Game.Tests.Visual.Gameplay private void restart() { - MusicController.Reset(); + MusicController.CurrentTrack?.Reset(); loadStoryboard(Beatmap.Value); - MusicController.Play(true); + MusicController.CurrentTrack?.Start(); } private void loadStoryboard(WorkingBeatmap working) @@ -104,7 +104,7 @@ namespace osu.Game.Tests.Visual.Gameplay storyboard.Passing = false; storyboardContainer.Add(storyboard); - decoupledClock.ChangeSource(musicController.GetTrackClock()); + decoupledClock.ChangeSource(musicController.CurrentTrack); } private void loadStoryboardNoVideo() @@ -127,7 +127,7 @@ namespace osu.Game.Tests.Visual.Gameplay storyboard = sb.CreateDrawable(Beatmap.Value); storyboardContainer.Add(storyboard); - decoupledClock.ChangeSource(musicController.GetTrackClock()); + decoupledClock.ChangeSource(musicController.CurrentTrack); } } } diff --git a/osu.Game.Tests/Visual/Menus/TestSceneIntroWelcome.cs b/osu.Game.Tests/Visual/Menus/TestSceneIntroWelcome.cs index 36f98b7a0c..a88704c831 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneIntroWelcome.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneIntroWelcome.cs @@ -16,7 +16,7 @@ namespace osu.Game.Tests.Visual.Menus { AddUntilStep("wait for load", () => MusicController.TrackLoaded); - AddAssert("check if menu music loops", () => MusicController.Looping); + AddAssert("check if menu music loops", () => MusicController.CurrentTrack?.Looping == true); } } } diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs index 455b7e56e6..946bc2a175 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs @@ -4,6 +4,7 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics.Containers; using osu.Framework.Screens; using osu.Game.Beatmaps; @@ -61,12 +62,12 @@ namespace osu.Game.Tests.Visual.Navigation AddUntilStep("wait for fail", () => player.HasFailed); AddUntilStep("wait for track stop", () => !MusicController.IsPlaying); - AddAssert("Ensure time before preview point", () => MusicController.CurrentTrackTime < beatmap().Metadata.PreviewTime); + AddAssert("Ensure time before preview point", () => MusicController.CurrentTrack?.CurrentTime < beatmap().Metadata.PreviewTime); pushEscape(); AddUntilStep("wait for track playing", () => MusicController.IsPlaying); - AddAssert("Ensure time wasn't reset to preview point", () => MusicController.CurrentTrackTime < beatmap().Metadata.PreviewTime); + AddAssert("Ensure time wasn't reset to preview point", () => MusicController.CurrentTrack?.CurrentTime < beatmap().Metadata.PreviewTime); } [Test] @@ -76,11 +77,11 @@ namespace osu.Game.Tests.Visual.Navigation PushAndConfirm(() => songSelect = new TestSongSelect()); - AddUntilStep("wait for no track", () => MusicController.IsDummyDevice); + AddUntilStep("wait for no track", () => MusicController.CurrentTrack?.IsDummyDevice == true); AddStep("return to menu", () => songSelect.Exit()); - AddUntilStep("wait for track", () => !MusicController.IsDummyDevice && MusicController.IsPlaying); + AddUntilStep("wait for track", () => MusicController.CurrentTrack?.IsDummyDevice == false && MusicController.IsPlaying); } [Test] @@ -135,8 +136,8 @@ namespace osu.Game.Tests.Visual.Navigation AddUntilStep("Wait for music controller", () => Game.MusicController.IsLoaded); AddStep("Seek close to end", () => { - Game.MusicController.SeekTo(MusicController.TrackLength - 1000); - // MusicController.Completed += () => trackCompleted = true; + Game.MusicController.SeekTo(MusicController.CurrentTrack.AsNonNull().Length - 1000); + MusicController.CurrentTrack.AsNonNull().Completed += () => trackCompleted = true; }); AddUntilStep("Track was completed", () => trackCompleted); diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs index f3fef8c355..ac743d76df 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs @@ -8,6 +8,7 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio.Track; using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -168,7 +169,7 @@ namespace osu.Game.Tests.Visual.UserInterface if (timingPoints.Count == 0) return 0; if (timingPoints[^1] == current) - return (int)Math.Ceiling((musicController.TrackLength - current.Time) / current.BeatLength); + return (int)Math.Ceiling((musicController.CurrentTrack.AsNonNull().Length - current.Time) / current.BeatLength); return (int)Math.Ceiling((getNextTimingPoint(current).Time - current.Time) / current.BeatLength); } diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneNowPlayingOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneNowPlayingOverlay.cs index 3ecd8ab550..0161ec0c56 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneNowPlayingOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneNowPlayingOverlay.cs @@ -80,12 +80,12 @@ namespace osu.Game.Tests.Visual.UserInterface AddStep("Store track", () => currentBeatmap = Beatmap.Value); AddStep(@"Seek track to 6 second", () => musicController.SeekTo(6000)); - AddUntilStep(@"Wait for current time to update", () => musicController.CurrentTrackTime > 5000); + AddUntilStep(@"Wait for current time to update", () => musicController.CurrentTrack?.CurrentTime > 5000); AddStep(@"Set previous", () => musicController.PreviousTrack()); AddAssert(@"Check beatmap didn't change", () => currentBeatmap == Beatmap.Value); - AddUntilStep("Wait for current time to update", () => musicController.CurrentTrackTime < 5000); + AddUntilStep("Wait for current time to update", () => musicController.CurrentTrack?.CurrentTime < 5000); AddStep(@"Set previous", () => musicController.PreviousTrack()); AddAssert(@"Check beatmap did change", () => currentBeatmap != Beatmap.Value); diff --git a/osu.Game/Graphics/Containers/BeatSyncedContainer.cs b/osu.Game/Graphics/Containers/BeatSyncedContainer.cs index 2dd28a01dc..92a9ed0566 100644 --- a/osu.Game/Graphics/Containers/BeatSyncedContainer.cs +++ b/osu.Game/Graphics/Containers/BeatSyncedContainer.cs @@ -51,6 +51,7 @@ namespace osu.Game.Graphics.Containers protected override void Update() { + ITrack track = null; IBeatmap beatmap = null; double currentTrackTime = 0; @@ -58,11 +59,14 @@ namespace osu.Game.Graphics.Containers EffectControlPoint effectPoint = null; if (musicController.TrackLoaded && Beatmap.Value.BeatmapLoaded) - beatmap = Beatmap.Value.Beatmap; - - if (beatmap != null && musicController.IsPlaying && musicController.TrackLength > 0) { - currentTrackTime = musicController.CurrentTrackTime + EarlyActivationMilliseconds; + track = musicController.CurrentTrack; + beatmap = Beatmap.Value.Beatmap; + } + + if (track != null && beatmap != null && musicController.IsPlaying && track.Length > 0) + { + currentTrackTime = track.CurrentTime + EarlyActivationMilliseconds; timingPoint = beatmap.ControlPointInfo.TimingPointAt(currentTrackTime); effectPoint = beatmap.ControlPointInfo.EffectPointAt(currentTrackTime); @@ -98,7 +102,7 @@ namespace osu.Game.Graphics.Containers return; using (BeginDelayedSequence(-TimeSinceLastBeat, true)) - OnNewBeat(beatIndex, timingPoint, effectPoint, musicController.CurrentAmplitudes); + OnNewBeat(beatIndex, timingPoint, effectPoint, track?.CurrentAmplitudes ?? ChannelAmplitudes.Empty); lastBeat = beatIndex; lastTimingPoint = timingPoint; diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index f5ca5a3a49..c22849b7d6 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -6,7 +6,6 @@ using System.Collections.Generic; using System.Linq; using JetBrains.Annotations; using osu.Framework.Allocation; -using osu.Framework.Audio; using osu.Framework.Audio.Track; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -15,7 +14,6 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Input.Bindings; using osu.Framework.Utils; using osu.Framework.Threading; -using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Input.Bindings; using osu.Game.Overlays.OSD; @@ -26,7 +24,7 @@ namespace osu.Game.Overlays /// /// Handles playback of the global music track. /// - public class MusicController : CompositeDrawable, IKeyBindingHandler, ITrack + public class MusicController : CompositeDrawable, IKeyBindingHandler { [Resolved] private BeatmapManager beatmaps { get; set; } @@ -70,10 +68,7 @@ namespace osu.Game.Overlays private readonly TrackContainer trackContainer; [CanBeNull] - private DrawableTrack drawableTrack; - - [CanBeNull] - private Track track; + public DrawableTrack CurrentTrack { get; private set; } private IBindable> managerUpdated; private IBindable> managerRemoved; @@ -116,33 +111,12 @@ namespace osu.Game.Overlays /// /// Returns whether the beatmap track is playing. /// - public bool IsPlaying => drawableTrack?.IsRunning ?? false; + public bool IsPlaying => CurrentTrack?.IsRunning ?? false; /// /// Returns whether the beatmap track is loaded. /// - public bool TrackLoaded => drawableTrack?.IsLoaded == true; - - /// - /// Returns the current time of the beatmap track. - /// - public double CurrentTrackTime => drawableTrack?.CurrentTime ?? 0; - - /// - /// Returns the length of the beatmap track. - /// - public double TrackLength => drawableTrack?.Length ?? 0; - - public void AddAdjustment(AdjustableProperty type, BindableNumber adjustBindable) - => trackContainer.AddAdjustment(type, adjustBindable); - - public void RemoveAdjustment(AdjustableProperty type, BindableNumber adjustBindable) - => trackContainer.RemoveAdjustment(type, adjustBindable); - - public void Reset() => drawableTrack?.Reset(); - - [CanBeNull] - public IAdjustableClock GetTrackClock() => track; + public bool TrackLoaded => CurrentTrack?.IsLoaded == true; private void beatmapUpdated(ValueChangedEvent> weakSet) { @@ -175,7 +149,7 @@ namespace osu.Game.Overlays seekDelegate = Schedule(() => { if (!beatmap.Disabled) - drawableTrack?.Seek(position); + CurrentTrack?.Seek(position); }); } @@ -187,7 +161,7 @@ namespace osu.Game.Overlays { if (IsUserPaused) return; - if (drawableTrack == null || drawableTrack.IsDummyDevice) + if (CurrentTrack == null || CurrentTrack.IsDummyDevice) { if (beatmap.Disabled) return; @@ -208,13 +182,13 @@ namespace osu.Game.Overlays { IsUserPaused = false; - if (drawableTrack == null) + if (CurrentTrack == null) return false; if (restart) - drawableTrack.Restart(); + CurrentTrack.Restart(); else if (!IsPlaying) - drawableTrack.Start(); + CurrentTrack.Start(); return true; } @@ -225,8 +199,8 @@ namespace osu.Game.Overlays public void Stop() { IsUserPaused = true; - if (drawableTrack?.IsRunning == true) - drawableTrack.Stop(); + if (CurrentTrack?.IsRunning == true) + CurrentTrack.Stop(); } /// @@ -235,7 +209,7 @@ namespace osu.Game.Overlays /// Whether the operation was successful. public bool TogglePause() { - if (drawableTrack?.IsRunning == true) + if (CurrentTrack?.IsRunning == true) Stop(); else Play(); @@ -257,7 +231,7 @@ namespace osu.Game.Overlays if (beatmap.Disabled) return PreviousTrackResult.None; - var currentTrackPosition = drawableTrack?.CurrentTime; + var currentTrackPosition = CurrentTrack?.CurrentTime; if (currentTrackPosition >= restart_cutoff_point) { @@ -311,7 +285,7 @@ namespace osu.Game.Overlays { // if not scheduled, the previously track will be stopped one frame later (see ScheduleAfterChildren logic in GameBase). // we probably want to move this to a central method for switching to a new working beatmap in the future. - Schedule(() => drawableTrack?.Restart()); + Schedule(() => CurrentTrack?.Restart()); } private WorkingBeatmap current; @@ -345,12 +319,11 @@ namespace osu.Game.Overlays current = beatmap.NewValue; - drawableTrack?.Expire(); - drawableTrack = null; - track = null; + CurrentTrack?.Expire(); + CurrentTrack = null; if (current != null) - trackContainer.Add(drawableTrack = new DrawableTrack(track = current.GetRealTrack())); + trackContainer.Add(CurrentTrack = new DrawableTrack(current.GetRealTrack())); TrackChanged?.Invoke(current, direction); @@ -379,15 +352,15 @@ namespace osu.Game.Overlays public void ResetTrackAdjustments() { - if (drawableTrack == null) + if (CurrentTrack == null) return; - drawableTrack.ResetSpeedAdjustments(); + CurrentTrack.ResetSpeedAdjustments(); if (allowRateAdjustments) { foreach (var mod in mods.Value.OfType()) - mod.ApplyToTrack(drawableTrack); + mod.ApplyToTrack(CurrentTrack); } } @@ -442,129 +415,6 @@ namespace osu.Game.Overlays private class TrackContainer : AudioContainer { } - - #region ITrack - - /// - /// The volume of this component. - /// - public BindableNumber Volume => drawableTrack?.Volume; // Todo: Bad - - /// - /// The playback balance of this sample (-1 .. 1 where 0 is centered) - /// - public BindableNumber Balance => drawableTrack?.Balance; // Todo: Bad - - /// - /// Rate at which the component is played back (affects pitch). 1 is 100% playback speed, or default frequency. - /// - public BindableNumber Frequency => drawableTrack?.Frequency; // Todo: Bad - - /// - /// Rate at which the component is played back (does not affect pitch). 1 is 100% playback speed. - /// - public BindableNumber Tempo => drawableTrack?.Tempo; // Todo: Bad - - public IBindable AggregateVolume => drawableTrack?.AggregateVolume; // Todo: Bad - - public IBindable AggregateBalance => drawableTrack?.AggregateBalance; // Todo: Bad - - public IBindable AggregateFrequency => drawableTrack?.AggregateFrequency; // Todo: Bad - - public IBindable AggregateTempo => drawableTrack?.AggregateTempo; // Todo: Bad - - /// - /// Overall playback rate (1 is 100%, -1 is reversed at 100%). - /// - public double Rate => AggregateFrequency.Value * AggregateTempo.Value; - - event Action ITrack.Completed - { - add - { - if (drawableTrack != null) - drawableTrack.Completed += value; - } - remove - { - if (drawableTrack != null) - drawableTrack.Completed -= value; - } - } - - event Action ITrack.Failed - { - add - { - if (drawableTrack != null) - drawableTrack.Failed += value; - } - remove - { - if (drawableTrack != null) - drawableTrack.Failed -= value; - } - } - - public bool Looping - { - get => drawableTrack?.Looping ?? false; - set - { - if (drawableTrack != null) - drawableTrack.Looping = value; - } - } - - public bool IsDummyDevice => drawableTrack?.IsDummyDevice ?? true; - - public double RestartPoint - { - get => drawableTrack?.RestartPoint ?? 0; - set - { - if (drawableTrack != null) - drawableTrack.RestartPoint = value; - } - } - - double ITrack.CurrentTime => CurrentTrackTime; - - double ITrack.Length - { - get => TrackLength; - set - { - if (drawableTrack != null) - drawableTrack.Length = value; - } - } - - public int? Bitrate => drawableTrack?.Bitrate; - - bool ITrack.IsRunning => IsPlaying; - - public bool IsReversed => drawableTrack?.IsReversed ?? false; - - public bool HasCompleted => drawableTrack?.HasCompleted ?? false; - - void ITrack.Reset() => drawableTrack?.Reset(); - - void ITrack.Restart() => Play(true); - - void ITrack.ResetSpeedAdjustments() => ResetTrackAdjustments(); - - bool ITrack.Seek(double seek) - { - SeekTo(seek); - return true; - } - - void ITrack.Start() => Play(); - - public ChannelAmplitudes CurrentAmplitudes => drawableTrack?.CurrentAmplitudes ?? ChannelAmplitudes.Empty; - - #endregion } public enum TrackChangeDirection diff --git a/osu.Game/Overlays/NowPlayingOverlay.cs b/osu.Game/Overlays/NowPlayingOverlay.cs index fde6a52fee..15b189ead6 100644 --- a/osu.Game/Overlays/NowPlayingOverlay.cs +++ b/osu.Game/Overlays/NowPlayingOverlay.cs @@ -234,12 +234,14 @@ namespace osu.Game.Overlays pendingBeatmapSwitch = null; } - if (musicController.IsDummyDevice == false) - { - progressBar.EndTime = musicController.TrackLength; - progressBar.CurrentTime = musicController.CurrentTrackTime; + var track = musicController.TrackLoaded ? musicController.CurrentTrack : null; - playButton.Icon = musicController.IsPlaying ? FontAwesome.Regular.PauseCircle : FontAwesome.Regular.PlayCircle; + if (track?.IsDummyDevice == false) + { + progressBar.EndTime = track.Length; + progressBar.CurrentTime = track.CurrentTime; + + playButton.Icon = track.IsRunning ? FontAwesome.Regular.PauseCircle : FontAwesome.Regular.PlayCircle; } else { diff --git a/osu.Game/Rulesets/Mods/IApplicableToTrack.cs b/osu.Game/Rulesets/Mods/IApplicableToTrack.cs index 9b840cea08..5ae41fe09c 100644 --- a/osu.Game/Rulesets/Mods/IApplicableToTrack.cs +++ b/osu.Game/Rulesets/Mods/IApplicableToTrack.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; namespace osu.Game.Rulesets.Mods @@ -10,6 +11,6 @@ namespace osu.Game.Rulesets.Mods /// public interface IApplicableToTrack : IApplicableMod { - void ApplyToTrack(ITrack track); + void ApplyToTrack(T track) where T : ITrack, IAdjustableAudioComponent; } } diff --git a/osu.Game/Rulesets/Mods/ModDaycore.cs b/osu.Game/Rulesets/Mods/ModDaycore.cs index 9cefeb3340..989978eb35 100644 --- a/osu.Game/Rulesets/Mods/ModDaycore.cs +++ b/osu.Game/Rulesets/Mods/ModDaycore.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Audio; -using osu.Framework.Audio.Track; using osu.Framework.Bindables; using osu.Framework.Graphics.Sprites; @@ -27,11 +26,11 @@ namespace osu.Game.Rulesets.Mods }, true); } - public override void ApplyToTrack(ITrack track) + public override void ApplyToTrack(T track) { // base.ApplyToTrack() intentionally not called (different tempo adjustment is applied) - (track as Track)?.AddAdjustment(AdjustableProperty.Frequency, freqAdjust); - (track as Track)?.AddAdjustment(AdjustableProperty.Tempo, tempoAdjust); + 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 b34affa77f..70cdaa6345 100644 --- a/osu.Game/Rulesets/Mods/ModNightcore.cs +++ b/osu.Game/Rulesets/Mods/ModNightcore.cs @@ -38,11 +38,11 @@ namespace osu.Game.Rulesets.Mods }, true); } - public override void ApplyToTrack(ITrack track) + public override void ApplyToTrack(T track) { // base.ApplyToTrack() intentionally not called (different tempo adjustment is applied) - (track as Track)?.AddAdjustment(AdjustableProperty.Frequency, freqAdjust); - (track as Track)?.AddAdjustment(AdjustableProperty.Tempo, tempoAdjust); + track.AddAdjustment(AdjustableProperty.Frequency, freqAdjust); + track.AddAdjustment(AdjustableProperty.Tempo, tempoAdjust); } public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) diff --git a/osu.Game/Rulesets/Mods/ModRateAdjust.cs b/osu.Game/Rulesets/Mods/ModRateAdjust.cs index ee1280da39..e2c8ac64d9 100644 --- a/osu.Game/Rulesets/Mods/ModRateAdjust.cs +++ b/osu.Game/Rulesets/Mods/ModRateAdjust.cs @@ -12,9 +12,9 @@ namespace osu.Game.Rulesets.Mods { public abstract BindableNumber SpeedChange { get; } - public virtual void ApplyToTrack(ITrack track) + public virtual void ApplyToTrack(T track) where T : ITrack, IAdjustableAudioComponent { - (track as Track)?.AddAdjustment(AdjustableProperty.Tempo, SpeedChange); + track.AddAdjustment(AdjustableProperty.Tempo, SpeedChange); } public virtual void ApplyToSample(SampleChannel sample) diff --git a/osu.Game/Rulesets/Mods/ModTimeRamp.cs b/osu.Game/Rulesets/Mods/ModTimeRamp.cs index 0257e241b8..b6cbe72971 100644 --- a/osu.Game/Rulesets/Mods/ModTimeRamp.cs +++ b/osu.Game/Rulesets/Mods/ModTimeRamp.cs @@ -42,7 +42,7 @@ namespace osu.Game.Rulesets.Mods Precision = 0.01, }; - private Track track; + private ITrack track; protected ModTimeRamp() { @@ -51,9 +51,9 @@ namespace osu.Game.Rulesets.Mods AdjustPitch.BindValueChanged(applyPitchAdjustment); } - public void ApplyToTrack(ITrack track) + public void ApplyToTrack(T track) where T : ITrack, IAdjustableAudioComponent { - this.track = track as Track; + this.track = track; FinalRate.TriggerChange(); AdjustPitch.TriggerChange(); @@ -89,9 +89,9 @@ namespace osu.Game.Rulesets.Mods private void applyPitchAdjustment(ValueChangedEvent adjustPitchSetting) { // remove existing old adjustment - track?.RemoveAdjustment(adjustmentForPitchSetting(adjustPitchSetting.OldValue), SpeedChange); + (track as IAdjustableAudioComponent)?.RemoveAdjustment(adjustmentForPitchSetting(adjustPitchSetting.OldValue), SpeedChange); - track?.AddAdjustment(adjustmentForPitchSetting(adjustPitchSetting.NewValue), SpeedChange); + (track as IAdjustableAudioComponent)?.AddAdjustment(adjustmentForPitchSetting(adjustPitchSetting.NewValue), SpeedChange); } private AdjustableProperty adjustmentForPitchSetting(bool adjustPitchSettingValue) diff --git a/osu.Game/Screens/Edit/Components/PlaybackControl.cs b/osu.Game/Screens/Edit/Components/PlaybackControl.cs index 0a9b4f06a7..412efe266c 100644 --- a/osu.Game/Screens/Edit/Components/PlaybackControl.cs +++ b/osu.Game/Screens/Edit/Components/PlaybackControl.cs @@ -66,12 +66,12 @@ namespace osu.Game.Screens.Edit.Components } }; - musicController.AddAdjustment(AdjustableProperty.Tempo, tempo); + musicController.CurrentTrack?.AddAdjustment(AdjustableProperty.Tempo, tempo); } protected override void Dispose(bool isDisposing) { - musicController?.RemoveAdjustment(AdjustableProperty.Tempo, tempo); + musicController?.CurrentTrack?.RemoveAdjustment(AdjustableProperty.Tempo, tempo); base.Dispose(isDisposing); } 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 446f7fdf88..24fb855009 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/TimelinePart.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/TimelinePart.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 osuTK; @@ -57,7 +58,8 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts return; } - content.RelativeChildSize = new Vector2((float)Math.Max(1, musicController.TrackLength), 1); + Debug.Assert(musicController.CurrentTrack != null); + content.RelativeChildSize = new Vector2((float)Math.Max(1, musicController.CurrentTrack.Length), 1); } protected virtual void LoadBeatmap(WorkingBeatmap beatmap) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs index f35e5defd8..d556d948f6 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs @@ -2,7 +2,9 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Diagnostics; using osu.Framework.Allocation; +using osu.Framework.Audio.Track; using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; @@ -60,21 +62,20 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline Beatmap.BindValueChanged(b => { waveform.Waveform = b.NewValue.Waveform; + track = musicController.CurrentTrack; - // Todo: Wrong. - Schedule(() => + Debug.Assert(track != null); + + if (track.Length > 0) { - if (musicController.TrackLength > 0) - { - MaxZoom = getZoomLevelForVisibleMilliseconds(500); - MinZoom = getZoomLevelForVisibleMilliseconds(10000); - Zoom = getZoomLevelForVisibleMilliseconds(2000); - } - }); + MaxZoom = getZoomLevelForVisibleMilliseconds(500); + MinZoom = getZoomLevelForVisibleMilliseconds(10000); + Zoom = getZoomLevelForVisibleMilliseconds(2000); + } }, true); } - private float getZoomLevelForVisibleMilliseconds(double milliseconds) => (float)(musicController.TrackLength / milliseconds); + private float getZoomLevelForVisibleMilliseconds(double milliseconds) => (float)(track.Length / milliseconds); /// /// The timeline's scroll position in the last frame. @@ -96,6 +97,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline /// private bool trackWasPlaying; + private ITrack track; + protected override void Update() { base.Update(); @@ -136,15 +139,15 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline if (!musicController.TrackLoaded) return; - editorClock.Seek(Current / Content.DrawWidth * musicController.TrackLength); + editorClock.Seek(Current / Content.DrawWidth * track.Length); } private void scrollToTrackTime() { - if (!musicController.TrackLoaded || musicController.TrackLength == 0) + if (!musicController.TrackLoaded || track.Length == 0) return; - ScrollTo((float)(editorClock.CurrentTime / musicController.TrackLength) * Content.DrawWidth, false); + ScrollTo((float)(editorClock.CurrentTime / track.Length) * Content.DrawWidth, false); } protected override bool OnMouseDown(MouseDownEvent e) @@ -188,7 +191,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline new SnapResult(screenSpacePosition, beatSnapProvider.SnapTime(getTimeFromPosition(Content.ToLocalSpace(screenSpacePosition)))); private double getTimeFromPosition(Vector2 localPosition) => - (localPosition.X / Content.DrawWidth) * musicController.TrackLength; + (localPosition.X / Content.DrawWidth) * track.Length; public float GetBeatSnapDistanceAt(double referenceTime) => throw new NotImplementedException(); diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs index a833b354ed..ceb0275a13 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs @@ -3,6 +3,7 @@ using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Game.Graphics; using osu.Game.Overlays; @@ -43,7 +44,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline for (var i = 0; i < beatmap.ControlPointInfo.TimingPoints.Count; i++) { var point = beatmap.ControlPointInfo.TimingPoints[i]; - var until = i + 1 < beatmap.ControlPointInfo.TimingPoints.Count ? beatmap.ControlPointInfo.TimingPoints[i + 1].Time : musicController.TrackLength; + var until = i + 1 < beatmap.ControlPointInfo.TimingPoints.Count ? beatmap.ControlPointInfo.TimingPoints[i + 1].Time : musicController.CurrentTrack.AsNonNull().Length; int beat = 0; diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 756b03d049..b02aabc24d 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -83,7 +83,7 @@ namespace osu.Game.Screens.Edit 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 = musicController.GetTrackClock() ?? new StopwatchClock(); + var sourceClock = (IAdjustableClock)musicController.CurrentTrack ?? new StopwatchClock(); clock = new EditorClock(Beatmap.Value, beatDivisor) { IsCoupled = false }; clock.ChangeSource(sourceClock); diff --git a/osu.Game/Screens/Edit/EditorClock.cs b/osu.Game/Screens/Edit/EditorClock.cs index 4cb24f90a6..4e589aeeef 100644 --- a/osu.Game/Screens/Edit/EditorClock.cs +++ b/osu.Game/Screens/Edit/EditorClock.cs @@ -55,7 +55,7 @@ namespace osu.Game.Screens.Edit private void load() { // Todo: What. - TrackLength ??= musicController.TrackLength; + TrackLength ??= musicController.CurrentTrack?.Length ?? 0; } /// diff --git a/osu.Game/Screens/Menu/IntroScreen.cs b/osu.Game/Screens/Menu/IntroScreen.cs index 7da5df2723..030923c228 100644 --- a/osu.Game/Screens/Menu/IntroScreen.cs +++ b/osu.Game/Screens/Menu/IntroScreen.cs @@ -113,7 +113,9 @@ namespace osu.Game.Screens.Menu if (setInfo != null) { initialBeatmap = beatmaps.GetWorkingBeatmap(setInfo.Beatmaps[0]); - UsingThemedIntro = !MusicController.IsDummyDevice; + + // Todo: Wrong. + UsingThemedIntro = MusicController.CurrentTrack?.IsDummyDevice == false; } return UsingThemedIntro; diff --git a/osu.Game/Screens/Menu/IntroTriangles.cs b/osu.Game/Screens/Menu/IntroTriangles.cs index 86a6fa3802..e29ea6e743 100644 --- a/osu.Game/Screens/Menu/IntroTriangles.cs +++ b/osu.Game/Screens/Menu/IntroTriangles.cs @@ -59,7 +59,7 @@ namespace osu.Game.Screens.Menu LoadComponentAsync(new TrianglesIntroSequence(logo, background) { RelativeSizeAxes = Axes.Both, - Clock = new FramedClock(UsingThemedIntro ? MusicController.GetTrackClock() : null), + Clock = new FramedClock(UsingThemedIntro ? MusicController.CurrentTrack : null), LoadMenu = LoadMenu }, t => { diff --git a/osu.Game/Screens/Menu/IntroWelcome.cs b/osu.Game/Screens/Menu/IntroWelcome.cs index 62cada577d..85f11eb244 100644 --- a/osu.Game/Screens/Menu/IntroWelcome.cs +++ b/osu.Game/Screens/Menu/IntroWelcome.cs @@ -44,7 +44,8 @@ namespace osu.Game.Screens.Menu pianoReverb = audio.Samples.Get(@"Intro/Welcome/welcome_piano"); - musicController.Looping = true; + if (musicController.CurrentTrack != null) + musicController.CurrentTrack.Looping = true; } protected override void LogoArriving(OsuLogo logo, bool resuming) diff --git a/osu.Game/Screens/Menu/LogoVisualisation.cs b/osu.Game/Screens/Menu/LogoVisualisation.cs index 349654165f..7a1ff4fa06 100644 --- a/osu.Game/Screens/Menu/LogoVisualisation.cs +++ b/osu.Game/Screens/Menu/LogoVisualisation.cs @@ -19,6 +19,7 @@ using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Track; using osu.Framework.Bindables; +using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Utils; using osu.Game.Overlays; @@ -108,14 +109,14 @@ namespace osu.Game.Screens.Menu private void updateAmplitudes() { var effect = beatmap.Value.BeatmapLoaded && musicController.TrackLoaded - ? beatmap.Value.Beatmap?.ControlPointInfo.EffectPointAt(musicController.CurrentTrackTime) + ? beatmap.Value.Beatmap?.ControlPointInfo.EffectPointAt(musicController.CurrentTrack.AsNonNull().CurrentTime) : null; for (int i = 0; i < temporalAmplitudes.Length; i++) temporalAmplitudes[i] = 0; if (musicController.TrackLoaded) - addAmplitudesFromSource(musicController); + addAmplitudesFromSource(musicController.CurrentTrack.AsNonNull()); foreach (var source in amplitudeSources) addAmplitudesFromSource(source); diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index c422f57332..ce48777ce1 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.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 osuTK; using osuTK.Graphics; @@ -181,11 +182,12 @@ namespace osu.Game.Screens.Menu if (last is IntroScreen && musicController.TrackLoaded) { - // Todo: Wrong. - if (!musicController.IsPlaying) + Debug.Assert(musicController.CurrentTrack != null); + + if (!musicController.CurrentTrack.IsRunning) { - musicController.SeekTo(metadata.PreviewTime != -1 ? metadata.PreviewTime : 0.4f * musicController.TrackLength); - musicController.Play(); + musicController.CurrentTrack.Seek(metadata.PreviewTime != -1 ? metadata.PreviewTime : 0.4f * musicController.CurrentTrack.Length); + musicController.CurrentTrack.Start(); } } diff --git a/osu.Game/Screens/Menu/OsuLogo.cs b/osu.Game/Screens/Menu/OsuLogo.cs index 1feb2481c3..f028f9b229 100644 --- a/osu.Game/Screens/Menu/OsuLogo.cs +++ b/osu.Game/Screens/Menu/OsuLogo.cs @@ -330,9 +330,9 @@ namespace osu.Game.Screens.Menu const float velocity_adjust_cutoff = 0.98f; const float paused_velocity = 0.5f; - if (musicController.IsPlaying) + if (musicController.CurrentTrack?.IsRunning == true) { - var maxAmplitude = lastBeatIndex >= 0 ? musicController.CurrentAmplitudes.Maximum : 0; + var maxAmplitude = lastBeatIndex >= 0 ? musicController.CurrentTrack.CurrentAmplitudes.Maximum : 0; logoAmplitudeContainer.Scale = new Vector2((float)Interpolation.Damp(logoAmplitudeContainer.Scale.X, 1 - Math.Max(0, maxAmplitude - scale_adjust_cutoff) * 0.04f, 0.9f, Time.Elapsed)); if (maxAmplitude > velocity_adjust_cutoff) diff --git a/osu.Game/Screens/Multi/Match/Components/ReadyButton.cs b/osu.Game/Screens/Multi/Match/Components/ReadyButton.cs index 032c8d5ca9..c7dc20ff23 100644 --- a/osu.Game/Screens/Multi/Match/Components/ReadyButton.cs +++ b/osu.Game/Screens/Multi/Match/Components/ReadyButton.cs @@ -104,7 +104,7 @@ namespace osu.Game.Screens.Multi.Match.Components return; } - bool hasEnoughTime = DateTimeOffset.UtcNow.AddSeconds(30).AddMilliseconds(musicController.TrackLength) < endDate.Value; + bool hasEnoughTime = musicController.CurrentTrack != null && DateTimeOffset.UtcNow.AddSeconds(30).AddMilliseconds(musicController.CurrentTrack.Length) < endDate.Value; Enabled.Value = hasBeatmap && hasEnoughTime; } diff --git a/osu.Game/Screens/Multi/Multiplayer.cs b/osu.Game/Screens/Multi/Multiplayer.cs index 2d74434c76..e068899c7b 100644 --- a/osu.Game/Screens/Multi/Multiplayer.cs +++ b/osu.Game/Screens/Multi/Multiplayer.cs @@ -343,9 +343,13 @@ namespace osu.Game.Screens.Multi { if (screenStack.CurrentScreen is MatchSubScreen) { - musicController.RestartPoint = Beatmap.Value.Metadata.PreviewTime; - musicController.Looping = true; - musicController.EnsurePlayingSomething(); + if (musicController.CurrentTrack != null) + { + musicController.CurrentTrack.RestartPoint = Beatmap.Value.Metadata.PreviewTime; + musicController.CurrentTrack.Looping = true; + + musicController.EnsurePlayingSomething(); + } } else { @@ -355,8 +359,11 @@ namespace osu.Game.Screens.Multi private void cancelLooping() { - musicController.Looping = false; - musicController.RestartPoint = 0; + if (musicController.CurrentTrack != null) + { + musicController.CurrentTrack.Looping = false; + musicController.CurrentTrack.RestartPoint = 0; + } } protected override void Dispose(bool isDisposing) diff --git a/osu.Game/Screens/Play/FailAnimation.cs b/osu.Game/Screens/Play/FailAnimation.cs index 0e0ef8c675..1171d8c3b0 100644 --- a/osu.Game/Screens/Play/FailAnimation.cs +++ b/osu.Game/Screens/Play/FailAnimation.cs @@ -9,6 +9,7 @@ using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Audio.Sample; using osu.Framework.Graphics; +using osu.Framework.Graphics.Audio; using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Overlays; @@ -30,21 +31,21 @@ namespace osu.Game.Screens.Play private readonly BindableDouble trackFreq = new BindableDouble(1); + private DrawableTrack track; + private const float duration = 2500; private SampleChannel failSample; - [Resolved] - private MusicController musicController { get; set; } - public FailAnimation(DrawableRuleset drawableRuleset) { this.drawableRuleset = drawableRuleset; } [BackgroundDependencyLoader] - private void load(AudioManager audio, IBindable beatmap) + private void load(AudioManager audio, IBindable beatmap, MusicController musicController) { + track = musicController.CurrentTrack; failSample = audio.Samples.Get(@"Gameplay/failsound"); } @@ -68,7 +69,7 @@ namespace osu.Game.Screens.Play Expire(); }); - musicController.AddAdjustment(AdjustableProperty.Frequency, trackFreq); + track.AddAdjustment(AdjustableProperty.Frequency, trackFreq); applyToPlayfield(drawableRuleset.Playfield); drawableRuleset.Playfield.HitObjectContainer.FlashColour(Color4.Red, 500); @@ -107,7 +108,7 @@ namespace osu.Game.Screens.Play protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); - musicController?.RemoveAdjustment(AdjustableProperty.Frequency, trackFreq); + track?.RemoveAdjustment(AdjustableProperty.Frequency, trackFreq); } } } diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index cf4678ab29..61272f56ad 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -8,8 +8,10 @@ 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.Audio; using osu.Framework.Graphics.Containers; using osu.Framework.Timing; using osu.Game.Beatmaps; @@ -27,8 +29,7 @@ namespace osu.Game.Screens.Play private readonly WorkingBeatmap beatmap; private readonly IReadOnlyList mods; - [Resolved] - private MusicController musicController { get; set; } + private DrawableTrack track; public readonly BindableBool IsPaused = new BindableBool(); @@ -95,8 +96,10 @@ namespace osu.Game.Screens.Play private readonly BindableDouble pauseFreqAdjust = new BindableDouble(1); [BackgroundDependencyLoader] - private void load(OsuConfigManager config) + private void load(OsuConfigManager config, MusicController musicController) { + track = musicController.CurrentTrack; + userAudioOffset = config.GetBindable(OsuSetting.AudioOffset); userAudioOffset.BindValueChanged(offset => userOffsetClock.Offset = offset.NewValue, true); @@ -121,15 +124,15 @@ namespace osu.Game.Screens.Play { // The Reset() call below causes speed adjustments to be reset in an async context, leading to deadlocks. // The deadlock can be prevented by resetting the track synchronously before entering the async context. - musicController.Reset(); + track.ResetSpeedAdjustments(); Task.Run(() => { - musicController.Reset(); + track.Reset(); Schedule(() => { - adjustableClock.ChangeSource(musicController.GetTrackClock()); + adjustableClock.ChangeSource(track); updateRate(); if (!IsPaused.Value) @@ -190,6 +193,20 @@ namespace osu.Game.Screens.Play IsPaused.Value = true; } + /// + /// Changes the backing clock to avoid using the originally provided track. + /// + public void StopUsingBeatmapClock() + { + if (track == null) + return; + + removeSourceClockAdjustments(); + + track = new DrawableTrack(new TrackVirtual(track.Length)); + adjustableClock.ChangeSource(track); + } + protected override void Update() { if (!IsPaused.Value) @@ -202,23 +219,30 @@ namespace osu.Game.Screens.Play private void updateRate() { + if (track == null) return; + speedAdjustmentsApplied = true; - musicController.ResetTrackAdjustments(); - musicController.AddAdjustment(AdjustableProperty.Frequency, pauseFreqAdjust); - musicController.AddAdjustment(AdjustableProperty.Tempo, UserPlaybackRate); + track.ResetSpeedAdjustments(); + + track.AddAdjustment(AdjustableProperty.Frequency, pauseFreqAdjust); + track.AddAdjustment(AdjustableProperty.Tempo, UserPlaybackRate); + + foreach (var mod in mods.OfType()) + mod.ApplyToTrack(track); } protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); removeSourceClockAdjustments(); + track = null; } private void removeSourceClockAdjustments() { if (speedAdjustmentsApplied) { - musicController.ResetTrackAdjustments(); + track.ResetSpeedAdjustments(); speedAdjustmentsApplied = false; } } diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 9ba2732920..ea04d82e67 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -31,6 +31,7 @@ using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using osu.Framework.Audio.Track; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Bindings; using osu.Game.Graphics.UserInterface; @@ -560,9 +561,9 @@ namespace osu.Game.Screens.Select BeatmapDetails.Refresh(); - if (music != null) + if (music?.CurrentTrack != null) { - music.Looping = true; + music.CurrentTrack.Looping = true; music.ResetTrackAdjustments(); } @@ -588,8 +589,8 @@ namespace osu.Game.Screens.Select BeatmapOptions.Hide(); - if (music != null) - music.Looping = false; + if (music?.CurrentTrack != null) + music.CurrentTrack.Looping = false; this.ScaleTo(1.1f, 250, Easing.InSine); @@ -610,8 +611,8 @@ namespace osu.Game.Screens.Select FilterControl.Deactivate(); - if (music != null) - music.Looping = false; + if (music?.CurrentTrack != null) + music.CurrentTrack.Looping = false; return false; } @@ -652,18 +653,30 @@ namespace osu.Game.Screens.Select BeatmapDetails.Beatmap = beatmap; - if (music != null) - music.Looping = false; + if (music?.CurrentTrack != null) + music.CurrentTrack.Looping = false; } + 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() { - music.RestartPoint = Beatmap.Value.Metadata.PreviewTime; - music.EnsurePlayingSomething(); + ITrack track = music?.CurrentTrack; + if (track == null) + return; + + bool isNewTrack = !lastTrack.TryGetTarget(out var last) || last != track; + + track.RestartPoint = Beatmap.Value.Metadata.PreviewTime; + + if (!track.IsRunning && (music?.IsUserPaused != true || isNewTrack)) + music?.Play(true); + + lastTrack.SetTarget(track); } private void carouselBeatmapsLoaded() diff --git a/osu.Game/Tests/Visual/EditorClockTestScene.cs b/osu.Game/Tests/Visual/EditorClockTestScene.cs index 5226a49def..780b4f1b3a 100644 --- a/osu.Game/Tests/Visual/EditorClockTestScene.cs +++ b/osu.Game/Tests/Visual/EditorClockTestScene.cs @@ -44,7 +44,7 @@ namespace osu.Game.Tests.Visual private void beatmapChanged(ValueChangedEvent e) { Clock.ControlPointInfo = e.NewValue.Beatmap.ControlPointInfo; - Clock.ChangeSource(MusicController.GetTrackClock() ?? new StopwatchClock()); + Clock.ChangeSource((IAdjustableClock)MusicController.CurrentTrack ?? new StopwatchClock()); Clock.ProcessFrame(); } diff --git a/osu.Game/Tests/Visual/RateAdjustedBeatmapTestScene.cs b/osu.Game/Tests/Visual/RateAdjustedBeatmapTestScene.cs index e7cb461d7b..ae4b0ef84a 100644 --- a/osu.Game/Tests/Visual/RateAdjustedBeatmapTestScene.cs +++ b/osu.Game/Tests/Visual/RateAdjustedBeatmapTestScene.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.Extensions.ObjectExtensions; + namespace osu.Game.Tests.Visual { /// @@ -13,7 +15,7 @@ namespace osu.Game.Tests.Visual base.Update(); // note that this will override any mod rate application - MusicController.Tempo.Value = Clock.Rate; + MusicController.CurrentTrack.AsNonNull().Tempo.Value = Clock.Rate; } } } From 58660c70a3c9cad61e78ef854d238e8f569ee08e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 5 Aug 2020 21:20:41 +0900 Subject: [PATCH 0407/1782] Cache before idle tracker --- 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 929254e8ad..3e41be2028 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -554,8 +554,8 @@ namespace osu.Game Container logoContainer; BackButton.Receptor receptor; - dependencies.CacheAs(idleTracker = new GameIdleTracker(6000)); dependencies.CacheAs(MusicController = new MusicController()); + dependencies.CacheAs(idleTracker = new GameIdleTracker(6000)); AddRange(new Drawable[] { From e9fc783b1d75f31ec8291fa8a11f254f28cb1860 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 5 Aug 2020 21:21:08 +0900 Subject: [PATCH 0408/1782] Add back loop-on-completion --- osu.Game/OsuGame.cs | 18 +----------------- osu.Game/Overlays/MusicController.cs | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 3e41be2028..a41c7b28a5 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -427,23 +427,7 @@ namespace osu.Game updateModDefaults(); - var newBeatmap = beatmap.NewValue; - - if (newBeatmap != null) - { - // MusicController.Completed += () => Scheduler.AddOnce(() => trackCompleted(newBeatmap)); - newBeatmap.BeginAsyncLoad(); - } - - // void trackCompleted(WorkingBeatmap b) - // { - // // the source of track completion is the audio thread, so the beatmap may have changed before firing. - // if (Beatmap.Value != b) - // return; - // - // if (!MusicController.Looping && !Beatmap.Disabled) - // MusicController.NextTrack(); - // } + beatmap.NewValue?.BeginAsyncLoad(); } private void modsChanged(ValueChangedEvent> mods) diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index c22849b7d6..50ad97be7c 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using JetBrains.Annotations; using osu.Framework.Allocation; @@ -323,7 +324,10 @@ namespace osu.Game.Overlays CurrentTrack = null; if (current != null) + { trackContainer.Add(CurrentTrack = new DrawableTrack(current.GetRealTrack())); + CurrentTrack.Completed += () => onTrackCompleted(current); + } TrackChanged?.Invoke(current, direction); @@ -332,6 +336,18 @@ namespace osu.Game.Overlays queuedDirection = null; } + private void onTrackCompleted(WorkingBeatmap workingBeatmap) + { + // the source of track completion is the audio thread, so the beatmap may have changed before firing. + if (current != workingBeatmap) + return; + + Debug.Assert(CurrentTrack != null); + + if (!CurrentTrack.Looping && !beatmap.Disabled) + NextTrack(); + } + private bool allowRateAdjustments; /// From 11a6c9bdccc65e89575a0e138512ccc379b1a15f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 5 Aug 2020 21:21:14 +0900 Subject: [PATCH 0409/1782] Revert unnecessary change --- osu.Game/Graphics/Containers/BeatSyncedContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Graphics/Containers/BeatSyncedContainer.cs b/osu.Game/Graphics/Containers/BeatSyncedContainer.cs index 92a9ed0566..69021e1634 100644 --- a/osu.Game/Graphics/Containers/BeatSyncedContainer.cs +++ b/osu.Game/Graphics/Containers/BeatSyncedContainer.cs @@ -64,7 +64,7 @@ namespace osu.Game.Graphics.Containers beatmap = Beatmap.Value.Beatmap; } - if (track != null && beatmap != null && musicController.IsPlaying && track.Length > 0) + if (track != null && beatmap != null && track.IsRunning && track.Length > 0) { currentTrackTime = track.CurrentTime + EarlyActivationMilliseconds; From f058f5e977bbfb532b2711e0101c76868022b808 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 5 Aug 2020 21:29:53 +0900 Subject: [PATCH 0410/1782] Fix incorrect value being set --- 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 ea04d82e67..80ed894233 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -654,7 +654,7 @@ namespace osu.Game.Screens.Select BeatmapDetails.Beatmap = beatmap; if (music?.CurrentTrack != null) - music.CurrentTrack.Looping = false; + music.CurrentTrack.Looping = true; } private readonly WeakReference lastTrack = new WeakReference(null); From 0edd50939783f86909c3e024d47100259417c42e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 5 Aug 2020 21:30:11 +0900 Subject: [PATCH 0411/1782] Only change track when audio doesn't equal --- osu.Game/Overlays/MusicController.cs | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index 50ad97be7c..47d1bef177 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -320,6 +320,18 @@ namespace osu.Game.Overlays current = beatmap.NewValue; + if (!beatmap.OldValue.BeatmapInfo.AudioEquals(current?.BeatmapInfo)) + changeTrack(); + + TrackChanged?.Invoke(current, direction); + + ResetTrackAdjustments(); + + queuedDirection = null; + } + + private void changeTrack() + { CurrentTrack?.Expire(); CurrentTrack = null; @@ -328,12 +340,6 @@ namespace osu.Game.Overlays trackContainer.Add(CurrentTrack = new DrawableTrack(current.GetRealTrack())); CurrentTrack.Completed += () => onTrackCompleted(current); } - - TrackChanged?.Invoke(current, direction); - - ResetTrackAdjustments(); - - queuedDirection = null; } private void onTrackCompleted(WorkingBeatmap workingBeatmap) From 86ae61c6b79aec0febbe8220781cc7ad3aefb9de Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 5 Aug 2020 22:09:47 +0900 Subject: [PATCH 0412/1782] Re-implement store transferral in BeatmapManager --- osu.Game/Beatmaps/BeatmapManager.cs | 19 +++++++++++-------- .../Beatmaps/BeatmapManager_WorkingBeatmap.cs | 17 +++++++++-------- 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index b2329f58ad..6a7d0b053f 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -218,7 +218,7 @@ namespace osu.Game.Beatmaps removeWorkingCache(info); } - private readonly WeakList workingCache = new WeakList(); + private readonly WeakList workingCache = new WeakList(); /// /// Retrieve a instance for the provided @@ -246,16 +246,19 @@ namespace osu.Game.Beatmaps lock (workingCache) { var working = workingCache.FirstOrDefault(w => w.BeatmapInfo?.ID == beatmapInfo.ID); + if (working != null) + return working; - if (working == null) - { - beatmapInfo.Metadata ??= beatmapInfo.BeatmapSet.Metadata; + beatmapInfo.Metadata ??= beatmapInfo.BeatmapSet.Metadata; - workingCache.Add(working = new BeatmapManagerWorkingBeatmap(Files.Store, - new LargeTextureStore(host?.CreateTextureLoaderStore(Files.Store)), beatmapInfo, audioManager)); - } + ITrackStore trackStore = workingCache.FirstOrDefault(b => b.BeatmapInfo.AudioEquals(beatmapInfo))?.TrackStore; + TextureStore textureStore = workingCache.FirstOrDefault(b => b.BeatmapInfo.BackgroundEquals(beatmapInfo))?.TextureStore; + + textureStore ??= new LargeTextureStore(host?.CreateTextureLoaderStore(Files.Store)); + trackStore ??= audioManager.GetTrackStore(Files.Store); + + workingCache.Add(working = new BeatmapManagerWorkingBeatmap(Files.Store, textureStore, trackStore, beatmapInfo, audioManager)); - // previous?.TransferTo(working); return working; } } diff --git a/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs b/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs index 33945a9eb1..ceefef5d7e 100644 --- a/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs @@ -19,13 +19,18 @@ namespace osu.Game.Beatmaps { protected class BeatmapManagerWorkingBeatmap : WorkingBeatmap { + public readonly TextureStore TextureStore; + public readonly ITrackStore TrackStore; + private readonly IResourceStore store; - public BeatmapManagerWorkingBeatmap(IResourceStore store, TextureStore textureStore, BeatmapInfo beatmapInfo, AudioManager audioManager) + public BeatmapManagerWorkingBeatmap(IResourceStore store, TextureStore textureStore, ITrackStore trackStore, BeatmapInfo beatmapInfo, AudioManager audioManager) : base(beatmapInfo, audioManager) { this.store = store; - this.textureStore = textureStore; + + TextureStore = textureStore; + TrackStore = trackStore; } protected override IBeatmap GetBeatmap() @@ -44,10 +49,6 @@ namespace osu.Game.Beatmaps private string getPathForFile(string filename) => BeatmapSetInfo.Files.SingleOrDefault(f => string.Equals(f.Filename, filename, StringComparison.OrdinalIgnoreCase))?.FileInfo.StoragePath; - private TextureStore textureStore; - - private ITrackStore trackStore; - protected override bool BackgroundStillValid(Texture b) => false; // bypass lazy logic. we want to return a new background each time for refcounting purposes. protected override Texture GetBackground() @@ -57,7 +58,7 @@ namespace osu.Game.Beatmaps try { - return textureStore.Get(getPathForFile(Metadata.BackgroundFile)); + return TextureStore.Get(getPathForFile(Metadata.BackgroundFile)); } catch (Exception e) { @@ -70,7 +71,7 @@ namespace osu.Game.Beatmaps { try { - return (trackStore ??= AudioManager.GetTrackStore(store)).Get(getPathForFile(Metadata.AudioFile)); + return TrackStore.Get(getPathForFile(Metadata.AudioFile)); } catch (Exception e) { From 0f7fde5d2cd5db7eb8e621bfe221ea28cc252da8 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 5 Aug 2020 22:32:44 +0900 Subject: [PATCH 0413/1782] Revert unnecessary change --- osu.Game/Overlays/Music/PlaylistOverlay.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Music/PlaylistOverlay.cs b/osu.Game/Overlays/Music/PlaylistOverlay.cs index c089158c01..b9a58c37cb 100644 --- a/osu.Game/Overlays/Music/PlaylistOverlay.cs +++ b/osu.Game/Overlays/Music/PlaylistOverlay.cs @@ -83,7 +83,10 @@ namespace osu.Game.Overlays.Music BeatmapInfo toSelect = list.FirstVisibleSet?.Beatmaps?.FirstOrDefault(); if (toSelect != null) + { beatmap.Value = beatmaps.GetWorkingBeatmap(toSelect); + musicController.CurrentTrack?.Restart(); + } }; } @@ -116,12 +119,12 @@ namespace osu.Game.Overlays.Music { if (set.ID == (beatmap.Value?.BeatmapSetInfo?.ID ?? -1)) { - musicController.SeekTo(0); + musicController.CurrentTrack?.Seek(0); return; } beatmap.Value = beatmaps.GetWorkingBeatmap(set.Beatmaps.First()); - musicController.Play(true); + musicController.CurrentTrack?.Restart(); } } From fe8c462498ad18a842544202bc4e6a14d3e217ed Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 6 Aug 2020 17:00:17 +0900 Subject: [PATCH 0414/1782] Remove intermediate container --- osu.Game/Overlays/MusicController.cs | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index 47d1bef177..6adfa1817e 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -65,20 +65,12 @@ namespace osu.Game.Overlays [Resolved(canBeNull: true)] private OnScreenDisplay onScreenDisplay { get; set; } - [NotNull] - private readonly TrackContainer trackContainer; - [CanBeNull] public DrawableTrack CurrentTrack { get; private set; } private IBindable> managerUpdated; private IBindable> managerRemoved; - public MusicController() - { - InternalChild = trackContainer = new TrackContainer { RelativeSizeAxes = Axes.Both }; - } - [BackgroundDependencyLoader] private void load() { @@ -337,8 +329,10 @@ namespace osu.Game.Overlays if (current != null) { - trackContainer.Add(CurrentTrack = new DrawableTrack(current.GetRealTrack())); + CurrentTrack = new DrawableTrack(current.GetRealTrack()); CurrentTrack.Completed += () => onTrackCompleted(current); + + AddInternal(CurrentTrack); } } @@ -433,10 +427,6 @@ namespace osu.Game.Overlays { } } - - private class TrackContainer : AudioContainer - { - } } public enum TrackChangeDirection From e8ab3cff3c38c5dad046b0c69f0f34b7478a08ce Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 6 Aug 2020 17:02:42 +0900 Subject: [PATCH 0415/1782] Add class constraint --- osu.Game/Rulesets/Mods/IApplicableToTrack.cs | 2 +- osu.Game/Rulesets/Mods/ModRateAdjust.cs | 3 ++- osu.Game/Rulesets/Mods/ModTimeRamp.cs | 3 ++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game/Rulesets/Mods/IApplicableToTrack.cs b/osu.Game/Rulesets/Mods/IApplicableToTrack.cs index 5ae41fe09c..b29ba55942 100644 --- a/osu.Game/Rulesets/Mods/IApplicableToTrack.cs +++ b/osu.Game/Rulesets/Mods/IApplicableToTrack.cs @@ -11,6 +11,6 @@ namespace osu.Game.Rulesets.Mods /// public interface IApplicableToTrack : IApplicableMod { - void ApplyToTrack(T track) where T : ITrack, IAdjustableAudioComponent; + void ApplyToTrack(T track) where T : class, ITrack, IAdjustableAudioComponent; } } diff --git a/osu.Game/Rulesets/Mods/ModRateAdjust.cs b/osu.Game/Rulesets/Mods/ModRateAdjust.cs index e2c8ac64d9..4aee5affe9 100644 --- a/osu.Game/Rulesets/Mods/ModRateAdjust.cs +++ b/osu.Game/Rulesets/Mods/ModRateAdjust.cs @@ -12,7 +12,8 @@ namespace osu.Game.Rulesets.Mods { public abstract BindableNumber SpeedChange { get; } - public virtual void ApplyToTrack(T track) where T : ITrack, IAdjustableAudioComponent + public virtual void ApplyToTrack(T track) + where T : class, ITrack, IAdjustableAudioComponent { track.AddAdjustment(AdjustableProperty.Tempo, SpeedChange); } diff --git a/osu.Game/Rulesets/Mods/ModTimeRamp.cs b/osu.Game/Rulesets/Mods/ModTimeRamp.cs index b6cbe72971..4b5241488f 100644 --- a/osu.Game/Rulesets/Mods/ModTimeRamp.cs +++ b/osu.Game/Rulesets/Mods/ModTimeRamp.cs @@ -51,7 +51,8 @@ namespace osu.Game.Rulesets.Mods AdjustPitch.BindValueChanged(applyPitchAdjustment); } - public void ApplyToTrack(T track) where T : ITrack, IAdjustableAudioComponent + public void ApplyToTrack(T track) + where T : class, ITrack, IAdjustableAudioComponent { this.track = track; From ad959ce5238dbd8ca4465305d703efac065a3dab Mon Sep 17 00:00:00 2001 From: Joehu Date: Thu, 6 Aug 2020 01:06:51 -0700 Subject: [PATCH 0416/1782] Make toolbar button abstract --- osu.Game/Overlays/Toolbar/ToolbarButton.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Toolbar/ToolbarButton.cs b/osu.Game/Overlays/Toolbar/ToolbarButton.cs index 86a3f5d8aa..a03ea64eb2 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarButton.cs @@ -18,7 +18,7 @@ using osu.Game.Graphics.UserInterface; namespace osu.Game.Overlays.Toolbar { - public class ToolbarButton : OsuClickableContainer + public abstract class ToolbarButton : OsuClickableContainer { public const float WIDTH = Toolbar.HEIGHT * 1.4f; @@ -68,7 +68,7 @@ namespace osu.Game.Overlays.Toolbar private readonly SpriteText tooltip2; protected FillFlowContainer Flow; - public ToolbarButton() + protected ToolbarButton() : base(HoverSampleSet.Loud) { Width = WIDTH; From c72ab9047e3ea00dbc6eb176a797d20dfd5c95d3 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 6 Aug 2020 17:15:33 +0900 Subject: [PATCH 0417/1782] Cleanup test scene disposal --- osu.Game/Tests/Visual/OsuTestScene.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/Tests/Visual/OsuTestScene.cs b/osu.Game/Tests/Visual/OsuTestScene.cs index e968f7e675..f2b9388fdc 100644 --- a/osu.Game/Tests/Visual/OsuTestScene.cs +++ b/osu.Game/Tests/Visual/OsuTestScene.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Linq; using System.Threading.Tasks; @@ -169,7 +170,10 @@ namespace osu.Game.Tests.Visual rulesetDependencies?.Dispose(); if (MusicController?.TrackLoaded == true) - MusicController.Stop(); + { + Debug.Assert(MusicController.CurrentTrack != null); + MusicController.CurrentTrack.Stop(); + } if (contextFactory.IsValueCreated) contextFactory.Value.ResetDatabase(); From 7bcb68ffacd76d2f7b5d743781598b733f2c2731 Mon Sep 17 00:00:00 2001 From: Joehu Date: Thu, 6 Aug 2020 01:17:24 -0700 Subject: [PATCH 0418/1782] Handle overlay toggling with toolbar buttons instead --- osu.Game/OsuGame.cs | 28 +----------------- .../Toolbar/ToolbarBeatmapListingButton.cs | 3 ++ osu.Game/Overlays/Toolbar/ToolbarButton.cs | 29 +++++++++++++++---- .../Overlays/Toolbar/ToolbarChatButton.cs | 3 ++ .../Overlays/Toolbar/ToolbarHomeButton.cs | 18 ++---------- .../Overlays/Toolbar/ToolbarMusicButton.cs | 3 ++ .../Toolbar/ToolbarNotificationButton.cs | 3 ++ .../Overlays/Toolbar/ToolbarSettingsButton.cs | 3 ++ .../Overlays/Toolbar/ToolbarSocialButton.cs | 3 ++ 9 files changed, 45 insertions(+), 48 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 26f7c3b93b..b5752214bd 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -67,8 +67,6 @@ namespace osu.Game [NotNull] private readonly NotificationOverlay notifications = new NotificationOverlay(); - private NowPlayingOverlay nowPlaying; - private BeatmapListingOverlay beatmapListing; private DashboardOverlay dashboard; @@ -650,7 +648,7 @@ namespace osu.Game Origin = Anchor.TopRight, }, rightFloatingOverlayContent.Add, true); - loadComponentSingleFile(nowPlaying = new NowPlayingOverlay + loadComponentSingleFile(new NowPlayingOverlay { GetToolbarHeight = () => ToolbarOffset, Anchor = Anchor.TopRight, @@ -862,18 +860,6 @@ namespace osu.Game switch (action) { - case GlobalAction.ToggleNowPlaying: - nowPlaying.ToggleVisibility(); - return true; - - case GlobalAction.ToggleChat: - chatOverlay.ToggleVisibility(); - return true; - - case GlobalAction.ToggleSocial: - dashboard.ToggleVisibility(); - return true; - case GlobalAction.ResetInputSettings: var sensitivity = frameworkConfig.GetBindable(FrameworkSetting.CursorSensitivity); @@ -889,18 +875,6 @@ namespace osu.Game Toolbar.ToggleVisibility(); return true; - case GlobalAction.ToggleSettings: - Settings.ToggleVisibility(); - return true; - - case GlobalAction.ToggleDirect: - beatmapListing.ToggleVisibility(); - return true; - - case GlobalAction.ToggleNotifications: - notifications.ToggleVisibility(); - return true; - case GlobalAction.ToggleGameplayMouseButtons: LocalConfig.Set(OsuSetting.MouseDisableButtons, !LocalConfig.Get(OsuSetting.MouseDisableButtons)); return true; diff --git a/osu.Game/Overlays/Toolbar/ToolbarBeatmapListingButton.cs b/osu.Game/Overlays/Toolbar/ToolbarBeatmapListingButton.cs index 64430c77ac..cde305fffd 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarBeatmapListingButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarBeatmapListingButton.cs @@ -3,6 +3,7 @@ using osu.Framework.Allocation; using osu.Game.Graphics; +using osu.Game.Input.Bindings; namespace osu.Game.Overlays.Toolbar { @@ -13,6 +14,8 @@ namespace osu.Game.Overlays.Toolbar SetIcon(OsuIcon.ChevronDownCircle); TooltipMain = "Beatmap listing"; TooltipSub = "Browse for new beatmaps"; + + Hotkey = GlobalAction.ToggleDirect; } [BackgroundDependencyLoader(true)] diff --git a/osu.Game/Overlays/Toolbar/ToolbarButton.cs b/osu.Game/Overlays/Toolbar/ToolbarButton.cs index a03ea64eb2..3f1dccc45a 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarButton.cs @@ -5,23 +5,27 @@ 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.Input.Bindings; +using osu.Framework.Input.Events; using osu.Game.Graphics; using osu.Game.Graphics.Backgrounds; +using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osu.Game.Input.Bindings; using osuTK; using osuTK.Graphics; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Input.Events; -using osu.Game.Graphics.Containers; -using osu.Game.Graphics.UserInterface; namespace osu.Game.Overlays.Toolbar { - public abstract class ToolbarButton : OsuClickableContainer + public abstract class ToolbarButton : OsuClickableContainer, IKeyBindingHandler { public const float WIDTH = Toolbar.HEIGHT * 1.4f; + protected GlobalAction? Hotkey { get; set; } + public void SetIcon(Drawable icon) { IconContainer.Icon = icon; @@ -164,6 +168,21 @@ namespace osu.Game.Overlays.Toolbar HoverBackground.FadeOut(200); tooltipContainer.FadeOut(100); } + + public bool OnPressed(GlobalAction action) + { + if (action == Hotkey) + { + Click(); + return true; + } + + return false; + } + + public void OnReleased(GlobalAction action) + { + } } public class OpaqueBackground : Container diff --git a/osu.Game/Overlays/Toolbar/ToolbarChatButton.cs b/osu.Game/Overlays/Toolbar/ToolbarChatButton.cs index ec7da54571..dee4be0c1f 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarChatButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarChatButton.cs @@ -3,6 +3,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics.Sprites; +using osu.Game.Input.Bindings; namespace osu.Game.Overlays.Toolbar { @@ -13,6 +14,8 @@ namespace osu.Game.Overlays.Toolbar SetIcon(FontAwesome.Solid.Comments); TooltipMain = "Chat"; TooltipSub = "Join the real-time discussion"; + + Hotkey = GlobalAction.ToggleChat; } [BackgroundDependencyLoader(true)] diff --git a/osu.Game/Overlays/Toolbar/ToolbarHomeButton.cs b/osu.Game/Overlays/Toolbar/ToolbarHomeButton.cs index e642f0c453..4845c9a99f 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarHomeButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarHomeButton.cs @@ -2,33 +2,19 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics.Sprites; -using osu.Framework.Input.Bindings; using osu.Game.Input.Bindings; namespace osu.Game.Overlays.Toolbar { - public class ToolbarHomeButton : ToolbarButton, IKeyBindingHandler + public class ToolbarHomeButton : ToolbarButton { public ToolbarHomeButton() { Icon = FontAwesome.Solid.Home; TooltipMain = "Home"; TooltipSub = "Return to the main menu"; - } - public bool OnPressed(GlobalAction action) - { - if (action == GlobalAction.Home) - { - Click(); - return true; - } - - return false; - } - - public void OnReleased(GlobalAction action) - { + Hotkey = GlobalAction.Home; } } } diff --git a/osu.Game/Overlays/Toolbar/ToolbarMusicButton.cs b/osu.Game/Overlays/Toolbar/ToolbarMusicButton.cs index 712da12208..59276a5943 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarMusicButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarMusicButton.cs @@ -3,6 +3,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics.Sprites; +using osu.Game.Input.Bindings; namespace osu.Game.Overlays.Toolbar { @@ -13,6 +14,8 @@ namespace osu.Game.Overlays.Toolbar Icon = FontAwesome.Solid.Music; TooltipMain = "Now playing"; TooltipSub = "Manage the currently playing track"; + + Hotkey = GlobalAction.ToggleNowPlaying; } [BackgroundDependencyLoader(true)] diff --git a/osu.Game/Overlays/Toolbar/ToolbarNotificationButton.cs b/osu.Game/Overlays/Toolbar/ToolbarNotificationButton.cs index dbd6c557d3..a699fd907f 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarNotificationButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarNotificationButton.cs @@ -9,6 +9,7 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; +using osu.Game.Input.Bindings; using osuTK; using osuTK.Graphics; @@ -28,6 +29,8 @@ namespace osu.Game.Overlays.Toolbar TooltipMain = "Notifications"; TooltipSub = "Waiting for 'ya"; + Hotkey = GlobalAction.ToggleNotifications; + Add(countDisplay = new CountCircle { Alpha = 0, diff --git a/osu.Game/Overlays/Toolbar/ToolbarSettingsButton.cs b/osu.Game/Overlays/Toolbar/ToolbarSettingsButton.cs index 79942012f9..ed2a23ec2a 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarSettingsButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarSettingsButton.cs @@ -3,6 +3,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics.Sprites; +using osu.Game.Input.Bindings; namespace osu.Game.Overlays.Toolbar { @@ -13,6 +14,8 @@ namespace osu.Game.Overlays.Toolbar Icon = FontAwesome.Solid.Cog; TooltipMain = "Settings"; TooltipSub = "Change your settings"; + + Hotkey = GlobalAction.ToggleSettings; } [BackgroundDependencyLoader(true)] diff --git a/osu.Game/Overlays/Toolbar/ToolbarSocialButton.cs b/osu.Game/Overlays/Toolbar/ToolbarSocialButton.cs index 0dbb552c15..6faa58c559 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarSocialButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarSocialButton.cs @@ -3,6 +3,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics.Sprites; +using osu.Game.Input.Bindings; namespace osu.Game.Overlays.Toolbar { @@ -13,6 +14,8 @@ namespace osu.Game.Overlays.Toolbar Icon = FontAwesome.Solid.Users; TooltipMain = "Friends"; TooltipSub = "Interact with those close to you"; + + Hotkey = GlobalAction.ToggleSocial; } [BackgroundDependencyLoader(true)] From d574cac702612dc4c36748588cebf4991d3ab64f Mon Sep 17 00:00:00 2001 From: Joehu Date: Thu, 6 Aug 2020 01:18:45 -0700 Subject: [PATCH 0419/1782] Add keybinding to toolbar button's tooltip --- osu.Game/Overlays/Toolbar/ToolbarButton.cs | 35 ++++++++++++++++++++-- 1 file changed, 32 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Toolbar/ToolbarButton.cs b/osu.Game/Overlays/Toolbar/ToolbarButton.cs index 3f1dccc45a..9fd47fd150 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarButton.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.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -14,6 +15,7 @@ using osu.Game.Graphics.Backgrounds; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; +using osu.Game.Input; using osu.Game.Input.Bindings; using osuTK; using osuTK.Graphics; @@ -70,8 +72,12 @@ namespace osu.Game.Overlays.Toolbar private readonly FillFlowContainer tooltipContainer; private readonly SpriteText tooltip1; private readonly SpriteText tooltip2; + private readonly SpriteText keyBindingTooltip; protected FillFlowContainer Flow; + [Resolved] + private KeyBindingStore store { get; set; } + protected ToolbarButton() : base(HoverSampleSet.Loud) { @@ -127,7 +133,7 @@ namespace osu.Game.Overlays.Toolbar Origin = TooltipAnchor, Position = new Vector2(TooltipAnchor.HasFlag(Anchor.x0) ? 5 : -5, 5), Alpha = 0, - Children = new[] + Children = new Drawable[] { tooltip1 = new OsuSpriteText { @@ -136,17 +142,40 @@ namespace osu.Game.Overlays.Toolbar Shadow = true, Font = OsuFont.GetFont(size: 22, weight: FontWeight.Bold), }, - tooltip2 = new OsuSpriteText + new FillFlowContainer { + AutoSizeAxes = Axes.Both, Anchor = TooltipAnchor, Origin = TooltipAnchor, - Shadow = true, + Direction = FillDirection.Horizontal, + Children = new[] + { + tooltip2 = new OsuSpriteText { Shadow = true }, + keyBindingTooltip = new OsuSpriteText { Shadow = true } + } } } } }; } + [BackgroundDependencyLoader] + private void load() + { + updateTooltip(); + + store.KeyBindingChanged += updateTooltip; + } + + private void updateTooltip() + { + var binding = store.Query().Find(b => (GlobalAction)b.Action == Hotkey); + + var keyBindingString = binding?.KeyCombination.ReadableString(); + + keyBindingTooltip.Text = !string.IsNullOrEmpty(keyBindingString) ? $" ({keyBindingString})" : string.Empty; + } + protected override bool OnMouseDown(MouseDownEvent e) => true; protected override bool OnClick(ClickEvent e) From f9c369b23cff9b0bdc1b8b7907ed74d8919bf123 Mon Sep 17 00:00:00 2001 From: Joehu Date: Thu, 6 Aug 2020 01:20:03 -0700 Subject: [PATCH 0420/1782] Fix toolbar music button tooltip overflowing off-screen --- osu.Game/Overlays/Toolbar/ToolbarMusicButton.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Overlays/Toolbar/ToolbarMusicButton.cs b/osu.Game/Overlays/Toolbar/ToolbarMusicButton.cs index 59276a5943..f9aa2de497 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarMusicButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarMusicButton.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Game.Input.Bindings; @@ -9,6 +10,8 @@ namespace osu.Game.Overlays.Toolbar { public class ToolbarMusicButton : ToolbarOverlayToggleButton { + protected override Anchor TooltipAnchor => Anchor.TopRight; + public ToolbarMusicButton() { Icon = FontAwesome.Solid.Music; From f53672193eb6ccd1d14c7900603aa5269e7468c9 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 6 Aug 2020 17:48:07 +0900 Subject: [PATCH 0421/1782] Fix track stores being kept alive --- osu.Game/Beatmaps/BeatmapManager.cs | 16 +++++++++++++++- .../Beatmaps/BeatmapManager_WorkingBeatmap.cs | 10 +++++++++- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 6a7d0b053f..f22f41531a 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -219,6 +219,7 @@ namespace osu.Game.Beatmaps } private readonly WeakList workingCache = new WeakList(); + private readonly Dictionary referencedTrackStores = new Dictionary(); /// /// Retrieve a instance for the provided @@ -257,12 +258,25 @@ namespace osu.Game.Beatmaps textureStore ??= new LargeTextureStore(host?.CreateTextureLoaderStore(Files.Store)); trackStore ??= audioManager.GetTrackStore(Files.Store); - workingCache.Add(working = new BeatmapManagerWorkingBeatmap(Files.Store, textureStore, trackStore, beatmapInfo, audioManager)); + workingCache.Add(working = new BeatmapManagerWorkingBeatmap(Files.Store, textureStore, trackStore, beatmapInfo, audioManager, dereferenceTrackStore)); + referencedTrackStores[trackStore] = referencedTrackStores.GetOrDefault(trackStore) + 1; return working; } } + private void dereferenceTrackStore(ITrackStore trackStore) + { + lock (workingCache) + { + if (--referencedTrackStores[trackStore] == 0) + { + referencedTrackStores.Remove(trackStore); + trackStore.Dispose(); + } + } + } + /// /// Perform a lookup query on available s. /// diff --git a/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs b/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs index ceefef5d7e..a54d46c1b1 100644 --- a/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs @@ -23,11 +23,14 @@ namespace osu.Game.Beatmaps public readonly ITrackStore TrackStore; private readonly IResourceStore store; + private readonly Action dereferenceAction; - public BeatmapManagerWorkingBeatmap(IResourceStore store, TextureStore textureStore, ITrackStore trackStore, BeatmapInfo beatmapInfo, AudioManager audioManager) + public BeatmapManagerWorkingBeatmap(IResourceStore store, TextureStore textureStore, ITrackStore trackStore, BeatmapInfo beatmapInfo, AudioManager audioManager, + Action dereferenceAction) : base(beatmapInfo, audioManager) { this.store = store; + this.dereferenceAction = dereferenceAction; TextureStore = textureStore; TrackStore = trackStore; @@ -137,6 +140,11 @@ namespace osu.Game.Beatmaps return null; } } + + ~BeatmapManagerWorkingBeatmap() + { + dereferenceAction?.Invoke(TrackStore); + } } } } From c8ebbc8594b79423825614268feb88f2f20a32e6 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 6 Aug 2020 18:19:55 +0900 Subject: [PATCH 0422/1782] Remove MusicController from EditorClock --- .../Timelines/Summary/Parts/MarkerPart.cs | 5 +-- osu.Game/Screens/Edit/Editor.cs | 2 +- osu.Game/Screens/Edit/EditorClock.cs | 36 ++++++------------- 3 files changed, 13 insertions(+), 30 deletions(-) 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 a353f79ef4..9e9ac93d23 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/MarkerPart.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/MarkerPart.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Diagnostics; using osuTK; using osu.Framework.Allocation; using osu.Framework.Graphics; @@ -59,9 +58,7 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts return; float markerPos = Math.Clamp(ToLocalSpace(screenPosition).X, 0, DrawWidth); - - Debug.Assert(editorClock.TrackLength != null); - editorClock.SeekTo(markerPos / DrawWidth * editorClock.TrackLength.Value); + editorClock.SeekTo(markerPos / DrawWidth * editorClock.TrackLength); }); } diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index b02aabc24d..79b13a7eac 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -84,7 +84,7 @@ namespace osu.Game.Screens.Edit // Todo: should probably be done at a DrawableRuleset level to share logic with Player. var sourceClock = (IAdjustableClock)musicController.CurrentTrack ?? new StopwatchClock(); - clock = new EditorClock(Beatmap.Value, beatDivisor) { IsCoupled = false }; + clock = new EditorClock(Beatmap.Value, musicController.CurrentTrack?.Length ?? 0, beatDivisor) { IsCoupled = false }; clock.ChangeSource(sourceClock); dependencies.CacheAs(clock); diff --git a/osu.Game/Screens/Edit/EditorClock.cs b/osu.Game/Screens/Edit/EditorClock.cs index 4e589aeeef..fbfa397795 100644 --- a/osu.Game/Screens/Edit/EditorClock.cs +++ b/osu.Game/Screens/Edit/EditorClock.cs @@ -2,16 +2,13 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Diagnostics; using System.Linq; -using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Transforms; using osu.Framework.Utils; using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Overlays; namespace osu.Game.Screens.Edit { @@ -20,7 +17,7 @@ namespace osu.Game.Screens.Edit /// public class EditorClock : Component, IFrameBasedClock, IAdjustableClock, ISourceChangeableClock { - public double? TrackLength { get; private set; } + public readonly double TrackLength; public ControlPointInfo ControlPointInfo; @@ -28,34 +25,24 @@ namespace osu.Game.Screens.Edit private readonly DecoupleableInterpolatingFramedClock underlyingClock; - [Resolved] - private MusicController musicController { get; set; } - - public EditorClock(WorkingBeatmap beatmap, BindableBeatDivisor beatDivisor) - : this(beatmap.Beatmap.ControlPointInfo, null, beatDivisor) + public EditorClock(WorkingBeatmap beatmap, double trackLength, BindableBeatDivisor beatDivisor) + : this(beatmap.Beatmap.ControlPointInfo, trackLength, beatDivisor) { } - public EditorClock(ControlPointInfo controlPointInfo, double? trackLength, BindableBeatDivisor beatDivisor) - { - this.beatDivisor = beatDivisor; - - ControlPointInfo = controlPointInfo; - TrackLength = trackLength; - - underlyingClock = new DecoupleableInterpolatingFramedClock(); - } - public EditorClock() : this(new ControlPointInfo(), 1000, new BindableBeatDivisor()) { } - [BackgroundDependencyLoader] - private void load() + public EditorClock(ControlPointInfo controlPointInfo, double trackLength, BindableBeatDivisor beatDivisor) { - // Todo: What. - TrackLength ??= musicController.CurrentTrack?.Length ?? 0; + this.beatDivisor = beatDivisor; + + ControlPointInfo = controlPointInfo; + TrackLength = trackLength; + + underlyingClock = new DecoupleableInterpolatingFramedClock(); } /// @@ -148,8 +135,7 @@ namespace osu.Game.Screens.Edit seekTime = timingPoint.Time; // Ensure the sought point is within the boundaries - Debug.Assert(TrackLength != null); - seekTime = Math.Clamp(seekTime, 0, TrackLength.Value); + seekTime = Math.Clamp(seekTime, 0, TrackLength); SeekTo(seekTime); } From 9685df0ecac3c264b6379a240c073473c4d410ac Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 6 Aug 2020 18:22:17 +0900 Subject: [PATCH 0423/1782] Only update key binding on next usage to avoid large blocking calls --- osu.Game/Overlays/Toolbar/ToolbarButton.cs | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/osu.Game/Overlays/Toolbar/ToolbarButton.cs b/osu.Game/Overlays/Toolbar/ToolbarButton.cs index 9fd47fd150..0afc6642b2 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarButton.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Framework.Caching; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -76,7 +77,7 @@ namespace osu.Game.Overlays.Toolbar protected FillFlowContainer Flow; [Resolved] - private KeyBindingStore store { get; set; } + private KeyBindingStore keyBindings { get; set; } protected ToolbarButton() : base(HoverSampleSet.Loud) @@ -159,21 +160,25 @@ namespace osu.Game.Overlays.Toolbar }; } + private readonly Cached tooltipKeyBinding = new Cached(); + [BackgroundDependencyLoader] private void load() { - updateTooltip(); - - store.KeyBindingChanged += updateTooltip; + keyBindings.KeyBindingChanged += () => tooltipKeyBinding.Invalidate(); + updateKeyBindingTooltip(); } - private void updateTooltip() + private void updateKeyBindingTooltip() { - var binding = store.Query().Find(b => (GlobalAction)b.Action == Hotkey); + if (tooltipKeyBinding.IsValid) + return; + var binding = keyBindings.Query().Find(b => (GlobalAction)b.Action == Hotkey); var keyBindingString = binding?.KeyCombination.ReadableString(); - keyBindingTooltip.Text = !string.IsNullOrEmpty(keyBindingString) ? $" ({keyBindingString})" : string.Empty; + + tooltipKeyBinding.Validate(); } protected override bool OnMouseDown(MouseDownEvent e) => true; @@ -187,6 +192,8 @@ namespace osu.Game.Overlays.Toolbar protected override bool OnHover(HoverEvent e) { + updateKeyBindingTooltip(); + HoverBackground.FadeIn(200); tooltipContainer.FadeIn(100); return base.OnHover(e); From 7c3ae4ed4291eeb86f704344956dedfef75e8735 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 6 Aug 2020 18:25:34 +0900 Subject: [PATCH 0424/1782] Remove generics from IApplicableToTrack --- osu.Game/Rulesets/Mods/IApplicableToTrack.cs | 3 +-- osu.Game/Rulesets/Mods/ModDaycore.cs | 7 ++++--- osu.Game/Rulesets/Mods/ModNightcore.cs | 6 +++--- osu.Game/Rulesets/Mods/ModRateAdjust.cs | 5 ++--- osu.Game/Rulesets/Mods/ModTimeRamp.cs | 3 +-- 5 files changed, 11 insertions(+), 13 deletions(-) diff --git a/osu.Game/Rulesets/Mods/IApplicableToTrack.cs b/osu.Game/Rulesets/Mods/IApplicableToTrack.cs index b29ba55942..9b840cea08 100644 --- a/osu.Game/Rulesets/Mods/IApplicableToTrack.cs +++ b/osu.Game/Rulesets/Mods/IApplicableToTrack.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.Audio; using osu.Framework.Audio.Track; namespace osu.Game.Rulesets.Mods @@ -11,6 +10,6 @@ namespace osu.Game.Rulesets.Mods /// public interface IApplicableToTrack : IApplicableMod { - void ApplyToTrack(T track) where T : class, ITrack, IAdjustableAudioComponent; + void ApplyToTrack(ITrack track); } } diff --git a/osu.Game/Rulesets/Mods/ModDaycore.cs b/osu.Game/Rulesets/Mods/ModDaycore.cs index 989978eb35..800312d047 100644 --- a/osu.Game/Rulesets/Mods/ModDaycore.cs +++ b/osu.Game/Rulesets/Mods/ModDaycore.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Audio; +using osu.Framework.Audio.Track; using osu.Framework.Bindables; using osu.Framework.Graphics.Sprites; @@ -26,11 +27,11 @@ namespace osu.Game.Rulesets.Mods }, true); } - public override void ApplyToTrack(T track) + public override void ApplyToTrack(ITrack track) { // base.ApplyToTrack() intentionally not called (different tempo adjustment is applied) - track.AddAdjustment(AdjustableProperty.Frequency, freqAdjust); - track.AddAdjustment(AdjustableProperty.Tempo, tempoAdjust); + (track as IAdjustableAudioComponent)?.AddAdjustment(AdjustableProperty.Frequency, freqAdjust); + (track as IAdjustableAudioComponent)?.AddAdjustment(AdjustableProperty.Tempo, tempoAdjust); } } } diff --git a/osu.Game/Rulesets/Mods/ModNightcore.cs b/osu.Game/Rulesets/Mods/ModNightcore.cs index 70cdaa6345..4932df08f1 100644 --- a/osu.Game/Rulesets/Mods/ModNightcore.cs +++ b/osu.Game/Rulesets/Mods/ModNightcore.cs @@ -38,11 +38,11 @@ namespace osu.Game.Rulesets.Mods }, true); } - public override void ApplyToTrack(T track) + public override void ApplyToTrack(ITrack track) { // base.ApplyToTrack() intentionally not called (different tempo adjustment is applied) - track.AddAdjustment(AdjustableProperty.Frequency, freqAdjust); - track.AddAdjustment(AdjustableProperty.Tempo, tempoAdjust); + (track as IAdjustableAudioComponent)?.AddAdjustment(AdjustableProperty.Frequency, freqAdjust); + (track as IAdjustableAudioComponent)?.AddAdjustment(AdjustableProperty.Tempo, tempoAdjust); } public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) diff --git a/osu.Game/Rulesets/Mods/ModRateAdjust.cs b/osu.Game/Rulesets/Mods/ModRateAdjust.cs index 4aee5affe9..ae7077c67b 100644 --- a/osu.Game/Rulesets/Mods/ModRateAdjust.cs +++ b/osu.Game/Rulesets/Mods/ModRateAdjust.cs @@ -12,10 +12,9 @@ namespace osu.Game.Rulesets.Mods { public abstract BindableNumber SpeedChange { get; } - public virtual void ApplyToTrack(T track) - where T : class, ITrack, IAdjustableAudioComponent + public virtual void ApplyToTrack(ITrack track) { - track.AddAdjustment(AdjustableProperty.Tempo, SpeedChange); + (track as IAdjustableAudioComponent)?.AddAdjustment(AdjustableProperty.Tempo, SpeedChange); } public virtual void ApplyToSample(SampleChannel sample) diff --git a/osu.Game/Rulesets/Mods/ModTimeRamp.cs b/osu.Game/Rulesets/Mods/ModTimeRamp.cs index 4b5241488f..b904cf007b 100644 --- a/osu.Game/Rulesets/Mods/ModTimeRamp.cs +++ b/osu.Game/Rulesets/Mods/ModTimeRamp.cs @@ -51,8 +51,7 @@ namespace osu.Game.Rulesets.Mods AdjustPitch.BindValueChanged(applyPitchAdjustment); } - public void ApplyToTrack(T track) - where T : class, ITrack, IAdjustableAudioComponent + public void ApplyToTrack(ITrack track) { this.track = track; From 2e3ecf71c70c035db37621df04cc289a4b7d7489 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 6 Aug 2020 18:31:08 +0900 Subject: [PATCH 0425/1782] Pass track from Player to components --- .../TestSceneGameplayClockContainer.cs | 7 +++++- .../Gameplay/TestSceneStoryboardSamples.cs | 6 +++-- .../Visual/Gameplay/TestSceneSkipOverlay.cs | 4 +++- osu.Game/Screens/Play/FailAnimation.cs | 16 ++++++-------- .../Screens/Play/GameplayClockContainer.cs | 22 +++++++++---------- osu.Game/Screens/Play/Player.cs | 11 +++++----- 6 files changed, 36 insertions(+), 30 deletions(-) diff --git a/osu.Game.Tests/Gameplay/TestSceneGameplayClockContainer.cs b/osu.Game.Tests/Gameplay/TestSceneGameplayClockContainer.cs index cd3669f160..40f6cecd9a 100644 --- a/osu.Game.Tests/Gameplay/TestSceneGameplayClockContainer.cs +++ b/osu.Game.Tests/Gameplay/TestSceneGameplayClockContainer.cs @@ -19,7 +19,12 @@ namespace osu.Game.Tests.Gameplay { GameplayClockContainer gcc = null; - AddStep("create container", () => Add(gcc = new GameplayClockContainer(CreateWorkingBeatmap(new OsuRuleset().RulesetInfo), Array.Empty(), 0))); + AddStep("create container", () => + { + var working = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo); + Add(gcc = new GameplayClockContainer(working.GetRealTrack(), working, Array.Empty(), 0)); + }); + AddStep("start track", () => gcc.Start()); AddUntilStep("elapsed greater than zero", () => gcc.GameplayClock.ElapsedFrameTime > 0); } diff --git a/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs b/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs index b30870d057..720436fae4 100644 --- a/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs +++ b/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs @@ -59,7 +59,9 @@ namespace osu.Game.Tests.Gameplay AddStep("create container", () => { - Add(gameplayContainer = new GameplayClockContainer(CreateWorkingBeatmap(new OsuRuleset().RulesetInfo), Array.Empty(), 0)); + var working = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo); + + Add(gameplayContainer = new GameplayClockContainer(working.GetRealTrack(), working, Array.Empty(), 0)); gameplayContainer.Add(sample = new DrawableStoryboardSample(new StoryboardSampleInfo(string.Empty, 0, 1)) { @@ -103,7 +105,7 @@ namespace osu.Game.Tests.Gameplay Beatmap.Value = new TestCustomSkinWorkingBeatmap(new OsuRuleset().RulesetInfo, Audio); SelectedMods.Value = new[] { testedMod }; - Add(gameplayContainer = new GameplayClockContainer(Beatmap.Value, SelectedMods.Value, 0)); + Add(gameplayContainer = new GameplayClockContainer(MusicController.CurrentTrack, Beatmap.Value, SelectedMods.Value, 0)); gameplayContainer.Add(sample = new TestDrawableStoryboardSample(new StoryboardSampleInfo("test-sample", 1, 1)) { diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs index 7ed7a116b4..68110d759c 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs @@ -32,7 +32,9 @@ namespace osu.Game.Tests.Visual.Gameplay requestCount = 0; increment = skip_time; - Child = gameplayClockContainer = new GameplayClockContainer(CreateWorkingBeatmap(CreateBeatmap(new OsuRuleset().RulesetInfo)), Array.Empty(), 0) + var working = CreateWorkingBeatmap(CreateBeatmap(new OsuRuleset().RulesetInfo)); + + Child = gameplayClockContainer = new GameplayClockContainer(working.GetRealTrack(), working, Array.Empty(), 0) { RelativeSizeAxes = Axes.Both, Children = new Drawable[] diff --git a/osu.Game/Screens/Play/FailAnimation.cs b/osu.Game/Screens/Play/FailAnimation.cs index 1171d8c3b0..a7bfca612e 100644 --- a/osu.Game/Screens/Play/FailAnimation.cs +++ b/osu.Game/Screens/Play/FailAnimation.cs @@ -8,11 +8,10 @@ using System; using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Audio.Sample; +using osu.Framework.Audio.Track; using osu.Framework.Graphics; -using osu.Framework.Graphics.Audio; using osu.Framework.Utils; using osu.Game.Beatmaps; -using osu.Game.Overlays; using osu.Game.Rulesets.Objects.Drawables; using osuTK; using osuTK.Graphics; @@ -28,24 +27,23 @@ namespace osu.Game.Screens.Play public Action OnComplete; private readonly DrawableRuleset drawableRuleset; + private readonly ITrack track; private readonly BindableDouble trackFreq = new BindableDouble(1); - private DrawableTrack track; - private const float duration = 2500; private SampleChannel failSample; - public FailAnimation(DrawableRuleset drawableRuleset) + public FailAnimation(DrawableRuleset drawableRuleset, ITrack track) { this.drawableRuleset = drawableRuleset; + this.track = track; } [BackgroundDependencyLoader] - private void load(AudioManager audio, IBindable beatmap, MusicController musicController) + private void load(AudioManager audio, IBindable beatmap) { - track = musicController.CurrentTrack; failSample = audio.Samples.Get(@"Gameplay/failsound"); } @@ -69,7 +67,7 @@ namespace osu.Game.Screens.Play Expire(); }); - track.AddAdjustment(AdjustableProperty.Frequency, trackFreq); + (track as IAdjustableAudioComponent)?.AddAdjustment(AdjustableProperty.Frequency, trackFreq); applyToPlayfield(drawableRuleset.Playfield); drawableRuleset.Playfield.HitObjectContainer.FlashColour(Color4.Red, 500); @@ -108,7 +106,7 @@ namespace osu.Game.Screens.Play protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); - track?.RemoveAdjustment(AdjustableProperty.Frequency, trackFreq); + (track as IAdjustableAudioComponent)?.RemoveAdjustment(AdjustableProperty.Frequency, trackFreq); } } } diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index 61272f56ad..c4f368e1f5 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -11,12 +11,10 @@ using osu.Framework.Audio; using osu.Framework.Audio.Track; using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Framework.Graphics.Audio; using osu.Framework.Graphics.Containers; using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Configuration; -using osu.Game.Overlays; using osu.Game.Rulesets.Mods; namespace osu.Game.Screens.Play @@ -29,7 +27,7 @@ namespace osu.Game.Screens.Play private readonly WorkingBeatmap beatmap; private readonly IReadOnlyList mods; - private DrawableTrack track; + private ITrack track; public readonly BindableBool IsPaused = new BindableBool(); @@ -62,11 +60,13 @@ namespace osu.Game.Screens.Play private readonly FramedOffsetClock platformOffsetClock; - public GameplayClockContainer(WorkingBeatmap beatmap, IReadOnlyList mods, double gameplayStartTime) + public GameplayClockContainer(ITrack track, WorkingBeatmap beatmap, IReadOnlyList mods, double gameplayStartTime) { this.beatmap = beatmap; this.mods = mods; this.gameplayStartTime = gameplayStartTime; + this.track = track; + firstHitObjectTime = beatmap.Beatmap.HitObjects.First().StartTime; RelativeSizeAxes = Axes.Both; @@ -96,10 +96,8 @@ namespace osu.Game.Screens.Play private readonly BindableDouble pauseFreqAdjust = new BindableDouble(1); [BackgroundDependencyLoader] - private void load(OsuConfigManager config, MusicController musicController) + private void load(OsuConfigManager config) { - track = musicController.CurrentTrack; - userAudioOffset = config.GetBindable(OsuSetting.AudioOffset); userAudioOffset.BindValueChanged(offset => userOffsetClock.Offset = offset.NewValue, true); @@ -132,7 +130,7 @@ namespace osu.Game.Screens.Play Schedule(() => { - adjustableClock.ChangeSource(track); + adjustableClock.ChangeSource((IAdjustableClock)track); updateRate(); if (!IsPaused.Value) @@ -203,8 +201,8 @@ namespace osu.Game.Screens.Play removeSourceClockAdjustments(); - track = new DrawableTrack(new TrackVirtual(track.Length)); - adjustableClock.ChangeSource(track); + track = new TrackVirtual(track.Length); + adjustableClock.ChangeSource((IAdjustableClock)track); } protected override void Update() @@ -224,8 +222,8 @@ namespace osu.Game.Screens.Play speedAdjustmentsApplied = true; track.ResetSpeedAdjustments(); - track.AddAdjustment(AdjustableProperty.Frequency, pauseFreqAdjust); - track.AddAdjustment(AdjustableProperty.Tempo, UserPlaybackRate); + (track as IAdjustableAudioComponent)?.AddAdjustment(AdjustableProperty.Frequency, pauseFreqAdjust); + (track as IAdjustableAudioComponent)?.AddAdjustment(AdjustableProperty.Tempo, UserPlaybackRate); foreach (var mod in mods.OfType()) mod.ApplyToTrack(track); diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 541275cf55..e92164de7c 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -8,6 +8,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.Graphics; using osu.Framework.Graphics.Containers; @@ -150,7 +151,7 @@ namespace osu.Game.Screens.Play } [BackgroundDependencyLoader] - private void load(AudioManager audio, OsuConfigManager config) + private void load(AudioManager audio, OsuConfigManager config, MusicController musicController) { Mods.Value = base.Mods.Value.Select(m => m.CreateCopy()).ToArray(); @@ -178,7 +179,7 @@ namespace osu.Game.Screens.Play if (!ScoreProcessor.Mode.Disabled) config.BindWith(OsuSetting.ScoreDisplayMode, ScoreProcessor.Mode); - InternalChild = GameplayClockContainer = new GameplayClockContainer(Beatmap.Value, Mods.Value, DrawableRuleset.GameplayStartTime); + InternalChild = GameplayClockContainer = new GameplayClockContainer(musicController.CurrentTrack, Beatmap.Value, Mods.Value, DrawableRuleset.GameplayStartTime); AddInternal(gameplayBeatmap = new GameplayBeatmap(playableBeatmap)); AddInternal(screenSuspension = new ScreenSuspensionHandler(GameplayClockContainer)); @@ -187,7 +188,7 @@ namespace osu.Game.Screens.Play addUnderlayComponents(GameplayClockContainer); addGameplayComponents(GameplayClockContainer, Beatmap.Value, playableBeatmap); - addOverlayComponents(GameplayClockContainer, Beatmap.Value); + addOverlayComponents(GameplayClockContainer, Beatmap.Value, musicController.CurrentTrack); if (!DrawableRuleset.AllowGameplayOverlays) { @@ -264,7 +265,7 @@ namespace osu.Game.Screens.Play }); } - private void addOverlayComponents(Container target, WorkingBeatmap working) + private void addOverlayComponents(Container target, WorkingBeatmap working, ITrack track) { target.AddRange(new[] { @@ -331,7 +332,7 @@ namespace osu.Game.Screens.Play performImmediateExit(); }, }, - failAnimation = new FailAnimation(DrawableRuleset) { OnComplete = onFailComplete, }, + failAnimation = new FailAnimation(DrawableRuleset, track) { OnComplete = onFailComplete, }, }); } From ef689d943aafc413dac909da50d96532816abd6c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 6 Aug 2020 18:54:08 +0900 Subject: [PATCH 0426/1782] Fix intros playing incorrectly --- osu.Game/OsuGame.cs | 4 ++-- osu.Game/Screens/Menu/IntroScreen.cs | 13 +++++++------ osu.Game/Screens/Menu/IntroTriangles.cs | 2 +- osu.Game/Screens/Menu/IntroWelcome.cs | 9 ++------- 4 files changed, 12 insertions(+), 16 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index a41c7b28a5..0049e5a520 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -581,6 +581,8 @@ namespace osu.Game ScreenStack.ScreenPushed += screenPushed; ScreenStack.ScreenExited += screenExited; + loadComponentSingleFile(MusicController, Add); + loadComponentSingleFile(osuLogo, logo => { logoContainer.Add(logo); @@ -602,8 +604,6 @@ namespace osu.Game loadComponentSingleFile(new OnScreenDisplay(), Add, true); - loadComponentSingleFile(MusicController, Add); - loadComponentSingleFile(notifications.With(d => { d.GetToolbarHeight = () => ToolbarOffset; diff --git a/osu.Game/Screens/Menu/IntroScreen.cs b/osu.Game/Screens/Menu/IntroScreen.cs index 030923c228..7e327261ab 100644 --- a/osu.Game/Screens/Menu/IntroScreen.cs +++ b/osu.Game/Screens/Menu/IntroScreen.cs @@ -44,8 +44,7 @@ namespace osu.Game.Screens.Menu private WorkingBeatmap initialBeatmap; - [Resolved] - protected MusicController MusicController { get; private set; } + protected ITrack Track { get; private set; } private readonly BindableDouble exitingVolumeFade = new BindableDouble(1); @@ -62,6 +61,9 @@ namespace osu.Game.Screens.Menu [Resolved] private AudioManager audio { get; set; } + [Resolved] + private MusicController musicController { get; set; } + /// /// Whether the is provided by osu! resources, rather than a user beatmap. /// @@ -113,9 +115,7 @@ namespace osu.Game.Screens.Menu if (setInfo != null) { initialBeatmap = beatmaps.GetWorkingBeatmap(setInfo.Beatmaps[0]); - - // Todo: Wrong. - UsingThemedIntro = MusicController.CurrentTrack?.IsDummyDevice == false; + UsingThemedIntro = initialBeatmap.GetRealTrack().IsDummyDevice == false; } return UsingThemedIntro; @@ -154,7 +154,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 Menu. if (UsingThemedIntro) - MusicController.Play(true); + Track.Restart(); } protected override void LogoArriving(OsuLogo logo, bool resuming) @@ -168,6 +168,7 @@ namespace osu.Game.Screens.Menu if (!resuming) { beatmap.Value = initialBeatmap; + Track = musicController.CurrentTrack; logo.MoveTo(new Vector2(0.5f)); logo.ScaleTo(Vector2.One); diff --git a/osu.Game/Screens/Menu/IntroTriangles.cs b/osu.Game/Screens/Menu/IntroTriangles.cs index e29ea6e743..86f7dbdd6f 100644 --- a/osu.Game/Screens/Menu/IntroTriangles.cs +++ b/osu.Game/Screens/Menu/IntroTriangles.cs @@ -59,7 +59,7 @@ namespace osu.Game.Screens.Menu LoadComponentAsync(new TrianglesIntroSequence(logo, background) { RelativeSizeAxes = Axes.Both, - Clock = new FramedClock(UsingThemedIntro ? MusicController.CurrentTrack : null), + Clock = new FramedClock(UsingThemedIntro ? (IAdjustableClock)Track : null), LoadMenu = LoadMenu }, t => { diff --git a/osu.Game/Screens/Menu/IntroWelcome.cs b/osu.Game/Screens/Menu/IntroWelcome.cs index 85f11eb244..e81646456f 100644 --- a/osu.Game/Screens/Menu/IntroWelcome.cs +++ b/osu.Game/Screens/Menu/IntroWelcome.cs @@ -11,7 +11,6 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; -using osu.Game.Overlays; using osu.Game.Screens.Backgrounds; using osuTK.Graphics; @@ -31,9 +30,6 @@ namespace osu.Game.Screens.Menu Alpha = 0, }; - [Resolved] - private MusicController musicController { get; set; } - private BackgroundScreenDefault background; [BackgroundDependencyLoader] @@ -43,9 +39,6 @@ namespace osu.Game.Screens.Menu welcome = audio.Samples.Get(@"Intro/Welcome/welcome"); pianoReverb = audio.Samples.Get(@"Intro/Welcome/welcome_piano"); - - if (musicController.CurrentTrack != null) - musicController.CurrentTrack.Looping = true; } protected override void LogoArriving(OsuLogo logo, bool resuming) @@ -54,6 +47,8 @@ namespace osu.Game.Screens.Menu if (!resuming) { + Track.Looping = true; + LoadComponentAsync(new WelcomeIntroSequence { RelativeSizeAxes = Axes.Both From f8279dab328d758f2a590924de44a8a8921ca595 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 6 Aug 2020 18:54:14 +0900 Subject: [PATCH 0427/1782] Refactor MainMenu --- osu.Game/Screens/Menu/MainMenu.cs | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index ce48777ce1..ea4347a285 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -170,9 +170,6 @@ namespace osu.Game.Screens.Menu [Resolved] private Storage storage { get; set; } - [Resolved] - private MusicController musicController { get; set; } - public override void OnEntering(IScreen last) { base.OnEntering(last); @@ -180,14 +177,14 @@ namespace osu.Game.Screens.Menu var metadata = Beatmap.Value.Metadata; - if (last is IntroScreen && musicController.TrackLoaded) + if (last is IntroScreen && music.TrackLoaded) { - Debug.Assert(musicController.CurrentTrack != null); + Debug.Assert(music.CurrentTrack != null); - if (!musicController.CurrentTrack.IsRunning) + if (!music.CurrentTrack.IsRunning) { - musicController.CurrentTrack.Seek(metadata.PreviewTime != -1 ? metadata.PreviewTime : 0.4f * musicController.CurrentTrack.Length); - musicController.CurrentTrack.Start(); + music.CurrentTrack.Seek(metadata.PreviewTime != -1 ? metadata.PreviewTime : 0.4f * music.CurrentTrack.Length); + music.CurrentTrack.Start(); } } From adf4f56dce1871816e29bde01e51fe78a77397ab Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 6 Aug 2020 19:01:23 +0900 Subject: [PATCH 0428/1782] Move MusicController to OsuGameBase --- osu.Game/OsuGame.cs | 5 ----- osu.Game/OsuGameBase.cs | 6 ++++++ 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 0049e5a520..cf4610793c 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -538,7 +538,6 @@ namespace osu.Game Container logoContainer; BackButton.Receptor receptor; - dependencies.CacheAs(MusicController = new MusicController()); dependencies.CacheAs(idleTracker = new GameIdleTracker(6000)); AddRange(new Drawable[] @@ -581,8 +580,6 @@ namespace osu.Game ScreenStack.ScreenPushed += screenPushed; ScreenStack.ScreenExited += screenExited; - loadComponentSingleFile(MusicController, Add); - loadComponentSingleFile(osuLogo, logo => { logoContainer.Add(logo); @@ -925,8 +922,6 @@ namespace osu.Game private ScalingContainer screenContainer; - protected MusicController MusicController { get; private set; } - protected override bool OnExiting() { if (ScreenStack.CurrentScreen is Loader) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 24c1f7849c..51b9b7278d 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -30,6 +30,7 @@ using osu.Game.Database; using osu.Game.Input; using osu.Game.Input.Bindings; using osu.Game.IO; +using osu.Game.Overlays; using osu.Game.Resources; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; @@ -73,6 +74,8 @@ namespace osu.Game protected MenuCursorContainer MenuCursorContainer; + protected MusicController MusicController; + private Container content; protected override Container Content => content; @@ -265,6 +268,9 @@ namespace osu.Game dependencies.Cache(previewTrackManager = new PreviewTrackManager()); Add(previewTrackManager); + AddInternal(MusicController = new MusicController()); + dependencies.CacheAs(MusicController); + Ruleset.BindValueChanged(onRulesetChanged); } From 4cfca71d080822734e7f29de27b5273f3304460a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 6 Aug 2020 19:05:15 +0900 Subject: [PATCH 0429/1782] Fix a few test scenes --- osu.Game/Tests/Visual/RateAdjustedBeatmapTestScene.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game/Tests/Visual/RateAdjustedBeatmapTestScene.cs b/osu.Game/Tests/Visual/RateAdjustedBeatmapTestScene.cs index ae4b0ef84a..a6266d210c 100644 --- a/osu.Game/Tests/Visual/RateAdjustedBeatmapTestScene.cs +++ b/osu.Game/Tests/Visual/RateAdjustedBeatmapTestScene.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.Extensions.ObjectExtensions; namespace osu.Game.Tests.Visual @@ -15,7 +16,11 @@ namespace osu.Game.Tests.Visual base.Update(); // note that this will override any mod rate application - MusicController.CurrentTrack.AsNonNull().Tempo.Value = Clock.Rate; + if (MusicController.TrackLoaded) + { + Debug.Assert(MusicController.CurrentTrack != null); + MusicController.CurrentTrack.Tempo.Value = Clock.Rate; + } } } } From d1af1429b3dd8d79ba36bc5d41ac02a75ad86bac Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 6 Aug 2020 19:08:45 +0900 Subject: [PATCH 0430/1782] Fix inspection --- osu.Game/Tests/Visual/RateAdjustedBeatmapTestScene.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Tests/Visual/RateAdjustedBeatmapTestScene.cs b/osu.Game/Tests/Visual/RateAdjustedBeatmapTestScene.cs index a6266d210c..54458716b1 100644 --- a/osu.Game/Tests/Visual/RateAdjustedBeatmapTestScene.cs +++ b/osu.Game/Tests/Visual/RateAdjustedBeatmapTestScene.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System.Diagnostics; -using osu.Framework.Extensions.ObjectExtensions; namespace osu.Game.Tests.Visual { From e3105fd4c80caec86c086e66b5bad55d2f5d29c5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 6 Aug 2020 19:16:26 +0900 Subject: [PATCH 0431/1782] Add more resilient logic for whether to avoid playing SkinnableSound on no volume --- .../Objects/Drawables/DrawableSpinner.cs | 6 ++---- osu.Game/Screens/Play/PauseOverlay.cs | 8 ++------ osu.Game/Skinning/SkinnableSound.cs | 12 +++++++++++- 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index 7363da0de8..b74a9c7197 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -82,8 +82,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables private SkinnableSound spinningSample; - private const float minimum_volume = 0.0001f; - protected override void LoadSamples() { base.LoadSamples(); @@ -100,7 +98,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables AddInternal(spinningSample = new SkinnableSound(clone) { - Volume = { Value = minimum_volume }, + Volume = { Value = 0 }, Looping = true, }); } @@ -118,7 +116,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables } else { - spinningSample?.VolumeTo(minimum_volume, 200).Finally(_ => spinningSample.Stop()); + spinningSample?.VolumeTo(0, 200).Finally(_ => spinningSample.Stop()); } } diff --git a/osu.Game/Screens/Play/PauseOverlay.cs b/osu.Game/Screens/Play/PauseOverlay.cs index fa917cda32..3cdc558951 100644 --- a/osu.Game/Screens/Play/PauseOverlay.cs +++ b/osu.Game/Screens/Play/PauseOverlay.cs @@ -25,8 +25,6 @@ namespace osu.Game.Screens.Play protected override Action BackAction => () => InternalButtons.Children.First().Click(); - private const float minimum_volume = 0.0001f; - [BackgroundDependencyLoader] private void load(OsuColour colours) { @@ -37,10 +35,8 @@ namespace osu.Game.Screens.Play AddInternal(pauseLoop = new SkinnableSound(new SampleInfo("pause-loop")) { Looping = true, + Volume = { Value = 0 } }); - - // SkinnableSound only plays a sound if its aggregate volume is > 0, so the volume must be turned up before playing it - pauseLoop.VolumeTo(minimum_volume); } protected override void PopIn() @@ -55,7 +51,7 @@ namespace osu.Game.Screens.Play { base.PopOut(); - pauseLoop.VolumeTo(minimum_volume, TRANSITION_DURATION, Easing.OutQuad).Finally(_ => pauseLoop.Stop()); + pauseLoop.VolumeTo(0, TRANSITION_DURATION, Easing.OutQuad).Finally(_ => pauseLoop.Stop()); } } } diff --git a/osu.Game/Skinning/SkinnableSound.cs b/osu.Game/Skinning/SkinnableSound.cs index 27f6c37895..8c18e83e92 100644 --- a/osu.Game/Skinning/SkinnableSound.cs +++ b/osu.Game/Skinning/SkinnableSound.cs @@ -28,6 +28,16 @@ namespace osu.Game.Skinning public override bool RemoveWhenNotAlive => false; public override bool RemoveCompletedTransforms => false; + /// + /// Whether to play the underlying sample when aggregate volume is zero. + /// Note that this is checked at the point of calling ; changing the volume post-play will not begin playback. + /// Defaults to false unless . + /// + /// + /// Can serve as an optimisation if it is known ahead-of-time that this behaviour will not negatively affect behaviour. + /// + protected bool SkipPlayWhenZeroVolume => !Looping; + private readonly AudioContainer samplesContainer; public SkinnableSound(ISampleInfo hitSamples) @@ -87,7 +97,7 @@ namespace osu.Game.Skinning { samplesContainer.ForEach(c => { - if (c.AggregateVolume.Value > 0) + if (!SkipPlayWhenZeroVolume || c.AggregateVolume.Value > 0) c.Play(); }); } From f994bf28884b66d7efc4654c50ebbb961640ecb0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 6 Aug 2020 21:34:48 +0900 Subject: [PATCH 0432/1782] 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 924e9c4a16..e5fed09c07 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,6 +52,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 627c2f3d33..18c3052ca3 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -24,7 +24,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index f443937017..b034253d88 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -80,7 +80,7 @@ - + From c84452cfbfdb8e72424702ee1e25cea82aa39606 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 6 Aug 2020 21:53:20 +0900 Subject: [PATCH 0433/1782] Update usages --- osu.Game/Rulesets/UI/DrawableRulesetDependencies.cs | 2 ++ osu.Game/Skinning/SkinnableSound.cs | 9 +++++---- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/osu.Game/Rulesets/UI/DrawableRulesetDependencies.cs b/osu.Game/Rulesets/UI/DrawableRulesetDependencies.cs index 168e937256..83a1077d70 100644 --- a/osu.Game/Rulesets/UI/DrawableRulesetDependencies.cs +++ b/osu.Game/Rulesets/UI/DrawableRulesetDependencies.cs @@ -117,6 +117,8 @@ namespace osu.Game.Rulesets.UI public void RemoveAdjustment(AdjustableProperty type, BindableNumber adjustBindable) => throw new NotSupportedException(); + public void RemoveAllAdjustments(AdjustableProperty type) => throw new NotSupportedException(); + public BindableNumber Volume => throw new NotSupportedException(); public BindableNumber Balance => throw new NotSupportedException(); diff --git a/osu.Game/Skinning/SkinnableSound.cs b/osu.Game/Skinning/SkinnableSound.cs index 27f6c37895..f19aaee821 100644 --- a/osu.Game/Skinning/SkinnableSound.cs +++ b/osu.Game/Skinning/SkinnableSound.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Audio; using osu.Framework.Audio.Track; using osu.Framework.Bindables; using osu.Framework.Extensions.IEnumerableExtensions; @@ -149,28 +150,28 @@ namespace osu.Game.Skinning /// Smoothly adjusts over time. /// /// A to which further transforms can be added. - public TransformSequence VolumeTo(double newVolume, double duration = 0, Easing easing = Easing.None) => + public TransformSequence> VolumeTo(double newVolume, double duration = 0, Easing easing = Easing.None) => samplesContainer.VolumeTo(newVolume, duration, easing); /// /// Smoothly adjusts over time. /// /// A to which further transforms can be added. - public TransformSequence BalanceTo(double newBalance, double duration = 0, Easing easing = Easing.None) => + public TransformSequence> BalanceTo(double newBalance, double duration = 0, Easing easing = Easing.None) => samplesContainer.BalanceTo(newBalance, duration, easing); /// /// Smoothly adjusts over time. /// /// A to which further transforms can be added. - public TransformSequence FrequencyTo(double newFrequency, double duration = 0, Easing easing = Easing.None) => + public TransformSequence> FrequencyTo(double newFrequency, double duration = 0, Easing easing = Easing.None) => samplesContainer.FrequencyTo(newFrequency, duration, easing); /// /// Smoothly adjusts over time. /// /// A to which further transforms can be added. - public TransformSequence TempoTo(double newTempo, double duration = 0, Easing easing = Easing.None) => + public TransformSequence> TempoTo(double newTempo, double duration = 0, Easing easing = Easing.None) => samplesContainer.TempoTo(newTempo, duration, easing); #endregion From bce3f3952fbc1154b74d190b2947624b1558c574 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 7 Aug 2020 16:36:40 +0900 Subject: [PATCH 0434/1782] Split out variable declaration --- osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs b/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs index 9fbd9f50b4..0ab3e8825b 100644 --- a/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs +++ b/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs @@ -30,7 +30,8 @@ namespace osu.Game.Rulesets.Osu.Skinning } private Container circleSprites; - private Sprite hitCircleSprite, hitCircleOverlay; + private Sprite hitCircleSprite; + private Sprite hitCircleOverlay; private SkinnableSpriteText hitCircleText; From f8ef53a62e0f4fecc26a614373749ccf08b14d43 Mon Sep 17 00:00:00 2001 From: Lucas A Date: Fri, 7 Aug 2020 10:17:11 +0200 Subject: [PATCH 0435/1782] Fix tests. --- .../Visual/Gameplay/TestSceneOverlayActivation.cs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneOverlayActivation.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneOverlayActivation.cs index 9e93cf363d..03e1337125 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneOverlayActivation.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneOverlayActivation.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using NUnit.Framework; -using osu.Framework.Allocation; using osu.Game.Configuration; using osu.Game.Overlays; using osu.Game.Rulesets; @@ -13,21 +12,25 @@ namespace osu.Game.Tests.Visual.Gameplay { private OverlayTestPlayer testPlayer; - [Resolved] - private OsuConfigManager config { get; set; } - public override void SetUpSteps() { - AddStep("disable overlay activation during gameplay", () => config.Set(OsuSetting.GameplayDisableOverlayActivation, true)); + AddStep("disable overlay activation during gameplay", () => LocalConfig.Set(OsuSetting.GameplayDisableOverlayActivation, true)); base.SetUpSteps(); } [Test] - public void TestGameplayOverlayActivationSetting() + public void TestGameplayOverlayActivation() { AddAssert("activation mode is disabled", () => testPlayer.OverlayActivationMode == OverlayActivation.Disabled); } + [Test] + public void TestGameplayOverlayActivationDisabled() + { + AddStep("enable overlay activation during gameplay", () => LocalConfig.Set(OsuSetting.GameplayDisableOverlayActivation, false)); + AddAssert("activation mode is user triggered", () => testPlayer.OverlayActivationMode == OverlayActivation.UserTriggered); + } + [Test] public void TestGameplayOverlayActivationPaused() { From 2e0f567d5def9ce469170c00f4d1c7caa4c0f43f Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Fri, 7 Aug 2020 11:33:02 +0300 Subject: [PATCH 0436/1782] Implement HomeNewsPanel component --- .../Visual/Online/TestSceneHomeNewsPanel.cs | 38 +++ osu.Game/Overlays/Dashboard/Home/HomePanel.cs | 58 +++++ .../Dashboard/Home/News/HomeNewsPanel.cs | 240 ++++++++++++++++++ osu.Game/Overlays/News/NewsCard.cs | 34 +-- osu.Game/Overlays/News/NewsPostBackground.cs | 37 +++ 5 files changed, 375 insertions(+), 32 deletions(-) create mode 100644 osu.Game.Tests/Visual/Online/TestSceneHomeNewsPanel.cs create mode 100644 osu.Game/Overlays/Dashboard/Home/HomePanel.cs create mode 100644 osu.Game/Overlays/Dashboard/Home/News/HomeNewsPanel.cs create mode 100644 osu.Game/Overlays/News/NewsPostBackground.cs diff --git a/osu.Game.Tests/Visual/Online/TestSceneHomeNewsPanel.cs b/osu.Game.Tests/Visual/Online/TestSceneHomeNewsPanel.cs new file mode 100644 index 0000000000..262bc51cd8 --- /dev/null +++ b/osu.Game.Tests/Visual/Online/TestSceneHomeNewsPanel.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.Graphics.Containers; +using osu.Framework.Graphics; +using osu.Game.Online.API.Requests.Responses; +using osu.Framework.Allocation; +using osu.Game.Overlays; +using System; +using osu.Game.Overlays.Dashboard.Home.News; + +namespace osu.Game.Tests.Visual.Online +{ + public class TestSceneHomeNewsPanel : OsuTestScene + { + [Cached] + private readonly OverlayColourProvider overlayColour = new OverlayColourProvider(OverlayColourScheme.Purple); + + public TestSceneHomeNewsPanel() + { + Add(new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AutoSizeAxes = Axes.Y, + Width = 500, + Child = new HomeNewsPanel(new APINewsPost + { + Title = "This post has an image which starts with \"/\" and has many authors!", + Preview = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.", + FirstImage = "/help/wiki/shared/news/banners/monthly-beatmapping-contest.png", + PublishedAt = DateTimeOffset.Now, + Slug = "2020-07-16-summer-theme-park-2020-voting-open" + }) + }); + } + } +} diff --git a/osu.Game/Overlays/Dashboard/Home/HomePanel.cs b/osu.Game/Overlays/Dashboard/Home/HomePanel.cs new file mode 100644 index 0000000000..bbe7e411fd --- /dev/null +++ b/osu.Game/Overlays/Dashboard/Home/HomePanel.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.Allocation; +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 osuTK; +using osuTK.Graphics; + +namespace osu.Game.Overlays.Dashboard.Home +{ + public class HomePanel : Container + { + protected override Container Content => content; + + [Resolved] + protected OverlayColourProvider ColourProvider { get; private set; } + + private readonly Container content; + private readonly Box background; + + public HomePanel() + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + Masking = true; + EdgeEffect = new EdgeEffectParameters + { + Colour = Color4.Black.Opacity(0.25f), + Type = EdgeEffectType.Shadow, + Radius = 3, + Offset = new Vector2(0, 1) + }; + + AddRangeInternal(new Drawable[] + { + background = new Box + { + RelativeSizeAxes = Axes.Both + }, + content = new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y + } + }); + } + + [BackgroundDependencyLoader] + private void load() + { + background.Colour = ColourProvider.Background4; + } + } +} diff --git a/osu.Game/Overlays/Dashboard/Home/News/HomeNewsPanel.cs b/osu.Game/Overlays/Dashboard/Home/News/HomeNewsPanel.cs new file mode 100644 index 0000000000..85e31b3034 --- /dev/null +++ b/osu.Game/Overlays/Dashboard/Home/News/HomeNewsPanel.cs @@ -0,0 +1,240 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Platform; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using osu.Game.Online.API.Requests.Responses; +using osu.Game.Overlays.News; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Overlays.Dashboard.Home.News +{ + public class HomeNewsPanel : HomePanel + { + private readonly APINewsPost post; + + public HomeNewsPanel(APINewsPost post) + { + this.post = post; + } + + [BackgroundDependencyLoader] + private void load() + { + Children = new Drawable[] + { + new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + new ClickableNewsBackground(post), + new GridContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + ColumnDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize), + new Dimension() + }, + RowDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize) + }, + Content = new[] + { + new Drawable[] + { + new Container + { + RelativeSizeAxes = Axes.Y, + Width = 80, + Padding = new MarginPadding(10), + Children = new Drawable[] + { + new Box + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + RelativeSizeAxes = Axes.Y, + Width = 1, + Colour = ColourProvider.Light1 + }, + new Container + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + AutoSizeAxes = Axes.Both, + Padding = new MarginPadding { Right = 11 }, + Child = new DateContainer(post.PublishedAt) + } + } + }, + new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding { Right = 10 }, + Children = new Drawable[] + { + new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Margin = new MarginPadding { Top = 5, Bottom = 10 }, + Spacing = new Vector2(0, 10), + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + new TitleLink(post), + new TextFlowContainer(f => + { + f.Font = OsuFont.GetFont(size: 12, weight: FontWeight.Regular); + }) + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Text = post.Preview + } + } + } + } + } + } + } + } + } + } + }; + } + + private class ClickableNewsBackground : OsuHoverContainer + { + private readonly APINewsPost post; + + public ClickableNewsBackground(APINewsPost post) + { + this.post = post; + + RelativeSizeAxes = Axes.X; + Height = 130; + } + + [BackgroundDependencyLoader] + private void load(GameHost host) + { + NewsPostBackground bg; + + Child = new DelayedLoadWrapper(bg = new NewsPostBackground(post.FirstImage) + { + RelativeSizeAxes = Axes.Both, + FillMode = FillMode.Fill, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Alpha = 0 + }) + { + RelativeSizeAxes = Axes.Both + }; + + bg.OnLoadComplete += d => d.FadeIn(250, Easing.In); + + TooltipText = "view in browser"; + Action = () => host.OpenUrlExternally("https://osu.ppy.sh/home/news/" + post.Slug); + + HoverColour = Color4.White; + } + } + + private class TitleLink : OsuHoverContainer + { + private readonly APINewsPost post; + + public TitleLink(APINewsPost post) + { + this.post = post; + + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + } + + [BackgroundDependencyLoader] + private void load(GameHost host) + { + Child = new TextFlowContainer(t => + { + t.Font = OsuFont.GetFont(weight: FontWeight.Bold); + }) + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Text = post.Title + }; + + TooltipText = "view in browser"; + Action = () => host.OpenUrlExternally("https://osu.ppy.sh/home/news/" + post.Slug); + } + } + + private class DateContainer : CompositeDrawable, IHasCustomTooltip + { + public ITooltip GetCustomTooltip() => new DateTooltip(); + + public object TooltipContent => date; + + private readonly DateTimeOffset date; + + public DateContainer(DateTimeOffset date) + { + this.date = date; + } + + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider) + { + AutoSizeAxes = Axes.Both; + InternalChild = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + Children = new Drawable[] + { + new OsuSpriteText + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + Font = OsuFont.GetFont(weight: FontWeight.Bold), // using Bold since there is no 800 weight alternative + Colour = colourProvider.Light1, + Text = date.Day.ToString() + }, + new TextFlowContainer(f => + { + f.Font = OsuFont.GetFont(size: 11, weight: FontWeight.Regular); + f.Colour = colourProvider.Light1; + }) + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + AutoSizeAxes = Axes.Both, + Text = $"{date:MMM yyyy}" + } + } + }; + } + } + } +} diff --git a/osu.Game/Overlays/News/NewsCard.cs b/osu.Game/Overlays/News/NewsCard.cs index 201c3ce826..599b45fa78 100644 --- a/osu.Game/Overlays/News/NewsCard.cs +++ b/osu.Game/Overlays/News/NewsCard.cs @@ -9,8 +9,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.Sprites; -using osu.Framework.Graphics.Textures; using osu.Framework.Input.Events; using osu.Framework.Platform; using osu.Game.Graphics; @@ -48,7 +46,7 @@ namespace osu.Game.Overlays.News Action = () => host.OpenUrlExternally("https://osu.ppy.sh/home/news/" + post.Slug); } - NewsBackground bg; + NewsPostBackground bg; AddRange(new Drawable[] { background = new Box @@ -70,7 +68,7 @@ namespace osu.Game.Overlays.News CornerRadius = 6, Children = new Drawable[] { - new DelayedLoadWrapper(bg = new NewsBackground(post.FirstImage) + new DelayedLoadWrapper(bg = new NewsPostBackground(post.FirstImage) { RelativeSizeAxes = Axes.Both, FillMode = FillMode.Fill, @@ -123,34 +121,6 @@ namespace osu.Game.Overlays.News main.AddText(post.Author, t => t.Font = OsuFont.GetFont(size: 12, weight: FontWeight.SemiBold)); } - [LongRunningLoad] - private class NewsBackground : Sprite - { - private readonly string sourceUrl; - - public NewsBackground(string sourceUrl) - { - this.sourceUrl = sourceUrl; - } - - [BackgroundDependencyLoader] - private void load(LargeTextureStore store) - { - Texture = store.Get(createUrl(sourceUrl)); - } - - private string createUrl(string source) - { - if (string.IsNullOrEmpty(source)) - return "Headers/news"; - - if (source.StartsWith('/')) - return "https://osu.ppy.sh" + source; - - return source; - } - } - private class DateContainer : CircularContainer, IHasCustomTooltip { public ITooltip GetCustomTooltip() => new DateTooltip(); diff --git a/osu.Game/Overlays/News/NewsPostBackground.cs b/osu.Game/Overlays/News/NewsPostBackground.cs new file mode 100644 index 0000000000..386ef7f669 --- /dev/null +++ b/osu.Game/Overlays/News/NewsPostBackground.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.Framework.Allocation; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Textures; + +namespace osu.Game.Overlays.News +{ + [LongRunningLoad] + public class NewsPostBackground : Sprite + { + private readonly string sourceUrl; + + public NewsPostBackground(string sourceUrl) + { + this.sourceUrl = sourceUrl; + } + + [BackgroundDependencyLoader] + private void load(LargeTextureStore store) + { + Texture = store.Get(createUrl(sourceUrl)); + } + + private string createUrl(string source) + { + if (string.IsNullOrEmpty(source)) + return "Headers/news"; + + if (source.StartsWith('/')) + return "https://osu.ppy.sh" + source; + + return source; + } + } +} From 76d35a7667eb082d6917e1e032ab3d9f418b905d Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Fri, 7 Aug 2020 12:59:45 +0300 Subject: [PATCH 0437/1782] Implement HomeNewsGroupPanel --- .../Visual/Online/TestSceneHomeNewsPanel.cs | 38 ++++- osu.Game/Overlays/Dashboard/Home/HomePanel.cs | 7 +- .../Dashboard/Home/News/HomeNewsGroupPanel.cs | 85 ++++++++++ .../Dashboard/Home/News/HomeNewsPanel.cs | 152 ++++-------------- .../Home/News/HomeNewsPanelFooter.cs | 79 +++++++++ .../Home/News/NewsPostDrawableDate.cs | 37 +++++ .../Dashboard/Home/News/NewsTitleLink.cs | 43 +++++ 7 files changed, 311 insertions(+), 130 deletions(-) create mode 100644 osu.Game/Overlays/Dashboard/Home/News/HomeNewsGroupPanel.cs create mode 100644 osu.Game/Overlays/Dashboard/Home/News/HomeNewsPanelFooter.cs create mode 100644 osu.Game/Overlays/Dashboard/Home/News/NewsPostDrawableDate.cs create mode 100644 osu.Game/Overlays/Dashboard/Home/News/NewsTitleLink.cs diff --git a/osu.Game.Tests/Visual/Online/TestSceneHomeNewsPanel.cs b/osu.Game.Tests/Visual/Online/TestSceneHomeNewsPanel.cs index 262bc51cd8..78d77c9e97 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneHomeNewsPanel.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneHomeNewsPanel.cs @@ -8,6 +8,8 @@ using osu.Framework.Allocation; using osu.Game.Overlays; using System; using osu.Game.Overlays.Dashboard.Home.News; +using osuTK; +using System.Collections.Generic; namespace osu.Game.Tests.Visual.Online { @@ -18,20 +20,40 @@ namespace osu.Game.Tests.Visual.Online public TestSceneHomeNewsPanel() { - Add(new Container + Add(new FillFlowContainer { Anchor = Anchor.Centre, Origin = Anchor.Centre, AutoSizeAxes = Axes.Y, Width = 500, - Child = new HomeNewsPanel(new APINewsPost + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 5), + Children = new Drawable[] { - Title = "This post has an image which starts with \"/\" and has many authors!", - Preview = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.", - FirstImage = "/help/wiki/shared/news/banners/monthly-beatmapping-contest.png", - PublishedAt = DateTimeOffset.Now, - Slug = "2020-07-16-summer-theme-park-2020-voting-open" - }) + new HomeNewsPanel(new APINewsPost + { + Title = "This post has an image which starts with \"/\" and has many authors!", + Preview = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.", + FirstImage = "/help/wiki/shared/news/banners/monthly-beatmapping-contest.png", + PublishedAt = DateTimeOffset.Now, + Slug = "2020-07-16-summer-theme-park-2020-voting-open" + }), + new HomeNewsGroupPanel(new List + { + new APINewsPost + { + Title = "Title 1", + Slug = "2020-07-16-summer-theme-park-2020-voting-open", + PublishedAt = DateTimeOffset.Now, + }, + new APINewsPost + { + Title = "Title of this post is Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.", + Slug = "2020-07-16-summer-theme-park-2020-voting-open", + PublishedAt = DateTimeOffset.Now, + } + }) + } }); } } diff --git a/osu.Game/Overlays/Dashboard/Home/HomePanel.cs b/osu.Game/Overlays/Dashboard/Home/HomePanel.cs index bbe7e411fd..ce053cd4ec 100644 --- a/osu.Game/Overlays/Dashboard/Home/HomePanel.cs +++ b/osu.Game/Overlays/Dashboard/Home/HomePanel.cs @@ -16,9 +16,6 @@ namespace osu.Game.Overlays.Dashboard.Home { protected override Container Content => content; - [Resolved] - protected OverlayColourProvider ColourProvider { get; private set; } - private readonly Container content; private readonly Box background; @@ -50,9 +47,9 @@ namespace osu.Game.Overlays.Dashboard.Home } [BackgroundDependencyLoader] - private void load() + private void load(OverlayColourProvider colourProvider) { - background.Colour = ColourProvider.Background4; + background.Colour = colourProvider.Background4; } } } diff --git a/osu.Game/Overlays/Dashboard/Home/News/HomeNewsGroupPanel.cs b/osu.Game/Overlays/Dashboard/Home/News/HomeNewsGroupPanel.cs new file mode 100644 index 0000000000..cd1c5393c5 --- /dev/null +++ b/osu.Game/Overlays/Dashboard/Home/News/HomeNewsGroupPanel.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 osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Graphics; +using osu.Game.Online.API.Requests.Responses; + +namespace osu.Game.Overlays.Dashboard.Home.News +{ + public class HomeNewsGroupPanel : HomePanel + { + private readonly List posts; + + public HomeNewsGroupPanel(List posts) + { + this.posts = posts; + } + + [BackgroundDependencyLoader] + private void load() + { + Content.Padding = new MarginPadding { Vertical = 5 }; + + Child = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Children = posts.Select(p => new CollapsedNewsPanel(p)).ToArray() + }; + } + + private class CollapsedNewsPanel : HomeNewsPanelFooter + { + public CollapsedNewsPanel(APINewsPost post) + : base(post) + { + } + + protected override Drawable CreateContent(APINewsPost post) => new NewsTitleLink(post); + + protected override NewsPostDrawableDate CreateDate(DateTimeOffset date) => new Date(date); + + private class Date : NewsPostDrawableDate + { + public Date(DateTimeOffset date) + : base(date) + { + } + + protected override Drawable CreateDate(DateTimeOffset date, OverlayColourProvider colourProvider) + { + var drawableDate = new TextFlowContainer(t => + { + t.Colour = colourProvider.Light1; + }) + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Margin = new MarginPadding { Vertical = 5 } + }; + + drawableDate.AddText($"{date:dd} ", t => + { + t.Font = OsuFont.GetFont(size: 14, weight: FontWeight.Bold); + }); + + drawableDate.AddText($"{date:MMM}", t => + { + t.Font = OsuFont.GetFont(size: 14, weight: FontWeight.Regular); + }); + + return drawableDate; + } + } + } + } +} diff --git a/osu.Game/Overlays/Dashboard/Home/News/HomeNewsPanel.cs b/osu.Game/Overlays/Dashboard/Home/News/HomeNewsPanel.cs index 85e31b3034..3548b7c88d 100644 --- a/osu.Game/Overlays/Dashboard/Home/News/HomeNewsPanel.cs +++ b/osu.Game/Overlays/Dashboard/Home/News/HomeNewsPanel.cs @@ -5,8 +5,6 @@ using System; 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.Platform; using osu.Game.Graphics; using osu.Game.Graphics.Containers; @@ -40,81 +38,7 @@ namespace osu.Game.Overlays.Dashboard.Home.News Children = new Drawable[] { new ClickableNewsBackground(post), - new GridContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - ColumnDimensions = new[] - { - new Dimension(GridSizeMode.AutoSize), - new Dimension() - }, - RowDimensions = new[] - { - new Dimension(GridSizeMode.AutoSize) - }, - Content = new[] - { - new Drawable[] - { - new Container - { - RelativeSizeAxes = Axes.Y, - Width = 80, - Padding = new MarginPadding(10), - Children = new Drawable[] - { - new Box - { - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - RelativeSizeAxes = Axes.Y, - Width = 1, - Colour = ColourProvider.Light1 - }, - new Container - { - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - AutoSizeAxes = Axes.Both, - Padding = new MarginPadding { Right = 11 }, - Child = new DateContainer(post.PublishedAt) - } - } - }, - new Container - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Padding = new MarginPadding { Right = 10 }, - Children = new Drawable[] - { - new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Margin = new MarginPadding { Top = 5, Bottom = 10 }, - Spacing = new Vector2(0, 10), - Direction = FillDirection.Vertical, - Children = new Drawable[] - { - new TitleLink(post), - new TextFlowContainer(f => - { - f.Font = OsuFont.GetFont(size: 12, weight: FontWeight.Regular); - }) - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Text = post.Preview - } - } - } - } - } - } - } - } + new Footer(post) } } }; @@ -158,54 +82,48 @@ namespace osu.Game.Overlays.Dashboard.Home.News } } - private class TitleLink : OsuHoverContainer + private class Footer : HomeNewsPanelFooter { - private readonly APINewsPost post; + protected override float BarPading => 10; - public TitleLink(APINewsPost post) + public Footer(APINewsPost post) + : base(post) { - this.post = post; - - RelativeSizeAxes = Axes.X; - AutoSizeAxes = Axes.Y; } - [BackgroundDependencyLoader] - private void load(GameHost host) + protected override NewsPostDrawableDate CreateDate(DateTimeOffset date) => new Date(date); + + protected override Drawable CreateContent(APINewsPost post) => new FillFlowContainer { - Child = new TextFlowContainer(t => + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Margin = new MarginPadding { Top = 5, Bottom = 10 }, + Spacing = new Vector2(0, 10), + Direction = FillDirection.Vertical, + Children = new Drawable[] { - t.Font = OsuFont.GetFont(weight: FontWeight.Bold); - }) + new NewsTitleLink(post), + new TextFlowContainer(f => + { + f.Font = OsuFont.GetFont(size: 12, weight: FontWeight.Regular); + }) + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Text = post.Preview + } + } + }; + + private class Date : NewsPostDrawableDate + { + public Date(DateTimeOffset date) + : base(date) { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Text = post.Title - }; + Margin = new MarginPadding { Top = 10 }; + } - TooltipText = "view in browser"; - Action = () => host.OpenUrlExternally("https://osu.ppy.sh/home/news/" + post.Slug); - } - } - - private class DateContainer : CompositeDrawable, IHasCustomTooltip - { - public ITooltip GetCustomTooltip() => new DateTooltip(); - - public object TooltipContent => date; - - private readonly DateTimeOffset date; - - public DateContainer(DateTimeOffset date) - { - this.date = date; - } - - [BackgroundDependencyLoader] - private void load(OverlayColourProvider colourProvider) - { - AutoSizeAxes = Axes.Both; - InternalChild = new FillFlowContainer + protected override Drawable CreateDate(DateTimeOffset date, OverlayColourProvider colourProvider) => new FillFlowContainer { AutoSizeAxes = Axes.Both, Direction = FillDirection.Vertical, @@ -219,7 +137,7 @@ namespace osu.Game.Overlays.Dashboard.Home.News Origin = Anchor.TopRight, Font = OsuFont.GetFont(weight: FontWeight.Bold), // using Bold since there is no 800 weight alternative Colour = colourProvider.Light1, - Text = date.Day.ToString() + Text = $"{date: dd}" }, new TextFlowContainer(f => { diff --git a/osu.Game/Overlays/Dashboard/Home/News/HomeNewsPanelFooter.cs b/osu.Game/Overlays/Dashboard/Home/News/HomeNewsPanelFooter.cs new file mode 100644 index 0000000000..591f53ac4a --- /dev/null +++ b/osu.Game/Overlays/Dashboard/Home/News/HomeNewsPanelFooter.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 osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Online.API.Requests.Responses; + +namespace osu.Game.Overlays.Dashboard.Home.News +{ + public abstract class HomeNewsPanelFooter : CompositeDrawable + { + protected virtual float BarPading { get; } = 0; + + private readonly APINewsPost post; + + protected HomeNewsPanelFooter(APINewsPost post) + { + this.post = post; + } + + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider) + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + InternalChild = new GridContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + RowDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize) + }, + ColumnDimensions = new[] + { + new Dimension(GridSizeMode.Absolute, size: 60), + new Dimension(GridSizeMode.Absolute, size: 20), + new Dimension() + }, + Content = new[] + { + new Drawable[] + { + CreateDate(post.PublishedAt), + new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Vertical = BarPading }, + Child = new Box + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopRight, + Width = 1, + RelativeSizeAxes = Axes.Y, + Colour = colourProvider.Light1 + } + }, + new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Padding = new MarginPadding { Right = 10 }, + Child = CreateContent(post) + } + } + } + }; + } + + protected abstract NewsPostDrawableDate CreateDate(DateTimeOffset date); + + protected abstract Drawable CreateContent(APINewsPost post); + } +} diff --git a/osu.Game/Overlays/Dashboard/Home/News/NewsPostDrawableDate.cs b/osu.Game/Overlays/Dashboard/Home/News/NewsPostDrawableDate.cs new file mode 100644 index 0000000000..8ba58e27a7 --- /dev/null +++ b/osu.Game/Overlays/Dashboard/Home/News/NewsPostDrawableDate.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 System; +using osu.Framework.Allocation; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; +using osu.Game.Graphics; +using osu.Framework.Graphics; + +namespace osu.Game.Overlays.Dashboard.Home.News +{ + public abstract class NewsPostDrawableDate : CompositeDrawable, IHasCustomTooltip + { + public ITooltip GetCustomTooltip() => new DateTooltip(); + + public object TooltipContent => date; + + private readonly DateTimeOffset date; + + protected NewsPostDrawableDate(DateTimeOffset date) + { + this.date = date; + } + + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider) + { + AutoSizeAxes = Axes.Both; + Anchor = Anchor.TopRight; + Origin = Anchor.TopRight; + InternalChild = CreateDate(date, colourProvider); + } + + protected abstract Drawable CreateDate(DateTimeOffset date, OverlayColourProvider colourProvider); + } +} diff --git a/osu.Game/Overlays/Dashboard/Home/News/NewsTitleLink.cs b/osu.Game/Overlays/Dashboard/Home/News/NewsTitleLink.cs new file mode 100644 index 0000000000..da98c92bbe --- /dev/null +++ b/osu.Game/Overlays/Dashboard/Home/News/NewsTitleLink.cs @@ -0,0 +1,43 @@ +// 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.Platform; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Online.API.Requests.Responses; + +namespace osu.Game.Overlays.Dashboard.Home.News +{ + public class NewsTitleLink : OsuHoverContainer + { + private readonly APINewsPost post; + + public NewsTitleLink(APINewsPost post) + { + this.post = post; + + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + } + + [BackgroundDependencyLoader] + private void load(GameHost host) + { + Child = new TextFlowContainer(t => + { + t.Font = OsuFont.GetFont(weight: FontWeight.Bold); + }) + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Text = post.Title + }; + + TooltipText = "view in browser"; + Action = () => host.OpenUrlExternally("https://osu.ppy.sh/home/news/" + post.Slug); + } + } +} From cddd4f0a97842eefd68ba1ddda18f81ec5ca09b3 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Fri, 7 Aug 2020 13:18:31 +0300 Subject: [PATCH 0438/1782] Implement HomeShowMoreNewsPanel --- .../Visual/Online/TestSceneHomeNewsPanel.cs | 3 +- .../Home/News/HomeShowMoreNewsPanel.cs | 51 +++++++++++++++++++ 2 files changed, 53 insertions(+), 1 deletion(-) create mode 100644 osu.Game/Overlays/Dashboard/Home/News/HomeShowMoreNewsPanel.cs diff --git a/osu.Game.Tests/Visual/Online/TestSceneHomeNewsPanel.cs b/osu.Game.Tests/Visual/Online/TestSceneHomeNewsPanel.cs index 78d77c9e97..b1c0c5adcd 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneHomeNewsPanel.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneHomeNewsPanel.cs @@ -52,7 +52,8 @@ namespace osu.Game.Tests.Visual.Online Slug = "2020-07-16-summer-theme-park-2020-voting-open", PublishedAt = DateTimeOffset.Now, } - }) + }), + new HomeShowMoreNewsPanel() } }); } diff --git a/osu.Game/Overlays/Dashboard/Home/News/HomeShowMoreNewsPanel.cs b/osu.Game/Overlays/Dashboard/Home/News/HomeShowMoreNewsPanel.cs new file mode 100644 index 0000000000..abb4bd7969 --- /dev/null +++ b/osu.Game/Overlays/Dashboard/Home/News/HomeShowMoreNewsPanel.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 System.Collections.Generic; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using osuTK.Graphics; + +namespace osu.Game.Overlays.Dashboard.Home.News +{ + public class HomeShowMoreNewsPanel : OsuHoverContainer + { + protected override IEnumerable EffectTargets => new[] { text }; + + [Resolved(canBeNull: true)] + private NewsOverlay overlay { get; set; } + + private OsuSpriteText text; + + public HomeShowMoreNewsPanel() + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + } + + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider) + { + Child = new HomePanel + { + Child = text = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Margin = new MarginPadding { Vertical = 20 }, + Text = "see more" + } + }; + + IdleColour = colourProvider.Light1; + HoverColour = Color4.White; + + Action = () => + { + overlay?.ShowFrontPage(); + }; + } + } +} From 61b632516eb73c43918697a9e408b5d75d04ab4d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 7 Aug 2020 19:43:16 +0900 Subject: [PATCH 0439/1782] Ensure CurrentTrack is never null --- osu.Game.Tests/Visual/Editing/TimelineTestScene.cs | 3 --- .../Visual/Navigation/TestSceneScreenNavigation.cs | 4 ++-- .../Visual/UserInterface/TestSceneBeatSyncedContainer.cs | 2 +- osu.Game/Overlays/MusicController.cs | 6 ++---- .../Edit/Components/Timelines/Summary/Parts/TimelinePart.cs | 1 - .../Edit/Compose/Components/Timeline/TimelineTickDisplay.cs | 2 +- osu.Game/Screens/Menu/LogoVisualisation.cs | 4 ++-- osu.Game/Screens/Menu/MainMenu.cs | 2 -- osu.Game/Tests/Visual/OsuTestScene.cs | 3 --- osu.Game/Tests/Visual/RateAdjustedBeatmapTestScene.cs | 3 --- 10 files changed, 8 insertions(+), 22 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TimelineTestScene.cs b/osu.Game.Tests/Visual/Editing/TimelineTestScene.cs index 4113bdddf8..347b5757c8 100644 --- a/osu.Game.Tests/Visual/Editing/TimelineTestScene.cs +++ b/osu.Game.Tests/Visual/Editing/TimelineTestScene.cs @@ -95,10 +95,7 @@ namespace osu.Game.Tests.Visual.Editing base.Update(); if (musicController.TrackLoaded) - { - Debug.Assert(musicController.CurrentTrack != null); marker.X = (float)(editorClock.CurrentTime / musicController.CurrentTrack.Length); - } } } diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs index 946bc2a175..e5d862cfc7 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs @@ -136,8 +136,8 @@ namespace osu.Game.Tests.Visual.Navigation AddUntilStep("Wait for music controller", () => Game.MusicController.IsLoaded); AddStep("Seek close to end", () => { - Game.MusicController.SeekTo(MusicController.CurrentTrack.AsNonNull().Length - 1000); - MusicController.CurrentTrack.AsNonNull().Completed += () => trackCompleted = true; + Game.MusicController.SeekTo(MusicController.CurrentTrack.Length - 1000); + MusicController.CurrentTrack.Completed += () => trackCompleted = true; }); AddUntilStep("Track was completed", () => trackCompleted); diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs index ac743d76df..3cccfa992e 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs @@ -169,7 +169,7 @@ namespace osu.Game.Tests.Visual.UserInterface if (timingPoints.Count == 0) return 0; if (timingPoints[^1] == current) - return (int)Math.Ceiling((musicController.CurrentTrack.AsNonNull().Length - current.Time) / current.BeatLength); + return (int)Math.Ceiling((musicController.CurrentTrack.Length - current.Time) / current.BeatLength); return (int)Math.Ceiling((getNextTimingPoint(current).Time - current.Time) / current.BeatLength); } diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index 6adfa1817e..7e3bb1ce89 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; -using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Audio.Track; using osu.Framework.Bindables; @@ -65,8 +64,7 @@ namespace osu.Game.Overlays [Resolved(canBeNull: true)] private OnScreenDisplay onScreenDisplay { get; set; } - [CanBeNull] - public DrawableTrack CurrentTrack { get; private set; } + public DrawableTrack CurrentTrack { get; private set; } = new DrawableTrack(new TrackVirtual(1000)); private IBindable> managerUpdated; private IBindable> managerRemoved; @@ -312,7 +310,7 @@ namespace osu.Game.Overlays current = beatmap.NewValue; - if (!beatmap.OldValue.BeatmapInfo.AudioEquals(current?.BeatmapInfo)) + if (CurrentTrack == null || !beatmap.OldValue.BeatmapInfo.AudioEquals(current?.BeatmapInfo)) changeTrack(); TrackChanged?.Invoke(current, direction); 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 24fb855009..7085c8b020 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/TimelinePart.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/TimelinePart.cs @@ -58,7 +58,6 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts return; } - Debug.Assert(musicController.CurrentTrack != null); content.RelativeChildSize = new Vector2((float)Math.Max(1, musicController.CurrentTrack.Length), 1); } diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs index ceb0275a13..1ce33f221a 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs @@ -44,7 +44,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline for (var i = 0; i < beatmap.ControlPointInfo.TimingPoints.Count; i++) { var point = beatmap.ControlPointInfo.TimingPoints[i]; - var until = i + 1 < beatmap.ControlPointInfo.TimingPoints.Count ? beatmap.ControlPointInfo.TimingPoints[i + 1].Time : musicController.CurrentTrack.AsNonNull().Length; + var until = i + 1 < beatmap.ControlPointInfo.TimingPoints.Count ? beatmap.ControlPointInfo.TimingPoints[i + 1].Time : musicController.CurrentTrack.Length; int beat = 0; diff --git a/osu.Game/Screens/Menu/LogoVisualisation.cs b/osu.Game/Screens/Menu/LogoVisualisation.cs index 7a1ff4fa06..974b704200 100644 --- a/osu.Game/Screens/Menu/LogoVisualisation.cs +++ b/osu.Game/Screens/Menu/LogoVisualisation.cs @@ -109,14 +109,14 @@ namespace osu.Game.Screens.Menu private void updateAmplitudes() { var effect = beatmap.Value.BeatmapLoaded && musicController.TrackLoaded - ? beatmap.Value.Beatmap?.ControlPointInfo.EffectPointAt(musicController.CurrentTrack.AsNonNull().CurrentTime) + ? beatmap.Value.Beatmap?.ControlPointInfo.EffectPointAt(musicController.CurrentTrack.CurrentTime) : null; for (int i = 0; i < temporalAmplitudes.Length; i++) temporalAmplitudes[i] = 0; if (musicController.TrackLoaded) - addAmplitudesFromSource(musicController.CurrentTrack.AsNonNull()); + addAmplitudesFromSource(musicController.CurrentTrack); foreach (var source in amplitudeSources) addAmplitudesFromSource(source); diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index ea4347a285..518277bce3 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -179,8 +179,6 @@ namespace osu.Game.Screens.Menu if (last is IntroScreen && music.TrackLoaded) { - Debug.Assert(music.CurrentTrack != null); - if (!music.CurrentTrack.IsRunning) { music.CurrentTrack.Seek(metadata.PreviewTime != -1 ? metadata.PreviewTime : 0.4f * music.CurrentTrack.Length); diff --git a/osu.Game/Tests/Visual/OsuTestScene.cs b/osu.Game/Tests/Visual/OsuTestScene.cs index f2b9388fdc..af7579aafb 100644 --- a/osu.Game/Tests/Visual/OsuTestScene.cs +++ b/osu.Game/Tests/Visual/OsuTestScene.cs @@ -170,10 +170,7 @@ namespace osu.Game.Tests.Visual rulesetDependencies?.Dispose(); if (MusicController?.TrackLoaded == true) - { - Debug.Assert(MusicController.CurrentTrack != null); MusicController.CurrentTrack.Stop(); - } if (contextFactory.IsValueCreated) contextFactory.Value.ResetDatabase(); diff --git a/osu.Game/Tests/Visual/RateAdjustedBeatmapTestScene.cs b/osu.Game/Tests/Visual/RateAdjustedBeatmapTestScene.cs index 54458716b1..027259d4f0 100644 --- a/osu.Game/Tests/Visual/RateAdjustedBeatmapTestScene.cs +++ b/osu.Game/Tests/Visual/RateAdjustedBeatmapTestScene.cs @@ -16,10 +16,7 @@ namespace osu.Game.Tests.Visual // note that this will override any mod rate application if (MusicController.TrackLoaded) - { - Debug.Assert(MusicController.CurrentTrack != null); MusicController.CurrentTrack.Tempo.Value = Clock.Rate; - } } } } From 5002d69f6973ab9753997dd28255c90a90de270a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 7 Aug 2020 20:51:56 +0900 Subject: [PATCH 0440/1782] Update inspections --- .../TestSceneHoldNoteInput.cs | 2 +- .../TestSceneOutOfOrderHits.cs | 2 +- .../TestSceneSliderInput.cs | 2 +- .../Skins/TestSceneBeatmapSkinResources.cs | 2 +- .../Visual/Editing/TimelineTestScene.cs | 1 - .../Visual/Gameplay/TestScenePause.cs | 2 +- .../Visual/Gameplay/TestScenePlayerLoader.cs | 4 +-- .../Visual/Gameplay/TestSceneStoryboard.cs | 4 +-- .../Visual/Menus/TestSceneIntroWelcome.cs | 2 +- .../Navigation/TestSceneScreenNavigation.cs | 9 +++--- .../TestSceneBeatSyncedContainer.cs | 1 - .../TestSceneNowPlayingOverlay.cs | 4 +-- osu.Game/Audio/PreviewTrackManager.cs | 2 +- osu.Game/Overlays/Music/PlaylistOverlay.cs | 6 ++-- osu.Game/Overlays/MusicController.cs | 31 +++++++------------ .../Edit/Components/PlaybackControl.cs | 4 +-- .../Timelines/Summary/Parts/TimelinePart.cs | 1 - .../Timeline/TimelineTickDisplay.cs | 1 - osu.Game/Screens/Edit/Editor.cs | 2 +- osu.Game/Screens/Menu/LogoVisualisation.cs | 1 - osu.Game/Screens/Menu/MainMenu.cs | 1 - osu.Game/Screens/Menu/OsuLogo.cs | 2 +- .../Multi/Match/Components/ReadyButton.cs | 2 +- osu.Game/Screens/Multi/Multiplayer.cs | 16 +++------- osu.Game/Tests/Visual/OsuTestScene.cs | 1 - .../Visual/RateAdjustedBeatmapTestScene.cs | 2 -- 26 files changed, 42 insertions(+), 65 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs index 98669efb10..19b69bac6d 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs @@ -343,7 +343,7 @@ namespace osu.Game.Rulesets.Mania.Tests judgementResults = new List(); }); - AddUntilStep("Beatmap at 0", () => MusicController.CurrentTrack?.CurrentTime == 0); + AddUntilStep("Beatmap at 0", () => MusicController.CurrentTrack.CurrentTime == 0); AddUntilStep("Wait until player is loaded", () => currentPlayer.IsCurrentScreen()); AddUntilStep("Wait for completion", () => currentPlayer.ScoreProcessor.HasCompleted.Value); } diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneOutOfOrderHits.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneOutOfOrderHits.cs index e5be778527..744ad46c28 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneOutOfOrderHits.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneOutOfOrderHits.cs @@ -385,7 +385,7 @@ namespace osu.Game.Rulesets.Osu.Tests judgementResults = new List(); }); - AddUntilStep("Beatmap at 0", () => MusicController.CurrentTrack?.CurrentTime == 0); + AddUntilStep("Beatmap at 0", () => MusicController.CurrentTrack.CurrentTime == 0); AddUntilStep("Wait until player is loaded", () => currentPlayer.IsCurrentScreen()); AddUntilStep("Wait for completion", () => currentPlayer.ScoreProcessor.HasCompleted.Value); } diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs index c9d13d3976..1690f648f9 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs @@ -366,7 +366,7 @@ namespace osu.Game.Rulesets.Osu.Tests judgementResults = new List(); }); - AddUntilStep("Beatmap at 0", () => MusicController.CurrentTrack?.CurrentTime == 0); + AddUntilStep("Beatmap at 0", () => MusicController.CurrentTrack.CurrentTime == 0); AddUntilStep("Wait until player is loaded", () => currentPlayer.IsCurrentScreen()); AddUntilStep("Wait for completion", () => currentPlayer.ScoreProcessor.HasCompleted.Value); } diff --git a/osu.Game.Tests/Skins/TestSceneBeatmapSkinResources.cs b/osu.Game.Tests/Skins/TestSceneBeatmapSkinResources.cs index 08a4e27ff7..2866692be4 100644 --- a/osu.Game.Tests/Skins/TestSceneBeatmapSkinResources.cs +++ b/osu.Game.Tests/Skins/TestSceneBeatmapSkinResources.cs @@ -31,6 +31,6 @@ namespace osu.Game.Tests.Skins public void TestRetrieveOggSample() => AddAssert("sample is non-null", () => beatmap.Skin.GetSample(new SampleInfo("sample")) != null); [Test] - public void TestRetrieveOggTrack() => AddAssert("track is non-null", () => MusicController.CurrentTrack?.IsDummyDevice == false); + public void TestRetrieveOggTrack() => AddAssert("track is non-null", () => MusicController.CurrentTrack.IsDummyDevice == false); } } diff --git a/osu.Game.Tests/Visual/Editing/TimelineTestScene.cs b/osu.Game.Tests/Visual/Editing/TimelineTestScene.cs index 347b5757c8..4988a09650 100644 --- a/osu.Game.Tests/Visual/Editing/TimelineTestScene.cs +++ b/osu.Game.Tests/Visual/Editing/TimelineTestScene.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.Allocation; using osu.Framework.Audio; using osu.Framework.Graphics; diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs index e500b451f0..e7dd586f4e 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs @@ -288,7 +288,7 @@ namespace osu.Game.Tests.Visual.Gameplay private void confirmNoTrackAdjustments() { - AddAssert("track has no adjustments", () => MusicController.CurrentTrack?.AggregateFrequency.Value == 1); + AddAssert("track has no adjustments", () => MusicController.CurrentTrack.AggregateFrequency.Value == 1); } private void restart() => AddStep("restart", () => Player.Restart()); diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs index c72ab7d3d1..c4882046de 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs @@ -77,12 +77,12 @@ namespace osu.Game.Tests.Visual.Gameplay { AddStep("load dummy beatmap", () => ResetPlayer(false, () => SelectedMods.Value = new[] { new OsuModNightcore() })); AddUntilStep("wait for current", () => loader.IsCurrentScreen()); - AddAssert("mod rate applied", () => MusicController.CurrentTrack?.Rate != 1); + AddAssert("mod rate applied", () => MusicController.CurrentTrack.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", () => MusicController.CurrentTrack?.Rate != 1); + AddAssert("mod rate still applied", () => MusicController.CurrentTrack.Rate != 1); } [Test] diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboard.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboard.cs index c7a012a03f..3d2dd8a0c5 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboard.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboard.cs @@ -87,9 +87,9 @@ namespace osu.Game.Tests.Visual.Gameplay private void restart() { - MusicController.CurrentTrack?.Reset(); + MusicController.CurrentTrack.Reset(); loadStoryboard(Beatmap.Value); - MusicController.CurrentTrack?.Start(); + MusicController.CurrentTrack.Start(); } private void loadStoryboard(WorkingBeatmap working) diff --git a/osu.Game.Tests/Visual/Menus/TestSceneIntroWelcome.cs b/osu.Game.Tests/Visual/Menus/TestSceneIntroWelcome.cs index a88704c831..29be250b12 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneIntroWelcome.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneIntroWelcome.cs @@ -16,7 +16,7 @@ namespace osu.Game.Tests.Visual.Menus { AddUntilStep("wait for load", () => MusicController.TrackLoaded); - AddAssert("check if menu music loops", () => MusicController.CurrentTrack?.Looping == true); + AddAssert("check if menu music loops", () => MusicController.CurrentTrack.Looping); } } } diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs index e5d862cfc7..d2c71c1d17 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs @@ -4,7 +4,6 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; -using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics.Containers; using osu.Framework.Screens; using osu.Game.Beatmaps; @@ -62,12 +61,12 @@ namespace osu.Game.Tests.Visual.Navigation AddUntilStep("wait for fail", () => player.HasFailed); AddUntilStep("wait for track stop", () => !MusicController.IsPlaying); - AddAssert("Ensure time before preview point", () => MusicController.CurrentTrack?.CurrentTime < beatmap().Metadata.PreviewTime); + AddAssert("Ensure time before preview point", () => MusicController.CurrentTrack.CurrentTime < beatmap().Metadata.PreviewTime); pushEscape(); AddUntilStep("wait for track playing", () => MusicController.IsPlaying); - AddAssert("Ensure time wasn't reset to preview point", () => MusicController.CurrentTrack?.CurrentTime < beatmap().Metadata.PreviewTime); + AddAssert("Ensure time wasn't reset to preview point", () => MusicController.CurrentTrack.CurrentTime < beatmap().Metadata.PreviewTime); } [Test] @@ -77,11 +76,11 @@ namespace osu.Game.Tests.Visual.Navigation PushAndConfirm(() => songSelect = new TestSongSelect()); - AddUntilStep("wait for no track", () => MusicController.CurrentTrack?.IsDummyDevice == true); + AddUntilStep("wait for no track", () => MusicController.CurrentTrack.IsDummyDevice); AddStep("return to menu", () => songSelect.Exit()); - AddUntilStep("wait for track", () => MusicController.CurrentTrack?.IsDummyDevice == false && MusicController.IsPlaying); + AddUntilStep("wait for track", () => MusicController.CurrentTrack.IsDummyDevice == false && MusicController.IsPlaying); } [Test] diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs index 3cccfa992e..127915c6c6 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs @@ -8,7 +8,6 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio.Track; using osu.Framework.Extensions.Color4Extensions; -using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneNowPlayingOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneNowPlayingOverlay.cs index 0161ec0c56..cadecbbef0 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneNowPlayingOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneNowPlayingOverlay.cs @@ -80,12 +80,12 @@ namespace osu.Game.Tests.Visual.UserInterface AddStep("Store track", () => currentBeatmap = Beatmap.Value); AddStep(@"Seek track to 6 second", () => musicController.SeekTo(6000)); - AddUntilStep(@"Wait for current time to update", () => musicController.CurrentTrack?.CurrentTime > 5000); + AddUntilStep(@"Wait for current time to update", () => musicController.CurrentTrack.CurrentTime > 5000); AddStep(@"Set previous", () => musicController.PreviousTrack()); AddAssert(@"Check beatmap didn't change", () => currentBeatmap == Beatmap.Value); - AddUntilStep("Wait for current time to update", () => musicController.CurrentTrack?.CurrentTime < 5000); + AddUntilStep("Wait for current time to update", () => musicController.CurrentTrack.CurrentTime < 5000); AddStep(@"Set previous", () => musicController.PreviousTrack()); AddAssert(@"Check beatmap did change", () => currentBeatmap != Beatmap.Value); diff --git a/osu.Game/Audio/PreviewTrackManager.cs b/osu.Game/Audio/PreviewTrackManager.cs index 862be41c1a..1c68ce71d4 100644 --- a/osu.Game/Audio/PreviewTrackManager.cs +++ b/osu.Game/Audio/PreviewTrackManager.cs @@ -48,7 +48,7 @@ namespace osu.Game.Audio track.Started += () => Schedule(() => { - CurrentTrack?.Stop(); + CurrentTrack.Stop(); CurrentTrack = track; audio.Tracks.AddAdjustment(AdjustableProperty.Volume, muteBindable); }); diff --git a/osu.Game/Overlays/Music/PlaylistOverlay.cs b/osu.Game/Overlays/Music/PlaylistOverlay.cs index b9a58c37cb..7471e31923 100644 --- a/osu.Game/Overlays/Music/PlaylistOverlay.cs +++ b/osu.Game/Overlays/Music/PlaylistOverlay.cs @@ -85,7 +85,7 @@ namespace osu.Game.Overlays.Music if (toSelect != null) { beatmap.Value = beatmaps.GetWorkingBeatmap(toSelect); - musicController.CurrentTrack?.Restart(); + musicController.CurrentTrack.Restart(); } }; } @@ -119,12 +119,12 @@ namespace osu.Game.Overlays.Music { if (set.ID == (beatmap.Value?.BeatmapSetInfo?.ID ?? -1)) { - musicController.CurrentTrack?.Seek(0); + musicController.CurrentTrack.Seek(0); return; } beatmap.Value = beatmaps.GetWorkingBeatmap(set.Beatmaps.First()); - musicController.CurrentTrack?.Restart(); + musicController.CurrentTrack.Restart(); } } diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index 7e3bb1ce89..3e93ae2ccd 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -3,8 +3,8 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.Linq; +using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Audio.Track; using osu.Framework.Bindables; @@ -64,6 +64,7 @@ namespace osu.Game.Overlays [Resolved(canBeNull: true)] private OnScreenDisplay onScreenDisplay { get; set; } + [NotNull] public DrawableTrack CurrentTrack { get; private set; } = new DrawableTrack(new TrackVirtual(1000)); private IBindable> managerUpdated; @@ -102,12 +103,12 @@ namespace osu.Game.Overlays /// /// Returns whether the beatmap track is playing. /// - public bool IsPlaying => CurrentTrack?.IsRunning ?? false; + public bool IsPlaying => CurrentTrack.IsRunning; /// /// Returns whether the beatmap track is loaded. /// - public bool TrackLoaded => CurrentTrack?.IsLoaded == true; + public bool TrackLoaded => CurrentTrack.IsLoaded; private void beatmapUpdated(ValueChangedEvent> weakSet) { @@ -140,7 +141,7 @@ namespace osu.Game.Overlays seekDelegate = Schedule(() => { if (!beatmap.Disabled) - CurrentTrack?.Seek(position); + CurrentTrack.Seek(position); }); } @@ -152,7 +153,7 @@ namespace osu.Game.Overlays { if (IsUserPaused) return; - if (CurrentTrack == null || CurrentTrack.IsDummyDevice) + if (CurrentTrack.IsDummyDevice) { if (beatmap.Disabled) return; @@ -173,9 +174,6 @@ namespace osu.Game.Overlays { IsUserPaused = false; - if (CurrentTrack == null) - return false; - if (restart) CurrentTrack.Restart(); else if (!IsPlaying) @@ -190,7 +188,7 @@ namespace osu.Game.Overlays public void Stop() { IsUserPaused = true; - if (CurrentTrack?.IsRunning == true) + if (CurrentTrack.IsRunning) CurrentTrack.Stop(); } @@ -200,7 +198,7 @@ namespace osu.Game.Overlays /// Whether the operation was successful. public bool TogglePause() { - if (CurrentTrack?.IsRunning == true) + if (CurrentTrack.IsRunning) Stop(); else Play(); @@ -222,7 +220,7 @@ namespace osu.Game.Overlays if (beatmap.Disabled) return PreviousTrackResult.None; - var currentTrackPosition = CurrentTrack?.CurrentTime; + var currentTrackPosition = CurrentTrack.CurrentTime; if (currentTrackPosition >= restart_cutoff_point) { @@ -276,7 +274,7 @@ namespace osu.Game.Overlays { // if not scheduled, the previously track will be stopped one frame later (see ScheduleAfterChildren logic in GameBase). // we probably want to move this to a central method for switching to a new working beatmap in the future. - Schedule(() => CurrentTrack?.Restart()); + Schedule(() => CurrentTrack.Restart()); } private WorkingBeatmap current; @@ -310,7 +308,7 @@ namespace osu.Game.Overlays current = beatmap.NewValue; - if (CurrentTrack == null || !beatmap.OldValue.BeatmapInfo.AudioEquals(current?.BeatmapInfo)) + if (CurrentTrack.IsDummyDevice || !beatmap.OldValue.BeatmapInfo.AudioEquals(current?.BeatmapInfo)) changeTrack(); TrackChanged?.Invoke(current, direction); @@ -322,7 +320,7 @@ namespace osu.Game.Overlays private void changeTrack() { - CurrentTrack?.Expire(); + CurrentTrack.Expire(); CurrentTrack = null; if (current != null) @@ -340,8 +338,6 @@ namespace osu.Game.Overlays if (current != workingBeatmap) return; - Debug.Assert(CurrentTrack != null); - if (!CurrentTrack.Looping && !beatmap.Disabled) NextTrack(); } @@ -366,9 +362,6 @@ namespace osu.Game.Overlays public void ResetTrackAdjustments() { - if (CurrentTrack == null) - return; - CurrentTrack.ResetSpeedAdjustments(); if (allowRateAdjustments) diff --git a/osu.Game/Screens/Edit/Components/PlaybackControl.cs b/osu.Game/Screens/Edit/Components/PlaybackControl.cs index 412efe266c..5bafc120af 100644 --- a/osu.Game/Screens/Edit/Components/PlaybackControl.cs +++ b/osu.Game/Screens/Edit/Components/PlaybackControl.cs @@ -66,12 +66,12 @@ namespace osu.Game.Screens.Edit.Components } }; - musicController.CurrentTrack?.AddAdjustment(AdjustableProperty.Tempo, tempo); + musicController.CurrentTrack.AddAdjustment(AdjustableProperty.Tempo, tempo); } protected override void Dispose(bool isDisposing) { - musicController?.CurrentTrack?.RemoveAdjustment(AdjustableProperty.Tempo, tempo); + musicController?.CurrentTrack.RemoveAdjustment(AdjustableProperty.Tempo, tempo); base.Dispose(isDisposing); } 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 7085c8b020..c8a470c58a 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/TimelinePart.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/TimelinePart.cs @@ -2,7 +2,6 @@ // 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 osuTK; diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs index 1ce33f221a..cb122c590e 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs @@ -3,7 +3,6 @@ using System.Linq; using osu.Framework.Allocation; -using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Game.Graphics; using osu.Game.Overlays; diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 79b13a7eac..9f2009b415 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -84,7 +84,7 @@ namespace osu.Game.Screens.Edit // Todo: should probably be done at a DrawableRuleset level to share logic with Player. var sourceClock = (IAdjustableClock)musicController.CurrentTrack ?? new StopwatchClock(); - clock = new EditorClock(Beatmap.Value, musicController.CurrentTrack?.Length ?? 0, beatDivisor) { IsCoupled = false }; + clock = new EditorClock(Beatmap.Value, musicController.CurrentTrack.Length, beatDivisor) { IsCoupled = false }; clock.ChangeSource(sourceClock); dependencies.CacheAs(clock); diff --git a/osu.Game/Screens/Menu/LogoVisualisation.cs b/osu.Game/Screens/Menu/LogoVisualisation.cs index 974b704200..4d95ee9b7b 100644 --- a/osu.Game/Screens/Menu/LogoVisualisation.cs +++ b/osu.Game/Screens/Menu/LogoVisualisation.cs @@ -19,7 +19,6 @@ using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Track; using osu.Framework.Bindables; -using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Utils; using osu.Game.Overlays; diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index 518277bce3..8837a49772 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.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 System.Linq; using osuTK; using osuTK.Graphics; diff --git a/osu.Game/Screens/Menu/OsuLogo.cs b/osu.Game/Screens/Menu/OsuLogo.cs index f028f9b229..4515ee8ed0 100644 --- a/osu.Game/Screens/Menu/OsuLogo.cs +++ b/osu.Game/Screens/Menu/OsuLogo.cs @@ -330,7 +330,7 @@ namespace osu.Game.Screens.Menu const float velocity_adjust_cutoff = 0.98f; const float paused_velocity = 0.5f; - if (musicController.CurrentTrack?.IsRunning == true) + if (musicController.CurrentTrack.IsRunning) { var maxAmplitude = lastBeatIndex >= 0 ? musicController.CurrentTrack.CurrentAmplitudes.Maximum : 0; logoAmplitudeContainer.Scale = new Vector2((float)Interpolation.Damp(logoAmplitudeContainer.Scale.X, 1 - Math.Max(0, maxAmplitude - scale_adjust_cutoff) * 0.04f, 0.9f, Time.Elapsed)); diff --git a/osu.Game/Screens/Multi/Match/Components/ReadyButton.cs b/osu.Game/Screens/Multi/Match/Components/ReadyButton.cs index c7dc20ff23..384d3bd5a5 100644 --- a/osu.Game/Screens/Multi/Match/Components/ReadyButton.cs +++ b/osu.Game/Screens/Multi/Match/Components/ReadyButton.cs @@ -104,7 +104,7 @@ namespace osu.Game.Screens.Multi.Match.Components return; } - bool hasEnoughTime = musicController.CurrentTrack != null && DateTimeOffset.UtcNow.AddSeconds(30).AddMilliseconds(musicController.CurrentTrack.Length) < endDate.Value; + bool hasEnoughTime = DateTimeOffset.UtcNow.AddSeconds(30).AddMilliseconds(musicController.CurrentTrack.Length) < endDate.Value; Enabled.Value = hasBeatmap && hasEnoughTime; } diff --git a/osu.Game/Screens/Multi/Multiplayer.cs b/osu.Game/Screens/Multi/Multiplayer.cs index e068899c7b..1a39d80f8d 100644 --- a/osu.Game/Screens/Multi/Multiplayer.cs +++ b/osu.Game/Screens/Multi/Multiplayer.cs @@ -343,13 +343,10 @@ namespace osu.Game.Screens.Multi { if (screenStack.CurrentScreen is MatchSubScreen) { - if (musicController.CurrentTrack != null) - { - musicController.CurrentTrack.RestartPoint = Beatmap.Value.Metadata.PreviewTime; - musicController.CurrentTrack.Looping = true; + musicController.CurrentTrack.RestartPoint = Beatmap.Value.Metadata.PreviewTime; + musicController.CurrentTrack.Looping = true; - musicController.EnsurePlayingSomething(); - } + musicController.EnsurePlayingSomething(); } else { @@ -359,11 +356,8 @@ namespace osu.Game.Screens.Multi private void cancelLooping() { - if (musicController.CurrentTrack != null) - { - musicController.CurrentTrack.Looping = false; - musicController.CurrentTrack.RestartPoint = 0; - } + musicController.CurrentTrack.Looping = false; + musicController.CurrentTrack.RestartPoint = 0; } protected override void Dispose(bool isDisposing) diff --git a/osu.Game/Tests/Visual/OsuTestScene.cs b/osu.Game/Tests/Visual/OsuTestScene.cs index af7579aafb..b0d15bf442 100644 --- a/osu.Game/Tests/Visual/OsuTestScene.cs +++ b/osu.Game/Tests/Visual/OsuTestScene.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.IO; using System.Linq; using System.Threading.Tasks; diff --git a/osu.Game/Tests/Visual/RateAdjustedBeatmapTestScene.cs b/osu.Game/Tests/Visual/RateAdjustedBeatmapTestScene.cs index 027259d4f0..7651285970 100644 --- a/osu.Game/Tests/Visual/RateAdjustedBeatmapTestScene.cs +++ b/osu.Game/Tests/Visual/RateAdjustedBeatmapTestScene.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.Diagnostics; - namespace osu.Game.Tests.Visual { /// From 028040344a9c2bfcc1cd25d3efad2e8dcf651207 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 7 Aug 2020 21:07:59 +0900 Subject: [PATCH 0441/1782] Fix test scene using local beatmap --- osu.Game.Tests/Skins/TestSceneBeatmapSkinResources.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Skins/TestSceneBeatmapSkinResources.cs b/osu.Game.Tests/Skins/TestSceneBeatmapSkinResources.cs index 2866692be4..03faee9ad2 100644 --- a/osu.Game.Tests/Skins/TestSceneBeatmapSkinResources.cs +++ b/osu.Game.Tests/Skins/TestSceneBeatmapSkinResources.cs @@ -18,17 +18,15 @@ namespace osu.Game.Tests.Skins [Resolved] private BeatmapManager beatmaps { get; set; } - private WorkingBeatmap beatmap; - [BackgroundDependencyLoader] private void load() { var imported = beatmaps.Import(new ZipArchiveReader(TestResources.OpenResource("Archives/ogg-beatmap.osz"))).Result; - beatmap = beatmaps.GetWorkingBeatmap(imported.Beatmaps[0]); + Beatmap.Value = beatmaps.GetWorkingBeatmap(imported.Beatmaps[0]); } [Test] - public void TestRetrieveOggSample() => AddAssert("sample is non-null", () => beatmap.Skin.GetSample(new SampleInfo("sample")) != null); + public void TestRetrieveOggSample() => AddAssert("sample is non-null", () => Beatmap.Value.Skin.GetSample(new SampleInfo("sample")) != null); [Test] public void TestRetrieveOggTrack() => AddAssert("track is non-null", () => MusicController.CurrentTrack.IsDummyDevice == false); From 961c6dab541c379e30cd4afe837f5bbfb265096b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 7 Aug 2020 21:08:03 +0900 Subject: [PATCH 0442/1782] Fix more inspections --- osu.Game/Screens/Edit/Editor.cs | 2 +- osu.Game/Tests/Visual/EditorClockTestScene.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 9f2009b415..1a7d76ba8f 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -83,7 +83,7 @@ namespace osu.Game.Screens.Edit 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)musicController.CurrentTrack ?? new StopwatchClock(); + var sourceClock = (IAdjustableClock)musicController.CurrentTrack; clock = new EditorClock(Beatmap.Value, musicController.CurrentTrack.Length, beatDivisor) { IsCoupled = false }; clock.ChangeSource(sourceClock); diff --git a/osu.Game/Tests/Visual/EditorClockTestScene.cs b/osu.Game/Tests/Visual/EditorClockTestScene.cs index 780b4f1b3a..1009151ac4 100644 --- a/osu.Game/Tests/Visual/EditorClockTestScene.cs +++ b/osu.Game/Tests/Visual/EditorClockTestScene.cs @@ -44,7 +44,7 @@ namespace osu.Game.Tests.Visual private void beatmapChanged(ValueChangedEvent e) { Clock.ControlPointInfo = e.NewValue.Beatmap.ControlPointInfo; - Clock.ChangeSource((IAdjustableClock)MusicController.CurrentTrack ?? new StopwatchClock()); + Clock.ChangeSource((IAdjustableClock)MusicController.CurrentTrack); Clock.ProcessFrame(); } From b08ebe6f81f9d596920ba602e04a1df9cb90798a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 7 Aug 2020 21:14:45 +0900 Subject: [PATCH 0443/1782] More inspections (rider is broken) --- osu.Game/Screens/Edit/Editor.cs | 4 +--- osu.Game/Tests/Visual/EditorClockTestScene.cs | 3 +-- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 1a7d76ba8f..78fa6c74b0 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -14,7 +14,6 @@ using osu.Framework.Bindables; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; using osu.Framework.Platform; -using osu.Framework.Timing; using osu.Game.Graphics.UserInterface; using osu.Game.Screens.Edit.Components; using osu.Game.Screens.Edit.Components.Menus; @@ -83,9 +82,8 @@ namespace osu.Game.Screens.Edit 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)musicController.CurrentTrack; clock = new EditorClock(Beatmap.Value, musicController.CurrentTrack.Length, beatDivisor) { IsCoupled = false }; - clock.ChangeSource(sourceClock); + clock.ChangeSource(musicController.CurrentTrack); dependencies.CacheAs(clock); AddInternal(clock); diff --git a/osu.Game/Tests/Visual/EditorClockTestScene.cs b/osu.Game/Tests/Visual/EditorClockTestScene.cs index 1009151ac4..59c9329d37 100644 --- a/osu.Game/Tests/Visual/EditorClockTestScene.cs +++ b/osu.Game/Tests/Visual/EditorClockTestScene.cs @@ -4,7 +4,6 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Input.Events; -using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Screens.Edit; @@ -44,7 +43,7 @@ namespace osu.Game.Tests.Visual private void beatmapChanged(ValueChangedEvent e) { Clock.ControlPointInfo = e.NewValue.Beatmap.ControlPointInfo; - Clock.ChangeSource((IAdjustableClock)MusicController.CurrentTrack); + Clock.ChangeSource(MusicController.CurrentTrack); Clock.ProcessFrame(); } From 08820c62ec6ab6121b60c59ad8e89f3dfbcef3f5 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 7 Aug 2020 21:36:02 +0900 Subject: [PATCH 0444/1782] Add back removed nullcheck --- osu.Game/Audio/PreviewTrackManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Audio/PreviewTrackManager.cs b/osu.Game/Audio/PreviewTrackManager.cs index 1c68ce71d4..862be41c1a 100644 --- a/osu.Game/Audio/PreviewTrackManager.cs +++ b/osu.Game/Audio/PreviewTrackManager.cs @@ -48,7 +48,7 @@ namespace osu.Game.Audio track.Started += () => Schedule(() => { - CurrentTrack.Stop(); + CurrentTrack?.Stop(); CurrentTrack = track; audio.Tracks.AddAdjustment(AdjustableProperty.Volume, muteBindable); }); From b6fb7a0d39e6a5667be3fa45d90a3f2a5072d5ed Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 7 Aug 2020 22:05:58 +0900 Subject: [PATCH 0445/1782] Fix possibly setting null track --- .../Visual/Online/TestSceneNowPlayingCommand.cs | 2 +- osu.Game/Beatmaps/DummyWorkingBeatmap.cs | 3 ++- osu.Game/Overlays/MusicController.cs | 8 +++----- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneNowPlayingCommand.cs b/osu.Game.Tests/Visual/Online/TestSceneNowPlayingCommand.cs index 103308d34d..9662bd65b4 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneNowPlayingCommand.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneNowPlayingCommand.cs @@ -59,7 +59,7 @@ namespace osu.Game.Tests.Visual.Online { AddStep("Set activity", () => API.Activity.Value = new UserActivity.InLobby()); - AddStep("Set beatmap", () => Beatmap.Value = new DummyWorkingBeatmap(null, null) + AddStep("Set beatmap", () => Beatmap.Value = new DummyWorkingBeatmap(Audio, null) { BeatmapInfo = { OnlineBeatmapID = hasOnlineId ? 1234 : (int?)null } }); diff --git a/osu.Game/Beatmaps/DummyWorkingBeatmap.cs b/osu.Game/Beatmaps/DummyWorkingBeatmap.cs index 8080e94075..ca801cf745 100644 --- a/osu.Game/Beatmaps/DummyWorkingBeatmap.cs +++ b/osu.Game/Beatmaps/DummyWorkingBeatmap.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using JetBrains.Annotations; using osu.Framework.Audio; using osu.Framework.Audio.Track; using osu.Framework.Extensions.IEnumerableExtensions; @@ -19,7 +20,7 @@ namespace osu.Game.Beatmaps { private readonly TextureStore textures; - public DummyWorkingBeatmap(AudioManager audio, TextureStore textures) + public DummyWorkingBeatmap([NotNull] AudioManager audio, TextureStore textures) : base(new BeatmapInfo { Metadata = new BeatmapMetadata diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index 3e93ae2ccd..2aed46a1d0 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -321,15 +321,13 @@ namespace osu.Game.Overlays private void changeTrack() { CurrentTrack.Expire(); - CurrentTrack = null; + CurrentTrack = new DrawableTrack(new TrackVirtual(1000)); if (current != null) - { CurrentTrack = new DrawableTrack(current.GetRealTrack()); - CurrentTrack.Completed += () => onTrackCompleted(current); - AddInternal(CurrentTrack); - } + CurrentTrack.Completed += () => onTrackCompleted(current); + AddInternal(CurrentTrack); } private void onTrackCompleted(WorkingBeatmap workingBeatmap) From d1765c8a45e3940974c35ec2c75366c14a96a4e1 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 7 Aug 2020 22:06:04 +0900 Subject: [PATCH 0446/1782] Fix using the wrong music controller instance --- .../Navigation/TestSceneScreenNavigation.cs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs index d2c71c1d17..0f06010a6a 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs @@ -60,13 +60,13 @@ namespace osu.Game.Tests.Visual.Navigation AddUntilStep("wait for player", () => (player = Game.ScreenStack.CurrentScreen as Player) != null); AddUntilStep("wait for fail", () => player.HasFailed); - AddUntilStep("wait for track stop", () => !MusicController.IsPlaying); - AddAssert("Ensure time before preview point", () => MusicController.CurrentTrack.CurrentTime < beatmap().Metadata.PreviewTime); + AddUntilStep("wait for track stop", () => !Game.MusicController.IsPlaying); + AddAssert("Ensure time before preview point", () => Game.MusicController.CurrentTrack.CurrentTime < beatmap().Metadata.PreviewTime); pushEscape(); - AddUntilStep("wait for track playing", () => MusicController.IsPlaying); - AddAssert("Ensure time wasn't reset to preview point", () => MusicController.CurrentTrack.CurrentTime < beatmap().Metadata.PreviewTime); + AddUntilStep("wait for track playing", () => Game.MusicController.IsPlaying); + AddAssert("Ensure time wasn't reset to preview point", () => Game.MusicController.CurrentTrack.CurrentTime < beatmap().Metadata.PreviewTime); } [Test] @@ -76,11 +76,11 @@ namespace osu.Game.Tests.Visual.Navigation PushAndConfirm(() => songSelect = new TestSongSelect()); - AddUntilStep("wait for no track", () => MusicController.CurrentTrack.IsDummyDevice); + AddUntilStep("wait for no track", () => Game.MusicController.CurrentTrack.IsDummyDevice); AddStep("return to menu", () => songSelect.Exit()); - AddUntilStep("wait for track", () => MusicController.CurrentTrack.IsDummyDevice == false && MusicController.IsPlaying); + AddUntilStep("wait for track", () => Game.MusicController.CurrentTrack.IsDummyDevice == false && Game.MusicController.IsPlaying); } [Test] @@ -135,12 +135,12 @@ namespace osu.Game.Tests.Visual.Navigation AddUntilStep("Wait for music controller", () => Game.MusicController.IsLoaded); AddStep("Seek close to end", () => { - Game.MusicController.SeekTo(MusicController.CurrentTrack.Length - 1000); - MusicController.CurrentTrack.Completed += () => trackCompleted = true; + Game.MusicController.SeekTo(Game.MusicController.CurrentTrack.Length - 1000); + Game.MusicController.CurrentTrack.Completed += () => trackCompleted = true; }); AddUntilStep("Track was completed", () => trackCompleted); - AddUntilStep("Track was restarted", () => MusicController.IsPlaying); + AddUntilStep("Track was restarted", () => Game.MusicController.IsPlaying); } private void pushEscape() => From e87f50f74f28ca8d5b5c6a0189b3ec2be21a1360 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 7 Aug 2020 22:31:41 +0900 Subject: [PATCH 0447/1782] Rename method --- osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs | 2 +- osu.Game.Tests/Gameplay/TestSceneGameplayClockContainer.cs | 2 +- osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs | 2 +- osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs | 2 +- osu.Game.Tests/WaveformTestBeatmap.cs | 2 +- osu.Game/Beatmaps/BeatmapManager.cs | 2 +- osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs | 2 +- osu.Game/Beatmaps/DummyWorkingBeatmap.cs | 2 +- osu.Game/Beatmaps/IWorkingBeatmap.cs | 5 +++++ osu.Game/Beatmaps/WorkingBeatmap.cs | 4 ++-- osu.Game/Overlays/MusicController.cs | 2 +- osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs | 2 +- osu.Game/Screens/Menu/IntroScreen.cs | 2 +- osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs | 2 +- osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs | 2 +- osu.Game/Tests/Visual/OsuTestScene.cs | 2 +- 16 files changed, 21 insertions(+), 16 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs index 30331e98d2..4a11e1785b 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs @@ -106,7 +106,7 @@ namespace osu.Game.Tests.Beatmaps.Formats protected override Texture GetBackground() => throw new NotImplementedException(); - protected override Track GetTrack() => throw new NotImplementedException(); + protected override Track GetBeatmapTrack() => throw new NotImplementedException(); } } } diff --git a/osu.Game.Tests/Gameplay/TestSceneGameplayClockContainer.cs b/osu.Game.Tests/Gameplay/TestSceneGameplayClockContainer.cs index 40f6cecd9a..bb60ae73db 100644 --- a/osu.Game.Tests/Gameplay/TestSceneGameplayClockContainer.cs +++ b/osu.Game.Tests/Gameplay/TestSceneGameplayClockContainer.cs @@ -22,7 +22,7 @@ namespace osu.Game.Tests.Gameplay AddStep("create container", () => { var working = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo); - Add(gcc = new GameplayClockContainer(working.GetRealTrack(), working, Array.Empty(), 0)); + Add(gcc = new GameplayClockContainer(working.GetTrack(), working, Array.Empty(), 0)); }); AddStep("start track", () => gcc.Start()); diff --git a/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs b/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs index 720436fae4..360e7eccdc 100644 --- a/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs +++ b/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs @@ -61,7 +61,7 @@ namespace osu.Game.Tests.Gameplay { var working = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo); - Add(gameplayContainer = new GameplayClockContainer(working.GetRealTrack(), working, Array.Empty(), 0)); + Add(gameplayContainer = new GameplayClockContainer(working.GetTrack(), working, Array.Empty(), 0)); gameplayContainer.Add(sample = new DrawableStoryboardSample(new StoryboardSampleInfo(string.Empty, 0, 1)) { diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs index 68110d759c..58fd760fc3 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs @@ -34,7 +34,7 @@ namespace osu.Game.Tests.Visual.Gameplay var working = CreateWorkingBeatmap(CreateBeatmap(new OsuRuleset().RulesetInfo)); - Child = gameplayClockContainer = new GameplayClockContainer(working.GetRealTrack(), working, Array.Empty(), 0) + Child = gameplayClockContainer = new GameplayClockContainer(working.GetTrack(), working, Array.Empty(), 0) { RelativeSizeAxes = Axes.Both, Children = new Drawable[] diff --git a/osu.Game.Tests/WaveformTestBeatmap.cs b/osu.Game.Tests/WaveformTestBeatmap.cs index 90c91eb007..7dc5ce1d7f 100644 --- a/osu.Game.Tests/WaveformTestBeatmap.cs +++ b/osu.Game.Tests/WaveformTestBeatmap.cs @@ -52,7 +52,7 @@ namespace osu.Game.Tests protected override Waveform GetWaveform() => new Waveform(trackStore.GetStream(firstAudioFile)); - protected override Track GetTrack() => trackStore.Get(firstAudioFile); + protected override Track GetBeatmapTrack() => trackStore.Get(firstAudioFile); private string firstAudioFile { diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index f22f41531a..e001185da9 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -476,7 +476,7 @@ namespace osu.Game.Beatmaps protected override IBeatmap GetBeatmap() => beatmap; protected override Texture GetBackground() => null; - protected override Track GetTrack() => null; + protected override Track GetBeatmapTrack() => null; } } diff --git a/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs b/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs index a54d46c1b1..f1289cd3aa 100644 --- a/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs @@ -70,7 +70,7 @@ namespace osu.Game.Beatmaps } } - protected override Track GetTrack() + protected override Track GetBeatmapTrack() { try { diff --git a/osu.Game/Beatmaps/DummyWorkingBeatmap.cs b/osu.Game/Beatmaps/DummyWorkingBeatmap.cs index ca801cf745..af2a2ac250 100644 --- a/osu.Game/Beatmaps/DummyWorkingBeatmap.cs +++ b/osu.Game/Beatmaps/DummyWorkingBeatmap.cs @@ -45,7 +45,7 @@ namespace osu.Game.Beatmaps protected override Texture GetBackground() => textures?.Get(@"Backgrounds/bg4"); - protected override Track GetTrack() => GetVirtualTrack(); + protected override Track GetBeatmapTrack() => GetVirtualTrack(); private class DummyRulesetInfo : RulesetInfo { diff --git a/osu.Game/Beatmaps/IWorkingBeatmap.cs b/osu.Game/Beatmaps/IWorkingBeatmap.cs index 086b7502a2..e020625b99 100644 --- a/osu.Game/Beatmaps/IWorkingBeatmap.cs +++ b/osu.Game/Beatmaps/IWorkingBeatmap.cs @@ -54,5 +54,10 @@ namespace osu.Game.Beatmaps /// The converted . /// If could not be converted to . IBeatmap GetPlayableBeatmap(RulesetInfo ruleset, IReadOnlyList mods = null, TimeSpan? timeout = null); + + /// + /// Retrieves the which this provides. + /// + Track GetTrack(); } } diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs index 171201ca68..af6a67ad3f 100644 --- a/osu.Game/Beatmaps/WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/WorkingBeatmap.cs @@ -249,9 +249,9 @@ namespace osu.Game.Beatmaps protected abstract Texture GetBackground(); private readonly RecyclableLazy background; - public Track GetRealTrack() => GetTrack() ?? GetVirtualTrack(1000); + public Track GetTrack() => GetBeatmapTrack() ?? GetVirtualTrack(1000); - protected abstract Track GetTrack(); + protected abstract Track GetBeatmapTrack(); public bool WaveformLoaded => waveform.IsResultAvailable; public Waveform Waveform => waveform.Value; diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index 2aed46a1d0..cf420c3b91 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -324,7 +324,7 @@ namespace osu.Game.Overlays CurrentTrack = new DrawableTrack(new TrackVirtual(1000)); if (current != null) - CurrentTrack = new DrawableTrack(current.GetRealTrack()); + CurrentTrack = new DrawableTrack(current.GetTrack()); CurrentTrack.Completed += () => onTrackCompleted(current); AddInternal(CurrentTrack); diff --git a/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs b/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs index fc3dd4c105..57b7ce6940 100644 --- a/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs +++ b/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs @@ -107,7 +107,7 @@ namespace osu.Game.Screens.Edit protected override Texture GetBackground() => throw new NotImplementedException(); - protected override Track GetTrack() => throw new NotImplementedException(); + protected override Track GetBeatmapTrack() => throw new NotImplementedException(); } } } diff --git a/osu.Game/Screens/Menu/IntroScreen.cs b/osu.Game/Screens/Menu/IntroScreen.cs index 7e327261ab..6e85abf7dc 100644 --- a/osu.Game/Screens/Menu/IntroScreen.cs +++ b/osu.Game/Screens/Menu/IntroScreen.cs @@ -115,7 +115,7 @@ namespace osu.Game.Screens.Menu if (setInfo != null) { initialBeatmap = beatmaps.GetWorkingBeatmap(setInfo.Beatmaps[0]); - UsingThemedIntro = initialBeatmap.GetRealTrack().IsDummyDevice == false; + UsingThemedIntro = initialBeatmap.GetTrack().IsDummyDevice == false; } return UsingThemedIntro; diff --git a/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs b/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs index 6ada632850..e492069c5e 100644 --- a/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs +++ b/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs @@ -208,7 +208,7 @@ namespace osu.Game.Tests.Beatmaps protected override Texture GetBackground() => throw new NotImplementedException(); - protected override Track GetTrack() => throw new NotImplementedException(); + protected override Track GetBeatmapTrack() => 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 ee04142035..d091da3206 100644 --- a/osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs +++ b/osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs @@ -35,6 +35,6 @@ namespace osu.Game.Tests.Beatmaps protected override Texture GetBackground() => null; - protected override Track GetTrack() => null; + protected override Track GetBeatmapTrack() => null; } } diff --git a/osu.Game/Tests/Visual/OsuTestScene.cs b/osu.Game/Tests/Visual/OsuTestScene.cs index b0d15bf442..756074c0b3 100644 --- a/osu.Game/Tests/Visual/OsuTestScene.cs +++ b/osu.Game/Tests/Visual/OsuTestScene.cs @@ -223,7 +223,7 @@ namespace osu.Game.Tests.Visual store?.Dispose(); } - protected override Track GetTrack() => track; + protected override Track GetBeatmapTrack() => track; public class TrackVirtualStore : AudioCollectionManager, ITrackStore { From b8373e89b7e30e0370170f10bf16c1d7bb36b835 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 7 Aug 2020 23:05:59 +0900 Subject: [PATCH 0448/1782] Move beatmap bind to BDL load() --- osu.Game/Overlays/MusicController.cs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index cf420c3b91..26df48171e 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -79,12 +79,9 @@ namespace osu.Game.Overlays managerRemoved.BindValueChanged(beatmapRemoved); beatmapSets.AddRange(beatmaps.GetAllUsableBeatmapSets(IncludedDetails.Minimal, true).OrderBy(_ => RNG.Next())); - } - - protected override void LoadComplete() - { - base.LoadComplete(); + // Todo: These binds really shouldn't be here, but are unlikely to cause any issues for now. + // They are placed here for now since some tests rely on setting the beatmap _and_ their hierarchies inside their load(), which runs before the MusicController's load(). beatmap.BindValueChanged(beatmapChanged, true); mods.BindValueChanged(_ => ResetTrackAdjustments(), true); } From 2351701ade19e518b2d07f308af5237df2e7eedd Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 7 Aug 2020 23:08:51 +0900 Subject: [PATCH 0449/1782] Fix test not having a long enough track --- .../UserInterface/TestSceneNowPlayingOverlay.cs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneNowPlayingOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneNowPlayingOverlay.cs index cadecbbef0..c14a1ddbf2 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneNowPlayingOverlay.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 System.Collections.Generic; +using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; @@ -11,6 +12,7 @@ using osu.Game.Beatmaps; using osu.Game.Overlays; using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; +using osu.Game.Tests.Resources; namespace osu.Game.Tests.Visual.UserInterface { @@ -20,8 +22,6 @@ namespace osu.Game.Tests.Visual.UserInterface [Cached] private MusicController musicController = new MusicController(); - private WorkingBeatmap currentBeatmap; - private NowPlayingOverlay nowPlayingOverlay; private RulesetStore rulesets; @@ -76,8 +76,13 @@ namespace osu.Game.Tests.Visual.UserInterface } }).Wait(), 5); - AddStep(@"Next track", () => musicController.NextTrack()); - AddStep("Store track", () => currentBeatmap = Beatmap.Value); + WorkingBeatmap currentBeatmap = null; + + AddStep("import beatmap with track", () => + { + var setWithTrack = manager.Import(TestResources.GetTestBeatmapForImport()).Result; + Beatmap.Value = currentBeatmap = manager.GetWorkingBeatmap(setWithTrack.Beatmaps.First()); + }); AddStep(@"Seek track to 6 second", () => musicController.SeekTo(6000)); AddUntilStep(@"Wait for current time to update", () => musicController.CurrentTrack.CurrentTime > 5000); From 87ce1e3558d598388c38cd078840d7148c95f0fd Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Sat, 8 Aug 2020 00:58:04 +0900 Subject: [PATCH 0450/1782] Remove impossible null case (DummyWorkingBeatmap) --- osu.Game/Overlays/MusicController.cs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index 26df48171e..c5ba82288c 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -318,12 +318,9 @@ namespace osu.Game.Overlays private void changeTrack() { CurrentTrack.Expire(); - CurrentTrack = new DrawableTrack(new TrackVirtual(1000)); - - if (current != null) - CurrentTrack = new DrawableTrack(current.GetTrack()); - + CurrentTrack = new DrawableTrack(current.GetTrack()); CurrentTrack.Completed += () => onTrackCompleted(current); + AddInternal(CurrentTrack); } From 1090137da3d46e76f2be85d26dc14a6696393a84 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 8 Aug 2020 23:23:02 +0900 Subject: [PATCH 0451/1782] Adjust comment to read better MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bartłomiej Dach --- 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 8c18e83e92..7ee0b474de 100644 --- a/osu.Game/Skinning/SkinnableSound.cs +++ b/osu.Game/Skinning/SkinnableSound.cs @@ -34,7 +34,7 @@ namespace osu.Game.Skinning /// Defaults to false unless . /// /// - /// Can serve as an optimisation if it is known ahead-of-time that this behaviour will not negatively affect behaviour. + /// Can serve as an optimisation if it is known ahead-of-time that this behaviour is allowed in a given use case. /// protected bool SkipPlayWhenZeroVolume => !Looping; From ffb2e56a8d31e6b62c3715380967e5b63711b672 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 8 Aug 2020 23:25:52 +0900 Subject: [PATCH 0452/1782] Reverse direction of bool to make mental parsing easier --- 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 7739be693d..32f49367f0 100644 --- a/osu.Game/Skinning/SkinnableSound.cs +++ b/osu.Game/Skinning/SkinnableSound.cs @@ -37,7 +37,7 @@ namespace osu.Game.Skinning /// /// Can serve as an optimisation if it is known ahead-of-time that this behaviour is allowed in a given use case. /// - protected bool SkipPlayWhenZeroVolume => !Looping; + protected bool PlayWhenZeroVolume => Looping; private readonly AudioContainer samplesContainer; @@ -98,7 +98,7 @@ namespace osu.Game.Skinning { samplesContainer.ForEach(c => { - if (!SkipPlayWhenZeroVolume || c.AggregateVolume.Value > 0) + if (PlayWhenZeroVolume || c.AggregateVolume.Value > 0) c.Play(); }); } From 9a09f97478f063384a03c0d35d23f359cc9d1353 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 8 Aug 2020 21:21:30 +0200 Subject: [PATCH 0453/1782] Extract constant to avoid double initial value spec --- 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 8f8128abfc..67283c843d 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -50,7 +50,13 @@ namespace osu.Game.Screens.Play public override bool HideOverlaysOnEnter => true; - public override OverlayActivation InitialOverlayActivationMode => OverlayActivation.UserTriggered; + private const OverlayActivation initial_overlay_activation_mode = OverlayActivation.UserTriggered; + public override OverlayActivation InitialOverlayActivationMode => initial_overlay_activation_mode; + + /// + /// The current activation mode for overlays. + /// + protected readonly Bindable OverlayActivationMode = new Bindable(initial_overlay_activation_mode); /// /// Whether gameplay should pause when the game window focus is lost. @@ -90,11 +96,6 @@ namespace osu.Game.Screens.Play private SkipOverlay skipOverlay; - /// - /// The current activation mode for overlays. - /// - protected readonly Bindable OverlayActivationMode = new Bindable(OverlayActivation.UserTriggered); - protected ScoreProcessor ScoreProcessor { get; private set; } protected HealthProcessor HealthProcessor { get; private set; } From a72a48624d82fb1f07264403e31f8744d0ae5ef8 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 9 Aug 2020 05:16:08 +0300 Subject: [PATCH 0454/1782] Remove NewsPostDrawableDate --- .../Dashboard/Home/News/HomeNewsGroupPanel.cs | 69 +++++++++++-------- .../Dashboard/Home/News/HomeNewsPanel.cs | 34 ++++++--- .../Home/News/HomeNewsPanelFooter.cs | 6 +- .../Home/News/NewsPostDrawableDate.cs | 37 ---------- 4 files changed, 67 insertions(+), 79 deletions(-) delete mode 100644 osu.Game/Overlays/Dashboard/Home/News/NewsPostDrawableDate.cs diff --git a/osu.Game/Overlays/Dashboard/Home/News/HomeNewsGroupPanel.cs b/osu.Game/Overlays/Dashboard/Home/News/HomeNewsGroupPanel.cs index cd1c5393c5..48ecaf57dc 100644 --- a/osu.Game/Overlays/Dashboard/Home/News/HomeNewsGroupPanel.cs +++ b/osu.Game/Overlays/Dashboard/Home/News/HomeNewsGroupPanel.cs @@ -7,6 +7,7 @@ using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; using osu.Game.Graphics; using osu.Game.Online.API.Requests.Responses; @@ -44,41 +45,51 @@ namespace osu.Game.Overlays.Dashboard.Home.News protected override Drawable CreateContent(APINewsPost post) => new NewsTitleLink(post); - protected override NewsPostDrawableDate CreateDate(DateTimeOffset date) => new Date(date); + protected override Drawable CreateDate(DateTimeOffset date) => new Date(date); + } - private class Date : NewsPostDrawableDate + private class Date : CompositeDrawable, IHasCustomTooltip + { + public ITooltip GetCustomTooltip() => new DateTooltip(); + + public object TooltipContent => date; + + private readonly DateTimeOffset date; + + public Date(DateTimeOffset date) { - public Date(DateTimeOffset date) - : base(date) + this.date = date; + } + + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider) + { + TextFlowContainer textFlow; + + AutoSizeAxes = Axes.Both; + Anchor = Anchor.TopRight; + Origin = Anchor.TopRight; + InternalChild = textFlow = new TextFlowContainer(t => { - } - - protected override Drawable CreateDate(DateTimeOffset date, OverlayColourProvider colourProvider) + t.Colour = colourProvider.Light1; + }) { - var drawableDate = new TextFlowContainer(t => - { - t.Colour = colourProvider.Light1; - }) - { - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Margin = new MarginPadding { Vertical = 5 } - }; + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Margin = new MarginPadding { Vertical = 5 } + }; - drawableDate.AddText($"{date:dd} ", t => - { - t.Font = OsuFont.GetFont(size: 14, weight: FontWeight.Bold); - }); + textFlow.AddText($"{date:dd}", t => + { + t.Font = OsuFont.GetFont(size: 14, weight: FontWeight.Bold); + }); - drawableDate.AddText($"{date:MMM}", t => - { - t.Font = OsuFont.GetFont(size: 14, weight: FontWeight.Regular); - }); - - return drawableDate; - } + textFlow.AddText($"{date: MMM}", t => + { + t.Font = OsuFont.GetFont(size: 14, weight: FontWeight.Regular); + }); } } } diff --git a/osu.Game/Overlays/Dashboard/Home/News/HomeNewsPanel.cs b/osu.Game/Overlays/Dashboard/Home/News/HomeNewsPanel.cs index 3548b7c88d..786c376fc9 100644 --- a/osu.Game/Overlays/Dashboard/Home/News/HomeNewsPanel.cs +++ b/osu.Game/Overlays/Dashboard/Home/News/HomeNewsPanel.cs @@ -5,6 +5,7 @@ using System; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; using osu.Framework.Platform; using osu.Game.Graphics; using osu.Game.Graphics.Containers; @@ -84,14 +85,14 @@ namespace osu.Game.Overlays.Dashboard.Home.News private class Footer : HomeNewsPanelFooter { - protected override float BarPading => 10; + protected override float BarPadding => 10; public Footer(APINewsPost post) : base(post) { } - protected override NewsPostDrawableDate CreateDate(DateTimeOffset date) => new Date(date); + protected override Drawable CreateDate(DateTimeOffset date) => new Date(date); protected override Drawable CreateContent(APINewsPost post) => new FillFlowContainer { @@ -114,16 +115,29 @@ namespace osu.Game.Overlays.Dashboard.Home.News } } }; + } - private class Date : NewsPostDrawableDate + private class Date : CompositeDrawable, IHasCustomTooltip + { + public ITooltip GetCustomTooltip() => new DateTooltip(); + + public object TooltipContent => date; + + private readonly DateTimeOffset date; + + public Date(DateTimeOffset date) { - public Date(DateTimeOffset date) - : base(date) - { - Margin = new MarginPadding { Top = 10 }; - } + this.date = date; + } - protected override Drawable CreateDate(DateTimeOffset date, OverlayColourProvider colourProvider) => new FillFlowContainer + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider) + { + AutoSizeAxes = Axes.Both; + Anchor = Anchor.TopRight; + Origin = Anchor.TopRight; + Margin = new MarginPadding { Top = 10 }; + InternalChild = new FillFlowContainer { AutoSizeAxes = Axes.Both, Direction = FillDirection.Vertical, @@ -137,7 +151,7 @@ namespace osu.Game.Overlays.Dashboard.Home.News Origin = Anchor.TopRight, Font = OsuFont.GetFont(weight: FontWeight.Bold), // using Bold since there is no 800 weight alternative Colour = colourProvider.Light1, - Text = $"{date: dd}" + Text = $"{date:dd}" }, new TextFlowContainer(f => { diff --git a/osu.Game/Overlays/Dashboard/Home/News/HomeNewsPanelFooter.cs b/osu.Game/Overlays/Dashboard/Home/News/HomeNewsPanelFooter.cs index 591f53ac4a..3e3301b603 100644 --- a/osu.Game/Overlays/Dashboard/Home/News/HomeNewsPanelFooter.cs +++ b/osu.Game/Overlays/Dashboard/Home/News/HomeNewsPanelFooter.cs @@ -12,7 +12,7 @@ namespace osu.Game.Overlays.Dashboard.Home.News { public abstract class HomeNewsPanelFooter : CompositeDrawable { - protected virtual float BarPading { get; } = 0; + protected virtual float BarPadding { get; } = 0; private readonly APINewsPost post; @@ -48,7 +48,7 @@ namespace osu.Game.Overlays.Dashboard.Home.News new Container { RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Vertical = BarPading }, + Padding = new MarginPadding { Vertical = BarPadding }, Child = new Box { Anchor = Anchor.TopCentre, @@ -72,7 +72,7 @@ namespace osu.Game.Overlays.Dashboard.Home.News }; } - protected abstract NewsPostDrawableDate CreateDate(DateTimeOffset date); + protected abstract Drawable CreateDate(DateTimeOffset date); protected abstract Drawable CreateContent(APINewsPost post); } diff --git a/osu.Game/Overlays/Dashboard/Home/News/NewsPostDrawableDate.cs b/osu.Game/Overlays/Dashboard/Home/News/NewsPostDrawableDate.cs deleted file mode 100644 index 8ba58e27a7..0000000000 --- a/osu.Game/Overlays/Dashboard/Home/News/NewsPostDrawableDate.cs +++ /dev/null @@ -1,37 +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.Framework.Graphics.Cursor; -using osu.Game.Graphics; -using osu.Framework.Graphics; - -namespace osu.Game.Overlays.Dashboard.Home.News -{ - public abstract class NewsPostDrawableDate : CompositeDrawable, IHasCustomTooltip - { - public ITooltip GetCustomTooltip() => new DateTooltip(); - - public object TooltipContent => date; - - private readonly DateTimeOffset date; - - protected NewsPostDrawableDate(DateTimeOffset date) - { - this.date = date; - } - - [BackgroundDependencyLoader] - private void load(OverlayColourProvider colourProvider) - { - AutoSizeAxes = Axes.Both; - Anchor = Anchor.TopRight; - Origin = Anchor.TopRight; - InternalChild = CreateDate(date, colourProvider); - } - - protected abstract Drawable CreateDate(DateTimeOffset date, OverlayColourProvider colourProvider); - } -} From d8f89306917a66833db4d1587f03efb295aab3de Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 9 Aug 2020 05:28:43 +0300 Subject: [PATCH 0455/1782] Remove HomeNewsPanelFooter --- .../Dashboard/Home/News/CollapsedNewsPanel.cs | 115 ++++++++++++++++++ .../Dashboard/Home/News/HomeNewsGroupPanel.cs | 60 --------- .../Dashboard/Home/News/HomeNewsPanel.cs | 95 +++++++++------ .../Home/News/HomeNewsPanelFooter.cs | 79 ------------ 4 files changed, 174 insertions(+), 175 deletions(-) create mode 100644 osu.Game/Overlays/Dashboard/Home/News/CollapsedNewsPanel.cs delete mode 100644 osu.Game/Overlays/Dashboard/Home/News/HomeNewsPanelFooter.cs diff --git a/osu.Game/Overlays/Dashboard/Home/News/CollapsedNewsPanel.cs b/osu.Game/Overlays/Dashboard/Home/News/CollapsedNewsPanel.cs new file mode 100644 index 0000000000..7dbc6a8f87 --- /dev/null +++ b/osu.Game/Overlays/Dashboard/Home/News/CollapsedNewsPanel.cs @@ -0,0 +1,115 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; +using osu.Game.Online.API.Requests.Responses; + +namespace osu.Game.Overlays.Dashboard.Home.News +{ + public class CollapsedNewsPanel : CompositeDrawable + { + private readonly APINewsPost post; + + public CollapsedNewsPanel(APINewsPost post) + { + this.post = post; + } + + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider) + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + InternalChild = new GridContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + RowDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize) + }, + ColumnDimensions = new[] + { + new Dimension(GridSizeMode.Absolute, size: 60), + new Dimension(GridSizeMode.Absolute, size: 20), + new Dimension() + }, + Content = new[] + { + new Drawable[] + { + new Date(post.PublishedAt), + new Box + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopRight, + Width = 1, + RelativeSizeAxes = Axes.Y, + Colour = colourProvider.Light1 + }, + new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Padding = new MarginPadding { Right = 10 }, + Child = new NewsTitleLink(post) + } + } + } + }; + } + + private class Date : CompositeDrawable, IHasCustomTooltip + { + public ITooltip GetCustomTooltip() => new DateTooltip(); + + public object TooltipContent => date; + + private readonly DateTimeOffset date; + + public Date(DateTimeOffset date) + { + this.date = date; + } + + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider) + { + TextFlowContainer textFlow; + + AutoSizeAxes = Axes.Both; + Anchor = Anchor.TopRight; + Origin = Anchor.TopRight; + InternalChild = textFlow = new TextFlowContainer(t => + { + t.Colour = colourProvider.Light1; + }) + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Margin = new MarginPadding { Vertical = 5 } + }; + + textFlow.AddText($"{date:dd}", t => + { + t.Font = OsuFont.GetFont(size: 14, weight: FontWeight.Bold); + }); + + textFlow.AddText($"{date: MMM}", t => + { + t.Font = OsuFont.GetFont(size: 14, weight: FontWeight.Regular); + }); + } + } + } +} diff --git a/osu.Game/Overlays/Dashboard/Home/News/HomeNewsGroupPanel.cs b/osu.Game/Overlays/Dashboard/Home/News/HomeNewsGroupPanel.cs index 48ecaf57dc..6007f1408b 100644 --- a/osu.Game/Overlays/Dashboard/Home/News/HomeNewsGroupPanel.cs +++ b/osu.Game/Overlays/Dashboard/Home/News/HomeNewsGroupPanel.cs @@ -1,14 +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 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.Game.Graphics; using osu.Game.Online.API.Requests.Responses; namespace osu.Game.Overlays.Dashboard.Home.News @@ -35,62 +32,5 @@ namespace osu.Game.Overlays.Dashboard.Home.News Children = posts.Select(p => new CollapsedNewsPanel(p)).ToArray() }; } - - private class CollapsedNewsPanel : HomeNewsPanelFooter - { - public CollapsedNewsPanel(APINewsPost post) - : base(post) - { - } - - protected override Drawable CreateContent(APINewsPost post) => new NewsTitleLink(post); - - protected override Drawable CreateDate(DateTimeOffset date) => new Date(date); - } - - private class Date : CompositeDrawable, IHasCustomTooltip - { - public ITooltip GetCustomTooltip() => new DateTooltip(); - - public object TooltipContent => date; - - private readonly DateTimeOffset date; - - public Date(DateTimeOffset date) - { - this.date = date; - } - - [BackgroundDependencyLoader] - private void load(OverlayColourProvider colourProvider) - { - TextFlowContainer textFlow; - - AutoSizeAxes = Axes.Both; - Anchor = Anchor.TopRight; - Origin = Anchor.TopRight; - InternalChild = textFlow = new TextFlowContainer(t => - { - t.Colour = colourProvider.Light1; - }) - { - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Margin = new MarginPadding { Vertical = 5 } - }; - - textFlow.AddText($"{date:dd}", t => - { - t.Font = OsuFont.GetFont(size: 14, weight: FontWeight.Bold); - }); - - textFlow.AddText($"{date: MMM}", t => - { - t.Font = OsuFont.GetFont(size: 14, weight: FontWeight.Regular); - }); - } - } } } diff --git a/osu.Game/Overlays/Dashboard/Home/News/HomeNewsPanel.cs b/osu.Game/Overlays/Dashboard/Home/News/HomeNewsPanel.cs index 786c376fc9..ca56c33315 100644 --- a/osu.Game/Overlays/Dashboard/Home/News/HomeNewsPanel.cs +++ b/osu.Game/Overlays/Dashboard/Home/News/HomeNewsPanel.cs @@ -6,6 +6,7 @@ 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.Platform; using osu.Game.Graphics; using osu.Game.Graphics.Containers; @@ -27,7 +28,7 @@ namespace osu.Game.Overlays.Dashboard.Home.News } [BackgroundDependencyLoader] - private void load() + private void load(OverlayColourProvider colourProvider) { Children = new Drawable[] { @@ -39,7 +40,63 @@ namespace osu.Game.Overlays.Dashboard.Home.News Children = new Drawable[] { new ClickableNewsBackground(post), - new Footer(post) + new GridContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + RowDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize) + }, + ColumnDimensions = new[] + { + new Dimension(GridSizeMode.Absolute, size: 60), + new Dimension(GridSizeMode.Absolute, size: 20), + new Dimension() + }, + Content = new[] + { + new Drawable[] + { + new Date(post.PublishedAt), + new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Vertical = 10 }, + Child = new Box + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopRight, + Width = 1, + RelativeSizeAxes = Axes.Y, + Colour = colourProvider.Light1 + } + }, + new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Margin = new MarginPadding { Top = 5, Bottom = 10 }, + Padding = new MarginPadding { Right = 10 }, + Spacing = new Vector2(0, 10), + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + new NewsTitleLink(post), + new TextFlowContainer(f => + { + f.Font = OsuFont.GetFont(size: 12, weight: FontWeight.Regular); + }) + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Text = post.Preview + } + } + } + } + } + } } } }; @@ -83,40 +140,6 @@ namespace osu.Game.Overlays.Dashboard.Home.News } } - private class Footer : HomeNewsPanelFooter - { - protected override float BarPadding => 10; - - public Footer(APINewsPost post) - : base(post) - { - } - - protected override Drawable CreateDate(DateTimeOffset date) => new Date(date); - - protected override Drawable CreateContent(APINewsPost post) => new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Margin = new MarginPadding { Top = 5, Bottom = 10 }, - Spacing = new Vector2(0, 10), - Direction = FillDirection.Vertical, - Children = new Drawable[] - { - new NewsTitleLink(post), - new TextFlowContainer(f => - { - f.Font = OsuFont.GetFont(size: 12, weight: FontWeight.Regular); - }) - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Text = post.Preview - } - } - }; - } - private class Date : CompositeDrawable, IHasCustomTooltip { public ITooltip GetCustomTooltip() => new DateTooltip(); diff --git a/osu.Game/Overlays/Dashboard/Home/News/HomeNewsPanelFooter.cs b/osu.Game/Overlays/Dashboard/Home/News/HomeNewsPanelFooter.cs deleted file mode 100644 index 3e3301b603..0000000000 --- a/osu.Game/Overlays/Dashboard/Home/News/HomeNewsPanelFooter.cs +++ /dev/null @@ -1,79 +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; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Game.Online.API.Requests.Responses; - -namespace osu.Game.Overlays.Dashboard.Home.News -{ - public abstract class HomeNewsPanelFooter : CompositeDrawable - { - protected virtual float BarPadding { get; } = 0; - - private readonly APINewsPost post; - - protected HomeNewsPanelFooter(APINewsPost post) - { - this.post = post; - } - - [BackgroundDependencyLoader] - private void load(OverlayColourProvider colourProvider) - { - RelativeSizeAxes = Axes.X; - AutoSizeAxes = Axes.Y; - InternalChild = new GridContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - RowDimensions = new[] - { - new Dimension(GridSizeMode.AutoSize) - }, - ColumnDimensions = new[] - { - new Dimension(GridSizeMode.Absolute, size: 60), - new Dimension(GridSizeMode.Absolute, size: 20), - new Dimension() - }, - Content = new[] - { - new Drawable[] - { - CreateDate(post.PublishedAt), - new Container - { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Vertical = BarPadding }, - Child = new Box - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopRight, - Width = 1, - RelativeSizeAxes = Axes.Y, - Colour = colourProvider.Light1 - } - }, - new Container - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Padding = new MarginPadding { Right = 10 }, - Child = CreateContent(post) - } - } - } - }; - } - - protected abstract Drawable CreateDate(DateTimeOffset date); - - protected abstract Drawable CreateContent(APINewsPost post); - } -} From 3a97ee4712557862fba5cecb1d625ae684d2fbee Mon Sep 17 00:00:00 2001 From: voidedWarranties Date: Sun, 9 Aug 2020 16:16:01 -0700 Subject: [PATCH 0456/1782] Context menu for duplicating multi rooms --- osu.Game/Online/Multiplayer/Room.cs | 50 +++++++++++-------- .../Multi/Lounge/Components/DrawableRoom.cs | 11 +++- .../Multi/Lounge/Components/RoomsContainer.cs | 15 ++++-- .../Screens/Multi/Lounge/LoungeSubScreen.cs | 15 +++++- osu.Game/Screens/Multi/Multiplayer.cs | 4 +- 5 files changed, 67 insertions(+), 28 deletions(-) diff --git a/osu.Game/Online/Multiplayer/Room.cs b/osu.Game/Online/Multiplayer/Room.cs index 34cf158442..01d9446bf6 100644 --- a/osu.Game/Online/Multiplayer/Room.cs +++ b/osu.Game/Online/Multiplayer/Room.cs @@ -103,38 +103,48 @@ namespace osu.Game.Online.Multiplayer [JsonIgnore] public readonly Bindable Position = new Bindable(-1); - public void CopyFrom(Room other) + /// + /// Copies the properties from another to this room. + /// + /// The room to copy + /// Whether the copy should exclude information unique to a specific room (i.e. when duplicating a room) + public void CopyFrom(Room other, bool duplicate = false) { - RoomID.Value = other.RoomID.Value; + if (!duplicate) + { + RoomID.Value = other.RoomID.Value; + + if (other.Host.Value != null && Host.Value?.Id != other.Host.Value.Id) + Host.Value = other.Host.Value; + + ChannelId.Value = other.ChannelId.Value; + Status.Value = other.Status.Value; + ParticipantCount.Value = other.ParticipantCount.Value; + EndDate.Value = other.EndDate.Value; + + if (DateTimeOffset.Now >= EndDate.Value) + Status.Value = new RoomStatusEnded(); + + if (!RecentParticipants.SequenceEqual(other.RecentParticipants)) + { + RecentParticipants.Clear(); + RecentParticipants.AddRange(other.RecentParticipants); + } + + Position.Value = other.Position.Value; + } + Name.Value = other.Name.Value; - if (other.Host.Value != null && Host.Value?.Id != other.Host.Value.Id) - Host.Value = other.Host.Value; - - ChannelId.Value = other.ChannelId.Value; - Status.Value = other.Status.Value; Availability.Value = other.Availability.Value; Type.Value = other.Type.Value; MaxParticipants.Value = other.MaxParticipants.Value; - ParticipantCount.Value = other.ParticipantCount.Value; - EndDate.Value = other.EndDate.Value; - - if (DateTimeOffset.Now >= EndDate.Value) - Status.Value = new RoomStatusEnded(); if (!Playlist.SequenceEqual(other.Playlist)) { Playlist.Clear(); Playlist.AddRange(other.Playlist); } - - if (!RecentParticipants.SequenceEqual(other.RecentParticipants)) - { - RecentParticipants.Clear(); - RecentParticipants.AddRange(other.RecentParticipants); - } - - Position.Value = other.Position.Value; } public bool ShouldSerializeRoomID() => false; diff --git a/osu.Game/Screens/Multi/Lounge/Components/DrawableRoom.cs b/osu.Game/Screens/Multi/Lounge/Components/DrawableRoom.cs index 8dd1b239e8..64fbae2503 100644 --- a/osu.Game/Screens/Multi/Lounge/Components/DrawableRoom.cs +++ b/osu.Game/Screens/Multi/Lounge/Components/DrawableRoom.cs @@ -21,10 +21,12 @@ using osu.Game.Online.Multiplayer; using osu.Game.Screens.Multi.Components; using osuTK; using osuTK.Graphics; +using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.UserInterface; namespace osu.Game.Screens.Multi.Lounge.Components { - public class DrawableRoom : OsuClickableContainer, IStateful, IFilterable + public class DrawableRoom : OsuClickableContainer, IStateful, IFilterable, IHasContextMenu { public const float SELECTION_BORDER_WIDTH = 4; private const float corner_radius = 5; @@ -36,6 +38,8 @@ namespace osu.Game.Screens.Multi.Lounge.Components public event Action StateChanged; + public Action DuplicateRoom; + private readonly Box selectionBox; private CachedModelDependencyContainer dependencies; @@ -232,5 +236,10 @@ namespace osu.Game.Screens.Multi.Lounge.Components Current = name; } } + + public MenuItem[] ContextMenuItems => new MenuItem[] + { + new OsuMenuItem("Duplicate", MenuItemType.Standard, () => DuplicateRoom?.Invoke(Room)) + }; } } diff --git a/osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs b/osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs index 447c99039a..f112dd80ee 100644 --- a/osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs +++ b/osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs @@ -16,6 +16,7 @@ using osu.Game.Graphics.UserInterface; using osu.Game.Input.Bindings; using osu.Game.Online.Multiplayer; using osuTK; +using osu.Game.Graphics.Cursor; namespace osu.Game.Screens.Multi.Lounge.Components { @@ -37,17 +38,24 @@ namespace osu.Game.Screens.Multi.Lounge.Components [Resolved] private IRoomManager roomManager { get; set; } + public Action DuplicateRoom; + public RoomsContainer() { RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; - InternalChild = roomFlow = new FillFlowContainer + InternalChild = new OsuContextMenuContainer { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, - Spacing = new Vector2(2), + Child = roomFlow = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Spacing = new Vector2(2), + } }; } @@ -88,6 +96,7 @@ namespace osu.Game.Screens.Multi.Lounge.Components { roomFlow.Add(new DrawableRoom(room) { + DuplicateRoom = DuplicateRoom, Action = () => { if (room == selectedRoom.Value) diff --git a/osu.Game/Screens/Multi/Lounge/LoungeSubScreen.cs b/osu.Game/Screens/Multi/Lounge/LoungeSubScreen.cs index ff7d56a95b..5d68386398 100644 --- a/osu.Game/Screens/Multi/Lounge/LoungeSubScreen.cs +++ b/osu.Game/Screens/Multi/Lounge/LoungeSubScreen.cs @@ -62,7 +62,18 @@ namespace osu.Game.Screens.Multi.Lounge RelativeSizeAxes = Axes.Both, ScrollbarOverlapsContent = false, Padding = new MarginPadding(10), - Child = roomsContainer = new RoomsContainer { JoinRequested = joinRequested } + Child = roomsContainer = new RoomsContainer + { + JoinRequested = joinRequested, + DuplicateRoom = room => + { + Room newRoom = new Room(); + newRoom.CopyFrom(room, true); + newRoom.Name.Value = $"Copy of {room.Name.Value}"; + + Open(newRoom); + } + } }, loadingLayer = new LoadingLayer(roomsContainer), } @@ -126,7 +137,7 @@ namespace osu.Game.Screens.Multi.Lounge if (selectedRoom.Value?.RoomID.Value == null) selectedRoom.Value = new Room(); - music.EnsurePlayingSomething(); + music?.EnsurePlayingSomething(); onReturning(); } diff --git a/osu.Game/Screens/Multi/Multiplayer.cs b/osu.Game/Screens/Multi/Multiplayer.cs index 4912df17b1..cdaeebefb7 100644 --- a/osu.Game/Screens/Multi/Multiplayer.cs +++ b/osu.Game/Screens/Multi/Multiplayer.cs @@ -51,7 +51,7 @@ namespace osu.Game.Screens.Multi [Cached] private readonly Bindable currentFilter = new Bindable(new FilterCriteria()); - [Resolved] + [Resolved(CanBeNull = true)] private MusicController music { get; set; } [Cached(Type = typeof(IRoomManager))] @@ -350,7 +350,7 @@ namespace osu.Game.Screens.Multi track.RestartPoint = Beatmap.Value.Metadata.PreviewTime; track.Looping = true; - music.EnsurePlayingSomething(); + music?.EnsurePlayingSomething(); } } else From 78692dc684e4efa337cdbfc12b02caa2eff6a821 Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Mon, 10 Aug 2020 05:21:10 +0200 Subject: [PATCH 0457/1782] Initial commit --- osu.Game/Beatmaps/BeatmapManager.cs | 6 +++- .../Beatmaps/Formats/LegacyBeatmapEncoder.cs | 33 ++++++++++++++++++- 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index b4b341634c..06acd4e9f2 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -26,6 +26,7 @@ using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Rulesets; using osu.Game.Rulesets.Objects; +using osu.Game.Skinning; using Decoder = osu.Game.Beatmaps.Formats.Decoder; namespace osu.Game.Beatmaps @@ -201,7 +202,10 @@ namespace osu.Game.Beatmaps using (var stream = new MemoryStream()) { using (var sw = new StreamWriter(stream, Encoding.UTF8, 1024, true)) - new LegacyBeatmapEncoder(beatmapContent).Encode(sw); + { + var skin = new LegacyBeatmapSkin(info, Files.Store, audioManager); + new LegacyBeatmapEncoder(beatmapContent, skin).Encode(sw); + } stream.Seek(0, SeekOrigin.Begin); diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs index 57555cce90..8c96e59f30 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs @@ -13,6 +13,7 @@ using osu.Game.Beatmaps.Legacy; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Legacy; using osu.Game.Rulesets.Objects.Types; +using osu.Game.Skinning; using osuTK; namespace osu.Game.Beatmaps.Formats @@ -22,10 +23,12 @@ namespace osu.Game.Beatmaps.Formats public const int LATEST_VERSION = 128; private readonly IBeatmap beatmap; + private readonly LegacyBeatmapSkin beatmapSkin; - public LegacyBeatmapEncoder(IBeatmap beatmap) + public LegacyBeatmapEncoder(IBeatmap beatmap, LegacyBeatmapSkin beatmapSkin = null) { this.beatmap = beatmap; + this.beatmapSkin = beatmapSkin; if (beatmap.BeatmapInfo.RulesetID < 0 || beatmap.BeatmapInfo.RulesetID > 3) throw new ArgumentException("Only beatmaps in the osu, taiko, catch, or mania rulesets can be encoded to the legacy beatmap format.", nameof(beatmap)); @@ -53,6 +56,9 @@ namespace osu.Game.Beatmaps.Formats writer.WriteLine(); handleControlPoints(writer); + writer.WriteLine(); + handleComboColours(writer); + writer.WriteLine(); handleHitObjects(writer); } @@ -196,6 +202,31 @@ namespace osu.Game.Beatmaps.Formats } } + private void handleComboColours(TextWriter writer) + { + if (beatmapSkin == null) + return; + + var colours = beatmapSkin.Configuration.ComboColours; + + if (colours.Count == 0) + return; + + writer.WriteLine("[Colours]"); + + for (var i = 0; i < colours.Count; i++) + { + var comboColour = colours[i]; + + var r = (byte)(comboColour.R * byte.MaxValue); + var g = (byte)(comboColour.G * byte.MaxValue); + var b = (byte)(comboColour.B * byte.MaxValue); + var a = (byte)(comboColour.A * byte.MaxValue); + + writer.WriteLine($"Combo{i}: {r},{g},{b},{a}"); + } + } + private void handleHitObjects(TextWriter writer) { if (beatmap.HitObjects.Count == 0) From 1f84e541518a29d0b514f99b9ce8830b68daf68e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 10 Aug 2020 20:16:16 +0900 Subject: [PATCH 0458/1782] Improve messaging when timeshift token retrieval fails Obviously not a final solution, but should better help self-compiling (or unofficial package) users better understand why this is happening. --- osu.Game/Screens/Multi/Play/TimeshiftPlayer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Multi/Play/TimeshiftPlayer.cs b/osu.Game/Screens/Multi/Play/TimeshiftPlayer.cs index da082692d7..79b4b04722 100644 --- a/osu.Game/Screens/Multi/Play/TimeshiftPlayer.cs +++ b/osu.Game/Screens/Multi/Play/TimeshiftPlayer.cs @@ -64,7 +64,7 @@ namespace osu.Game.Screens.Multi.Play { failed = true; - Logger.Error(e, "Failed to retrieve a score submission token."); + Logger.Error(e, "Failed to retrieve a score submission token.\n\nThis may happen if you are not running an official release of osu! (ie. you are self-compiling)."); Schedule(() => { From 730d13fda6f08a6976c30139ab32d908b78eadc8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 10 Aug 2020 20:48:04 +0900 Subject: [PATCH 0459/1782] Always show newly presented overlay at front This feels much better. Does not change order if the overlay to be shown is not yet completely hidden. - Closes #9815. --- osu.Game/OsuGame.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index b5752214bd..623c677991 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -702,6 +702,9 @@ namespace osu.Game if (state.NewValue == Visibility.Hidden) return; singleDisplayOverlays.Where(o => o != overlay).ForEach(o => o.Hide()); + + if (!overlay.IsPresent) + overlayContent.ChangeChildDepth(overlay, (float)-Clock.CurrentTime); }; } From d7de8b2916af35ebd843b08c6f2a3c0d8ad788a6 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 10 Aug 2020 17:17:07 +0000 Subject: [PATCH 0460/1782] Bump Microsoft.NET.Test.Sdk from 16.6.1 to 16.7.0 Bumps [Microsoft.NET.Test.Sdk](https://github.com/microsoft/vstest) from 16.6.1 to 16.7.0. - [Release notes](https://github.com/microsoft/vstest/releases) - [Commits](https://github.com/microsoft/vstest/compare/v16.6.1...v16.7.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 7c0b73e8c3..f9d56dfa78 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 972cbec4a2..ed00ed0b4c 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 d6a68abaf2..f3837ea6b1 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 ada7ac5d74..e896606ee8 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 4b0506d818..d767973528 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 f256b8e4e9..95f5deb2cc 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 61f1c4fe62d5d4c52fa98ee2895af00229c9748d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 10 Aug 2020 19:51:00 +0200 Subject: [PATCH 0461/1782] Extract replay-transforming helper test method --- .../TestSceneSpinnerRotation.cs | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs index b46964e8b7..816c0c38d9 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs @@ -121,19 +121,7 @@ namespace osu.Game.Rulesets.Osu.Tests public void TestRotationDirection([Values(true, false)] bool clockwise) { if (clockwise) - { - AddStep("flip replay", () => - { - var drawableRuleset = this.ChildrenOfType().Single(); - var score = drawableRuleset.ReplayScore; - var scoreWithFlippedReplay = new Score - { - ScoreInfo = score.ScoreInfo, - Replay = flipReplay(score.Replay) - }; - drawableRuleset.SetReplayScore(scoreWithFlippedReplay); - }); - } + transformReplay(flip); addSeekStep(5000); @@ -141,7 +129,7 @@ namespace osu.Game.Rulesets.Osu.Tests AddAssert("spinner symbol direction correct", () => clockwise ? spinnerSymbol.Rotation > 0 : spinnerSymbol.Rotation < 0); } - private Replay flipReplay(Replay scoreReplay) => new Replay + private Replay flip(Replay scoreReplay) => new Replay { Frames = scoreReplay .Frames @@ -203,6 +191,18 @@ namespace osu.Game.Rulesets.Osu.Tests AddUntilStep("wait for seek to finish", () => Precision.AlmostEquals(time, Player.DrawableRuleset.FrameStableClock.CurrentTime, 100)); } + private void transformReplay(Func replayTransformation) => AddStep("set replay", () => + { + var drawableRuleset = this.ChildrenOfType().Single(); + var score = drawableRuleset.ReplayScore; + var transformedScore = new Score + { + ScoreInfo = score.ScoreInfo, + Replay = replayTransformation.Invoke(score.Replay) + }; + drawableRuleset.SetReplayScore(transformedScore); + }); + protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new Beatmap { HitObjects = new List From 052bb06c910e3618b8cc83badeb41fa0b71c7f1f Mon Sep 17 00:00:00 2001 From: Lucas A Date: Mon, 10 Aug 2020 20:13:50 +0200 Subject: [PATCH 0462/1782] Add ability to open overlays during gameplay breaks. --- .../Visual/Gameplay/TestSceneOverlayActivation.cs | 11 +++++++++++ osu.Game/Screens/Play/Player.cs | 3 ++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneOverlayActivation.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneOverlayActivation.cs index 03e1337125..7fd5158515 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneOverlayActivation.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneOverlayActivation.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 NUnit.Framework; using osu.Game.Configuration; using osu.Game.Overlays; @@ -47,6 +48,16 @@ namespace osu.Game.Tests.Visual.Gameplay AddAssert("activation mode is user triggered", () => testPlayer.OverlayActivationMode == OverlayActivation.UserTriggered); } + [Test] + public void TestGameplayOverlayActivationBreaks() + { + AddAssert("activation mode is disabled", () => testPlayer.OverlayActivationMode == OverlayActivation.Disabled); + AddStep("seek to break", () => testPlayer.GameplayClockContainer.Seek(Beatmap.Value.Beatmap.Breaks.First().StartTime)); + AddUntilStep("activation mode is user triggered", () => testPlayer.OverlayActivationMode == OverlayActivation.UserTriggered); + AddStep("seek to break end", () => testPlayer.GameplayClockContainer.Seek(Beatmap.Value.Beatmap.Breaks.First().EndTime)); + AddUntilStep("activation mode is disabled", () => testPlayer.OverlayActivationMode == OverlayActivation.Disabled); + } + protected override TestPlayer CreatePlayer(Ruleset ruleset) => testPlayer = new OverlayTestPlayer(); private class OverlayTestPlayer : TestPlayer diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 67283c843d..fba35af29e 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -215,6 +215,7 @@ namespace osu.Game.Screens.Play gameplayOverlaysDisabled.BindValueChanged(_ => updateOverlayActivationMode()); DrawableRuleset.IsPaused.BindValueChanged(_ => updateOverlayActivationMode()); DrawableRuleset.HasReplayLoaded.BindValueChanged(_ => updateOverlayActivationMode()); + breakTracker.IsBreakTime.BindValueChanged(_ => updateOverlayActivationMode()); DrawableRuleset.HasReplayLoaded.BindValueChanged(_ => updatePauseOnFocusLostState(), true); @@ -362,7 +363,7 @@ namespace osu.Game.Screens.Play private void updateOverlayActivationMode() { - bool canTriggerOverlays = DrawableRuleset.IsPaused.Value || !gameplayOverlaysDisabled.Value; + bool canTriggerOverlays = DrawableRuleset.IsPaused.Value || breakTracker.IsBreakTime.Value || !gameplayOverlaysDisabled.Value; if (DrawableRuleset.HasReplayLoaded.Value || canTriggerOverlays) OverlayActivationMode.Value = OverlayActivation.UserTriggered; From f74e162bbc90e5c32be17cc4231571a960fc482b Mon Sep 17 00:00:00 2001 From: Lucas A Date: Mon, 10 Aug 2020 20:27:42 +0200 Subject: [PATCH 0463/1782] Fix overlay activation mode being updated when player is not current screen. --- osu.Game/Screens/Play/Player.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index fba35af29e..2ecddf0f23 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -363,6 +363,9 @@ namespace osu.Game.Screens.Play private void updateOverlayActivationMode() { + if (!this.IsCurrentScreen()) + return; + bool canTriggerOverlays = DrawableRuleset.IsPaused.Value || breakTracker.IsBreakTime.Value || !gameplayOverlaysDisabled.Value; if (DrawableRuleset.HasReplayLoaded.Value || canTriggerOverlays) From 5d63b5f6a5be7657a75024448b44c6ed214bf7b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 10 Aug 2020 20:04:14 +0200 Subject: [PATCH 0464/1782] Add failing test cases --- .../TestSceneSpinnerRotation.cs | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs index 816c0c38d9..b6f4efc24c 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs @@ -7,6 +7,7 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; +using osu.Framework.Bindables; using osu.Framework.Graphics.Sprites; using osu.Framework.Testing; using osu.Framework.Timing; @@ -184,6 +185,49 @@ namespace osu.Game.Rulesets.Osu.Tests AddAssert("spm still valid", () => Precision.AlmostEquals(drawableSpinner.SpmCounter.SpinsPerMinute, estimatedSpm, 1.0)); } + [TestCase(0.5)] + [TestCase(2.0)] + public void TestSpinUnaffectedByClockRate(double rate) + { + double expectedProgress = 0; + double expectedSpm = 0; + + addSeekStep(1000); + AddStep("retrieve spinner state", () => + { + expectedProgress = drawableSpinner.Progress; + expectedSpm = drawableSpinner.SpmCounter.SpinsPerMinute; + }); + + addSeekStep(0); + + AddStep("adjust track rate", () => track.AddAdjustment(AdjustableProperty.Tempo, new BindableDouble(rate))); + // autoplay replay frames use track time; + // if a spin takes 1000ms in track time and we're playing with a 2x rate adjustment, the spin will take 500ms of *real* time. + // therefore we need to apply the rate adjustment to the replay itself to change from track time to real time, + // as real time is what we care about for spinners + // (so we're making the spin take 1000ms in real time *always*, regardless of the track clock's rate). + transformReplay(replay => applyRateAdjustment(replay, rate)); + + addSeekStep(1000); + AddAssert("progress almost same", () => Precision.AlmostEquals(expectedProgress, drawableSpinner.Progress, 0.05)); + AddAssert("spm almost same", () => Precision.AlmostEquals(expectedSpm, drawableSpinner.SpmCounter.SpinsPerMinute, 2.0)); + } + + private Replay applyRateAdjustment(Replay scoreReplay, double rate) => new Replay + { + Frames = scoreReplay + .Frames + .Cast() + .Select(replayFrame => + { + var adjustedTime = replayFrame.Time * rate; + return new OsuReplayFrame(adjustedTime, replayFrame.Position, replayFrame.Actions.ToArray()); + }) + .Cast() + .ToList() + }; + private void addSeekStep(double time) { AddStep($"seek to {time}", () => track.Seek(time)); From cca78235d5e9c0028852563329802f1d026da6c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 10 Aug 2020 22:17:47 +0200 Subject: [PATCH 0465/1782] Replace CumulativeRotation with RateAdjustedRotation --- .../TestSceneSpinnerRotation.cs | 12 +++++------ .../Objects/Drawables/DrawableSpinner.cs | 6 +++--- .../Drawables/Pieces/DefaultSpinnerDisc.cs | 2 +- .../Pieces/SpinnerRotationTracker.cs | 21 +++++++++++++++---- 4 files changed, 27 insertions(+), 14 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs index b6f4efc24c..69857f8ef9 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs @@ -70,11 +70,11 @@ namespace osu.Game.Rulesets.Osu.Tests trackerRotationTolerance = Math.Abs(drawableSpinner.RotationTracker.Rotation * 0.1f); }); AddAssert("is disc rotation not almost 0", () => !Precision.AlmostEquals(drawableSpinner.RotationTracker.Rotation, 0, 100)); - AddAssert("is disc rotation absolute not almost 0", () => !Precision.AlmostEquals(drawableSpinner.RotationTracker.CumulativeRotation, 0, 100)); + AddAssert("is disc rotation absolute not almost 0", () => !Precision.AlmostEquals(drawableSpinner.RotationTracker.RateAdjustedRotation, 0, 100)); addSeekStep(0); AddAssert("is disc rotation almost 0", () => Precision.AlmostEquals(drawableSpinner.RotationTracker.Rotation, 0, trackerRotationTolerance)); - AddAssert("is disc rotation absolute almost 0", () => Precision.AlmostEquals(drawableSpinner.RotationTracker.CumulativeRotation, 0, 100)); + AddAssert("is disc rotation absolute almost 0", () => Precision.AlmostEquals(drawableSpinner.RotationTracker.RateAdjustedRotation, 0, 100)); } [Test] @@ -95,7 +95,7 @@ namespace osu.Game.Rulesets.Osu.Tests finalSpinnerSymbolRotation = spinnerSymbol.Rotation; spinnerSymbolRotationTolerance = Math.Abs(finalSpinnerSymbolRotation * 0.05f); }); - AddStep("retrieve cumulative disc rotation", () => finalCumulativeTrackerRotation = drawableSpinner.RotationTracker.CumulativeRotation); + AddStep("retrieve cumulative disc rotation", () => finalCumulativeTrackerRotation = drawableSpinner.RotationTracker.RateAdjustedRotation); addSeekStep(2500); AddAssert("disc rotation rewound", @@ -107,7 +107,7 @@ namespace osu.Game.Rulesets.Osu.Tests () => Precision.AlmostEquals(spinnerSymbol.Rotation, finalSpinnerSymbolRotation / 2, spinnerSymbolRotationTolerance)); AddAssert("is cumulative rotation rewound", // cumulative rotation is not damped, so we're treating it as the "ground truth" and allowing a comparatively smaller margin of error. - () => Precision.AlmostEquals(drawableSpinner.RotationTracker.CumulativeRotation, finalCumulativeTrackerRotation / 2, 100)); + () => Precision.AlmostEquals(drawableSpinner.RotationTracker.RateAdjustedRotation, finalCumulativeTrackerRotation / 2, 100)); addSeekStep(5000); AddAssert("is disc rotation almost same", @@ -115,7 +115,7 @@ namespace osu.Game.Rulesets.Osu.Tests AddAssert("is symbol rotation almost same", () => Precision.AlmostEquals(spinnerSymbol.Rotation, finalSpinnerSymbolRotation, spinnerSymbolRotationTolerance)); AddAssert("is cumulative rotation almost same", - () => Precision.AlmostEquals(drawableSpinner.RotationTracker.CumulativeRotation, finalCumulativeTrackerRotation, 100)); + () => Precision.AlmostEquals(drawableSpinner.RotationTracker.RateAdjustedRotation, finalCumulativeTrackerRotation, 100)); } [Test] @@ -153,7 +153,7 @@ namespace osu.Game.Rulesets.Osu.Tests { // multipled by 2 to nullify the score multiplier. (autoplay mod selected) var totalScore = ((ScoreExposedPlayer)Player).ScoreProcessor.TotalScore.Value * 2; - return totalScore == (int)(drawableSpinner.RotationTracker.CumulativeRotation / 360) * SpinnerTick.SCORE_PER_TICK; + return totalScore == (int)(drawableSpinner.RotationTracker.RateAdjustedRotation / 360) * SpinnerTick.SCORE_PER_TICK; }); addSeekStep(0); diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index a2a49b5c42..f10d11827b 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -185,7 +185,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables // these become implicitly hit. return 1; - return Math.Clamp(RotationTracker.CumulativeRotation / 360 / Spinner.SpinsRequired, 0, 1); + return Math.Clamp(RotationTracker.RateAdjustedRotation / 360 / Spinner.SpinsRequired, 0, 1); } } @@ -233,7 +233,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables if (!SpmCounter.IsPresent && RotationTracker.Tracking) SpmCounter.FadeIn(HitObject.TimeFadeIn); - SpmCounter.SetRotation(RotationTracker.CumulativeRotation); + SpmCounter.SetRotation(RotationTracker.RateAdjustedRotation); updateBonusScore(); } @@ -245,7 +245,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables if (ticks.Count == 0) return; - int spins = (int)(RotationTracker.CumulativeRotation / 360); + int spins = (int)(RotationTracker.RateAdjustedRotation / 360); if (spins < wholeSpins) { diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/DefaultSpinnerDisc.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/DefaultSpinnerDisc.cs index dfb692eba9..1476fe6010 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/DefaultSpinnerDisc.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/DefaultSpinnerDisc.cs @@ -177,7 +177,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces { get { - int rotations = (int)(drawableSpinner.RotationTracker.CumulativeRotation / 360); + int rotations = (int)(drawableSpinner.RotationTracker.RateAdjustedRotation / 360); if (wholeRotationCount == rotations) return false; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerRotationTracker.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerRotationTracker.cs index 0cc6c842f4..f1a782cbb5 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerRotationTracker.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerRotationTracker.cs @@ -31,17 +31,28 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces public readonly BindableBool Complete = new BindableBool(); /// - /// The total rotation performed on the spinner disc, disregarding the spin direction. + /// The total rotation performed on the spinner disc, disregarding the spin direction, + /// adjusted for the track's playback rate. /// /// + /// /// This value is always non-negative and is monotonically increasing with time /// (i.e. will only increase if time is passing forward, but can decrease during rewind). + /// + /// + /// The rotation from each frame is multiplied by the clock's current playback rate. + /// The reason this is done is to ensure that spinners give the same score and require the same number of spins + /// regardless of whether speed-modifying mods are applied. + /// /// /// - /// If the spinner is spun 360 degrees clockwise and then 360 degrees counter-clockwise, + /// Assuming no speed-modifying mods are active, + /// if the spinner is spun 360 degrees clockwise and then 360 degrees counter-clockwise, /// this property will return the value of 720 (as opposed to 0 for ). + /// If Double Time is active instead (with a speed multiplier of 1.5x), + /// in the same scenario the property will return 720 * 1.5 = 1080. /// - public float CumulativeRotation { get; private set; } + public float RateAdjustedRotation { get; private set; } /// /// Whether the spinning is spinning at a reasonable speed to be considered visually spinning. @@ -113,7 +124,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces } currentRotation += angle; - CumulativeRotation += Math.Abs(angle) * Math.Sign(Clock.ElapsedFrameTime); + // rate has to be applied each frame, because it's not guaranteed to be constant throughout playback + // (see: ModTimeRamp) + RateAdjustedRotation += (float)(Math.Abs(angle) * Clock.Rate); } } } From ecb4826e1974ce4752020732d78d6631863d9e9f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 11 Aug 2020 06:54:26 +0900 Subject: [PATCH 0466/1782] 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 e5fed09c07..a384ad4c34 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,6 +52,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 18c3052ca3..b38ef38ec2 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -24,7 +24,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index b034253d88..00ddd94d53 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -80,7 +80,7 @@ - + From d1b106a3b557c0c8dd13746679a7e68193863eff Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 11 Aug 2020 10:59:28 +0900 Subject: [PATCH 0467/1782] Include mention of old releases in error message --- osu.Game/Screens/Multi/Play/TimeshiftPlayer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Multi/Play/TimeshiftPlayer.cs b/osu.Game/Screens/Multi/Play/TimeshiftPlayer.cs index 79b4b04722..04da943a10 100644 --- a/osu.Game/Screens/Multi/Play/TimeshiftPlayer.cs +++ b/osu.Game/Screens/Multi/Play/TimeshiftPlayer.cs @@ -64,7 +64,7 @@ namespace osu.Game.Screens.Multi.Play { failed = true; - Logger.Error(e, "Failed to retrieve a score submission token.\n\nThis may happen if you are not running an official release of osu! (ie. you are self-compiling)."); + Logger.Error(e, "Failed to retrieve a score submission token.\n\nThis may happen if you are running an old or non-official release of osu! (ie. you are self-compiling)."); Schedule(() => { From 471ed968e3d7a81b566a137078b5e3bef9e35d10 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 11 Aug 2020 11:09:02 +0900 Subject: [PATCH 0468/1782] Fix crash when same ruleset loaded more than once If the same ruleset assembly was present more than once in the current AppDomain, the game would crash. We recently saw this in Rider EAP9. While this behaviour may change going forward, this is a good safety measure regardless. --- osu.Game/Rulesets/RulesetStore.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game/Rulesets/RulesetStore.cs b/osu.Game/Rulesets/RulesetStore.cs index dd43092c0d..5d93f5186b 100644 --- a/osu.Game/Rulesets/RulesetStore.cs +++ b/osu.Game/Rulesets/RulesetStore.cs @@ -191,6 +191,11 @@ namespace osu.Game.Rulesets if (loadedAssemblies.ContainsKey(assembly)) return; + // the same assembly may be loaded twice in the same AppDomain (currently a thing in certain Rider versions https://youtrack.jetbrains.com/issue/RIDER-48799). + // as a failsafe, also compare by FullName. + if (loadedAssemblies.Any(a => a.Key.FullName == assembly.FullName)) + return; + try { loadedAssemblies[assembly] = assembly.GetTypes().First(t => t.IsPublic && t.IsSubclassOf(typeof(Ruleset))); From 20197e276861128b96dec6fcf7f9fd148a763e8d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 11 Aug 2020 12:27:32 +0900 Subject: [PATCH 0469/1782] Remove locally-cached music controller --- osu.Game.Tests/Visual/Gameplay/TestSceneStoryboard.cs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboard.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboard.cs index 3d2dd8a0c5..c11a47d62b 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboard.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboard.cs @@ -25,16 +25,12 @@ namespace osu.Game.Tests.Visual.Gameplay private readonly Container storyboardContainer; private DrawableStoryboard storyboard; - [Cached] - private MusicController musicController = new MusicController(); - public TestSceneStoryboard() { Clock = new FramedClock(); AddRange(new Drawable[] { - musicController, new Container { RelativeSizeAxes = Axes.Both, @@ -104,7 +100,7 @@ namespace osu.Game.Tests.Visual.Gameplay storyboard.Passing = false; storyboardContainer.Add(storyboard); - decoupledClock.ChangeSource(musicController.CurrentTrack); + decoupledClock.ChangeSource(MusicController.CurrentTrack); } private void loadStoryboardNoVideo() @@ -127,7 +123,7 @@ namespace osu.Game.Tests.Visual.Gameplay storyboard = sb.CreateDrawable(Beatmap.Value); storyboardContainer.Add(storyboard); - decoupledClock.ChangeSource(musicController.CurrentTrack); + decoupledClock.ChangeSource(MusicController.CurrentTrack); } } } From b64142dff9d04deb9295eeb9c5b3e52623c63cdf Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 11 Aug 2020 12:37:00 +0900 Subject: [PATCH 0470/1782] Fix incorrect load state being used --- 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 c5ba82288c..813ad26ae4 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -105,7 +105,7 @@ namespace osu.Game.Overlays /// /// Returns whether the beatmap track is loaded. /// - public bool TrackLoaded => CurrentTrack.IsLoaded; + public bool TrackLoaded => CurrentTrack.TrackLoaded; private void beatmapUpdated(ValueChangedEvent> weakSet) { From c66a14e9c5d160def0fdcdabcf39772e72cdd0dc Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 11 Aug 2020 12:37:48 +0900 Subject: [PATCH 0471/1782] Remove beatmap from FailAnimation --- osu.Game/Screens/Play/FailAnimation.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/FailAnimation.cs b/osu.Game/Screens/Play/FailAnimation.cs index a7bfca612e..122ae505ca 100644 --- a/osu.Game/Screens/Play/FailAnimation.cs +++ b/osu.Game/Screens/Play/FailAnimation.cs @@ -11,7 +11,6 @@ using osu.Framework.Audio.Sample; using osu.Framework.Audio.Track; using osu.Framework.Graphics; using osu.Framework.Utils; -using osu.Game.Beatmaps; using osu.Game.Rulesets.Objects.Drawables; using osuTK; using osuTK.Graphics; @@ -42,7 +41,7 @@ namespace osu.Game.Screens.Play } [BackgroundDependencyLoader] - private void load(AudioManager audio, IBindable beatmap) + private void load(AudioManager audio) { failSample = audio.Samples.Get(@"Gameplay/failsound"); } From 7d35893ecd2bf8b277c2e6ac9632592dbf313f6f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 11 Aug 2020 12:40:58 +0900 Subject: [PATCH 0472/1782] Make MusicController non-nullable --- osu.Game/Screens/Menu/MainMenu.cs | 2 +- .../Screens/Multi/Lounge/LoungeSubScreen.cs | 2 +- osu.Game/Screens/Select/SongSelect.cs | 28 +++++++------------ 3 files changed, 12 insertions(+), 20 deletions(-) diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index 8837a49772..470e8ca9a6 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -45,7 +45,7 @@ namespace osu.Game.Screens.Menu [Resolved] private GameHost host { get; set; } - [Resolved(canBeNull: true)] + [Resolved] private MusicController music { get; set; } [Resolved(canBeNull: true)] diff --git a/osu.Game/Screens/Multi/Lounge/LoungeSubScreen.cs b/osu.Game/Screens/Multi/Lounge/LoungeSubScreen.cs index ff7d56a95b..f9c5fd13a4 100644 --- a/osu.Game/Screens/Multi/Lounge/LoungeSubScreen.cs +++ b/osu.Game/Screens/Multi/Lounge/LoungeSubScreen.cs @@ -32,7 +32,7 @@ namespace osu.Game.Screens.Multi.Lounge [Resolved] private Bindable selectedRoom { get; set; } - [Resolved(canBeNull: true)] + [Resolved] private MusicController music { get; set; } private bool joiningRoom; diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 80ed894233..ddbb021054 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -99,7 +99,7 @@ namespace osu.Game.Screens.Select private readonly Bindable decoupledRuleset = new Bindable(); - [Resolved(canBeNull: true)] + [Resolved] private MusicController music { get; set; } [BackgroundDependencyLoader(true)] @@ -561,18 +561,15 @@ namespace osu.Game.Screens.Select BeatmapDetails.Refresh(); - if (music?.CurrentTrack != null) - { - music.CurrentTrack.Looping = true; - music.ResetTrackAdjustments(); - } + music.CurrentTrack.Looping = true; + music.ResetTrackAdjustments(); if (Beatmap != null && !Beatmap.Value.BeatmapSetInfo.DeletePending) { updateComponentFromBeatmap(Beatmap.Value); // restart playback on returning to song select, regardless. - music?.Play(); + music.Play(); } this.FadeIn(250); @@ -589,8 +586,7 @@ namespace osu.Game.Screens.Select BeatmapOptions.Hide(); - if (music?.CurrentTrack != null) - music.CurrentTrack.Looping = false; + music.CurrentTrack.Looping = false; this.ScaleTo(1.1f, 250, Easing.InSine); @@ -611,8 +607,7 @@ namespace osu.Game.Screens.Select FilterControl.Deactivate(); - if (music?.CurrentTrack != null) - music.CurrentTrack.Looping = false; + music.CurrentTrack.Looping = false; return false; } @@ -653,8 +648,7 @@ namespace osu.Game.Screens.Select BeatmapDetails.Beatmap = beatmap; - if (music?.CurrentTrack != null) - music.CurrentTrack.Looping = true; + music.CurrentTrack.Looping = true; } private readonly WeakReference lastTrack = new WeakReference(null); @@ -665,16 +659,14 @@ namespace osu.Game.Screens.Select /// private void ensurePlayingSelected() { - ITrack track = music?.CurrentTrack; - if (track == null) - return; + ITrack track = music.CurrentTrack; bool isNewTrack = !lastTrack.TryGetTarget(out var last) || last != track; track.RestartPoint = Beatmap.Value.Metadata.PreviewTime; - if (!track.IsRunning && (music?.IsUserPaused != true || isNewTrack)) - music?.Play(true); + if (!track.IsRunning && (music.IsUserPaused != true || isNewTrack)) + music.Play(true); lastTrack.SetTarget(track); } From 322d08af1bfe25f45f90fcd8bc395219ce85c108 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 11 Aug 2020 13:11:59 +0900 Subject: [PATCH 0473/1782] Remove more local music controller caches --- osu.Game.Tests/Visual/Menus/TestSceneSongTicker.cs | 5 ----- .../Visual/UserInterface/TestSceneBeatSyncedContainer.cs | 4 ---- 2 files changed, 9 deletions(-) diff --git a/osu.Game.Tests/Visual/Menus/TestSceneSongTicker.cs b/osu.Game.Tests/Visual/Menus/TestSceneSongTicker.cs index d7f23f5cc0..4b22af38c5 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneSongTicker.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneSongTicker.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.Overlays; @@ -11,14 +10,10 @@ namespace osu.Game.Tests.Visual.Menus { public class TestSceneSongTicker : OsuTestScene { - [Cached] - private MusicController musicController = new MusicController(); - public TestSceneSongTicker() { AddRange(new Drawable[] { - musicController, new SongTicker { Anchor = Anchor.Centre, diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs index 127915c6c6..82b7e65c4f 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs @@ -26,9 +26,6 @@ namespace osu.Game.Tests.Visual.UserInterface { private readonly NowPlayingOverlay np; - [Cached] - private MusicController musicController = new MusicController(); - public TestSceneBeatSyncedContainer() { Clock = new FramedClock(); @@ -36,7 +33,6 @@ namespace osu.Game.Tests.Visual.UserInterface AddRange(new Drawable[] { - musicController, new BeatContainer { Anchor = Anchor.BottomCentre, From 6aafb3d2710174fe4bf302300ecf2cd1edd52988 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 11 Aug 2020 13:14:20 +0900 Subject: [PATCH 0474/1782] Cleanup TestSceneScreenNavigation --- osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs index 0f06010a6a..73a833c15d 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs @@ -80,7 +80,7 @@ namespace osu.Game.Tests.Visual.Navigation AddStep("return to menu", () => songSelect.Exit()); - AddUntilStep("wait for track", () => Game.MusicController.CurrentTrack.IsDummyDevice == false && Game.MusicController.IsPlaying); + AddUntilStep("wait for track", () => !Game.MusicController.CurrentTrack.IsDummyDevice && Game.MusicController.IsPlaying); } [Test] From 338c01fa43657beb3f1476aab90db227605deed2 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 11 Aug 2020 13:16:06 +0900 Subject: [PATCH 0475/1782] Remove track store reference counting, use single instance stores --- osu.Game/Beatmaps/BeatmapManager.cs | 29 ++++--------------- .../Beatmaps/BeatmapManager_WorkingBeatmap.cs | 24 +++++---------- 2 files changed, 13 insertions(+), 40 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index e001185da9..6f01580998 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -63,8 +63,9 @@ namespace osu.Game.Beatmaps private readonly RulesetStore rulesets; private readonly BeatmapStore beatmaps; private readonly AudioManager audioManager; - private readonly GameHost host; private readonly BeatmapOnlineLookupQueue onlineLookupQueue; + private readonly TextureStore textureStore; + private readonly ITrackStore trackStore; public BeatmapManager(Storage storage, IDatabaseContextFactory contextFactory, RulesetStore rulesets, IAPIProvider api, AudioManager audioManager, GameHost host = null, WorkingBeatmap defaultBeatmap = null) @@ -72,7 +73,6 @@ namespace osu.Game.Beatmaps { this.rulesets = rulesets; this.audioManager = audioManager; - this.host = host; DefaultBeatmap = defaultBeatmap; @@ -83,6 +83,9 @@ namespace osu.Game.Beatmaps beatmaps.ItemUpdated += removeWorkingCache; onlineLookupQueue = new BeatmapOnlineLookupQueue(api, storage); + + textureStore = new LargeTextureStore(host?.CreateTextureLoaderStore(Files.Store)); + trackStore = audioManager.GetTrackStore(Files.Store); } protected override ArchiveDownloadRequest CreateDownloadRequest(BeatmapSetInfo set, bool minimiseDownloadSize) => @@ -219,7 +222,6 @@ namespace osu.Game.Beatmaps } private readonly WeakList workingCache = new WeakList(); - private readonly Dictionary referencedTrackStores = new Dictionary(); /// /// Retrieve a instance for the provided @@ -252,31 +254,12 @@ namespace osu.Game.Beatmaps beatmapInfo.Metadata ??= beatmapInfo.BeatmapSet.Metadata; - ITrackStore trackStore = workingCache.FirstOrDefault(b => b.BeatmapInfo.AudioEquals(beatmapInfo))?.TrackStore; - TextureStore textureStore = workingCache.FirstOrDefault(b => b.BeatmapInfo.BackgroundEquals(beatmapInfo))?.TextureStore; - - textureStore ??= new LargeTextureStore(host?.CreateTextureLoaderStore(Files.Store)); - trackStore ??= audioManager.GetTrackStore(Files.Store); - - workingCache.Add(working = new BeatmapManagerWorkingBeatmap(Files.Store, textureStore, trackStore, beatmapInfo, audioManager, dereferenceTrackStore)); - referencedTrackStores[trackStore] = referencedTrackStores.GetOrDefault(trackStore) + 1; + workingCache.Add(working = new BeatmapManagerWorkingBeatmap(Files.Store, textureStore, trackStore, beatmapInfo, audioManager)); return working; } } - private void dereferenceTrackStore(ITrackStore trackStore) - { - lock (workingCache) - { - if (--referencedTrackStores[trackStore] == 0) - { - referencedTrackStores.Remove(trackStore); - trackStore.Dispose(); - } - } - } - /// /// Perform a lookup query on available s. /// diff --git a/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs b/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs index f1289cd3aa..aa93833f33 100644 --- a/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs @@ -19,21 +19,16 @@ namespace osu.Game.Beatmaps { protected class BeatmapManagerWorkingBeatmap : WorkingBeatmap { - public readonly TextureStore TextureStore; - public readonly ITrackStore TrackStore; - private readonly IResourceStore store; - private readonly Action dereferenceAction; + private readonly TextureStore textureStore; + private readonly ITrackStore trackStore; - public BeatmapManagerWorkingBeatmap(IResourceStore store, TextureStore textureStore, ITrackStore trackStore, BeatmapInfo beatmapInfo, AudioManager audioManager, - Action dereferenceAction) + public BeatmapManagerWorkingBeatmap(IResourceStore store, TextureStore textureStore, ITrackStore trackStore, BeatmapInfo beatmapInfo, AudioManager audioManager) : base(beatmapInfo, audioManager) { this.store = store; - this.dereferenceAction = dereferenceAction; - - TextureStore = textureStore; - TrackStore = trackStore; + this.textureStore = textureStore; + this.trackStore = trackStore; } protected override IBeatmap GetBeatmap() @@ -61,7 +56,7 @@ namespace osu.Game.Beatmaps try { - return TextureStore.Get(getPathForFile(Metadata.BackgroundFile)); + return textureStore.Get(getPathForFile(Metadata.BackgroundFile)); } catch (Exception e) { @@ -74,7 +69,7 @@ namespace osu.Game.Beatmaps { try { - return TrackStore.Get(getPathForFile(Metadata.AudioFile)); + return trackStore.Get(getPathForFile(Metadata.AudioFile)); } catch (Exception e) { @@ -140,11 +135,6 @@ namespace osu.Game.Beatmaps return null; } } - - ~BeatmapManagerWorkingBeatmap() - { - dereferenceAction?.Invoke(TrackStore); - } } } } From faff0a70c49734841f6c1dd36c8df2775d1b867e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 11 Aug 2020 13:48:57 +0900 Subject: [PATCH 0476/1782] Privatise BMWB --- osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs b/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs index aa93833f33..92199789ec 100644 --- a/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs @@ -17,7 +17,7 @@ namespace osu.Game.Beatmaps { public partial class BeatmapManager { - protected class BeatmapManagerWorkingBeatmap : WorkingBeatmap + private class BeatmapManagerWorkingBeatmap : WorkingBeatmap { private readonly IResourceStore store; private readonly TextureStore textureStore; From 031d29ac34bd6684340f51aab051b84db42c4644 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 11 Aug 2020 13:53:23 +0900 Subject: [PATCH 0477/1782] Inspect current track directly --- osu.Game/Overlays/NowPlayingOverlay.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/NowPlayingOverlay.cs b/osu.Game/Overlays/NowPlayingOverlay.cs index 15b189ead6..dc52300cdb 100644 --- a/osu.Game/Overlays/NowPlayingOverlay.cs +++ b/osu.Game/Overlays/NowPlayingOverlay.cs @@ -234,9 +234,9 @@ namespace osu.Game.Overlays pendingBeatmapSwitch = null; } - var track = musicController.TrackLoaded ? musicController.CurrentTrack : null; + var track = musicController.CurrentTrack; - if (track?.IsDummyDevice == false) + if (track.IsDummyDevice == false) { progressBar.EndTime = track.Length; progressBar.CurrentTime = track.CurrentTime; From 8bfe6ba27c275208f665e6c125b73fade6c6fdd7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 11 Aug 2020 23:04:00 +0900 Subject: [PATCH 0478/1782] Fix informational overlays not hiding each other correctly --- osu.Game/OsuGame.cs | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 623c677991..053eb01dcd 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -683,9 +683,8 @@ namespace osu.Game { overlay.State.ValueChanged += state => { - if (state.NewValue == Visibility.Hidden) return; - - informationalOverlays.Where(o => o != overlay).ForEach(o => o.Hide()); + if (state.NewValue != Visibility.Hidden) + showOverlayAboveOthers(overlay, informationalOverlays); }; } @@ -699,12 +698,8 @@ namespace osu.Game // informational overlays should be dismissed on a show or hide of a full overlay. informationalOverlays.ForEach(o => o.Hide()); - if (state.NewValue == Visibility.Hidden) return; - - singleDisplayOverlays.Where(o => o != overlay).ForEach(o => o.Hide()); - - if (!overlay.IsPresent) - overlayContent.ChangeChildDepth(overlay, (float)-Clock.CurrentTime); + if (state.NewValue != Visibility.Hidden) + showOverlayAboveOthers(overlay, singleDisplayOverlays); }; } @@ -729,6 +724,15 @@ namespace osu.Game notifications.State.ValueChanged += _ => updateScreenOffset(); } + private void showOverlayAboveOthers(OverlayContainer overlay, OverlayContainer[] otherOverlays) + { + otherOverlays.Where(o => o != overlay).ForEach(o => o.Hide()); + + // show above others if not visible at all, else leave at current depth. + if (!overlay.IsPresent) + overlayContent.ChangeChildDepth(overlay, (float)-Clock.CurrentTime); + } + public class GameIdleTracker : IdleTracker { private InputManager inputManager; From 070d71ec27fa74eec6d27fa551dd5162ab628d4e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 12 Aug 2020 00:48:38 +0900 Subject: [PATCH 0479/1782] More cleanups --- .../Skinning/TestSceneDrawableTaikoMascot.cs | 4 ++-- .../Skinning/TestSceneTaikoPlayfield.cs | 2 +- osu.Game.Tests/Skins/TestSceneBeatmapSkinResources.cs | 2 +- .../Visual/Gameplay/TestSceneNightcoreBeatContainer.cs | 4 ++-- osu.Game/Overlays/NowPlayingOverlay.cs | 2 +- osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs | 3 --- osu.Game/Screens/Edit/Editor.cs | 4 ++-- osu.Game/Screens/Edit/EditorClock.cs | 4 ++-- osu.Game/Screens/Menu/IntroScreen.cs | 2 +- 9 files changed, 12 insertions(+), 15 deletions(-) diff --git a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableTaikoMascot.cs b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableTaikoMascot.cs index ba5aef5968..6141bf062e 100644 --- a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableTaikoMascot.cs +++ b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableTaikoMascot.cs @@ -175,11 +175,11 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning private void createDrawableRuleset() { - AddUntilStep("wait for beatmap to be loaded", () => MusicController.TrackLoaded); + AddUntilStep("wait for beatmap to be loaded", () => MusicController.CurrentTrack.TrackLoaded); AddStep("create drawable ruleset", () => { - MusicController.Play(true); + MusicController.CurrentTrack.Start(); SetContents(() => { diff --git a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneTaikoPlayfield.cs b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneTaikoPlayfield.cs index 5c54393fb8..a3d3bc81c4 100644 --- a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneTaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneTaikoPlayfield.cs @@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning beatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = 1000 }); - MusicController.Play(true); + MusicController.CurrentTrack.Start(); }); AddStep("Load playfield", () => SetContents(() => new TaikoPlayfield(new ControlPointInfo()) diff --git a/osu.Game.Tests/Skins/TestSceneBeatmapSkinResources.cs b/osu.Game.Tests/Skins/TestSceneBeatmapSkinResources.cs index 03faee9ad2..49b40daf99 100644 --- a/osu.Game.Tests/Skins/TestSceneBeatmapSkinResources.cs +++ b/osu.Game.Tests/Skins/TestSceneBeatmapSkinResources.cs @@ -29,6 +29,6 @@ namespace osu.Game.Tests.Skins public void TestRetrieveOggSample() => AddAssert("sample is non-null", () => Beatmap.Value.Skin.GetSample(new SampleInfo("sample")) != null); [Test] - public void TestRetrieveOggTrack() => AddAssert("track is non-null", () => MusicController.CurrentTrack.IsDummyDevice == false); + public void TestRetrieveOggTrack() => AddAssert("track is non-null", () => !MusicController.CurrentTrack.IsDummyDevice); } } diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneNightcoreBeatContainer.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneNightcoreBeatContainer.cs index ce99d85e92..53e06a632e 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneNightcoreBeatContainer.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneNightcoreBeatContainer.cs @@ -19,8 +19,8 @@ namespace osu.Game.Tests.Visual.Gameplay Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo); - MusicController.Play(true); - MusicController.SeekTo(Beatmap.Value.Beatmap.HitObjects.First().StartTime - 1000); + MusicController.CurrentTrack.Start(); + MusicController.CurrentTrack.Seek(Beatmap.Value.Beatmap.HitObjects.First().StartTime - 1000); Add(new ModNightcore.NightcoreBeatContainer()); diff --git a/osu.Game/Overlays/NowPlayingOverlay.cs b/osu.Game/Overlays/NowPlayingOverlay.cs index dc52300cdb..af692486b7 100644 --- a/osu.Game/Overlays/NowPlayingOverlay.cs +++ b/osu.Game/Overlays/NowPlayingOverlay.cs @@ -236,7 +236,7 @@ namespace osu.Game.Overlays var track = musicController.CurrentTrack; - if (track.IsDummyDevice == false) + if (!track.IsDummyDevice) { progressBar.EndTime = track.Length; progressBar.CurrentTime = track.CurrentTime; diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs index d556d948f6..e1702d3eff 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Diagnostics; using osu.Framework.Allocation; using osu.Framework.Audio.Track; using osu.Framework.Bindables; @@ -64,8 +63,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline waveform.Waveform = b.NewValue.Waveform; track = musicController.CurrentTrack; - Debug.Assert(track != null); - if (track.Length > 0) { MaxZoom = getZoomLevelForVisibleMilliseconds(500); diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 78fa6c74b0..6722d9179c 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -82,7 +82,7 @@ namespace osu.Game.Screens.Edit beatDivisor.BindValueChanged(divisor => Beatmap.Value.BeatmapInfo.BeatDivisor = divisor.NewValue); // Todo: should probably be done at a DrawableRuleset level to share logic with Player. - clock = new EditorClock(Beatmap.Value, musicController.CurrentTrack.Length, beatDivisor) { IsCoupled = false }; + clock = new EditorClock(Beatmap.Value, beatDivisor) { IsCoupled = false }; clock.ChangeSource(musicController.CurrentTrack); dependencies.CacheAs(clock); @@ -348,7 +348,7 @@ namespace osu.Game.Screens.Edit private void resetTrack(bool seekToStart = false) { - musicController.Stop(); + musicController.CurrentTrack.Stop(); if (seekToStart) { diff --git a/osu.Game/Screens/Edit/EditorClock.cs b/osu.Game/Screens/Edit/EditorClock.cs index fbfa397795..d0e265adb0 100644 --- a/osu.Game/Screens/Edit/EditorClock.cs +++ b/osu.Game/Screens/Edit/EditorClock.cs @@ -25,8 +25,8 @@ namespace osu.Game.Screens.Edit private readonly DecoupleableInterpolatingFramedClock underlyingClock; - public EditorClock(WorkingBeatmap beatmap, double trackLength, BindableBeatDivisor beatDivisor) - : this(beatmap.Beatmap.ControlPointInfo, trackLength, beatDivisor) + public EditorClock(WorkingBeatmap beatmap, BindableBeatDivisor beatDivisor) + : this(beatmap.Beatmap.ControlPointInfo, beatmap.GetTrack().Length, beatDivisor) { } diff --git a/osu.Game/Screens/Menu/IntroScreen.cs b/osu.Game/Screens/Menu/IntroScreen.cs index 6e85abf7dc..389629445c 100644 --- a/osu.Game/Screens/Menu/IntroScreen.cs +++ b/osu.Game/Screens/Menu/IntroScreen.cs @@ -115,7 +115,7 @@ namespace osu.Game.Screens.Menu if (setInfo != null) { initialBeatmap = beatmaps.GetWorkingBeatmap(setInfo.Beatmaps[0]); - UsingThemedIntro = initialBeatmap.GetTrack().IsDummyDevice == false; + UsingThemedIntro = !initialBeatmap.GetTrack().IsDummyDevice; } return UsingThemedIntro; From b66f303e71cf9083c2f764964cf3424bb4433f64 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 12 Aug 2020 00:48:45 +0900 Subject: [PATCH 0480/1782] Add annotation --- osu.Game/Beatmaps/WorkingBeatmap.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs index af6a67ad3f..bec2679103 100644 --- a/osu.Game/Beatmaps/WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/WorkingBeatmap.cs @@ -7,6 +7,7 @@ using System.Diagnostics; using System.Linq; using System.Threading; using System.Threading.Tasks; +using JetBrains.Annotations; using osu.Framework.Audio; using osu.Framework.Audio.Track; using osu.Framework.Graphics.Textures; @@ -249,6 +250,7 @@ namespace osu.Game.Beatmaps protected abstract Texture GetBackground(); private readonly RecyclableLazy background; + [NotNull] public Track GetTrack() => GetBeatmapTrack() ?? GetVirtualTrack(1000); protected abstract Track GetBeatmapTrack(); From eec94e1f535e1a5f9d3a17561bfd1ca6d2e5ccab Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 12 Aug 2020 00:50:56 +0900 Subject: [PATCH 0481/1782] Make track not-null in GameplayClockContainer/FailAnimation --- osu.Game/Screens/Play/FailAnimation.cs | 5 ++++- osu.Game/Screens/Play/GameplayClockContainer.cs | 10 +++------- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/Play/FailAnimation.cs b/osu.Game/Screens/Play/FailAnimation.cs index 122ae505ca..6ebee209e2 100644 --- a/osu.Game/Screens/Play/FailAnimation.cs +++ b/osu.Game/Screens/Play/FailAnimation.cs @@ -6,6 +6,7 @@ using osu.Framework.Bindables; using osu.Game.Rulesets.UI; using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using osu.Framework.Allocation; using osu.Framework.Audio.Sample; using osu.Framework.Audio.Track; @@ -26,6 +27,8 @@ namespace osu.Game.Screens.Play public Action OnComplete; private readonly DrawableRuleset drawableRuleset; + + [NotNull] private readonly ITrack track; private readonly BindableDouble trackFreq = new BindableDouble(1); @@ -34,7 +37,7 @@ namespace osu.Game.Screens.Play private SampleChannel failSample; - public FailAnimation(DrawableRuleset drawableRuleset, ITrack track) + public FailAnimation(DrawableRuleset drawableRuleset, [NotNull] ITrack track) { this.drawableRuleset = drawableRuleset; this.track = track; diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index c4f368e1f5..f3466a562e 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Threading.Tasks; using osu.Framework; @@ -27,6 +28,7 @@ namespace osu.Game.Screens.Play private readonly WorkingBeatmap beatmap; private readonly IReadOnlyList mods; + [NotNull] private ITrack track; public readonly BindableBool IsPaused = new BindableBool(); @@ -60,7 +62,7 @@ namespace osu.Game.Screens.Play private readonly FramedOffsetClock platformOffsetClock; - public GameplayClockContainer(ITrack track, WorkingBeatmap beatmap, IReadOnlyList mods, double gameplayStartTime) + public GameplayClockContainer([NotNull] ITrack track, WorkingBeatmap beatmap, IReadOnlyList mods, double gameplayStartTime) { this.beatmap = beatmap; this.mods = mods; @@ -196,9 +198,6 @@ namespace osu.Game.Screens.Play /// public void StopUsingBeatmapClock() { - if (track == null) - return; - removeSourceClockAdjustments(); track = new TrackVirtual(track.Length); @@ -217,8 +216,6 @@ namespace osu.Game.Screens.Play private void updateRate() { - if (track == null) return; - speedAdjustmentsApplied = true; track.ResetSpeedAdjustments(); @@ -233,7 +230,6 @@ namespace osu.Game.Screens.Play { base.Dispose(isDisposing); removeSourceClockAdjustments(); - track = null; } private void removeSourceClockAdjustments() From 688e4479506645bdacee7cdc7ae9ee9deaa5f1b4 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 12 Aug 2020 01:33:06 +0900 Subject: [PATCH 0482/1782] Fix potential hierarchy mutation from async context --- osu.Game/Overlays/MusicController.cs | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index 813ad26ae4..c18b564b4f 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -317,11 +317,28 @@ namespace osu.Game.Overlays private void changeTrack() { - CurrentTrack.Expire(); - CurrentTrack = new DrawableTrack(current.GetTrack()); - CurrentTrack.Completed += () => onTrackCompleted(current); + var lastTrack = CurrentTrack; - AddInternal(CurrentTrack); + var newTrack = new DrawableTrack(current.GetTrack()); + newTrack.Completed += () => onTrackCompleted(current); + + CurrentTrack = newTrack; + + // At this point we may potentially be in an async context from tests. This is extremely dangerous but we have to make do for now. + // CurrentTrack is immediately updated above for situations where a immediate knowledge about the new track is required, + // but the mutation of the hierarchy is scheduled to avoid exceptions. + Schedule(() => + { + lastTrack.Expire(); + + if (newTrack == CurrentTrack) + AddInternal(newTrack); + else + { + // If the track has changed via changeTrack() being called multiple times in a single update, force disposal on the old track. + newTrack.Dispose(); + } + }); } private void onTrackCompleted(WorkingBeatmap workingBeatmap) From e47a1eb313f86d9c0f635e58545644139d5cbf34 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 12 Aug 2020 01:41:21 +0900 Subject: [PATCH 0483/1782] Use adjustable ITrack --- osu.Game/Rulesets/Mods/ModDaycore.cs | 4 ++-- osu.Game/Rulesets/Mods/ModNightcore.cs | 4 ++-- osu.Game/Rulesets/Mods/ModRateAdjust.cs | 2 +- osu.Game/Rulesets/Mods/ModTimeRamp.cs | 4 ++-- osu.Game/Screens/Play/FailAnimation.cs | 4 ++-- osu.Game/Screens/Play/GameplayClockContainer.cs | 4 ++-- 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/osu.Game/Rulesets/Mods/ModDaycore.cs b/osu.Game/Rulesets/Mods/ModDaycore.cs index 800312d047..61ad7db706 100644 --- a/osu.Game/Rulesets/Mods/ModDaycore.cs +++ b/osu.Game/Rulesets/Mods/ModDaycore.cs @@ -30,8 +30,8 @@ namespace osu.Game.Rulesets.Mods public override void ApplyToTrack(ITrack track) { // base.ApplyToTrack() intentionally not called (different tempo adjustment is applied) - (track as IAdjustableAudioComponent)?.AddAdjustment(AdjustableProperty.Frequency, freqAdjust); - (track as IAdjustableAudioComponent)?.AddAdjustment(AdjustableProperty.Tempo, tempoAdjust); + 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 4932df08f1..4004953cd1 100644 --- a/osu.Game/Rulesets/Mods/ModNightcore.cs +++ b/osu.Game/Rulesets/Mods/ModNightcore.cs @@ -41,8 +41,8 @@ namespace osu.Game.Rulesets.Mods public override void ApplyToTrack(ITrack track) { // base.ApplyToTrack() intentionally not called (different tempo adjustment is applied) - (track as IAdjustableAudioComponent)?.AddAdjustment(AdjustableProperty.Frequency, freqAdjust); - (track as IAdjustableAudioComponent)?.AddAdjustment(AdjustableProperty.Tempo, tempoAdjust); + track.AddAdjustment(AdjustableProperty.Frequency, freqAdjust); + track.AddAdjustment(AdjustableProperty.Tempo, tempoAdjust); } public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) diff --git a/osu.Game/Rulesets/Mods/ModRateAdjust.cs b/osu.Game/Rulesets/Mods/ModRateAdjust.cs index ae7077c67b..fec21764b0 100644 --- a/osu.Game/Rulesets/Mods/ModRateAdjust.cs +++ b/osu.Game/Rulesets/Mods/ModRateAdjust.cs @@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Mods public virtual void ApplyToTrack(ITrack track) { - (track as IAdjustableAudioComponent)?.AddAdjustment(AdjustableProperty.Tempo, SpeedChange); + track.AddAdjustment(AdjustableProperty.Tempo, SpeedChange); } public virtual void ApplyToSample(SampleChannel sample) diff --git a/osu.Game/Rulesets/Mods/ModTimeRamp.cs b/osu.Game/Rulesets/Mods/ModTimeRamp.cs index b904cf007b..20c8d0f3e7 100644 --- a/osu.Game/Rulesets/Mods/ModTimeRamp.cs +++ b/osu.Game/Rulesets/Mods/ModTimeRamp.cs @@ -89,9 +89,9 @@ namespace osu.Game.Rulesets.Mods private void applyPitchAdjustment(ValueChangedEvent adjustPitchSetting) { // remove existing old adjustment - (track as IAdjustableAudioComponent)?.RemoveAdjustment(adjustmentForPitchSetting(adjustPitchSetting.OldValue), SpeedChange); + track?.RemoveAdjustment(adjustmentForPitchSetting(adjustPitchSetting.OldValue), SpeedChange); - (track as IAdjustableAudioComponent)?.AddAdjustment(adjustmentForPitchSetting(adjustPitchSetting.NewValue), SpeedChange); + track?.AddAdjustment(adjustmentForPitchSetting(adjustPitchSetting.NewValue), SpeedChange); } private AdjustableProperty adjustmentForPitchSetting(bool adjustPitchSettingValue) diff --git a/osu.Game/Screens/Play/FailAnimation.cs b/osu.Game/Screens/Play/FailAnimation.cs index 6ebee209e2..dade904180 100644 --- a/osu.Game/Screens/Play/FailAnimation.cs +++ b/osu.Game/Screens/Play/FailAnimation.cs @@ -69,7 +69,7 @@ namespace osu.Game.Screens.Play Expire(); }); - (track as IAdjustableAudioComponent)?.AddAdjustment(AdjustableProperty.Frequency, trackFreq); + track.AddAdjustment(AdjustableProperty.Frequency, trackFreq); applyToPlayfield(drawableRuleset.Playfield); drawableRuleset.Playfield.HitObjectContainer.FlashColour(Color4.Red, 500); @@ -108,7 +108,7 @@ namespace osu.Game.Screens.Play protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); - (track as IAdjustableAudioComponent)?.RemoveAdjustment(AdjustableProperty.Frequency, trackFreq); + track?.RemoveAdjustment(AdjustableProperty.Frequency, trackFreq); } } } diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index f3466a562e..59e26cdf55 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -219,8 +219,8 @@ namespace osu.Game.Screens.Play speedAdjustmentsApplied = true; track.ResetSpeedAdjustments(); - (track as IAdjustableAudioComponent)?.AddAdjustment(AdjustableProperty.Frequency, pauseFreqAdjust); - (track as IAdjustableAudioComponent)?.AddAdjustment(AdjustableProperty.Tempo, UserPlaybackRate); + track.AddAdjustment(AdjustableProperty.Frequency, pauseFreqAdjust); + track.AddAdjustment(AdjustableProperty.Tempo, UserPlaybackRate); foreach (var mod in mods.OfType()) mod.ApplyToTrack(track); From c0031955c9ed6c0b74fd655366f988b33ba34ae6 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 12 Aug 2020 01:50:18 +0900 Subject: [PATCH 0484/1782] Update with further framework changes --- osu.Game/Screens/Menu/IntroTriangles.cs | 2 +- osu.Game/Screens/Play/GameplayClockContainer.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Menu/IntroTriangles.cs b/osu.Game/Screens/Menu/IntroTriangles.cs index 86f7dbdd6f..a9ef20436f 100644 --- a/osu.Game/Screens/Menu/IntroTriangles.cs +++ b/osu.Game/Screens/Menu/IntroTriangles.cs @@ -59,7 +59,7 @@ namespace osu.Game.Screens.Menu LoadComponentAsync(new TrianglesIntroSequence(logo, background) { RelativeSizeAxes = Axes.Both, - Clock = new FramedClock(UsingThemedIntro ? (IAdjustableClock)Track : null), + Clock = new FramedClock(UsingThemedIntro ? Track : null), LoadMenu = LoadMenu }, t => { diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index 59e26cdf55..f0bbcf957a 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -132,7 +132,7 @@ namespace osu.Game.Screens.Play Schedule(() => { - adjustableClock.ChangeSource((IAdjustableClock)track); + adjustableClock.ChangeSource(track); updateRate(); if (!IsPaused.Value) @@ -201,7 +201,7 @@ namespace osu.Game.Screens.Play removeSourceClockAdjustments(); track = new TrackVirtual(track.Length); - adjustableClock.ChangeSource((IAdjustableClock)track); + adjustableClock.ChangeSource(track); } protected override void Update() From 84655b0798d16f462ec24bb9727c91c2edcde55e Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 11 Aug 2020 20:17:29 +0300 Subject: [PATCH 0485/1782] Change hover colour for news title --- osu.Game/Overlays/Dashboard/Home/News/NewsTitleLink.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Dashboard/Home/News/NewsTitleLink.cs b/osu.Game/Overlays/Dashboard/Home/News/NewsTitleLink.cs index da98c92bbe..d6a3a69fe0 100644 --- a/osu.Game/Overlays/Dashboard/Home/News/NewsTitleLink.cs +++ b/osu.Game/Overlays/Dashboard/Home/News/NewsTitleLink.cs @@ -24,7 +24,7 @@ namespace osu.Game.Overlays.Dashboard.Home.News } [BackgroundDependencyLoader] - private void load(GameHost host) + private void load(GameHost host, OverlayColourProvider colourProvider) { Child = new TextFlowContainer(t => { @@ -36,6 +36,8 @@ namespace osu.Game.Overlays.Dashboard.Home.News Text = post.Title }; + HoverColour = colourProvider.Light1; + TooltipText = "view in browser"; Action = () => host.OpenUrlExternally("https://osu.ppy.sh/home/news/" + post.Slug); } From b78ccf8a347ca3995712c4424f8d72f580a929de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 11 Aug 2020 21:28:00 +0200 Subject: [PATCH 0486/1782] Rewrite Spun Out test scene --- .../Mods/TestSceneOsuModSpunOut.cs | 39 ++++++++++++ .../TestSceneSpinnerSpunOut.cs | 59 ------------------- 2 files changed, 39 insertions(+), 59 deletions(-) create mode 100644 osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSpunOut.cs delete mode 100644 osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerSpunOut.cs diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSpunOut.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSpunOut.cs new file mode 100644 index 0000000000..1b052600ca --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSpunOut.cs @@ -0,0 +1,39 @@ +// 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.Testing; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Osu.Mods; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Objects.Drawables; +using osuTK; + +namespace osu.Game.Rulesets.Osu.Tests.Mods +{ + public class TestSceneOsuModSpunOut : OsuModTestScene + { + [Test] + public void TestSpinnerAutoCompleted() => CreateModTest(new ModTestData + { + Mod = new OsuModSpunOut(), + Autoplay = false, + Beatmap = new Beatmap + { + HitObjects = new List + { + new Spinner + { + Position = new Vector2(256, 192), + StartTime = 500, + Duration = 2000 + } + } + }, + PassCondition = () => Player.ChildrenOfType().Single().Progress >= 1 + }); + } +} diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerSpunOut.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerSpunOut.cs deleted file mode 100644 index d1210db6b1..0000000000 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerSpunOut.cs +++ /dev/null @@ -1,59 +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.Linq; -using NUnit.Framework; -using osu.Framework.Graphics; -using osu.Game.Beatmaps; -using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Osu.Mods; -using osu.Game.Rulesets.Osu.Objects; -using osu.Game.Rulesets.Osu.Objects.Drawables; -using osu.Game.Tests.Visual; - -namespace osu.Game.Rulesets.Osu.Tests -{ - [TestFixture] - public class TestSceneSpinnerSpunOut : OsuTestScene - { - [SetUp] - public void SetUp() => Schedule(() => - { - SelectedMods.Value = new[] { new OsuModSpunOut() }; - }); - - [Test] - public void TestSpunOut() - { - DrawableSpinner spinner = null; - - AddStep("create spinner", () => spinner = createSpinner()); - - AddUntilStep("wait for end", () => Time.Current > spinner.LifetimeEnd); - - AddAssert("spinner is completed", () => spinner.Progress >= 1); - } - - private DrawableSpinner createSpinner() - { - var spinner = new Spinner - { - StartTime = Time.Current + 500, - EndTime = Time.Current + 2500 - }; - spinner.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); - - var drawableSpinner = new DrawableSpinner(spinner) - { - Anchor = Anchor.Centre - }; - - foreach (var mod in SelectedMods.Value.OfType()) - mod.ApplyToDrawableHitObjects(new[] { drawableSpinner }); - - Add(drawableSpinner); - return drawableSpinner; - } - } -} From 8fe5775ecb82be2a6d52149a4f0393150316ec58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 11 Aug 2020 21:55:20 +0200 Subject: [PATCH 0487/1782] Allow testing mod combinations in ModTestScenes --- osu.Game/Tests/Visual/ModTestScene.cs | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/osu.Game/Tests/Visual/ModTestScene.cs b/osu.Game/Tests/Visual/ModTestScene.cs index 23b5ad0bd8..a71d008eb9 100644 --- a/osu.Game/Tests/Visual/ModTestScene.cs +++ b/osu.Game/Tests/Visual/ModTestScene.cs @@ -40,8 +40,8 @@ namespace osu.Game.Tests.Visual { var mods = new List(SelectedMods.Value); - if (currentTestData.Mod != null) - mods.Add(currentTestData.Mod); + if (currentTestData.Mods != null) + mods.AddRange(currentTestData.Mods); if (currentTestData.Autoplay) mods.Add(ruleset.GetAutoplayMod()); @@ -85,9 +85,18 @@ namespace osu.Game.Tests.Visual public Func PassCondition; /// - /// The this test case tests. + /// The s this test case tests. /// - public Mod Mod; + public IReadOnlyList Mods; + + /// + /// Convenience property for setting if only + /// a single mod is to be tested. + /// + public Mod Mod + { + set => Mods = new[] { value }; + } } } } From 25f59e0489a292b825f60af57d6a8ac9df7e1692 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 11 Aug 2020 21:55:50 +0200 Subject: [PATCH 0488/1782] Add failing test cases --- .../Mods/TestSceneOsuModSpunOut.cs | 50 ++++++++++++++----- 1 file changed, 38 insertions(+), 12 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSpunOut.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSpunOut.cs index 1b052600ca..d8064d36ea 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSpunOut.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSpunOut.cs @@ -1,39 +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 System.Linq; using NUnit.Framework; using osu.Framework.Testing; +using osu.Framework.Utils; using osu.Game.Beatmaps; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces; using osuTK; namespace osu.Game.Rulesets.Osu.Tests.Mods { public class TestSceneOsuModSpunOut : OsuModTestScene { + protected override bool AllowFail => true; + [Test] public void TestSpinnerAutoCompleted() => CreateModTest(new ModTestData { Mod = new OsuModSpunOut(), Autoplay = false, - Beatmap = new Beatmap - { - HitObjects = new List - { - new Spinner - { - Position = new Vector2(256, 192), - StartTime = 500, - Duration = 2000 - } - } - }, + Beatmap = singleSpinnerBeatmap, PassCondition = () => Player.ChildrenOfType().Single().Progress >= 1 }); + + [TestCase(null)] + [TestCase(typeof(OsuModDoubleTime))] + [TestCase(typeof(OsuModHalfTime))] + public void TestSpinRateUnaffectedByMods(Type additionalModType) + { + var mods = new List { new OsuModSpunOut() }; + if (additionalModType != null) + mods.Add((Mod)Activator.CreateInstance(additionalModType)); + + CreateModTest(new ModTestData + { + Mods = mods, + Autoplay = false, + Beatmap = singleSpinnerBeatmap, + PassCondition = () => Precision.AlmostEquals(Player.ChildrenOfType().Single().SpinsPerMinute, 286, 1) + }); + } + + private Beatmap singleSpinnerBeatmap => new Beatmap + { + HitObjects = new List + { + new Spinner + { + Position = new Vector2(256, 192), + StartTime = 500, + Duration = 2000 + } + } + }; } } From bcaaf2527892e3ac2a02fc64e8226f5664a4a5f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 11 Aug 2020 22:04:18 +0200 Subject: [PATCH 0489/1782] Fix Spun Out mod being affected by rate-changing mods --- osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs b/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs index 47d765fecd..2816073e8c 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs @@ -41,7 +41,12 @@ namespace osu.Game.Rulesets.Osu.Mods var spinner = (DrawableSpinner)drawable; spinner.RotationTracker.Tracking = true; - spinner.RotationTracker.AddRotation(MathUtils.RadiansToDegrees((float)spinner.Clock.ElapsedFrameTime * 0.03f)); + + // because the spinner is under the gameplay clock, it is affected by rate adjustments on the track; + // for that reason using ElapsedFrameTime directly leads to fewer SPM with Half Time and more SPM with Double Time. + // for spinners we want the real (wall clock) elapsed time; to achieve that, unapply the clock rate locally here. + var rateIndependentElapsedTime = spinner.Clock.ElapsedFrameTime / spinner.Clock.Rate; + spinner.RotationTracker.AddRotation(MathUtils.RadiansToDegrees((float)rateIndependentElapsedTime * 0.03f)); } } } From 4f3f95540be8836006ce2bffee47f51efa987617 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 11 Aug 2020 22:34:46 +0200 Subject: [PATCH 0490/1782] Check for zero rate to prevent crashes on unpause --- osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs b/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs index 2816073e8c..f080e11933 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs @@ -42,6 +42,10 @@ namespace osu.Game.Rulesets.Osu.Mods spinner.RotationTracker.Tracking = true; + // early-return if we were paused to avoid division-by-zero in the subsequent calculations. + if (Precision.AlmostEquals(spinner.Clock.Rate, 0)) + return; + // because the spinner is under the gameplay clock, it is affected by rate adjustments on the track; // for that reason using ElapsedFrameTime directly leads to fewer SPM with Half Time and more SPM with Double Time. // for spinners we want the real (wall clock) elapsed time; to achieve that, unapply the clock rate locally here. From 139c0c75f87e03ad4d86fd524e61c4dea79f0e81 Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Wed, 12 Aug 2020 06:37:25 +0200 Subject: [PATCH 0491/1782] Add documentation for constructor --- osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs index 8c96e59f30..3e744056bb 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs @@ -7,6 +7,7 @@ using System.Globalization; using System.IO; using System.Linq; using System.Text; +using JetBrains.Annotations; using osu.Game.Audio; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.Legacy; @@ -25,7 +26,9 @@ namespace osu.Game.Beatmaps.Formats private readonly IBeatmap beatmap; private readonly LegacyBeatmapSkin beatmapSkin; - public LegacyBeatmapEncoder(IBeatmap beatmap, LegacyBeatmapSkin beatmapSkin = null) + /// The beatmap to encode + /// An optional beatmap skin, for encoding the beatmap's combo colours. + public LegacyBeatmapEncoder(IBeatmap beatmap, [CanBeNull] LegacyBeatmapSkin beatmapSkin) { this.beatmap = beatmap; this.beatmapSkin = beatmapSkin; From 8ffaa49839bdf3902e57c57b0ff429b8c91a2fb1 Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Wed, 12 Aug 2020 06:37:33 +0200 Subject: [PATCH 0492/1782] Handle additional null case --- osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs index 3e744056bb..eb148794de 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs @@ -207,12 +207,9 @@ namespace osu.Game.Beatmaps.Formats private void handleComboColours(TextWriter writer) { - if (beatmapSkin == null) - return; + var colours = beatmapSkin?.Configuration.ComboColours; - var colours = beatmapSkin.Configuration.ComboColours; - - if (colours.Count == 0) + if (colours == null || colours.Count == 0) return; writer.WriteLine("[Colours]"); From 69590113d62c54ec56c86c8bc529a49b0c2e337a Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Wed, 12 Aug 2020 06:38:05 +0200 Subject: [PATCH 0493/1782] Temporary changes --- .../Beatmaps/Formats/LegacyBeatmapEncoderTest.cs | 8 +++++++- osu.Game.Tests/Editing/LegacyEditorBeatmapPatcherTest.cs | 8 +++++++- osu.Game/Screens/Edit/EditorChangeHandler.cs | 8 +++++++- 3 files changed, 21 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs index 30331e98d2..fba63f8539 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs @@ -10,6 +10,7 @@ using System.Text; using NUnit.Framework; using osu.Framework.Audio.Track; using osu.Framework.Graphics.Textures; +using osu.Framework.IO.Stores; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.Formats; @@ -19,6 +20,7 @@ using osu.Game.Rulesets.Catch; using osu.Game.Rulesets.Mania; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Taiko; +using osu.Game.Skinning; using osu.Game.Tests.Resources; namespace osu.Game.Tests.Beatmaps.Formats @@ -61,7 +63,11 @@ namespace osu.Game.Tests.Beatmaps.Formats var stream = new MemoryStream(); using (var writer = new StreamWriter(stream, Encoding.UTF8, 1024, true)) - new LegacyBeatmapEncoder(beatmap).Encode(writer); + using (var rs = new ResourceStore()) + { + var skin = new LegacyBeatmapSkin(beatmap.BeatmapInfo, rs, null); + new LegacyBeatmapEncoder(beatmap, skin).Encode(writer); + } stream.Position = 0; diff --git a/osu.Game.Tests/Editing/LegacyEditorBeatmapPatcherTest.cs b/osu.Game.Tests/Editing/LegacyEditorBeatmapPatcherTest.cs index ff17f23d50..74b6f66d85 100644 --- a/osu.Game.Tests/Editing/LegacyEditorBeatmapPatcherTest.cs +++ b/osu.Game.Tests/Editing/LegacyEditorBeatmapPatcherTest.cs @@ -4,6 +4,7 @@ using System.IO; using System.Text; using NUnit.Framework; +using osu.Framework.IO.Stores; using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Formats; @@ -14,6 +15,7 @@ using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Beatmaps; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Screens.Edit; +using osu.Game.Skinning; using osuTK; using Decoder = osu.Game.Beatmaps.Formats.Decoder; @@ -351,7 +353,11 @@ namespace osu.Game.Tests.Editing using (var encoded = new MemoryStream()) { using (var sw = new StreamWriter(encoded)) - new LegacyBeatmapEncoder(beatmap).Encode(sw); + using (var rs = new ResourceStore()) + { + var skin = new LegacyBeatmapSkin(beatmap.BeatmapInfo, rs, null); + new LegacyBeatmapEncoder(beatmap, skin).Encode(sw); + } return encoded.ToArray(); } diff --git a/osu.Game/Screens/Edit/EditorChangeHandler.cs b/osu.Game/Screens/Edit/EditorChangeHandler.cs index 1553c2d2ef..6393093c74 100644 --- a/osu.Game/Screens/Edit/EditorChangeHandler.cs +++ b/osu.Game/Screens/Edit/EditorChangeHandler.cs @@ -6,8 +6,10 @@ using System.Collections.Generic; using System.IO; using System.Text; using osu.Framework.Bindables; +using osu.Framework.IO.Stores; using osu.Game.Beatmaps.Formats; using osu.Game.Rulesets.Objects; +using osu.Game.Skinning; namespace osu.Game.Screens.Edit { @@ -85,7 +87,11 @@ namespace osu.Game.Screens.Edit using (var stream = new MemoryStream()) { using (var sw = new StreamWriter(stream, Encoding.UTF8, 1024, true)) - new LegacyBeatmapEncoder(editorBeatmap).Encode(sw); + using (var rs = new ResourceStore()) + { + var skin = new LegacyBeatmapSkin(editorBeatmap.BeatmapInfo, rs, null); + new LegacyBeatmapEncoder(editorBeatmap, skin).Encode(sw); + } savedStates.Add(stream.ToArray()); } From f3202fb123807ed3ef1b1a693e88f80f9ff42296 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 12 Aug 2020 11:24:26 +0300 Subject: [PATCH 0494/1782] Naming adjustments --- osu.Game.Tests/Visual/Online/TestSceneHomeNewsPanel.cs | 6 +++--- .../News/{HomeNewsPanel.cs => FeaturedNewsItemPanel.cs} | 4 ++-- .../Home/News/{CollapsedNewsPanel.cs => NewsGroupItem.cs} | 4 ++-- .../News/{HomeNewsGroupPanel.cs => NewsItemGroupPanel.cs} | 8 ++++---- .../{HomeShowMoreNewsPanel.cs => ShowMoreNewsPanel.cs} | 4 ++-- 5 files changed, 13 insertions(+), 13 deletions(-) rename osu.Game/Overlays/Dashboard/Home/News/{HomeNewsPanel.cs => FeaturedNewsItemPanel.cs} (98%) rename osu.Game/Overlays/Dashboard/Home/News/{CollapsedNewsPanel.cs => NewsGroupItem.cs} (97%) rename osu.Game/Overlays/Dashboard/Home/News/{HomeNewsGroupPanel.cs => NewsItemGroupPanel.cs} (76%) rename osu.Game/Overlays/Dashboard/Home/News/{HomeShowMoreNewsPanel.cs => ShowMoreNewsPanel.cs} (93%) diff --git a/osu.Game.Tests/Visual/Online/TestSceneHomeNewsPanel.cs b/osu.Game.Tests/Visual/Online/TestSceneHomeNewsPanel.cs index b1c0c5adcd..a1251ca793 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneHomeNewsPanel.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneHomeNewsPanel.cs @@ -30,7 +30,7 @@ namespace osu.Game.Tests.Visual.Online Spacing = new Vector2(0, 5), Children = new Drawable[] { - new HomeNewsPanel(new APINewsPost + new FeaturedNewsItemPanel(new APINewsPost { Title = "This post has an image which starts with \"/\" and has many authors!", Preview = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.", @@ -38,7 +38,7 @@ namespace osu.Game.Tests.Visual.Online PublishedAt = DateTimeOffset.Now, Slug = "2020-07-16-summer-theme-park-2020-voting-open" }), - new HomeNewsGroupPanel(new List + new NewsItemGroupPanel(new List { new APINewsPost { @@ -53,7 +53,7 @@ namespace osu.Game.Tests.Visual.Online PublishedAt = DateTimeOffset.Now, } }), - new HomeShowMoreNewsPanel() + new ShowMoreNewsPanel() } }); } diff --git a/osu.Game/Overlays/Dashboard/Home/News/HomeNewsPanel.cs b/osu.Game/Overlays/Dashboard/Home/News/FeaturedNewsItemPanel.cs similarity index 98% rename from osu.Game/Overlays/Dashboard/Home/News/HomeNewsPanel.cs rename to osu.Game/Overlays/Dashboard/Home/News/FeaturedNewsItemPanel.cs index ca56c33315..ee88469e2f 100644 --- a/osu.Game/Overlays/Dashboard/Home/News/HomeNewsPanel.cs +++ b/osu.Game/Overlays/Dashboard/Home/News/FeaturedNewsItemPanel.cs @@ -18,11 +18,11 @@ using osuTK.Graphics; namespace osu.Game.Overlays.Dashboard.Home.News { - public class HomeNewsPanel : HomePanel + public class FeaturedNewsItemPanel : HomePanel { private readonly APINewsPost post; - public HomeNewsPanel(APINewsPost post) + public FeaturedNewsItemPanel(APINewsPost post) { this.post = post; } diff --git a/osu.Game/Overlays/Dashboard/Home/News/CollapsedNewsPanel.cs b/osu.Game/Overlays/Dashboard/Home/News/NewsGroupItem.cs similarity index 97% rename from osu.Game/Overlays/Dashboard/Home/News/CollapsedNewsPanel.cs rename to osu.Game/Overlays/Dashboard/Home/News/NewsGroupItem.cs index 7dbc6a8f87..dc4f3f8c92 100644 --- a/osu.Game/Overlays/Dashboard/Home/News/CollapsedNewsPanel.cs +++ b/osu.Game/Overlays/Dashboard/Home/News/NewsGroupItem.cs @@ -12,11 +12,11 @@ using osu.Game.Online.API.Requests.Responses; namespace osu.Game.Overlays.Dashboard.Home.News { - public class CollapsedNewsPanel : CompositeDrawable + public class NewsGroupItem : CompositeDrawable { private readonly APINewsPost post; - public CollapsedNewsPanel(APINewsPost post) + public NewsGroupItem(APINewsPost post) { this.post = post; } diff --git a/osu.Game/Overlays/Dashboard/Home/News/HomeNewsGroupPanel.cs b/osu.Game/Overlays/Dashboard/Home/News/NewsItemGroupPanel.cs similarity index 76% rename from osu.Game/Overlays/Dashboard/Home/News/HomeNewsGroupPanel.cs rename to osu.Game/Overlays/Dashboard/Home/News/NewsItemGroupPanel.cs index 6007f1408b..c1d5a87ef5 100644 --- a/osu.Game/Overlays/Dashboard/Home/News/HomeNewsGroupPanel.cs +++ b/osu.Game/Overlays/Dashboard/Home/News/NewsItemGroupPanel.cs @@ -10,11 +10,11 @@ using osu.Game.Online.API.Requests.Responses; namespace osu.Game.Overlays.Dashboard.Home.News { - public class HomeNewsGroupPanel : HomePanel + public class NewsItemGroupPanel : HomePanel { private readonly List posts; - public HomeNewsGroupPanel(List posts) + public NewsItemGroupPanel(List posts) { this.posts = posts; } @@ -24,12 +24,12 @@ namespace osu.Game.Overlays.Dashboard.Home.News { Content.Padding = new MarginPadding { Vertical = 5 }; - Child = new FillFlowContainer + Child = new FillFlowContainer { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Direction = FillDirection.Vertical, - Children = posts.Select(p => new CollapsedNewsPanel(p)).ToArray() + Children = posts.Select(p => new NewsGroupItem(p)).ToArray() }; } } diff --git a/osu.Game/Overlays/Dashboard/Home/News/HomeShowMoreNewsPanel.cs b/osu.Game/Overlays/Dashboard/Home/News/ShowMoreNewsPanel.cs similarity index 93% rename from osu.Game/Overlays/Dashboard/Home/News/HomeShowMoreNewsPanel.cs rename to osu.Game/Overlays/Dashboard/Home/News/ShowMoreNewsPanel.cs index abb4bd7969..d25df6f189 100644 --- a/osu.Game/Overlays/Dashboard/Home/News/HomeShowMoreNewsPanel.cs +++ b/osu.Game/Overlays/Dashboard/Home/News/ShowMoreNewsPanel.cs @@ -10,7 +10,7 @@ using osuTK.Graphics; namespace osu.Game.Overlays.Dashboard.Home.News { - public class HomeShowMoreNewsPanel : OsuHoverContainer + public class ShowMoreNewsPanel : OsuHoverContainer { protected override IEnumerable EffectTargets => new[] { text }; @@ -19,7 +19,7 @@ namespace osu.Game.Overlays.Dashboard.Home.News private OsuSpriteText text; - public HomeShowMoreNewsPanel() + public ShowMoreNewsPanel() { RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; From b10cddf625c9a51bfeac4e22a9871818486cd1f9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 12 Aug 2020 23:28:08 +0900 Subject: [PATCH 0495/1782] 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 a384ad4c34..7c0cb9271e 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,6 +52,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index b38ef38ec2..bb89492e8b 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -24,7 +24,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 00ddd94d53..8364caa42f 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -80,7 +80,7 @@ - + From 00f8bb7c3e36ccb09de2b1634a9f5161b3a8cd67 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 12 Aug 2020 23:28:45 +0900 Subject: [PATCH 0496/1782] 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 7c0cb9271e..241b836aac 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -51,7 +51,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index bb89492e8b..63267e1494 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 8364caa42f..3500eb75dc 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -71,7 +71,7 @@ - + From 45876bc55aca3d28034c8ba7d6b3ea5322708c23 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 12 Aug 2020 23:50:33 +0900 Subject: [PATCH 0497/1782] Fix reference to non-existent variable --- 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 ab6cf60a79..3c559765d4 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs @@ -194,7 +194,7 @@ namespace osu.Game.Rulesets.Osu.Tests addSeekStep(0); - AddStep("adjust track rate", () => track.AddAdjustment(AdjustableProperty.Tempo, new BindableDouble(rate))); + AddStep("adjust track rate", () => MusicController.CurrentTrack.AddAdjustment(AdjustableProperty.Tempo, new BindableDouble(rate))); // autoplay replay frames use track time; // if a spin takes 1000ms in track time and we're playing with a 2x rate adjustment, the spin will take 500ms of *real* time. // therefore we need to apply the rate adjustment to the replay itself to change from track time to real time, From 91e28b849de0ed31402536b63069b8b2d5383f4b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 13 Aug 2020 00:29:23 +0900 Subject: [PATCH 0498/1782] Fix incorrect BeatmapManager construction --- .../Visual/UserInterface/TestSceneDeleteLocalScore.cs | 3 ++- osu.Game/Beatmaps/BeatmapManager.cs | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs index eb4750a597..e54292f7cc 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs @@ -6,6 +6,7 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Graphics; using osu.Framework.Allocation; +using osu.Framework.Audio; using osu.Framework.Graphics.Cursor; using osu.Framework.Platform; using osu.Framework.Testing; @@ -79,7 +80,7 @@ namespace osu.Game.Tests.Visual.UserInterface var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); dependencies.Cache(rulesetStore = new RulesetStore(ContextFactory)); - dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, ContextFactory, rulesetStore, null, Audio, dependencies.Get(), Beatmap.Default)); + dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, ContextFactory, rulesetStore, null, dependencies.Get(), dependencies.Get(), Beatmap.Default)); dependencies.Cache(scoreManager = new ScoreManager(rulesetStore, () => beatmapManager, LocalStorage, null, ContextFactory)); beatmap = beatmapManager.Import(TestResources.GetTestBeatmapForImport()).Result.Beatmaps[0]; diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 6f01580998..0cadcf5947 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -9,6 +9,7 @@ using System.Linq.Expressions; using System.Text; using System.Threading; using System.Threading.Tasks; +using JetBrains.Annotations; using Microsoft.EntityFrameworkCore; using osu.Framework.Audio; using osu.Framework.Audio.Track; @@ -67,7 +68,7 @@ namespace osu.Game.Beatmaps private readonly TextureStore textureStore; private readonly ITrackStore trackStore; - public BeatmapManager(Storage storage, IDatabaseContextFactory contextFactory, RulesetStore rulesets, IAPIProvider api, AudioManager audioManager, GameHost host = null, + public BeatmapManager(Storage storage, IDatabaseContextFactory contextFactory, RulesetStore rulesets, IAPIProvider api, [NotNull] AudioManager audioManager, GameHost host = null, WorkingBeatmap defaultBeatmap = null) : base(storage, contextFactory, api, new BeatmapStore(contextFactory), host) { From d2a03f1146dc8257c1ffec299272edeff3f05d03 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 13 Aug 2020 00:59:22 +0900 Subject: [PATCH 0499/1782] Refactor TaikoDifficultyHitObject --- .../Preprocessing/TaikoDifficultyHitObject.cs | 13 ++++++------- osu.Game.Rulesets.Taiko/Difficulty/Skills/Rhythm.cs | 4 ++-- .../Difficulty/Skills/Stamina.cs | 10 +++++----- 3 files changed, 13 insertions(+), 14 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/TaikoDifficultyHitObject.cs b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/TaikoDifficultyHitObject.cs index cd45db2119..d0f621f4ad 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/TaikoDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/TaikoDifficultyHitObject.cs @@ -17,21 +17,20 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing public bool StaminaCheese = false; - public readonly double NoteLength; + public readonly int ObjectIndex; - public readonly int N; - - public TaikoDifficultyHitObject(HitObject hitObject, HitObject lastObject, HitObject lastLastObject, double clockRate, int n, IEnumerable commonRhythms) + public TaikoDifficultyHitObject(HitObject hitObject, HitObject lastObject, HitObject lastLastObject, double clockRate, int objectIndex, + IEnumerable commonRhythms) : base(hitObject, lastObject, clockRate) { var currentHit = hitObject as Hit; - NoteLength = DeltaTime; double prevLength = (lastObject.StartTime - lastLastObject.StartTime) / clockRate; - Rhythm = getClosestRhythm(NoteLength / prevLength, commonRhythms); + + Rhythm = getClosestRhythm(DeltaTime / prevLength, commonRhythms); IsKat = currentHit?.Type == HitType.Rim; - N = n; + ObjectIndex = objectIndex; } private TaikoDifficultyHitObjectRhythm getClosestRhythm(double ratio, IEnumerable commonRhythms) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Rhythm.cs b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Rhythm.cs index c3e6ee4d12..31dc93a6b2 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Rhythm.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Rhythm.cs @@ -53,7 +53,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills if (samePattern) // Repetition found! { - int notesSince = hitobject.N - rhythmHistory[start].N; + int notesSince = hitobject.ObjectIndex - rhythmHistory[start].ObjectIndex; penalty *= repetitionPenalty(notesSince); break; } @@ -104,7 +104,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills objectStrain *= repetitionPenalties(hitobject); objectStrain *= patternLengthPenalty(notesSinceRhythmChange); - objectStrain *= speedPenalty(hitobject.NoteLength); + objectStrain *= speedPenalty(hitobject.DeltaTime); notesSinceRhythmChange = 0; diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Stamina.cs b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Stamina.cs index 29c1c3c322..c9a691a2aa 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Stamina.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Stamina.cs @@ -49,14 +49,14 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills TaikoDifficultyHitObject hitObject = (TaikoDifficultyHitObject)current; - if (hitObject.N % 2 == hand) + if (hitObject.ObjectIndex % 2 == hand) { double objectStrain = 1; - if (hitObject.N == 1) + if (hitObject.ObjectIndex == 1) return 1; - notePairDurationHistory.Add(hitObject.NoteLength + offhandObjectDuration); + notePairDurationHistory.Add(hitObject.DeltaTime + offhandObjectDuration); if (notePairDurationHistory.Count > max_history_length) notePairDurationHistory.RemoveAt(0); @@ -65,12 +65,12 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills objectStrain += speedBonus(shortestRecentNote); if (hitObject.StaminaCheese) - objectStrain *= cheesePenalty(hitObject.NoteLength + offhandObjectDuration); + objectStrain *= cheesePenalty(hitObject.DeltaTime + offhandObjectDuration); return objectStrain; } - offhandObjectDuration = hitObject.NoteLength; + offhandObjectDuration = hitObject.DeltaTime; return 0; } From 5010d2044a8b53ed8475dbfde17286485bd64872 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 13 Aug 2020 01:35:56 +0900 Subject: [PATCH 0500/1782] Replace IsKat with HitType --- .../Preprocessing/StaminaCheeseDetector.cs | 15 ++-- .../Preprocessing/TaikoDifficultyHitObject.cs | 4 +- .../Difficulty/Skills/Colour.cs | 87 +++++++++---------- 3 files changed, 49 insertions(+), 57 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/StaminaCheeseDetector.cs b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/StaminaCheeseDetector.cs index b52dad5198..b53bc66f39 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/StaminaCheeseDetector.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/StaminaCheeseDetector.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using osu.Game.Rulesets.Taiko.Objects; namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing { @@ -17,10 +18,10 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing hitObjects = difficultyHitObjects; findRolls(3); findRolls(4); - findTlTap(0, true); - findTlTap(1, true); - findTlTap(0, false); - findTlTap(1, false); + findTlTap(0, HitType.Rim); + findTlTap(1, HitType.Rim); + findTlTap(0, HitType.Centre); + findTlTap(1, HitType.Centre); } private void findRolls(int patternLength) @@ -40,7 +41,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing for (int j = 0; j < patternLength; j++) { - if (history[j].IsKat != history[j + patternLength].IsKat) + if (history[j].HitType != history[j + patternLength].HitType) { isRepeat = false; } @@ -63,13 +64,13 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing } } - private void findTlTap(int parity, bool kat) + private void findTlTap(int parity, HitType type) { int tlLength = -2; for (int i = parity; i < hitObjects.Count; i += 2) { - if (kat == hitObjects[i].IsKat) + if (hitObjects[i].HitType == type) { tlLength += 2; } diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/TaikoDifficultyHitObject.cs b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/TaikoDifficultyHitObject.cs index d0f621f4ad..817e974fe8 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/TaikoDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/TaikoDifficultyHitObject.cs @@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing public class TaikoDifficultyHitObject : DifficultyHitObject { public readonly TaikoDifficultyHitObjectRhythm Rhythm; - public readonly bool IsKat; + public readonly HitType? HitType; public bool StaminaCheese = false; @@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing double prevLength = (lastObject.StartTime - lastLastObject.StartTime) / clockRate; Rhythm = getClosestRhythm(DeltaTime / prevLength, commonRhythms); - IsKat = currentHit?.Type == HitType.Rim; + HitType = currentHit?.Type; ObjectIndex = objectIndex; } diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Colour.cs b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Colour.cs index 7c1623c54e..a348c25331 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Colour.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Colour.cs @@ -12,26 +12,54 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills { public class Colour : Skill { + private const int mono_history_max_length = 5; + protected override double SkillMultiplier => 1; protected override double StrainDecayBase => 0.4; - private NoteColour prevNoteColour = NoteColour.None; + private HitType? previousHitType; private int currentMonoLength = 1; private readonly List monoHistory = new List(); - private const int mono_history_max_length = 5; + + protected override double StrainValueOf(DifficultyHitObject current) + { + if (!(current.LastObject is Hit && current.BaseObject is Hit && current.DeltaTime < 1000)) + { + previousHitType = null; + return 0.0; + } + + var taikoCurrent = (TaikoDifficultyHitObject)current; + + double objectStrain = 0.0; + + if (taikoCurrent.HitType != null && previousHitType != null && taikoCurrent.HitType != previousHitType) + { + objectStrain = 1.0; + + if (monoHistory.Count < 2) + objectStrain = 0.0; + else if ((monoHistory[^1] + currentMonoLength) % 2 == 0) + objectStrain *= sameParityPenalty(); + + objectStrain *= repetitionPenalties(); + currentMonoLength = 1; + } + else + { + currentMonoLength += 1; + } + + previousHitType = taikoCurrent.HitType; + return objectStrain; + } private double sameParityPenalty() { return 0.0; } - private double repetitionPenalty(int notesSince) - { - double n = notesSince; - return Math.Min(1.0, 0.032 * n); - } - private double repetitionPenalties() { double penalty = 1.0; @@ -68,47 +96,10 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills return penalty; } - protected override double StrainValueOf(DifficultyHitObject current) + private double repetitionPenalty(int notesSince) { - if (!(current.LastObject is Hit && current.BaseObject is Hit && current.DeltaTime < 1000)) - { - prevNoteColour = NoteColour.None; - return 0.0; - } - - TaikoDifficultyHitObject hitObject = (TaikoDifficultyHitObject)current; - - double objectStrain = 0.0; - - NoteColour noteColour = hitObject.IsKat ? NoteColour.Ka : NoteColour.Don; - - if (noteColour == NoteColour.Don && prevNoteColour == NoteColour.Ka || - noteColour == NoteColour.Ka && prevNoteColour == NoteColour.Don) - { - objectStrain = 1.0; - - if (monoHistory.Count < 2) - objectStrain = 0.0; - else if ((monoHistory[^1] + currentMonoLength) % 2 == 0) - objectStrain *= sameParityPenalty(); - - objectStrain *= repetitionPenalties(); - currentMonoLength = 1; - } - else - { - currentMonoLength += 1; - } - - prevNoteColour = noteColour; - return objectStrain; - } - - private enum NoteColour - { - Don, - Ka, - None + double n = notesSince; + return Math.Min(1.0, 0.032 * n); } } } From 27cd9e119aa27be6ce39ee7989f30c5ead5437db Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 13 Aug 2020 12:04:32 +0900 Subject: [PATCH 0501/1782] Delay beatmap load until after transition has finished Previously the beatmap would begin loading at the same time the `PlayerLoader` class was. This can cause a horribly visible series of stutters, especially when a storyboard is involved. Obviously we should be aiming to reduce the stutters via changes to the beatmap load process (such as incremental storyboard loading, `DrawableHitObject` pooling, etc.) but this improves user experience tenfold in the mean time. --- osu.Game/Screens/Play/PlayerLoader.cs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index 93a734589c..d32fae1b90 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -153,8 +153,6 @@ namespace osu.Game.Screens.Play { base.OnEntering(last); - prepareNewPlayer(); - content.ScaleTo(0.7f); Background?.FadeColour(Color4.White, 800, Easing.OutQuint); @@ -172,11 +170,6 @@ namespace osu.Game.Screens.Play contentIn(); - MetadataInfo.Loading = true; - - // we will only be resumed if the player has requested a re-run (see restartRequested). - prepareNewPlayer(); - this.Delay(400).Schedule(pushWhenLoaded); } @@ -257,6 +250,9 @@ namespace osu.Game.Screens.Play private void prepareNewPlayer() { + if (!this.IsCurrentScreen()) + return; + var restartCount = player?.RestartCount + 1 ?? 0; player = createPlayer(); @@ -274,8 +270,10 @@ namespace osu.Game.Screens.Play private void contentIn() { - content.ScaleTo(1, 650, Easing.OutQuint); + MetadataInfo.Loading = true; + content.FadeInFromZero(400); + content.ScaleTo(1, 650, Easing.OutQuint).Then().Schedule(prepareNewPlayer); } private void contentOut() From 99bea6b8e9e5d5a586179c385d7a14c246c522f4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 13 Aug 2020 12:52:35 +0900 Subject: [PATCH 0502/1782] Add missing null check (player construction is potentially delayed now) --- 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 d32fae1b90..dcf84a8821 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -68,7 +68,7 @@ namespace osu.Game.Screens.Play private bool readyForPush => // don't push unless the player is completely loaded - player.LoadState == LoadState.Ready + player?.LoadState == LoadState.Ready // don't push if the user is hovering one of the panes, unless they are idle. && (IsHovered || idleTracker.IsIdle.Value) // don't push if the user is dragging a slider or otherwise. From 5b536aebe71a49f2c4eab4edd508985d00bdcdf3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 13 Aug 2020 12:53:37 +0900 Subject: [PATCH 0503/1782] Add missing null checks and avoid cross-test pollution --- osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs index 4c73065087..c34b523c97 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs @@ -49,6 +49,8 @@ namespace osu.Game.Tests.Visual.Gameplay /// An action to run after container load. public void ResetPlayer(bool interactive, Action beforeLoadAction = null, Action afterLoadAction = null) { + player = null; + audioManager.Volume.SetDefault(); InputManager.Clear(); @@ -80,7 +82,7 @@ namespace osu.Game.Tests.Visual.Gameplay 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); + AddAssert("player did not load", () => player?.IsLoaded != true); AddUntilStep("player disposed", () => loader.DisposalTask?.IsCompleted == true); AddAssert("mod rate still applied", () => Beatmap.Value.Track.Rate != 1); } @@ -94,7 +96,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddUntilStep("wait for load ready", () => { moveMouse(); - return player.LoadState == LoadState.Ready; + return player?.LoadState == LoadState.Ready; }); AddRepeatStep("move mouse", moveMouse, 20); @@ -222,7 +224,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("reset notification lock", () => sessionStatics.GetBindable(Static.MutedAudioNotificationShownOnce).Value = false); AddStep("load player", () => ResetPlayer(false, beforeLoad, afterLoad)); - AddUntilStep("wait for player", () => player.LoadState == LoadState.Ready); + AddUntilStep("wait for player", () => player?.LoadState == LoadState.Ready); AddAssert("check for notification", () => container.NotificationOverlay.UnreadCount.Value == 1); AddStep("click notification", () => From fd7bf70b7d4302c1536a6b0e1489ac0c9ff5a1ff Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 13 Aug 2020 12:59:00 +0900 Subject: [PATCH 0504/1782] Remove weird "after load" action This was pretty pointless anyway and from its usages, doesn't look to need to exist. --- .../Visual/Gameplay/TestScenePlayerLoader.cs | 24 ++++++++----------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs index c34b523c97..d6742a27c2 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs @@ -46,8 +46,7 @@ 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. - /// An action to run after container load. - public void ResetPlayer(bool interactive, Action beforeLoadAction = null, Action afterLoadAction = null) + public void ResetPlayer(bool interactive, Action beforeLoadAction = null) { player = null; @@ -55,18 +54,16 @@ namespace osu.Game.Tests.Visual.Gameplay InputManager.Clear(); + container = new TestPlayerLoaderContainer(loader = new TestPlayerLoader(() => player = new TestPlayer(interactive, interactive))); + beforeLoadAction?.Invoke(); + Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo); foreach (var mod in SelectedMods.Value.OfType()) mod.ApplyToTrack(Beatmap.Value.Track); - InputManager.Child = container = new TestPlayerLoaderContainer( - loader = new TestPlayerLoader(() => - { - afterLoadAction?.Invoke(); - return player = new TestPlayer(interactive, interactive); - })); + InputManager.Child = container; } /// @@ -197,19 +194,19 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestMutedNotificationMasterVolume() { - addVolumeSteps("master volume", () => audioManager.Volume.Value = 0, null, () => audioManager.Volume.IsDefault); + addVolumeSteps("master volume", () => audioManager.Volume.Value = 0, () => audioManager.Volume.IsDefault); } [Test] public void TestMutedNotificationTrackVolume() { - addVolumeSteps("music volume", () => audioManager.VolumeTrack.Value = 0, null, () => audioManager.VolumeTrack.IsDefault); + addVolumeSteps("music volume", () => audioManager.VolumeTrack.Value = 0, () => audioManager.VolumeTrack.IsDefault); } [Test] public void TestMutedNotificationMuteButton() { - addVolumeSteps("mute button", null, () => container.VolumeOverlay.IsMuted.Value = true, () => !container.VolumeOverlay.IsMuted.Value); + addVolumeSteps("mute button", () => container.VolumeOverlay.IsMuted.Value = true, () => !container.VolumeOverlay.IsMuted.Value); } /// @@ -217,13 +214,12 @@ namespace osu.Game.Tests.Visual.Gameplay /// /// 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) + private void addVolumeSteps(string volumeName, Action beforeLoad, Func assert) { AddStep("reset notification lock", () => sessionStatics.GetBindable(Static.MutedAudioNotificationShownOnce).Value = false); - AddStep("load player", () => ResetPlayer(false, beforeLoad, afterLoad)); + AddStep("load player", () => ResetPlayer(false, beforeLoad)); AddUntilStep("wait for player", () => player?.LoadState == LoadState.Ready); AddAssert("check for notification", () => container.NotificationOverlay.UnreadCount.Value == 1); From cf9bda6c199bddcbf957033191285814c531b04a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 13 Aug 2020 13:05:00 +0900 Subject: [PATCH 0505/1782] Add coverage of early exit with null and non-null player --- .../Visual/Gameplay/TestScenePlayerLoader.cs | 28 ++++++++++++++----- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs index d6742a27c2..e698d31176 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs @@ -66,20 +66,34 @@ namespace osu.Game.Tests.Visual.Gameplay InputManager.Child = container; } - /// - /// 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() + public void TestEarlyExitBeforePlayerConstruction() { 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()); AddUntilStep("wait for not current", () => !loader.IsCurrentScreen()); - AddAssert("player did not load", () => player?.IsLoaded != true); + AddAssert("player did not load", () => player == null); + AddUntilStep("player disposed", () => loader.DisposalTask == null); + AddAssert("mod rate still applied", () => Beatmap.Value.Track.Rate != 1); + } + + /// + /// 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 TestEarlyExitAfterPlayerConstruction() + { + 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); + AddUntilStep("wait for non-null player", () => player != null); + 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); } From 8ded5925ff0fbf2dcdbf4e00146898009ab556e5 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 13 Aug 2020 13:47:35 +0900 Subject: [PATCH 0506/1782] Xmldoc colour strain --- .../Difficulty/Skills/Colour.cs | 32 +++++++++++++------ 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Colour.cs b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Colour.cs index a348c25331..db445c7d27 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Colour.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Colour.cs @@ -19,7 +19,14 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills private HitType? previousHitType; + /// + /// Length of the current mono pattern. + /// private int currentMonoLength = 1; + + /// + /// List of the last most recent mono patterns, with the most recent at the end of the list. + /// private readonly List monoHistory = new List(); protected override double StrainValueOf(DifficultyHitObject current) @@ -36,12 +43,20 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills if (taikoCurrent.HitType != null && previousHitType != null && taikoCurrent.HitType != previousHitType) { + // The colour has changed. objectStrain = 1.0; if (monoHistory.Count < 2) + { + // There needs to be at least two streaks to determine a strain. objectStrain = 0.0; + } else if ((monoHistory[^1] + currentMonoLength) % 2 == 0) + { + // The last streak in the history is guaranteed to be a different type to the current streak. + // If the total number of notes in the two streaks is even, apply a penalty. objectStrain *= sameParityPenalty(); + } objectStrain *= repetitionPenalties(); currentMonoLength = 1; @@ -55,11 +70,14 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills return objectStrain; } - private double sameParityPenalty() - { - return 0.0; - } + /// + /// The penalty to apply when the total number of notes in the two most recent colour streaks is even. + /// + private double sameParityPenalty() => 0.0; + /// + /// The penalty to apply due to the length of repetition in colour streaks. + /// private double repetitionPenalties() { double penalty = 1.0; @@ -96,10 +114,6 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills return penalty; } - private double repetitionPenalty(int notesSince) - { - double n = notesSince; - return Math.Min(1.0, 0.032 * n); - } + private double repetitionPenalty(int notesSince) => Math.Min(1.0, 0.032 * notesSince); } } From c71ee0877ff0ad2d5b161a6923a51281c513b76a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 13 Aug 2020 14:07:07 +0900 Subject: [PATCH 0507/1782] Update fastlane and plugins --- Gemfile.lock | 73 ++++++++++++++++++++++++++-------------------------- 1 file changed, 36 insertions(+), 37 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index bf971d2c22..a4b49af7e4 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -6,35 +6,36 @@ GEM public_suffix (>= 2.0.2, < 5.0) atomos (0.1.3) aws-eventstream (1.1.0) - aws-partitions (1.329.0) - aws-sdk-core (3.99.2) + aws-partitions (1.354.0) + aws-sdk-core (3.104.3) aws-eventstream (~> 1, >= 1.0.2) aws-partitions (~> 1, >= 1.239.0) aws-sigv4 (~> 1.1) jmespath (~> 1.0) - aws-sdk-kms (1.34.1) + aws-sdk-kms (1.36.0) aws-sdk-core (~> 3, >= 3.99.0) aws-sigv4 (~> 1.1) - aws-sdk-s3 (1.68.1) - aws-sdk-core (~> 3, >= 3.99.0) + aws-sdk-s3 (1.78.0) + aws-sdk-core (~> 3, >= 3.104.3) aws-sdk-kms (~> 1) aws-sigv4 (~> 1.1) - aws-sigv4 (1.1.4) - aws-eventstream (~> 1.0, >= 1.0.2) + aws-sigv4 (1.2.1) + aws-eventstream (~> 1, >= 1.0.2) babosa (1.0.3) claide (1.0.3) colored (1.2) colored2 (3.1.2) commander-fastlane (4.4.6) highline (~> 1.7.2) - declarative (0.0.10) + declarative (0.0.20) declarative-option (0.1.0) - digest-crc (0.5.1) + digest-crc (0.6.1) + rake (~> 13.0) domain_name (0.5.20190701) unf (>= 0.0.5, < 1.0.0) - dotenv (2.7.5) - emoji_regex (1.0.1) - excon (0.74.0) + dotenv (2.7.6) + emoji_regex (3.0.0) + excon (0.76.0) faraday (1.0.1) multipart-post (>= 1.2, < 3) faraday-cookie_jar (0.0.6) @@ -42,34 +43,32 @@ GEM http-cookie (~> 1.0.0) faraday_middleware (1.0.0) faraday (~> 1.0) - fastimage (2.1.7) - fastlane (2.149.1) + fastimage (2.2.0) + fastlane (2.156.0) CFPropertyList (>= 2.3, < 4.0.0) addressable (>= 2.3, < 3.0.0) aws-sdk-s3 (~> 1.0) - babosa (>= 1.0.2, < 2.0.0) + babosa (>= 1.0.3, < 2.0.0) bundler (>= 1.12.0, < 3.0.0) colored commander-fastlane (>= 4.4.6, < 5.0.0) dotenv (>= 2.1.1, < 3.0.0) - emoji_regex (>= 0.1, < 2.0) + emoji_regex (>= 0.1, < 4.0) excon (>= 0.71.0, < 1.0.0) - faraday (>= 0.17, < 2.0) + faraday (~> 1.0) faraday-cookie_jar (~> 0.0.6) - faraday_middleware (>= 0.13.1, < 2.0) + faraday_middleware (~> 1.0) fastimage (>= 2.1.0, < 3.0.0) gh_inspector (>= 1.1.2, < 2.0.0) google-api-client (>= 0.37.0, < 0.39.0) google-cloud-storage (>= 1.15.0, < 2.0.0) highline (>= 1.7.2, < 2.0.0) json (< 3.0.0) - jwt (~> 2.1.0) + jwt (>= 2.1.0, < 3) mini_magick (>= 4.9.4, < 5.0.0) - multi_xml (~> 0.5) multipart-post (~> 2.0.0) plist (>= 3.1.0, < 4.0.0) - public_suffix (~> 2.0.0) - rubyzip (>= 1.3.0, < 2.0.0) + rubyzip (>= 2.0.0, < 3.0.0) security (= 0.1.3) simctl (~> 1.6.3) slack-notifier (>= 2.0.0, < 3.0.0) @@ -97,17 +96,17 @@ GEM google-cloud-core (1.5.0) google-cloud-env (~> 1.0) google-cloud-errors (~> 1.0) - google-cloud-env (1.3.2) + google-cloud-env (1.3.3) faraday (>= 0.17.3, < 2.0) google-cloud-errors (1.0.1) - google-cloud-storage (1.26.2) + google-cloud-storage (1.27.0) addressable (~> 2.5) digest-crc (~> 0.4) google-api-client (~> 0.33) google-cloud-core (~> 1.2) googleauth (~> 0.9) mini_mime (~> 1.0) - googleauth (0.12.0) + googleauth (0.13.1) faraday (>= 0.17.3, < 2.0) jwt (>= 1.4, < 3.0) memoist (~> 0.16) @@ -119,29 +118,29 @@ GEM domain_name (~> 0.5) httpclient (2.8.3) jmespath (1.4.0) - json (2.3.0) - jwt (2.1.0) + json (2.3.1) + jwt (2.2.1) memoist (0.16.2) mini_magick (4.10.1) mini_mime (1.0.2) mini_portile2 (2.4.0) - multi_json (1.14.1) - multi_xml (0.6.0) + multi_json (1.15.0) multipart-post (2.0.0) - nanaimo (0.2.6) + nanaimo (0.3.0) naturally (2.2.0) - nokogiri (1.10.7) + nokogiri (1.10.10) mini_portile2 (~> 2.4.0) - os (1.1.0) + os (1.1.1) plist (3.5.0) - public_suffix (2.0.5) + public_suffix (4.0.5) + rake (13.0.1) representable (3.0.4) declarative (< 0.1.0) declarative-option (< 0.2.0) uber (< 0.2.0) retriable (3.1.2) rouge (2.0.7) - rubyzip (1.3.0) + rubyzip (2.3.0) security (0.1.3) signet (0.14.0) addressable (~> 2.3) @@ -160,7 +159,7 @@ GEM terminal-table (1.8.0) unicode-display_width (~> 1.1, >= 1.1.1) tty-cursor (0.7.1) - tty-screen (0.8.0) + tty-screen (0.8.1) tty-spinner (0.9.3) tty-cursor (~> 0.7) uber (0.1.0) @@ -169,12 +168,12 @@ GEM unf_ext (0.0.7.7) unicode-display_width (1.7.0) word_wrap (1.0.0) - xcodeproj (1.16.0) + xcodeproj (1.18.0) CFPropertyList (>= 2.3.3, < 4.0) atomos (~> 0.1.3) claide (>= 1.0.2, < 2.0) colored2 (~> 3.1) - nanaimo (~> 0.2.6) + nanaimo (~> 0.3.0) xcpretty (0.3.0) rouge (~> 2.0.7) xcpretty-travis-formatter (1.0.0) From 84cb36b6a8ee180552b41f48711b149d710cbaf6 Mon Sep 17 00:00:00 2001 From: Lucas A Date: Thu, 13 Aug 2020 10:57:18 +0200 Subject: [PATCH 0508/1782] Defer subscriptions for updateOverlayActivationMode() to OnEntering() --- osu.Game/Screens/Play/Player.cs | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 2ecddf0f23..6bb4be4096 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -212,11 +212,6 @@ namespace osu.Game.Screens.Play if (game != null) OverlayActivationMode.BindTo(game.OverlayActivationMode); - gameplayOverlaysDisabled.BindValueChanged(_ => updateOverlayActivationMode()); - DrawableRuleset.IsPaused.BindValueChanged(_ => updateOverlayActivationMode()); - DrawableRuleset.HasReplayLoaded.BindValueChanged(_ => updateOverlayActivationMode()); - breakTracker.IsBreakTime.BindValueChanged(_ => updateOverlayActivationMode()); - DrawableRuleset.HasReplayLoaded.BindValueChanged(_ => updatePauseOnFocusLostState(), true); // bind clock into components that require it @@ -363,9 +358,6 @@ namespace osu.Game.Screens.Play private void updateOverlayActivationMode() { - if (!this.IsCurrentScreen()) - return; - bool canTriggerOverlays = DrawableRuleset.IsPaused.Value || breakTracker.IsBreakTime.Value || !gameplayOverlaysDisabled.Value; if (DrawableRuleset.HasReplayLoaded.Value || canTriggerOverlays) @@ -661,7 +653,10 @@ namespace osu.Game.Screens.Play foreach (var mod in Mods.Value.OfType()) mod.ApplyToHUD(HUDOverlay); - updateOverlayActivationMode(); + DrawableRuleset.IsPaused.BindValueChanged(_ => updateOverlayActivationMode()); + DrawableRuleset.HasReplayLoaded.BindValueChanged(_ => updateOverlayActivationMode()); + breakTracker.IsBreakTime.BindValueChanged(_ => updateOverlayActivationMode()); + gameplayOverlaysDisabled.BindValueChanged(_ => updateOverlayActivationMode(), true); } public override void OnSuspending(IScreen next) From 662281d727561b70572f76d40174a1bc75d67604 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 13 Aug 2020 18:20:45 +0900 Subject: [PATCH 0509/1782] Adjust legacy spinners to fade in later Matches stable 1:1 for legacy skins. I've left lazer default as it is because changing to use the shorter apperance looks bad. This will probably change as we proceed with the redesign of the default skin. --- osu.Game.Rulesets.Osu/Skinning/LegacyNewStyleSpinner.cs | 4 ++-- osu.Game.Rulesets.Osu/Skinning/LegacyOldStyleSpinner.cs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacyNewStyleSpinner.cs b/osu.Game.Rulesets.Osu/Skinning/LegacyNewStyleSpinner.cs index 72bc3ddc9a..739c87e037 100644 --- a/osu.Game.Rulesets.Osu/Skinning/LegacyNewStyleSpinner.cs +++ b/osu.Game.Rulesets.Osu/Skinning/LegacyNewStyleSpinner.cs @@ -79,8 +79,8 @@ namespace osu.Game.Rulesets.Osu.Skinning { var spinner = (Spinner)drawableSpinner.HitObject; - using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimePreempt / 2, true)) - this.FadeInFromZero(spinner.TimePreempt / 2); + using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimeFadeIn / 2, true)) + this.FadeInFromZero(spinner.TimeFadeIn / 2); fixedMiddle.FadeColour(Color4.White); using (BeginAbsoluteSequence(spinner.StartTime, true)) diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacyOldStyleSpinner.cs b/osu.Game.Rulesets.Osu/Skinning/LegacyOldStyleSpinner.cs index 0ae1d8f683..81a0df5ea5 100644 --- a/osu.Game.Rulesets.Osu/Skinning/LegacyOldStyleSpinner.cs +++ b/osu.Game.Rulesets.Osu/Skinning/LegacyOldStyleSpinner.cs @@ -85,8 +85,8 @@ namespace osu.Game.Rulesets.Osu.Skinning { var spinner = drawableSpinner.HitObject; - using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimePreempt / 2, true)) - this.FadeInFromZero(spinner.TimePreempt / 2); + using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimeFadeIn / 2, true)) + this.FadeInFromZero(spinner.TimeFadeIn / 2); } protected override void Update() From 3cb22fad82d6d1f3b0bc07f8bb025acabb090cd5 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 13 Aug 2020 19:48:31 +0900 Subject: [PATCH 0510/1782] Fix mods sharing bindable instances --- .../UserInterface/TestSceneModSettings.cs | 20 ++++++++++++++++++ osu.Game/Rulesets/Mods/Mod.cs | 21 ++++++++++++++++++- 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs index 7ff463361a..c5ce3751ef 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs @@ -8,6 +8,7 @@ using NUnit.Framework; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Graphics.UserInterface; @@ -15,6 +16,7 @@ using osu.Game.Overlays.Mods; using osu.Game.Rulesets; using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.UI; namespace osu.Game.Tests.Visual.UserInterface @@ -75,6 +77,24 @@ namespace osu.Game.Tests.Visual.UserInterface AddAssert("Customisation closed", () => modSelect.ModSettingsContainer.Alpha == 0); } + [Test] + public void TestModSettingsUnboundWhenCopied() + { + OsuModDoubleTime original = null; + OsuModDoubleTime copy = null; + + AddStep("create mods", () => + { + original = new OsuModDoubleTime(); + copy = (OsuModDoubleTime)original.CreateCopy(); + }); + + AddStep("change property", () => original.SpeedChange.Value = 2); + + AddAssert("original has new value", () => Precision.AlmostEquals(2.0, original.SpeedChange.Value)); + AddAssert("copy has original value", () => Precision.AlmostEquals(1.5, copy.SpeedChange.Value)); + } + private void createModSelect() { AddStep("create mod select", () => diff --git a/osu.Game/Rulesets/Mods/Mod.cs b/osu.Game/Rulesets/Mods/Mod.cs index 0e5fe3fc9c..52ffa0ad2a 100644 --- a/osu.Game/Rulesets/Mods/Mod.cs +++ b/osu.Game/Rulesets/Mods/Mod.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Reflection; using Newtonsoft.Json; @@ -126,7 +127,25 @@ namespace osu.Game.Rulesets.Mods /// /// Creates a copy of this initialised to a default state. /// - public virtual Mod CreateCopy() => (Mod)MemberwiseClone(); + public virtual Mod CreateCopy() + { + var copy = (Mod)Activator.CreateInstance(GetType()); + + // Copy bindable values across + foreach (var (_, prop) in this.GetSettingsSourceProperties()) + { + var origBindable = prop.GetValue(this); + var copyBindable = prop.GetValue(copy); + + // The bindables themselves are readonly, so the value must be transferred through the Bindable.Value property. + var valueProperty = origBindable.GetType().GetProperty(nameof(Bindable.Value), BindingFlags.Public | BindingFlags.Instance); + Debug.Assert(valueProperty != null); + + valueProperty.SetValue(copyBindable, valueProperty.GetValue(origBindable)); + } + + return copy; + } public bool Equals(IMod other) => GetType() == other?.GetType(); } From 0500d82b5bed73153b1bcee54374c557a4408ab4 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 13 Aug 2020 19:48:41 +0900 Subject: [PATCH 0511/1782] Fix playlist items sharing mod instances --- .../Multiplayer/TestSceneMatchSongSelect.cs | 17 +++++++++++++++++ osu.Game/Screens/Select/MatchSongSelect.cs | 2 ++ 2 files changed, 19 insertions(+) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchSongSelect.cs index c62479faa0..3d225aa0a9 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchSongSelect.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchSongSelect.cs @@ -16,7 +16,9 @@ using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Online.Multiplayer; using osu.Game.Rulesets; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Osu.Mods; using osu.Game.Screens.Multi.Components; using osu.Game.Screens.Select; @@ -145,6 +147,21 @@ namespace osu.Game.Tests.Visual.Multiplayer AddAssert("new item has id 2", () => Room.Playlist.Last().ID == 2); } + /// + /// Tests that the same instances are not shared between two playlist items. + /// + [Test] + public void TestNewItemHasNewModInstances() + { + AddStep("set dt mod", () => SelectedMods.Value = new[] { new OsuModDoubleTime() }); + AddStep("create item", () => songSelect.BeatmapDetails.CreateNewItem()); + AddStep("change mod rate", () => ((OsuModDoubleTime)SelectedMods.Value[0]).SpeedChange.Value = 2); + AddStep("create item", () => songSelect.BeatmapDetails.CreateNewItem()); + + AddAssert("item 1 has rate 1.5", () => Precision.AlmostEquals(1.5, ((OsuModDoubleTime)Room.Playlist.First().RequiredMods[0]).SpeedChange.Value)); + AddAssert("item 2 has rate 2", () => Precision.AlmostEquals(2, ((OsuModDoubleTime)Room.Playlist.Last().RequiredMods[0]).SpeedChange.Value)); + } + private class TestMatchSongSelect : MatchSongSelect { public new MatchBeatmapDetailArea BeatmapDetails => (MatchBeatmapDetailArea)base.BeatmapDetails; diff --git a/osu.Game/Screens/Select/MatchSongSelect.cs b/osu.Game/Screens/Select/MatchSongSelect.cs index 2f3674642e..96a48fa3ac 100644 --- a/osu.Game/Screens/Select/MatchSongSelect.cs +++ b/osu.Game/Screens/Select/MatchSongSelect.cs @@ -77,6 +77,8 @@ namespace osu.Game.Screens.Select item.RequiredMods.Clear(); item.RequiredMods.AddRange(Mods.Value); + + Mods.Value = Mods.Value.Select(m => m.CreateCopy()).ToArray(); } } } From 74a8a4bca8dc7ee11a15e764e87c828bb9509668 Mon Sep 17 00:00:00 2001 From: Lucas A Date: Thu, 13 Aug 2020 21:53:17 +0200 Subject: [PATCH 0512/1782] Make testing code clearer to understand. --- .../Gameplay/TestSceneOverlayActivation.cs | 42 +++++++++---------- 1 file changed, 20 insertions(+), 22 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneOverlayActivation.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneOverlayActivation.cs index 7fd5158515..04c67433fa 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneOverlayActivation.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneOverlayActivation.cs @@ -11,56 +11,54 @@ namespace osu.Game.Tests.Visual.Gameplay { public class TestSceneOverlayActivation : OsuPlayerTestScene { - private OverlayTestPlayer testPlayer; - - public override void SetUpSteps() - { - AddStep("disable overlay activation during gameplay", () => LocalConfig.Set(OsuSetting.GameplayDisableOverlayActivation, true)); - base.SetUpSteps(); - } + protected new OverlayTestPlayer Player => base.Player as OverlayTestPlayer; [Test] public void TestGameplayOverlayActivation() { - AddAssert("activation mode is disabled", () => testPlayer.OverlayActivationMode == OverlayActivation.Disabled); + AddStep("disable overlay activation during gameplay", () => LocalConfig.Set(OsuSetting.GameplayDisableOverlayActivation, true)); + AddAssert("activation mode is disabled", () => Player.OverlayActivationMode == OverlayActivation.Disabled); } [Test] public void TestGameplayOverlayActivationDisabled() { AddStep("enable overlay activation during gameplay", () => LocalConfig.Set(OsuSetting.GameplayDisableOverlayActivation, false)); - AddAssert("activation mode is user triggered", () => testPlayer.OverlayActivationMode == OverlayActivation.UserTriggered); + AddAssert("activation mode is user triggered", () => Player.OverlayActivationMode == OverlayActivation.UserTriggered); } [Test] public void TestGameplayOverlayActivationPaused() { - AddUntilStep("activation mode is disabled", () => testPlayer.OverlayActivationMode == OverlayActivation.Disabled); - AddStep("pause gameplay", () => testPlayer.Pause()); - AddUntilStep("activation mode is user triggered", () => testPlayer.OverlayActivationMode == OverlayActivation.UserTriggered); + AddStep("disable overlay activation during gameplay", () => LocalConfig.Set(OsuSetting.GameplayDisableOverlayActivation, true)); + AddUntilStep("activation mode is disabled", () => Player.OverlayActivationMode == OverlayActivation.Disabled); + AddStep("pause gameplay", () => Player.Pause()); + AddUntilStep("activation mode is user triggered", () => Player.OverlayActivationMode == OverlayActivation.UserTriggered); } [Test] public void TestGameplayOverlayActivationReplayLoaded() { - AddAssert("activation mode is disabled", () => testPlayer.OverlayActivationMode == OverlayActivation.Disabled); - AddStep("load a replay", () => testPlayer.DrawableRuleset.HasReplayLoaded.Value = true); - AddAssert("activation mode is user triggered", () => testPlayer.OverlayActivationMode == OverlayActivation.UserTriggered); + AddStep("disable overlay activation during gameplay", () => LocalConfig.Set(OsuSetting.GameplayDisableOverlayActivation, true)); + AddAssert("activation mode is disabled", () => Player.OverlayActivationMode == OverlayActivation.Disabled); + AddStep("load a replay", () => Player.DrawableRuleset.HasReplayLoaded.Value = true); + AddAssert("activation mode is user triggered", () => Player.OverlayActivationMode == OverlayActivation.UserTriggered); } [Test] public void TestGameplayOverlayActivationBreaks() { - AddAssert("activation mode is disabled", () => testPlayer.OverlayActivationMode == OverlayActivation.Disabled); - AddStep("seek to break", () => testPlayer.GameplayClockContainer.Seek(Beatmap.Value.Beatmap.Breaks.First().StartTime)); - AddUntilStep("activation mode is user triggered", () => testPlayer.OverlayActivationMode == OverlayActivation.UserTriggered); - AddStep("seek to break end", () => testPlayer.GameplayClockContainer.Seek(Beatmap.Value.Beatmap.Breaks.First().EndTime)); - AddUntilStep("activation mode is disabled", () => testPlayer.OverlayActivationMode == OverlayActivation.Disabled); + AddStep("disable overlay activation during gameplay", () => LocalConfig.Set(OsuSetting.GameplayDisableOverlayActivation, true)); + AddAssert("activation mode is disabled", () => Player.OverlayActivationMode == OverlayActivation.Disabled); + AddStep("seek to break", () => Player.GameplayClockContainer.Seek(Beatmap.Value.Beatmap.Breaks.First().StartTime)); + AddUntilStep("activation mode is user triggered", () => Player.OverlayActivationMode == OverlayActivation.UserTriggered); + AddStep("seek to break end", () => Player.GameplayClockContainer.Seek(Beatmap.Value.Beatmap.Breaks.First().EndTime)); + AddUntilStep("activation mode is disabled", () => Player.OverlayActivationMode == OverlayActivation.Disabled); } - protected override TestPlayer CreatePlayer(Ruleset ruleset) => testPlayer = new OverlayTestPlayer(); + protected override TestPlayer CreatePlayer(Ruleset ruleset) => new OverlayTestPlayer(); - private class OverlayTestPlayer : TestPlayer + protected class OverlayTestPlayer : TestPlayer { public new OverlayActivation OverlayActivationMode => base.OverlayActivationMode.Value; } From 671141ec61e1eaf8b0daeea84c1bb03c498dc997 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 14 Aug 2020 18:05:05 +0900 Subject: [PATCH 0513/1782] Load menu backgrounds via LargeTextureStore to reduce memory usage --- 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 387e189dc4..058d2ed0f9 100644 --- a/osu.Game/Graphics/Backgrounds/BeatmapBackground.cs +++ b/osu.Game/Graphics/Backgrounds/BeatmapBackground.cs @@ -20,7 +20,7 @@ namespace osu.Game.Graphics.Backgrounds } [BackgroundDependencyLoader] - private void load(TextureStore textures) + private void load(LargeTextureStore textures) { Sprite.Texture = Beatmap?.Background ?? textures.Get(fallbackTextureName); } From c3757a4660f1b1f1633e9b16af75d2960b343006 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 14 Aug 2020 19:22:23 +0900 Subject: [PATCH 0514/1782] Fix beatmap covers not being unloaded in most overlays Eventually we'll probably want something smarter than this, but for the time being this helps stop runaway memory usage. --- .../Drawables/UpdateableBeatmapSetCover.cs | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/osu.Game/Beatmaps/Drawables/UpdateableBeatmapSetCover.cs b/osu.Game/Beatmaps/Drawables/UpdateableBeatmapSetCover.cs index c60bd0286e..6c229755e7 100644 --- a/osu.Game/Beatmaps/Drawables/UpdateableBeatmapSetCover.cs +++ b/osu.Game/Beatmaps/Drawables/UpdateableBeatmapSetCover.cs @@ -67,19 +67,18 @@ namespace osu.Game.Beatmaps.Drawables if (beatmapSet != null) { - BeatmapSetCover cover; - - Add(displayedCover = new DelayedLoadWrapper( - cover = new BeatmapSetCover(beatmapSet, coverType) + Add(displayedCover = new DelayedLoadUnloadWrapper(() => + { + var cover = new BeatmapSetCover(beatmapSet, coverType) { Anchor = Anchor.Centre, Origin = Anchor.Centre, RelativeSizeAxes = Axes.Both, FillMode = FillMode.Fill, - }) - ); - - cover.OnLoadComplete += d => d.FadeInFromZero(400, Easing.Out); + }; + cover.OnLoadComplete += d => d.FadeInFromZero(400, Easing.Out); + return cover; + })); } } } From e39b2e7218acf876cea7c7efcaa6ae9a4faf7818 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 14 Aug 2020 21:53:18 +0900 Subject: [PATCH 0515/1782] 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 241b836aac..f3fb949f76 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,6 +52,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 63267e1494..a12ce138bd 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -24,7 +24,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 3500eb75dc..0170e94140 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -80,7 +80,7 @@ - + From c1a9bf507af61e2737c6c81dc06efcda3dac91c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 15 Aug 2020 13:06:53 +0200 Subject: [PATCH 0516/1782] Add failing test case --- .../Gameplay/TestSceneGameplayMenuOverlay.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayMenuOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayMenuOverlay.cs index e8b8c7c8e9..fc9cbb073e 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayMenuOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayMenuOverlay.cs @@ -272,7 +272,21 @@ namespace osu.Game.Tests.Visual.Gameplay AddAssert("Overlay is closed", () => pauseOverlay.State.Value == Visibility.Hidden); } + [Test] + public void TestSelectionResetOnVisibilityChange() + { + showOverlay(); + AddStep("Select last button", () => InputManager.Key(Key.Up)); + + hideOverlay(); + showOverlay(); + + AddAssert("No button selected", + () => pauseOverlay.Buttons.All(button => !button.Selected.Value)); + } + private void showOverlay() => AddStep("Show overlay", () => pauseOverlay.Show()); + private void hideOverlay() => AddStep("Hide overlay", () => pauseOverlay.Hide()); private DialogButton getButton(int index) => pauseOverlay.Buttons.Skip(index).First(); From a426ff1d5b26e158c868cb49ec51f21a6f265971 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 15 Aug 2020 13:36:00 +0200 Subject: [PATCH 0517/1782] Refactor gameplay menu overlay to fix regression --- osu.Game/Screens/Play/GameplayMenuOverlay.cs | 78 ++++++++++++-------- 1 file changed, 47 insertions(+), 31 deletions(-) diff --git a/osu.Game/Screens/Play/GameplayMenuOverlay.cs b/osu.Game/Screens/Play/GameplayMenuOverlay.cs index 57403a0987..f938839be3 100644 --- a/osu.Game/Screens/Play/GameplayMenuOverlay.cs +++ b/osu.Game/Screens/Play/GameplayMenuOverlay.cs @@ -50,7 +50,7 @@ namespace osu.Game.Screens.Play public abstract string Description { get; } - protected internal FillFlowContainer InternalButtons; + protected ButtonContainer InternalButtons; public IReadOnlyList Buttons => InternalButtons; private FillFlowContainer retryCounterContainer; @@ -59,7 +59,7 @@ namespace osu.Game.Screens.Play { RelativeSizeAxes = Axes.Both; - State.ValueChanged += s => selectionIndex = -1; + State.ValueChanged += s => InternalButtons.Deselect(); } [BackgroundDependencyLoader] @@ -114,7 +114,7 @@ namespace osu.Game.Screens.Play } } }, - InternalButtons = new FillFlowContainer + InternalButtons = new ButtonContainer { Origin = Anchor.TopCentre, Anchor = Anchor.TopCentre, @@ -186,40 +186,16 @@ namespace osu.Game.Screens.Play InternalButtons.Add(button); } - private int selectionIndex = -1; - - private void setSelected(int value) - { - if (selectionIndex == value) - return; - - // Deselect the previously-selected button - if (selectionIndex != -1) - InternalButtons[selectionIndex].Selected.Value = false; - - selectionIndex = value; - - // Select the newly-selected button - if (selectionIndex != -1) - InternalButtons[selectionIndex].Selected.Value = true; - } - public bool OnPressed(GlobalAction action) { switch (action) { case GlobalAction.SelectPrevious: - if (selectionIndex == -1 || selectionIndex == 0) - setSelected(InternalButtons.Count - 1); - else - setSelected(selectionIndex - 1); + InternalButtons.SelectPrevious(); return true; case GlobalAction.SelectNext: - if (selectionIndex == -1 || selectionIndex == InternalButtons.Count - 1) - setSelected(0); - else - setSelected(selectionIndex + 1); + InternalButtons.SelectNext(); return true; case GlobalAction.Back: @@ -241,9 +217,9 @@ namespace osu.Game.Screens.Play private void buttonSelectionChanged(DialogButton button, bool isSelected) { if (!isSelected) - setSelected(-1); + InternalButtons.Deselect(); else - setSelected(InternalButtons.IndexOf(button)); + InternalButtons.Select(button); } private void updateRetryCount() @@ -277,6 +253,46 @@ namespace osu.Game.Screens.Play }; } + protected class ButtonContainer : FillFlowContainer + { + private int selectedIndex = -1; + + private void setSelected(int value) + { + if (selectedIndex == value) + return; + + // Deselect the previously-selected button + if (selectedIndex != -1) + this[selectedIndex].Selected.Value = false; + + selectedIndex = value; + + // Select the newly-selected button + if (selectedIndex != -1) + this[selectedIndex].Selected.Value = true; + } + + public void SelectNext() + { + if (selectedIndex == -1 || selectedIndex == Count - 1) + setSelected(0); + else + setSelected(selectedIndex + 1); + } + + public void SelectPrevious() + { + if (selectedIndex == -1 || selectedIndex == 0) + setSelected(Count - 1); + else + setSelected(selectedIndex - 1); + } + + public void Deselect() => setSelected(-1); + public void Select(DialogButton button) => setSelected(IndexOf(button)); + } + private class Button : DialogButton { // required to ensure keyboard navigation always starts from an extremity (unless the cursor is moved) From 5c11270b988f7e8f85eddafe329d048d14228ad6 Mon Sep 17 00:00:00 2001 From: Ron B Date: Sat, 15 Aug 2020 20:12:06 +0300 Subject: [PATCH 0518/1782] Add SpinnerFrequencyModulate skin config option --- .../Objects/Drawables/DrawableSpinner.cs | 9 ++++++--- osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs | 3 ++- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index d1a6463d72..273a9fda84 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -12,6 +12,7 @@ using osu.Game.Graphics; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces; +using osu.Game.Rulesets.Osu.Skinning; using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Ranking; using osu.Game.Skinning; @@ -31,6 +32,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables private readonly IBindable positionBindable = new Bindable(); + private bool spinnerFrequencyModulate; + public DrawableSpinner(Spinner s) : base(s) { @@ -165,10 +168,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables } [BackgroundDependencyLoader] - private void load(OsuColour colours) + private void load(OsuColour colours, ISkinSource skin) { positionBindable.BindValueChanged(pos => Position = pos.NewValue); positionBindable.BindTo(HitObject.PositionBindable); + spinnerFrequencyModulate = skin.GetConfig(OsuSkinConfiguration.SpinnerFrequencyModulate)?.Value ?? true; } /// @@ -221,8 +225,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables RotationTracker.Tracking = !Result.HasResult && (OsuActionInputManager?.PressedActions.Any(x => x == OsuAction.LeftButton || x == OsuAction.RightButton) ?? false); if (spinningSample != null) - // todo: implement SpinnerFrequencyModulate - spinningSample.Frequency.Value = 0.5f + Progress; + spinningSample.Frequency.Value = spinnerFrequencyModulate ? 0.5f + Progress : 0.5f; } protected override void UpdateAfterChildren() diff --git a/osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs b/osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs index 154160fdb5..54755bd9d5 100644 --- a/osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs +++ b/osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs @@ -13,6 +13,7 @@ namespace osu.Game.Rulesets.Osu.Skinning CursorExpand, CursorRotate, HitCircleOverlayAboveNumber, - HitCircleOverlayAboveNumer // Some old skins will have this typo + HitCircleOverlayAboveNumer, // Some old skins will have this typo + SpinnerFrequencyModulate } } From 896a87e62921bfef2281a822eb20c784e3612fd2 Mon Sep 17 00:00:00 2001 From: Ron B Date: Sat, 15 Aug 2020 20:14:36 +0300 Subject: [PATCH 0519/1782] Replace accidental tab with spaces --- osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs b/osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs index 54755bd9d5..1d34727c04 100644 --- a/osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs +++ b/osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs @@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.Osu.Skinning CursorExpand, CursorRotate, HitCircleOverlayAboveNumber, - HitCircleOverlayAboveNumer, // Some old skins will have this typo + HitCircleOverlayAboveNumer, // Some old skins will have this typo SpinnerFrequencyModulate } } From 61de3c75402f55704c07da9128778f35b374a52f Mon Sep 17 00:00:00 2001 From: Ron B Date: Sat, 15 Aug 2020 20:16:28 +0300 Subject: [PATCH 0520/1782] Replace accidental tab with spaces --- osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs b/osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs index 1d34727c04..e034e14eb0 100644 --- a/osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs +++ b/osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs @@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.Osu.Skinning CursorExpand, CursorRotate, HitCircleOverlayAboveNumber, - HitCircleOverlayAboveNumer, // Some old skins will have this typo - SpinnerFrequencyModulate + HitCircleOverlayAboveNumer, // Some old skins will have this typo + SpinnerFrequencyModulate } } From 07c25d5a78d2df33e3d54d3922da6723c3622f2f Mon Sep 17 00:00:00 2001 From: Ron B Date: Sat, 15 Aug 2020 20:51:33 +0300 Subject: [PATCH 0521/1782] Move spinnerFrequencyModulate set to ApplySkin --- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index 273a9fda84..5bf87ba16b 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -172,6 +172,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { positionBindable.BindValueChanged(pos => Position = pos.NewValue); positionBindable.BindTo(HitObject.PositionBindable); + } + + protected override void ApplySkin(ISkinSource skin, bool allowFallback) + { spinnerFrequencyModulate = skin.GetConfig(OsuSkinConfiguration.SpinnerFrequencyModulate)?.Value ?? true; } From 40445d0005fe33943baf6350e8e2b35869e08643 Mon Sep 17 00:00:00 2001 From: Ron B Date: Sat, 15 Aug 2020 21:07:44 +0300 Subject: [PATCH 0522/1782] replicate osu-stable behaviour for spinningSample frequency --- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index 5bf87ba16b..dfe10eeaab 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -104,6 +104,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { Volume = { Value = 0 }, Looping = true, + Frequency = { Value = 1.0f } }); } } @@ -228,8 +229,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables if (HandleUserInput) RotationTracker.Tracking = !Result.HasResult && (OsuActionInputManager?.PressedActions.Any(x => x == OsuAction.LeftButton || x == OsuAction.RightButton) ?? false); - if (spinningSample != null) - spinningSample.Frequency.Value = spinnerFrequencyModulate ? 0.5f + Progress : 0.5f; + if (spinningSample != null && spinnerFrequencyModulate) + spinningSample.Frequency.Value = 0.5f + Progress; } protected override void UpdateAfterChildren() From a1079bac3234f53f39e894dbd888321e21561907 Mon Sep 17 00:00:00 2001 From: Ron B Date: Sat, 15 Aug 2020 21:19:47 +0300 Subject: [PATCH 0523/1782] Move frequency values into consts --- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index dfe10eeaab..c44553a1c5 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -85,6 +85,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables } private SkinnableSound spinningSample; + private const float SPINNING_SAMPLE_INITAL_FREQUENCY = 1.0f; + private const float SPINNING_SAMPLE_MODULATED_BASE_FREQUENCY = 0.5f; protected override void LoadSamples() { @@ -104,7 +106,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { Volume = { Value = 0 }, Looping = true, - Frequency = { Value = 1.0f } + Frequency = { Value = SPINNING_SAMPLE_INITAL_FREQUENCY } }); } } @@ -230,7 +232,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables RotationTracker.Tracking = !Result.HasResult && (OsuActionInputManager?.PressedActions.Any(x => x == OsuAction.LeftButton || x == OsuAction.RightButton) ?? false); if (spinningSample != null && spinnerFrequencyModulate) - spinningSample.Frequency.Value = 0.5f + Progress; + spinningSample.Frequency.Value = SPINNING_SAMPLE_MODULATED_BASE_FREQUENCY + Progress; } protected override void UpdateAfterChildren() From 390e87273065aef35335d3d1e617dc62c9ea90eb Mon Sep 17 00:00:00 2001 From: Ron B Date: Sat, 15 Aug 2020 21:34:17 +0300 Subject: [PATCH 0524/1782] Fix acoording to review --- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index c44553a1c5..a4636050bb 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -85,7 +85,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables } private SkinnableSound spinningSample; - private const float SPINNING_SAMPLE_INITAL_FREQUENCY = 1.0f; + private const float SPINNING_SAMPLE_INITIAL_FREQUENCY = 1.0f; private const float SPINNING_SAMPLE_MODULATED_BASE_FREQUENCY = 0.5f; protected override void LoadSamples() @@ -106,7 +106,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { Volume = { Value = 0 }, Looping = true, - Frequency = { Value = SPINNING_SAMPLE_INITAL_FREQUENCY } + Frequency = { Value = SPINNING_SAMPLE_INITIAL_FREQUENCY } }); } } @@ -171,7 +171,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables } [BackgroundDependencyLoader] - private void load(OsuColour colours, ISkinSource skin) + private void load(OsuColour colours) { positionBindable.BindValueChanged(pos => Position = pos.NewValue); positionBindable.BindTo(HitObject.PositionBindable); @@ -179,6 +179,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables protected override void ApplySkin(ISkinSource skin, bool allowFallback) { + base.ApplySkin(skin, allowFallback); spinnerFrequencyModulate = skin.GetConfig(OsuSkinConfiguration.SpinnerFrequencyModulate)?.Value ?? true; } From 5f35b3ebb98821e2f8870964302e9c60dab84d8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 15 Aug 2020 20:44:02 +0200 Subject: [PATCH 0525/1782] Fix constant casing --- .../Objects/Drawables/DrawableSpinner.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index a4636050bb..a57bb466c7 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -85,8 +85,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables } private SkinnableSound spinningSample; - private const float SPINNING_SAMPLE_INITIAL_FREQUENCY = 1.0f; - private const float SPINNING_SAMPLE_MODULATED_BASE_FREQUENCY = 0.5f; + private const float spinning_sample_initial_frequency = 1.0f; + private const float spinning_sample_modulated_base_frequency = 0.5f; protected override void LoadSamples() { @@ -106,7 +106,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { Volume = { Value = 0 }, Looping = true, - Frequency = { Value = SPINNING_SAMPLE_INITIAL_FREQUENCY } + Frequency = { Value = spinning_sample_initial_frequency } }); } } @@ -233,7 +233,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables RotationTracker.Tracking = !Result.HasResult && (OsuActionInputManager?.PressedActions.Any(x => x == OsuAction.LeftButton || x == OsuAction.RightButton) ?? false); if (spinningSample != null && spinnerFrequencyModulate) - spinningSample.Frequency.Value = SPINNING_SAMPLE_MODULATED_BASE_FREQUENCY + Progress; + spinningSample.Frequency.Value = spinning_sample_modulated_base_frequency + Progress; } protected override void UpdateAfterChildren() From c4a7fac760efde4622956f1fa3c581e469c8e508 Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Sat, 15 Aug 2020 22:03:24 +0200 Subject: [PATCH 0526/1782] Add required parameters and other various changes --- .../Formats/LegacyBeatmapEncoderTest.cs | 30 ++++++++++++++----- .../Beatmaps/IO/ImportBeatmapTest.cs | 5 ++-- .../Editing/EditorChangeHandlerTest.cs | 6 ++-- .../Editing/LegacyEditorBeatmapPatcherTest.cs | 8 +---- osu.Game/Beatmaps/BeatmapManager.cs | 4 +-- .../Beatmaps/Formats/LegacyBeatmapEncoder.cs | 13 ++++---- osu.Game/Screens/Edit/Editor.cs | 7 +++-- osu.Game/Screens/Edit/EditorChangeHandler.cs | 11 +++---- 8 files changed, 51 insertions(+), 33 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs index fba63f8539..38f730995e 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs @@ -28,13 +28,25 @@ namespace osu.Game.Tests.Beatmaps.Formats [TestFixture] public class LegacyBeatmapEncoderTest { - private static IEnumerable allBeatmaps => TestResources.GetStore().GetAvailableResources().Where(res => res.EndsWith(".osu")); + private static readonly DllResourceStore resource_store = TestResources.GetStore(); + + private static IEnumerable allBeatmaps = resource_store.GetAvailableResources().Where(res => res.EndsWith(".osu")); + + private static Stream beatmapSkinStream = resource_store.GetStream("skin.ini"); + + private ISkin skin; + + [SetUp] + public void Init() + { + skin = decodeSkinFromLegacy(beatmapSkinStream); + } [TestCaseSource(nameof(allBeatmaps))] public void TestEncodeDecodeStability(string name) { - var decoded = decodeFromLegacy(TestResources.GetStore().GetStream(name)); - var decodedAfterEncode = decodeFromLegacy(encodeToLegacy(decoded)); + var decoded = decodeBeatmapFromLegacy(TestResources.GetStore().GetStream(name)); + var decodedAfterEncode = decodeBeatmapFromLegacy(encodeToLegacy(decoded, skin)); sort(decoded); sort(decodedAfterEncode); @@ -52,20 +64,24 @@ namespace osu.Game.Tests.Beatmaps.Formats } } - private IBeatmap decodeFromLegacy(Stream stream) + private IBeatmap decodeBeatmapFromLegacy(Stream stream) { using (var reader = new LineBufferedReader(stream)) return convert(new LegacyBeatmapDecoder { ApplyOffsets = false }.Decode(reader)); } - private Stream encodeToLegacy(IBeatmap beatmap) + private ISkin decodeSkinFromLegacy(Stream stream) + { + using (var reader = new LineBufferedReader(stream)) + return new LegacySkin(SkinInfo.Default, resource_store, null); + } + + private Stream encodeToLegacy(IBeatmap beatmap, ISkin skin) { var stream = new MemoryStream(); using (var writer = new StreamWriter(stream, Encoding.UTF8, 1024, true)) - using (var rs = new ResourceStore()) { - var skin = new LegacyBeatmapSkin(beatmap.BeatmapInfo, rs, null); new LegacyBeatmapEncoder(beatmap, skin).Encode(writer); } diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs index 0151678db3..17271184c0 100644 --- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs @@ -730,7 +730,8 @@ namespace osu.Game.Tests.Beatmaps.IO BeatmapSetInfo setToUpdate = manager.GetAllUsableBeatmapSets()[0]; var beatmapInfo = setToUpdate.Beatmaps.First(b => b.RulesetID == 0); - Beatmap beatmapToUpdate = (Beatmap)manager.GetWorkingBeatmap(setToUpdate.Beatmaps.First(b => b.RulesetID == 0)).Beatmap; + var workingBeatmap = manager.GetWorkingBeatmap(setToUpdate.Beatmaps.First(b => b.RulesetID == 0)); + Beatmap beatmapToUpdate = (Beatmap)workingBeatmap.Beatmap; BeatmapSetFileInfo fileToUpdate = setToUpdate.Files.First(f => beatmapToUpdate.BeatmapInfo.Path.Contains(f.Filename)); string oldMd5Hash = beatmapToUpdate.BeatmapInfo.MD5Hash; @@ -738,7 +739,7 @@ namespace osu.Game.Tests.Beatmaps.IO beatmapToUpdate.HitObjects.Clear(); beatmapToUpdate.HitObjects.Add(new HitCircle { StartTime = 5000 }); - manager.Save(beatmapInfo, beatmapToUpdate); + manager.Save(beatmapInfo, beatmapToUpdate, workingBeatmap.Skin); // Check that the old file reference has been removed Assert.That(manager.QueryBeatmapSet(s => s.ID == setToUpdate.ID).Files.All(f => f.ID != fileToUpdate.ID)); diff --git a/osu.Game.Tests/Editing/EditorChangeHandlerTest.cs b/osu.Game.Tests/Editing/EditorChangeHandlerTest.cs index feda1ae0e9..6d708ce838 100644 --- a/osu.Game.Tests/Editing/EditorChangeHandlerTest.cs +++ b/osu.Game.Tests/Editing/EditorChangeHandlerTest.cs @@ -13,7 +13,7 @@ namespace osu.Game.Tests.Editing [Test] public void TestSaveRestoreState() { - var handler = new EditorChangeHandler(new EditorBeatmap(new Beatmap())); + var handler = new EditorChangeHandler(new EditorBeatmap(new Beatmap()), null); Assert.That(handler.CanUndo.Value, Is.False); Assert.That(handler.CanRedo.Value, Is.False); @@ -32,7 +32,7 @@ namespace osu.Game.Tests.Editing [Test] public void TestMaxStatesSaved() { - var handler = new EditorChangeHandler(new EditorBeatmap(new Beatmap())); + var handler = new EditorChangeHandler(new EditorBeatmap(new Beatmap()), null); Assert.That(handler.CanUndo.Value, Is.False); @@ -53,7 +53,7 @@ namespace osu.Game.Tests.Editing [Test] public void TestMaxStatesExceeded() { - var handler = new EditorChangeHandler(new EditorBeatmap(new Beatmap())); + var handler = new EditorChangeHandler(new EditorBeatmap(new Beatmap()), null); Assert.That(handler.CanUndo.Value, Is.False); diff --git a/osu.Game.Tests/Editing/LegacyEditorBeatmapPatcherTest.cs b/osu.Game.Tests/Editing/LegacyEditorBeatmapPatcherTest.cs index 74b6f66d85..b491157627 100644 --- a/osu.Game.Tests/Editing/LegacyEditorBeatmapPatcherTest.cs +++ b/osu.Game.Tests/Editing/LegacyEditorBeatmapPatcherTest.cs @@ -4,7 +4,6 @@ using System.IO; using System.Text; using NUnit.Framework; -using osu.Framework.IO.Stores; using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Formats; @@ -15,7 +14,6 @@ using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Beatmaps; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Screens.Edit; -using osu.Game.Skinning; using osuTK; using Decoder = osu.Game.Beatmaps.Formats.Decoder; @@ -353,11 +351,7 @@ namespace osu.Game.Tests.Editing using (var encoded = new MemoryStream()) { using (var sw = new StreamWriter(encoded)) - using (var rs = new ResourceStore()) - { - var skin = new LegacyBeatmapSkin(beatmap.BeatmapInfo, rs, null); - new LegacyBeatmapEncoder(beatmap, skin).Encode(sw); - } + new LegacyBeatmapEncoder(beatmap, null).Encode(sw); return encoded.ToArray(); } diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 06acd4e9f2..bd757d30ca 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -195,7 +195,8 @@ namespace osu.Game.Beatmaps /// /// The to save the content against. The file referenced by will be replaced. /// The content to write. - public void Save(BeatmapInfo info, IBeatmap beatmapContent) + /// Optional beatmap skin for inline skin configuration in beatmap files. + public void Save(BeatmapInfo info, IBeatmap beatmapContent, ISkin skin) { var setInfo = QueryBeatmapSet(s => s.Beatmaps.Any(b => b.ID == info.ID)); @@ -203,7 +204,6 @@ namespace osu.Game.Beatmaps { using (var sw = new StreamWriter(stream, Encoding.UTF8, 1024, true)) { - var skin = new LegacyBeatmapSkin(info, Files.Store, audioManager); new LegacyBeatmapEncoder(beatmapContent, skin).Encode(sw); } diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs index eb148794de..716f1bc814 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs @@ -24,14 +24,14 @@ namespace osu.Game.Beatmaps.Formats public const int LATEST_VERSION = 128; private readonly IBeatmap beatmap; - private readonly LegacyBeatmapSkin beatmapSkin; + private readonly ISkin skin; /// The beatmap to encode - /// An optional beatmap skin, for encoding the beatmap's combo colours. - public LegacyBeatmapEncoder(IBeatmap beatmap, [CanBeNull] LegacyBeatmapSkin beatmapSkin) + /// An optional skin, for encoding the beatmap's combo colours. This will only work if the parameter is a type of . + public LegacyBeatmapEncoder(IBeatmap beatmap, [CanBeNull] ISkin skin) { this.beatmap = beatmap; - this.beatmapSkin = beatmapSkin; + this.skin = skin; if (beatmap.BeatmapInfo.RulesetID < 0 || beatmap.BeatmapInfo.RulesetID > 3) throw new ArgumentException("Only beatmaps in the osu, taiko, catch, or mania rulesets can be encoded to the legacy beatmap format.", nameof(beatmap)); @@ -207,7 +207,10 @@ namespace osu.Game.Beatmaps.Formats private void handleComboColours(TextWriter writer) { - var colours = beatmapSkin?.Configuration.ComboColours; + if (!(skin is LegacyBeatmapSkin legacySkin)) + return; + + var colours = legacySkin?.Configuration.ComboColours; if (colours == null || colours.Count == 0) return; diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index d92f3922c3..d52d832bf3 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -33,6 +33,7 @@ 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.Skinning; using osu.Game.Users; namespace osu.Game.Screens.Edit @@ -64,6 +65,7 @@ namespace osu.Game.Screens.Edit private IBeatmap playableBeatmap; private EditorBeatmap editorBeatmap; private EditorChangeHandler changeHandler; + private ISkin beatmapSkin; private DependencyContainer dependencies; @@ -92,6 +94,7 @@ namespace osu.Game.Screens.Edit try { playableBeatmap = Beatmap.Value.GetPlayableBeatmap(Beatmap.Value.BeatmapInfo.Ruleset); + beatmapSkin = Beatmap.Value.Skin; } catch (Exception e) { @@ -104,7 +107,7 @@ namespace osu.Game.Screens.Edit AddInternal(editorBeatmap = new EditorBeatmap(playableBeatmap)); dependencies.CacheAs(editorBeatmap); - changeHandler = new EditorChangeHandler(editorBeatmap); + changeHandler = new EditorChangeHandler(editorBeatmap, beatmapSkin); dependencies.CacheAs(changeHandler); EditorMenuBar menuBar; @@ -399,7 +402,7 @@ namespace osu.Game.Screens.Edit clock.SeekForward(!clock.IsRunning, amount); } - private void saveBeatmap() => beatmapManager.Save(playableBeatmap.BeatmapInfo, editorBeatmap); + private void saveBeatmap() => beatmapManager.Save(playableBeatmap.BeatmapInfo, editorBeatmap, Beatmap.Value.Skin); private void exportBeatmap() { diff --git a/osu.Game/Screens/Edit/EditorChangeHandler.cs b/osu.Game/Screens/Edit/EditorChangeHandler.cs index 6393093c74..f305d2a15d 100644 --- a/osu.Game/Screens/Edit/EditorChangeHandler.cs +++ b/osu.Game/Screens/Edit/EditorChangeHandler.cs @@ -6,7 +6,6 @@ using System.Collections.Generic; using System.IO; using System.Text; using osu.Framework.Bindables; -using osu.Framework.IO.Stores; using osu.Game.Beatmaps.Formats; using osu.Game.Rulesets.Objects; using osu.Game.Skinning; @@ -27,6 +26,7 @@ namespace osu.Game.Screens.Edit private int currentState = -1; private readonly EditorBeatmap editorBeatmap; + private readonly ISkin beatmapSkin; private int bulkChangesStarted; private bool isRestoring; @@ -36,7 +36,8 @@ namespace osu.Game.Screens.Edit /// Creates a new . /// /// The to track the s of. - public EditorChangeHandler(EditorBeatmap editorBeatmap) + /// The skin to track the inline skin configuration of. + public EditorChangeHandler(EditorBeatmap editorBeatmap, ISkin beatmapSkin) { this.editorBeatmap = editorBeatmap; @@ -46,6 +47,8 @@ namespace osu.Game.Screens.Edit patcher = new LegacyEditorBeatmapPatcher(editorBeatmap); + this.beatmapSkin = beatmapSkin; + // Initial state. SaveState(); } @@ -87,10 +90,8 @@ namespace osu.Game.Screens.Edit using (var stream = new MemoryStream()) { using (var sw = new StreamWriter(stream, Encoding.UTF8, 1024, true)) - using (var rs = new ResourceStore()) { - var skin = new LegacyBeatmapSkin(editorBeatmap.BeatmapInfo, rs, null); - new LegacyBeatmapEncoder(editorBeatmap, skin).Encode(sw); + new LegacyBeatmapEncoder(editorBeatmap, beatmapSkin).Encode(sw); } savedStates.Add(stream.ToArray()); From 9e4b9188e1266f1b719e6ba1212aa00ccf1ebbd8 Mon Sep 17 00:00:00 2001 From: voidedWarranties Date: Sat, 15 Aug 2020 13:06:16 -0700 Subject: [PATCH 0527/1782] Cache LoungeSubScreen, separate method, rename option --- osu.Game/Online/Multiplayer/Room.cs | 56 +++++++++++-------- .../Multi/Lounge/Components/DrawableRoom.cs | 4 +- .../Multi/Lounge/Components/RoomsContainer.cs | 11 +++- .../Screens/Multi/Lounge/LoungeSubScreen.cs | 14 +---- 4 files changed, 45 insertions(+), 40 deletions(-) diff --git a/osu.Game/Online/Multiplayer/Room.cs b/osu.Game/Online/Multiplayer/Room.cs index 01d9446bf6..5feebe8da1 100644 --- a/osu.Game/Online/Multiplayer/Room.cs +++ b/osu.Game/Online/Multiplayer/Room.cs @@ -104,47 +104,55 @@ namespace osu.Game.Online.Multiplayer public readonly Bindable Position = new Bindable(-1); /// - /// Copies the properties from another to this room. + /// Create a copy of this room, without information specific to it, such as Room ID or host /// - /// The room to copy - /// Whether the copy should exclude information unique to a specific room (i.e. when duplicating a room) - public void CopyFrom(Room other, bool duplicate = false) + public Room CreateCopy() { - if (!duplicate) + Room newRoom = new Room { - RoomID.Value = other.RoomID.Value; + Name = { Value = Name.Value }, + Availability = { Value = Availability.Value }, + Type = { Value = Type.Value }, + MaxParticipants = { Value = MaxParticipants.Value } + }; - if (other.Host.Value != null && Host.Value?.Id != other.Host.Value.Id) - Host.Value = other.Host.Value; + newRoom.Playlist.AddRange(Playlist); - ChannelId.Value = other.ChannelId.Value; - Status.Value = other.Status.Value; - ParticipantCount.Value = other.ParticipantCount.Value; - EndDate.Value = other.EndDate.Value; - - if (DateTimeOffset.Now >= EndDate.Value) - Status.Value = new RoomStatusEnded(); - - if (!RecentParticipants.SequenceEqual(other.RecentParticipants)) - { - RecentParticipants.Clear(); - RecentParticipants.AddRange(other.RecentParticipants); - } - - Position.Value = other.Position.Value; - } + return newRoom; + } + public void CopyFrom(Room other) + { + RoomID.Value = other.RoomID.Value; Name.Value = other.Name.Value; + if (other.Host.Value != null && Host.Value?.Id != other.Host.Value.Id) + Host.Value = other.Host.Value; + + ChannelId.Value = other.ChannelId.Value; + Status.Value = other.Status.Value; Availability.Value = other.Availability.Value; Type.Value = other.Type.Value; MaxParticipants.Value = other.MaxParticipants.Value; + ParticipantCount.Value = other.ParticipantCount.Value; + EndDate.Value = other.EndDate.Value; + + if (DateTimeOffset.Now >= EndDate.Value) + Status.Value = new RoomStatusEnded(); if (!Playlist.SequenceEqual(other.Playlist)) { Playlist.Clear(); Playlist.AddRange(other.Playlist); } + + if (!RecentParticipants.SequenceEqual(other.RecentParticipants)) + { + RecentParticipants.Clear(); + RecentParticipants.AddRange(other.RecentParticipants); + } + + Position.Value = other.Position.Value; } public bool ShouldSerializeRoomID() => false; diff --git a/osu.Game/Screens/Multi/Lounge/Components/DrawableRoom.cs b/osu.Game/Screens/Multi/Lounge/Components/DrawableRoom.cs index 64fbae2503..db75df6054 100644 --- a/osu.Game/Screens/Multi/Lounge/Components/DrawableRoom.cs +++ b/osu.Game/Screens/Multi/Lounge/Components/DrawableRoom.cs @@ -38,7 +38,7 @@ namespace osu.Game.Screens.Multi.Lounge.Components public event Action StateChanged; - public Action DuplicateRoom; + public Action DuplicateRoom; private readonly Box selectionBox; private CachedModelDependencyContainer dependencies; @@ -239,7 +239,7 @@ namespace osu.Game.Screens.Multi.Lounge.Components public MenuItem[] ContextMenuItems => new MenuItem[] { - new OsuMenuItem("Duplicate", MenuItemType.Standard, () => DuplicateRoom?.Invoke(Room)) + new OsuMenuItem("Create copy", MenuItemType.Standard, DuplicateRoom) }; } } diff --git a/osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs b/osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs index f112dd80ee..206ce8da0f 100644 --- a/osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs +++ b/osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs @@ -38,7 +38,8 @@ namespace osu.Game.Screens.Multi.Lounge.Components [Resolved] private IRoomManager roomManager { get; set; } - public Action DuplicateRoom; + [Resolved] + private LoungeSubScreen loungeSubScreen { get; set; } public RoomsContainer() { @@ -96,7 +97,13 @@ namespace osu.Game.Screens.Multi.Lounge.Components { roomFlow.Add(new DrawableRoom(room) { - DuplicateRoom = DuplicateRoom, + DuplicateRoom = () => + { + Room newRoom = room.CreateCopy(); + newRoom.Name.Value = $"Copy of {room.Name.Value}"; + + loungeSubScreen.Open(newRoom); + }, Action = () => { if (room == selectedRoom.Value) diff --git a/osu.Game/Screens/Multi/Lounge/LoungeSubScreen.cs b/osu.Game/Screens/Multi/Lounge/LoungeSubScreen.cs index 5d68386398..a5b2499c76 100644 --- a/osu.Game/Screens/Multi/Lounge/LoungeSubScreen.cs +++ b/osu.Game/Screens/Multi/Lounge/LoungeSubScreen.cs @@ -18,6 +18,7 @@ using osu.Game.Screens.Multi.Match; namespace osu.Game.Screens.Multi.Lounge { + [Cached] public class LoungeSubScreen : MultiplayerSubScreen { public override string Title => "Lounge"; @@ -62,18 +63,7 @@ namespace osu.Game.Screens.Multi.Lounge RelativeSizeAxes = Axes.Both, ScrollbarOverlapsContent = false, Padding = new MarginPadding(10), - Child = roomsContainer = new RoomsContainer - { - JoinRequested = joinRequested, - DuplicateRoom = room => - { - Room newRoom = new Room(); - newRoom.CopyFrom(room, true); - newRoom.Name.Value = $"Copy of {room.Name.Value}"; - - Open(newRoom); - } - } + Child = roomsContainer = new RoomsContainer { JoinRequested = joinRequested } }, loadingLayer = new LoadingLayer(roomsContainer), } From 0e8411f76c156110fb7e693292d5649dd2d17265 Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Sat, 15 Aug 2020 22:06:26 +0200 Subject: [PATCH 0528/1782] Rename fields and make readonly --- .../Beatmaps/Formats/LegacyBeatmapEncoderTest.cs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs index 38f730995e..63fdf2a8ae 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs @@ -32,14 +32,13 @@ namespace osu.Game.Tests.Beatmaps.Formats private static IEnumerable allBeatmaps = resource_store.GetAvailableResources().Where(res => res.EndsWith(".osu")); - private static Stream beatmapSkinStream = resource_store.GetStream("skin.ini"); - + private static readonly Stream beatmap_skin_stream = resource_store.GetStream("skin.ini"); private ISkin skin; [SetUp] public void Init() { - skin = decodeSkinFromLegacy(beatmapSkinStream); + skin = decodeSkinFromLegacy(beatmap_skin_stream); } [TestCaseSource(nameof(allBeatmaps))] @@ -76,14 +75,12 @@ namespace osu.Game.Tests.Beatmaps.Formats return new LegacySkin(SkinInfo.Default, resource_store, null); } - private Stream encodeToLegacy(IBeatmap beatmap, ISkin skin) + private Stream encodeToLegacy(IBeatmap beatmap, ISkin beatmapSkin) { var stream = new MemoryStream(); using (var writer = new StreamWriter(stream, Encoding.UTF8, 1024, true)) - { - new LegacyBeatmapEncoder(beatmap, skin).Encode(writer); - } + new LegacyBeatmapEncoder(beatmap, beatmapSkin).Encode(writer); stream.Position = 0; From f5877810588dd76cd30b2ccde309e8c25cccccb7 Mon Sep 17 00:00:00 2001 From: voidedWarranties Date: Sat, 15 Aug 2020 14:27:49 -0700 Subject: [PATCH 0529/1782] Allow LoungeSubScreen to be null (fix test) --- osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs b/osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs index 206ce8da0f..1954d97a8f 100644 --- a/osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs +++ b/osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs @@ -38,7 +38,7 @@ namespace osu.Game.Screens.Multi.Lounge.Components [Resolved] private IRoomManager roomManager { get; set; } - [Resolved] + [Resolved(CanBeNull = true)] private LoungeSubScreen loungeSubScreen { get; set; } public RoomsContainer() @@ -100,9 +100,10 @@ namespace osu.Game.Screens.Multi.Lounge.Components DuplicateRoom = () => { Room newRoom = room.CreateCopy(); - newRoom.Name.Value = $"Copy of {room.Name.Value}"; + if (!newRoom.Name.Value.StartsWith("Copy of ")) + newRoom.Name.Value = $"Copy of {room.Name.Value}"; - loungeSubScreen.Open(newRoom); + loungeSubScreen?.Open(newRoom); }, Action = () => { From 3a6e378a08ce5fde5b23ee302f48ba4afdf4f113 Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Sat, 15 Aug 2020 23:41:53 +0200 Subject: [PATCH 0530/1782] Change skin testing --- .../Formats/LegacyBeatmapEncoderTest.cs | 48 ++++++++++--------- 1 file changed, 25 insertions(+), 23 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs index 63fdf2a8ae..6ede30d7d8 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs @@ -32,25 +32,28 @@ namespace osu.Game.Tests.Beatmaps.Formats private static IEnumerable allBeatmaps = resource_store.GetAvailableResources().Where(res => res.EndsWith(".osu")); - private static readonly Stream beatmap_skin_stream = resource_store.GetStream("skin.ini"); - private ISkin skin; - - [SetUp] - public void Init() - { - skin = decodeSkinFromLegacy(beatmap_skin_stream); - } - [TestCaseSource(nameof(allBeatmaps))] public void TestEncodeDecodeStability(string name) { - var decoded = decodeBeatmapFromLegacy(TestResources.GetStore().GetStream(name)); - var decodedAfterEncode = decodeBeatmapFromLegacy(encodeToLegacy(decoded, skin)); + var decoded = decodeFromLegacy(TestResources.GetStore().GetStream(name)); + var decodedAfterEncode = decodeFromLegacy(encodeToLegacy(decoded)); - sort(decoded); - sort(decodedAfterEncode); + sort(decoded.beatmap); + sort(decodedAfterEncode.beatmap); - Assert.That(decodedAfterEncode.Serialize(), Is.EqualTo(decoded.Serialize())); + Assert.That(decodedAfterEncode.beatmap.Serialize(), Is.EqualTo(decoded.beatmap.Serialize())); + + areSkinsEqual(decoded.beatmapSkin, decodedAfterEncode.beatmapSkin); + } + + private void areSkinsEqual(LegacySkin expected, LegacySkin actual) + { + var expectedColours = expected.Configuration.ComboColours; + var actualColours = actual.Configuration.ComboColours; + + Assert.AreEqual(expectedColours.Count, actualColours.Count); + for (int i = 0; i < expectedColours.Count; i++) + Assert.AreEqual(expectedColours[i], actualColours[i]); } private void sort(IBeatmap beatmap) @@ -63,20 +66,19 @@ namespace osu.Game.Tests.Beatmaps.Formats } } - private IBeatmap decodeBeatmapFromLegacy(Stream stream) + private (IBeatmap beatmap, LegacyBeatmapSkin beatmapSkin) decodeFromLegacy(Stream stream) { using (var reader = new LineBufferedReader(stream)) - return convert(new LegacyBeatmapDecoder { ApplyOffsets = false }.Decode(reader)); + { + var beatmap = new LegacyBeatmapDecoder { ApplyOffsets = false }.Decode(reader); + var beatmapSkin = new LegacyBeatmapSkin(beatmap.BeatmapInfo, resource_store, null); + return (convert(beatmap), beatmapSkin); + } } - private ISkin decodeSkinFromLegacy(Stream stream) - { - using (var reader = new LineBufferedReader(stream)) - return new LegacySkin(SkinInfo.Default, resource_store, null); - } - - private Stream encodeToLegacy(IBeatmap beatmap, ISkin beatmapSkin) + private Stream encodeToLegacy((IBeatmap beatmap, LegacyBeatmapSkin beatmapSkin) fullBeatmap) { + var (beatmap, beatmapSkin) = fullBeatmap; var stream = new MemoryStream(); using (var writer = new StreamWriter(stream, Encoding.UTF8, 1024, true)) From 48bdbb0cfbd9f94b8d9b4182290a1dec66c7c256 Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Sat, 15 Aug 2020 23:46:10 +0200 Subject: [PATCH 0531/1782] Use existing field in Editor --- 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 d52d832bf3..a585db1ee9 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -402,7 +402,7 @@ namespace osu.Game.Screens.Edit clock.SeekForward(!clock.IsRunning, amount); } - private void saveBeatmap() => beatmapManager.Save(playableBeatmap.BeatmapInfo, editorBeatmap, Beatmap.Value.Skin); + private void saveBeatmap() => beatmapManager.Save(playableBeatmap.BeatmapInfo, editorBeatmap, beatmapSkin); private void exportBeatmap() { From 434354c44c2ab3f0bde6d0d04ade551b0a4d00ac Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Sun, 16 Aug 2020 00:21:26 +0200 Subject: [PATCH 0532/1782] Properly implement SkinConfiguration equality --- .../Beatmaps/Formats/LegacyBeatmapEncoderTest.cs | 13 +------------ osu.Game/Skinning/SkinConfiguration.cs | 12 +++++++++++- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs index 6ede30d7d8..66b39648e5 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs @@ -42,18 +42,7 @@ namespace osu.Game.Tests.Beatmaps.Formats sort(decodedAfterEncode.beatmap); Assert.That(decodedAfterEncode.beatmap.Serialize(), Is.EqualTo(decoded.beatmap.Serialize())); - - areSkinsEqual(decoded.beatmapSkin, decodedAfterEncode.beatmapSkin); - } - - private void areSkinsEqual(LegacySkin expected, LegacySkin actual) - { - var expectedColours = expected.Configuration.ComboColours; - var actualColours = actual.Configuration.ComboColours; - - Assert.AreEqual(expectedColours.Count, actualColours.Count); - for (int i = 0; i < expectedColours.Count; i++) - Assert.AreEqual(expectedColours[i], actualColours[i]); + Assert.IsTrue(decoded.beatmapSkin.Configuration.Equals(decodedAfterEncode.beatmapSkin.Configuration)); } private void sort(IBeatmap beatmap) diff --git a/osu.Game/Skinning/SkinConfiguration.cs b/osu.Game/Skinning/SkinConfiguration.cs index a55870aa6d..4b29111504 100644 --- a/osu.Game/Skinning/SkinConfiguration.cs +++ b/osu.Game/Skinning/SkinConfiguration.cs @@ -1,7 +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.Linq; using osu.Game.Beatmaps.Formats; using osuTK.Graphics; @@ -10,7 +12,7 @@ namespace osu.Game.Skinning /// /// An empty skin configuration. /// - public class SkinConfiguration : IHasComboColours, IHasCustomColours + public class SkinConfiguration : IHasComboColours, IHasCustomColours, IEquatable { public readonly SkinInfo SkinInfo = new SkinInfo(); @@ -48,5 +50,13 @@ namespace osu.Game.Skinning public Dictionary CustomColours { get; set; } = new Dictionary(); public readonly Dictionary ConfigDictionary = new Dictionary(); + + public bool Equals(SkinConfiguration other) + { + return other != null && + ConfigDictionary.SequenceEqual(other.ConfigDictionary) && + ComboColours.SequenceEqual(other.ComboColours) && + CustomColours.SequenceEqual(other.CustomColours); + } } } From cfd82104dbe5f23b9a92f2129441f8a328bc80a1 Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Sun, 16 Aug 2020 01:00:28 +0200 Subject: [PATCH 0533/1782] Minor changes and improvements --- osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs | 4 ++-- osu.Game/Screens/Edit/EditorChangeHandler.cs | 2 -- osu.Game/Skinning/SkinConfiguration.cs | 2 +- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs index 66b39648e5..db18f9b444 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs @@ -55,7 +55,7 @@ namespace osu.Game.Tests.Beatmaps.Formats } } - private (IBeatmap beatmap, LegacyBeatmapSkin beatmapSkin) decodeFromLegacy(Stream stream) + private (IBeatmap beatmap, LegacySkin beatmapSkin) decodeFromLegacy(Stream stream) { using (var reader = new LineBufferedReader(stream)) { @@ -65,7 +65,7 @@ namespace osu.Game.Tests.Beatmaps.Formats } } - private Stream encodeToLegacy((IBeatmap beatmap, LegacyBeatmapSkin beatmapSkin) fullBeatmap) + private Stream encodeToLegacy((IBeatmap beatmap, LegacySkin beatmapSkin) fullBeatmap) { var (beatmap, beatmapSkin) = fullBeatmap; var stream = new MemoryStream(); diff --git a/osu.Game/Screens/Edit/EditorChangeHandler.cs b/osu.Game/Screens/Edit/EditorChangeHandler.cs index f305d2a15d..0489236d45 100644 --- a/osu.Game/Screens/Edit/EditorChangeHandler.cs +++ b/osu.Game/Screens/Edit/EditorChangeHandler.cs @@ -90,9 +90,7 @@ namespace osu.Game.Screens.Edit using (var stream = new MemoryStream()) { using (var sw = new StreamWriter(stream, Encoding.UTF8, 1024, true)) - { new LegacyBeatmapEncoder(editorBeatmap, beatmapSkin).Encode(sw); - } savedStates.Add(stream.ToArray()); } diff --git a/osu.Game/Skinning/SkinConfiguration.cs b/osu.Game/Skinning/SkinConfiguration.cs index 4b29111504..a48d713771 100644 --- a/osu.Game/Skinning/SkinConfiguration.cs +++ b/osu.Game/Skinning/SkinConfiguration.cs @@ -56,7 +56,7 @@ namespace osu.Game.Skinning return other != null && ConfigDictionary.SequenceEqual(other.ConfigDictionary) && ComboColours.SequenceEqual(other.ComboColours) && - CustomColours.SequenceEqual(other.CustomColours); + CustomColours?.SequenceEqual(other.CustomColours) == true; } } } From f98e96e45b22b3b34e56543f2249d43e62585d5f Mon Sep 17 00:00:00 2001 From: Shane Woolcock Date: Sun, 16 Aug 2020 10:52:23 +0930 Subject: [PATCH 0534/1782] Add osu!-specific enum for confine mouse mode --- osu.Game/Input/OsuConfineMouseMode.cs | 37 +++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 osu.Game/Input/OsuConfineMouseMode.cs diff --git a/osu.Game/Input/OsuConfineMouseMode.cs b/osu.Game/Input/OsuConfineMouseMode.cs new file mode 100644 index 0000000000..32b456395c --- /dev/null +++ b/osu.Game/Input/OsuConfineMouseMode.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 System.ComponentModel; +using osu.Framework.Input; + +namespace osu.Game.Input +{ + /// + /// Determines the situations in which the mouse cursor should be confined to the window. + /// Expands upon by providing the option to confine during gameplay. + /// + public enum OsuConfineMouseMode + { + /// + /// The mouse cursor will be free to move outside the game window. + /// + Never, + + /// + /// The mouse cursor will be locked to the window bounds while in fullscreen mode. + /// + Fullscreen, + + /// + /// The mouse cursor will be locked to the window bounds during gameplay, + /// but may otherwise move freely. + /// + [Description("During Gameplay")] + DuringGameplay, + + /// + /// The mouse cursor will always be locked to the window bounds while the game has focus. + /// + Always + } +} From 322d179076a383cf7fd2e7506e27189fba025278 Mon Sep 17 00:00:00 2001 From: Shane Woolcock Date: Sun, 16 Aug 2020 11:04:28 +0930 Subject: [PATCH 0535/1782] Replace settings item with osu! confine cursor mode --- osu.Game/Configuration/OsuConfigManager.cs | 3 +++ osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs | 6 +++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index a8a8794320..9ef846c974 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -6,6 +6,7 @@ using osu.Framework.Configuration; using osu.Framework.Configuration.Tracking; using osu.Framework.Extensions; using osu.Framework.Platform; +using osu.Game.Input; using osu.Game.Overlays; using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Select; @@ -66,6 +67,7 @@ namespace osu.Game.Configuration Set(OsuSetting.MouseDisableButtons, false); Set(OsuSetting.MouseDisableWheel, false); + Set(OsuSetting.ConfineMouseMode, OsuConfineMouseMode.DuringGameplay); // Graphics Set(OsuSetting.ShowFpsDisplay, false); @@ -191,6 +193,7 @@ namespace osu.Game.Configuration FadePlayfieldWhenHealthLow, MouseDisableButtons, MouseDisableWheel, + ConfineMouseMode, AudioOffset, VolumeInactive, MenuMusic, diff --git a/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs b/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs index d27ab63fb7..0d98508e3b 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs @@ -6,9 +6,9 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Configuration; using osu.Framework.Graphics; -using osu.Framework.Input; using osu.Game.Configuration; using osu.Game.Graphics.UserInterface; +using osu.Game.Input; namespace osu.Game.Overlays.Settings.Sections.Input { @@ -47,10 +47,10 @@ namespace osu.Game.Overlays.Settings.Sections.Input LabelText = "Map absolute input to window", Bindable = config.GetBindable(FrameworkSetting.MapAbsoluteInputToWindow) }, - new SettingsEnumDropdown + new SettingsEnumDropdown { LabelText = "Confine mouse cursor to window", - Bindable = config.GetBindable(FrameworkSetting.ConfineMouseMode), + Bindable = osuConfig.GetBindable(OsuSetting.ConfineMouseMode) }, new SettingsCheckbox { From 3d6d22f70fbdc37b960d3cbc1bd90f78ba0fcb6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 16 Aug 2020 12:39:41 +0200 Subject: [PATCH 0536/1782] Adjust README.md to read better --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index dc3ee63844..d3e9ca5121 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,9 @@ [![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. +A free-to-win rhythm game. 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 From 6c44513115ec085bcdc181b3dd04a369130b6e51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 16 Aug 2020 12:53:31 +0200 Subject: [PATCH 0537/1782] Update .csproj descriptions to match --- osu.Desktop/osu.Desktop.csproj | 2 +- osu.Desktop/osu.nuspec | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/osu.Desktop/osu.Desktop.csproj b/osu.Desktop/osu.Desktop.csproj index 7a99c70999..62e8f7c518 100644 --- a/osu.Desktop/osu.Desktop.csproj +++ b/osu.Desktop/osu.Desktop.csproj @@ -3,7 +3,7 @@ netcoreapp3.1 WinExe true - click the circles. to the beat. + A free-to-win rhythm game. Rhythm is just a *click* away! osu! osu!lazer osu!lazer diff --git a/osu.Desktop/osu.nuspec b/osu.Desktop/osu.nuspec index a919d54f38..2fc6009183 100644 --- a/osu.Desktop/osu.nuspec +++ b/osu.Desktop/osu.nuspec @@ -9,8 +9,7 @@ https://osu.ppy.sh/ https://puu.sh/tYyXZ/9a01a5d1b0.ico false - click the circles. to the beat. - click the circles. + A free-to-win rhythm game. Rhythm is just a *click* away! testing Copyright (c) 2020 ppy Pty Ltd en-AU From ef3c8fa21f8105ec181be6392bd65c929a597f40 Mon Sep 17 00:00:00 2001 From: Shane Woolcock Date: Sun, 16 Aug 2020 11:38:35 +0930 Subject: [PATCH 0538/1782] Add tracking component to handle OsuConfineMouseMode --- osu.Game/Input/ConfineMouseTracker.cs | 72 +++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 osu.Game/Input/ConfineMouseTracker.cs diff --git a/osu.Game/Input/ConfineMouseTracker.cs b/osu.Game/Input/ConfineMouseTracker.cs new file mode 100644 index 0000000000..b111488a5b --- /dev/null +++ b/osu.Game/Input/ConfineMouseTracker.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.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Configuration; +using osu.Framework.Graphics; +using osu.Framework.Input; +using osu.Game.Configuration; +using osu.Game.Screens.Play; + +namespace osu.Game.Input +{ + /// + /// Connects with + /// while providing a property for to indicate whether gameplay is currently active. + /// + public class ConfineMouseTracker : Component + { + private Bindable frameworkConfineMode; + private Bindable osuConfineMode; + + private bool gameplayActive; + + /// + /// Indicates whether osu! is currently considered "in gameplay" for the + /// purposes of . + /// + public bool GameplayActive + { + get => gameplayActive; + set + { + if (gameplayActive == value) + return; + + gameplayActive = value; + updateConfineMode(); + } + } + + [BackgroundDependencyLoader] + private void load(FrameworkConfigManager frameworkConfigManager, OsuConfigManager osuConfigManager) + { + frameworkConfineMode = frameworkConfigManager.GetBindable(FrameworkSetting.ConfineMouseMode); + osuConfineMode = osuConfigManager.GetBindable(OsuSetting.ConfineMouseMode); + osuConfineMode.BindValueChanged(_ => updateConfineMode(), true); + } + + private void updateConfineMode() + { + switch (osuConfineMode.Value) + { + case OsuConfineMouseMode.Never: + frameworkConfineMode.Value = ConfineMouseMode.Never; + break; + + case OsuConfineMouseMode.Fullscreen: + frameworkConfineMode.Value = ConfineMouseMode.Fullscreen; + break; + + case OsuConfineMouseMode.DuringGameplay: + frameworkConfineMode.Value = GameplayActive ? ConfineMouseMode.Always : ConfineMouseMode.Never; + break; + + case OsuConfineMouseMode.Always: + frameworkConfineMode.Value = ConfineMouseMode.Always; + break; + } + } + } +} From 00f15231bc78a6e7830694bf41cbc1db331e505f Mon Sep 17 00:00:00 2001 From: Shane Woolcock Date: Sun, 16 Aug 2020 21:52:39 +0930 Subject: [PATCH 0539/1782] Cache ConfineMouseTracker --- osu.Game/OsuGame.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 053eb01dcd..7358918758 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -88,6 +88,8 @@ namespace osu.Game private IdleTracker idleTracker; + private ConfineMouseTracker confineMouseTracker; + public readonly Bindable OverlayActivationMode = new Bindable(); protected OsuScreenStack ScreenStack; @@ -553,6 +555,7 @@ namespace osu.Game BackButton.Receptor receptor; dependencies.CacheAs(idleTracker = new GameIdleTracker(6000)); + dependencies.Cache(confineMouseTracker = new ConfineMouseTracker()); AddRange(new Drawable[] { @@ -588,7 +591,8 @@ namespace osu.Game rightFloatingOverlayContent = new Container { RelativeSizeAxes = Axes.Both }, leftFloatingOverlayContent = new Container { RelativeSizeAxes = Axes.Both }, topMostOverlayContent = new Container { RelativeSizeAxes = Axes.Both }, - idleTracker + idleTracker, + confineMouseTracker }); ScreenStack.ScreenPushed += screenPushed; From 85b3fff9c8a695d89453b0dbd1b55eb0d27fe5e4 Mon Sep 17 00:00:00 2001 From: Shane Woolcock Date: Sun, 16 Aug 2020 23:11:09 +0930 Subject: [PATCH 0540/1782] Update mouse confine when gameplay state changes --- osu.Game/Screens/Play/Player.cs | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 541275cf55..3b8c4aea01 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -18,6 +18,7 @@ using osu.Framework.Threading; using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Graphics.Containers; +using osu.Game.Input; using osu.Game.IO.Archives; using osu.Game.Online.API; using osu.Game.Overlays; @@ -63,6 +64,9 @@ namespace osu.Game.Screens.Play private Bindable mouseWheelDisabled; + [Resolved(CanBeNull = true)] + private ConfineMouseTracker confineMouseTracker { get; set; } + private readonly Bindable storyboardReplacesBackground = new Bindable(); public int RestartCount; @@ -197,10 +201,15 @@ namespace osu.Game.Screens.Play skipOverlay.Hide(); } - DrawableRuleset.HasReplayLoaded.BindValueChanged(_ => updatePauseOnFocusLostState(), true); + DrawableRuleset.HasReplayLoaded.BindValueChanged(_ => + { + updatePauseOnFocusLostState(); + updateConfineMouse(); + }, true); // bind clock into components that require it DrawableRuleset.IsPaused.BindTo(GameplayClockContainer.IsPaused); + DrawableRuleset.IsPaused.ValueChanged += _ => updateConfineMouse(); DrawableRuleset.OnNewResult += r => { @@ -346,6 +355,12 @@ namespace osu.Game.Screens.Play && !DrawableRuleset.HasReplayLoaded.Value && !breakTracker.IsBreakTime.Value; + private void updateConfineMouse() + { + if (confineMouseTracker != null) + confineMouseTracker.GameplayActive = !GameplayClockContainer.IsPaused.Value && !DrawableRuleset.HasReplayLoaded.Value && !HasFailed; + } + private IBeatmap loadPlayableBeatmap() { IBeatmap playable; @@ -379,7 +394,7 @@ namespace osu.Game.Screens.Play } catch (Exception e) { - Logger.Error(e, "Could not load beatmap sucessfully!"); + Logger.Error(e, "Could not load beatmap successfully!"); //couldn't load, hard abort! return null; } From a6708c4286d3973c8ed68be43176228da1137237 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 16 Aug 2020 23:04:49 +0900 Subject: [PATCH 0541/1782] Rename resolved variable in MainMenu --- osu.Game/Screens/Menu/MainMenu.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index 470e8ca9a6..fac9e9eb49 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -46,7 +46,7 @@ namespace osu.Game.Screens.Menu private GameHost host { get; set; } [Resolved] - private MusicController music { get; set; } + private MusicController musicController { get; set; } [Resolved(canBeNull: true)] private LoginOverlay login { get; set; } @@ -176,12 +176,12 @@ namespace osu.Game.Screens.Menu var metadata = Beatmap.Value.Metadata; - if (last is IntroScreen && music.TrackLoaded) + if (last is IntroScreen && musicController.TrackLoaded) { - if (!music.CurrentTrack.IsRunning) + if (!musicController.CurrentTrack.IsRunning) { - music.CurrentTrack.Seek(metadata.PreviewTime != -1 ? metadata.PreviewTime : 0.4f * music.CurrentTrack.Length); - music.CurrentTrack.Start(); + musicController.CurrentTrack.Seek(metadata.PreviewTime != -1 ? metadata.PreviewTime : 0.4f * musicController.CurrentTrack.Length); + musicController.CurrentTrack.Start(); } } @@ -256,7 +256,7 @@ namespace osu.Game.Screens.Menu // we may have consumed our preloaded instance, so let's make another. preloadSongSelect(); - music.EnsurePlayingSomething(); + musicController.EnsurePlayingSomething(); } public override bool OnExiting(IScreen next) From 5d433c0b055d3c284b1737df65856f5e118a817f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 16 Aug 2020 23:11:29 +0900 Subject: [PATCH 0542/1782] Fix a couple of new Resharper inspections --- osu.Game/Screens/Menu/ButtonSystem.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Menu/ButtonSystem.cs b/osu.Game/Screens/Menu/ButtonSystem.cs index 30e5e9702e..5ba7a8ddc3 100644 --- a/osu.Game/Screens/Menu/ButtonSystem.cs +++ b/osu.Game/Screens/Menu/ButtonSystem.cs @@ -324,10 +324,9 @@ namespace osu.Game.Screens.Menu bool impact = logo.Scale.X > 0.6f; - if (lastState == ButtonSystemState.Initial) - logo.ScaleTo(0.5f, 200, Easing.In); + logo.ScaleTo(0.5f, 200, Easing.In); - logoTrackingContainer.StartTracking(logo, lastState == ButtonSystemState.EnteringMode ? 0 : 200, Easing.In); + logoTrackingContainer.StartTracking(logo, 200, Easing.In); logoDelayedAction?.Cancel(); logoDelayedAction = Scheduler.AddDelayed(() => From 589d4eeb5297a046b7001cb7c55a2e9b5c3ea824 Mon Sep 17 00:00:00 2001 From: Lucas A Date: Sun, 16 Aug 2020 17:18:40 +0200 Subject: [PATCH 0543/1782] Remove setting. --- .../Visual/Gameplay/TestSceneOverlayActivation.cs | 12 ------------ osu.Game/Configuration/OsuConfigManager.cs | 2 -- .../Settings/Sections/Gameplay/GeneralSettings.cs | 5 ----- osu.Game/Screens/Play/Player.cs | 14 ++++++-------- 4 files changed, 6 insertions(+), 27 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneOverlayActivation.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneOverlayActivation.cs index 04c67433fa..3ee0f4e720 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneOverlayActivation.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneOverlayActivation.cs @@ -3,7 +3,6 @@ using System.Linq; using NUnit.Framework; -using osu.Game.Configuration; using osu.Game.Overlays; using osu.Game.Rulesets; @@ -16,21 +15,12 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestGameplayOverlayActivation() { - AddStep("disable overlay activation during gameplay", () => LocalConfig.Set(OsuSetting.GameplayDisableOverlayActivation, true)); AddAssert("activation mode is disabled", () => Player.OverlayActivationMode == OverlayActivation.Disabled); } - [Test] - public void TestGameplayOverlayActivationDisabled() - { - AddStep("enable overlay activation during gameplay", () => LocalConfig.Set(OsuSetting.GameplayDisableOverlayActivation, false)); - AddAssert("activation mode is user triggered", () => Player.OverlayActivationMode == OverlayActivation.UserTriggered); - } - [Test] public void TestGameplayOverlayActivationPaused() { - AddStep("disable overlay activation during gameplay", () => LocalConfig.Set(OsuSetting.GameplayDisableOverlayActivation, true)); AddUntilStep("activation mode is disabled", () => Player.OverlayActivationMode == OverlayActivation.Disabled); AddStep("pause gameplay", () => Player.Pause()); AddUntilStep("activation mode is user triggered", () => Player.OverlayActivationMode == OverlayActivation.UserTriggered); @@ -39,7 +29,6 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestGameplayOverlayActivationReplayLoaded() { - AddStep("disable overlay activation during gameplay", () => LocalConfig.Set(OsuSetting.GameplayDisableOverlayActivation, true)); AddAssert("activation mode is disabled", () => Player.OverlayActivationMode == OverlayActivation.Disabled); AddStep("load a replay", () => Player.DrawableRuleset.HasReplayLoaded.Value = true); AddAssert("activation mode is user triggered", () => Player.OverlayActivationMode == OverlayActivation.UserTriggered); @@ -48,7 +37,6 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestGameplayOverlayActivationBreaks() { - AddStep("disable overlay activation during gameplay", () => LocalConfig.Set(OsuSetting.GameplayDisableOverlayActivation, true)); AddAssert("activation mode is disabled", () => Player.OverlayActivationMode == OverlayActivation.Disabled); AddStep("seek to break", () => Player.GameplayClockContainer.Seek(Beatmap.Value.Beatmap.Breaks.First().StartTime)); AddUntilStep("activation mode is user triggered", () => Player.OverlayActivationMode == OverlayActivation.UserTriggered); diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 44c0fbde82..d49c78183e 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -100,7 +100,6 @@ namespace osu.Game.Configuration Set(OsuSetting.IncreaseFirstObjectVisibility, true); Set(OsuSetting.GameplayDisableWinKey, true); - Set(OsuSetting.GameplayDisableOverlayActivation, true); // Update Set(OsuSetting.ReleaseStream, ReleaseStream.Lazer); @@ -233,6 +232,5 @@ namespace osu.Game.Configuration HitLighting, MenuBackgroundSource, GameplayDisableWinKey, - GameplayDisableOverlayActivation } } diff --git a/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs index c2e668fe68..0149e6c3a6 100644 --- a/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs @@ -77,11 +77,6 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay { LabelText = "Score display mode", Bindable = config.GetBindable(OsuSetting.ScoreDisplayMode) - }, - new SettingsCheckbox - { - LabelText = "Disable overlays during gameplay", - Bindable = config.GetBindable(OsuSetting.GameplayDisableOverlayActivation) } }; diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 6bb4be4096..17838e0502 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -69,8 +69,6 @@ namespace osu.Game.Screens.Play private Bindable mouseWheelDisabled; - private Bindable gameplayOverlaysDisabled; - private readonly Bindable storyboardReplacesBackground = new Bindable(); public int RestartCount; @@ -176,7 +174,6 @@ namespace osu.Game.Screens.Play sampleRestart = audio.Samples.Get(@"Gameplay/restart"); mouseWheelDisabled = config.GetBindable(OsuSetting.MouseDisableWheel); - gameplayOverlaysDisabled = config.GetBindable(OsuSetting.GameplayDisableOverlayActivation); DrawableRuleset = ruleset.CreateDrawableRulesetWith(playableBeatmap, Mods.Value); @@ -212,6 +209,10 @@ namespace osu.Game.Screens.Play if (game != null) OverlayActivationMode.BindTo(game.OverlayActivationMode); + DrawableRuleset.IsPaused.BindValueChanged(_ => updateOverlayActivationMode()); + DrawableRuleset.HasReplayLoaded.BindValueChanged(_ => updateOverlayActivationMode()); + breakTracker.IsBreakTime.BindValueChanged(_ => updateOverlayActivationMode()); + DrawableRuleset.HasReplayLoaded.BindValueChanged(_ => updatePauseOnFocusLostState(), true); // bind clock into components that require it @@ -358,7 +359,7 @@ namespace osu.Game.Screens.Play private void updateOverlayActivationMode() { - bool canTriggerOverlays = DrawableRuleset.IsPaused.Value || breakTracker.IsBreakTime.Value || !gameplayOverlaysDisabled.Value; + bool canTriggerOverlays = DrawableRuleset.IsPaused.Value || breakTracker.IsBreakTime.Value; if (DrawableRuleset.HasReplayLoaded.Value || canTriggerOverlays) OverlayActivationMode.Value = OverlayActivation.UserTriggered; @@ -653,10 +654,7 @@ namespace osu.Game.Screens.Play foreach (var mod in Mods.Value.OfType()) mod.ApplyToHUD(HUDOverlay); - DrawableRuleset.IsPaused.BindValueChanged(_ => updateOverlayActivationMode()); - DrawableRuleset.HasReplayLoaded.BindValueChanged(_ => updateOverlayActivationMode()); - breakTracker.IsBreakTime.BindValueChanged(_ => updateOverlayActivationMode()); - gameplayOverlaysDisabled.BindValueChanged(_ => updateOverlayActivationMode(), true); + updateOverlayActivationMode(); } public override void OnSuspending(IScreen next) From 948c3cfbf1fe65a5974b13f555e84f2fd3566db1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 17 Aug 2020 14:56:05 +0900 Subject: [PATCH 0544/1782] Improve visibility of toolbar tooltips against bright backgrounds --- osu.Game/Overlays/Toolbar/Toolbar.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Toolbar/Toolbar.cs b/osu.Game/Overlays/Toolbar/Toolbar.cs index 5bdd86c671..beac6adc59 100644 --- a/osu.Game/Overlays/Toolbar/Toolbar.cs +++ b/osu.Game/Overlays/Toolbar/Toolbar.cs @@ -118,9 +118,9 @@ namespace osu.Game.Overlays.Toolbar RelativeSizeAxes = Axes.X, Anchor = Anchor.BottomLeft, Alpha = 0, - Height = 90, + Height = 100, Colour = ColourInfo.GradientVertical( - OsuColour.Gray(0.1f).Opacity(0.5f), OsuColour.Gray(0.1f).Opacity(0)), + OsuColour.Gray(0).Opacity(0.9f), OsuColour.Gray(0).Opacity(0)), }, }; } From d9debef1568a393f0da721759d09206ffe5c3701 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 17 Aug 2020 15:38:16 +0900 Subject: [PATCH 0545/1782] Add explicit LoadTrack method --- .../TestSceneGameplayClockContainer.cs | 4 +++- .../Gameplay/TestSceneStoryboardSamples.cs | 5 ++-- .../Visual/Gameplay/TestSceneSkipOverlay.cs | 3 ++- osu.Game/Beatmaps/IWorkingBeatmap.cs | 2 +- osu.Game/Beatmaps/WorkingBeatmap.cs | 23 ++++++++++++++++++- osu.Game/Overlays/MusicController.cs | 2 +- osu.Game/Screens/Edit/EditorClock.cs | 2 +- osu.Game/Screens/Menu/IntroScreen.cs | 2 +- .../Screens/Play/GameplayClockContainer.cs | 4 ++-- osu.Game/Screens/Play/Player.cs | 2 +- 10 files changed, 37 insertions(+), 12 deletions(-) diff --git a/osu.Game.Tests/Gameplay/TestSceneGameplayClockContainer.cs b/osu.Game.Tests/Gameplay/TestSceneGameplayClockContainer.cs index bb60ae73db..dc9f540907 100644 --- a/osu.Game.Tests/Gameplay/TestSceneGameplayClockContainer.cs +++ b/osu.Game.Tests/Gameplay/TestSceneGameplayClockContainer.cs @@ -22,7 +22,9 @@ namespace osu.Game.Tests.Gameplay AddStep("create container", () => { var working = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo); - Add(gcc = new GameplayClockContainer(working.GetTrack(), working, Array.Empty(), 0)); + working.LoadTrack(); + + Add(gcc = new GameplayClockContainer(working, Array.Empty(), 0)); }); AddStep("start track", () => gcc.Start()); diff --git a/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs b/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs index 360e7eccdc..6f788a070e 100644 --- a/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs +++ b/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs @@ -60,8 +60,9 @@ namespace osu.Game.Tests.Gameplay AddStep("create container", () => { var working = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo); + working.LoadTrack(); - Add(gameplayContainer = new GameplayClockContainer(working.GetTrack(), working, Array.Empty(), 0)); + Add(gameplayContainer = new GameplayClockContainer(working, Array.Empty(), 0)); gameplayContainer.Add(sample = new DrawableStoryboardSample(new StoryboardSampleInfo(string.Empty, 0, 1)) { @@ -105,7 +106,7 @@ namespace osu.Game.Tests.Gameplay Beatmap.Value = new TestCustomSkinWorkingBeatmap(new OsuRuleset().RulesetInfo, Audio); SelectedMods.Value = new[] { testedMod }; - Add(gameplayContainer = new GameplayClockContainer(MusicController.CurrentTrack, Beatmap.Value, SelectedMods.Value, 0)); + Add(gameplayContainer = new GameplayClockContainer(Beatmap.Value, SelectedMods.Value, 0)); gameplayContainer.Add(sample = new TestDrawableStoryboardSample(new StoryboardSampleInfo("test-sample", 1, 1)) { diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs index 58fd760fc3..c7e5e2a7ec 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs @@ -33,8 +33,9 @@ namespace osu.Game.Tests.Visual.Gameplay increment = skip_time; var working = CreateWorkingBeatmap(CreateBeatmap(new OsuRuleset().RulesetInfo)); + working.LoadTrack(); - Child = gameplayClockContainer = new GameplayClockContainer(working.GetTrack(), working, Array.Empty(), 0) + Child = gameplayClockContainer = new GameplayClockContainer(working, Array.Empty(), 0) { RelativeSizeAxes = Axes.Both, Children = new Drawable[] diff --git a/osu.Game/Beatmaps/IWorkingBeatmap.cs b/osu.Game/Beatmaps/IWorkingBeatmap.cs index e020625b99..88d73fd7c4 100644 --- a/osu.Game/Beatmaps/IWorkingBeatmap.cs +++ b/osu.Game/Beatmaps/IWorkingBeatmap.cs @@ -58,6 +58,6 @@ namespace osu.Game.Beatmaps /// /// Retrieves the which this provides. /// - Track GetTrack(); + Track LoadTrack(); } } diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs index bec2679103..b4046a4e95 100644 --- a/osu.Game/Beatmaps/WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/WorkingBeatmap.cs @@ -250,8 +250,29 @@ namespace osu.Game.Beatmaps protected abstract Texture GetBackground(); private readonly RecyclableLazy background; + private Track loadedTrack; + + /// + /// Load a new audio track instance for this beatmap. + /// + /// A fresh track instance, which will also be available via . [NotNull] - public Track GetTrack() => GetBeatmapTrack() ?? GetVirtualTrack(1000); + public Track LoadTrack() => loadedTrack = GetBeatmapTrack() ?? GetVirtualTrack(1000); + + /// + /// Get the loaded audio track instance. must have first been called. + /// This generally happens via MusicController when changing the global beatmap. + /// + public Track Track + { + get + { + if (loadedTrack == null) + throw new InvalidOperationException($"Cannot access {nameof(Track)} without first calling {nameof(LoadTrack)}."); + + return loadedTrack; + } + } protected abstract Track GetBeatmapTrack(); diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index c18b564b4f..8bbae33811 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -319,7 +319,7 @@ namespace osu.Game.Overlays { var lastTrack = CurrentTrack; - var newTrack = new DrawableTrack(current.GetTrack()); + var newTrack = new DrawableTrack(current.LoadTrack()); newTrack.Completed += () => onTrackCompleted(current); CurrentTrack = newTrack; diff --git a/osu.Game/Screens/Edit/EditorClock.cs b/osu.Game/Screens/Edit/EditorClock.cs index d0e265adb0..634a6f7e25 100644 --- a/osu.Game/Screens/Edit/EditorClock.cs +++ b/osu.Game/Screens/Edit/EditorClock.cs @@ -26,7 +26,7 @@ namespace osu.Game.Screens.Edit private readonly DecoupleableInterpolatingFramedClock underlyingClock; public EditorClock(WorkingBeatmap beatmap, BindableBeatDivisor beatDivisor) - : this(beatmap.Beatmap.ControlPointInfo, beatmap.GetTrack().Length, beatDivisor) + : this(beatmap.Beatmap.ControlPointInfo, beatmap.Track.Length, beatDivisor) { } diff --git a/osu.Game/Screens/Menu/IntroScreen.cs b/osu.Game/Screens/Menu/IntroScreen.cs index 389629445c..3e4320ae44 100644 --- a/osu.Game/Screens/Menu/IntroScreen.cs +++ b/osu.Game/Screens/Menu/IntroScreen.cs @@ -115,7 +115,7 @@ namespace osu.Game.Screens.Menu if (setInfo != null) { initialBeatmap = beatmaps.GetWorkingBeatmap(setInfo.Beatmaps[0]); - UsingThemedIntro = !initialBeatmap.GetTrack().IsDummyDevice; + UsingThemedIntro = !initialBeatmap.LoadTrack().IsDummyDevice; } return UsingThemedIntro; diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index f0bbcf957a..50a7331e4f 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -62,12 +62,12 @@ namespace osu.Game.Screens.Play private readonly FramedOffsetClock platformOffsetClock; - public GameplayClockContainer([NotNull] ITrack track, WorkingBeatmap beatmap, IReadOnlyList mods, double gameplayStartTime) + public GameplayClockContainer(WorkingBeatmap beatmap, IReadOnlyList mods, double gameplayStartTime) { this.beatmap = beatmap; this.mods = mods; this.gameplayStartTime = gameplayStartTime; - this.track = track; + track = beatmap.Track; firstHitObjectTime = beatmap.Beatmap.HitObjects.First().StartTime; diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index e92164de7c..ccdd4ea8a4 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -179,7 +179,7 @@ namespace osu.Game.Screens.Play if (!ScoreProcessor.Mode.Disabled) config.BindWith(OsuSetting.ScoreDisplayMode, ScoreProcessor.Mode); - InternalChild = GameplayClockContainer = new GameplayClockContainer(musicController.CurrentTrack, Beatmap.Value, Mods.Value, DrawableRuleset.GameplayStartTime); + InternalChild = GameplayClockContainer = new GameplayClockContainer(Beatmap.Value, Mods.Value, DrawableRuleset.GameplayStartTime); AddInternal(gameplayBeatmap = new GameplayBeatmap(playableBeatmap)); AddInternal(screenSuspension = new ScreenSuspensionHandler(GameplayClockContainer)); From 93a8bc3d5aa330cb6ef008ac8858ae4d005434aa Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 17 Aug 2020 22:36:03 +0900 Subject: [PATCH 0546/1782] Remove local reset method in GameplayClockContainer --- .../TestSceneGameplayClockContainer.cs | 4 +-- .../Gameplay/TestSceneStoryboardSamples.cs | 4 +-- .../Visual/Gameplay/TestSceneSkipOverlay.cs | 4 +-- .../Screens/Play/GameplayClockContainer.cs | 31 +++++++------------ osu.Game/Screens/Play/Player.cs | 2 +- osu.Game/Tests/Visual/PlayerTestScene.cs | 2 ++ 6 files changed, 19 insertions(+), 28 deletions(-) diff --git a/osu.Game.Tests/Gameplay/TestSceneGameplayClockContainer.cs b/osu.Game.Tests/Gameplay/TestSceneGameplayClockContainer.cs index dc9f540907..891537c4ad 100644 --- a/osu.Game.Tests/Gameplay/TestSceneGameplayClockContainer.cs +++ b/osu.Game.Tests/Gameplay/TestSceneGameplayClockContainer.cs @@ -1,10 +1,8 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using NUnit.Framework; using osu.Framework.Testing; -using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu; using osu.Game.Screens.Play; using osu.Game.Tests.Visual; @@ -24,7 +22,7 @@ namespace osu.Game.Tests.Gameplay var working = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo); working.LoadTrack(); - Add(gcc = new GameplayClockContainer(working, Array.Empty(), 0)); + Add(gcc = new GameplayClockContainer(working, 0)); }); AddStep("start track", () => gcc.Start()); diff --git a/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs b/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs index 6f788a070e..a690eb3b59 100644 --- a/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs +++ b/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs @@ -62,7 +62,7 @@ namespace osu.Game.Tests.Gameplay var working = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo); working.LoadTrack(); - Add(gameplayContainer = new GameplayClockContainer(working, Array.Empty(), 0)); + Add(gameplayContainer = new GameplayClockContainer(working, 0)); gameplayContainer.Add(sample = new DrawableStoryboardSample(new StoryboardSampleInfo(string.Empty, 0, 1)) { @@ -106,7 +106,7 @@ namespace osu.Game.Tests.Gameplay Beatmap.Value = new TestCustomSkinWorkingBeatmap(new OsuRuleset().RulesetInfo, Audio); SelectedMods.Value = new[] { testedMod }; - Add(gameplayContainer = new GameplayClockContainer(Beatmap.Value, SelectedMods.Value, 0)); + Add(gameplayContainer = new GameplayClockContainer(Beatmap.Value, 0)); gameplayContainer.Add(sample = new TestDrawableStoryboardSample(new StoryboardSampleInfo("test-sample", 1, 1)) { diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs index c7e5e2a7ec..841722a8f1 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.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 System; using NUnit.Framework; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu; using osu.Game.Screens.Play; using osuTK; @@ -35,7 +33,7 @@ namespace osu.Game.Tests.Visual.Gameplay var working = CreateWorkingBeatmap(CreateBeatmap(new OsuRuleset().RulesetInfo)); working.LoadTrack(); - Child = gameplayClockContainer = new GameplayClockContainer(working, Array.Empty(), 0) + Child = gameplayClockContainer = new GameplayClockContainer(working, 0) { RelativeSizeAxes = Axes.Both, Children = new Drawable[] diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index 50a7331e4f..7a9cb3dddd 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.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.Diagnostics.CodeAnalysis; using System.Linq; using System.Threading.Tasks; @@ -16,7 +15,6 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Configuration; -using osu.Game.Rulesets.Mods; namespace osu.Game.Screens.Play { @@ -26,7 +24,6 @@ namespace osu.Game.Screens.Play public class GameplayClockContainer : Container { private readonly WorkingBeatmap beatmap; - private readonly IReadOnlyList mods; [NotNull] private ITrack track; @@ -62,10 +59,9 @@ namespace osu.Game.Screens.Play private readonly FramedOffsetClock platformOffsetClock; - public GameplayClockContainer(WorkingBeatmap beatmap, IReadOnlyList mods, double gameplayStartTime) + public GameplayClockContainer(WorkingBeatmap beatmap, double gameplayStartTime) { this.beatmap = beatmap; - this.mods = mods; this.gameplayStartTime = gameplayStartTime; track = beatmap.Track; @@ -122,13 +118,10 @@ namespace osu.Game.Screens.Play public void Restart() { - // The Reset() call below causes speed adjustments to be reset in an async context, leading to deadlocks. - // The deadlock can be prevented by resetting the track synchronously before entering the async context. - track.ResetSpeedAdjustments(); - Task.Run(() => { - track.Reset(); + track.Seek(0); + track.Stop(); Schedule(() => { @@ -216,14 +209,13 @@ namespace osu.Game.Screens.Play private void updateRate() { - speedAdjustmentsApplied = true; - track.ResetSpeedAdjustments(); + if (speedAdjustmentsApplied) + return; track.AddAdjustment(AdjustableProperty.Frequency, pauseFreqAdjust); track.AddAdjustment(AdjustableProperty.Tempo, UserPlaybackRate); - foreach (var mod in mods.OfType()) - mod.ApplyToTrack(track); + speedAdjustmentsApplied = true; } protected override void Dispose(bool isDisposing) @@ -234,11 +226,12 @@ namespace osu.Game.Screens.Play private void removeSourceClockAdjustments() { - if (speedAdjustmentsApplied) - { - track.ResetSpeedAdjustments(); - speedAdjustmentsApplied = false; - } + if (!speedAdjustmentsApplied) return; + + track.RemoveAdjustment(AdjustableProperty.Frequency, pauseFreqAdjust); + track.RemoveAdjustment(AdjustableProperty.Tempo, UserPlaybackRate); + + speedAdjustmentsApplied = false; } private class HardwareCorrectionOffsetClock : FramedOffsetClock diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index ccdd4ea8a4..cc70995b26 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -179,7 +179,7 @@ namespace osu.Game.Screens.Play if (!ScoreProcessor.Mode.Disabled) config.BindWith(OsuSetting.ScoreDisplayMode, ScoreProcessor.Mode); - InternalChild = GameplayClockContainer = new GameplayClockContainer(Beatmap.Value, Mods.Value, DrawableRuleset.GameplayStartTime); + InternalChild = GameplayClockContainer = new GameplayClockContainer(Beatmap.Value, DrawableRuleset.GameplayStartTime); AddInternal(gameplayBeatmap = new GameplayBeatmap(playableBeatmap)); AddInternal(screenSuspension = new ScreenSuspensionHandler(GameplayClockContainer)); diff --git a/osu.Game/Tests/Visual/PlayerTestScene.cs b/osu.Game/Tests/Visual/PlayerTestScene.cs index 2c46e7f6d3..7d06c99133 100644 --- a/osu.Game/Tests/Visual/PlayerTestScene.cs +++ b/osu.Game/Tests/Visual/PlayerTestScene.cs @@ -29,6 +29,8 @@ namespace osu.Game.Tests.Visual { Dependencies.Cache(LocalConfig = new OsuConfigManager(LocalStorage)); LocalConfig.GetBindable(OsuSetting.DimLevel).Value = 1.0; + + MusicController.AllowRateAdjustments = true; } [SetUpSteps] From 548ccc1a50694b931924d7cc0b8d0e49803f3037 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 18 Aug 2020 00:29:00 +0900 Subject: [PATCH 0547/1782] Initial implementation of hold note freezing --- .../Objects/Drawables/DrawableHoldNote.cs | 36 ++++++++++++++----- 1 file changed, 27 insertions(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs index 0c5289efe1..39b1771643 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; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces; @@ -11,6 +12,7 @@ using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Skinning; +using osuTK; namespace osu.Game.Rulesets.Mania.Objects.Drawables { @@ -32,6 +34,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables private readonly Container tailContainer; private readonly Container tickContainer; + private readonly Container bodyPieceContainer; private readonly Drawable bodyPiece; /// @@ -44,19 +47,25 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables /// public bool HasBroken { get; private set; } + /// + /// Whether the hold note has been released potentially without having caused a break. + /// + private bool hasReleased; + public DrawableHoldNote(HoldNote hitObject) : base(hitObject) { RelativeSizeAxes = Axes.X; - AddRangeInternal(new[] + AddRangeInternal(new Drawable[] { - bodyPiece = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.HoldNoteBody, hitObject.Column), _ => new DefaultBodyPiece + bodyPieceContainer = new Container { - RelativeSizeAxes = Axes.Both - }) - { - RelativeSizeAxes = Axes.X + RelativeSizeAxes = Axes.X, + Child = bodyPiece = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.HoldNoteBody, hitObject.Column), _ => new DefaultBodyPiece + { + RelativeSizeAxes = Axes.Both + }) }, tickContainer = new Container { RelativeSizeAxes = Axes.Both }, headContainer = new Container { RelativeSizeAxes = Axes.Both }, @@ -127,7 +136,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables { base.OnDirectionChanged(e); - bodyPiece.Anchor = bodyPiece.Origin = e.NewValue == ScrollingDirection.Up ? Anchor.TopLeft : Anchor.BottomLeft; + bodyPieceContainer.Anchor = bodyPieceContainer.Origin = e.NewValue == ScrollingDirection.Up ? Anchor.TopLeft : Anchor.BottomLeft; + bodyPieceContainer.Anchor = bodyPieceContainer.Origin = e.NewValue == ScrollingDirection.Up ? Anchor.BottomLeft : Anchor.TopLeft; } public override void PlaySamples() @@ -140,8 +150,14 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables base.Update(); // Make the body piece not lie under the head note - bodyPiece.Y = (Direction.Value == ScrollingDirection.Up ? 1 : -1) * Head.Height / 2; - bodyPiece.Height = DrawHeight - Head.Height / 2 + Tail.Height / 2; + bodyPieceContainer.Y = (Direction.Value == ScrollingDirection.Up ? 1 : -1) * Head.Height / 2; + bodyPieceContainer.Height = DrawHeight - Head.Height / 2 + Tail.Height / 2; + + if (Head.IsHit && !hasReleased) + { + float heightDecrease = (float)(Math.Max(0, Time.Current - HitObject.StartTime) / HitObject.Duration); + bodyPiece.Height = MathHelper.Clamp(1 - heightDecrease, 0, 1); + } } protected override void UpdateStateTransforms(ArmedState state) @@ -206,6 +222,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables // If the key has been released too early, the user should not receive full score for the release if (!Tail.IsHit) HasBroken = true; + + hasReleased = true; } private void endHold() From b969bc03e0d48b91c1c17eec744b948945dec70a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 18 Aug 2020 00:47:32 +0900 Subject: [PATCH 0548/1782] Add loading spinner while editor screen loads --- osu.Game/Screens/Edit/EditorScreenWithTimeline.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs b/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs index e9ff0b5598..67442aa55e 100644 --- a/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs +++ b/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs @@ -7,6 +7,7 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics.UserInterface; using osu.Game.Screens.Edit.Compose.Components; using osu.Game.Screens.Edit.Compose.Components.Timeline; using osuTK.Graphics; @@ -32,6 +33,8 @@ namespace osu.Game.Screens.Edit Container mainContent; + LoadingSpinner spinner; + Children = new Drawable[] { mainContent = new Container @@ -44,6 +47,10 @@ namespace osu.Game.Screens.Edit Top = vertical_margins + timeline_height, Bottom = vertical_margins }, + Child = spinner = new LoadingSpinner(true) + { + State = { Value = Visibility.Visible }, + }, }, new Container { @@ -87,9 +94,10 @@ namespace osu.Game.Screens.Edit } }, }; - LoadComponentAsync(CreateMainContent(), content => { + spinner.State.Value = Visibility.Hidden; + mainContent.Add(content); content.FadeInFromZero(300, Easing.OutQuint); From 583760100a633b037c5194cf8b2e0cae3820af40 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 18 Aug 2020 01:40:55 +0900 Subject: [PATCH 0549/1782] Implement mania invert mod --- .../Mods/TestSceneManiaModInvert.cs | 21 ++++++ osu.Game.Rulesets.Mania/ManiaRuleset.cs | 1 + .../Mods/ManiaModInvert.cs | 68 +++++++++++++++++++ osu.Game.Rulesets.Mania/Objects/HoldNote.cs | 6 +- osu.Game/Beatmaps/WorkingBeatmap.cs | 9 +++ .../Mods/IApplicableAfterBeatmapConversion.cs | 19 ++++++ 6 files changed, 121 insertions(+), 3 deletions(-) create mode 100644 osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModInvert.cs create mode 100644 osu.Game.Rulesets.Mania/Mods/ManiaModInvert.cs create mode 100644 osu.Game/Rulesets/Mods/IApplicableAfterBeatmapConversion.cs diff --git a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModInvert.cs b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModInvert.cs new file mode 100644 index 0000000000..f2cc254e38 --- /dev/null +++ b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModInvert.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 NUnit.Framework; +using osu.Game.Rulesets.Mania.Mods; +using osu.Game.Tests.Visual; + +namespace osu.Game.Rulesets.Mania.Tests.Mods +{ + public class TestSceneManiaModInvert : ModTestScene + { + protected override Ruleset CreatePlayerRuleset() => new ManiaRuleset(); + + [Test] + public void TestInversion() => CreateModTest(new ModTestData + { + Mod = new ManiaModInvert(), + PassCondition = () => Player.ScoreProcessor.JudgedHits >= 2 + }); + } +} diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index 68dce8b139..2795868c97 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -220,6 +220,7 @@ namespace osu.Game.Rulesets.Mania new ManiaModDualStages(), new ManiaModMirror(), new ManiaModDifficultyAdjust(), + new ManiaModInvert(), }; case ModType.Automation: diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModInvert.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModInvert.cs new file mode 100644 index 0000000000..2fb7a75141 --- /dev/null +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModInvert.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.Collections.Generic; +using System.Linq; +using osu.Game.Audio; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Mania.Beatmaps; +using osu.Game.Rulesets.Mania.Objects; +using osu.Game.Rulesets.Mods; + +namespace osu.Game.Rulesets.Mania.Mods +{ + public class ManiaModInvert : Mod, IApplicableAfterBeatmapConversion + { + public override string Name => "Invert"; + + public override string Acronym => "IN"; + public override double ScoreMultiplier => 1; + + public override string Description => "Hold the keys. To the beat."; + + public override ModType Type => ModType.Conversion; + + public void ApplyToBeatmap(IBeatmap beatmap) + { + var maniaBeatmap = (ManiaBeatmap)beatmap; + + var newObjects = new List(); + + foreach (var column in maniaBeatmap.HitObjects.GroupBy(h => h.Column)) + { + var newColumnObjects = new List(); + + var locations = column.OfType().Select(n => (startTime: n.StartTime, samples: n.Samples)) + .Concat(column.OfType().SelectMany(h => new[] + { + (startTime: h.StartTime, samples: h.GetNodeSamples(0)), + (startTime: h.EndTime, samples: h.GetNodeSamples(1)) + })) + .OrderBy(h => h.startTime).ToList(); + + for (int i = 0; i < locations.Count - 1; i += 2) + { + newColumnObjects.Add(new HoldNote + { + Column = column.Key, + StartTime = locations[i].startTime, + Duration = locations[i + 1].startTime - locations[i].startTime, + Samples = locations[i].samples, + NodeSamples = new List> + { + locations[i].samples, + locations[i + 1].samples + } + }); + } + + newObjects.AddRange(newColumnObjects); + } + + maniaBeatmap.HitObjects = newObjects.OrderBy(h => h.StartTime).ToList(); + + // No breaks + maniaBeatmap.Breaks.Clear(); + } + } +} diff --git a/osu.Game.Rulesets.Mania/Objects/HoldNote.cs b/osu.Game.Rulesets.Mania/Objects/HoldNote.cs index a100c9a58e..6cc7ff92d3 100644 --- a/osu.Game.Rulesets.Mania/Objects/HoldNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/HoldNote.cs @@ -102,14 +102,14 @@ namespace osu.Game.Rulesets.Mania.Objects { StartTime = StartTime, Column = Column, - Samples = getNodeSamples(0), + Samples = GetNodeSamples(0), }); AddNested(Tail = new TailNote { StartTime = EndTime, Column = Column, - Samples = getNodeSamples((NodeSamples?.Count - 1) ?? 1), + Samples = GetNodeSamples((NodeSamples?.Count - 1) ?? 1), }); } @@ -134,7 +134,7 @@ namespace osu.Game.Rulesets.Mania.Objects protected override HitWindows CreateHitWindows() => HitWindows.Empty; - private IList getNodeSamples(int nodeIndex) => + public IList GetNodeSamples(int nodeIndex) => nodeIndex < NodeSamples?.Count ? NodeSamples[nodeIndex] : Samples; } } diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs index ac399e37c4..b4bcf285b9 100644 --- a/osu.Game/Beatmaps/WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/WorkingBeatmap.cs @@ -109,6 +109,15 @@ namespace osu.Game.Beatmaps // Convert IBeatmap converted = converter.Convert(); + // Apply conversion mods to the result + foreach (var mod in mods.OfType()) + { + if (cancellationSource.IsCancellationRequested) + throw new BeatmapLoadTimeoutException(BeatmapInfo); + + mod.ApplyToBeatmap(converted); + } + // Apply difficulty mods if (mods.Any(m => m is IApplicableToDifficulty)) { diff --git a/osu.Game/Rulesets/Mods/IApplicableAfterBeatmapConversion.cs b/osu.Game/Rulesets/Mods/IApplicableAfterBeatmapConversion.cs new file mode 100644 index 0000000000..d45311675d --- /dev/null +++ b/osu.Game/Rulesets/Mods/IApplicableAfterBeatmapConversion.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.Beatmaps; + +namespace osu.Game.Rulesets.Mods +{ + /// + /// Interface for a that applies changes to the generated by the . + /// + public interface IApplicableAfterBeatmapConversion : IApplicableMod + { + /// + /// Applies this to the after conversion has taken place. + /// + /// The converted . + void ApplyToBeatmap(IBeatmap beatmap); + } +} From 9e8192e31d0237043743792ddb3cd75cddd86804 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 17 Aug 2020 17:14:51 +0000 Subject: [PATCH 0550/1782] Bump Microsoft.CodeAnalysis.BannedApiAnalyzers from 3.0.0 to 3.3.0 Bumps [Microsoft.CodeAnalysis.BannedApiAnalyzers](https://github.com/dotnet/roslyn-analyzers) from 3.0.0 to 3.3.0. - [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/v3.0.0...v3.3.0) 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 2cd40c8675..2d3478f256 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -16,7 +16,7 @@ - + From e4303d79436113148cbe505655e5e0f151a6cbc8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 18 Aug 2020 12:35:23 +0900 Subject: [PATCH 0551/1782] Fix PlayerLoader test failures due to too many steps --- osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs index e698d31176..4fac7bb45f 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs @@ -71,7 +71,6 @@ namespace osu.Game.Tests.Visual.Gameplay { 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()); AddUntilStep("wait for not current", () => !loader.IsCurrentScreen()); AddAssert("player did not load", () => player == null); From 083bcde3cf260147304b110d4a0956285690e4ba Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 18 Aug 2020 13:01:35 +0900 Subject: [PATCH 0552/1782] Fix beatmap transfer not working --- osu.Game/Beatmaps/WorkingBeatmap.cs | 7 +++++++ osu.Game/Overlays/MusicController.cs | 13 ++++++++++--- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs index b4046a4e95..c9a60d7948 100644 --- a/osu.Game/Beatmaps/WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/WorkingBeatmap.cs @@ -259,6 +259,13 @@ namespace osu.Game.Beatmaps [NotNull] public Track LoadTrack() => loadedTrack = GetBeatmapTrack() ?? GetVirtualTrack(1000); + /// + /// Transfer a valid audio track into this working beatmap. Used as an optimisation to avoid reload / track swap + /// across difficulties in the same beatmap set. + /// + /// The track to transfer. + public void TransferTrack([NotNull] Track track) => loadedTrack = track ?? throw new ArgumentNullException(nameof(track)); + /// /// Get the loaded audio track instance. must have first been called. /// This generally happens via MusicController when changing the global beatmap. diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index 8bbae33811..c1116ff651 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -282,10 +282,10 @@ namespace osu.Game.Overlays { TrackChangeDirection direction = TrackChangeDirection.None; + bool audioEquals = beatmap.NewValue?.BeatmapInfo?.AudioEquals(current?.BeatmapInfo) ?? false; + if (current != null) { - bool audioEquals = beatmap.NewValue?.BeatmapInfo?.AudioEquals(current.BeatmapInfo) ?? false; - if (audioEquals) direction = TrackChangeDirection.None; else if (queuedDirection.HasValue) @@ -305,8 +305,15 @@ namespace osu.Game.Overlays current = beatmap.NewValue; - if (CurrentTrack.IsDummyDevice || !beatmap.OldValue.BeatmapInfo.AudioEquals(current?.BeatmapInfo)) + if (!audioEquals || CurrentTrack.IsDummyDevice) + { changeTrack(); + } + else + { + // transfer still valid track to new working beatmap + current.TransferTrack(beatmap.OldValue.Track); + } TrackChanged?.Invoke(current, direction); From 848f3bbf51b0b44d48f7424d4a48fc0bc3e369a7 Mon Sep 17 00:00:00 2001 From: Joehu Date: Mon, 17 Aug 2020 21:09:55 -0700 Subject: [PATCH 0553/1782] Show tooltip of leaderboard score rank when 1000 or higher --- .../Online/Leaderboards/LeaderboardScore.cs | 30 ++++++++++++------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs index b60d71cfe7..662c02df0e 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs @@ -82,20 +82,10 @@ namespace osu.Game.Online.Leaderboards Children = new Drawable[] { - new Container + new RankLabel(rank) { RelativeSizeAxes = Axes.Y, Width = rank_width, - Children = new[] - { - new OsuSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Font = OsuFont.GetFont(size: 20, italics: true), - Text = rank == null ? "-" : rank.Value.ToMetric(decimals: rank < 100000 ? 1 : 0), - }, - }, }, content = new Container { @@ -356,6 +346,24 @@ namespace osu.Game.Online.Leaderboards } } + private class RankLabel : Container, IHasTooltip + { + public RankLabel(int? rank) + { + TooltipText = rank == null || rank < 1000 ? null : $"{rank}"; + + Child = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Font = OsuFont.GetFont(size: 20, italics: true), + Text = rank == null ? "-" : rank.Value.ToMetric(decimals: rank < 100000 ? 1 : 0), + }; + } + + public string TooltipText { get; } + } + public class LeaderboardScoreStatistic { public IconUsage Icon; From e0383f61008db34bbcc3317e9ad12ff94f56ec7b Mon Sep 17 00:00:00 2001 From: Joehu Date: Mon, 17 Aug 2020 22:07:04 -0700 Subject: [PATCH 0554/1782] Change format of rank tooltip --- 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 662c02df0e..a4c20d1b9e 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs @@ -350,7 +350,7 @@ namespace osu.Game.Online.Leaderboards { public RankLabel(int? rank) { - TooltipText = rank == null || rank < 1000 ? null : $"{rank}"; + TooltipText = rank == null || rank < 1000 ? null : $"#{rank:N0}"; Child = new OsuSpriteText { From 4d6b52a0d6a9a9d8e3d847c8d610d978b80d7802 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Mon, 17 Aug 2020 23:08:51 -0700 Subject: [PATCH 0555/1782] Simply condition Co-authored-by: Dean Herbert --- osu.Game/Online/Leaderboards/LeaderboardScore.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs index a4c20d1b9e..87b283f6b5 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs @@ -350,7 +350,8 @@ namespace osu.Game.Online.Leaderboards { public RankLabel(int? rank) { - TooltipText = rank == null || rank < 1000 ? null : $"#{rank:N0}"; + if (rank >= 1000) + TooltipText = $"#{rank:N0}"; Child = new OsuSpriteText { From f4f642fbcf5b5109727ca17776cb91fd53b49630 Mon Sep 17 00:00:00 2001 From: Joehu Date: Mon, 17 Aug 2020 23:21:44 -0700 Subject: [PATCH 0556/1782] Add ability to skip cutscene with forward mouse button --- osu.Game/Input/Bindings/GlobalActionContainer.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs index 6ae420b162..45b07581ec 100644 --- a/osu.Game/Input/Bindings/GlobalActionContainer.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs @@ -53,6 +53,7 @@ namespace osu.Game.Input.Bindings public IEnumerable InGameKeyBindings => new[] { new KeyBinding(InputKey.Space, GlobalAction.SkipCutscene), + new KeyBinding(InputKey.ExtraMouseButton2, GlobalAction.SkipCutscene), new KeyBinding(InputKey.Tilde, GlobalAction.QuickRetry), new KeyBinding(new[] { InputKey.Control, InputKey.Tilde }, GlobalAction.QuickExit), new KeyBinding(new[] { InputKey.Control, InputKey.Plus }, GlobalAction.IncreaseScrollSpeed), From e1ed8554a1805dcbe055518962a0cfd3fb7674a5 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 18 Aug 2020 17:23:11 +0900 Subject: [PATCH 0557/1782] Use yinyang icon --- osu.Game.Rulesets.Mania/Mods/ManiaModInvert.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModInvert.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModInvert.cs index 2fb7a75141..69f883cd3c 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModInvert.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModInvert.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; +using osu.Framework.Graphics.Sprites; using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Rulesets.Mania.Beatmaps; @@ -20,6 +21,8 @@ namespace osu.Game.Rulesets.Mania.Mods public override string Description => "Hold the keys. To the beat."; + public override IconUsage? Icon => FontAwesome.Solid.YinYang; + public override ModType Type => ModType.Conversion; public void ApplyToBeatmap(IBeatmap beatmap) From 628be66653e94ea692a717096a8b234f7f4b0a87 Mon Sep 17 00:00:00 2001 From: Jihoon Yang Date: Tue, 18 Aug 2020 01:24:56 -0700 Subject: [PATCH 0558/1782] Updated calculation of mania scroll speed --- .../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 7e84f17809..df453cf562 100644 --- a/osu.Game.Rulesets.Mania/Configuration/ManiaRulesetConfigManager.cs +++ b/osu.Game.Rulesets.Mania/Configuration/ManiaRulesetConfigManager.cs @@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Mania.Configuration public override TrackedSettings CreateTrackedSettings() => new TrackedSettings { new TrackedSetting(ManiaRulesetSetting.ScrollTime, - v => new SettingDescription(v, "Scroll Speed", $"{(int)Math.Round(DrawableManiaRuleset.MAX_TIME_RANGE / v)} ({v}ms)")) + v => new SettingDescription(v, "Scroll Speed", $"{(int)Math.Round(13720.0 / v)} ({v}ms)")) }; } From d157c42340224d340fe632f3da416f7c2bf60a61 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 18 Aug 2020 17:40:44 +0900 Subject: [PATCH 0559/1782] Increase density by not skipping objects --- osu.Game.Rulesets.Mania/Mods/ManiaModInvert.cs | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModInvert.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModInvert.cs index 69f883cd3c..56f6e389bf 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModInvert.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModInvert.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.Graphics.Sprites; @@ -43,13 +44,22 @@ namespace osu.Game.Rulesets.Mania.Mods })) .OrderBy(h => h.startTime).ToList(); - for (int i = 0; i < locations.Count - 1; i += 2) + for (int i = 0; i < locations.Count - 1; i++) { + // Full duration of the hold note. + double duration = locations[i + 1].startTime - locations[i].startTime; + + // Beat length at the end of the hold note. + double beatLength = beatmap.ControlPointInfo.TimingPointAt(locations[i + 1].startTime).BeatLength; + + // Decrease the duration by at most a 1/4 beat to ensure there's no instantaneous notes. + duration = Math.Max(duration / 2, duration - beatLength / 4); + newColumnObjects.Add(new HoldNote { Column = column.Key, StartTime = locations[i].startTime, - Duration = locations[i + 1].startTime - locations[i].startTime, + Duration = duration, Samples = locations[i].samples, NodeSamples = new List> { From 4ddc04793f5a8c61c9467c25f77fbbf860e34262 Mon Sep 17 00:00:00 2001 From: Jihoon Yang Date: Tue, 18 Aug 2020 01:44:30 -0700 Subject: [PATCH 0560/1782] Changed MAX_TIME_RANGE instead of the single instance --- .../Configuration/ManiaRulesetConfigManager.cs | 2 +- osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Configuration/ManiaRulesetConfigManager.cs b/osu.Game.Rulesets.Mania/Configuration/ManiaRulesetConfigManager.cs index df453cf562..7e84f17809 100644 --- a/osu.Game.Rulesets.Mania/Configuration/ManiaRulesetConfigManager.cs +++ b/osu.Game.Rulesets.Mania/Configuration/ManiaRulesetConfigManager.cs @@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Mania.Configuration public override TrackedSettings CreateTrackedSettings() => new TrackedSettings { new TrackedSetting(ManiaRulesetSetting.ScrollTime, - v => new SettingDescription(v, "Scroll Speed", $"{(int)Math.Round(13720.0 / v)} ({v}ms)")) + v => new SettingDescription(v, "Scroll Speed", $"{(int)Math.Round(DrawableManiaRuleset.MAX_TIME_RANGE / v)} ({v}ms)")) }; } diff --git a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs index 94b5ee9486..5b46550333 100644 --- a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs @@ -36,7 +36,7 @@ namespace osu.Game.Rulesets.Mania.UI /// /// The maximum time range. This occurs at a of 1. /// - public const double MAX_TIME_RANGE = 6000; + public const double MAX_TIME_RANGE = 13720; protected new ManiaPlayfield Playfield => (ManiaPlayfield)base.Playfield; From 138dc5929e9b40e40fdba96097e831eea386098b Mon Sep 17 00:00:00 2001 From: Jihoon Yang Date: Tue, 18 Aug 2020 01:46:07 -0700 Subject: [PATCH 0561/1782] Changed MIN_TIME_RANGE as well --- osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs index 5b46550333..7f5b9a6ee0 100644 --- a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs @@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Mania.UI /// /// The minimum time range. This occurs at a of 40. /// - public const double MIN_TIME_RANGE = 150; + public const double MIN_TIME_RANGE = 340; /// /// The maximum time range. This occurs at a of 1. From 385f7cf85d52c0d412f03b8706157ac686fea070 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 18 Aug 2020 17:56:48 +0900 Subject: [PATCH 0562/1782] Implement mania hold note body recycling --- .../Blueprints/Components/EditBodyPiece.cs | 4 +- .../Objects/Drawables/DrawableHoldNote.cs | 10 +- .../Drawables/Pieces/DefaultBodyPiece.cs | 157 +++++++++++------- .../Objects/Drawables/Pieces/IHoldNoteBody.cs | 16 ++ 4 files changed, 121 insertions(+), 66 deletions(-) create mode 100644 osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/IHoldNoteBody.cs diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/Components/EditBodyPiece.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/Components/EditBodyPiece.cs index efcfe11dad..5fa687298a 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/Components/EditBodyPiece.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/Components/EditBodyPiece.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Framework.Graphics; using osu.Game.Graphics; using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces; @@ -15,7 +16,8 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints.Components AccentColour.Value = colours.Yellow; Background.Alpha = 0.5f; - Foreground.Alpha = 0; } + + protected override Drawable CreateForeground() => base.CreateForeground().With(d => d.Alpha = 0); } } diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs index 0c5289efe1..a44f8a8886 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs @@ -32,7 +32,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables private readonly Container tailContainer; private readonly Container tickContainer; - private readonly Drawable bodyPiece; + private readonly SkinnableDrawable bodyPiece; /// /// Time at which the user started holding this hold note. Null if the user is not holding this hold note. @@ -49,7 +49,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables { RelativeSizeAxes = Axes.X; - AddRangeInternal(new[] + AddRangeInternal(new Drawable[] { bodyPiece = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.HoldNoteBody, hitObject.Column), _ => new DefaultBodyPiece { @@ -135,6 +135,12 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables // Samples are played by the head/tail notes. } + public override void OnKilled() + { + base.OnKilled(); + (bodyPiece.Drawable as IHoldNoteBody)?.Recycle(); + } + protected override void Update() { base.Update(); diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/DefaultBodyPiece.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/DefaultBodyPiece.cs index bc4a095395..9999983af5 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/DefaultBodyPiece.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/DefaultBodyPiece.cs @@ -19,24 +19,17 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces /// /// Represents length-wise portion of a hold note. /// - public class DefaultBodyPiece : CompositeDrawable + public class DefaultBodyPiece : CompositeDrawable, IHoldNoteBody { protected readonly Bindable AccentColour = new Bindable(); - - private readonly LayoutValue subtractionCache = new LayoutValue(Invalidation.DrawSize); - private readonly IBindable isHitting = new Bindable(); + protected readonly IBindable IsHitting = new Bindable(); protected Drawable Background { get; private set; } - protected BufferedContainer Foreground { get; private set; } - - private BufferedContainer subtractionContainer; - private Container subtractionLayer; + private Container foregroundContainer; public DefaultBodyPiece() { Blending = BlendingParameters.Additive; - - AddLayout(subtractionCache); } [BackgroundDependencyLoader(true)] @@ -45,7 +38,54 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces InternalChildren = new[] { Background = new Box { RelativeSizeAxes = Axes.Both }, - Foreground = new BufferedContainer + foregroundContainer = new Container { RelativeSizeAxes = Axes.Both } + }; + + if (drawableObject != null) + { + var holdNote = (DrawableHoldNote)drawableObject; + + AccentColour.BindTo(drawableObject.AccentColour); + IsHitting.BindTo(holdNote.IsHitting); + } + + AccentColour.BindValueChanged(onAccentChanged, true); + + Recycle(); + } + + public void Recycle() => foregroundContainer.Child = CreateForeground(); + + protected virtual Drawable CreateForeground() => new ForegroundPiece + { + AccentColour = { BindTarget = AccentColour }, + IsHitting = { BindTarget = IsHitting } + }; + + private void onAccentChanged(ValueChangedEvent accent) => Background.Colour = accent.NewValue.Opacity(0.7f); + + private class ForegroundPiece : CompositeDrawable + { + public readonly Bindable AccentColour = new Bindable(); + public readonly IBindable IsHitting = new Bindable(); + + private readonly LayoutValue subtractionCache = new LayoutValue(Invalidation.DrawSize); + + private BufferedContainer foregroundBuffer; + private BufferedContainer subtractionBuffer; + private Container subtractionLayer; + + public ForegroundPiece() + { + RelativeSizeAxes = Axes.Both; + + AddLayout(subtractionCache); + } + + [BackgroundDependencyLoader] + private void load() + { + InternalChild = foregroundBuffer = new BufferedContainer { Blending = BlendingParameters.Additive, RelativeSizeAxes = Axes.Both, @@ -53,7 +93,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces Children = new Drawable[] { new Box { RelativeSizeAxes = Axes.Both }, - subtractionContainer = new BufferedContainer + subtractionBuffer = new BufferedContainer { RelativeSizeAxes = Axes.Both, // This is needed because we're blending with another object @@ -77,60 +117,51 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces } } } - } - }; - - if (drawableObject != null) - { - var holdNote = (DrawableHoldNote)drawableObject; - - AccentColour.BindTo(drawableObject.AccentColour); - isHitting.BindTo(holdNote.IsHitting); - } - - AccentColour.BindValueChanged(onAccentChanged, true); - isHitting.BindValueChanged(_ => onAccentChanged(new ValueChangedEvent(AccentColour.Value, AccentColour.Value)), true); - } - - private void onAccentChanged(ValueChangedEvent accent) - { - Foreground.Colour = accent.NewValue.Opacity(0.5f); - Background.Colour = accent.NewValue.Opacity(0.7f); - - const float animation_length = 50; - - Foreground.ClearTransforms(false, nameof(Foreground.Colour)); - - if (isHitting.Value) - { - // wait for the next sync point - double synchronisedOffset = animation_length * 2 - Time.Current % (animation_length * 2); - using (Foreground.BeginDelayedSequence(synchronisedOffset)) - Foreground.FadeColour(accent.NewValue.Lighten(0.2f), animation_length).Then().FadeColour(Foreground.Colour, animation_length).Loop(); - } - - subtractionCache.Invalidate(); - } - - protected override void Update() - { - base.Update(); - - if (!subtractionCache.IsValid) - { - subtractionLayer.Width = 5; - subtractionLayer.Height = Math.Max(0, DrawHeight - DrawWidth); - subtractionLayer.EdgeEffect = new EdgeEffectParameters - { - Colour = Color4.White, - Type = EdgeEffectType.Glow, - Radius = DrawWidth }; - Foreground.ForceRedraw(); - subtractionContainer.ForceRedraw(); + AccentColour.BindValueChanged(onAccentChanged, true); + IsHitting.BindValueChanged(_ => onAccentChanged(new ValueChangedEvent(AccentColour.Value, AccentColour.Value)), true); + } - subtractionCache.Validate(); + private void onAccentChanged(ValueChangedEvent accent) + { + foregroundBuffer.Colour = accent.NewValue.Opacity(0.5f); + + const float animation_length = 50; + + foregroundBuffer.ClearTransforms(false, nameof(foregroundBuffer.Colour)); + + if (IsHitting.Value) + { + // wait for the next sync point + double synchronisedOffset = animation_length * 2 - Time.Current % (animation_length * 2); + using (foregroundBuffer.BeginDelayedSequence(synchronisedOffset)) + foregroundBuffer.FadeColour(accent.NewValue.Lighten(0.2f), animation_length).Then().FadeColour(foregroundBuffer.Colour, animation_length).Loop(); + } + + subtractionCache.Invalidate(); + } + + protected override void Update() + { + base.Update(); + + if (!subtractionCache.IsValid) + { + subtractionLayer.Width = 5; + subtractionLayer.Height = Math.Max(0, DrawHeight - DrawWidth); + subtractionLayer.EdgeEffect = new EdgeEffectParameters + { + Colour = Color4.White, + Type = EdgeEffectType.Glow, + Radius = DrawWidth + }; + + foregroundBuffer.ForceRedraw(); + subtractionBuffer.ForceRedraw(); + + subtractionCache.Validate(); + } } } } diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/IHoldNoteBody.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/IHoldNoteBody.cs new file mode 100644 index 0000000000..ac3792c01d --- /dev/null +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/IHoldNoteBody.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.Mania.Objects.Drawables.Pieces +{ + /// + /// Interface for mania hold note bodies. + /// + public interface IHoldNoteBody + { + /// + /// Recycles the contents of this to free used resources. + /// + void Recycle(); + } +} From da07354f050d15f94a6291d9296cd1286885143c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 18 Aug 2020 19:51:16 +0900 Subject: [PATCH 0563/1782] Fix some judgements potentially giving wrong score --- osu.Game.Rulesets.Mania/Judgements/HoldNoteTickJudgement.cs | 2 +- osu.Game.Rulesets.Osu/Objects/SpinnerBonusTick.cs | 2 +- osu.Game.Rulesets.Osu/Objects/SpinnerTick.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Judgements/HoldNoteTickJudgement.cs b/osu.Game.Rulesets.Mania/Judgements/HoldNoteTickJudgement.cs index 294aab1e4e..28e5d2cc1b 100644 --- a/osu.Game.Rulesets.Mania/Judgements/HoldNoteTickJudgement.cs +++ b/osu.Game.Rulesets.Mania/Judgements/HoldNoteTickJudgement.cs @@ -7,7 +7,7 @@ namespace osu.Game.Rulesets.Mania.Judgements { public class HoldNoteTickJudgement : ManiaJudgement { - protected override int NumericResultFor(HitResult result) => 20; + protected override int NumericResultFor(HitResult result) => result == MaxResult ? 20 : 0; protected override double HealthIncreaseFor(HitResult result) { diff --git a/osu.Game.Rulesets.Osu/Objects/SpinnerBonusTick.cs b/osu.Game.Rulesets.Osu/Objects/SpinnerBonusTick.cs index 9c4b6f774f..0b1232b8db 100644 --- a/osu.Game.Rulesets.Osu/Objects/SpinnerBonusTick.cs +++ b/osu.Game.Rulesets.Osu/Objects/SpinnerBonusTick.cs @@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Osu.Objects public class OsuSpinnerBonusTickJudgement : OsuSpinnerTickJudgement { - protected override int NumericResultFor(HitResult result) => SCORE_PER_TICK; + protected override int NumericResultFor(HitResult result) => result == MaxResult ? SCORE_PER_TICK : 0; protected override double HealthIncreaseFor(HitResult result) => base.HealthIncreaseFor(result) * 2; } diff --git a/osu.Game.Rulesets.Osu/Objects/SpinnerTick.cs b/osu.Game.Rulesets.Osu/Objects/SpinnerTick.cs index de3ae27e55..f54e7a9a15 100644 --- a/osu.Game.Rulesets.Osu/Objects/SpinnerTick.cs +++ b/osu.Game.Rulesets.Osu/Objects/SpinnerTick.cs @@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Osu.Objects { public override bool AffectsCombo => false; - protected override int NumericResultFor(HitResult result) => SCORE_PER_TICK; + protected override int NumericResultFor(HitResult result) => result == MaxResult ? SCORE_PER_TICK : 0; protected override double HealthIncreaseFor(HitResult result) => result == MaxResult ? 0.6 * base.HealthIncreaseFor(result) : 0; } From a4ad0bd1744a14207e0f39b5c363e24ace00005c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 18 Aug 2020 19:51:26 +0900 Subject: [PATCH 0564/1782] Ensure 0 score from miss judgements, add test --- .../Gameplay/TestSceneScoreProcessor.cs | 41 +++++++++++++++++++ osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 12 ++++-- 2 files changed, 49 insertions(+), 4 deletions(-) create mode 100644 osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs diff --git a/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs b/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs new file mode 100644 index 0000000000..b0baf0385e --- /dev/null +++ b/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.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 NUnit.Framework; +using osu.Framework.Testing; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Scoring; +using osu.Game.Tests.Visual; + +namespace osu.Game.Tests.Gameplay +{ + [HeadlessTest] + public class TestSceneScoreProcessor : OsuTestScene + { + [Test] + public void TestNoScoreIncreaseFromMiss() + { + var beatmap = new Beatmap { HitObjects = { new TestHitObject() } }; + + var scoreProcessor = new ScoreProcessor(); + scoreProcessor.ApplyBeatmap(beatmap); + + // Apply a miss judgement + scoreProcessor.ApplyResult(new JudgementResult(new TestHitObject(), new TestJudgement()) { Type = HitResult.Miss }); + + Assert.That(scoreProcessor.TotalScore.Value, Is.EqualTo(0.0)); + } + + private class TestHitObject : HitObject + { + public override Judgement CreateJudgement() => new TestJudgement(); + } + + private class TestJudgement : Judgement + { + protected override int NumericResultFor(HitResult result) => 100; + } + } +} diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index f1cdfd93c8..eac47aa089 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -133,17 +133,19 @@ namespace osu.Game.Rulesets.Scoring } } + double scoreIncrease = result.Type == HitResult.Miss ? 0 : result.Judgement.NumericResultFor(result); + if (result.Judgement.IsBonus) { if (result.IsHit) - bonusScore += result.Judgement.NumericResultFor(result); + bonusScore += scoreIncrease; } else { if (result.HasResult) scoreResultCounts[result.Type] = scoreResultCounts.GetOrDefault(result.Type) + 1; - baseScore += result.Judgement.NumericResultFor(result); + baseScore += scoreIncrease; rollingMaxBaseScore += result.Judgement.MaxNumericResult; } @@ -169,17 +171,19 @@ namespace osu.Game.Rulesets.Scoring if (result.FailedAtJudgement) return; + double scoreIncrease = result.Type == HitResult.Miss ? 0 : result.Judgement.NumericResultFor(result); + if (result.Judgement.IsBonus) { if (result.IsHit) - bonusScore -= result.Judgement.NumericResultFor(result); + bonusScore -= scoreIncrease; } else { if (result.HasResult) scoreResultCounts[result.Type] = scoreResultCounts.GetOrDefault(result.Type) - 1; - baseScore -= result.Judgement.NumericResultFor(result); + baseScore -= scoreIncrease; rollingMaxBaseScore -= result.Judgement.MaxNumericResult; } From 6aa31dffdb2b194eb0c593c8094e3d37f96f614e Mon Sep 17 00:00:00 2001 From: Lucas A Date: Tue, 18 Aug 2020 15:25:51 +0200 Subject: [PATCH 0565/1782] Fix toolbar not respecting current overlay activation mode. --- .../Visual/Menus/TestSceneToolbar.cs | 27 +++++++++++++++++-- osu.Game/Overlays/Toolbar/Toolbar.cs | 21 +++++++++------ 2 files changed, 38 insertions(+), 10 deletions(-) diff --git a/osu.Game.Tests/Visual/Menus/TestSceneToolbar.cs b/osu.Game.Tests/Visual/Menus/TestSceneToolbar.cs index b4985cad9f..5170058700 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneToolbar.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneToolbar.cs @@ -4,8 +4,10 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics.Containers; using osu.Framework.Testing; +using osu.Game.Overlays; using osu.Game.Overlays.Toolbar; using osu.Game.Rulesets; using osuTK.Input; @@ -15,7 +17,7 @@ namespace osu.Game.Tests.Visual.Menus [TestFixture] public class TestSceneToolbar : OsuManualInputManagerTestScene { - private Toolbar toolbar; + private TestToolbar toolbar; [Resolved] private RulesetStore rulesets { get; set; } @@ -23,7 +25,7 @@ namespace osu.Game.Tests.Visual.Menus [SetUp] public void SetUp() => Schedule(() => { - Child = toolbar = new Toolbar { State = { Value = Visibility.Visible } }; + Child = toolbar = new TestToolbar { State = { Value = Visibility.Visible } }; }); [Test] @@ -72,5 +74,26 @@ namespace osu.Game.Tests.Visual.Menus AddUntilStep("ruleset switched", () => rulesetSelector.Current.Value.Equals(expected)); } } + + [TestCase(OverlayActivation.All)] + [TestCase(OverlayActivation.Disabled)] + public void TestRespectsOverlayActivation(OverlayActivation mode) + { + AddStep($"set activation mode to {mode}", () => toolbar.OverlayActivationMode.Value = mode); + AddStep("hide toolbar", () => toolbar.Hide()); + AddStep("try to show toolbar", () => toolbar.Show()); + + if (mode == OverlayActivation.Disabled) + AddUntilStep("toolbar still hidden", () => toolbar.Visibility == Visibility.Hidden); + else + AddAssert("toolbar is visible", () => toolbar.Visibility == Visibility.Visible); + } + + public class TestToolbar : Toolbar + { + public new Bindable OverlayActivationMode => base.OverlayActivationMode; + + public Visibility Visibility => State.Value; + } } } diff --git a/osu.Game/Overlays/Toolbar/Toolbar.cs b/osu.Game/Overlays/Toolbar/Toolbar.cs index beac6adc59..3bf9e85428 100644 --- a/osu.Game/Overlays/Toolbar/Toolbar.cs +++ b/osu.Game/Overlays/Toolbar/Toolbar.cs @@ -28,7 +28,7 @@ namespace osu.Game.Overlays.Toolbar private const double transition_time = 500; - private readonly Bindable overlayActivationMode = new Bindable(OverlayActivation.All); + protected readonly Bindable OverlayActivationMode = new Bindable(OverlayActivation.All); // Toolbar components like RulesetSelector should receive keyboard input events even when the toolbar is hidden. public override bool PropagateNonPositionalInputSubTree => true; @@ -89,14 +89,8 @@ namespace osu.Game.Overlays.Toolbar // Bound after the selector is added to the hierarchy to give it a chance to load the available rulesets rulesetSelector.Current.BindTo(parentRuleset); - State.ValueChanged += visibility => - { - if (overlayActivationMode.Value == OverlayActivation.Disabled) - Hide(); - }; - if (osuGame != null) - overlayActivationMode.BindTo(osuGame.OverlayActivationMode); + OverlayActivationMode.BindTo(osuGame.OverlayActivationMode); } public class ToolbarBackground : Container @@ -137,6 +131,17 @@ namespace osu.Game.Overlays.Toolbar } } + protected override void UpdateState(ValueChangedEvent state) + { + if (state.NewValue == Visibility.Visible && OverlayActivationMode.Value == OverlayActivation.Disabled) + { + State.Value = Visibility.Hidden; + return; + } + + base.UpdateState(state); + } + protected override void PopIn() { this.MoveToY(0, transition_time, Easing.OutQuint); From 988ad378a7c7ccfe0e20c11b334e47a1cc368082 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 19 Aug 2020 00:05:05 +0900 Subject: [PATCH 0566/1782] Fix body size + freeze head piece --- .../Objects/Drawables/DrawableHoldNote.cs | 48 ++++++++++++------- .../Objects/Drawables/DrawableHoldNoteHead.cs | 8 ++++ 2 files changed, 40 insertions(+), 16 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs index 39b1771643..008cc3519e 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs @@ -34,8 +34,15 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables private readonly Container tailContainer; private readonly Container tickContainer; - private readonly Container bodyPieceContainer; - private readonly Drawable bodyPiece; + /// + /// Contains the maximum size/position of the body prior to any offset or size adjustments. + /// + private readonly Container bodyContainer; + + /// + /// Contains the offset size/position of the body such that the body extends half-way between the head and tail pieces. + /// + private readonly Container bodyOffsetContainer; /// /// Time at which the user started holding this hold note. Null if the user is not holding this hold note. @@ -57,18 +64,27 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables { RelativeSizeAxes = Axes.X; - AddRangeInternal(new Drawable[] + AddRangeInternal(new[] { - bodyPieceContainer = new Container + bodyContainer = new Container { - RelativeSizeAxes = Axes.X, - Child = bodyPiece = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.HoldNoteBody, hitObject.Column), _ => new DefaultBodyPiece + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] { - RelativeSizeAxes = Axes.Both - }) + bodyOffsetContainer = new Container + { + RelativeSizeAxes = Axes.X, + Child = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.HoldNoteBody, hitObject.Column), _ => new DefaultBodyPiece + { + RelativeSizeAxes = Axes.Both + }) + }, + // The head needs to move along with changes in the size of the body. + headContainer = new Container { RelativeSizeAxes = Axes.Both } + } }, tickContainer = new Container { RelativeSizeAxes = Axes.Both }, - headContainer = new Container { RelativeSizeAxes = Axes.Both }, + headContainer.CreateProxy(), tailContainer = new Container { RelativeSizeAxes = Axes.Both }, }); } @@ -136,8 +152,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables { base.OnDirectionChanged(e); - bodyPieceContainer.Anchor = bodyPieceContainer.Origin = e.NewValue == ScrollingDirection.Up ? Anchor.TopLeft : Anchor.BottomLeft; - bodyPieceContainer.Anchor = bodyPieceContainer.Origin = e.NewValue == ScrollingDirection.Up ? Anchor.BottomLeft : Anchor.TopLeft; + bodyOffsetContainer.Anchor = bodyOffsetContainer.Origin = e.NewValue == ScrollingDirection.Up ? Anchor.TopLeft : Anchor.BottomLeft; } public override void PlaySamples() @@ -149,15 +164,16 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables { base.Update(); - // Make the body piece not lie under the head note - bodyPieceContainer.Y = (Direction.Value == ScrollingDirection.Up ? 1 : -1) * Head.Height / 2; - bodyPieceContainer.Height = DrawHeight - Head.Height / 2 + Tail.Height / 2; - + // Decrease the size of the body while the hold note is held and the head has been hit. if (Head.IsHit && !hasReleased) { float heightDecrease = (float)(Math.Max(0, Time.Current - HitObject.StartTime) / HitObject.Duration); - bodyPiece.Height = MathHelper.Clamp(1 - heightDecrease, 0, 1); + bodyContainer.Height = MathHelper.Clamp(1 - heightDecrease, 0, 1); } + + // Offset the body to extend half-way under the head and tail. + bodyOffsetContainer.Y = (Direction.Value == ScrollingDirection.Up ? 1 : -1) * Head.Height / 2; + bodyOffsetContainer.Height = bodyContainer.DrawHeight - Head.Height / 2 + Tail.Height / 2; } protected override void UpdateStateTransforms(ArmedState state) diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteHead.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteHead.cs index a73fe259e4..cd56b81e10 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteHead.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteHead.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.Objects.Drawables; + namespace osu.Game.Rulesets.Mania.Objects.Drawables { /// @@ -17,6 +19,12 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables public void UpdateResult() => base.UpdateResult(true); + protected override void UpdateStateTransforms(ArmedState state) + { + // This hitobject should never expire, so this is just a safe maximum. + LifetimeEnd = LifetimeStart + 30000; + } + public override bool OnPressed(ManiaAction action) => false; // Handled by the hold note public override void OnReleased(ManiaAction action) From 99315a4aa74c434bb4938357d75b65543150ff59 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 19 Aug 2020 00:05:36 +0900 Subject: [PATCH 0567/1782] Fix incorrect anchors for up-scroll --- .../Objects/Drawables/DrawableHoldNote.cs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs index 008cc3519e..e120fab21b 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs @@ -152,7 +152,18 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables { base.OnDirectionChanged(e); - bodyOffsetContainer.Anchor = bodyOffsetContainer.Origin = e.NewValue == ScrollingDirection.Up ? Anchor.TopLeft : Anchor.BottomLeft; + // The body container is anchored from the position of the tail, since its height is changed when the hold note is being hit. + // The body offset container is anchored from the position of the head (inverse of the above). + if (e.NewValue == ScrollingDirection.Up) + { + bodyContainer.Anchor = bodyContainer.Origin = Anchor.BottomLeft; + bodyOffsetContainer.Anchor = bodyOffsetContainer.Origin = Anchor.TopLeft; + } + else + { + bodyContainer.Anchor = bodyContainer.Origin = Anchor.TopLeft; + bodyOffsetContainer.Anchor = bodyOffsetContainer.Origin = Anchor.BottomLeft; + } } public override void PlaySamples() From af8f727721cc227082ffc78d20b3b39dbf73fb3e Mon Sep 17 00:00:00 2001 From: Jihoon Yang Date: Tue, 18 Aug 2020 08:28:53 -0700 Subject: [PATCH 0568/1782] Disable LegacyHitExplosion for hold notes --- osu.Game.Rulesets.Mania/Skinning/LegacyHitExplosion.cs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyHitExplosion.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyHitExplosion.cs index 12747924de..e80d968f37 100644 --- a/osu.Game.Rulesets.Mania/Skinning/LegacyHitExplosion.cs +++ b/osu.Game.Rulesets.Mania/Skinning/LegacyHitExplosion.cs @@ -7,6 +7,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Animations; using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Mania.Judgements; using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Skinning; @@ -66,10 +67,13 @@ namespace osu.Game.Rulesets.Mania.Skinning public void Animate(JudgementResult result) { - (explosion as IFramedAnimation)?.GotoFrame(0); + if (!(result.Judgement is HoldNoteTickJudgement)) + { + (explosion as IFramedAnimation)?.GotoFrame(0); - explosion?.FadeInFromZero(80) - .Then().FadeOut(120); + explosion?.FadeInFromZero(80) + .Then().FadeOut(120); + } } } } From cd2280b5bf8947f29d379da16cb8826eff637fe2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 18 Aug 2020 15:18:52 +0200 Subject: [PATCH 0569/1782] Fix cheese indexing bug --- .../Difficulty/Preprocessing/StaminaCheeseDetector.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/StaminaCheeseDetector.cs b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/StaminaCheeseDetector.cs index b53bc66f39..29e631e515 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/StaminaCheeseDetector.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/StaminaCheeseDetector.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.Game.Rulesets.Taiko.Objects; @@ -58,7 +59,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing { for (int j = repetitionStart; j < i; j++) { - hitObjects[i].StaminaCheese = true; + hitObjects[j].StaminaCheese = true; } } } @@ -81,9 +82,9 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing if (tlLength >= tl_min_repetitions) { - for (int j = i - tlLength; j < i; j++) + for (int j = Math.Max(0, i - tlLength); j < i; j++) { - hitObjects[i].StaminaCheese = true; + hitObjects[j].StaminaCheese = true; } } } From 9fb494d5d3067bb39081222cb3f39d5b2dc9ba74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 18 Aug 2020 15:24:30 +0200 Subject: [PATCH 0570/1782] Eliminate unnecessary loop --- .../Difficulty/Skills/Colour.cs | 30 +++++++++---------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Colour.cs b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Colour.cs index db445c7d27..e93893d894 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Colour.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Colour.cs @@ -80,6 +80,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills /// private double repetitionPenalties() { + const int l = 2; double penalty = 1.0; monoHistory.Add(currentMonoLength); @@ -87,27 +88,24 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills if (monoHistory.Count > mono_history_max_length) monoHistory.RemoveAt(0); - for (int l = 2; l <= mono_history_max_length / 2; l++) + for (int start = monoHistory.Count - l - 1; start >= 0; start--) { - for (int start = monoHistory.Count - l - 1; start >= 0; start--) + bool samePattern = true; + + for (int i = 0; i < l; i++) { - bool samePattern = true; - - for (int i = 0; i < l; i++) + if (monoHistory[start + i] != monoHistory[monoHistory.Count - l + i]) { - if (monoHistory[start + i] != monoHistory[monoHistory.Count - l + i]) - { - samePattern = false; - } + samePattern = false; } + } - if (samePattern) // Repetition found! - { - int notesSince = 0; - for (int i = start; i < monoHistory.Count; i++) notesSince += monoHistory[i]; - penalty *= repetitionPenalty(notesSince); - break; - } + if (samePattern) // Repetition found! + { + int notesSince = 0; + for (int i = start; i < monoHistory.Count; i++) notesSince += monoHistory[i]; + penalty *= repetitionPenalty(notesSince); + break; } } From 474f2452226cf79b271813824c5bad9add33a177 Mon Sep 17 00:00:00 2001 From: Jihoon Yang Date: Tue, 18 Aug 2020 08:40:29 -0700 Subject: [PATCH 0571/1782] Replace nested loop with early return --- .../Skinning/LegacyHitExplosion.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyHitExplosion.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyHitExplosion.cs index e80d968f37..41f3090afd 100644 --- a/osu.Game.Rulesets.Mania/Skinning/LegacyHitExplosion.cs +++ b/osu.Game.Rulesets.Mania/Skinning/LegacyHitExplosion.cs @@ -67,13 +67,13 @@ namespace osu.Game.Rulesets.Mania.Skinning public void Animate(JudgementResult result) { - if (!(result.Judgement is HoldNoteTickJudgement)) - { - (explosion as IFramedAnimation)?.GotoFrame(0); + if (result.Judgement is HoldNoteTickJudgement) + return; - explosion?.FadeInFromZero(80) - .Then().FadeOut(120); - } + (explosion as IFramedAnimation)?.GotoFrame(0); + + explosion?.FadeInFromZero(80) + .Then().FadeOut(120); } } } From 4d4d9b7356e1963b6d397bee2951a4b67a5bea25 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 19 Aug 2020 01:37:24 +0900 Subject: [PATCH 0572/1782] Add rewinding support --- .../Objects/Drawables/DrawableHoldNote.cs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs index e120fab21b..0e1e700702 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs @@ -57,7 +57,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables /// /// Whether the hold note has been released potentially without having caused a break. /// - private bool hasReleased; + private double? releaseTime; public DrawableHoldNote(HoldNote hitObject) : base(hitObject) @@ -175,8 +175,11 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables { base.Update(); - // Decrease the size of the body while the hold note is held and the head has been hit. - if (Head.IsHit && !hasReleased) + if (Time.Current < releaseTime) + releaseTime = null; + + // Decrease the size of the body while the hold note is held and the head has been hit. This stops at the very first release point. + if (Head.IsHit && releaseTime == null) { float heightDecrease = (float)(Math.Max(0, Time.Current - HitObject.StartTime) / HitObject.Duration); bodyContainer.Height = MathHelper.Clamp(1 - heightDecrease, 0, 1); @@ -250,7 +253,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables if (!Tail.IsHit) HasBroken = true; - hasReleased = true; + releaseTime = Time.Current; } private void endHold() From 1d9d885d27fbd22f1118944a99fc2889d3617ca3 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 19 Aug 2020 01:40:26 +0900 Subject: [PATCH 0573/1782] Mask the tail as the body gets shorter --- .../Objects/Drawables/DrawableHoldNote.cs | 38 +++++++++++++++++-- 1 file changed, 34 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs index 0e1e700702..d2412df7c3 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs @@ -44,6 +44,11 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables /// private readonly Container bodyOffsetContainer; + /// + /// Contains the masking area for the tail, which is resized along with . + /// + private readonly Container tailMaskingContainer; + /// /// Time at which the user started holding this hold note. Null if the user is not holding this hold note. /// @@ -84,8 +89,16 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables } }, tickContainer = new Container { RelativeSizeAxes = Axes.Both }, - headContainer.CreateProxy(), - tailContainer = new Container { RelativeSizeAxes = Axes.Both }, + tailMaskingContainer = new Container + { + RelativeSizeAxes = Axes.X, + Masking = true, + Child = tailContainer = new Container + { + RelativeSizeAxes = Axes.X, + } + }, + headContainer.CreateProxy() }); } @@ -154,15 +167,22 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables // The body container is anchored from the position of the tail, since its height is changed when the hold note is being hit. // The body offset container is anchored from the position of the head (inverse of the above). + // The tail containers are both anchored from the position of the tail. if (e.NewValue == ScrollingDirection.Up) { bodyContainer.Anchor = bodyContainer.Origin = Anchor.BottomLeft; bodyOffsetContainer.Anchor = bodyOffsetContainer.Origin = Anchor.TopLeft; + + tailMaskingContainer.Anchor = tailMaskingContainer.Origin = Anchor.BottomLeft; + tailContainer.Anchor = tailContainer.Origin = Anchor.BottomLeft; } else { bodyContainer.Anchor = bodyContainer.Origin = Anchor.TopLeft; bodyOffsetContainer.Anchor = bodyOffsetContainer.Origin = Anchor.BottomLeft; + + tailMaskingContainer.Anchor = tailMaskingContainer.Origin = Anchor.TopLeft; + tailContainer.Anchor = tailContainer.Origin = Anchor.TopLeft; } } @@ -185,9 +205,19 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables bodyContainer.Height = MathHelper.Clamp(1 - heightDecrease, 0, 1); } - // Offset the body to extend half-way under the head and tail. + // Re-position the body half-way up the head, and extend the height until it's half-way under the tail. bodyOffsetContainer.Y = (Direction.Value == ScrollingDirection.Up ? 1 : -1) * Head.Height / 2; - bodyOffsetContainer.Height = bodyContainer.DrawHeight - Head.Height / 2 + Tail.Height / 2; + bodyOffsetContainer.Height = bodyContainer.DrawHeight + Tail.Height / 2 - Head.Height / 2; + + // The tail is positioned to be "outside" the hold note, so re-position its masking container to fully cover the tail and extend the height until it's half-way under the head. + // The masking height is determined by the size of the body so that the head and tail don't overlap as the body becomes shorter via hitting (above). + tailMaskingContainer.Y = (Direction.Value == ScrollingDirection.Up ? 1 : -1) * Tail.Height; + tailMaskingContainer.Height = bodyContainer.DrawHeight + Tail.Height - Head.Height / 2; + + // The tail container needs the reverse of the above offset applied to bring the tail to its original position. + // It also needs the full original height of the hold note to maintain positioning even as the height of the masking container changes. + tailContainer.Y = -tailMaskingContainer.Y; + tailContainer.Height = DrawHeight; } protected override void UpdateStateTransforms(ArmedState state) From 6c759f31f175f784f447f1ef0d21e20460429ae3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 18 Aug 2020 19:13:18 +0200 Subject: [PATCH 0574/1782] Add and use limited capacity queue --- .../Preprocessing/StaminaCheeseDetector.cs | 10 +- .../Difficulty/Skills/Colour.cs | 9 +- .../Difficulty/Skills/Rhythm.cs | 9 +- .../Difficulty/Skills/Stamina.cs | 9 +- .../NonVisual/LimitedCapacityQueueTest.cs | 98 +++++++++++++++ .../Difficulty/Utils/LimitedCapacityQueue.cs | 114 ++++++++++++++++++ 6 files changed, 226 insertions(+), 23 deletions(-) create mode 100644 osu.Game.Tests/NonVisual/LimitedCapacityQueueTest.cs create mode 100644 osu.Game/Rulesets/Difficulty/Utils/LimitedCapacityQueue.cs diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/StaminaCheeseDetector.cs b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/StaminaCheeseDetector.cs index 29e631e515..c6317ff195 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/StaminaCheeseDetector.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/StaminaCheeseDetector.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using osu.Game.Rulesets.Difficulty.Utils; using osu.Game.Rulesets.Taiko.Objects; namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing @@ -27,16 +28,15 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing private void findRolls(int patternLength) { - List history = new List(); + var history = new LimitedCapacityQueue(2 * patternLength); int repetitionStart = 0; for (int i = 0; i < hitObjects.Count; i++) { - history.Add(hitObjects[i]); - if (history.Count < 2 * patternLength) continue; - - if (history.Count > 2 * patternLength) history.RemoveAt(0); + history.Enqueue(hitObjects[i]); + if (!history.Full) + continue; bool isRepeat = true; diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Colour.cs b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Colour.cs index e93893d894..e9e0930a9a 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Colour.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Colour.cs @@ -2,9 +2,9 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Collections.Generic; using osu.Game.Rulesets.Difficulty.Preprocessing; using osu.Game.Rulesets.Difficulty.Skills; +using osu.Game.Rulesets.Difficulty.Utils; using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing; using osu.Game.Rulesets.Taiko.Objects; @@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills /// /// List of the last most recent mono patterns, with the most recent at the end of the list. /// - private readonly List monoHistory = new List(); + private readonly LimitedCapacityQueue monoHistory = new LimitedCapacityQueue(mono_history_max_length); protected override double StrainValueOf(DifficultyHitObject current) { @@ -83,10 +83,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills const int l = 2; double penalty = 1.0; - monoHistory.Add(currentMonoLength); - - if (monoHistory.Count > mono_history_max_length) - monoHistory.RemoveAt(0); + monoHistory.Enqueue(currentMonoLength); for (int start = monoHistory.Count - l - 1; start >= 0; start--) { diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Rhythm.cs b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Rhythm.cs index 31dc93a6b2..caf1acccf4 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Rhythm.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Rhythm.cs @@ -2,9 +2,9 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Collections.Generic; using osu.Game.Rulesets.Difficulty.Preprocessing; using osu.Game.Rulesets.Difficulty.Skills; +using osu.Game.Rulesets.Difficulty.Utils; using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing; using osu.Game.Rulesets.Taiko.Objects; @@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills private const double strain_decay = 0.96; private double currentStrain; - private readonly List rhythmHistory = new List(); + private readonly LimitedCapacityQueue rhythmHistory = new LimitedCapacityQueue(rhythm_history_max_length); private const int rhythm_history_max_length = 8; private int notesSinceRhythmChange; @@ -32,10 +32,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills { double penalty = 1; - rhythmHistory.Add(hitobject); - - if (rhythmHistory.Count > rhythm_history_max_length) - rhythmHistory.RemoveAt(0); + rhythmHistory.Enqueue(hitobject); for (int l = 2; l <= rhythm_history_max_length / 2; l++) { diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Stamina.cs b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Stamina.cs index c9a691a2aa..430a553113 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Stamina.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Stamina.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 System.Linq; using osu.Game.Rulesets.Difficulty.Preprocessing; using osu.Game.Rulesets.Difficulty.Skills; +using osu.Game.Rulesets.Difficulty.Utils; using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing; using osu.Game.Rulesets.Taiko.Objects; @@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills protected override double StrainDecayBase => 0.4; private const int max_history_length = 2; - private readonly List notePairDurationHistory = new List(); + private readonly LimitedCapacityQueue notePairDurationHistory = new LimitedCapacityQueue(max_history_length); private double offhandObjectDuration = double.MaxValue; @@ -56,10 +56,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills if (hitObject.ObjectIndex == 1) return 1; - notePairDurationHistory.Add(hitObject.DeltaTime + offhandObjectDuration); - - if (notePairDurationHistory.Count > max_history_length) - notePairDurationHistory.RemoveAt(0); + notePairDurationHistory.Enqueue(hitObject.DeltaTime + offhandObjectDuration); double shortestRecentNote = notePairDurationHistory.Min(); objectStrain += speedBonus(shortestRecentNote); diff --git a/osu.Game.Tests/NonVisual/LimitedCapacityQueueTest.cs b/osu.Game.Tests/NonVisual/LimitedCapacityQueueTest.cs new file mode 100644 index 0000000000..52463dd7eb --- /dev/null +++ b/osu.Game.Tests/NonVisual/LimitedCapacityQueueTest.cs @@ -0,0 +1,98 @@ +// Copyright (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.Rulesets.Difficulty.Utils; + +namespace osu.Game.Tests.NonVisual +{ + [TestFixture] + public class LimitedCapacityQueueTest + { + private const int capacity = 3; + + private LimitedCapacityQueue queue; + + [SetUp] + public void SetUp() + { + queue = new LimitedCapacityQueue(capacity); + } + + [Test] + public void TestEmptyQueue() + { + Assert.AreEqual(0, queue.Count); + + Assert.Throws(() => _ = queue[0]); + + Assert.Throws(() => _ = queue.Dequeue()); + + int count = 0; + foreach (var _ in queue) + count++; + + Assert.AreEqual(0, count); + } + + [TestCase(1)] + [TestCase(2)] + [TestCase(3)] + public void TestBelowCapacity(int count) + { + for (int i = 0; i < count; ++i) + queue.Enqueue(i); + + Assert.AreEqual(count, queue.Count); + + for (int i = 0; i < count; ++i) + Assert.AreEqual(i, queue[i]); + + int j = 0; + foreach (var item in queue) + Assert.AreEqual(j++, item); + + for (int i = queue.Count; i < queue.Count + capacity; i++) + Assert.Throws(() => _ = queue[i]); + } + + [TestCase(4)] + [TestCase(5)] + [TestCase(6)] + public void TestEnqueueAtFullCapacity(int count) + { + for (int i = 0; i < count; ++i) + queue.Enqueue(i); + + Assert.AreEqual(capacity, queue.Count); + + for (int i = 0; i < queue.Count; ++i) + Assert.AreEqual(count - capacity + i, queue[i]); + + int j = count - capacity; + foreach (var item in queue) + Assert.AreEqual(j++, item); + + for (int i = queue.Count; i < queue.Count + capacity; i++) + Assert.Throws(() => _ = queue[i]); + } + + [TestCase(4)] + [TestCase(5)] + [TestCase(6)] + public void TestDequeueAtFullCapacity(int count) + { + for (int i = 0; i < count; ++i) + queue.Enqueue(i); + + for (int i = 0; i < capacity; ++i) + { + Assert.AreEqual(count - capacity + i, queue.Dequeue()); + Assert.AreEqual(2 - i, queue.Count); + } + + Assert.Throws(() => queue.Dequeue()); + } + } +} diff --git a/osu.Game/Rulesets/Difficulty/Utils/LimitedCapacityQueue.cs b/osu.Game/Rulesets/Difficulty/Utils/LimitedCapacityQueue.cs new file mode 100644 index 0000000000..0f014e8a8c --- /dev/null +++ b/osu.Game/Rulesets/Difficulty/Utils/LimitedCapacityQueue.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 System; +using System.Collections; +using System.Collections.Generic; + +namespace osu.Game.Rulesets.Difficulty.Utils +{ + /// + /// An indexed queue with limited capacity. + /// Respects first-in-first-out insertion order. + /// + public class LimitedCapacityQueue : IEnumerable + { + /// + /// The number of elements in the queue. + /// + public int Count { get; private set; } + + /// + /// Whether the queue is full (adding any new items will cause removing existing ones). + /// + public bool Full => Count == capacity; + + private readonly T[] array; + private readonly int capacity; + + // Markers tracking the queue's first and last element. + private int start, end; + + /// + /// Constructs a new + /// + /// The number of items the queue can hold. + public LimitedCapacityQueue(int capacity) + { + if (capacity < 0) + throw new ArgumentOutOfRangeException(nameof(capacity)); + + this.capacity = capacity; + array = new T[capacity]; + start = 0; + end = -1; + } + + /// + /// Removes an item from the front of the . + /// + /// The item removed from the front of the queue. + public T Dequeue() + { + if (Count == 0) + throw new InvalidOperationException("Queue is empty."); + + var result = array[start]; + start = (start + 1) % capacity; + Count--; + return result; + } + + /// + /// Adds an item to the back of the . + /// If the queue is holding elements at the point of addition, + /// the item at the front of the queue will be removed. + /// + /// The item to be added to the back of the queue. + public void Enqueue(T item) + { + end = (end + 1) % capacity; + if (Count == capacity) + start = (start + 1) % capacity; + else + Count++; + array[end] = item; + } + + /// + /// Retrieves the item at the given index in the queue. + /// + /// + /// The index of the item to retrieve. + /// The item with index 0 is at the front of the queue + /// (it was added the earliest). + /// + public T this[int index] + { + get + { + if (index < 0 || index >= Count) + throw new ArgumentOutOfRangeException(nameof(index)); + + return array[(start + index) % capacity]; + } + } + + /// + /// Enumerates the queue from its start to its end. + /// + public IEnumerator GetEnumerator() + { + if (Count == 0) + yield break; + + for (int i = 0; i < Count; i++) + yield return array[(start + i) % capacity]; + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } +} From 292d38362c504196b7593042fc8dcba2104a20af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 18 Aug 2020 19:18:36 +0200 Subject: [PATCH 0575/1782] De-nest cheese detection logic --- .../Preprocessing/StaminaCheeseDetector.cs | 47 +++++++++---------- 1 file changed, 22 insertions(+), 25 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/StaminaCheeseDetector.cs b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/StaminaCheeseDetector.cs index c6317ff195..e1dad70d90 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/StaminaCheeseDetector.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/StaminaCheeseDetector.cs @@ -38,33 +38,34 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing if (!history.Full) continue; - bool isRepeat = true; - - for (int j = 0; j < patternLength; j++) - { - if (history[j].HitType != history[j + patternLength].HitType) - { - isRepeat = false; - } - } - - if (!isRepeat) + if (!containsPatternRepeat(history, patternLength)) { repetitionStart = i - 2 * patternLength; + continue; } int repeatedLength = i - repetitionStart; + if (repeatedLength < roll_min_repetitions) + continue; - if (repeatedLength >= roll_min_repetitions) + for (int j = repetitionStart; j < i; j++) { - for (int j = repetitionStart; j < i; j++) - { - hitObjects[j].StaminaCheese = true; - } + hitObjects[j].StaminaCheese = true; } } } + private static bool containsPatternRepeat(LimitedCapacityQueue history, int patternLength) + { + for (int j = 0; j < patternLength; j++) + { + if (history[j].HitType != history[j + patternLength].HitType) + return false; + } + + return true; + } + private void findTlTap(int parity, HitType type) { int tlLength = -2; @@ -72,20 +73,16 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing for (int i = parity; i < hitObjects.Count; i += 2) { if (hitObjects[i].HitType == type) - { tlLength += 2; - } else - { tlLength = -2; - } - if (tlLength >= tl_min_repetitions) + if (tlLength < tl_min_repetitions) + continue; + + for (int j = Math.Max(0, i - tlLength); j < i; j++) { - for (int j = Math.Max(0, i - tlLength); j < i; j++) - { - hitObjects[j].StaminaCheese = true; - } + hitObjects[j].StaminaCheese = true; } } } From ff44437706bc5eb34582329db869c2031772954e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 18 Aug 2020 19:29:51 +0200 Subject: [PATCH 0576/1782] Extract method for marking cheese --- .../Preprocessing/StaminaCheeseDetector.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/StaminaCheeseDetector.cs b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/StaminaCheeseDetector.cs index e1dad70d90..3f952d8317 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/StaminaCheeseDetector.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/StaminaCheeseDetector.cs @@ -48,10 +48,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing if (repeatedLength < roll_min_repetitions) continue; - for (int j = repetitionStart; j < i; j++) - { - hitObjects[j].StaminaCheese = true; - } + markObjectsAsCheese(repetitionStart, i); } } @@ -80,11 +77,14 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing if (tlLength < tl_min_repetitions) continue; - for (int j = Math.Max(0, i - tlLength); j < i; j++) - { - hitObjects[j].StaminaCheese = true; - } + markObjectsAsCheese(Math.Max(0, i - tlLength), i); } } + + private void markObjectsAsCheese(int start, int end) + { + for (int i = start; i < end; ++i) + hitObjects[i].StaminaCheese = true; + } } } From f22050c9759648521ea2bd05f51b3932e718eb00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 18 Aug 2020 19:31:10 +0200 Subject: [PATCH 0577/1782] Remove unnecessary initialiser --- .../Difficulty/Preprocessing/TaikoDifficultyHitObject.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/TaikoDifficultyHitObject.cs b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/TaikoDifficultyHitObject.cs index 817e974fe8..81b304af13 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/TaikoDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/TaikoDifficultyHitObject.cs @@ -15,7 +15,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing public readonly TaikoDifficultyHitObjectRhythm Rhythm; public readonly HitType? HitType; - public bool StaminaCheese = false; + public bool StaminaCheese; public readonly int ObjectIndex; From c6a640db55aa386b8acc05fb788c08cfee76aafb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 18 Aug 2020 19:34:44 +0200 Subject: [PATCH 0578/1782] Remove superfluous IsRepeat field --- .../TaikoDifficultyHitObjectRhythm.cs | 4 +--- .../Difficulty/Skills/Rhythm.cs | 2 +- .../Difficulty/TaikoDifficultyCalculator.cs | 18 +++++++++--------- 3 files changed, 11 insertions(+), 13 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/TaikoDifficultyHitObjectRhythm.cs b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/TaikoDifficultyHitObjectRhythm.cs index 0ad885d9bd..9c22eff22a 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/TaikoDifficultyHitObjectRhythm.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/TaikoDifficultyHitObjectRhythm.cs @@ -7,13 +7,11 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing { public readonly double Difficulty; public readonly double Ratio; - public readonly bool IsRepeat; - public TaikoDifficultyHitObjectRhythm(int numerator, int denominator, double difficulty, bool isRepeat) + public TaikoDifficultyHitObjectRhythm(int numerator, int denominator, double difficulty) { Ratio = numerator / (double)denominator; Difficulty = difficulty; - IsRepeat = isRepeat; } } } diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Rhythm.cs b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Rhythm.cs index caf1acccf4..483e94cd70 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Rhythm.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Rhythm.cs @@ -92,7 +92,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills TaikoDifficultyHitObject hitobject = (TaikoDifficultyHitObject)current; notesSinceRhythmChange += 1; - if (hitobject.Rhythm.IsRepeat) + if (hitobject.Rhythm.Difficulty == 0.0) { return 0.0; } diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs index 789fd7c63b..7a9f1765ae 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs @@ -26,15 +26,15 @@ namespace osu.Game.Rulesets.Taiko.Difficulty private readonly TaikoDifficultyHitObjectRhythm[] commonRhythms = { - new TaikoDifficultyHitObjectRhythm(1, 1, 0.0, true), - new TaikoDifficultyHitObjectRhythm(2, 1, 0.3, false), - new TaikoDifficultyHitObjectRhythm(1, 2, 0.5, false), - new TaikoDifficultyHitObjectRhythm(3, 1, 0.3, false), - new TaikoDifficultyHitObjectRhythm(1, 3, 0.35, false), - new TaikoDifficultyHitObjectRhythm(3, 2, 0.6, false), - new TaikoDifficultyHitObjectRhythm(2, 3, 0.4, false), - new TaikoDifficultyHitObjectRhythm(5, 4, 0.5, false), - new TaikoDifficultyHitObjectRhythm(4, 5, 0.7, false) + new TaikoDifficultyHitObjectRhythm(1, 1, 0.0), + new TaikoDifficultyHitObjectRhythm(2, 1, 0.3), + new TaikoDifficultyHitObjectRhythm(1, 2, 0.5), + new TaikoDifficultyHitObjectRhythm(3, 1, 0.3), + new TaikoDifficultyHitObjectRhythm(1, 3, 0.35), + new TaikoDifficultyHitObjectRhythm(3, 2, 0.6), + new TaikoDifficultyHitObjectRhythm(2, 3, 0.4), + new TaikoDifficultyHitObjectRhythm(5, 4, 0.5), + new TaikoDifficultyHitObjectRhythm(4, 5, 0.7) }; public TaikoDifficultyCalculator(Ruleset ruleset, WorkingBeatmap beatmap) From 00ae456f0879342fb3bc55e8be717585b7ef5e7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 18 Aug 2020 19:39:03 +0200 Subject: [PATCH 0579/1782] Remove unnecessary null check --- osu.Game.Rulesets.Taiko/Difficulty/Skills/Colour.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Colour.cs b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Colour.cs index e9e0930a9a..2a72f884d1 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Colour.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Colour.cs @@ -41,7 +41,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills double objectStrain = 0.0; - if (taikoCurrent.HitType != null && previousHitType != null && taikoCurrent.HitType != previousHitType) + if (previousHitType != null && taikoCurrent.HitType != previousHitType) { // The colour has changed. objectStrain = 1.0; From d7ff3d77eb538b598e9878fa3cd814daf15fc499 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 18 Aug 2020 19:44:41 +0200 Subject: [PATCH 0580/1782] Slightly optimise and de-branch repetition pattern recognition --- .../Difficulty/Skills/Colour.cs | 33 ++++++++++--------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Colour.cs b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Colour.cs index 2a72f884d1..dd8b536afc 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Colour.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Colour.cs @@ -87,28 +87,29 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills for (int start = monoHistory.Count - l - 1; start >= 0; start--) { - bool samePattern = true; + if (!isSamePattern(start, l)) + continue; - for (int i = 0; i < l; i++) - { - if (monoHistory[start + i] != monoHistory[monoHistory.Count - l + i]) - { - samePattern = false; - } - } - - if (samePattern) // Repetition found! - { - int notesSince = 0; - for (int i = start; i < monoHistory.Count; i++) notesSince += monoHistory[i]; - penalty *= repetitionPenalty(notesSince); - break; - } + int notesSince = 0; + for (int i = start; i < monoHistory.Count; i++) notesSince += monoHistory[i]; + penalty *= repetitionPenalty(notesSince); + break; } return penalty; } + private bool isSamePattern(int start, int l) + { + for (int i = 0; i < l; i++) + { + if (monoHistory[start + i] != monoHistory[monoHistory.Count - l + i]) + return false; + } + + return true; + } + private double repetitionPenalty(int notesSince) => Math.Min(1.0, 0.032 * notesSince); } } From ce0e5cf9a168ce86b5ea176a4767d4929aa6f211 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 18 Aug 2020 19:47:36 +0200 Subject: [PATCH 0581/1782] Slightly optimise and de-branch rhythm pattern recognition --- .../Difficulty/Skills/Rhythm.cs | 31 ++++++++++--------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Rhythm.cs b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Rhythm.cs index 483e94cd70..4c06deb5c0 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Rhythm.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Rhythm.cs @@ -38,28 +38,29 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills { for (int start = rhythmHistory.Count - l - 1; start >= 0; start--) { - bool samePattern = true; + if (!samePattern(start, l)) + continue; - for (int i = 0; i < l; i++) - { - if (rhythmHistory[start + i].Rhythm != rhythmHistory[rhythmHistory.Count - l + i].Rhythm) - { - samePattern = false; - } - } - - if (samePattern) // Repetition found! - { - int notesSince = hitobject.ObjectIndex - rhythmHistory[start].ObjectIndex; - penalty *= repetitionPenalty(notesSince); - break; - } + int notesSince = hitobject.ObjectIndex - rhythmHistory[start].ObjectIndex; + penalty *= repetitionPenalty(notesSince); + break; } } return penalty; } + private bool samePattern(int start, int l) + { + for (int i = 0; i < l; i++) + { + if (rhythmHistory[start + i].Rhythm != rhythmHistory[rhythmHistory.Count - l + i].Rhythm) + return false; + } + + return true; + } + private double patternLengthPenalty(int patternLength) { double shortPatternPenalty = Math.Min(0.15 * patternLength, 1.0); From 80e4c157279d5a58b873d14fedf3ea3b26ad7bf2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 18 Aug 2020 19:50:16 +0200 Subject: [PATCH 0582/1782] Use Math.Clamp --- osu.Game.Rulesets.Taiko/Difficulty/Skills/Rhythm.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Rhythm.cs b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Rhythm.cs index 4c06deb5c0..f6ef6470ed 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Rhythm.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Rhythm.cs @@ -64,7 +64,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills private double patternLengthPenalty(int patternLength) { double shortPatternPenalty = Math.Min(0.15 * patternLength, 1.0); - double longPatternPenalty = Math.Max(Math.Min(2.5 - 0.15 * patternLength, 1.0), 0.0); + double longPatternPenalty = Math.Clamp(2.5 - 0.15 * patternLength, 0.0, 1.0); return Math.Min(shortPatternPenalty, longPatternPenalty); } From c827e215069bde7be1747524f6fc9a9e755d61d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 18 Aug 2020 19:51:19 +0200 Subject: [PATCH 0583/1782] Extract helper method to reset rhythm strain --- osu.Game.Rulesets.Taiko/Difficulty/Skills/Rhythm.cs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Rhythm.cs b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Rhythm.cs index f6ef6470ed..6bb2eaf06a 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Rhythm.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Rhythm.cs @@ -74,8 +74,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills if (noteLengthMs < 80) return 1; if (noteLengthMs < 210) return Math.Max(0, 1.4 - 0.005 * noteLengthMs); - currentStrain = 0.0; - notesSinceRhythmChange = 0; + resetRhythmStrain(); return 0.0; } @@ -83,8 +82,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills { if (!(current.BaseObject is Hit)) { - currentStrain = 0.0; - notesSinceRhythmChange = 0; + resetRhythmStrain(); return 0.0; } @@ -109,5 +107,11 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills currentStrain += objectStrain; return currentStrain; } + + private void resetRhythmStrain() + { + currentStrain = 0.0; + notesSinceRhythmChange = 0; + } } } From 51d41515ef857386cb89467b8777a8429ab9c072 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 18 Aug 2020 19:54:20 +0200 Subject: [PATCH 0584/1782] Simplify expression with ternary --- osu.Game.Rulesets.Taiko/Difficulty/Skills/Stamina.cs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Stamina.cs b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Stamina.cs index 430a553113..13510290f7 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Stamina.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Stamina.cs @@ -73,12 +73,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills public Stamina(bool rightHand) { - hand = 0; - - if (rightHand) - { - hand = 1; - } + hand = rightHand ? 1 : 0; } } } From cb5ea6aa9a54d26d454456837fc1b15dbb7f7819 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 18 Aug 2020 19:59:28 +0200 Subject: [PATCH 0585/1782] Generalise p-norm function --- .../Difficulty/TaikoDifficultyCalculator.cs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs index 7a9f1765ae..aa21df0228 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs @@ -49,13 +49,9 @@ namespace osu.Game.Rulesets.Taiko.Difficulty return 0.79 - Math.Atan(staminaDifficulty / colorDifficulty - 12) / Math.PI / 2; } - private double norm(double p, double v1, double v2, double v3) + private double norm(double p, params double[] values) { - return Math.Pow( - Math.Pow(v1, p) + - Math.Pow(v2, p) + - Math.Pow(v3, p) - , 1 / p); + return Math.Pow(values.Sum(x => Math.Pow(x, p)), 1 / p); } private double rescale(double sr) From 27f97973ee188bf77c6a10248aa88ddad6057989 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 18 Aug 2020 20:14:00 +0200 Subject: [PATCH 0586/1782] Add more proper typing to skills --- .../Difficulty/TaikoDifficultyCalculator.cs | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs index aa21df0228..d3ff0b95ee 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs @@ -61,7 +61,8 @@ namespace osu.Game.Rulesets.Taiko.Difficulty return 10.43 * Math.Log(sr / 8 + 1); } - private double locallyCombinedDifficulty(double staminaPenalty, Skill colour, Skill rhythm, Skill stamina1, Skill stamina2) + private double locallyCombinedDifficulty( + double staminaPenalty, Colour colour, Rhythm rhythm, Stamina staminaRight, Stamina staminaLeft) { double difficulty = 0; double weight = 1; @@ -71,7 +72,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty { double colourPeak = colour.StrainPeaks[i] * colour_skill_multiplier; double rhythmPeak = rhythm.StrainPeaks[i] * rhythm_skill_multiplier; - double staminaPeak = (stamina1.StrainPeaks[i] + stamina2.StrainPeaks[i]) * stamina_skill_multiplier * staminaPenalty; + double staminaPeak = (staminaRight.StrainPeaks[i] + staminaLeft.StrainPeaks[i]) * stamina_skill_multiplier * staminaPenalty; peaks.Add(norm(2, colourPeak, rhythmPeak, staminaPeak)); } @@ -89,14 +90,19 @@ namespace osu.Game.Rulesets.Taiko.Difficulty if (beatmap.HitObjects.Count == 0) return new TaikoDifficultyAttributes { Mods = mods, Skills = skills }; - double colourRating = skills[0].DifficultyValue() * colour_skill_multiplier; - double rhythmRating = skills[1].DifficultyValue() * rhythm_skill_multiplier; - double staminaRating = (skills[2].DifficultyValue() + skills[3].DifficultyValue()) * stamina_skill_multiplier; + var colour = (Colour)skills[0]; + var rhythm = (Rhythm)skills[1]; + var staminaRight = (Stamina)skills[2]; + var staminaLeft = (Stamina)skills[3]; + + double colourRating = colour.DifficultyValue() * colour_skill_multiplier; + double rhythmRating = rhythm.DifficultyValue() * rhythm_skill_multiplier; + double staminaRating = (staminaRight.DifficultyValue() + staminaLeft.DifficultyValue()) * stamina_skill_multiplier; double staminaPenalty = simpleColourPenalty(staminaRating, colourRating); staminaRating *= staminaPenalty; - double combinedRating = locallyCombinedDifficulty(staminaPenalty, skills[0], skills[1], skills[2], skills[3]); + double combinedRating = locallyCombinedDifficulty(staminaPenalty, colour, rhythm, staminaRight, staminaLeft); double separatedRating = norm(1.5, colourRating, rhythmRating, staminaRating); double starRating = 1.4 * separatedRating + 0.5 * combinedRating; starRating = rescale(starRating); From 8f1a71c6b1c563b7238c81c997b60df1dadd8440 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 19 Aug 2020 07:44:45 +0300 Subject: [PATCH 0587/1782] Remove counter sprite attributes for not being of any reasonable use --- .../Visual/Gameplay/TestSceneScoreCounter.cs | 2 - .../UserInterface/PercentageCounter.cs | 9 ++--- .../Graphics/UserInterface/RollingCounter.cs | 38 +------------------ .../Graphics/UserInterface/ScoreCounter.cs | 8 +--- .../UserInterface/SimpleComboCounter.cs | 7 +++- osu.Game/Screens/Play/HUDOverlay.cs | 3 -- 6 files changed, 13 insertions(+), 54 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneScoreCounter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneScoreCounter.cs index 030d420ec0..09b4f9b761 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneScoreCounter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneScoreCounter.cs @@ -20,7 +20,6 @@ namespace osu.Game.Tests.Visual.Gameplay { Origin = Anchor.TopRight, Anchor = Anchor.TopRight, - TextSize = 40, Margin = new MarginPadding(20), }; Add(score); @@ -30,7 +29,6 @@ namespace osu.Game.Tests.Visual.Gameplay Origin = Anchor.BottomLeft, Anchor = Anchor.BottomLeft, Margin = new MarginPadding(10), - TextSize = 40, }; Add(comboCounter); diff --git a/osu.Game/Graphics/UserInterface/PercentageCounter.cs b/osu.Game/Graphics/UserInterface/PercentageCounter.cs index 9b31935eee..3ea9c1053c 100644 --- a/osu.Game/Graphics/UserInterface/PercentageCounter.cs +++ b/osu.Game/Graphics/UserInterface/PercentageCounter.cs @@ -3,6 +3,7 @@ using System; using osu.Framework.Allocation; +using osu.Framework.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Utils; @@ -28,7 +29,7 @@ namespace osu.Game.Graphics.UserInterface } [BackgroundDependencyLoader] - private void load(OsuColour colours) => AccentColour = colours.BlueLighter; + private void load(OsuColour colours) => Colour = colours.BlueLighter; protected override string FormatCount(double count) => count.FormatAccuracy(); @@ -38,11 +39,7 @@ namespace osu.Game.Graphics.UserInterface } protected override OsuSpriteText CreateSpriteText() - { - var spriteText = base.CreateSpriteText(); - spriteText.Font = spriteText.Font.With(fixedWidth: true); - return spriteText; - } + => base.CreateSpriteText().With(s => s.Font = s.Font.With(size: 20f, fixedWidth: true)); public override void Increment(double amount) { diff --git a/osu.Game/Graphics/UserInterface/RollingCounter.cs b/osu.Game/Graphics/UserInterface/RollingCounter.cs index 76bb4bf69d..7c53d4fa0d 100644 --- a/osu.Game/Graphics/UserInterface/RollingCounter.cs +++ b/osu.Game/Graphics/UserInterface/RollingCounter.cs @@ -9,11 +9,10 @@ using System; using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Bindables; -using osuTK.Graphics; namespace osu.Game.Graphics.UserInterface { - public abstract class RollingCounter : Container, IHasAccentColour + public abstract class RollingCounter : Container where T : struct, IEquatable { /// @@ -60,38 +59,6 @@ namespace osu.Game.Graphics.UserInterface public abstract void Increment(T amount); - private float textSize = 40f; - - public float TextSize - { - get => displayedCountSpriteText?.Font.Size ?? textSize; - set - { - if (TextSize == value) - return; - - textSize = value; - if (displayedCountSpriteText != null) - displayedCountSpriteText.Font = displayedCountSpriteText.Font.With(size: value); - } - } - - private Color4 accentColour; - - public Color4 AccentColour - { - get => displayedCountSpriteText?.Colour ?? accentColour; - set - { - if (AccentColour == value) - return; - - accentColour = value; - if (displayedCountSpriteText != null) - displayedCountSpriteText.Colour = value; - } - } - /// /// Skeleton of a numeric counter which value rolls over time. /// @@ -185,8 +152,7 @@ namespace osu.Game.Graphics.UserInterface protected virtual OsuSpriteText CreateSpriteText() => new OsuSpriteText { - Font = OsuFont.Numeric.With(size: textSize), - Colour = accentColour, + Font = OsuFont.Numeric.With(size: 40f), }; } } diff --git a/osu.Game/Graphics/UserInterface/ScoreCounter.cs b/osu.Game/Graphics/UserInterface/ScoreCounter.cs index 438fe6c13b..faabe69f87 100644 --- a/osu.Game/Graphics/UserInterface/ScoreCounter.cs +++ b/osu.Game/Graphics/UserInterface/ScoreCounter.cs @@ -29,7 +29,7 @@ namespace osu.Game.Graphics.UserInterface } [BackgroundDependencyLoader] - private void load(OsuColour colours) => AccentColour = colours.BlueLighter; + private void load(OsuColour colours) => Colour = colours.BlueLighter; protected override double GetProportionalDuration(double currentValue, double newValue) { @@ -50,11 +50,7 @@ namespace osu.Game.Graphics.UserInterface } protected override OsuSpriteText CreateSpriteText() - { - var spriteText = base.CreateSpriteText(); - spriteText.Font = spriteText.Font.With(fixedWidth: true); - return spriteText; - } + => base.CreateSpriteText().With(s => s.Font = s.Font.With(fixedWidth: true)); public override void Increment(double amount) { diff --git a/osu.Game/Graphics/UserInterface/SimpleComboCounter.cs b/osu.Game/Graphics/UserInterface/SimpleComboCounter.cs index af03cbb63e..aac0166774 100644 --- a/osu.Game/Graphics/UserInterface/SimpleComboCounter.cs +++ b/osu.Game/Graphics/UserInterface/SimpleComboCounter.cs @@ -3,6 +3,8 @@ using System; using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Game.Graphics.Sprites; namespace osu.Game.Graphics.UserInterface { @@ -19,7 +21,7 @@ namespace osu.Game.Graphics.UserInterface } [BackgroundDependencyLoader] - private void load(OsuColour colours) => AccentColour = colours.BlueLighter; + private void load(OsuColour colours) => Colour = colours.BlueLighter; protected override string FormatCount(int count) { @@ -35,5 +37,8 @@ namespace osu.Game.Graphics.UserInterface { Current.Value += amount; } + + protected override OsuSpriteText CreateSpriteText() + => base.CreateSpriteText().With(s => s.Font = s.Font.With(size: 20f)); } } diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index f09745cf71..26aefa138b 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -232,7 +232,6 @@ namespace osu.Game.Screens.Play protected virtual RollingCounter CreateAccuracyCounter() => new PercentageCounter { - TextSize = 20, BypassAutoSizeAxes = Axes.X, Anchor = Anchor.TopLeft, Origin = Anchor.TopRight, @@ -241,14 +240,12 @@ namespace osu.Game.Screens.Play protected virtual ScoreCounter CreateScoreCounter() => new ScoreCounter(6) { - TextSize = 40, Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, }; protected virtual RollingCounter CreateComboCounter() => new SimpleComboCounter { - TextSize = 20, BypassAutoSizeAxes = Axes.X, Anchor = Anchor.TopRight, Origin = Anchor.TopLeft, From 5759ffff6fc8696dc7ca98ef507c5b39c6743334 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 19 Aug 2020 07:45:05 +0300 Subject: [PATCH 0588/1782] Use the property instead of the backing field --- osu.Game/Graphics/UserInterface/RollingCounter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Graphics/UserInterface/RollingCounter.cs b/osu.Game/Graphics/UserInterface/RollingCounter.cs index 7c53d4fa0d..6763198213 100644 --- a/osu.Game/Graphics/UserInterface/RollingCounter.cs +++ b/osu.Game/Graphics/UserInterface/RollingCounter.cs @@ -77,7 +77,7 @@ namespace osu.Game.Graphics.UserInterface private void load() { displayedCountSpriteText = CreateSpriteText(); - displayedCountSpriteText.Text = FormatCount(displayedCount); + displayedCountSpriteText.Text = FormatCount(DisplayedCount); Child = displayedCountSpriteText; } From ee9fa11d142ed4fe14ca3dc06bfcc9edb56c02f5 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 19 Aug 2020 07:47:02 +0300 Subject: [PATCH 0589/1782] Use `With(s => ...)` extension for better readability --- .../Gameplay/Components/MatchScoreDisplay.cs | 23 ++++++++++--------- .../Expanded/Statistics/AccuracyStatistic.cs | 10 ++++---- .../Expanded/Statistics/CounterStatistic.cs | 10 ++++---- .../Ranking/Expanded/TotalScoreCounter.cs | 14 +++++------ 4 files changed, 26 insertions(+), 31 deletions(-) diff --git a/osu.Game.Tournament/Screens/Gameplay/Components/MatchScoreDisplay.cs b/osu.Game.Tournament/Screens/Gameplay/Components/MatchScoreDisplay.cs index 25417921bc..695c6d6f3e 100644 --- a/osu.Game.Tournament/Screens/Gameplay/Components/MatchScoreDisplay.cs +++ b/osu.Game.Tournament/Screens/Gameplay/Components/MatchScoreDisplay.cs @@ -137,19 +137,20 @@ namespace osu.Game.Tournament.Screens.Gameplay.Components public bool Winning { - set => displayedSpriteText.Font = value + set => updateFont(value); + } + + protected override OsuSpriteText CreateSpriteText() => base.CreateSpriteText().With(s => + { + displayedSpriteText = s; + displayedSpriteText.Spacing = new Vector2(-6); + updateFont(false); + }); + + private void updateFont(bool winning) + => displayedSpriteText.Font = winning ? OsuFont.Torus.With(weight: FontWeight.Bold, size: 50, fixedWidth: true) : OsuFont.Torus.With(weight: FontWeight.Regular, size: 40, fixedWidth: true); - } - - protected override OsuSpriteText CreateSpriteText() - { - displayedSpriteText = base.CreateSpriteText(); - displayedSpriteText.Spacing = new Vector2(-6); - Winning = false; - - return displayedSpriteText; - } } } } diff --git a/osu.Game/Screens/Ranking/Expanded/Statistics/AccuracyStatistic.cs b/osu.Game/Screens/Ranking/Expanded/Statistics/AccuracyStatistic.cs index 921ad80976..6933456e7e 100644 --- a/osu.Game/Screens/Ranking/Expanded/Statistics/AccuracyStatistic.cs +++ b/osu.Game/Screens/Ranking/Expanded/Statistics/AccuracyStatistic.cs @@ -49,13 +49,11 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics public override void Increment(double amount) => Current.Value += amount; - protected override OsuSpriteText CreateSpriteText() + protected override OsuSpriteText CreateSpriteText() => base.CreateSpriteText().With(s => { - var spriteText = base.CreateSpriteText(); - spriteText.Font = OsuFont.Torus.With(size: 20, fixedWidth: true); - spriteText.Spacing = new Vector2(-2, 0); - return spriteText; - } + s.Font = OsuFont.Torus.With(size: 20, fixedWidth: true); + s.Spacing = new Vector2(-2, 0); + }); } } } diff --git a/osu.Game/Screens/Ranking/Expanded/Statistics/CounterStatistic.cs b/osu.Game/Screens/Ranking/Expanded/Statistics/CounterStatistic.cs index cc0f49c968..043a560d12 100644 --- a/osu.Game/Screens/Ranking/Expanded/Statistics/CounterStatistic.cs +++ b/osu.Game/Screens/Ranking/Expanded/Statistics/CounterStatistic.cs @@ -44,13 +44,11 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics protected override Easing RollingEasing => AccuracyCircle.ACCURACY_TRANSFORM_EASING; - protected override OsuSpriteText CreateSpriteText() + protected override OsuSpriteText CreateSpriteText() => base.CreateSpriteText().With(s => { - var spriteText = base.CreateSpriteText(); - spriteText.Font = OsuFont.Torus.With(size: 20, fixedWidth: true); - spriteText.Spacing = new Vector2(-2, 0); - return spriteText; - } + s.Font = OsuFont.Torus.With(size: 20, fixedWidth: true); + s.Spacing = new Vector2(-2, 0); + }); public override void Increment(int amount) => Current.Value += amount; diff --git a/osu.Game/Screens/Ranking/Expanded/TotalScoreCounter.cs b/osu.Game/Screens/Ranking/Expanded/TotalScoreCounter.cs index b0060d19ac..7f6fd1eabe 100644 --- a/osu.Game/Screens/Ranking/Expanded/TotalScoreCounter.cs +++ b/osu.Game/Screens/Ranking/Expanded/TotalScoreCounter.cs @@ -28,16 +28,14 @@ namespace osu.Game.Screens.Ranking.Expanded protected override string FormatCount(long count) => count.ToString("N0"); - protected override OsuSpriteText CreateSpriteText() + protected override OsuSpriteText CreateSpriteText() => base.CreateSpriteText().With(s => { - var spriteText = base.CreateSpriteText(); - spriteText.Anchor = Anchor.TopCentre; - spriteText.Origin = Anchor.TopCentre; + s.Anchor = Anchor.TopCentre; + s.Origin = Anchor.TopCentre; - spriteText.Font = OsuFont.Torus.With(size: 60, weight: FontWeight.Light, fixedWidth: true); - spriteText.Spacing = new Vector2(-5, 0); - return spriteText; - } + s.Font = OsuFont.Torus.With(size: 60, weight: FontWeight.Light, fixedWidth: true); + s.Spacing = new Vector2(-5, 0); + }); public override void Increment(long amount) => Current.Value += amount; From 422100192c3a6c14628b03369be6dbfdb93cf18d Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 19 Aug 2020 07:58:23 +0300 Subject: [PATCH 0590/1782] Move HasFont to legacy skin extensions class instead --- osu.Game.Rulesets.Catch/Skinning/CatchLegacySkinTransformer.cs | 2 +- osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs | 2 +- osu.Game/Skinning/LegacySkinExtensions.cs | 3 +++ osu.Game/Skinning/LegacySkinTransformer.cs | 2 -- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Skinning/CatchLegacySkinTransformer.cs b/osu.Game.Rulesets.Catch/Skinning/CatchLegacySkinTransformer.cs index c2432e1dbb..0e434291c1 100644 --- a/osu.Game.Rulesets.Catch/Skinning/CatchLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Catch/Skinning/CatchLegacySkinTransformer.cs @@ -58,7 +58,7 @@ namespace osu.Game.Rulesets.Catch.Skinning var fontOverlap = GetConfig(LegacySetting.ComboOverlap)?.Value ?? -2f; // For simplicity, let's use legacy combo font texture existence as a way to identify legacy skins from default. - if (HasFont(comboFont)) + if (this.HasFont(comboFont)) return new LegacyComboCounter(Source, comboFont, fontOverlap); break; diff --git a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs index b955150c90..851a8d56c9 100644 --- a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs @@ -94,7 +94,7 @@ namespace osu.Game.Rulesets.Osu.Skinning var font = GetConfig(OsuSkinConfiguration.HitCirclePrefix)?.Value ?? "default"; var overlap = GetConfig(OsuSkinConfiguration.HitCircleOverlap)?.Value ?? -2; - return !HasFont(font) + return !this.HasFont(font) ? null : new LegacySpriteText(Source, font) { diff --git a/osu.Game/Skinning/LegacySkinExtensions.cs b/osu.Game/Skinning/LegacySkinExtensions.cs index bb46dc8b9f..0ee02a2442 100644 --- a/osu.Game/Skinning/LegacySkinExtensions.cs +++ b/osu.Game/Skinning/LegacySkinExtensions.cs @@ -62,6 +62,9 @@ namespace osu.Game.Skinning } } + public static bool HasFont(this ISkin source, string fontPrefix) + => source.GetTexture($"{fontPrefix}-0") != null; + public class SkinnableTextureAnimation : TextureAnimation { [Resolved(canBeNull: true)] diff --git a/osu.Game/Skinning/LegacySkinTransformer.cs b/osu.Game/Skinning/LegacySkinTransformer.cs index acca53835c..ebc4757e75 100644 --- a/osu.Game/Skinning/LegacySkinTransformer.cs +++ b/osu.Game/Skinning/LegacySkinTransformer.cs @@ -47,7 +47,5 @@ namespace osu.Game.Skinning } public abstract IBindable GetConfig(TLookup lookup); - - protected bool HasFont(string fontPrefix) => GetTexture($"{fontPrefix}-0") != null; } } From 885f8104f566cb350aef38bc8e99649f49a4a7ea Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 19 Aug 2020 08:00:57 +0300 Subject: [PATCH 0591/1782] Always use public accessors even on legacy classes Because of https://github.com/ppy/osu-framework/issues/3727 --- osu.Game.Rulesets.Catch/Skinning/LegacyComboCounter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch/Skinning/LegacyComboCounter.cs b/osu.Game.Rulesets.Catch/Skinning/LegacyComboCounter.cs index 90dd1f4e9f..13c751ac5d 100644 --- a/osu.Game.Rulesets.Catch/Skinning/LegacyComboCounter.cs +++ b/osu.Game.Rulesets.Catch/Skinning/LegacyComboCounter.cs @@ -16,7 +16,7 @@ using osuTK.Graphics; namespace osu.Game.Rulesets.Catch.Skinning { - internal class LegacyComboCounter : CompositeDrawable, ICatchComboCounter + public class LegacyComboCounter : CompositeDrawable, ICatchComboCounter { private readonly ISkin skin; From d4bde0afe57c04a01fe5ab880f7dc70686fc0f0e Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 19 Aug 2020 08:18:30 +0300 Subject: [PATCH 0592/1782] Do not pass accent value on a reverted miss judgement --- osu.Game.Rulesets.Catch/UI/CatchComboDisplay.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game.Rulesets.Catch/UI/CatchComboDisplay.cs b/osu.Game.Rulesets.Catch/UI/CatchComboDisplay.cs index 10aee2ea31..351610646d 100644 --- a/osu.Game.Rulesets.Catch/UI/CatchComboDisplay.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchComboDisplay.cs @@ -47,6 +47,12 @@ namespace osu.Game.Rulesets.Catch.UI if (!result.Judgement.AffectsCombo || !result.HasResult) return; + if (result.Type == HitResult.Miss) + { + updateCombo(result.ComboAtJudgement, null); + return; + } + updateCombo(result.ComboAtJudgement, judgedObject.AccentColour.Value); } From a59dabca7ed30c48e6c8cae98b8fe6c2558b9e5c Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 19 Aug 2020 08:28:11 +0300 Subject: [PATCH 0593/1782] Use existing hit objects instead of defining own --- .../TestSceneComboCounter.cs | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneComboCounter.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneComboCounter.cs index 2581e305dd..079cdfc44b 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneComboCounter.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneComboCounter.cs @@ -55,25 +55,13 @@ namespace osu.Game.Rulesets.Catch.Tests private void performJudgement(HitResult type, Judgement judgement = null) { - var judgedObject = new TestDrawableCatchHitObject(new TestCatchHitObject()); + var judgedObject = new DrawableFruit(new Fruit()) { AccentColour = { Value = judgedObjectColour } }; + var result = new JudgementResult(judgedObject.HitObject, judgement ?? new Judgement()) { Type = type }; scoreProcessor.ApplyResult(result); foreach (var counter in CreatedDrawables.Cast()) counter.OnNewResult(judgedObject, result); } - - private class TestDrawableCatchHitObject : DrawableCatchHitObject - { - public TestDrawableCatchHitObject(CatchHitObject hitObject) - : base(hitObject) - { - AccentColour.Value = Color4.White; - } - } - - private class TestCatchHitObject : CatchHitObject - { - } } } From dde0bc6070f41bf001e4d108bf8a0fe77f22191b Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 19 Aug 2020 08:29:24 +0300 Subject: [PATCH 0594/1782] Add step to randomize judged object's combo colour --- .../TestSceneComboCounter.cs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneComboCounter.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneComboCounter.cs index 079cdfc44b..89521d616d 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneComboCounter.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneComboCounter.cs @@ -6,6 +6,7 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Utils; using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.Objects.Drawables; using osu.Game.Rulesets.Catch.UI; @@ -23,6 +24,8 @@ namespace osu.Game.Rulesets.Catch.Tests private GameplayBeatmap gameplayBeatmap; private readonly Bindable isBreakTime = new BindableBool(); + private Color4 judgedObjectColour = Color4.White; + [BackgroundDependencyLoader] private void load() { @@ -50,7 +53,17 @@ namespace osu.Game.Rulesets.Catch.Tests { AddRepeatStep("perform hit", () => performJudgement(HitResult.Perfect), 20); AddStep("perform miss", () => performJudgement(HitResult.Miss)); + AddToggleStep("toggle gameplay break", v => isBreakTime.Value = v); + AddStep("randomize judged object colour", () => + { + judgedObjectColour = new Color4( + RNG.NextSingle(1f), + RNG.NextSingle(1f), + RNG.NextSingle(1f), + 1f + ); + }); } private void performJudgement(HitResult type, Judgement judgement = null) From af52b73b06ed9b6482ebb7cba4c0765b19ebdc24 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 19 Aug 2020 08:39:40 +0300 Subject: [PATCH 0595/1782] Fill out missing documentation --- osu.Game.Rulesets.Catch/Skinning/LegacyComboCounter.cs | 3 +++ osu.Game.Rulesets.Catch/UI/CatchComboDisplay.cs | 3 +++ 2 files changed, 6 insertions(+) diff --git a/osu.Game.Rulesets.Catch/Skinning/LegacyComboCounter.cs b/osu.Game.Rulesets.Catch/Skinning/LegacyComboCounter.cs index 13c751ac5d..8ea06688cb 100644 --- a/osu.Game.Rulesets.Catch/Skinning/LegacyComboCounter.cs +++ b/osu.Game.Rulesets.Catch/Skinning/LegacyComboCounter.cs @@ -16,6 +16,9 @@ using osuTK.Graphics; namespace osu.Game.Rulesets.Catch.Skinning { + /// + /// A combo counter implementation that visually behaves almost similar to osu!stable's combo counter. + /// public class LegacyComboCounter : CompositeDrawable, ICatchComboCounter { private readonly ISkin skin; diff --git a/osu.Game.Rulesets.Catch/UI/CatchComboDisplay.cs b/osu.Game.Rulesets.Catch/UI/CatchComboDisplay.cs index 351610646d..b53711e4ed 100644 --- a/osu.Game.Rulesets.Catch/UI/CatchComboDisplay.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchComboDisplay.cs @@ -10,6 +10,9 @@ using osuTK.Graphics; namespace osu.Game.Rulesets.Catch.UI { + /// + /// Represents a component that displays a skinned and handles combo judgement results for updating it accordingly. + /// public class CatchComboDisplay : SkinnableDrawable { private int currentCombo; From 06503597e00f8b784c0c712f97c17ff589b086f1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 19 Aug 2020 19:09:35 +0900 Subject: [PATCH 0596/1782] Remove unnecessarily exposed visibility state --- osu.Game.Tests/Visual/Menus/TestSceneToolbar.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Menus/TestSceneToolbar.cs b/osu.Game.Tests/Visual/Menus/TestSceneToolbar.cs index 5170058700..56c030df77 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneToolbar.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneToolbar.cs @@ -84,16 +84,14 @@ namespace osu.Game.Tests.Visual.Menus AddStep("try to show toolbar", () => toolbar.Show()); if (mode == OverlayActivation.Disabled) - AddUntilStep("toolbar still hidden", () => toolbar.Visibility == Visibility.Hidden); + AddUntilStep("toolbar still hidden", () => toolbar.State.Value == Visibility.Hidden); else - AddAssert("toolbar is visible", () => toolbar.Visibility == Visibility.Visible); + AddAssert("toolbar is visible", () => toolbar.State.Value == Visibility.Visible); } public class TestToolbar : Toolbar { public new Bindable OverlayActivationMode => base.OverlayActivationMode; - - public Visibility Visibility => State.Value; } } } From 3e4eae7fe4bcad660abf0bb6018e3f2f0d66bf3f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 19 Aug 2020 19:10:45 +0900 Subject: [PATCH 0597/1782] Remove unnecessary until step --- osu.Game.Tests/Visual/Menus/TestSceneToolbar.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Menus/TestSceneToolbar.cs b/osu.Game.Tests/Visual/Menus/TestSceneToolbar.cs index 56c030df77..f819ae4682 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneToolbar.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneToolbar.cs @@ -84,7 +84,7 @@ namespace osu.Game.Tests.Visual.Menus AddStep("try to show toolbar", () => toolbar.Show()); if (mode == OverlayActivation.Disabled) - AddUntilStep("toolbar still hidden", () => toolbar.State.Value == Visibility.Hidden); + AddAssert("toolbar still hidden", () => toolbar.State.Value == Visibility.Hidden); else AddAssert("toolbar is visible", () => toolbar.State.Value == Visibility.Visible); } From f6ca31688e73757dbf12a37381f52e6c91917f46 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 19 Aug 2020 21:39:55 +0900 Subject: [PATCH 0598/1782] Fix incorrect spacing --- osu.Game.Rulesets.Mania/Skinning/LegacyHitExplosion.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyHitExplosion.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyHitExplosion.cs index 41f3090afd..7c5d41efcf 100644 --- a/osu.Game.Rulesets.Mania/Skinning/LegacyHitExplosion.cs +++ b/osu.Game.Rulesets.Mania/Skinning/LegacyHitExplosion.cs @@ -73,7 +73,7 @@ namespace osu.Game.Rulesets.Mania.Skinning (explosion as IFramedAnimation)?.GotoFrame(0); explosion?.FadeInFromZero(80) - .Then().FadeOut(120); + .Then().FadeOut(120); } } } From 4397be60e2a3bd249662437d9f6e1272b470bc0c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 19 Aug 2020 21:58:02 +0900 Subject: [PATCH 0599/1782] 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 f3fb949f76..1a76a24496 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,6 +52,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index a12ce138bd..d1e2033596 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -24,7 +24,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 0170e94140..9b25eaab41 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -80,7 +80,7 @@ - + From 1badc584f6cc7920558fce900d6a275f62f55188 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 19 Aug 2020 22:10:58 +0900 Subject: [PATCH 0600/1782] Update textbox event names --- osu.Game/Graphics/UserInterface/OsuTextBox.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/OsuTextBox.cs b/osu.Game/Graphics/UserInterface/OsuTextBox.cs index 0d173e2d3e..1ec4dfc91a 100644 --- a/osu.Game/Graphics/UserInterface/OsuTextBox.cs +++ b/osu.Game/Graphics/UserInterface/OsuTextBox.cs @@ -74,9 +74,9 @@ namespace osu.Game.Graphics.UserInterface protected override Color4 SelectionColour => new Color4(249, 90, 255, 255); - protected override void OnTextAdded(string added) + protected override void OnUserTextAdded(string added) { - base.OnTextAdded(added); + base.OnUserTextAdded(added); if (added.Any(char.IsUpper) && AllowUniqueCharacterSamples) capsTextAddedSample?.Play(); @@ -84,9 +84,9 @@ namespace osu.Game.Graphics.UserInterface textAddedSamples[RNG.Next(0, 3)]?.Play(); } - protected override void OnTextRemoved(string removed) + protected override void OnUserTextRemoved(string removed) { - base.OnTextRemoved(removed); + base.OnUserTextRemoved(removed); textRemovedSample?.Play(); } From ff0dec3dd928aa0ab0467cadb1027414bfe1606e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 20 Aug 2020 12:23:39 +0900 Subject: [PATCH 0601/1782] Update plist path to work with newer fastlane version It seems they have fixed the working/current directory and the parent traversal is no longer required. --- fastlane/Fastfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 4fd0e5e8c7..8c278604aa 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -113,7 +113,7 @@ platform :ios do souyuz( platform: "ios", - plist_path: "../osu.iOS/Info.plist" + plist_path: "osu.iOS/Info.plist" ) end @@ -127,7 +127,7 @@ platform :ios do end lane :update_version do |options| - options[:plist_path] = '../osu.iOS/Info.plist' + options[:plist_path] = 'osu.iOS/Info.plist' app_version(options) end From 6358f4a661f282719597d215a1129af97e63ae5b Mon Sep 17 00:00:00 2001 From: Shane Woolcock Date: Thu, 20 Aug 2020 17:33:08 +0930 Subject: [PATCH 0602/1782] Disable CA2225 warning regarding operator overloads --- .editorconfig | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.editorconfig b/.editorconfig index 67f98f94eb..a5f7795882 100644 --- a/.editorconfig +++ b/.editorconfig @@ -191,4 +191,7 @@ 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 +dotnet_diagnostic.IDE0069.severity = none + +#Disable operator overloads requiring alternate named methods +dotnet_diagnostic.CA2225.severity = none \ No newline at end of file From 1f14d9b690d39122cd89ef799429c12c7511e34e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 20 Aug 2020 18:15:06 +0900 Subject: [PATCH 0603/1782] Use correct width adjust for osu!catch playfield --- .../UI/CatchPlayfieldAdjustmentContainer.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch/UI/CatchPlayfieldAdjustmentContainer.cs b/osu.Game.Rulesets.Catch/UI/CatchPlayfieldAdjustmentContainer.cs index 8ee23461ba..040247a264 100644 --- a/osu.Game.Rulesets.Catch/UI/CatchPlayfieldAdjustmentContainer.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchPlayfieldAdjustmentContainer.cs @@ -10,6 +10,8 @@ namespace osu.Game.Rulesets.Catch.UI { public class CatchPlayfieldAdjustmentContainer : PlayfieldAdjustmentContainer { + private const float playfield_size_adjust = 0.8f; + protected override Container Content => content; private readonly Container content; @@ -18,7 +20,7 @@ namespace osu.Game.Rulesets.Catch.UI Anchor = Anchor.TopCentre; Origin = Anchor.TopCentre; - Size = new Vector2(0.86f); // matches stable's vertical offset for catcher plate + Size = new Vector2(playfield_size_adjust); InternalChild = new Container { From a94a86178bcbbde7a7b1a96cf871be29c3e96b40 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 20 Aug 2020 19:12:37 +0900 Subject: [PATCH 0604/1782] Align osu!catch playfield with stable 1:1 --- .../UI/CatchPlayfieldAdjustmentContainer.cs | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Catch/UI/CatchPlayfieldAdjustmentContainer.cs b/osu.Game.Rulesets.Catch/UI/CatchPlayfieldAdjustmentContainer.cs index 040247a264..efc1b24ed5 100644 --- a/osu.Game.Rulesets.Catch/UI/CatchPlayfieldAdjustmentContainer.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchPlayfieldAdjustmentContainer.cs @@ -17,8 +17,12 @@ namespace osu.Game.Rulesets.Catch.UI public CatchPlayfieldAdjustmentContainer() { - Anchor = Anchor.TopCentre; - Origin = Anchor.TopCentre; + // because we are using centre anchor/origin, we will need to limit visibility in the future + // to ensure tall windows do not get a readability advantage. + // it may be possible to bake the catch-specific offsets (-100..340 mentioned below) into new values + // which are compatible with TopCentre alignment. + Anchor = Anchor.Centre; + Origin = Anchor.Centre; Size = new Vector2(playfield_size_adjust); @@ -29,7 +33,7 @@ namespace osu.Game.Rulesets.Catch.UI RelativeSizeAxes = Axes.Both, FillMode = FillMode.Fit, FillAspectRatio = 4f / 3, - Child = content = new ScalingContainer { RelativeSizeAxes = Axes.Both } + Child = content = new ScalingContainer { RelativeSizeAxes = Axes.Both, } }; } @@ -42,8 +46,14 @@ namespace osu.Game.Rulesets.Catch.UI { base.Update(); + // in stable, fruit fall vertically from -100 to 340. + // to emulate this, we want to make our playfield 440 gameplay pixels high. + // we then offset it -100 vertically in the position set below. + const float stable_v_offset_ratio = 440 / 384f; + Scale = new Vector2(Parent.ChildSize.X / CatchPlayfield.WIDTH); - Size = Vector2.Divide(Vector2.One, Scale); + Position = new Vector2(0, -100 * stable_v_offset_ratio + Scale.X); + Size = Vector2.Divide(new Vector2(1, stable_v_offset_ratio), Scale); } } } From e6d13edafb8200de6122d685dd5cdffaf724d2c3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 20 Aug 2020 19:41:27 +0900 Subject: [PATCH 0605/1782] Force tournament client to run in windowed mode We generally haven't tested in other modes, and it doesn't really make sense as you wouldn't be able to use it in a meaningful way otherwise. - [ ] Test on windows. --- osu.Game.Tournament/TournamentGame.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/osu.Game.Tournament/TournamentGame.cs b/osu.Game.Tournament/TournamentGame.cs index 7b1a174c1e..307ee1c773 100644 --- a/osu.Game.Tournament/TournamentGame.cs +++ b/osu.Game.Tournament/TournamentGame.cs @@ -31,6 +31,7 @@ namespace osu.Game.Tournament public static readonly Color4 TEXT_COLOUR = Color4Extensions.FromHex("#fff"); private Drawable heightWarning; private Bindable windowSize; + private Bindable windowMode; [BackgroundDependencyLoader] private void load(FrameworkConfigManager frameworkConfig) @@ -43,6 +44,12 @@ namespace osu.Game.Tournament heightWarning.Alpha = size.NewValue.Width < minWidth ? 1 : 0; }), true); + windowMode = frameworkConfig.GetBindable(FrameworkSetting.WindowMode); + windowMode.BindValueChanged(mode => ScheduleAfterChildren(() => + { + windowMode.Value = WindowMode.Windowed; + }), true); + AddRange(new[] { new Container From c89509aca01da1f461382a3851f72c32131fc54e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 20 Aug 2020 20:25:40 +0900 Subject: [PATCH 0606/1782] Fix right bound not being applied correctly --- .../CatchBeatmapConversionTest.cs | 1 + .../Beatmaps/CatchBeatmapProcessor.cs | 2 +- ...t-bound-hr-offset-expected-conversion.json | 17 ++++++++++++++++ .../Beatmaps/right-bound-hr-offset.osu | 20 +++++++++++++++++++ 4 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/right-bound-hr-offset-expected-conversion.json create mode 100644 osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/right-bound-hr-offset.osu diff --git a/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs b/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs index df54df7b01..8c48158acd 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("hardrock-stream", new[] { typeof(CatchModHardRock) })] [TestCase("hardrock-repeat-slider", new[] { typeof(CatchModHardRock) })] [TestCase("hardrock-spinner", new[] { typeof(CatchModHardRock) })] + [TestCase("right-bound-hr-offset", new[] { typeof(CatchModHardRock) })] public new void Test(string name, params Type[] mods) => base.Test(name, mods); protected override IEnumerable CreateConvertValue(HitObject hitObject) diff --git a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs index bb14988414..15e6e98f5a 100644 --- a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs +++ b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs @@ -179,7 +179,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps if (amount > 0) { // Clamp to the right bound - if (position + amount < 1) + if (position + amount < CatchPlayfield.WIDTH) position += amount; } else diff --git a/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/right-bound-hr-offset-expected-conversion.json b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/right-bound-hr-offset-expected-conversion.json new file mode 100644 index 0000000000..3bde97070c --- /dev/null +++ b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/right-bound-hr-offset-expected-conversion.json @@ -0,0 +1,17 @@ +{ + "Mappings": [{ + "StartTime": 3368, + "Objects": [{ + "StartTime": 3368, + "Position": 374 + }] + }, + { + "StartTime": 3501, + "Objects": [{ + "StartTime": 3501, + "Position": 446 + }] + } + ] +} \ No newline at end of file diff --git a/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/right-bound-hr-offset.osu b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/right-bound-hr-offset.osu new file mode 100644 index 0000000000..6630f369d5 --- /dev/null +++ b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/right-bound-hr-offset.osu @@ -0,0 +1,20 @@ +osu file format v14 + +[General] +StackLeniency: 0.7 +Mode: 2 + +[Difficulty] +HPDrainRate:6 +CircleSize:4 +OverallDifficulty:9.6 +ApproachRate:9.6 +SliderMultiplier:1.9 +SliderTickRate:1 + +[TimingPoints] +2169,266.666666666667,4,2,1,70,1,0 + +[HitObjects] +374,60,3368,1,0,0:0:0:0: +410,146,3501,1,2,0:1:0:0: \ No newline at end of file From f1e09466036ae60f50296dd22c700cc6e14e9522 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 20 Aug 2020 22:38:47 +0900 Subject: [PATCH 0607/1782] Remove release samples in invert mod --- osu.Game.Rulesets.Mania/Mods/ManiaModInvert.cs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModInvert.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModInvert.cs index 56f6e389bf..593b459e8a 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModInvert.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModInvert.cs @@ -60,12 +60,7 @@ namespace osu.Game.Rulesets.Mania.Mods Column = column.Key, StartTime = locations[i].startTime, Duration = duration, - Samples = locations[i].samples, - NodeSamples = new List> - { - locations[i].samples, - locations[i + 1].samples - } + NodeSamples = new List> { locations[i].samples, new List() } }); } From 54a2322090a7c1555e7c170ce28443c46f1b20cc Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 20 Aug 2020 22:51:52 +0900 Subject: [PATCH 0608/1782] Use Array.Empty<> --- osu.Game.Rulesets.Mania/Mods/ManiaModInvert.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModInvert.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModInvert.cs index 593b459e8a..1ea45c295c 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModInvert.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModInvert.cs @@ -60,7 +60,7 @@ namespace osu.Game.Rulesets.Mania.Mods Column = column.Key, StartTime = locations[i].startTime, Duration = duration, - NodeSamples = new List> { locations[i].samples, new List() } + NodeSamples = new List> { locations[i].samples, Array.Empty() } }); } From a193fb79071a6cbfb8ddf09b1b27e2b38987bcb1 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 20 Aug 2020 23:15:30 +0900 Subject: [PATCH 0609/1782] Fix test not working for droplets/tinydroplets --- .../TestSceneFruitObjects.cs | 22 +++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneFruitObjects.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneFruitObjects.cs index c07e4fdad3..6182faedd1 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneFruitObjects.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneFruitObjects.cs @@ -30,9 +30,8 @@ namespace osu.Game.Rulesets.Catch.Tests private Drawable createDrawableTinyDroplet() { - var droplet = new TinyDroplet + var droplet = new TestCatchTinyDroplet { - StartTime = Clock.CurrentTime, Scale = 1.5f, }; @@ -49,9 +48,8 @@ namespace osu.Game.Rulesets.Catch.Tests private Drawable createDrawableDroplet() { - var droplet = new Droplet + var droplet = new TestCatchDroplet { - StartTime = Clock.CurrentTime, Scale = 1.5f, }; @@ -95,5 +93,21 @@ namespace osu.Game.Rulesets.Catch.Tests public override FruitVisualRepresentation VisualRepresentation { get; } } + + public class TestCatchDroplet : Droplet + { + public TestCatchDroplet() + { + StartTime = 1000000000000; + } + } + + public class TestCatchTinyDroplet : TinyDroplet + { + public TestCatchTinyDroplet() + { + StartTime = 1000000000000; + } + } } } From 725caa9382128e5dedfec1a664e26dae89b7489e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 20 Aug 2020 23:16:37 +0900 Subject: [PATCH 0610/1782] Add visual test for hyperdash droplets --- osu.Game.Rulesets.Catch.Tests/TestSceneFruitObjects.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneFruitObjects.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneFruitObjects.cs index 6182faedd1..385d8ed7fa 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneFruitObjects.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneFruitObjects.cs @@ -20,12 +20,13 @@ namespace osu.Game.Rulesets.Catch.Tests foreach (FruitVisualRepresentation rep in Enum.GetValues(typeof(FruitVisualRepresentation))) AddStep($"show {rep}", () => SetContents(() => createDrawable(rep))); - AddStep("show droplet", () => SetContents(createDrawableDroplet)); - + AddStep("show droplet", () => SetContents(() => createDrawableDroplet())); AddStep("show tiny droplet", () => SetContents(createDrawableTinyDroplet)); foreach (FruitVisualRepresentation rep in Enum.GetValues(typeof(FruitVisualRepresentation))) AddStep($"show hyperdash {rep}", () => SetContents(() => createDrawable(rep, true))); + + AddStep("show hyperdash droplet", () => SetContents(() => createDrawableDroplet(true))); } private Drawable createDrawableTinyDroplet() @@ -46,11 +47,12 @@ namespace osu.Game.Rulesets.Catch.Tests }; } - private Drawable createDrawableDroplet() + private Drawable createDrawableDroplet(bool hyperdash = false) { var droplet = new TestCatchDroplet { Scale = 1.5f, + HyperDashTarget = hyperdash ? new Banana() : null }; return new DrawableDroplet(droplet) From 40a456170b73754fd172c2cb3bf4bc697f5c3bcd Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 20 Aug 2020 23:34:40 +0900 Subject: [PATCH 0611/1782] Add default skin display for hyperdash droplets --- .../Objects/Drawables/DrawableDroplet.cs | 7 +- .../Objects/Drawables/DropletPiece.cs | 70 +++++++++++++++++++ .../Objects/Drawables/FruitPiece.cs | 6 -- 3 files changed, 71 insertions(+), 12 deletions(-) create mode 100644 osu.Game.Rulesets.Catch/Objects/Drawables/DropletPiece.cs diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableDroplet.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableDroplet.cs index cad8892283..592b69d963 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableDroplet.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableDroplet.cs @@ -4,7 +4,6 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Utils; -using osu.Game.Rulesets.Catch.Objects.Drawables.Pieces; using osu.Game.Skinning; namespace osu.Game.Rulesets.Catch.Objects.Drawables @@ -21,11 +20,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables [BackgroundDependencyLoader] private void load() { - ScaleContainer.Child = new SkinnableDrawable(new CatchSkinComponent(CatchSkinComponents.Droplet), _ => new Pulp - { - Size = Size / 4, - AccentColour = { BindTarget = AccentColour } - }); + ScaleContainer.Child = new SkinnableDrawable(new CatchSkinComponent(CatchSkinComponents.Droplet), _ => new DropletPiece()); } protected override void UpdateInitialTransforms() diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DropletPiece.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DropletPiece.cs new file mode 100644 index 0000000000..d6c9f4398f --- /dev/null +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DropletPiece.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.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Rulesets.Catch.Objects.Drawables.Pieces; +using osu.Game.Rulesets.Catch.UI; +using osu.Game.Rulesets.Objects.Drawables; +using osuTK; + +namespace osu.Game.Rulesets.Catch.Objects.Drawables +{ + public class DropletPiece : CompositeDrawable + { + public DropletPiece() + { + Size = new Vector2(CatchHitObject.OBJECT_RADIUS / 2); + } + + [BackgroundDependencyLoader] + private void load(DrawableHitObject drawableObject) + { + DrawableCatchHitObject drawableCatchObject = (DrawableCatchHitObject)drawableObject; + var hitObject = drawableCatchObject.HitObject; + + InternalChild = new Pulp + { + // RelativeSizeAxes is not used since the edge effect uses Size. + Size = Size, + AccentColour = { BindTarget = drawableObject.AccentColour } + }; + + if (hitObject.HyperDash) + { + AddInternal(new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Size = new Vector2(2f), + Depth = 1, + Children = new Drawable[] + { + new Circle + { + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + BorderColour = Catcher.DEFAULT_HYPER_DASH_COLOUR, + BorderThickness = 6, + Children = new Drawable[] + { + new Box + { + AlwaysPresent = true, + Alpha = 0.3f, + Blending = BlendingParameters.Additive, + RelativeSizeAxes = Axes.Both, + Colour = Catcher.DEFAULT_HYPER_DASH_COLOUR, + } + } + } + } + }); + } + } + } +} diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/FruitPiece.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/FruitPiece.cs index 7ac9f11ad6..4bffdab3d8 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawables/FruitPiece.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/FruitPiece.cs @@ -3,7 +3,6 @@ using System; using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -21,11 +20,8 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables public const float RADIUS_ADJUST = 1.1f; private Circle border; - private CatchHitObject hitObject; - private readonly IBindable accentColour = new Bindable(); - public FruitPiece() { RelativeSizeAxes = Axes.Both; @@ -37,8 +33,6 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables DrawableCatchHitObject drawableCatchObject = (DrawableCatchHitObject)drawableObject; hitObject = drawableCatchObject.HitObject; - accentColour.BindTo(drawableCatchObject.AccentColour); - AddRangeInternal(new[] { getFruitFor(drawableCatchObject.HitObject.VisualRepresentation), From 35ff25940b11437e8823b0b904c78c99ee101428 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 20 Aug 2020 18:16:32 +0200 Subject: [PATCH 0612/1782] Add sample playback to juice stream test scene --- osu.Game.Rulesets.Catch.Tests/TestSceneAutoJuiceStream.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneAutoJuiceStream.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneAutoJuiceStream.cs index d6bba3d55e..3c636a5b97 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneAutoJuiceStream.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneAutoJuiceStream.cs @@ -1,7 +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.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.UI; @@ -38,7 +40,11 @@ namespace osu.Game.Rulesets.Catch.Tests new Vector2(width, 0) }), StartTime = i * 2000, - NewCombo = i % 8 == 0 + NewCombo = i % 8 == 0, + Samples = new List(new[] + { + new HitSampleInfo { Bank = "normal", Name = "hitnormal", Volume = 100 } + }) }); } From 45e2ea71b4e28380fdcad4719271fa7438beab98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 20 Aug 2020 18:41:08 +0200 Subject: [PATCH 0613/1782] Rename Palpable{-> Drawable}CatchHitObject --- .../Objects/Drawables/DrawableCatchHitObject.cs | 4 ++-- osu.Game.Rulesets.Catch/Objects/Drawables/DrawableDroplet.cs | 2 +- osu.Game.Rulesets.Catch/Objects/Drawables/DrawableFruit.cs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableCatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableCatchHitObject.cs index c6345a9df7..883d2048ed 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableCatchHitObject.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableCatchHitObject.cs @@ -15,14 +15,14 @@ using osuTK.Graphics; namespace osu.Game.Rulesets.Catch.Objects.Drawables { - public abstract class PalpableCatchHitObject : DrawableCatchHitObject + public abstract class PalpableDrawableCatchHitObject : DrawableCatchHitObject where TObject : CatchHitObject { public override bool CanBePlated => true; protected Container ScaleContainer { get; private set; } - protected PalpableCatchHitObject(TObject hitObject) + protected PalpableDrawableCatchHitObject(TObject hitObject) : base(hitObject) { Origin = Anchor.Centre; diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableDroplet.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableDroplet.cs index cad8892283..77ae7e9a54 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableDroplet.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableDroplet.cs @@ -9,7 +9,7 @@ using osu.Game.Skinning; namespace osu.Game.Rulesets.Catch.Objects.Drawables { - public class DrawableDroplet : PalpableCatchHitObject + public class DrawableDroplet : PalpableDrawableCatchHitObject { public override bool StaysOnPlate => false; diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableFruit.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableFruit.cs index fae5a10d04..c1c34e4157 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableFruit.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableFruit.cs @@ -8,7 +8,7 @@ using osu.Game.Skinning; namespace osu.Game.Rulesets.Catch.Objects.Drawables { - public class DrawableFruit : PalpableCatchHitObject + public class DrawableFruit : PalpableDrawableCatchHitObject { public DrawableFruit(Fruit h) : base(h) From f956c9fe37925edb9780f778b181b24a6a44dade Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 21 Aug 2020 02:01:29 +0900 Subject: [PATCH 0614/1782] Clobber in a gameplay test --- .../TestSceneHyperDash.cs | 19 +++++++++++++++++++ osu.Game.Rulesets.Catch/UI/Catcher.cs | 5 ++--- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDash.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDash.cs index ad24adf352..1aa333c401 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDash.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDash.cs @@ -6,6 +6,7 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Testing; using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.UI; using osu.Game.Rulesets.Objects; @@ -31,6 +32,8 @@ namespace osu.Game.Rulesets.Catch.Tests AddUntilStep("wait for right hyperdash", () => getCatcher().Scale.X > 0 && getCatcher().HyperDashing); AddUntilStep("wait for left hyperdash", () => getCatcher().Scale.X < 0 && getCatcher().HyperDashing); } + + AddUntilStep("wait for right hyperdash", () => getCatcher().Scale.X > 0 && getCatcher().HyperDashing); } private Catcher getCatcher() => Player.ChildrenOfType().First().MovableCatcher; @@ -46,6 +49,8 @@ namespace osu.Game.Rulesets.Catch.Tests } }; + beatmap.ControlPointInfo.Add(0, new TimingControlPoint()); + // Should produce a hyper-dash (edge case test) beatmap.HitObjects.Add(new Fruit { StartTime = 1816, X = 56, NewCombo = true }); beatmap.HitObjects.Add(new Fruit { StartTime = 2008, X = 308, NewCombo = true }); @@ -63,6 +68,20 @@ namespace osu.Game.Rulesets.Catch.Tests createObjects(() => new Fruit { X = right_x }); createObjects(() => new TestJuiceStream(left_x), 1); + beatmap.ControlPointInfo.Add(7900, new TimingControlPoint + { + BeatLength = 50 + }); + + createObjects(() => new TestJuiceStream(left_x) + { + Path = new SliderPath(new[] + { + new PathControlPoint(Vector2.Zero), + new PathControlPoint(new Vector2(512, 0)) + }) + }, 1); + return beatmap; void createObjects(Func createObject, int count = 3) diff --git a/osu.Game.Rulesets.Catch/UI/Catcher.cs b/osu.Game.Rulesets.Catch/UI/Catcher.cs index 8820dff730..0897ccf2d5 100644 --- a/osu.Game.Rulesets.Catch/UI/Catcher.cs +++ b/osu.Game.Rulesets.Catch/UI/Catcher.cs @@ -226,9 +226,8 @@ namespace osu.Game.Rulesets.Catch.UI catchObjectPosition >= catcherPosition - halfCatchWidth && catchObjectPosition <= catcherPosition + halfCatchWidth; - // only update hyperdash state if we are catching a fruit. - // exceptions are Droplets and JuiceStreams. - if (!(fruit is Fruit)) return validCatch; + // only update hyperdash state if we are catching a fruit or a droplet (and not a tiny droplet). + if (!(fruit is Fruit || fruit is Droplet) || fruit is TinyDroplet) return validCatch; if (validCatch && fruit.HyperDash) { From 28534c1599ed85f8b1a6a1a99fc3c78d7f21b131 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 20 Aug 2020 18:48:01 +0200 Subject: [PATCH 0615/1782] Reintroduce PalpableCatchHitObject at data level --- osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs | 13 +++++++++++++ .../Objects/Drawables/DrawableCatchHitObject.cs | 8 ++------ osu.Game.Rulesets.Catch/Objects/Droplet.cs | 2 +- osu.Game.Rulesets.Catch/Objects/Fruit.cs | 2 +- osu.Game.Rulesets.Catch/UI/CatcherArea.cs | 2 +- 5 files changed, 18 insertions(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs index 04932ecdbb..5985ec9b68 100644 --- a/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs +++ b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs @@ -27,6 +27,11 @@ namespace osu.Game.Rulesets.Catch.Objects set => x = value; } + /// + /// Whether this object can be placed on the catcher's plate. + /// + public virtual bool CanBePlated => false; + /// /// A random offset applied to , set by the . /// @@ -100,6 +105,14 @@ namespace osu.Game.Rulesets.Catch.Objects protected override HitWindows CreateHitWindows() => HitWindows.Empty; } + /// + /// Represents a single object that can be caught by the catcher. + /// + public abstract class PalpableCatchHitObject : CatchHitObject + { + public override bool CanBePlated => true; + } + public enum FruitVisualRepresentation { Pear, diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableCatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableCatchHitObject.cs index 883d2048ed..2fe017dc62 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableCatchHitObject.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableCatchHitObject.cs @@ -16,10 +16,8 @@ using osuTK.Graphics; namespace osu.Game.Rulesets.Catch.Objects.Drawables { public abstract class PalpableDrawableCatchHitObject : DrawableCatchHitObject - where TObject : CatchHitObject + where TObject : PalpableCatchHitObject { - public override bool CanBePlated => true; - protected Container ScaleContainer { get; private set; } protected PalpableDrawableCatchHitObject(TObject hitObject) @@ -65,9 +63,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables public abstract class DrawableCatchHitObject : DrawableHitObject { - public virtual bool CanBePlated => false; - - public virtual bool StaysOnPlate => CanBePlated; + public virtual bool StaysOnPlate => HitObject.CanBePlated; public float DisplayRadius => DrawSize.X / 2 * Scale.X * HitObject.Scale; diff --git a/osu.Game.Rulesets.Catch/Objects/Droplet.cs b/osu.Game.Rulesets.Catch/Objects/Droplet.cs index 7b0bb3f0ae..9c1004a04b 100644 --- a/osu.Game.Rulesets.Catch/Objects/Droplet.cs +++ b/osu.Game.Rulesets.Catch/Objects/Droplet.cs @@ -6,7 +6,7 @@ using osu.Game.Rulesets.Judgements; namespace osu.Game.Rulesets.Catch.Objects { - public class Droplet : CatchHitObject + public class Droplet : PalpableCatchHitObject { public override Judgement CreateJudgement() => new CatchDropletJudgement(); } diff --git a/osu.Game.Rulesets.Catch/Objects/Fruit.cs b/osu.Game.Rulesets.Catch/Objects/Fruit.cs index 6f0423b420..43486796ad 100644 --- a/osu.Game.Rulesets.Catch/Objects/Fruit.cs +++ b/osu.Game.Rulesets.Catch/Objects/Fruit.cs @@ -6,7 +6,7 @@ using osu.Game.Rulesets.Judgements; namespace osu.Game.Rulesets.Catch.Objects { - public class Fruit : CatchHitObject + public class Fruit : PalpableCatchHitObject { public override Judgement CreateJudgement() => new CatchJudgement(); } diff --git a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs index 4255c3b1af..03ebf01b9b 100644 --- a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs +++ b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs @@ -55,7 +55,7 @@ namespace osu.Game.Rulesets.Catch.UI lastPlateableFruit.OnLoadComplete += _ => action(); } - if (result.IsHit && fruit.CanBePlated) + if (result.IsHit && fruit.HitObject.CanBePlated) { // create a new (cloned) fruit to stay on the plate. the original is faded out immediately. var caughtFruit = (DrawableCatchHitObject)CreateDrawableRepresentation?.Invoke(fruit.HitObject); From 9546fbb64bf823392b92333238ed1cf560c6fcae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 20 Aug 2020 18:58:07 +0200 Subject: [PATCH 0616/1782] Prevent catcher from performing invalid catches --- osu.Game.Rulesets.Catch/UI/Catcher.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game.Rulesets.Catch/UI/Catcher.cs b/osu.Game.Rulesets.Catch/UI/Catcher.cs index 8820dff730..e4a3c01dbc 100644 --- a/osu.Game.Rulesets.Catch/UI/Catcher.cs +++ b/osu.Game.Rulesets.Catch/UI/Catcher.cs @@ -216,6 +216,9 @@ namespace osu.Game.Rulesets.Catch.UI /// Whether the catch is possible. public bool AttemptCatch(CatchHitObject fruit) { + if (!fruit.CanBePlated) + return false; + var halfCatchWidth = catchWidth * 0.5f; // this stuff wil disappear once we move fruit to non-relative coordinate space in the future. From 738ff7ba217e1db02c5f9232076a61b8438a2229 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 21 Aug 2020 02:21:16 +0900 Subject: [PATCH 0617/1782] Use full catcher width for hyperdash calculation --- osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs | 6 ++++++ osu.Game.Rulesets.Catch/UI/Catcher.cs | 4 ++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs index 15e6e98f5a..a08c5b6fb1 100644 --- a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs +++ b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs @@ -212,6 +212,12 @@ namespace osu.Game.Rulesets.Catch.Beatmaps objectWithDroplets.Sort((h1, h2) => h1.StartTime.CompareTo(h2.StartTime)); double halfCatcherWidth = Catcher.CalculateCatchWidth(beatmap.BeatmapInfo.BaseDifficulty) / 2; + + // Todo: This is wrong. osu!stable calculated hyperdashes using the full catcher size, excluding the margins. + // This should theoretically cause impossible scenarios, but practically, likely due to the size of the playfield, it doesn't seem possible. + // For now, to bring gameplay (and diffcalc!) completely in-line with stable, this code also uses the full catcher size. + halfCatcherWidth /= Catcher.ALLOWED_CATCH_RANGE; + int lastDirection = 0; double lastExcess = halfCatcherWidth; diff --git a/osu.Game.Rulesets.Catch/UI/Catcher.cs b/osu.Game.Rulesets.Catch/UI/Catcher.cs index 8820dff730..11e69678ca 100644 --- a/osu.Game.Rulesets.Catch/UI/Catcher.cs +++ b/osu.Game.Rulesets.Catch/UI/Catcher.cs @@ -64,7 +64,7 @@ namespace osu.Game.Rulesets.Catch.UI /// /// The width of the catcher which can receive fruit. Equivalent to "catchMargin" in osu-stable. /// - private const float allowed_catch_range = 0.8f; + public const float ALLOWED_CATCH_RANGE = 0.8f; /// /// The drawable catcher for . @@ -166,7 +166,7 @@ namespace osu.Game.Rulesets.Catch.UI /// /// The scale of the catcher. internal static float CalculateCatchWidth(Vector2 scale) - => CatcherArea.CATCHER_SIZE * Math.Abs(scale.X) * allowed_catch_range; + => CatcherArea.CATCHER_SIZE * Math.Abs(scale.X) * ALLOWED_CATCH_RANGE; /// /// Calculates the width of the area used for attempting catches in gameplay. From bd4acdce789776c1252c7f60604296b6ae63bd9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 20 Aug 2020 21:01:58 +0200 Subject: [PATCH 0618/1782] Add until step to ensure failure --- osu.Game.Tests/Visual/Multiplayer/TestSceneMultiScreen.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiScreen.cs index 61859c9da3..dbf5b98e52 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiScreen.cs @@ -14,7 +14,8 @@ namespace osu.Game.Tests.Visual.Multiplayer { Screens.Multi.Multiplayer multi = new Screens.Multi.Multiplayer(); - AddStep(@"show", () => LoadScreen(multi)); + AddStep("show", () => LoadScreen(multi)); + AddUntilStep("wait for loaded", () => multi.IsLoaded); } } } From dcce7a213052cc16b49196c3c3441c38ee4f9809 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 20 Aug 2020 21:03:27 +0200 Subject: [PATCH 0619/1782] Cache local music controller to resolve failure --- osu.Game.Tests/Visual/Multiplayer/TestSceneMultiScreen.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiScreen.cs index dbf5b98e52..3924b0333f 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiScreen.cs @@ -2,6 +2,8 @@ // See the LICENCE file in the repository root for full licence text. using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Game.Overlays; namespace osu.Game.Tests.Visual.Multiplayer { @@ -10,6 +12,9 @@ namespace osu.Game.Tests.Visual.Multiplayer { protected override bool UseOnlineAPI => true; + [Cached] + private MusicController musicController { get; set; } = new MusicController(); + public TestSceneMultiScreen() { Screens.Multi.Multiplayer multi = new Screens.Multi.Multiplayer(); From f00bc67aaa0852e9ed77f83eced4bcaeb9ebd35a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 21 Aug 2020 12:29:28 +0900 Subject: [PATCH 0620/1782] Fix pulp and use relative sizse --- osu.Game.Rulesets.Catch/Objects/Drawables/DropletPiece.cs | 3 +-- osu.Game.Rulesets.Catch/Objects/Drawables/Pieces/Pulp.cs | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DropletPiece.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DropletPiece.cs index d6c9f4398f..c2499446fa 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawables/DropletPiece.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DropletPiece.cs @@ -27,8 +27,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables InternalChild = new Pulp { - // RelativeSizeAxes is not used since the edge effect uses Size. - Size = Size, + RelativeSizeAxes = Axes.Both, AccentColour = { BindTarget = drawableObject.AccentColour } }; diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/Pieces/Pulp.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/Pieces/Pulp.cs index 1e7506a257..d3e4945611 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawables/Pieces/Pulp.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/Pieces/Pulp.cs @@ -36,7 +36,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables.Pieces EdgeEffect = new EdgeEffectParameters { Type = EdgeEffectType.Glow, - Radius = Size.X / 2, + Radius = DrawWidth / 2, Colour = colour.NewValue.Darken(0.2f).Opacity(0.75f) }; } From dd1f2db1752be453e14964d6b47f87aa04e8a611 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 21 Aug 2020 12:30:33 +0900 Subject: [PATCH 0621/1782] Use startTime in test --- osu.Game.Rulesets.Catch.Tests/TestSceneHyperDash.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDash.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDash.cs index 1aa333c401..6dab2a0b56 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDash.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDash.cs @@ -68,7 +68,7 @@ namespace osu.Game.Rulesets.Catch.Tests createObjects(() => new Fruit { X = right_x }); createObjects(() => new TestJuiceStream(left_x), 1); - beatmap.ControlPointInfo.Add(7900, new TimingControlPoint + beatmap.ControlPointInfo.Add(startTime, new TimingControlPoint { BeatLength = 50 }); From 6ad7a3686b011c35cb17e6bfa8d0303e1cf9fc78 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 21 Aug 2020 13:13:08 +0900 Subject: [PATCH 0622/1782] Simplify condition --- osu.Game.Rulesets.Catch/UI/Catcher.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Catch/UI/Catcher.cs b/osu.Game.Rulesets.Catch/UI/Catcher.cs index 7fffa1fdc3..8e74437834 100644 --- a/osu.Game.Rulesets.Catch/UI/Catcher.cs +++ b/osu.Game.Rulesets.Catch/UI/Catcher.cs @@ -229,8 +229,8 @@ namespace osu.Game.Rulesets.Catch.UI catchObjectPosition >= catcherPosition - halfCatchWidth && catchObjectPosition <= catcherPosition + halfCatchWidth; - // only update hyperdash state if we are catching a fruit or a droplet (and not a tiny droplet). - if (!(fruit is Fruit || fruit is Droplet) || fruit is TinyDroplet) return validCatch; + // only update hyperdash state if we are catching not catching a tiny droplet. + if (fruit is TinyDroplet) return validCatch; if (validCatch && fruit.HyperDash) { From 62d833d63d93690d5a40162d09d2ee763858c4cd Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 21 Aug 2020 13:14:50 +0900 Subject: [PATCH 0623/1782] Fix comment --- osu.Game.Rulesets.Catch/UI/Catcher.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch/UI/Catcher.cs b/osu.Game.Rulesets.Catch/UI/Catcher.cs index 8e74437834..952ff6b0ce 100644 --- a/osu.Game.Rulesets.Catch/UI/Catcher.cs +++ b/osu.Game.Rulesets.Catch/UI/Catcher.cs @@ -229,7 +229,7 @@ namespace osu.Game.Rulesets.Catch.UI catchObjectPosition >= catcherPosition - halfCatchWidth && catchObjectPosition <= catcherPosition + halfCatchWidth; - // only update hyperdash state if we are catching not catching a tiny droplet. + // only update hyperdash state if we are not catching a tiny droplet. if (fruit is TinyDroplet) return validCatch; if (validCatch && fruit.HyperDash) From 526f06be4c714410061a732dfb041c84c3c45f0d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 21 Aug 2020 13:53:12 +0900 Subject: [PATCH 0624/1782] Add back track loaded bool in WorkingBeatmap --- osu.Game/Beatmaps/WorkingBeatmap.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs index 051d66af7b..8d5543cadb 100644 --- a/osu.Game/Beatmaps/WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/WorkingBeatmap.cs @@ -275,6 +275,11 @@ namespace osu.Game.Beatmaps /// The track to transfer. public void TransferTrack([NotNull] Track track) => loadedTrack = track ?? throw new ArgumentNullException(nameof(track)); + /// + /// Whether this beatmap's track has been loaded via . + /// + public bool TrackLoaded => loadedTrack != null; + /// /// Get the loaded audio track instance. must have first been called. /// This generally happens via MusicController when changing the global beatmap. @@ -283,7 +288,7 @@ namespace osu.Game.Beatmaps { get { - if (loadedTrack == null) + if (!TrackLoaded) throw new InvalidOperationException($"Cannot access {nameof(Track)} without first calling {nameof(LoadTrack)}."); return loadedTrack; From 0b0ff626477a611a161ece951ffb9f0682dd8a44 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 21 Aug 2020 14:46:23 +0900 Subject: [PATCH 0625/1782] Switch timeline to use track directly from beatmap again --- .../Compose/Components/Timeline/Timeline.cs | 49 ++++++++++--------- 1 file changed, 25 insertions(+), 24 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs index e1702d3eff..96c48c0ddc 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs @@ -30,6 +30,28 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline [Resolved] private MusicController musicController { get; set; } + /// + /// The timeline's scroll position in the last frame. + /// + private float lastScrollPosition; + + /// + /// The track time in the last frame. + /// + private double lastTrackTime; + + /// + /// Whether the user is currently dragging the timeline. + /// + private bool handlingDragInput; + + /// + /// Whether the track was playing before a user drag event. + /// + private bool trackWasPlaying; + + private ITrack track; + public Timeline() { ZoomDuration = 200; @@ -61,9 +83,10 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline Beatmap.BindValueChanged(b => { waveform.Waveform = b.NewValue.Waveform; - track = musicController.CurrentTrack; + track = b.NewValue.Track; - if (track.Length > 0) + // todo: i don't think this is safe, the track may not be loaded yet. + if (b.NewValue.Track.Length > 0) { MaxZoom = getZoomLevelForVisibleMilliseconds(500); MinZoom = getZoomLevelForVisibleMilliseconds(10000); @@ -74,28 +97,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private float getZoomLevelForVisibleMilliseconds(double milliseconds) => (float)(track.Length / milliseconds); - /// - /// The timeline's scroll position in the last frame. - /// - private float lastScrollPosition; - - /// - /// The track time in the last frame. - /// - private double lastTrackTime; - - /// - /// Whether the user is currently dragging the timeline. - /// - private bool handlingDragInput; - - /// - /// Whether the track was playing before a user drag event. - /// - private bool trackWasPlaying; - - private ITrack track; - protected override void Update() { base.Update(); From d2c2e8bbe89536f5fed2a35ea35aa514029ac28d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 21 Aug 2020 15:05:56 +0900 Subject: [PATCH 0626/1782] Revert some more usage of MusicController back to WorkingBeatmap --- .../TestSceneHoldNoteInput.cs | 2 +- osu.Game.Rulesets.Osu.Tests/TestSceneOutOfOrderHits.cs | 2 +- osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs | 2 +- .../Skinning/TestSceneDrawableTaikoMascot.cs | 4 ++-- .../Skinning/TestSceneTaikoPlayfield.cs | 2 +- osu.Game.Tests/Skins/TestSceneBeatmapSkinResources.cs | 10 +++++++--- osu.Game/Beatmaps/WorkingBeatmap.cs | 2 +- osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs | 2 ++ osu.Game/Tests/Visual/RateAdjustedBeatmapTestScene.cs | 3 +-- 9 files changed, 17 insertions(+), 12 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs index 19b69bac6d..95072cf4f8 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs @@ -343,7 +343,7 @@ namespace osu.Game.Rulesets.Mania.Tests judgementResults = new List(); }); - AddUntilStep("Beatmap at 0", () => MusicController.CurrentTrack.CurrentTime == 0); + AddUntilStep("Beatmap at 0", () => Beatmap.Value.Track.CurrentTime == 0); AddUntilStep("Wait until player is loaded", () => currentPlayer.IsCurrentScreen()); AddUntilStep("Wait for completion", () => currentPlayer.ScoreProcessor.HasCompleted.Value); } diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneOutOfOrderHits.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneOutOfOrderHits.cs index 744ad46c28..854626d362 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneOutOfOrderHits.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneOutOfOrderHits.cs @@ -385,7 +385,7 @@ namespace osu.Game.Rulesets.Osu.Tests judgementResults = new List(); }); - AddUntilStep("Beatmap at 0", () => MusicController.CurrentTrack.CurrentTime == 0); + AddUntilStep("Beatmap at 0", () => Beatmap.Value.Track.CurrentTime == 0); AddUntilStep("Wait until player is loaded", () => currentPlayer.IsCurrentScreen()); AddUntilStep("Wait for completion", () => currentPlayer.ScoreProcessor.HasCompleted.Value); } diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs index 1690f648f9..b543b6fa94 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs @@ -366,7 +366,7 @@ namespace osu.Game.Rulesets.Osu.Tests judgementResults = new List(); }); - AddUntilStep("Beatmap at 0", () => MusicController.CurrentTrack.CurrentTime == 0); + AddUntilStep("Beatmap at 0", () => Beatmap.Value.Track.CurrentTime == 0); AddUntilStep("Wait until player is loaded", () => currentPlayer.IsCurrentScreen()); AddUntilStep("Wait for completion", () => currentPlayer.ScoreProcessor.HasCompleted.Value); } diff --git a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableTaikoMascot.cs b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableTaikoMascot.cs index 6141bf062e..47d8a5c012 100644 --- a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableTaikoMascot.cs +++ b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableTaikoMascot.cs @@ -175,11 +175,11 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning private void createDrawableRuleset() { - AddUntilStep("wait for beatmap to be loaded", () => MusicController.CurrentTrack.TrackLoaded); + AddUntilStep("wait for beatmap to be loaded", () => Beatmap.Value.Track.IsLoaded); AddStep("create drawable ruleset", () => { - MusicController.CurrentTrack.Start(); + Beatmap.Value.Track.Start(); SetContents(() => { diff --git a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneTaikoPlayfield.cs b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneTaikoPlayfield.cs index a3d3bc81c4..7b7e2c43d1 100644 --- a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneTaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneTaikoPlayfield.cs @@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning beatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = 1000 }); - MusicController.CurrentTrack.Start(); + Beatmap.Value.Track.Start(); }); AddStep("Load playfield", () => SetContents(() => new TaikoPlayfield(new ControlPointInfo()) diff --git a/osu.Game.Tests/Skins/TestSceneBeatmapSkinResources.cs b/osu.Game.Tests/Skins/TestSceneBeatmapSkinResources.cs index 49b40daf99..eff430ac25 100644 --- a/osu.Game.Tests/Skins/TestSceneBeatmapSkinResources.cs +++ b/osu.Game.Tests/Skins/TestSceneBeatmapSkinResources.cs @@ -3,6 +3,7 @@ using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Audio.Track; using osu.Framework.Testing; using osu.Game.Audio; using osu.Game.Beatmaps; @@ -18,17 +19,20 @@ namespace osu.Game.Tests.Skins [Resolved] private BeatmapManager beatmaps { get; set; } + private WorkingBeatmap beatmap; + [BackgroundDependencyLoader] private void load() { var imported = beatmaps.Import(new ZipArchiveReader(TestResources.OpenResource("Archives/ogg-beatmap.osz"))).Result; - Beatmap.Value = beatmaps.GetWorkingBeatmap(imported.Beatmaps[0]); + beatmap = beatmaps.GetWorkingBeatmap(imported.Beatmaps[0]); + beatmap.LoadTrack(); } [Test] - public void TestRetrieveOggSample() => AddAssert("sample is non-null", () => Beatmap.Value.Skin.GetSample(new SampleInfo("sample")) != null); + public void TestRetrieveOggSample() => AddAssert("sample is non-null", () => beatmap.Skin.GetSample(new SampleInfo("sample")) != null); [Test] - public void TestRetrieveOggTrack() => AddAssert("track is non-null", () => !MusicController.CurrentTrack.IsDummyDevice); + public void TestRetrieveOggTrack() => AddAssert("track is non-null", () => !(beatmap.Track is TrackVirtual)); } } diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs index 8d5543cadb..6a89739e6f 100644 --- a/osu.Game/Beatmaps/WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/WorkingBeatmap.cs @@ -278,7 +278,7 @@ namespace osu.Game.Beatmaps /// /// Whether this beatmap's track has been loaded via . /// - public bool TrackLoaded => loadedTrack != null; + public virtual bool TrackLoaded => loadedTrack != null; /// /// Get the loaded audio track instance. must have first been called. diff --git a/osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs b/osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs index d091da3206..bfcb2403c1 100644 --- a/osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs +++ b/osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs @@ -27,6 +27,8 @@ namespace osu.Game.Tests.Beatmaps this.storyboard = storyboard; } + public override bool TrackLoaded => true; + public override bool BeatmapLoaded => true; protected override IBeatmap GetBeatmap() => beatmap; diff --git a/osu.Game/Tests/Visual/RateAdjustedBeatmapTestScene.cs b/osu.Game/Tests/Visual/RateAdjustedBeatmapTestScene.cs index 7651285970..ad24ffc7b8 100644 --- a/osu.Game/Tests/Visual/RateAdjustedBeatmapTestScene.cs +++ b/osu.Game/Tests/Visual/RateAdjustedBeatmapTestScene.cs @@ -13,8 +13,7 @@ namespace osu.Game.Tests.Visual base.Update(); // note that this will override any mod rate application - if (MusicController.TrackLoaded) - MusicController.CurrentTrack.Tempo.Value = Clock.Rate; + Beatmap.Value.Track.Tempo.Value = Clock.Rate; } } } From f7e4feee3449630475a11a2291803997fb23e029 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 21 Aug 2020 15:25:57 +0900 Subject: [PATCH 0627/1782] Update remaining Player components to use WorkingBeatmap again --- osu.Game/Screens/Play/FailAnimation.cs | 13 ++++++------- osu.Game/Screens/Play/Player.cs | 8 ++++---- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/osu.Game/Screens/Play/FailAnimation.cs b/osu.Game/Screens/Play/FailAnimation.cs index dade904180..54c644c999 100644 --- a/osu.Game/Screens/Play/FailAnimation.cs +++ b/osu.Game/Screens/Play/FailAnimation.cs @@ -6,12 +6,12 @@ using osu.Framework.Bindables; using osu.Game.Rulesets.UI; using System; using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; using osu.Framework.Allocation; using osu.Framework.Audio.Sample; using osu.Framework.Audio.Track; using osu.Framework.Graphics; using osu.Framework.Utils; +using osu.Game.Beatmaps; using osu.Game.Rulesets.Objects.Drawables; using osuTK; using osuTK.Graphics; @@ -28,24 +28,23 @@ namespace osu.Game.Screens.Play private readonly DrawableRuleset drawableRuleset; - [NotNull] - private readonly ITrack track; - private readonly BindableDouble trackFreq = new BindableDouble(1); + private Track track; + private const float duration = 2500; private SampleChannel failSample; - public FailAnimation(DrawableRuleset drawableRuleset, [NotNull] ITrack track) + public FailAnimation(DrawableRuleset drawableRuleset) { this.drawableRuleset = drawableRuleset; - this.track = track; } [BackgroundDependencyLoader] - private void load(AudioManager audio) + private void load(AudioManager audio, IBindable beatmap) { + track = beatmap.Value.Track; failSample = audio.Samples.Get(@"Gameplay/failsound"); } diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index cc70995b26..a8fda10604 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -151,7 +151,7 @@ namespace osu.Game.Screens.Play } [BackgroundDependencyLoader] - private void load(AudioManager audio, OsuConfigManager config, MusicController musicController) + private void load(AudioManager audio, OsuConfigManager config) { Mods.Value = base.Mods.Value.Select(m => m.CreateCopy()).ToArray(); @@ -188,7 +188,7 @@ namespace osu.Game.Screens.Play addUnderlayComponents(GameplayClockContainer); addGameplayComponents(GameplayClockContainer, Beatmap.Value, playableBeatmap); - addOverlayComponents(GameplayClockContainer, Beatmap.Value, musicController.CurrentTrack); + addOverlayComponents(GameplayClockContainer, Beatmap.Value); if (!DrawableRuleset.AllowGameplayOverlays) { @@ -265,7 +265,7 @@ namespace osu.Game.Screens.Play }); } - private void addOverlayComponents(Container target, WorkingBeatmap working, ITrack track) + private void addOverlayComponents(Container target, WorkingBeatmap working) { target.AddRange(new[] { @@ -332,7 +332,7 @@ namespace osu.Game.Screens.Play performImmediateExit(); }, }, - failAnimation = new FailAnimation(DrawableRuleset, track) { OnComplete = onFailComplete, }, + failAnimation = new FailAnimation(DrawableRuleset) { OnComplete = onFailComplete, }, }); } From 0ae460fb8f4e7b9c235ce0cb27ae933157d162c6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 21 Aug 2020 15:50:14 +0900 Subject: [PATCH 0628/1782] Avoid beatmap load call in IntroScreen --- osu.Game.Tests/Visual/Menus/TestSceneIntroWelcome.cs | 3 ++- osu.Game/Screens/Menu/IntroScreen.cs | 3 ++- osu.Game/Screens/Menu/IntroTriangles.cs | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Menus/TestSceneIntroWelcome.cs b/osu.Game.Tests/Visual/Menus/TestSceneIntroWelcome.cs index 29be250b12..5f135febf4 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneIntroWelcome.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneIntroWelcome.cs @@ -3,6 +3,7 @@ using NUnit.Framework; using osu.Framework.Screens; +using osu.Framework.Utils; using osu.Game.Screens.Menu; namespace osu.Game.Tests.Visual.Menus @@ -15,7 +16,7 @@ namespace osu.Game.Tests.Visual.Menus public TestSceneIntroWelcome() { AddUntilStep("wait for load", () => MusicController.TrackLoaded); - + AddAssert("correct track", () => Precision.AlmostEquals(MusicController.CurrentTrack.Length, 48000, 1)); AddAssert("check if menu music loops", () => MusicController.CurrentTrack.Looping); } } diff --git a/osu.Game/Screens/Menu/IntroScreen.cs b/osu.Game/Screens/Menu/IntroScreen.cs index 3e4320ae44..363933694d 100644 --- a/osu.Game/Screens/Menu/IntroScreen.cs +++ b/osu.Game/Screens/Menu/IntroScreen.cs @@ -66,6 +66,7 @@ namespace osu.Game.Screens.Menu /// /// Whether the is provided by osu! resources, rather than a user beatmap. + /// Only valid during or after . /// protected bool UsingThemedIntro { get; private set; } @@ -115,7 +116,6 @@ namespace osu.Game.Screens.Menu if (setInfo != null) { initialBeatmap = beatmaps.GetWorkingBeatmap(setInfo.Beatmaps[0]); - UsingThemedIntro = !initialBeatmap.LoadTrack().IsDummyDevice; } return UsingThemedIntro; @@ -169,6 +169,7 @@ namespace osu.Game.Screens.Menu { beatmap.Value = initialBeatmap; Track = musicController.CurrentTrack; + UsingThemedIntro = !initialBeatmap.LoadTrack().IsDummyDevice; logo.MoveTo(new Vector2(0.5f)); logo.ScaleTo(Vector2.One); diff --git a/osu.Game/Screens/Menu/IntroTriangles.cs b/osu.Game/Screens/Menu/IntroTriangles.cs index a9ef20436f..52281967ee 100644 --- a/osu.Game/Screens/Menu/IntroTriangles.cs +++ b/osu.Game/Screens/Menu/IntroTriangles.cs @@ -44,7 +44,7 @@ namespace osu.Game.Screens.Menu [BackgroundDependencyLoader] private void load() { - if (MenuVoice.Value && !UsingThemedIntro) + if (MenuVoice.Value) welcome = audio.Samples.Get(@"Intro/welcome"); } From 3b03116179c3e0ec88b815dd2bcfc32961058510 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 21 Aug 2020 16:45:59 +0900 Subject: [PATCH 0629/1782] Remove unnecessary using statement --- 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 a8fda10604..9be4fd6a65 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -8,7 +8,6 @@ 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.Graphics; using osu.Framework.Graphics.Containers; From 70697cf1a0e36e57b3533613a5795bdb8722a746 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 21 Aug 2020 16:58:45 +0900 Subject: [PATCH 0630/1782] Restore remaining editor components to use Beatmap.Track --- osu.Game.Tests/Visual/Editing/TimelineTestScene.cs | 11 ++++++----- .../Screens/Edit/Components/BottomBarContainer.cs | 2 ++ osu.Game/Screens/Edit/Components/PlaybackControl.cs | 8 ++------ .../Timelines/Summary/Parts/TimelinePart.cs | 8 ++------ .../Edit/Compose/Components/Timeline/Timeline.cs | 6 +++--- .../Components/Timeline/TimelineTickDisplay.cs | 7 ++++--- osu.Game/Screens/Edit/Editor.cs | 10 ++++------ osu.Game/Screens/Edit/EditorClock.cs | 10 +++++----- osu.Game/Tests/Visual/EditorClockTestScene.cs | 3 ++- 9 files changed, 30 insertions(+), 35 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TimelineTestScene.cs b/osu.Game.Tests/Visual/Editing/TimelineTestScene.cs index 4988a09650..fdb8781563 100644 --- a/osu.Game.Tests/Visual/Editing/TimelineTestScene.cs +++ b/osu.Game.Tests/Visual/Editing/TimelineTestScene.cs @@ -3,11 +3,12 @@ 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.Shapes; +using osu.Game.Beatmaps; using osu.Game.Graphics.UserInterface; -using osu.Game.Overlays; using osu.Game.Rulesets.Edit; using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Compose.Components.Timeline; @@ -64,10 +65,10 @@ namespace osu.Game.Tests.Visual.Editing private readonly Drawable marker; [Resolved] - private EditorClock editorClock { get; set; } + private IBindable beatmap { get; set; } [Resolved] - private MusicController musicController { get; set; } + private EditorClock editorClock { get; set; } public AudioVisualiser() { @@ -93,8 +94,8 @@ namespace osu.Game.Tests.Visual.Editing { base.Update(); - if (musicController.TrackLoaded) - marker.X = (float)(editorClock.CurrentTime / musicController.CurrentTrack.Length); + if (beatmap.Value.Track.IsLoaded) + marker.X = (float)(editorClock.CurrentTime / beatmap.Value.Track.Length); } } diff --git a/osu.Game/Screens/Edit/Components/BottomBarContainer.cs b/osu.Game/Screens/Edit/Components/BottomBarContainer.cs index 8d8c59b2ee..cb5078a479 100644 --- a/osu.Game/Screens/Edit/Components/BottomBarContainer.cs +++ b/osu.Game/Screens/Edit/Components/BottomBarContainer.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.Track; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -17,6 +18,7 @@ namespace osu.Game.Screens.Edit.Components private const float contents_padding = 15; protected readonly IBindable Beatmap = new Bindable(); + protected Track Track => Beatmap.Value.Track; private readonly Drawable background; private readonly Container content; diff --git a/osu.Game/Screens/Edit/Components/PlaybackControl.cs b/osu.Game/Screens/Edit/Components/PlaybackControl.cs index 5bafc120af..59b3d1c565 100644 --- a/osu.Game/Screens/Edit/Components/PlaybackControl.cs +++ b/osu.Game/Screens/Edit/Components/PlaybackControl.cs @@ -16,7 +16,6 @@ using osu.Framework.Input.Events; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; -using osu.Game.Overlays; using osuTK.Input; namespace osu.Game.Screens.Edit.Components @@ -28,9 +27,6 @@ namespace osu.Game.Screens.Edit.Components [Resolved] private EditorClock editorClock { get; set; } - [Resolved] - private MusicController musicController { get; set; } - private readonly BindableNumber tempo = new BindableDouble(1); [BackgroundDependencyLoader] @@ -66,12 +62,12 @@ namespace osu.Game.Screens.Edit.Components } }; - musicController.CurrentTrack.AddAdjustment(AdjustableProperty.Tempo, tempo); + Track?.AddAdjustment(AdjustableProperty.Tempo, tempo); } protected override void Dispose(bool isDisposing) { - musicController?.CurrentTrack.RemoveAdjustment(AdjustableProperty.Tempo, tempo); + Track?.RemoveAdjustment(AdjustableProperty.Tempo, tempo); base.Dispose(isDisposing); } 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 c8a470c58a..4a7c3f26bc 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/TimelinePart.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/TimelinePart.cs @@ -8,7 +8,6 @@ using osuTK; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps; -using osu.Game.Overlays; namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts { @@ -27,9 +26,6 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts protected override Container Content => content; - [Resolved] - private MusicController musicController { get; set; } - public TimelinePart(Container content = null) { AddInternal(this.content = content ?? new Container { RelativeSizeAxes = Axes.Both }); @@ -50,14 +46,14 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts private void updateRelativeChildSize() { // the track may not be loaded completely (only has a length once it is). - if (!musicController.TrackLoaded) + if (!Beatmap.Value.Track.IsLoaded) { content.RelativeChildSize = Vector2.One; Schedule(updateRelativeChildSize); return; } - content.RelativeChildSize = new Vector2((float)Math.Max(1, musicController.CurrentTrack.Length), 1); + content.RelativeChildSize = new Vector2((float)Math.Max(1, Beatmap.Value.Track.Length), 1); } protected virtual void LoadBeatmap(WorkingBeatmap beatmap) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs index 96c48c0ddc..c6bfdda698 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs @@ -50,7 +50,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline /// private bool trackWasPlaying; - private ITrack track; + private Track track; public Timeline() { @@ -134,7 +134,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private void seekTrackToCurrent() { - if (!musicController.TrackLoaded) + if (!track.IsLoaded) return; editorClock.Seek(Current / Content.DrawWidth * track.Length); @@ -142,7 +142,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private void scrollToTrackTime() { - if (!musicController.TrackLoaded || track.Length == 0) + if (!track.IsLoaded || track.Length == 0) return; ScrollTo((float)(editorClock.CurrentTime / track.Length) * Content.DrawWidth, false); diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs index cb122c590e..36ee976bf7 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs @@ -3,9 +3,10 @@ using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Game.Beatmaps; using osu.Game.Graphics; -using osu.Game.Overlays; using osu.Game.Screens.Edit.Components.Timelines.Summary.Parts; using osu.Game.Screens.Edit.Components.Timelines.Summary.Visualisations; @@ -17,7 +18,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private EditorBeatmap beatmap { get; set; } [Resolved] - private MusicController musicController { get; set; } + private Bindable working { get; set; } [Resolved] private BindableBeatDivisor beatDivisor { get; set; } @@ -43,7 +44,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline for (var i = 0; i < beatmap.ControlPointInfo.TimingPoints.Count; i++) { var point = beatmap.ControlPointInfo.TimingPoints[i]; - var until = i + 1 < beatmap.ControlPointInfo.TimingPoints.Count ? beatmap.ControlPointInfo.TimingPoints[i + 1].Time : musicController.CurrentTrack.Length; + var until = i + 1 < beatmap.ControlPointInfo.TimingPoints.Count ? beatmap.ControlPointInfo.TimingPoints[i + 1].Time : working.Value.Track.Length; int beat = 0; diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 6722d9179c..d92f3922c3 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -14,6 +14,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; using osu.Framework.Platform; +using osu.Framework.Timing; using osu.Game.Graphics.UserInterface; using osu.Game.Screens.Edit.Components; using osu.Game.Screens.Edit.Components.Menus; @@ -27,7 +28,6 @@ using osu.Framework.Logging; using osu.Game.Beatmaps; using osu.Game.Graphics.Cursor; using osu.Game.Input.Bindings; -using osu.Game.Overlays; using osu.Game.Rulesets.Edit; using osu.Game.Screens.Edit.Compose; using osu.Game.Screens.Edit.Setup; @@ -53,9 +53,6 @@ namespace osu.Game.Screens.Edit [Resolved] private BeatmapManager beatmapManager { get; set; } - [Resolved] - private MusicController musicController { get; set; } - private Box bottomBackground; private Container screenContainer; @@ -82,8 +79,9 @@ namespace osu.Game.Screens.Edit 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(musicController.CurrentTrack); + clock.ChangeSource(sourceClock); dependencies.CacheAs(clock); AddInternal(clock); @@ -348,7 +346,7 @@ namespace osu.Game.Screens.Edit private void resetTrack(bool seekToStart = false) { - musicController.CurrentTrack.Stop(); + Beatmap.Value.Track?.Stop(); if (seekToStart) { diff --git a/osu.Game/Screens/Edit/EditorClock.cs b/osu.Game/Screens/Edit/EditorClock.cs index 634a6f7e25..d4d0feb813 100644 --- a/osu.Game/Screens/Edit/EditorClock.cs +++ b/osu.Game/Screens/Edit/EditorClock.cs @@ -30,11 +30,6 @@ namespace osu.Game.Screens.Edit { } - public EditorClock() - : this(new ControlPointInfo(), 1000, new BindableBeatDivisor()) - { - } - public EditorClock(ControlPointInfo controlPointInfo, double trackLength, BindableBeatDivisor beatDivisor) { this.beatDivisor = beatDivisor; @@ -45,6 +40,11 @@ namespace osu.Game.Screens.Edit underlyingClock = new DecoupleableInterpolatingFramedClock(); } + public EditorClock() + : this(new ControlPointInfo(), 1000, new BindableBeatDivisor()) + { + } + /// /// Seek to the closest snappable beat from a time. /// diff --git a/osu.Game/Tests/Visual/EditorClockTestScene.cs b/osu.Game/Tests/Visual/EditorClockTestScene.cs index 59c9329d37..f0ec638fc9 100644 --- a/osu.Game/Tests/Visual/EditorClockTestScene.cs +++ b/osu.Game/Tests/Visual/EditorClockTestScene.cs @@ -4,6 +4,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Input.Events; +using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Screens.Edit; @@ -43,7 +44,7 @@ namespace osu.Game.Tests.Visual private void beatmapChanged(ValueChangedEvent e) { Clock.ControlPointInfo = e.NewValue.Beatmap.ControlPointInfo; - Clock.ChangeSource(MusicController.CurrentTrack); + Clock.ChangeSource((IAdjustableClock)e.NewValue.Track ?? new StopwatchClock()); Clock.ProcessFrame(); } From a47b8222b53a00daab14556988952824e59ec1ac Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 21 Aug 2020 16:59:11 +0900 Subject: [PATCH 0631/1782] Restore multiplayer to use beatmap track --- osu.Game/Screens/Multi/Multiplayer.cs | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Multi/Multiplayer.cs b/osu.Game/Screens/Multi/Multiplayer.cs index 1a39d80f8d..4912df17b1 100644 --- a/osu.Game/Screens/Multi/Multiplayer.cs +++ b/osu.Game/Screens/Multi/Multiplayer.cs @@ -52,7 +52,7 @@ namespace osu.Game.Screens.Multi private readonly Bindable currentFilter = new Bindable(new FilterCriteria()); [Resolved] - private MusicController musicController { get; set; } + private MusicController music { get; set; } [Cached(Type = typeof(IRoomManager))] private RoomManager roomManager; @@ -343,10 +343,15 @@ namespace osu.Game.Screens.Multi { if (screenStack.CurrentScreen is MatchSubScreen) { - musicController.CurrentTrack.RestartPoint = Beatmap.Value.Metadata.PreviewTime; - musicController.CurrentTrack.Looping = true; + var track = Beatmap.Value?.Track; - musicController.EnsurePlayingSomething(); + if (track != null) + { + track.RestartPoint = Beatmap.Value.Metadata.PreviewTime; + track.Looping = true; + + music.EnsurePlayingSomething(); + } } else { @@ -356,8 +361,13 @@ namespace osu.Game.Screens.Multi private void cancelLooping() { - musicController.CurrentTrack.Looping = false; - musicController.CurrentTrack.RestartPoint = 0; + var track = Beatmap?.Value?.Track; + + if (track != null) + { + track.Looping = false; + track.RestartPoint = 0; + } } protected override void Dispose(bool isDisposing) From d5cbb589c2ff6fa2a8cfac96ef881d3406149ca9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 21 Aug 2020 17:21:08 +0900 Subject: [PATCH 0632/1782] Revert some test scene changes to use Beatmap.Track where relevant --- .../Gameplay/TestSceneCompletionCancellation.cs | 6 +++--- .../Visual/Gameplay/TestSceneGameplayRewinding.cs | 12 ++++-------- .../Gameplay/TestSceneNightcoreBeatContainer.cs | 4 ++-- osu.Game.Tests/Visual/Gameplay/TestScenePause.cs | 2 +- .../Visual/Gameplay/TestScenePlayerLoader.cs | 8 ++++---- .../Visual/Gameplay/TestSceneStoryboard.cs | 14 ++++++++++---- 6 files changed, 24 insertions(+), 22 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneCompletionCancellation.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneCompletionCancellation.cs index b39cfc3699..6fd5511e5a 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneCompletionCancellation.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneCompletionCancellation.cs @@ -31,7 +31,7 @@ namespace osu.Game.Tests.Visual.Gameplay base.SetUpSteps(); // Ensure track has actually running before attempting to seek - AddUntilStep("wait for track to start running", () => MusicController.IsPlaying); + AddUntilStep("wait for track to start running", () => Beatmap.Value.Track.IsRunning); } [Test] @@ -70,13 +70,13 @@ namespace osu.Game.Tests.Visual.Gameplay private void complete() { - AddStep("seek to completion", () => MusicController.SeekTo(5000)); + AddStep("seek to completion", () => Beatmap.Value.Track.Seek(5000)); AddUntilStep("completion set by processor", () => Player.ScoreProcessor.HasCompleted.Value); } private void cancel() { - AddStep("rewind to cancel", () => MusicController.SeekTo(4000)); + AddStep("rewind to cancel", () => Beatmap.Value.Track.Seek(4000)); AddUntilStep("completion cleared by processor", () => !Player.ScoreProcessor.HasCompleted.Value); } diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayRewinding.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayRewinding.cs index 6bdc65078a..73c6970482 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayRewinding.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayRewinding.cs @@ -8,7 +8,6 @@ using osu.Framework.Audio; using osu.Framework.Utils; using osu.Framework.Timing; using osu.Game.Beatmaps; -using osu.Game.Overlays; using osu.Game.Rulesets; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Storyboards; @@ -21,16 +20,13 @@ namespace osu.Game.Tests.Visual.Gameplay [Resolved] private AudioManager audioManager { get; set; } - [Resolved] - private MusicController musicController { get; set; } - - protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null) - => new ClockBackedTestWorkingBeatmap(beatmap, storyboard, new FramedClock(new ManualClock { Rate = 1 }), audioManager); + protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null) => + new ClockBackedTestWorkingBeatmap(beatmap, storyboard, new FramedClock(new ManualClock { Rate = 1 }), audioManager); [Test] public void TestNoJudgementsOnRewind() { - AddUntilStep("wait for track to start running", () => MusicController.IsPlaying); + AddUntilStep("wait for track to start running", () => Beatmap.Value.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)); @@ -43,7 +39,7 @@ namespace osu.Game.Tests.Visual.Gameplay private void addSeekStep(double time) { - AddStep($"seek to {time}", () => MusicController.SeekTo(time)); + AddStep($"seek to {time}", () => Beatmap.Value.Track.Seek(time)); // Allow a few frames of lenience AddUntilStep("wait for seek to finish", () => Precision.AlmostEquals(time, Player.DrawableRuleset.FrameStableClock.CurrentTime, 100)); diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneNightcoreBeatContainer.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneNightcoreBeatContainer.cs index 53e06a632e..951ee1489d 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneNightcoreBeatContainer.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneNightcoreBeatContainer.cs @@ -19,8 +19,8 @@ namespace osu.Game.Tests.Visual.Gameplay Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo); - MusicController.CurrentTrack.Start(); - MusicController.CurrentTrack.Seek(Beatmap.Value.Beatmap.HitObjects.First().StartTime - 1000); + Beatmap.Value.Track.Start(); + Beatmap.Value.Track.Seek(Beatmap.Value.Beatmap.HitObjects.First().StartTime - 1000); Add(new ModNightcore.NightcoreBeatContainer()); diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs index e7dd586f4e..420bf29429 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs @@ -288,7 +288,7 @@ namespace osu.Game.Tests.Visual.Gameplay private void confirmNoTrackAdjustments() { - AddAssert("track has no adjustments", () => MusicController.CurrentTrack.AggregateFrequency.Value == 1); + AddAssert("track has no adjustments", () => Beatmap.Value.Track.AggregateFrequency.Value == 1); } private void restart() => AddStep("restart", () => Player.Restart()); diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs index c3be6aee2b..4fac7bb45f 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs @@ -61,7 +61,7 @@ namespace osu.Game.Tests.Visual.Gameplay Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo); foreach (var mod in SelectedMods.Value.OfType()) - mod.ApplyToTrack(MusicController.CurrentTrack); + mod.ApplyToTrack(Beatmap.Value.Track); InputManager.Child = container; } @@ -75,7 +75,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddUntilStep("wait for not current", () => !loader.IsCurrentScreen()); AddAssert("player did not load", () => player == null); AddUntilStep("player disposed", () => loader.DisposalTask == null); - AddAssert("mod rate still applied", () => MusicController.CurrentTrack.Rate != 1); + AddAssert("mod rate still applied", () => Beatmap.Value.Track.Rate != 1); } /// @@ -88,13 +88,13 @@ namespace osu.Game.Tests.Visual.Gameplay { AddStep("load dummy beatmap", () => ResetPlayer(false, () => SelectedMods.Value = new[] { new OsuModNightcore() })); AddUntilStep("wait for current", () => loader.IsCurrentScreen()); - AddAssert("mod rate applied", () => MusicController.CurrentTrack.Rate != 1); + AddAssert("mod rate applied", () => Beatmap.Value.Track.Rate != 1); AddUntilStep("wait for non-null player", () => player != null); 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", () => MusicController.CurrentTrack.Rate != 1); + AddAssert("mod rate still applied", () => Beatmap.Value.Track.Rate != 1); } [Test] diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboard.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboard.cs index c11a47d62b..9f1492a25f 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboard.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboard.cs @@ -25,12 +25,16 @@ namespace osu.Game.Tests.Visual.Gameplay private readonly Container storyboardContainer; private DrawableStoryboard storyboard; + [Cached] + private MusicController musicController = new MusicController(); + public TestSceneStoryboard() { Clock = new FramedClock(); AddRange(new Drawable[] { + musicController, new Container { RelativeSizeAxes = Axes.Both, @@ -83,9 +87,11 @@ namespace osu.Game.Tests.Visual.Gameplay private void restart() { - MusicController.CurrentTrack.Reset(); + var track = Beatmap.Value.Track; + + track.Reset(); loadStoryboard(Beatmap.Value); - MusicController.CurrentTrack.Start(); + track.Start(); } private void loadStoryboard(WorkingBeatmap working) @@ -100,7 +106,7 @@ namespace osu.Game.Tests.Visual.Gameplay storyboard.Passing = false; storyboardContainer.Add(storyboard); - decoupledClock.ChangeSource(MusicController.CurrentTrack); + decoupledClock.ChangeSource(working.Track); } private void loadStoryboardNoVideo() @@ -123,7 +129,7 @@ namespace osu.Game.Tests.Visual.Gameplay storyboard = sb.CreateDrawable(Beatmap.Value); storyboardContainer.Add(storyboard); - decoupledClock.ChangeSource(MusicController.CurrentTrack); + decoupledClock.ChangeSource(Beatmap.Value.Track); } } } From aead13628bbaa284b4dc1719c7c83e173f9e8565 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 21 Aug 2020 17:52:42 +0900 Subject: [PATCH 0633/1782] Rework freezing to use masking --- .../Objects/Drawables/DrawableHoldNote.cs | 112 +++++++++--------- 1 file changed, 56 insertions(+), 56 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs index 229ce355d7..e959509b96 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs @@ -12,7 +12,6 @@ using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Skinning; -using osuTK; namespace osu.Game.Rulesets.Mania.Objects.Drawables { @@ -35,19 +34,14 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables private readonly Container tickContainer; /// - /// Contains the maximum size/position of the body prior to any offset or size adjustments. + /// Contains the size of the hold note covering the whole head/tail bounds. The size of this container changes as the hold note is being pressed. /// - private readonly Container bodyContainer; + private readonly Container sizingContainer; /// - /// Contains the offset size/position of the body such that the body extends half-way between the head and tail pieces. + /// Contains the contents of the hold note that should be masked as the hold note is being pressed. Follows changes in the size of . /// - private readonly Container bodyOffsetContainer; - - /// - /// Contains the masking area for the tail, which is resized along with . - /// - private readonly Container tailMaskingContainer; + private readonly Container maskingContainer; private readonly SkinnableDrawable bodyPiece; @@ -71,36 +65,43 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables { RelativeSizeAxes = Axes.X; - AddRangeInternal(new[] + Container maskedContents; + + AddRangeInternal(new Drawable[] { - bodyContainer = new Container + sizingContainer = new Container { RelativeSizeAxes = Axes.Both, Children = new Drawable[] { - bodyOffsetContainer = new Container + maskingContainer = new Container { - RelativeSizeAxes = Axes.X, - Child = bodyPiece = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.HoldNoteBody, hitObject.Column), _ => new DefaultBodyPiece + RelativeSizeAxes = Axes.Both, + Child = maskedContents = new Container { - RelativeSizeAxes = Axes.Both - }) + RelativeSizeAxes = Axes.Both, + Masking = true, + } }, - // The head needs to move along with changes in the size of the body. headContainer = new Container { RelativeSizeAxes = Axes.Both } } }, - tickContainer = new Container { RelativeSizeAxes = Axes.Both }, - tailMaskingContainer = new Container + bodyPiece = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.HoldNoteBody, hitObject.Column), _ => new DefaultBodyPiece { - RelativeSizeAxes = Axes.X, - Masking = true, - Child = tailContainer = new Container - { - RelativeSizeAxes = Axes.X, - } + RelativeSizeAxes = Axes.Both, + }) + { + RelativeSizeAxes = Axes.X }, - headContainer.CreateProxy() + tickContainer = new Container { RelativeSizeAxes = Axes.Both }, + tailContainer = new Container { RelativeSizeAxes = Axes.Both }, + }); + + maskedContents.AddRange(new[] + { + bodyPiece.CreateProxy(), + tickContainer.CreateProxy(), + tailContainer.CreateProxy(), }); } @@ -167,24 +168,15 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables { base.OnDirectionChanged(e); - // The body container is anchored from the position of the tail, since its height is changed when the hold note is being hit. - // The body offset container is anchored from the position of the head (inverse of the above). - // The tail containers are both anchored from the position of the tail. if (e.NewValue == ScrollingDirection.Up) { - bodyContainer.Anchor = bodyContainer.Origin = Anchor.BottomLeft; - bodyOffsetContainer.Anchor = bodyOffsetContainer.Origin = Anchor.TopLeft; - - tailMaskingContainer.Anchor = tailMaskingContainer.Origin = Anchor.BottomLeft; - tailContainer.Anchor = tailContainer.Origin = Anchor.BottomLeft; + bodyPiece.Anchor = bodyPiece.Origin = Anchor.TopLeft; + sizingContainer.Anchor = sizingContainer.Origin = Anchor.BottomLeft; } else { - bodyContainer.Anchor = bodyContainer.Origin = Anchor.TopLeft; - bodyOffsetContainer.Anchor = bodyOffsetContainer.Origin = Anchor.BottomLeft; - - tailMaskingContainer.Anchor = tailMaskingContainer.Origin = Anchor.TopLeft; - tailContainer.Anchor = tailContainer.Origin = Anchor.TopLeft; + bodyPiece.Anchor = bodyPiece.Origin = Anchor.BottomLeft; + sizingContainer.Anchor = sizingContainer.Origin = Anchor.TopLeft; } } @@ -206,26 +198,34 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables if (Time.Current < releaseTime) releaseTime = null; - // Decrease the size of the body while the hold note is held and the head has been hit. This stops at the very first release point. + // Pad the full size container so its contents (i.e. the masking container) reach under the tail. + // This is required for the tail to not be masked away, since it lies outside the bounds of the hold note. + sizingContainer.Padding = new MarginPadding + { + Top = Direction.Value == ScrollingDirection.Down ? -Tail.Height : 0, + Bottom = Direction.Value == ScrollingDirection.Up ? -Tail.Height : 0, + }; + + // Pad the masking container to the starting position of the body piece (half-way under the head). + // This is required ot make the body start getting masked immediately as soon as the note is held. + maskingContainer.Padding = new MarginPadding + { + Top = Direction.Value == ScrollingDirection.Up ? Head.Height / 2 : 0, + Bottom = Direction.Value == ScrollingDirection.Down ? Head.Height / 2 : 0, + }; + + // Position and resize the body to lie half-way under the head and the tail notes. + bodyPiece.Y = (Direction.Value == ScrollingDirection.Up ? 1 : -1) * Head.Height / 2; + bodyPiece.Height = DrawHeight - Head.Height / 2 + Tail.Height / 2; + + // As the note is being held, adjust the size of the fullSizeContainer. This has two effects: + // 1. The contained masking container will mask the body and ticks. + // 2. The head note will move along with the new "head position" in the container. if (Head.IsHit && releaseTime == null) { float heightDecrease = (float)(Math.Max(0, Time.Current - HitObject.StartTime) / HitObject.Duration); - bodyContainer.Height = MathHelper.Clamp(1 - heightDecrease, 0, 1); + sizingContainer.Height = Math.Clamp(1 - heightDecrease, 0, 1); } - - // Re-position the body half-way up the head, and extend the height until it's half-way under the tail. - bodyOffsetContainer.Y = (Direction.Value == ScrollingDirection.Up ? 1 : -1) * Head.Height / 2; - bodyOffsetContainer.Height = bodyContainer.DrawHeight + Tail.Height / 2 - Head.Height / 2; - - // The tail is positioned to be "outside" the hold note, so re-position its masking container to fully cover the tail and extend the height until it's half-way under the head. - // The masking height is determined by the size of the body so that the head and tail don't overlap as the body becomes shorter via hitting (above). - tailMaskingContainer.Y = (Direction.Value == ScrollingDirection.Up ? 1 : -1) * Tail.Height; - tailMaskingContainer.Height = bodyContainer.DrawHeight + Tail.Height - Head.Height / 2; - - // The tail container needs the reverse of the above offset applied to bring the tail to its original position. - // It also needs the full original height of the hold note to maintain positioning even as the height of the masking container changes. - tailContainer.Y = -tailMaskingContainer.Y; - tailContainer.Height = DrawHeight; } protected override void UpdateStateTransforms(ArmedState state) From 69cb9f309123ec3719064ad3bfc9b155ddb4b796 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 21 Aug 2020 18:19:47 +0900 Subject: [PATCH 0634/1782] Fix potential crash if disposing a DrawableStoryboardSample twice --- osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs index 8eaf9ac652..119c48836b 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs @@ -76,6 +76,8 @@ namespace osu.Game.Storyboards.Drawables protected override void Dispose(bool isDisposing) { Channel?.Stop(); + Channel = null; + base.Dispose(isDisposing); } } From 1edafc39bac27e54ebb32dfb44124382ec3b55b5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 21 Aug 2020 18:33:24 +0900 Subject: [PATCH 0635/1782] Fix intro welcome playing double due to missing conditional --- osu.Game/Screens/Menu/IntroTriangles.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Menu/IntroTriangles.cs b/osu.Game/Screens/Menu/IntroTriangles.cs index 52281967ee..a96fddb5ad 100644 --- a/osu.Game/Screens/Menu/IntroTriangles.cs +++ b/osu.Game/Screens/Menu/IntroTriangles.cs @@ -64,7 +64,8 @@ namespace osu.Game.Screens.Menu }, t => { AddInternal(t); - welcome?.Play(); + if (!UsingThemedIntro) + welcome?.Play(); StartTrack(); }); From 308d9f59679033e0585cd96b4d5e550c9d2b31d0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 21 Aug 2020 18:43:58 +0900 Subject: [PATCH 0636/1782] Ensure locally executed methods are always loaded before propagation --- osu.Game/Overlays/MusicController.cs | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index c1116ff651..112026d9e2 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -231,9 +231,7 @@ namespace osu.Game.Overlays if (playable != null) { - if (beatmap is Bindable working) - working.Value = beatmaps.GetWorkingBeatmap(playable.Beatmaps.First(), beatmap.Value); - + changeBeatmap(beatmaps.GetWorkingBeatmap(playable.Beatmaps.First(), beatmap.Value)); restartTrack(); return PreviousTrackResult.Previous; } @@ -257,9 +255,7 @@ namespace osu.Game.Overlays if (playable != null) { - if (beatmap is Bindable working) - working.Value = beatmaps.GetWorkingBeatmap(playable.Beatmaps.First(), beatmap.Value); - + changeBeatmap(beatmaps.GetWorkingBeatmap(playable.Beatmaps.First(), beatmap.Value)); restartTrack(); return true; } @@ -278,11 +274,15 @@ namespace osu.Game.Overlays private TrackChangeDirection? queuedDirection; - private void beatmapChanged(ValueChangedEvent beatmap) + private void beatmapChanged(ValueChangedEvent beatmap) => changeBeatmap(beatmap.NewValue); + + private void changeBeatmap(WorkingBeatmap newWorking) { + var lastWorking = current; + TrackChangeDirection direction = TrackChangeDirection.None; - bool audioEquals = beatmap.NewValue?.BeatmapInfo?.AudioEquals(current?.BeatmapInfo) ?? false; + bool audioEquals = newWorking?.BeatmapInfo?.AudioEquals(current?.BeatmapInfo) ?? false; if (current != null) { @@ -297,13 +297,13 @@ namespace osu.Game.Overlays { // 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 next = newWorking == null ? -1 : BeatmapSets.TakeWhile(b => b.ID != newWorking.BeatmapSetInfo?.ID).Count(); direction = last > next ? TrackChangeDirection.Prev : TrackChangeDirection.Next; } } - current = beatmap.NewValue; + current = newWorking; if (!audioEquals || CurrentTrack.IsDummyDevice) { @@ -312,7 +312,7 @@ namespace osu.Game.Overlays else { // transfer still valid track to new working beatmap - current.TransferTrack(beatmap.OldValue.Track); + current.TransferTrack(lastWorking.Track); } TrackChanged?.Invoke(current, direction); @@ -320,6 +320,11 @@ namespace osu.Game.Overlays ResetTrackAdjustments(); queuedDirection = null; + + // this will be a noop if coming from the beatmapChanged event. + // the exception is local operations like next/prev, where we want to complete loading the track before sending out a change. + if (beatmap.Value != current && beatmap is Bindable working) + working.Value = current; } private void changeTrack() From 4239e9f684e7e1594fc652be1640a940d9092cd0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 21 Aug 2020 18:44:14 +0900 Subject: [PATCH 0637/1782] Fix storyboard test not actually working due to incorrect track referencing --- .../Visual/Gameplay/TestSceneStoryboard.cs | 50 ++++++++----------- 1 file changed, 21 insertions(+), 29 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboard.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboard.cs index 9f1492a25f..5a2b8d22fd 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboard.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboard.cs @@ -22,19 +22,32 @@ namespace osu.Game.Tests.Visual.Gameplay [TestFixture] public class TestSceneStoryboard : OsuTestScene { - private readonly Container storyboardContainer; + private Container storyboardContainer; private DrawableStoryboard storyboard; - [Cached] - private MusicController musicController = new MusicController(); + [Test] + public void TestStoryboard() + { + AddStep("Restart", restart); + AddToggleStep("Passing", passing => + { + if (storyboard != null) storyboard.Passing = passing; + }); + } - public TestSceneStoryboard() + [Test] + public void TestStoryboardMissingVideo() + { + AddStep("Load storyboard with missing video", loadStoryboardNoVideo); + } + + [BackgroundDependencyLoader] + private void load() { Clock = new FramedClock(); AddRange(new Drawable[] { - musicController, new Container { RelativeSizeAxes = Axes.Both, @@ -58,32 +71,11 @@ namespace osu.Game.Tests.Visual.Gameplay State = { Value = Visibility.Visible }, } }); + + Beatmap.BindValueChanged(beatmapChanged, true); } - [Test] - public void TestStoryboard() - { - AddStep("Restart", restart); - AddToggleStep("Passing", passing => - { - if (storyboard != null) storyboard.Passing = passing; - }); - } - - [Test] - public void TestStoryboardMissingVideo() - { - AddStep("Load storyboard with missing video", loadStoryboardNoVideo); - } - - [BackgroundDependencyLoader] - private void load() - { - Beatmap.ValueChanged += beatmapChanged; - } - - private void beatmapChanged(ValueChangedEvent e) - => loadStoryboard(e.NewValue); + private void beatmapChanged(ValueChangedEvent e) => loadStoryboard(e.NewValue); private void restart() { From f63d1ba612df01640d3abe79a1db5217a947f54a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 21 Aug 2020 18:52:53 +0900 Subject: [PATCH 0638/1782] Remove stray call to LoadTrack that was forgotten --- osu.Game/Screens/Menu/IntroScreen.cs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Menu/IntroScreen.cs b/osu.Game/Screens/Menu/IntroScreen.cs index 363933694d..884cbfe107 100644 --- a/osu.Game/Screens/Menu/IntroScreen.cs +++ b/osu.Game/Screens/Menu/IntroScreen.cs @@ -12,7 +12,6 @@ using osu.Framework.Screens; using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.IO.Archives; -using osu.Game.Overlays; using osu.Game.Screens.Backgrounds; using osu.Game.Skinning; using osuTK; @@ -61,9 +60,6 @@ namespace osu.Game.Screens.Menu [Resolved] private AudioManager audio { get; set; } - [Resolved] - private MusicController musicController { get; set; } - /// /// Whether the is provided by osu! resources, rather than a user beatmap. /// Only valid during or after . @@ -168,8 +164,8 @@ namespace osu.Game.Screens.Menu if (!resuming) { beatmap.Value = initialBeatmap; - Track = musicController.CurrentTrack; - UsingThemedIntro = !initialBeatmap.LoadTrack().IsDummyDevice; + Track = initialBeatmap.Track; + UsingThemedIntro = !initialBeatmap.Track.IsDummyDevice; logo.MoveTo(new Vector2(0.5f)); logo.ScaleTo(Vector2.One); From 42ee9b75df7a411fb2354ab2a47feafd288b815f Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 21 Aug 2020 19:38:59 +0900 Subject: [PATCH 0639/1782] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bartłomiej Dach --- .../Objects/Drawables/DrawableHoldNote.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs index e959509b96..40c5764a97 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs @@ -207,7 +207,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables }; // Pad the masking container to the starting position of the body piece (half-way under the head). - // This is required ot make the body start getting masked immediately as soon as the note is held. + // This is required to make the body start getting masked immediately as soon as the note is held. maskingContainer.Padding = new MarginPadding { Top = Direction.Value == ScrollingDirection.Up ? Head.Height / 2 : 0, @@ -218,13 +218,13 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables bodyPiece.Y = (Direction.Value == ScrollingDirection.Up ? 1 : -1) * Head.Height / 2; bodyPiece.Height = DrawHeight - Head.Height / 2 + Tail.Height / 2; - // As the note is being held, adjust the size of the fullSizeContainer. This has two effects: + // As the note is being held, adjust the size of the sizing container. This has two effects: // 1. The contained masking container will mask the body and ticks. // 2. The head note will move along with the new "head position" in the container. if (Head.IsHit && releaseTime == null) { - float heightDecrease = (float)(Math.Max(0, Time.Current - HitObject.StartTime) / HitObject.Duration); - sizingContainer.Height = Math.Clamp(1 - heightDecrease, 0, 1); + float remainingHeight = (float)(Math.Max(0, HitObject.GetEndTime() - Time.Current) / HitObject.Duration); + sizingContainer.Height = Math.Clamp(remainingHeight, 0, 1); } } From 8632c3adf0c1bc945aa26d14b7206db5ed37a69c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 21 Aug 2020 23:11:15 +0900 Subject: [PATCH 0640/1782] Fix hold notes bouncing with SV changes --- .../Objects/Drawables/DrawableHoldNote.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs index 40c5764a97..0712026ca6 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs @@ -223,8 +223,9 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables // 2. The head note will move along with the new "head position" in the container. if (Head.IsHit && releaseTime == null) { - float remainingHeight = (float)(Math.Max(0, HitObject.GetEndTime() - Time.Current) / HitObject.Duration); - sizingContainer.Height = Math.Clamp(remainingHeight, 0, 1); + // How far past the hit target this hold note is. Always a positive value. + float yOffset = Math.Max(0, Direction.Value == ScrollingDirection.Up ? -Y : Y); + sizingContainer.Height = Math.Clamp(1 - yOffset / DrawHeight, 0, 1); } } From b3338347b7fd99375bc0926c4c29beda38f1171c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 21 Aug 2020 23:56:27 +0900 Subject: [PATCH 0641/1782] Remove fade on successful hits --- .../Objects/Drawables/DrawableManiaHitObject.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs index a44d8b09aa..ab76a5b8f8 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs @@ -120,7 +120,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables break; case ArmedState.Hit: - this.FadeOut(150, Easing.OutQuint); + this.FadeOut(); break; } } From 88d50b6c4751e7fc87580b8e5bed753052017fa3 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Sat, 22 Aug 2020 00:15:37 +0900 Subject: [PATCH 0642/1782] Remove alpha mangling from LegacyDecoder --- osu.Game/Beatmaps/Formats/LegacyDecoder.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs index 44ef9bcacc..c15240a4f6 100644 --- a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs @@ -104,10 +104,6 @@ namespace osu.Game.Beatmaps.Formats try { byte alpha = split.Length == 4 ? byte.Parse(split[3]) : (byte)255; - - if (alpha == 0) - alpha = 255; - colour = new Color4(byte.Parse(split[0]), byte.Parse(split[1]), byte.Parse(split[2]), alpha); } catch From 2424fa08027ebe105e1102997e8379ec31528c0a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Sat, 22 Aug 2020 00:15:58 +0900 Subject: [PATCH 0643/1782] Add helper methods --- osu.Game/Skinning/LegacySkinExtensions.cs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/osu.Game/Skinning/LegacySkinExtensions.cs b/osu.Game/Skinning/LegacySkinExtensions.cs index bb46dc8b9f..088eae4bce 100644 --- a/osu.Game/Skinning/LegacySkinExtensions.cs +++ b/osu.Game/Skinning/LegacySkinExtensions.cs @@ -9,6 +9,7 @@ using osu.Framework.Graphics.Animations; using osu.Framework.Graphics.OpenGL.Textures; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; +using osuTK.Graphics; using static osu.Game.Skinning.LegacySkinConfiguration; namespace osu.Game.Skinning @@ -62,6 +63,21 @@ namespace osu.Game.Skinning } } + public static Color4 ToLegacyColour(this Color4 colour) + { + if (colour.A == 0) + colour.A = 1; + return colour; + } + + public static T WithInitialColour(this T drawable, Color4 colour) + where T : Drawable + { + drawable.Alpha = colour.A; + drawable.Colour = ToLegacyColour(colour); + return drawable; + } + public class SkinnableTextureAnimation : TextureAnimation { [Resolved(canBeNull: true)] From eaba32335327dc0a4f987f8b8f35bb89a01d51cb Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Sat, 22 Aug 2020 00:17:35 +0900 Subject: [PATCH 0644/1782] Update catch with legacy colour setters --- .../Skinning/CatchLegacySkinTransformer.cs | 8 +++++++- osu.Game.Rulesets.Catch/Skinning/LegacyFruitPiece.cs | 3 +-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Skinning/CatchLegacySkinTransformer.cs b/osu.Game.Rulesets.Catch/Skinning/CatchLegacySkinTransformer.cs index d929da1a29..5abd87d6f4 100644 --- a/osu.Game.Rulesets.Catch/Skinning/CatchLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Catch/Skinning/CatchLegacySkinTransformer.cs @@ -6,6 +6,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Skinning; using osuTK; +using osuTK.Graphics; namespace osu.Game.Rulesets.Catch.Skinning { @@ -61,7 +62,12 @@ namespace osu.Game.Rulesets.Catch.Skinning switch (lookup) { case CatchSkinColour colour: - return Source.GetConfig(new SkinCustomColourLookup(colour)); + var result = (Bindable)Source.GetConfig(new SkinCustomColourLookup(colour)); + if (result == null) + return null; + + result.Value = result.Value.ToLegacyColour(); + return (IBindable)result; } return Source.GetConfig(lookup); diff --git a/osu.Game.Rulesets.Catch/Skinning/LegacyFruitPiece.cs b/osu.Game.Rulesets.Catch/Skinning/LegacyFruitPiece.cs index 5be54d3882..c9dd1d1f3e 100644 --- a/osu.Game.Rulesets.Catch/Skinning/LegacyFruitPiece.cs +++ b/osu.Game.Rulesets.Catch/Skinning/LegacyFruitPiece.cs @@ -40,7 +40,6 @@ namespace osu.Game.Rulesets.Catch.Skinning colouredSprite = new Sprite { Texture = skin.GetTexture(lookupName), - Colour = drawableObject.AccentColour.Value, Anchor = Anchor.Centre, Origin = Anchor.Centre, }, @@ -76,7 +75,7 @@ namespace osu.Game.Rulesets.Catch.Skinning { base.LoadComplete(); - accentColour.BindValueChanged(colour => colouredSprite.Colour = colour.NewValue, true); + accentColour.BindValueChanged(colour => colouredSprite.Colour = colour.NewValue.ToLegacyColour(), true); } } } From 454564b18928a17525e12596ca38446844cb0600 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Sat, 22 Aug 2020 00:19:15 +0900 Subject: [PATCH 0645/1782] Update mania with legacy colour setters --- .../Skinning/LegacyColumnBackground.cs | 20 ++++++++----------- .../Skinning/LegacyHitTarget.cs | 2 +- 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyColumnBackground.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyColumnBackground.cs index 64a7641421..b97547bbc6 100644 --- a/osu.Game.Rulesets.Mania/Skinning/LegacyColumnBackground.cs +++ b/osu.Game.Rulesets.Mania/Skinning/LegacyColumnBackground.cs @@ -58,28 +58,24 @@ namespace osu.Game.Rulesets.Mania.Skinning InternalChildren = new Drawable[] { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = backgroundColour - }, - new Box + new Box { RelativeSizeAxes = Axes.Both }.WithInitialColour(backgroundColour), + new Container { RelativeSizeAxes = Axes.Y, Width = leftLineWidth, Scale = new Vector2(0.740f, 1), - Colour = lineColour, - Alpha = hasLeftLine ? 1 : 0 + Alpha = hasLeftLine ? 1 : 0, + Child = new Box { RelativeSizeAxes = Axes.Both }.WithInitialColour(lineColour) }, - new Box + new Container { Anchor = Anchor.TopRight, Origin = Anchor.TopRight, RelativeSizeAxes = Axes.Y, Width = rightLineWidth, Scale = new Vector2(0.740f, 1), - Colour = lineColour, - Alpha = hasRightLine ? 1 : 0 + Alpha = hasRightLine ? 1 : 0, + Child = new Box { RelativeSizeAxes = Axes.Both }.WithInitialColour(lineColour) }, lightContainer = new Container { @@ -90,7 +86,7 @@ namespace osu.Game.Rulesets.Mania.Skinning { Anchor = Anchor.BottomCentre, Origin = Anchor.BottomCentre, - Colour = lightColour, + Colour = lightColour.ToLegacyColour(), Texture = skin.GetTexture(lightImage), RelativeSizeAxes = Axes.X, Width = 1, diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyHitTarget.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyHitTarget.cs index d055ef3480..2177eaa5e6 100644 --- a/osu.Game.Rulesets.Mania/Skinning/LegacyHitTarget.cs +++ b/osu.Game.Rulesets.Mania/Skinning/LegacyHitTarget.cs @@ -56,7 +56,7 @@ namespace osu.Game.Rulesets.Mania.Skinning Anchor = Anchor.CentreLeft, RelativeSizeAxes = Axes.X, Height = 1, - Colour = lineColour, + Colour = lineColour.ToLegacyColour(), Alpha = showJudgementLine ? 0.9f : 0 } } From 16a2ab9dea4fd58e56eb00c017e61fce002b7ed2 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Sat, 22 Aug 2020 00:20:33 +0900 Subject: [PATCH 0646/1782] Update osu with legacy colour setters --- osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs | 3 +-- osu.Game.Rulesets.Osu/Skinning/LegacySliderBall.cs | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs b/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs index 0ab3e8825b..8a6beddb51 100644 --- a/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs +++ b/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs @@ -59,7 +59,6 @@ namespace osu.Game.Rulesets.Osu.Skinning hitCircleSprite = new Sprite { Texture = getTextureWithFallback(string.Empty), - Colour = drawableObject.AccentColour.Value, Anchor = Anchor.Centre, Origin = Anchor.Centre, }, @@ -107,7 +106,7 @@ namespace osu.Game.Rulesets.Osu.Skinning base.LoadComplete(); state.BindValueChanged(updateState, true); - accentColour.BindValueChanged(colour => hitCircleSprite.Colour = colour.NewValue, true); + accentColour.BindValueChanged(colour => hitCircleSprite.Colour = colour.NewValue.ToLegacyColour(), true); indexInCurrentCombo.BindValueChanged(index => hitCircleText.Text = (index.NewValue + 1).ToString(), true); } diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacySliderBall.cs b/osu.Game.Rulesets.Osu/Skinning/LegacySliderBall.cs index 0f586034d5..3b75fcc8a0 100644 --- a/osu.Game.Rulesets.Osu/Skinning/LegacySliderBall.cs +++ b/osu.Game.Rulesets.Osu/Skinning/LegacySliderBall.cs @@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Osu.Skinning [BackgroundDependencyLoader] private void load(ISkinSource skin, DrawableHitObject drawableObject) { - animationContent.Colour = skin.GetConfig(OsuSkinColour.SliderBall)?.Value ?? Color4.White; + var ballColour = skin.GetConfig(OsuSkinColour.SliderBall)?.Value ?? Color4.White; InternalChildren = new[] { @@ -39,7 +39,7 @@ namespace osu.Game.Rulesets.Osu.Skinning Texture = skin.GetTexture("sliderb-nd"), Colour = new Color4(5, 5, 5, 255), }, - animationContent.With(d => + animationContent.WithInitialColour(ballColour).With(d => { d.Anchor = Anchor.Centre; d.Origin = Anchor.Centre; From 9fbc5f3aeb546fc27572a4c511793d6dd91088ea Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Sat, 22 Aug 2020 00:23:08 +0900 Subject: [PATCH 0647/1782] Update taiko with legacy colour setters --- osu.Game.Rulesets.Taiko/Skinning/LegacyCirclePiece.cs | 2 +- osu.Game.Rulesets.Taiko/Skinning/LegacyDrumRoll.cs | 6 +++--- osu.Game.Rulesets.Taiko/Skinning/LegacyHit.cs | 5 +++-- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Skinning/LegacyCirclePiece.cs b/osu.Game.Rulesets.Taiko/Skinning/LegacyCirclePiece.cs index bfcf268c3d..ed69b529ed 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/LegacyCirclePiece.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/LegacyCirclePiece.cs @@ -90,7 +90,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning private void updateAccentColour() { - backgroundLayer.Colour = accentColour; + backgroundLayer.Colour = accentColour.ToLegacyColour(); } } } diff --git a/osu.Game.Rulesets.Taiko/Skinning/LegacyDrumRoll.cs b/osu.Game.Rulesets.Taiko/Skinning/LegacyDrumRoll.cs index 8223e3bc01..6bb8f9433e 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/LegacyDrumRoll.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/LegacyDrumRoll.cs @@ -76,9 +76,9 @@ namespace osu.Game.Rulesets.Taiko.Skinning private void updateAccentColour() { - headCircle.AccentColour = accentColour; - body.Colour = accentColour; - end.Colour = accentColour; + headCircle.AccentColour = accentColour.ToLegacyColour(); + body.Colour = accentColour.ToLegacyColour(); + end.Colour = accentColour.ToLegacyColour(); } } } diff --git a/osu.Game.Rulesets.Taiko/Skinning/LegacyHit.cs b/osu.Game.Rulesets.Taiko/Skinning/LegacyHit.cs index 656728f6e4..f36aae205a 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/LegacyHit.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/LegacyHit.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Game.Skinning; using osuTK.Graphics; namespace osu.Game.Rulesets.Taiko.Skinning @@ -18,9 +19,9 @@ namespace osu.Game.Rulesets.Taiko.Skinning [BackgroundDependencyLoader] private void load() { - AccentColour = component == TaikoSkinComponents.CentreHit + AccentColour = (component == TaikoSkinComponents.CentreHit ? new Color4(235, 69, 44, 255) - : new Color4(67, 142, 172, 255); + : new Color4(67, 142, 172, 255)).ToLegacyColour(); } } } From f89b6f44653112a86cc5df93be0d6e11ad0ad8d3 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Sat, 22 Aug 2020 00:52:53 +0900 Subject: [PATCH 0648/1782] Add xmldocs --- osu.Game/Skinning/LegacySkinExtensions.cs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/osu.Game/Skinning/LegacySkinExtensions.cs b/osu.Game/Skinning/LegacySkinExtensions.cs index 088eae4bce..ee7d74d7ec 100644 --- a/osu.Game/Skinning/LegacySkinExtensions.cs +++ b/osu.Game/Skinning/LegacySkinExtensions.cs @@ -63,6 +63,11 @@ namespace osu.Game.Skinning } } + /// + /// The resultant colour after setting a post-constructor colour in osu!stable. + /// + /// The to convert. + /// The converted . public static Color4 ToLegacyColour(this Color4 colour) { if (colour.A == 0) @@ -70,6 +75,16 @@ namespace osu.Game.Skinning return colour; } + /// + /// Equivalent of setting a colour in the constructor in osu!stable. + /// Doubles the alpha channel into and uses to set . + /// + /// + /// Beware: Any existing value in is overwritten. + /// + /// The to set the "InitialColour" of. + /// The to set. + /// The given . public static T WithInitialColour(this T drawable, Color4 colour) where T : Drawable { From 356c67f00d778e1f6d2535eb534a864d6b04caa4 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Sat, 22 Aug 2020 00:55:03 +0900 Subject: [PATCH 0649/1782] Remove outdated/wrong test --- osu.Game.Tests/Resources/skin-zero-alpha-colour.ini | 5 ----- osu.Game.Tests/Skins/LegacySkinDecoderTest.cs | 10 ---------- 2 files changed, 15 deletions(-) delete mode 100644 osu.Game.Tests/Resources/skin-zero-alpha-colour.ini diff --git a/osu.Game.Tests/Resources/skin-zero-alpha-colour.ini b/osu.Game.Tests/Resources/skin-zero-alpha-colour.ini deleted file mode 100644 index 3c0dae6b13..0000000000 --- a/osu.Game.Tests/Resources/skin-zero-alpha-colour.ini +++ /dev/null @@ -1,5 +0,0 @@ -[General] -Version: latest - -[Colours] -Combo1: 255,255,255,0 \ No newline at end of file diff --git a/osu.Game.Tests/Skins/LegacySkinDecoderTest.cs b/osu.Game.Tests/Skins/LegacySkinDecoderTest.cs index c408d2f182..aedf26ee75 100644 --- a/osu.Game.Tests/Skins/LegacySkinDecoderTest.cs +++ b/osu.Game.Tests/Skins/LegacySkinDecoderTest.cs @@ -108,15 +108,5 @@ namespace osu.Game.Tests.Skins using (var stream = new LineBufferedReader(resStream)) Assert.That(decoder.Decode(stream).LegacyVersion, Is.EqualTo(1.0m)); } - - [Test] - public void TestDecodeColourWithZeroAlpha() - { - var decoder = new LegacySkinDecoder(); - - using (var resStream = TestResources.OpenResource("skin-zero-alpha-colour.ini")) - using (var stream = new LineBufferedReader(resStream)) - Assert.That(decoder.Decode(stream).ComboColours[0].A, Is.EqualTo(1.0f)); - } } } From 08078b9513895806f9df2a08922fc7c4bf826fd9 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Sat, 22 Aug 2020 00:56:29 +0900 Subject: [PATCH 0650/1782] Rename method to remove "InitialColour" namings --- osu.Game.Rulesets.Mania/Skinning/LegacyColumnBackground.cs | 6 +++--- osu.Game.Rulesets.Osu/Skinning/LegacySliderBall.cs | 2 +- osu.Game/Skinning/LegacySkinExtensions.cs | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyColumnBackground.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyColumnBackground.cs index b97547bbc6..54a16b840f 100644 --- a/osu.Game.Rulesets.Mania/Skinning/LegacyColumnBackground.cs +++ b/osu.Game.Rulesets.Mania/Skinning/LegacyColumnBackground.cs @@ -58,14 +58,14 @@ namespace osu.Game.Rulesets.Mania.Skinning InternalChildren = new Drawable[] { - new Box { RelativeSizeAxes = Axes.Both }.WithInitialColour(backgroundColour), + new Box { RelativeSizeAxes = Axes.Both }.WithLegacyColour(backgroundColour), new Container { RelativeSizeAxes = Axes.Y, Width = leftLineWidth, Scale = new Vector2(0.740f, 1), Alpha = hasLeftLine ? 1 : 0, - Child = new Box { RelativeSizeAxes = Axes.Both }.WithInitialColour(lineColour) + Child = new Box { RelativeSizeAxes = Axes.Both }.WithLegacyColour(lineColour) }, new Container { @@ -75,7 +75,7 @@ namespace osu.Game.Rulesets.Mania.Skinning Width = rightLineWidth, Scale = new Vector2(0.740f, 1), Alpha = hasRightLine ? 1 : 0, - Child = new Box { RelativeSizeAxes = Axes.Both }.WithInitialColour(lineColour) + Child = new Box { RelativeSizeAxes = Axes.Both }.WithLegacyColour(lineColour) }, lightContainer = new Container { diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacySliderBall.cs b/osu.Game.Rulesets.Osu/Skinning/LegacySliderBall.cs index 3b75fcc8a0..27dec1b691 100644 --- a/osu.Game.Rulesets.Osu/Skinning/LegacySliderBall.cs +++ b/osu.Game.Rulesets.Osu/Skinning/LegacySliderBall.cs @@ -39,7 +39,7 @@ namespace osu.Game.Rulesets.Osu.Skinning Texture = skin.GetTexture("sliderb-nd"), Colour = new Color4(5, 5, 5, 255), }, - animationContent.WithInitialColour(ballColour).With(d => + animationContent.WithLegacyColour(ballColour).With(d => { d.Anchor = Anchor.Centre; d.Origin = Anchor.Centre; diff --git a/osu.Game/Skinning/LegacySkinExtensions.cs b/osu.Game/Skinning/LegacySkinExtensions.cs index ee7d74d7ec..7420f82f04 100644 --- a/osu.Game/Skinning/LegacySkinExtensions.cs +++ b/osu.Game/Skinning/LegacySkinExtensions.cs @@ -82,10 +82,10 @@ namespace osu.Game.Skinning /// /// Beware: Any existing value in is overwritten. /// - /// The to set the "InitialColour" of. + /// The to set the colour of. /// The to set. /// The given . - public static T WithInitialColour(this T drawable, Color4 colour) + public static T WithLegacyColour(this T drawable, Color4 colour) where T : Drawable { drawable.Alpha = colour.A; From 891f5cb130b50748cc517369cd33f1f6cf91aca1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 21 Aug 2020 20:00:20 +0200 Subject: [PATCH 0651/1782] Add padding to mania column borders to match stable --- .../Skinning/LegacyColumnBackground.cs | 50 +++++++++++++------ 1 file changed, 34 insertions(+), 16 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyColumnBackground.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyColumnBackground.cs index 64a7641421..f9286b5095 100644 --- a/osu.Game.Rulesets.Mania/Skinning/LegacyColumnBackground.cs +++ b/osu.Game.Rulesets.Mania/Skinning/LegacyColumnBackground.cs @@ -8,6 +8,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Bindings; +using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Skinning; using osuTK; @@ -20,9 +21,12 @@ namespace osu.Game.Rulesets.Mania.Skinning private readonly IBindable direction = new Bindable(); private readonly bool isLastColumn; + private Container borderLineContainer; private Container lightContainer; private Sprite light; + private float hitPosition; + public LegacyColumnBackground(bool isLastColumn) { this.isLastColumn = isLastColumn; @@ -44,6 +48,9 @@ namespace osu.Game.Rulesets.Mania.Skinning bool hasRightLine = rightLineWidth > 0 && skin.GetConfig(LegacySkinConfiguration.LegacySetting.Version)?.Value >= 2.4m || isLastColumn; + hitPosition = GetColumnSkinConfig(skin, LegacyManiaSkinConfigurationLookups.HitPosition)?.Value + ?? Stage.HIT_TARGET_POSITION; + float lightPosition = GetColumnSkinConfig(skin, LegacyManiaSkinConfigurationLookups.LightPosition)?.Value ?? 0; @@ -63,23 +70,30 @@ namespace osu.Game.Rulesets.Mania.Skinning RelativeSizeAxes = Axes.Both, Colour = backgroundColour }, - new Box + borderLineContainer = new Container { - RelativeSizeAxes = Axes.Y, - Width = leftLineWidth, - Scale = new Vector2(0.740f, 1), - Colour = lineColour, - Alpha = hasLeftLine ? 1 : 0 - }, - new Box - { - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - RelativeSizeAxes = Axes.Y, - Width = rightLineWidth, - Scale = new Vector2(0.740f, 1), - Colour = lineColour, - Alpha = hasRightLine ? 1 : 0 + RelativeSizeAxes = Axes.Both, + Children = new[] + { + new Box + { + RelativeSizeAxes = Axes.Y, + Width = leftLineWidth, + Scale = new Vector2(0.740f, 1), + Colour = lineColour, + Alpha = hasLeftLine ? 1 : 0 + }, + new Box + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + RelativeSizeAxes = Axes.Y, + Width = rightLineWidth, + Scale = new Vector2(0.740f, 1), + Colour = lineColour, + Alpha = hasRightLine ? 1 : 0 + } + } }, lightContainer = new Container { @@ -109,11 +123,15 @@ namespace osu.Game.Rulesets.Mania.Skinning { lightContainer.Anchor = Anchor.TopCentre; lightContainer.Scale = new Vector2(1, -1); + + borderLineContainer.Padding = new MarginPadding { Top = hitPosition }; } else { lightContainer.Anchor = Anchor.BottomCentre; lightContainer.Scale = Vector2.One; + + borderLineContainer.Padding = new MarginPadding { Bottom = hitPosition }; } } From 809a61afcbb07daa8683191fbafdd16cc6204d5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 21 Aug 2020 23:05:19 +0200 Subject: [PATCH 0652/1782] Adjust key binding panel tests to not rely on row indices --- .../Settings/TestSceneKeyBindingPanel.cs | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs b/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs index e06b3a8a7e..987a4a67fe 100644 --- a/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs +++ b/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs @@ -68,22 +68,22 @@ namespace osu.Game.Tests.Visual.Settings [Test] public void TestClearButtonOnBindings() { - KeyBindingRow backBindingRow = null; + KeyBindingRow multiBindingRow = null; - AddStep("click back binding row", () => + AddStep("click first row with two bindings", () => { - backBindingRow = panel.ChildrenOfType().ElementAt(10); - InputManager.MoveMouseTo(backBindingRow); + multiBindingRow = panel.ChildrenOfType().First(row => row.Defaults.Count() > 1); + InputManager.MoveMouseTo(multiBindingRow); InputManager.Click(MouseButton.Left); }); clickClearButton(); - AddAssert("first binding cleared", () => string.IsNullOrEmpty(backBindingRow.ChildrenOfType().First().Text.Text)); + AddAssert("first binding cleared", () => string.IsNullOrEmpty(multiBindingRow.ChildrenOfType().First().Text.Text)); AddStep("click second binding", () => { - var target = backBindingRow.ChildrenOfType().ElementAt(1); + var target = multiBindingRow.ChildrenOfType().ElementAt(1); InputManager.MoveMouseTo(target); InputManager.Click(MouseButton.Left); @@ -91,13 +91,13 @@ namespace osu.Game.Tests.Visual.Settings clickClearButton(); - AddAssert("second binding cleared", () => string.IsNullOrEmpty(backBindingRow.ChildrenOfType().ElementAt(1).Text.Text)); + AddAssert("second binding cleared", () => string.IsNullOrEmpty(multiBindingRow.ChildrenOfType().ElementAt(1).Text.Text)); void clickClearButton() { AddStep("click clear button", () => { - var clearButton = backBindingRow.ChildrenOfType().Single(); + var clearButton = multiBindingRow.ChildrenOfType().Single(); InputManager.MoveMouseTo(clearButton); InputManager.Click(MouseButton.Left); @@ -108,20 +108,20 @@ namespace osu.Game.Tests.Visual.Settings [Test] public void TestClickRowSelectsFirstBinding() { - KeyBindingRow backBindingRow = null; + KeyBindingRow multiBindingRow = null; - AddStep("click back binding row", () => + AddStep("click first row with two bindings", () => { - backBindingRow = panel.ChildrenOfType().ElementAt(10); - InputManager.MoveMouseTo(backBindingRow); + multiBindingRow = panel.ChildrenOfType().First(row => row.Defaults.Count() > 1); + InputManager.MoveMouseTo(multiBindingRow); InputManager.Click(MouseButton.Left); }); - AddAssert("first binding selected", () => backBindingRow.ChildrenOfType().First().IsBinding); + AddAssert("first binding selected", () => multiBindingRow.ChildrenOfType().First().IsBinding); AddStep("click second binding", () => { - var target = backBindingRow.ChildrenOfType().ElementAt(1); + var target = multiBindingRow.ChildrenOfType().ElementAt(1); InputManager.MoveMouseTo(target); InputManager.Click(MouseButton.Left); @@ -129,12 +129,12 @@ namespace osu.Game.Tests.Visual.Settings AddStep("click back binding row", () => { - backBindingRow = panel.ChildrenOfType().ElementAt(10); - InputManager.MoveMouseTo(backBindingRow); + multiBindingRow = panel.ChildrenOfType().ElementAt(10); + InputManager.MoveMouseTo(multiBindingRow); InputManager.Click(MouseButton.Left); }); - AddAssert("first binding selected", () => backBindingRow.ChildrenOfType().First().IsBinding); + AddAssert("first binding selected", () => multiBindingRow.ChildrenOfType().First().IsBinding); } } } From 0b6185cd14c18b32a031ef0b8d67b00ea6eef134 Mon Sep 17 00:00:00 2001 From: Keijia Date: Sat, 22 Aug 2020 01:09:35 +0300 Subject: [PATCH 0653/1782] add "hp" filter keyword --- osu.Game/Screens/Select/FilterQueryParser.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/FilterQueryParser.cs b/osu.Game/Screens/Select/FilterQueryParser.cs index 89afc729fe..b7bcf99ce0 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|creator|artist)(?[=:><]+)(?("".*"")|(\S*))", + @"\b(?stars|ar|dr|hp|cs|divisor|length|objects|bpm|status|creator|artist)(?[=:><]+)(?("".*"")|(\S*))", RegexOptions.Compiled | RegexOptions.IgnoreCase); internal static void ApplyQueries(FilterCriteria criteria, string query) @@ -46,6 +46,10 @@ namespace osu.Game.Screens.Select updateCriteriaRange(ref criteria.DrainRate, op, dr, 0.1f / 2); break; + case "hp" 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; From f9fe37a8a51aba8955bcf3a6e5d7169cd480adbb Mon Sep 17 00:00:00 2001 From: Keijia Date: Sat, 22 Aug 2020 01:54:01 +0300 Subject: [PATCH 0654/1782] Added test for "hp" filter keyword --- .../NonVisual/Filtering/FilterQueryParserTest.cs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs index 7b2913b817..d15682b1eb 100644 --- a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs +++ b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs @@ -60,7 +60,7 @@ namespace osu.Game.Tests.NonVisual.Filtering } [Test] - public void TestApplyDrainRateQueries() + public void TestApplyDrainRateQueriesByDrKeyword() { const string query = "dr>2 quite specific dr<:6"; var filterCriteria = new FilterCriteria(); @@ -73,6 +73,20 @@ namespace osu.Game.Tests.NonVisual.Filtering Assert.Less(filterCriteria.DrainRate.Min, 6.1f); } + [Test] + public void TestApplyDrainRateQueriesByHpKeyword() + { + const string query = "hp>2 quite specific hp<=6"; + var filterCriteria = new FilterCriteria(); + FilterQueryParser.ApplyQueries(filterCriteria, query); + Assert.AreEqual("quite specific", filterCriteria.SearchText.Trim()); + Assert.AreEqual(2, filterCriteria.SearchTerms.Length); + 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] public void TestApplyBPMQueries() { From b5b2e523ad3493f059fc33e15c810a98995d1a0d Mon Sep 17 00:00:00 2001 From: Keijia Date: Sat, 22 Aug 2020 12:10:31 +0300 Subject: [PATCH 0655/1782] change switch cases --- osu.Game/Screens/Select/FilterQueryParser.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/osu.Game/Screens/Select/FilterQueryParser.cs b/osu.Game/Screens/Select/FilterQueryParser.cs index b7bcf99ce0..39fa4f777d 100644 --- a/osu.Game/Screens/Select/FilterQueryParser.cs +++ b/osu.Game/Screens/Select/FilterQueryParser.cs @@ -43,10 +43,7 @@ namespace osu.Game.Screens.Select break; case "dr" when parseFloatWithPoint(value, out var dr): - updateCriteriaRange(ref criteria.DrainRate, op, dr, 0.1f / 2); - break; - - case "hp" when parseFloatWithPoint(value, out var dr): + case "hp" when parseFloatWithPoint(value, out dr): updateCriteriaRange(ref criteria.DrainRate, op, dr, 0.1f / 2); break; From 7ae45b29dbc82c98012c4525bbf7a13d10bef161 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 22 Aug 2020 12:20:50 +0300 Subject: [PATCH 0656/1782] Finish internal counter transformation regardless of the combo --- osu.Game.Rulesets.Catch/Skinning/LegacyComboCounter.cs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Skinning/LegacyComboCounter.cs b/osu.Game.Rulesets.Catch/Skinning/LegacyComboCounter.cs index 8ea06688cb..0d9df4f9a0 100644 --- a/osu.Game.Rulesets.Catch/Skinning/LegacyComboCounter.cs +++ b/osu.Game.Rulesets.Catch/Skinning/LegacyComboCounter.cs @@ -27,7 +27,6 @@ namespace osu.Game.Rulesets.Catch.Skinning private readonly float fontOverlap; private readonly LegacyRollingCounter counter; - private LegacyRollingCounter lastExplosion; public LegacyComboCounter(ISkin skin, string fontName, float fontOverlap) { @@ -70,8 +69,14 @@ namespace osu.Game.Rulesets.Catch.Skinning public void DisplayInitialCombo(int combo) => updateCombo(combo, null, true); public void UpdateCombo(int combo, Color4? hitObjectColour) => updateCombo(combo, hitObjectColour, false); + private LegacyRollingCounter lastExplosion; + private void updateCombo(int combo, Color4? hitObjectColour, bool immediate) { + // There may still be existing transforms to the counter (including value change after 250ms), + // finish them immediately before new transforms. + counter.FinishTransforms(); + // Combo fell to zero, roll down and fade out the counter. if (combo == 0) { @@ -83,8 +88,7 @@ namespace osu.Game.Rulesets.Catch.Skinning return; } - // There may still be previous transforms being applied, finish them and remove explosion. - FinishTransforms(true); + // Remove last explosion to not conflict with the upcoming one. if (lastExplosion != null) RemoveInternal(lastExplosion); From 7e838c80420d35a9c633cfc038fd3afd7ecf728a Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 22 Aug 2020 13:07:15 +0300 Subject: [PATCH 0657/1782] Add comment explaining why direct string lookups are used --- osu.Game/Tests/Visual/SkinnableTestScene.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Tests/Visual/SkinnableTestScene.cs b/osu.Game/Tests/Visual/SkinnableTestScene.cs index 49ba02fdea..a9e3d36ca0 100644 --- a/osu.Game/Tests/Visual/SkinnableTestScene.cs +++ b/osu.Game/Tests/Visual/SkinnableTestScene.cs @@ -160,6 +160,7 @@ namespace osu.Game.Tests.Visual { this.extrapolateAnimations = extrapolateAnimations; + // Use a direct string lookup for simplicity, as they're legacy settings and not worth creating enums for them. legacyFontPrefixes.Add(GetConfig("HitCirclePrefix")?.Value ?? "default"); legacyFontPrefixes.Add(GetConfig("ScorePrefix")?.Value ?? "score"); legacyFontPrefixes.Add(GetConfig("ComboPrefix")?.Value ?? "score"); From b72f06fef68a219b599671cefe3ce2e2b252d3e9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 22 Aug 2020 19:42:34 +0900 Subject: [PATCH 0658/1782] Centralise and clarify LoadTrack documentation --- osu.Game/Beatmaps/IWorkingBeatmap.cs | 10 +++++++++- osu.Game/Beatmaps/WorkingBeatmap.cs | 4 ---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/osu.Game/Beatmaps/IWorkingBeatmap.cs b/osu.Game/Beatmaps/IWorkingBeatmap.cs index 88d73fd7c4..bcd94d76fd 100644 --- a/osu.Game/Beatmaps/IWorkingBeatmap.cs +++ b/osu.Game/Beatmaps/IWorkingBeatmap.cs @@ -56,8 +56,16 @@ namespace osu.Game.Beatmaps IBeatmap GetPlayableBeatmap(RulesetInfo ruleset, IReadOnlyList mods = null, TimeSpan? timeout = null); /// - /// Retrieves the which this provides. + /// Load a new audio track instance for this beatmap. This should be called once before accessing . + /// The caller of this method is responsible for the lifetime of the track. /// + /// + /// In a standard game context, the loading of the track is managed solely by MusicController, which will + /// automatically load the track of the current global IBindable WorkingBeatmap. + /// As such, this method should only be called in very special scenarios, such as external tests or apps which are + /// outside of the game context. + /// + /// A fresh track instance, which will also be available via . Track LoadTrack(); } } diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs index 6a89739e6f..6a161e6e04 100644 --- a/osu.Game/Beatmaps/WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/WorkingBeatmap.cs @@ -261,10 +261,6 @@ namespace osu.Game.Beatmaps private Track loadedTrack; - /// - /// Load a new audio track instance for this beatmap. - /// - /// A fresh track instance, which will also be available via . [NotNull] public Track LoadTrack() => loadedTrack = GetBeatmapTrack() ?? GetVirtualTrack(1000); From db5226042747b7e5078903cdb14d3a36f9e4af73 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 22 Aug 2020 19:44:54 +0900 Subject: [PATCH 0659/1782] Rename and clarify comment regarding "previous" track disposal --- osu.Game/Overlays/MusicController.cs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index 112026d9e2..6d5b5d43cd 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -331,10 +331,10 @@ namespace osu.Game.Overlays { var lastTrack = CurrentTrack; - var newTrack = new DrawableTrack(current.LoadTrack()); - newTrack.Completed += () => onTrackCompleted(current); + var queuedTrack = new DrawableTrack(current.LoadTrack()); + queuedTrack.Completed += () => onTrackCompleted(current); - CurrentTrack = newTrack; + CurrentTrack = queuedTrack; // At this point we may potentially be in an async context from tests. This is extremely dangerous but we have to make do for now. // CurrentTrack is immediately updated above for situations where a immediate knowledge about the new track is required, @@ -343,12 +343,13 @@ namespace osu.Game.Overlays { lastTrack.Expire(); - if (newTrack == CurrentTrack) - AddInternal(newTrack); + if (queuedTrack == CurrentTrack) + AddInternal(queuedTrack); else { - // If the track has changed via changeTrack() being called multiple times in a single update, force disposal on the old track. - newTrack.Dispose(); + // If the track has changed since the call to changeTrack, it is safe to dispose the + // queued track rather than consume it. + queuedTrack.Dispose(); } }); } From 122265ff0e233a72a48a329370e8f6f8c1fde6e3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 22 Aug 2020 19:47:05 +0900 Subject: [PATCH 0660/1782] Revert non-track usage --- osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs index c6bfdda698..c617950c64 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs @@ -86,7 +86,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline track = b.NewValue.Track; // todo: i don't think this is safe, the track may not be loaded yet. - if (b.NewValue.Track.Length > 0) + if (track.Length > 0) { MaxZoom = getZoomLevelForVisibleMilliseconds(500); MinZoom = getZoomLevelForVisibleMilliseconds(10000); From fafdbb0a81ae98b0070f24f48622494aa2da6f0e Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 22 Aug 2020 17:26:54 +0300 Subject: [PATCH 0661/1782] Adjust recently added inline comment MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bartłomiej Dach --- osu.Game/Tests/Visual/SkinnableTestScene.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Tests/Visual/SkinnableTestScene.cs b/osu.Game/Tests/Visual/SkinnableTestScene.cs index a9e3d36ca0..58e0b23fab 100644 --- a/osu.Game/Tests/Visual/SkinnableTestScene.cs +++ b/osu.Game/Tests/Visual/SkinnableTestScene.cs @@ -160,7 +160,7 @@ namespace osu.Game.Tests.Visual { this.extrapolateAnimations = extrapolateAnimations; - // Use a direct string lookup for simplicity, as they're legacy settings and not worth creating enums for them. + // use a direct string lookup instead of enum to avoid having to reference ruleset assemblies. legacyFontPrefixes.Add(GetConfig("HitCirclePrefix")?.Value ?? "default"); legacyFontPrefixes.Add(GetConfig("ScorePrefix")?.Value ?? "score"); legacyFontPrefixes.Add(GetConfig("ComboPrefix")?.Value ?? "score"); From ec99fcd7ab2d7ee681677cb4cb082727448f3afa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 22 Aug 2020 17:10:31 +0200 Subject: [PATCH 0662/1782] Avoid passing down rhythm list every time --- .../Preprocessing/TaikoDifficultyHitObject.cs | 23 ++++++++++++++----- .../Difficulty/TaikoDifficultyCalculator.cs | 15 +----------- 2 files changed, 18 insertions(+), 20 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/TaikoDifficultyHitObject.cs b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/TaikoDifficultyHitObject.cs index 81b304af13..e52f616371 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/TaikoDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/TaikoDifficultyHitObject.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.Game.Rulesets.Difficulty.Preprocessing; using osu.Game.Rulesets.Objects; @@ -19,23 +18,35 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing public readonly int ObjectIndex; - public TaikoDifficultyHitObject(HitObject hitObject, HitObject lastObject, HitObject lastLastObject, double clockRate, int objectIndex, - IEnumerable commonRhythms) + public TaikoDifficultyHitObject(HitObject hitObject, HitObject lastObject, HitObject lastLastObject, double clockRate, int objectIndex) : base(hitObject, lastObject, clockRate) { var currentHit = hitObject as Hit; double prevLength = (lastObject.StartTime - lastLastObject.StartTime) / clockRate; - Rhythm = getClosestRhythm(DeltaTime / prevLength, commonRhythms); + Rhythm = getClosestRhythm(DeltaTime / prevLength); HitType = currentHit?.Type; ObjectIndex = objectIndex; } - private TaikoDifficultyHitObjectRhythm getClosestRhythm(double ratio, IEnumerable commonRhythms) + private static readonly TaikoDifficultyHitObjectRhythm[] common_rhythms = { - return commonRhythms.OrderBy(x => Math.Abs(x.Ratio - ratio)).First(); + new TaikoDifficultyHitObjectRhythm(1, 1, 0.0), + new TaikoDifficultyHitObjectRhythm(2, 1, 0.3), + new TaikoDifficultyHitObjectRhythm(1, 2, 0.5), + new TaikoDifficultyHitObjectRhythm(3, 1, 0.3), + new TaikoDifficultyHitObjectRhythm(1, 3, 0.35), + new TaikoDifficultyHitObjectRhythm(3, 2, 0.6), + new TaikoDifficultyHitObjectRhythm(2, 3, 0.4), + new TaikoDifficultyHitObjectRhythm(5, 4, 0.5), + new TaikoDifficultyHitObjectRhythm(4, 5, 0.7) + }; + + private TaikoDifficultyHitObjectRhythm getClosestRhythm(double ratio) + { + return common_rhythms.OrderBy(x => Math.Abs(x.Ratio - ratio)).First(); } } } diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs index d3ff0b95ee..961a2dfcda 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs @@ -24,19 +24,6 @@ namespace osu.Game.Rulesets.Taiko.Difficulty private const double colour_skill_multiplier = 0.01; private const double stamina_skill_multiplier = 0.02; - private readonly TaikoDifficultyHitObjectRhythm[] commonRhythms = - { - new TaikoDifficultyHitObjectRhythm(1, 1, 0.0), - new TaikoDifficultyHitObjectRhythm(2, 1, 0.3), - new TaikoDifficultyHitObjectRhythm(1, 2, 0.5), - new TaikoDifficultyHitObjectRhythm(3, 1, 0.3), - new TaikoDifficultyHitObjectRhythm(1, 3, 0.35), - new TaikoDifficultyHitObjectRhythm(3, 2, 0.6), - new TaikoDifficultyHitObjectRhythm(2, 3, 0.4), - new TaikoDifficultyHitObjectRhythm(5, 4, 0.5), - new TaikoDifficultyHitObjectRhythm(4, 5, 0.7) - }; - public TaikoDifficultyCalculator(Ruleset ruleset, WorkingBeatmap beatmap) : base(ruleset, beatmap) { @@ -135,7 +122,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty { taikoDifficultyHitObjects.Add( new TaikoDifficultyHitObject( - beatmap.HitObjects[i], beatmap.HitObjects[i - 1], beatmap.HitObjects[i - 2], clockRate, i, commonRhythms + beatmap.HitObjects[i], beatmap.HitObjects[i - 1], beatmap.HitObjects[i - 2], clockRate, i ) ); } From cb3fef76161d8cddc1bf73f99953cb3175e33fc7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 22 Aug 2020 17:15:08 +0200 Subject: [PATCH 0663/1782] Inline same parity penalty --- osu.Game.Rulesets.Taiko/Difficulty/Skills/Colour.cs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Colour.cs b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Colour.cs index dd8b536afc..0453882f45 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Colour.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Colour.cs @@ -54,8 +54,8 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills else if ((monoHistory[^1] + currentMonoLength) % 2 == 0) { // The last streak in the history is guaranteed to be a different type to the current streak. - // If the total number of notes in the two streaks is even, apply a penalty. - objectStrain *= sameParityPenalty(); + // If the total number of notes in the two streaks is even, nullify this object's strain. + objectStrain = 0.0; } objectStrain *= repetitionPenalties(); @@ -70,11 +70,6 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills return objectStrain; } - /// - /// The penalty to apply when the total number of notes in the two most recent colour streaks is even. - /// - private double sameParityPenalty() => 0.0; - /// /// The penalty to apply due to the length of repetition in colour streaks. /// From bcf3cd56574338f9ac2005854e1803a704abc439 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 22 Aug 2020 17:24:51 +0200 Subject: [PATCH 0664/1782] Remove unnecessary yield iteration --- .../Difficulty/TaikoDifficultyCalculator.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs index 961a2dfcda..7a99abdac6 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs @@ -129,8 +129,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty } new StaminaCheeseDetector().FindCheese(taikoDifficultyHitObjects); - foreach (var hitobject in taikoDifficultyHitObjects) - yield return hitobject; + return taikoDifficultyHitObjects; } protected override Skill[] CreateSkills(IBeatmap beatmap) => new Skill[] From 7e2bef3b9fce0fffb8feb2fa0a4293a1714343bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 22 Aug 2020 17:34:08 +0200 Subject: [PATCH 0665/1782] Split conditional for readability --- .../Difficulty/TaikoDifficultyCalculator.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs index 7a99abdac6..cbbef6e957 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs @@ -118,7 +118,10 @@ namespace osu.Game.Rulesets.Taiko.Difficulty for (int i = 2; i < beatmap.HitObjects.Count; i++) { // Check for negative durations - if (beatmap.HitObjects[i].StartTime > beatmap.HitObjects[i - 1].StartTime && beatmap.HitObjects[i - 1].StartTime > beatmap.HitObjects[i - 2].StartTime) + var currentAfterLast = beatmap.HitObjects[i].StartTime > beatmap.HitObjects[i - 1].StartTime; + var lastAfterSecondLast = beatmap.HitObjects[i - 1].StartTime > beatmap.HitObjects[i - 2].StartTime; + + if (currentAfterLast && lastAfterSecondLast) { taikoDifficultyHitObjects.Add( new TaikoDifficultyHitObject( From 8ace7df0fde9c2480a0f68ba06165e04fd18529d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 22 Aug 2020 17:51:35 +0200 Subject: [PATCH 0666/1782] Reorder members for better readability --- .../Preprocessing/StaminaCheeseDetector.cs | 10 +- .../Preprocessing/TaikoDifficultyHitObject.cs | 7 +- .../Difficulty/Skills/Colour.cs | 14 +- .../Difficulty/Skills/Rhythm.cs | 68 ++++--- .../Difficulty/Skills/Stamina.cs | 38 ++-- .../Difficulty/TaikoDifficultyCalculator.cs | 181 +++++++++--------- 6 files changed, 158 insertions(+), 160 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/StaminaCheeseDetector.cs b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/StaminaCheeseDetector.cs index 3f952d8317..ef5f4433bf 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/StaminaCheeseDetector.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/StaminaCheeseDetector.cs @@ -13,11 +13,15 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing private const int roll_min_repetitions = 12; private const int tl_min_repetitions = 16; - private List hitObjects; + private readonly List hitObjects; - public void FindCheese(List difficultyHitObjects) + public StaminaCheeseDetector(List hitObjects) + { + this.hitObjects = hitObjects; + } + + public void FindCheese() { - hitObjects = difficultyHitObjects; findRolls(3); findRolls(4); findTlTap(0, HitType.Rim); diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/TaikoDifficultyHitObject.cs b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/TaikoDifficultyHitObject.cs index e52f616371..5f7f8040c7 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/TaikoDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/TaikoDifficultyHitObject.cs @@ -13,11 +13,10 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing { public readonly TaikoDifficultyHitObjectRhythm Rhythm; public readonly HitType? HitType; + public readonly int ObjectIndex; public bool StaminaCheese; - public readonly int ObjectIndex; - public TaikoDifficultyHitObject(HitObject hitObject, HitObject lastObject, HitObject lastLastObject, double clockRate, int objectIndex) : base(hitObject, lastObject, clockRate) { @@ -45,8 +44,6 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing }; private TaikoDifficultyHitObjectRhythm getClosestRhythm(double ratio) - { - return common_rhythms.OrderBy(x => Math.Abs(x.Ratio - ratio)).First(); - } + => common_rhythms.OrderBy(x => Math.Abs(x.Ratio - ratio)).First(); } } diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Colour.cs b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Colour.cs index 0453882f45..1adbf272b3 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Colour.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Colour.cs @@ -12,11 +12,16 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills { public class Colour : Skill { - private const int mono_history_max_length = 5; - protected override double SkillMultiplier => 1; protected override double StrainDecayBase => 0.4; + private const int mono_history_max_length = 5; + + /// + /// List of the last most recent mono patterns, with the most recent at the end of the list. + /// + private readonly LimitedCapacityQueue monoHistory = new LimitedCapacityQueue(mono_history_max_length); + private HitType? previousHitType; /// @@ -24,11 +29,6 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills /// private int currentMonoLength = 1; - /// - /// List of the last most recent mono patterns, with the most recent at the end of the list. - /// - private readonly LimitedCapacityQueue monoHistory = new LimitedCapacityQueue(mono_history_max_length); - protected override double StrainValueOf(DifficultyHitObject current) { if (!(current.LastObject is Hit && current.BaseObject is Hit && current.DeltaTime < 1000)) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Rhythm.cs b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Rhythm.cs index 6bb2eaf06a..f37a4c3f65 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Rhythm.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Rhythm.cs @@ -14,17 +14,43 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills { protected override double SkillMultiplier => 10; protected override double StrainDecayBase => 0; - private const double strain_decay = 0.96; - private double currentStrain; - private readonly LimitedCapacityQueue rhythmHistory = new LimitedCapacityQueue(rhythm_history_max_length); + private const double strain_decay = 0.96; private const int rhythm_history_max_length = 8; + private readonly LimitedCapacityQueue rhythmHistory = new LimitedCapacityQueue(rhythm_history_max_length); + + private double currentStrain; private int notesSinceRhythmChange; - private double repetitionPenalty(int notesSince) + protected override double StrainValueOf(DifficultyHitObject current) { - return Math.Min(1.0, 0.032 * notesSince); + if (!(current.BaseObject is Hit)) + { + resetRhythmStrain(); + return 0.0; + } + + currentStrain *= strain_decay; + + TaikoDifficultyHitObject hitobject = (TaikoDifficultyHitObject)current; + notesSinceRhythmChange += 1; + + if (hitobject.Rhythm.Difficulty == 0.0) + { + return 0.0; + } + + double objectStrain = hitobject.Rhythm.Difficulty; + + objectStrain *= repetitionPenalties(hitobject); + objectStrain *= patternLengthPenalty(notesSinceRhythmChange); + objectStrain *= speedPenalty(hitobject.DeltaTime); + + notesSinceRhythmChange = 0; + + currentStrain += objectStrain; + return currentStrain; } // Finds repetitions and applies penalties @@ -61,6 +87,8 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills return true; } + private double repetitionPenalty(int notesSince) => Math.Min(1.0, 0.032 * notesSince); + private double patternLengthPenalty(int patternLength) { double shortPatternPenalty = Math.Min(0.15 * patternLength, 1.0); @@ -78,36 +106,6 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills return 0.0; } - protected override double StrainValueOf(DifficultyHitObject current) - { - if (!(current.BaseObject is Hit)) - { - resetRhythmStrain(); - return 0.0; - } - - currentStrain *= strain_decay; - - TaikoDifficultyHitObject hitobject = (TaikoDifficultyHitObject)current; - notesSinceRhythmChange += 1; - - if (hitobject.Rhythm.Difficulty == 0.0) - { - return 0.0; - } - - double objectStrain = hitobject.Rhythm.Difficulty; - - objectStrain *= repetitionPenalties(hitobject); - objectStrain *= patternLengthPenalty(notesSinceRhythmChange); - objectStrain *= speedPenalty(hitobject.DeltaTime); - - notesSinceRhythmChange = 0; - - currentStrain += objectStrain; - return currentStrain; - } - private void resetRhythmStrain() { currentStrain = 0.0; diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Stamina.cs b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Stamina.cs index 13510290f7..3fd21b5e6d 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Stamina.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Stamina.cs @@ -12,32 +12,19 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills { public class Stamina : Skill { - private readonly int hand; - protected override double SkillMultiplier => 1; protected override double StrainDecayBase => 0.4; private const int max_history_length = 2; + + private readonly int hand; private readonly LimitedCapacityQueue notePairDurationHistory = new LimitedCapacityQueue(max_history_length); private double offhandObjectDuration = double.MaxValue; - // Penalty for tl tap or roll - private double cheesePenalty(double notePairDuration) + public Stamina(bool rightHand) { - if (notePairDuration > 125) return 1; - if (notePairDuration < 100) return 0.6; - - return 0.6 + (notePairDuration - 100) * 0.016; - } - - private double speedBonus(double notePairDuration) - { - if (notePairDuration >= 200) return 0; - - double bonus = 200 - notePairDuration; - bonus *= bonus; - return bonus / 100000; + hand = rightHand ? 1 : 0; } protected override double StrainValueOf(DifficultyHitObject current) @@ -71,9 +58,22 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills return 0; } - public Stamina(bool rightHand) + // Penalty for tl tap or roll + private double cheesePenalty(double notePairDuration) { - hand = rightHand ? 1 : 0; + if (notePairDuration > 125) return 1; + if (notePairDuration < 100) return 0.6; + + return 0.6 + (notePairDuration - 100) * 0.016; + } + + private double speedBonus(double notePairDuration) + { + if (notePairDuration >= 200) return 0; + + double bonus = 200 - notePairDuration; + bonus *= bonus; + return bonus / 100000; } } } diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs index cbbef6e957..8e0cb2a094 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs @@ -29,87 +29,21 @@ namespace osu.Game.Rulesets.Taiko.Difficulty { } - private double simpleColourPenalty(double staminaDifficulty, double colorDifficulty) + protected override Skill[] CreateSkills(IBeatmap beatmap) => new Skill[] { - if (colorDifficulty <= 0) return 0.79 - 0.25; + new Colour(), + new Rhythm(), + new Stamina(true), + new Stamina(false), + }; - return 0.79 - Math.Atan(staminaDifficulty / colorDifficulty - 12) / Math.PI / 2; - } - - private double norm(double p, params double[] values) + protected override Mod[] DifficultyAdjustmentMods => new Mod[] { - return Math.Pow(values.Sum(x => Math.Pow(x, p)), 1 / p); - } - - private double rescale(double sr) - { - if (sr < 0) return sr; - - return 10.43 * Math.Log(sr / 8 + 1); - } - - private double locallyCombinedDifficulty( - double staminaPenalty, Colour colour, Rhythm rhythm, Stamina staminaRight, Stamina staminaLeft) - { - double difficulty = 0; - double weight = 1; - List peaks = new List(); - - for (int i = 0; i < colour.StrainPeaks.Count; i++) - { - double colourPeak = colour.StrainPeaks[i] * colour_skill_multiplier; - double rhythmPeak = rhythm.StrainPeaks[i] * rhythm_skill_multiplier; - double staminaPeak = (staminaRight.StrainPeaks[i] + staminaLeft.StrainPeaks[i]) * stamina_skill_multiplier * staminaPenalty; - peaks.Add(norm(2, colourPeak, rhythmPeak, staminaPeak)); - } - - foreach (double strain in peaks.OrderByDescending(d => d)) - { - difficulty += strain * weight; - weight *= 0.9; - } - - return difficulty; - } - - protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate) - { - if (beatmap.HitObjects.Count == 0) - return new TaikoDifficultyAttributes { Mods = mods, Skills = skills }; - - var colour = (Colour)skills[0]; - var rhythm = (Rhythm)skills[1]; - var staminaRight = (Stamina)skills[2]; - var staminaLeft = (Stamina)skills[3]; - - double colourRating = colour.DifficultyValue() * colour_skill_multiplier; - double rhythmRating = rhythm.DifficultyValue() * rhythm_skill_multiplier; - double staminaRating = (staminaRight.DifficultyValue() + staminaLeft.DifficultyValue()) * stamina_skill_multiplier; - - double staminaPenalty = simpleColourPenalty(staminaRating, colourRating); - staminaRating *= staminaPenalty; - - double combinedRating = locallyCombinedDifficulty(staminaPenalty, colour, rhythm, staminaRight, staminaLeft); - double separatedRating = norm(1.5, colourRating, rhythmRating, staminaRating); - double starRating = 1.4 * separatedRating + 0.5 * combinedRating; - starRating = rescale(starRating); - - HitWindows hitWindows = new TaikoHitWindows(); - hitWindows.SetDifficulty(beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty); - - return new TaikoDifficultyAttributes - { - StarRating = starRating, - Mods = mods, - StaminaStrain = staminaRating, - RhythmStrain = rhythmRating, - ColourStrain = colourRating, - // Todo: This int cast is temporary to achieve 1:1 results with osu!stable, and should be removed in the future - GreatHitWindow = (int)hitWindows.WindowFor(HitResult.Great) / clockRate, - MaxCombo = beatmap.HitObjects.Count(h => h is Hit), - Skills = skills - }; - } + new TaikoModDoubleTime(), + new TaikoModHalfTime(), + new TaikoModEasy(), + new TaikoModHardRock(), + }; protected override IEnumerable CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate) { @@ -131,24 +65,89 @@ namespace osu.Game.Rulesets.Taiko.Difficulty } } - new StaminaCheeseDetector().FindCheese(taikoDifficultyHitObjects); + new StaminaCheeseDetector(taikoDifficultyHitObjects).FindCheese(); return taikoDifficultyHitObjects; } - protected override Skill[] CreateSkills(IBeatmap beatmap) => new Skill[] + protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate) { - new Colour(), - new Rhythm(), - new Stamina(true), - new Stamina(false), - }; + if (beatmap.HitObjects.Count == 0) + return new TaikoDifficultyAttributes { Mods = mods, Skills = skills }; - protected override Mod[] DifficultyAdjustmentMods => new Mod[] + var colour = (Colour)skills[0]; + var rhythm = (Rhythm)skills[1]; + var staminaRight = (Stamina)skills[2]; + var staminaLeft = (Stamina)skills[3]; + + double colourRating = colour.DifficultyValue() * colour_skill_multiplier; + double rhythmRating = rhythm.DifficultyValue() * rhythm_skill_multiplier; + double staminaRating = (staminaRight.DifficultyValue() + staminaLeft.DifficultyValue()) * stamina_skill_multiplier; + + double staminaPenalty = simpleColourPenalty(staminaRating, colourRating); + staminaRating *= staminaPenalty; + + double combinedRating = locallyCombinedDifficulty(colour, rhythm, staminaRight, staminaLeft, staminaPenalty); + double separatedRating = norm(1.5, colourRating, rhythmRating, staminaRating); + double starRating = 1.4 * separatedRating + 0.5 * combinedRating; + starRating = rescale(starRating); + + HitWindows hitWindows = new TaikoHitWindows(); + hitWindows.SetDifficulty(beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty); + + return new TaikoDifficultyAttributes + { + StarRating = starRating, + Mods = mods, + StaminaStrain = staminaRating, + RhythmStrain = rhythmRating, + ColourStrain = colourRating, + // Todo: This int cast is temporary to achieve 1:1 results with osu!stable, and should be removed in the future + GreatHitWindow = (int)hitWindows.WindowFor(HitResult.Great) / clockRate, + MaxCombo = beatmap.HitObjects.Count(h => h is Hit), + Skills = skills + }; + } + + private double simpleColourPenalty(double staminaDifficulty, double colorDifficulty) { - new TaikoModDoubleTime(), - new TaikoModHalfTime(), - new TaikoModEasy(), - new TaikoModHardRock(), - }; + if (colorDifficulty <= 0) return 0.79 - 0.25; + + return 0.79 - Math.Atan(staminaDifficulty / colorDifficulty - 12) / Math.PI / 2; + } + + private double norm(double p, params double[] values) + { + return Math.Pow(values.Sum(x => Math.Pow(x, p)), 1 / p); + } + + private double rescale(double sr) + { + if (sr < 0) return sr; + + return 10.43 * Math.Log(sr / 8 + 1); + } + + private double locallyCombinedDifficulty(Colour colour, Rhythm rhythm, Stamina staminaRight, Stamina staminaLeft, double staminaPenalty) + { + double difficulty = 0; + double weight = 1; + List peaks = new List(); + + for (int i = 0; i < colour.StrainPeaks.Count; i++) + { + double colourPeak = colour.StrainPeaks[i] * colour_skill_multiplier; + double rhythmPeak = rhythm.StrainPeaks[i] * rhythm_skill_multiplier; + double staminaPeak = (staminaRight.StrainPeaks[i] + staminaLeft.StrainPeaks[i]) * stamina_skill_multiplier * staminaPenalty; + peaks.Add(norm(2, colourPeak, rhythmPeak, staminaPeak)); + } + + foreach (double strain in peaks.OrderByDescending(d => d)) + { + difficulty += strain * weight; + weight *= 0.9; + } + + return difficulty; + } } } From a0807747995823ed520f7beba6a39ddbb4580f41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 22 Aug 2020 19:34:16 +0200 Subject: [PATCH 0667/1782] Add xmldoc to taiko difficulty calculation code --- .../Preprocessing/StaminaCheeseDetector.cs | 41 +++++++ .../Preprocessing/TaikoDifficultyHitObject.cs | 57 ++++++++-- .../TaikoDifficultyHitObjectRhythm.cs | 17 +++ .../Difficulty/Skills/Colour.cs | 34 ++++-- .../Difficulty/Skills/Rhythm.cs | 100 +++++++++++++----- .../Difficulty/Skills/Stamina.cs | 36 ++++++- .../Difficulty/TaikoDifficultyCalculator.cs | 47 +++++--- 7 files changed, 281 insertions(+), 51 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/StaminaCheeseDetector.cs b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/StaminaCheeseDetector.cs index ef5f4433bf..5187d101ac 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/StaminaCheeseDetector.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/StaminaCheeseDetector.cs @@ -8,11 +8,32 @@ using osu.Game.Rulesets.Taiko.Objects; namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing { + /// + /// Detects special hit object patterns which are easier to hit using special techniques + /// than normally assumed in the fully-alternating play style. + /// + /// + /// This component detects two basic types of patterns, leveraged by the following techniques: + /// + /// Rolling allows hitting patterns with quickly and regularly alternating notes with a single hand. + /// TL tapping makes hitting longer sequences of consecutive same-colour notes with little to no colour changes in-between. + /// + /// public class StaminaCheeseDetector { + /// + /// The minimum number of consecutive objects with repeating patterns that can be classified as hittable using a roll. + /// private const int roll_min_repetitions = 12; + + /// + /// The minimum number of consecutive objects with repeating patterns that can be classified as hittable using a TL tap. + /// private const int tl_min_repetitions = 16; + /// + /// The list of all s in the map. + /// private readonly List hitObjects; public StaminaCheeseDetector(List hitObjects) @@ -20,16 +41,25 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing this.hitObjects = hitObjects; } + /// + /// Finds and marks all objects in that special difficulty-reducing techiques apply to + /// with the flag. + /// public void FindCheese() { findRolls(3); findRolls(4); + findTlTap(0, HitType.Rim); findTlTap(1, HitType.Rim); findTlTap(0, HitType.Centre); findTlTap(1, HitType.Centre); } + /// + /// Finds and marks all sequences hittable using a roll. + /// + /// The length of a single repeating pattern to consider (triplets/quadruplets). private void findRolls(int patternLength) { var history = new LimitedCapacityQueue(2 * patternLength); @@ -56,6 +86,9 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing } } + /// + /// Determines whether the objects stored in contain a repetition of a pattern of length . + /// private static bool containsPatternRepeat(LimitedCapacityQueue history, int patternLength) { for (int j = 0; j < patternLength; j++) @@ -67,6 +100,11 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing return true; } + /// + /// Finds and marks all sequences hittable using a TL tap. + /// + /// Whether sequences starting with an odd- (1) or even-indexed (0) hit object should be checked. + /// The type of hit to check for TL taps. private void findTlTap(int parity, HitType type) { int tlLength = -2; @@ -85,6 +123,9 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing } } + /// + /// Marks all objects from index up until (exclusive) as . + /// private void markObjectsAsCheese(int start, int end) { for (int i = start; i < end; ++i) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/TaikoDifficultyHitObject.cs b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/TaikoDifficultyHitObject.cs index 5f7f8040c7..ae33c184d0 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/TaikoDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/TaikoDifficultyHitObject.cs @@ -9,27 +9,61 @@ using osu.Game.Rulesets.Taiko.Objects; namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing { + /// + /// Represents a single hit object in taiko difficulty calculation. + /// public class TaikoDifficultyHitObject : DifficultyHitObject { + /// + /// The rhythm required to hit this hit object. + /// public readonly TaikoDifficultyHitObjectRhythm Rhythm; + + /// + /// The hit type of this hit object. + /// public readonly HitType? HitType; + + /// + /// The index of the object in the beatmap. + /// public readonly int ObjectIndex; + /// + /// Whether the object should carry a penalty due to being hittable using special techniques + /// making it easier to do so. + /// public bool StaminaCheese; + /// + /// Creates a new difficulty hit object. + /// + /// The gameplay associated with this difficulty object. + /// The gameplay preceding . + /// The gameplay preceding . + /// The rate of the gameplay clock. Modified by speed-changing mods. + /// The index of the object in the beatmap. public TaikoDifficultyHitObject(HitObject hitObject, HitObject lastObject, HitObject lastLastObject, double clockRate, int objectIndex) : base(hitObject, lastObject, clockRate) { var currentHit = hitObject as Hit; - double prevLength = (lastObject.StartTime - lastLastObject.StartTime) / clockRate; - - Rhythm = getClosestRhythm(DeltaTime / prevLength); + Rhythm = getClosestRhythm(lastObject, lastLastObject, clockRate); HitType = currentHit?.Type; ObjectIndex = objectIndex; } + /// + /// List of most common rhythm changes in taiko maps. + /// + /// + /// The general guidelines for the values are: + /// + /// rhythm changes with ratio closer to 1 (that are not 1) are harder to play, + /// speeding up is generally harder than slowing down (with exceptions of rhythm changes requiring a hand switch). + /// + /// private static readonly TaikoDifficultyHitObjectRhythm[] common_rhythms = { new TaikoDifficultyHitObjectRhythm(1, 1, 0.0), @@ -37,13 +71,24 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing new TaikoDifficultyHitObjectRhythm(1, 2, 0.5), new TaikoDifficultyHitObjectRhythm(3, 1, 0.3), new TaikoDifficultyHitObjectRhythm(1, 3, 0.35), - new TaikoDifficultyHitObjectRhythm(3, 2, 0.6), + new TaikoDifficultyHitObjectRhythm(3, 2, 0.6), // purposefully higher (requires hand switch in full alternating gameplay style) new TaikoDifficultyHitObjectRhythm(2, 3, 0.4), new TaikoDifficultyHitObjectRhythm(5, 4, 0.5), new TaikoDifficultyHitObjectRhythm(4, 5, 0.7) }; - private TaikoDifficultyHitObjectRhythm getClosestRhythm(double ratio) - => common_rhythms.OrderBy(x => Math.Abs(x.Ratio - ratio)).First(); + /// + /// Returns the closest rhythm change from required to hit this object. + /// + /// The gameplay preceding this one. + /// The gameplay preceding . + /// The rate of the gameplay clock. + private TaikoDifficultyHitObjectRhythm getClosestRhythm(HitObject lastObject, HitObject lastLastObject, double clockRate) + { + double prevLength = (lastObject.StartTime - lastLastObject.StartTime) / clockRate; + double ratio = DeltaTime / prevLength; + + return common_rhythms.OrderBy(x => Math.Abs(x.Ratio - ratio)).First(); + } } } diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/TaikoDifficultyHitObjectRhythm.cs b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/TaikoDifficultyHitObjectRhythm.cs index 9c22eff22a..b6dc69a380 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/TaikoDifficultyHitObjectRhythm.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/TaikoDifficultyHitObjectRhythm.cs @@ -3,11 +3,28 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing { + /// + /// Represents a rhythm change in a taiko map. + /// public class TaikoDifficultyHitObjectRhythm { + /// + /// The difficulty multiplier associated with this rhythm change. + /// public readonly double Difficulty; + + /// + /// The ratio of current to previous for the rhythm change. + /// A above 1 indicates a slow-down; a below 1 indicates a speed-up. + /// public readonly double Ratio; + /// + /// Creates an object representing a rhythm change. + /// + /// The numerator for . + /// The denominator for + /// The difficulty multiplier associated with this rhythm change. public TaikoDifficultyHitObjectRhythm(int numerator, int denominator, double difficulty) { Ratio = numerator / (double)denominator; diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Colour.cs b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Colour.cs index 1adbf272b3..9fad83c6a1 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Colour.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Colour.cs @@ -10,18 +10,28 @@ using osu.Game.Rulesets.Taiko.Objects; namespace osu.Game.Rulesets.Taiko.Difficulty.Skills { + /// + /// Calculates the colour coefficient of taiko difficulty. + /// public class Colour : Skill { protected override double SkillMultiplier => 1; protected override double StrainDecayBase => 0.4; + /// + /// Maximum number of entries to keep in . + /// private const int mono_history_max_length = 5; /// - /// List of the last most recent mono patterns, with the most recent at the end of the list. + /// Queue with the lengths of the last most recent mono (single-colour) patterns, + /// with the most recent value at the end of the queue. /// private readonly LimitedCapacityQueue monoHistory = new LimitedCapacityQueue(mono_history_max_length); + /// + /// The of the last object hit before the one being considered. + /// private HitType? previousHitType; /// @@ -31,6 +41,8 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills protected override double StrainValueOf(DifficultyHitObject current) { + // changing from/to a drum roll or a swell does not constitute a colour change. + // hits spaced more than a second apart are also exempt from colour strain. if (!(current.LastObject is Hit && current.BaseObject is Hit && current.DeltaTime < 1000)) { previousHitType = null; @@ -75,14 +87,14 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills /// private double repetitionPenalties() { - const int l = 2; + const int most_recent_patterns_to_compare = 2; double penalty = 1.0; monoHistory.Enqueue(currentMonoLength); - for (int start = monoHistory.Count - l - 1; start >= 0; start--) + for (int start = monoHistory.Count - most_recent_patterns_to_compare - 1; start >= 0; start--) { - if (!isSamePattern(start, l)) + if (!isSamePattern(start, most_recent_patterns_to_compare)) continue; int notesSince = 0; @@ -94,17 +106,25 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills return penalty; } - private bool isSamePattern(int start, int l) + /// + /// Determines whether the last patterns have repeated in the history + /// of single-colour note sequences, starting from . + /// + private bool isSamePattern(int start, int mostRecentPatternsToCompare) { - for (int i = 0; i < l; i++) + for (int i = 0; i < mostRecentPatternsToCompare; i++) { - if (monoHistory[start + i] != monoHistory[monoHistory.Count - l + i]) + if (monoHistory[start + i] != monoHistory[monoHistory.Count - mostRecentPatternsToCompare + i]) return false; } return true; } + /// + /// Calculates the strain penalty for a colour pattern repetition. + /// + /// The number of notes since the last repetition of the pattern. private double repetitionPenalty(int notesSince) => Math.Min(1.0, 0.032 * notesSince); } } diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Rhythm.cs b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Rhythm.cs index f37a4c3f65..5569b27ad5 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Rhythm.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Rhythm.cs @@ -10,64 +10,97 @@ using osu.Game.Rulesets.Taiko.Objects; namespace osu.Game.Rulesets.Taiko.Difficulty.Skills { + /// + /// Calculates the rhythm coefficient of taiko difficulty. + /// public class Rhythm : Skill { protected override double SkillMultiplier => 10; protected override double StrainDecayBase => 0; + /// + /// The note-based decay for rhythm strain. + /// + /// + /// is not used here, as it's time- and not note-based. + /// private const double strain_decay = 0.96; + + /// + /// Maximum number of entries in . + /// private const int rhythm_history_max_length = 8; + /// + /// Contains the last changes in note sequence rhythms. + /// private readonly LimitedCapacityQueue rhythmHistory = new LimitedCapacityQueue(rhythm_history_max_length); + /// + /// Contains the rolling rhythm strain. + /// Used to apply per-note decay. + /// private double currentStrain; + + /// + /// Number of notes since the last rhythm change has taken place. + /// private int notesSinceRhythmChange; protected override double StrainValueOf(DifficultyHitObject current) { + // drum rolls and swells are exempt. if (!(current.BaseObject is Hit)) { - resetRhythmStrain(); + resetRhythmAndStrain(); return 0.0; } currentStrain *= strain_decay; - TaikoDifficultyHitObject hitobject = (TaikoDifficultyHitObject)current; + TaikoDifficultyHitObject hitObject = (TaikoDifficultyHitObject)current; notesSinceRhythmChange += 1; - if (hitobject.Rhythm.Difficulty == 0.0) + // rhythm difficulty zero (due to rhythm not changing) => no rhythm strain. + if (hitObject.Rhythm.Difficulty == 0.0) { return 0.0; } - double objectStrain = hitobject.Rhythm.Difficulty; + double objectStrain = hitObject.Rhythm.Difficulty; - objectStrain *= repetitionPenalties(hitobject); + objectStrain *= repetitionPenalties(hitObject); objectStrain *= patternLengthPenalty(notesSinceRhythmChange); - objectStrain *= speedPenalty(hitobject.DeltaTime); + objectStrain *= speedPenalty(hitObject.DeltaTime); + // careful - needs to be done here since calls above read this value notesSinceRhythmChange = 0; currentStrain += objectStrain; return currentStrain; } - // Finds repetitions and applies penalties - private double repetitionPenalties(TaikoDifficultyHitObject hitobject) + /// + /// Returns a penalty to apply to the current hit object caused by repeating rhythm changes. + /// + /// + /// Repetitions of more recent patterns are associated with a higher penalty. + /// + /// The current hit object being considered. + private double repetitionPenalties(TaikoDifficultyHitObject hitObject) { double penalty = 1; - rhythmHistory.Enqueue(hitobject); + rhythmHistory.Enqueue(hitObject); - for (int l = 2; l <= rhythm_history_max_length / 2; l++) + for (int mostRecentPatternsToCompare = 2; mostRecentPatternsToCompare <= rhythm_history_max_length / 2; mostRecentPatternsToCompare++) { - for (int start = rhythmHistory.Count - l - 1; start >= 0; start--) + for (int start = rhythmHistory.Count - mostRecentPatternsToCompare - 1; start >= 0; start--) { - if (!samePattern(start, l)) + if (!samePattern(start, mostRecentPatternsToCompare)) continue; - int notesSince = hitobject.ObjectIndex - rhythmHistory[start].ObjectIndex; + int notesSince = hitObject.ObjectIndex - rhythmHistory[start].ObjectIndex; penalty *= repetitionPenalty(notesSince); break; } @@ -76,37 +109,56 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills return penalty; } - private bool samePattern(int start, int l) + /// + /// Determines whether the rhythm change pattern starting at is a repeat of any of the + /// . + /// + private bool samePattern(int start, int mostRecentPatternsToCompare) { - for (int i = 0; i < l; i++) + for (int i = 0; i < mostRecentPatternsToCompare; i++) { - if (rhythmHistory[start + i].Rhythm != rhythmHistory[rhythmHistory.Count - l + i].Rhythm) + if (rhythmHistory[start + i].Rhythm != rhythmHistory[rhythmHistory.Count - mostRecentPatternsToCompare + i].Rhythm) return false; } return true; } - private double repetitionPenalty(int notesSince) => Math.Min(1.0, 0.032 * notesSince); + /// + /// Calculates a single rhythm repetition penalty. + /// + /// Number of notes since the last repetition of a rhythm change. + private static double repetitionPenalty(int notesSince) => Math.Min(1.0, 0.032 * notesSince); - private double patternLengthPenalty(int patternLength) + /// + /// Calculates a penalty based on the number of notes since the last rhythm change. + /// Both rare and frequent rhythm changes are penalised. + /// + /// Number of notes since the last rhythm change. + private static double patternLengthPenalty(int patternLength) { double shortPatternPenalty = Math.Min(0.15 * patternLength, 1.0); double longPatternPenalty = Math.Clamp(2.5 - 0.15 * patternLength, 0.0, 1.0); return Math.Min(shortPatternPenalty, longPatternPenalty); } - // Penalty for notes so slow that alternating is not necessary. - private double speedPenalty(double noteLengthMs) + /// + /// Calculates a penalty for objects that do not require alternating hands. + /// + /// Time (in milliseconds) since the last hit object. + private double speedPenalty(double deltaTime) { - if (noteLengthMs < 80) return 1; - if (noteLengthMs < 210) return Math.Max(0, 1.4 - 0.005 * noteLengthMs); + if (deltaTime < 80) return 1; + if (deltaTime < 210) return Math.Max(0, 1.4 - 0.005 * deltaTime); - resetRhythmStrain(); + resetRhythmAndStrain(); return 0.0; } - private void resetRhythmStrain() + /// + /// Resets the rolling strain value and counter. + /// + private void resetRhythmAndStrain() { currentStrain = 0.0; notesSinceRhythmChange = 0; diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Stamina.cs b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Stamina.cs index 3fd21b5e6d..0b61eb9930 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Stamina.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Stamina.cs @@ -10,18 +10,45 @@ using osu.Game.Rulesets.Taiko.Objects; namespace osu.Game.Rulesets.Taiko.Difficulty.Skills { + /// + /// Calculates the stamina coefficient of taiko difficulty. + /// + /// + /// The reference play style chosen uses two hands, with full alternating (the hand changes after every hit). + /// public class Stamina : Skill { protected override double SkillMultiplier => 1; protected override double StrainDecayBase => 0.4; + /// + /// Maximum number of entries to keep in . + /// private const int max_history_length = 2; + /// + /// The index of the hand this instance is associated with. + /// + /// + /// The value of 0 indicates the left hand (full alternating gameplay starting with left hand is assumed). + /// This naturally translates onto index offsets of the objects in the map. + /// private readonly int hand; + + /// + /// Stores the last durations between notes hit with the hand indicated by . + /// private readonly LimitedCapacityQueue notePairDurationHistory = new LimitedCapacityQueue(max_history_length); + /// + /// Stores the of the last object that was hit by the other hand. + /// private double offhandObjectDuration = double.MaxValue; + /// + /// Creates a skill. + /// + /// Whether this instance is performing calculations for the right hand. public Stamina(bool rightHand) { hand = rightHand ? 1 : 0; @@ -58,7 +85,10 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills return 0; } - // Penalty for tl tap or roll + /// + /// Applies a penalty for hit objects marked with . + /// + /// The duration between the current and previous note hit using the hand indicated by . private double cheesePenalty(double notePairDuration) { if (notePairDuration > 125) return 1; @@ -67,6 +97,10 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills return 0.6 + (notePairDuration - 100) * 0.016; } + /// + /// Applies a speed bonus dependent on the time since the last hit performed using this hand. + /// + /// The duration between the current and previous note hit using the hand indicated by . private double speedBonus(double notePairDuration) { if (notePairDuration >= 200) return 0; diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs index 8e0cb2a094..ef43fc6d1e 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs @@ -108,6 +108,13 @@ namespace osu.Game.Rulesets.Taiko.Difficulty }; } + /// + /// Calculates the penalty for the stamina skill for maps with low colour difficulty. + /// + /// + /// Some maps (especially converts) can be easy to read despite a high note density. + /// This penalty aims to reduce the star rating of such maps by factoring in colour difficulty to the stamina skill. + /// private double simpleColourPenalty(double staminaDifficulty, double colorDifficulty) { if (colorDifficulty <= 0) return 0.79 - 0.25; @@ -115,22 +122,22 @@ namespace osu.Game.Rulesets.Taiko.Difficulty return 0.79 - Math.Atan(staminaDifficulty / colorDifficulty - 12) / Math.PI / 2; } - private double norm(double p, params double[] values) - { - return Math.Pow(values.Sum(x => Math.Pow(x, p)), 1 / p); - } - - private double rescale(double sr) - { - if (sr < 0) return sr; - - return 10.43 * Math.Log(sr / 8 + 1); - } + /// + /// Returns the p-norm of an n-dimensional vector. + /// + /// The value of p to calculate the norm for. + /// The coefficients of the vector. + private double norm(double p, params double[] values) => Math.Pow(values.Sum(x => Math.Pow(x, p)), 1 / p); + /// + /// Returns the partial star rating of the beatmap, calculated using peak strains from all sections of the map. + /// + /// + /// For each section, the peak strains of all separate skills are combined into a single peak strain for the section. + /// The resulting partial rating of the beatmap is a weighted sum of the combined peaks (higher peaks are weighted more). + /// private double locallyCombinedDifficulty(Colour colour, Rhythm rhythm, Stamina staminaRight, Stamina staminaLeft, double staminaPenalty) { - double difficulty = 0; - double weight = 1; List peaks = new List(); for (int i = 0; i < colour.StrainPeaks.Count; i++) @@ -141,6 +148,9 @@ namespace osu.Game.Rulesets.Taiko.Difficulty peaks.Add(norm(2, colourPeak, rhythmPeak, staminaPeak)); } + double difficulty = 0; + double weight = 1; + foreach (double strain in peaks.OrderByDescending(d => d)) { difficulty += strain * weight; @@ -149,5 +159,16 @@ namespace osu.Game.Rulesets.Taiko.Difficulty return difficulty; } + + /// + /// Applies a final re-scaling of the star rating to bring maps with recorded full combos below 9.5 stars. + /// + /// The raw star rating value before re-scaling. + private double rescale(double sr) + { + if (sr < 0) return sr; + + return 10.43 * Math.Log(sr / 8 + 1); + } } } From 5afe9b73d2736c6563826916c1056fcdf285bf17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 22 Aug 2020 21:27:08 +0200 Subject: [PATCH 0668/1782] Fix invalid cref --- .../Difficulty/Preprocessing/TaikoDifficultyHitObjectRhythm.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/TaikoDifficultyHitObjectRhythm.cs b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/TaikoDifficultyHitObjectRhythm.cs index b6dc69a380..ea6a224094 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/TaikoDifficultyHitObjectRhythm.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/TaikoDifficultyHitObjectRhythm.cs @@ -14,7 +14,8 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing public readonly double Difficulty; /// - /// The ratio of current to previous for the rhythm change. + /// The ratio of current + /// to previous for the rhythm change. /// A above 1 indicates a slow-down; a below 1 indicates a speed-up. /// public readonly double Ratio; From 7c9fae55ad8b5aa2706f29800f91aacca11eedca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 22 Aug 2020 22:50:58 +0200 Subject: [PATCH 0669/1782] Hopefully fix off-by-one errors --- .../Preprocessing/StaminaCheeseDetector.cs | 25 +++++++++++-------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/StaminaCheeseDetector.cs b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/StaminaCheeseDetector.cs index 5187d101ac..d07bff4369 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/StaminaCheeseDetector.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/StaminaCheeseDetector.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 osu.Game.Rulesets.Difficulty.Utils; using osu.Game.Rulesets.Taiko.Objects; @@ -64,7 +63,10 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing { var history = new LimitedCapacityQueue(2 * patternLength); - int repetitionStart = 0; + // for convenience, we're tracking the index of the item *before* our suspected repeat's start, + // as that index can be simply subtracted from the current index to get the number of elements in between + // without off-by-one errors + int indexBeforeLastRepeat = -1; for (int i = 0; i < hitObjects.Count; i++) { @@ -74,15 +76,18 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing if (!containsPatternRepeat(history, patternLength)) { - repetitionStart = i - 2 * patternLength; + // we're setting this up for the next iteration, hence the +1. + // right here this index will point at the queue's front (oldest item), + // but that item is about to be popped next loop with an enqueue. + indexBeforeLastRepeat = i - history.Count + 1; continue; } - int repeatedLength = i - repetitionStart; + int repeatedLength = i - indexBeforeLastRepeat; if (repeatedLength < roll_min_repetitions) continue; - markObjectsAsCheese(repetitionStart, i); + markObjectsAsCheese(i, repeatedLength); } } @@ -119,17 +124,17 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing if (tlLength < tl_min_repetitions) continue; - markObjectsAsCheese(Math.Max(0, i - tlLength), i); + markObjectsAsCheese(i, tlLength); } } /// - /// Marks all objects from index up until (exclusive) as . + /// Marks elements counting backwards from as . /// - private void markObjectsAsCheese(int start, int end) + private void markObjectsAsCheese(int end, int count) { - for (int i = start; i < end; ++i) - hitObjects[i].StaminaCheese = true; + for (int i = 0; i < count; ++i) + hitObjects[end - i].StaminaCheese = true; } } } From 0e9242ee9a327e220d7716c812ffb4bb3468790d Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 23 Aug 2020 10:28:05 +0300 Subject: [PATCH 0670/1782] Move combo font retrieval inside the legacy component --- .../Skinning/CatchLegacySkinTransformer.cs | 3 +-- osu.Game.Rulesets.Catch/Skinning/LegacyComboCounter.cs | 7 ++++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Skinning/CatchLegacySkinTransformer.cs b/osu.Game.Rulesets.Catch/Skinning/CatchLegacySkinTransformer.cs index 0e434291c1..28cd0fb65b 100644 --- a/osu.Game.Rulesets.Catch/Skinning/CatchLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Catch/Skinning/CatchLegacySkinTransformer.cs @@ -55,11 +55,10 @@ namespace osu.Game.Rulesets.Catch.Skinning case CatchSkinComponents.CatchComboCounter: var comboFont = GetConfig(LegacySetting.ComboPrefix)?.Value ?? "score"; - var fontOverlap = GetConfig(LegacySetting.ComboOverlap)?.Value ?? -2f; // For simplicity, let's use legacy combo font texture existence as a way to identify legacy skins from default. if (this.HasFont(comboFont)) - return new LegacyComboCounter(Source, comboFont, fontOverlap); + return new LegacyComboCounter(Source); break; } diff --git a/osu.Game.Rulesets.Catch/Skinning/LegacyComboCounter.cs b/osu.Game.Rulesets.Catch/Skinning/LegacyComboCounter.cs index 0d9df4f9a0..c1c70c3a0e 100644 --- a/osu.Game.Rulesets.Catch/Skinning/LegacyComboCounter.cs +++ b/osu.Game.Rulesets.Catch/Skinning/LegacyComboCounter.cs @@ -13,6 +13,7 @@ using osu.Game.Screens.Play; using osu.Game.Skinning; using osuTK; using osuTK.Graphics; +using static osu.Game.Skinning.LegacySkinConfiguration; namespace osu.Game.Rulesets.Catch.Skinning { @@ -28,12 +29,12 @@ namespace osu.Game.Rulesets.Catch.Skinning private readonly LegacyRollingCounter counter; - public LegacyComboCounter(ISkin skin, string fontName, float fontOverlap) + public LegacyComboCounter(ISkin skin) { this.skin = skin; - this.fontName = fontName; - this.fontOverlap = fontOverlap; + fontName = skin.GetConfig(LegacySetting.ComboPrefix)?.Value ?? "score"; + fontOverlap = skin.GetConfig(LegacySetting.ComboOverlap)?.Value ?? -2f; AutoSizeAxes = Axes.Both; From e6646b9877883560187415b1781e3a726da1ca43 Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Sun, 23 Aug 2020 15:08:02 +0200 Subject: [PATCH 0671/1782] Resolve review comments --- .../Formats/LegacyBeatmapEncoderTest.cs | 31 +++++++++++++------ .../Beatmaps/IO/ImportBeatmapTest.cs | 5 ++- osu.Game/Beatmaps/BeatmapManager.cs | 12 +++++-- .../Beatmaps/Formats/IHasCustomColours.cs | 2 +- .../Beatmaps/Formats/LegacyBeatmapEncoder.cs | 25 ++++++++------- osu.Game/Screens/Edit/Editor.cs | 4 +-- osu.Game/Screens/Edit/EditorChangeHandler.cs | 4 +-- .../Skinning/LegacyManiaSkinConfiguration.cs | 2 +- osu.Game/Skinning/SkinConfiguration.cs | 16 +++++----- 9 files changed, 59 insertions(+), 42 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs index db18f9b444..4e9e6743c9 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs @@ -28,25 +28,27 @@ namespace osu.Game.Tests.Beatmaps.Formats [TestFixture] public class LegacyBeatmapEncoderTest { - private static readonly DllResourceStore resource_store = TestResources.GetStore(); + private static readonly DllResourceStore beatmaps_resource_store = TestResources.GetStore(); - private static IEnumerable allBeatmaps = resource_store.GetAvailableResources().Where(res => res.EndsWith(".osu")); + private static IEnumerable allBeatmaps = beatmaps_resource_store.GetAvailableResources().Where(res => res.EndsWith(".osu")); [TestCaseSource(nameof(allBeatmaps))] public void TestEncodeDecodeStability(string name) { - var decoded = decodeFromLegacy(TestResources.GetStore().GetStream(name)); - var decodedAfterEncode = decodeFromLegacy(encodeToLegacy(decoded)); + var decoded = decodeFromLegacy(TestResources.GetStore().GetStream(name), name); + var decodedAfterEncode = decodeFromLegacy(encodeToLegacy(decoded), name); - sort(decoded.beatmap); - sort(decodedAfterEncode.beatmap); + sort(decoded); + sort(decodedAfterEncode); Assert.That(decodedAfterEncode.beatmap.Serialize(), Is.EqualTo(decoded.beatmap.Serialize())); Assert.IsTrue(decoded.beatmapSkin.Configuration.Equals(decodedAfterEncode.beatmapSkin.Configuration)); } - private void sort(IBeatmap beatmap) + private void sort((IBeatmap, LegacyBeatmapSkin) bs) { + var (beatmap, beatmapSkin) = bs; + // Sort control points to ensure a sane ordering, as they may be parsed in different orders. This works because each group contains only uniquely-typed control points. foreach (var g in beatmap.ControlPointInfo.Groups) { @@ -55,17 +57,26 @@ namespace osu.Game.Tests.Beatmaps.Formats } } - private (IBeatmap beatmap, LegacySkin beatmapSkin) decodeFromLegacy(Stream stream) + private (IBeatmap beatmap, LegacyBeatmapSkin beatmapSkin) decodeFromLegacy(Stream stream, string name) { using (var reader = new LineBufferedReader(stream)) { var beatmap = new LegacyBeatmapDecoder { ApplyOffsets = false }.Decode(reader); - var beatmapSkin = new LegacyBeatmapSkin(beatmap.BeatmapInfo, resource_store, null); + beatmap.BeatmapInfo.BeatmapSet.Files = new List + { + new BeatmapSetFileInfo + { + Filename = name, + FileInfo = new osu.Game.IO.FileInfo() { Hash = name } + } + }; + + var beatmapSkin = new LegacyBeatmapSkin(beatmap.BeatmapInfo, beatmaps_resource_store, null); return (convert(beatmap), beatmapSkin); } } - private Stream encodeToLegacy((IBeatmap beatmap, LegacySkin beatmapSkin) fullBeatmap) + private Stream encodeToLegacy((IBeatmap beatmap, LegacyBeatmapSkin beatmapSkin) fullBeatmap) { var (beatmap, beatmapSkin) = fullBeatmap; var stream = new MemoryStream(); diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs index 17271184c0..0151678db3 100644 --- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs @@ -730,8 +730,7 @@ namespace osu.Game.Tests.Beatmaps.IO BeatmapSetInfo setToUpdate = manager.GetAllUsableBeatmapSets()[0]; var beatmapInfo = setToUpdate.Beatmaps.First(b => b.RulesetID == 0); - var workingBeatmap = manager.GetWorkingBeatmap(setToUpdate.Beatmaps.First(b => b.RulesetID == 0)); - Beatmap beatmapToUpdate = (Beatmap)workingBeatmap.Beatmap; + Beatmap beatmapToUpdate = (Beatmap)manager.GetWorkingBeatmap(setToUpdate.Beatmaps.First(b => b.RulesetID == 0)).Beatmap; BeatmapSetFileInfo fileToUpdate = setToUpdate.Files.First(f => beatmapToUpdate.BeatmapInfo.Path.Contains(f.Filename)); string oldMd5Hash = beatmapToUpdate.BeatmapInfo.MD5Hash; @@ -739,7 +738,7 @@ namespace osu.Game.Tests.Beatmaps.IO beatmapToUpdate.HitObjects.Clear(); beatmapToUpdate.HitObjects.Add(new HitCircle { StartTime = 5000 }); - manager.Save(beatmapInfo, beatmapToUpdate, workingBeatmap.Skin); + manager.Save(beatmapInfo, beatmapToUpdate); // Check that the old file reference has been removed Assert.That(manager.QueryBeatmapSet(s => s.ID == setToUpdate.ID).Files.All(f => f.ID != fileToUpdate.ID)); diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index bd757d30ca..86d35749ac 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -195,8 +195,8 @@ namespace osu.Game.Beatmaps /// /// The to save the content against. The file referenced by will be replaced. /// The content to write. - /// Optional beatmap skin for inline skin configuration in beatmap files. - public void Save(BeatmapInfo info, IBeatmap beatmapContent, ISkin skin) + /// The beatmap content to write, or null if not to be changed. + public void Save(BeatmapInfo info, IBeatmap beatmapContent, LegacyBeatmapSkin beatmapSkin = null) { var setInfo = QueryBeatmapSet(s => s.Beatmaps.Any(b => b.ID == info.ID)); @@ -204,7 +204,13 @@ namespace osu.Game.Beatmaps { using (var sw = new StreamWriter(stream, Encoding.UTF8, 1024, true)) { - new LegacyBeatmapEncoder(beatmapContent, skin).Encode(sw); + if (beatmapSkin == null) + { + var workingBeatmap = GetWorkingBeatmap(info); + beatmapSkin = (workingBeatmap.Skin is LegacyBeatmapSkin legacy) ? legacy : null; + } + + new LegacyBeatmapEncoder(beatmapContent, beatmapSkin).Encode(sw); } stream.Seek(0, SeekOrigin.Begin); diff --git a/osu.Game/Beatmaps/Formats/IHasCustomColours.cs b/osu.Game/Beatmaps/Formats/IHasCustomColours.cs index 8f6c7dc328..1ac5ca83cb 100644 --- a/osu.Game/Beatmaps/Formats/IHasCustomColours.cs +++ b/osu.Game/Beatmaps/Formats/IHasCustomColours.cs @@ -8,6 +8,6 @@ namespace osu.Game.Beatmaps.Formats { public interface IHasCustomColours { - Dictionary CustomColours { get; set; } + IDictionary CustomColours { get; } } } diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs index 716f1bc814..497c3c88d0 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs @@ -16,6 +16,7 @@ using osu.Game.Rulesets.Objects.Legacy; using osu.Game.Rulesets.Objects.Types; using osu.Game.Skinning; using osuTK; +using osuTK.Graphics; namespace osu.Game.Beatmaps.Formats { @@ -24,11 +25,14 @@ namespace osu.Game.Beatmaps.Formats public const int LATEST_VERSION = 128; private readonly IBeatmap beatmap; - private readonly ISkin skin; + private readonly LegacyBeatmapSkin skin; - /// The beatmap to encode - /// An optional skin, for encoding the beatmap's combo colours. This will only work if the parameter is a type of . - public LegacyBeatmapEncoder(IBeatmap beatmap, [CanBeNull] ISkin skin) + /// + /// Creates a new . + /// + /// The beatmap to encode. + /// An optional skin, for encoding the beatmap's combo colours. + public LegacyBeatmapEncoder(IBeatmap beatmap, [CanBeNull] LegacyBeatmapSkin skin) { this.beatmap = beatmap; this.skin = skin; @@ -210,7 +214,7 @@ namespace osu.Game.Beatmaps.Formats if (!(skin is LegacyBeatmapSkin legacySkin)) return; - var colours = legacySkin?.Configuration.ComboColours; + var colours = legacySkin.GetConfig>(GlobalSkinColours.ComboColours)?.Value; if (colours == null || colours.Count == 0) return; @@ -221,12 +225,11 @@ namespace osu.Game.Beatmaps.Formats { var comboColour = colours[i]; - var r = (byte)(comboColour.R * byte.MaxValue); - var g = (byte)(comboColour.G * byte.MaxValue); - var b = (byte)(comboColour.B * byte.MaxValue); - var a = (byte)(comboColour.A * byte.MaxValue); - - writer.WriteLine($"Combo{i}: {r},{g},{b},{a}"); + writer.Write(FormattableString.Invariant($"Combo{i}: ")); + writer.Write(FormattableString.Invariant($"{(byte)(comboColour.R * byte.MaxValue)},")); + writer.Write(FormattableString.Invariant($"{(byte)(comboColour.G * byte.MaxValue)},")); + writer.Write(FormattableString.Invariant($"{(byte)(comboColour.B * byte.MaxValue)},")); + writer.WriteLine(FormattableString.Invariant($"{(byte)(comboColour.A * byte.MaxValue)}")); } } diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index a585db1ee9..7bd6529897 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -65,7 +65,7 @@ namespace osu.Game.Screens.Edit private IBeatmap playableBeatmap; private EditorBeatmap editorBeatmap; private EditorChangeHandler changeHandler; - private ISkin beatmapSkin; + private LegacyBeatmapSkin beatmapSkin; private DependencyContainer dependencies; @@ -94,7 +94,6 @@ namespace osu.Game.Screens.Edit try { playableBeatmap = Beatmap.Value.GetPlayableBeatmap(Beatmap.Value.BeatmapInfo.Ruleset); - beatmapSkin = Beatmap.Value.Skin; } catch (Exception e) { @@ -107,6 +106,7 @@ namespace osu.Game.Screens.Edit AddInternal(editorBeatmap = new EditorBeatmap(playableBeatmap)); dependencies.CacheAs(editorBeatmap); + beatmapSkin = (Beatmap.Value.Skin is LegacyBeatmapSkin legacy) ? legacy : null; changeHandler = new EditorChangeHandler(editorBeatmap, beatmapSkin); dependencies.CacheAs(changeHandler); diff --git a/osu.Game/Screens/Edit/EditorChangeHandler.cs b/osu.Game/Screens/Edit/EditorChangeHandler.cs index 0489236d45..1d10eaf5cb 100644 --- a/osu.Game/Screens/Edit/EditorChangeHandler.cs +++ b/osu.Game/Screens/Edit/EditorChangeHandler.cs @@ -26,7 +26,7 @@ namespace osu.Game.Screens.Edit private int currentState = -1; private readonly EditorBeatmap editorBeatmap; - private readonly ISkin beatmapSkin; + private readonly LegacyBeatmapSkin beatmapSkin; private int bulkChangesStarted; private bool isRestoring; @@ -37,7 +37,7 @@ namespace osu.Game.Screens.Edit /// /// The to track the s of. /// The skin to track the inline skin configuration of. - public EditorChangeHandler(EditorBeatmap editorBeatmap, ISkin beatmapSkin) + public EditorChangeHandler(EditorBeatmap editorBeatmap, LegacyBeatmapSkin beatmapSkin) { this.editorBeatmap = editorBeatmap; diff --git a/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs b/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs index af7d6007f3..f92da0b446 100644 --- a/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs +++ b/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs @@ -23,7 +23,7 @@ namespace osu.Game.Skinning public readonly int Keys; - public Dictionary CustomColours { get; set; } = new Dictionary(); + public IDictionary CustomColours { get; set; } = new SortedDictionary(); public Dictionary ImageLookups = new Dictionary(); diff --git a/osu.Game/Skinning/SkinConfiguration.cs b/osu.Game/Skinning/SkinConfiguration.cs index a48d713771..9565eee827 100644 --- a/osu.Game/Skinning/SkinConfiguration.cs +++ b/osu.Game/Skinning/SkinConfiguration.cs @@ -12,7 +12,7 @@ namespace osu.Game.Skinning /// /// An empty skin configuration. /// - public class SkinConfiguration : IHasComboColours, IHasCustomColours, IEquatable + public class SkinConfiguration : IEquatable, IHasComboColours, IHasCustomColours { public readonly SkinInfo SkinInfo = new SkinInfo(); @@ -47,16 +47,14 @@ namespace osu.Game.Skinning public void AddComboColours(params Color4[] colours) => comboColours.AddRange(colours); - public Dictionary CustomColours { get; set; } = new Dictionary(); + public IDictionary CustomColours { get; set; } = new SortedDictionary(); - public readonly Dictionary ConfigDictionary = new Dictionary(); + public readonly SortedDictionary ConfigDictionary = new SortedDictionary(); public bool Equals(SkinConfiguration other) - { - return other != null && - ConfigDictionary.SequenceEqual(other.ConfigDictionary) && - ComboColours.SequenceEqual(other.ComboColours) && - CustomColours?.SequenceEqual(other.CustomColours) == true; - } + => other != null + && ConfigDictionary.SequenceEqual(other.ConfigDictionary) + && ComboColours.SequenceEqual(other.ComboColours) + && CustomColours?.SequenceEqual(other.CustomColours) == true; } } From 492be0e0169248794305cda313b187b1ca0c3894 Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Sun, 23 Aug 2020 15:23:10 +0200 Subject: [PATCH 0672/1782] Fix formatting --- .../Beatmaps/Formats/LegacyBeatmapEncoderTest.cs | 8 +++----- osu.Game/Skinning/LegacyManiaSkinConfiguration.cs | 2 +- osu.Game/Skinning/SkinConfiguration.cs | 11 +++++------ 3 files changed, 9 insertions(+), 12 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs index 4e9e6743c9..f093180085 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs @@ -45,12 +45,10 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.IsTrue(decoded.beatmapSkin.Configuration.Equals(decodedAfterEncode.beatmapSkin.Configuration)); } - private void sort((IBeatmap, LegacyBeatmapSkin) bs) + private void sort((IBeatmap beatmap, LegacyBeatmapSkin beatmapSkin) tuple) { - var (beatmap, beatmapSkin) = bs; - // Sort control points to ensure a sane ordering, as they may be parsed in different orders. This works because each group contains only uniquely-typed control points. - foreach (var g in beatmap.ControlPointInfo.Groups) + foreach (var g in tuple.beatmap.ControlPointInfo.Groups) { ArrayList.Adapter((IList)g.ControlPoints).Sort( Comparer.Create((c1, c2) => string.Compare(c1.GetType().ToString(), c2.GetType().ToString(), StringComparison.Ordinal))); @@ -67,7 +65,7 @@ namespace osu.Game.Tests.Beatmaps.Formats new BeatmapSetFileInfo { Filename = name, - FileInfo = new osu.Game.IO.FileInfo() { Hash = name } + FileInfo = new osu.Game.IO.FileInfo { Hash = name } } }; diff --git a/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs b/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs index f92da0b446..baa7fa817c 100644 --- a/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs +++ b/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs @@ -23,7 +23,7 @@ namespace osu.Game.Skinning public readonly int Keys; - public IDictionary CustomColours { get; set; } = new SortedDictionary(); + public IDictionary CustomColours { get; } = new SortedDictionary(); public Dictionary ImageLookups = new Dictionary(); diff --git a/osu.Game/Skinning/SkinConfiguration.cs b/osu.Game/Skinning/SkinConfiguration.cs index 9565eee827..27589577e6 100644 --- a/osu.Game/Skinning/SkinConfiguration.cs +++ b/osu.Game/Skinning/SkinConfiguration.cs @@ -47,14 +47,13 @@ namespace osu.Game.Skinning public void AddComboColours(params Color4[] colours) => comboColours.AddRange(colours); - public IDictionary CustomColours { get; set; } = new SortedDictionary(); + public IDictionary CustomColours { get; } = new SortedDictionary(); public readonly SortedDictionary ConfigDictionary = new SortedDictionary(); - public bool Equals(SkinConfiguration other) - => other != null - && ConfigDictionary.SequenceEqual(other.ConfigDictionary) - && ComboColours.SequenceEqual(other.ComboColours) - && CustomColours?.SequenceEqual(other.CustomColours) == true; + public bool Equals(SkinConfiguration other) => other != null + && ConfigDictionary.SequenceEqual(other.ConfigDictionary) + && ComboColours.SequenceEqual(other.ComboColours) + && CustomColours?.SequenceEqual(other.CustomColours) == true; } } From 8f9e090f4c02549c8ed613e8c70785eed38d17be Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Sun, 23 Aug 2020 15:39:48 +0200 Subject: [PATCH 0673/1782] Remove Indent --- osu.Game/Skinning/SkinConfiguration.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/osu.Game/Skinning/SkinConfiguration.cs b/osu.Game/Skinning/SkinConfiguration.cs index 27589577e6..2ac4dfa0c8 100644 --- a/osu.Game/Skinning/SkinConfiguration.cs +++ b/osu.Game/Skinning/SkinConfiguration.cs @@ -51,9 +51,6 @@ namespace osu.Game.Skinning public readonly SortedDictionary ConfigDictionary = new SortedDictionary(); - public bool Equals(SkinConfiguration other) => other != null - && ConfigDictionary.SequenceEqual(other.ConfigDictionary) - && ComboColours.SequenceEqual(other.ComboColours) - && CustomColours?.SequenceEqual(other.CustomColours) == true; + public bool Equals(SkinConfiguration other) => other != null && ConfigDictionary.SequenceEqual(other.ConfigDictionary) && ComboColours.SequenceEqual(other.ComboColours) && CustomColours?.SequenceEqual(other.CustomColours) == true; } } From 2dce850f5b61b248285d8df4b10ead3e7167948d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 23 Aug 2020 23:11:56 +0900 Subject: [PATCH 0674/1782] Rewrite hyperdash test to not rely on timing --- .../TestSceneHyperDash.cs | 39 +++++++++++++------ 1 file changed, 28 insertions(+), 11 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDash.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDash.cs index 6dab2a0b56..514d2aae22 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDash.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDash.cs @@ -22,21 +22,38 @@ namespace osu.Game.Rulesets.Catch.Tests [Test] public void TestHyperDash() { - AddAssert("First note is hyperdash", () => Beatmap.Value.Beatmap.HitObjects[0] is Fruit f && f.HyperDash); - AddUntilStep("wait for right movement", () => getCatcher().Scale.X > 0); // don't check hyperdashing as it happens too fast. - - AddUntilStep("wait for left movement", () => getCatcher().Scale.X < 0); - - for (int i = 0; i < 3; i++) + AddStep("reset count", () => { - AddUntilStep("wait for right hyperdash", () => getCatcher().Scale.X > 0 && getCatcher().HyperDashing); - AddUntilStep("wait for left hyperdash", () => getCatcher().Scale.X < 0 && getCatcher().HyperDashing); - } + inHyperDash = false; + hyperDashCount = 0; + }); - AddUntilStep("wait for right hyperdash", () => getCatcher().Scale.X > 0 && getCatcher().HyperDashing); + AddAssert("First note is hyperdash", () => Beatmap.Value.Beatmap.HitObjects[0] is Fruit f && f.HyperDash); + + for (int i = 0; i < 9; i++) + { + int count = i + 1; + AddUntilStep("wait for next hyperdash", () => hyperDashCount == count); + } } - private Catcher getCatcher() => Player.ChildrenOfType().First().MovableCatcher; + private int hyperDashCount; + private bool inHyperDash; + + protected override void Update() + { + var catcher = Player.ChildrenOfType().FirstOrDefault()?.MovableCatcher; + + if (catcher == null) + return; + + if (catcher.HyperDashing != inHyperDash) + { + inHyperDash = catcher.HyperDashing; + if (catcher.HyperDashing) + hyperDashCount++; + } + } protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) { From d274652b3a30ace18ab23d92c7d4064c2421fd5f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 24 Aug 2020 00:13:26 +0900 Subject: [PATCH 0675/1782] Fix failures if test ran too fast --- osu.Game.Rulesets.Catch.Tests/TestSceneHyperDash.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDash.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDash.cs index 514d2aae22..a12e4b69e3 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDash.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDash.cs @@ -33,7 +33,7 @@ namespace osu.Game.Rulesets.Catch.Tests for (int i = 0; i < 9; i++) { int count = i + 1; - AddUntilStep("wait for next hyperdash", () => hyperDashCount == count); + AddUntilStep("wait for next hyperdash", () => hyperDashCount >= count); } } From 12ca870b74e3883ccc3396e32e160d239f419193 Mon Sep 17 00:00:00 2001 From: "Orosfai I. Zsolt" Date: Sun, 23 Aug 2020 17:34:57 +0200 Subject: [PATCH 0676/1782] Fix osu!catch relax mod --- 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 c1d24395e4..1e42c6a240 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs @@ -52,7 +52,7 @@ namespace osu.Game.Rulesets.Catch.Mods protected override bool OnMouseMove(MouseMoveEvent e) { - catcher.UpdatePosition(e.MousePosition.X / DrawSize.X); + catcher.UpdatePosition(e.MousePosition.X / DrawSize.X * CatchPlayfield.WIDTH); return base.OnMouseMove(e); } } From 68a043a0703202fc918e5879cc6eef296b14f7eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 23 Aug 2020 18:00:06 +0200 Subject: [PATCH 0677/1782] Add test case covering regression --- .../Mods/TestSceneCatchModRelax.cs | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModRelax.cs diff --git a/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModRelax.cs b/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModRelax.cs new file mode 100644 index 0000000000..80939d756e --- /dev/null +++ b/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModRelax.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 System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using osu.Framework.Testing; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Catch.Mods; +using osu.Game.Rulesets.Catch.Objects; +using osu.Game.Rulesets.Catch.UI; +using osu.Game.Rulesets.Objects; +using osu.Game.Tests.Visual; + +namespace osu.Game.Rulesets.Catch.Tests.Mods +{ + public class TestSceneCatchModRelax : ModTestScene + { + protected override Ruleset CreatePlayerRuleset() => new CatchRuleset(); + + [Test] + public void TestModRelax() => CreateModTest(new ModTestData + { + Mod = new CatchModRelax(), + Autoplay = false, + PassCondition = () => + { + var playfield = this.ChildrenOfType().Single(); + InputManager.MoveMouseTo(playfield.ScreenSpaceDrawQuad.Centre); + + return Player.ScoreProcessor.Combo.Value > 0; + }, + Beatmap = new Beatmap + { + HitObjects = new List + { + new Fruit + { + X = CatchPlayfield.CENTER_X + } + } + } + }); + } +} From a8a7d9af297efdca7a6aecd414e1c8f16421cf16 Mon Sep 17 00:00:00 2001 From: "Orosfai I. Zsolt" Date: Sun, 23 Aug 2020 21:35:15 +0200 Subject: [PATCH 0678/1782] Add testcase to osu!catch relax mod --- .../Mods/TestSceneCatchModRelax.cs | 52 ++++++++++++++++--- 1 file changed, 44 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModRelax.cs b/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModRelax.cs index 80939d756e..385de0cea7 100644 --- a/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModRelax.cs +++ b/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModRelax.cs @@ -10,7 +10,9 @@ using osu.Game.Rulesets.Catch.Mods; using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.UI; using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; using osu.Game.Tests.Visual; +using osuTK; namespace osu.Game.Rulesets.Catch.Tests.Mods { @@ -23,23 +25,57 @@ namespace osu.Game.Rulesets.Catch.Tests.Mods { Mod = new CatchModRelax(), Autoplay = false, - PassCondition = () => - { - var playfield = this.ChildrenOfType().Single(); - InputManager.MoveMouseTo(playfield.ScreenSpaceDrawQuad.Centre); - - return Player.ScoreProcessor.Combo.Value > 0; - }, + PassCondition = passCondition, Beatmap = new Beatmap { HitObjects = new List { new Fruit { - X = CatchPlayfield.CENTER_X + X = CatchPlayfield.CENTER_X, + StartTime = 0 + }, + new Fruit + { + X = 0, + StartTime = 250 + }, + new Fruit + { + X = CatchPlayfield.WIDTH, + StartTime = 500 + }, + new JuiceStream + { + X = CatchPlayfield.CENTER_X, + StartTime = 750, + Path = new SliderPath(PathType.Linear, new Vector2[] { Vector2.Zero, Vector2.UnitY * 200 }) } } } }); + + private bool passCondition() + { + var playfield = this.ChildrenOfType().Single(); + + switch (Player.ScoreProcessor.Combo.Value) + { + case 0: + InputManager.MoveMouseTo(playfield.ScreenSpaceDrawQuad.Centre); + break; + case 1: + InputManager.MoveMouseTo(playfield.ScreenSpaceDrawQuad.BottomLeft); + break; + case 2: + InputManager.MoveMouseTo(playfield.ScreenSpaceDrawQuad.BottomRight); + break; + case 3: + InputManager.MoveMouseTo(playfield.ScreenSpaceDrawQuad.Centre); + break; + } + + return Player.ScoreProcessor.Combo.Value >= 6; + } } } From 3d68f30467c02390e50f5176f294d2a1053056d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 23 Aug 2020 21:52:50 +0200 Subject: [PATCH 0679/1782] Fix code style issues --- osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModRelax.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModRelax.cs b/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModRelax.cs index 385de0cea7..1eb0975010 100644 --- a/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModRelax.cs +++ b/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModRelax.cs @@ -49,7 +49,7 @@ namespace osu.Game.Rulesets.Catch.Tests.Mods { X = CatchPlayfield.CENTER_X, StartTime = 750, - Path = new SliderPath(PathType.Linear, new Vector2[] { Vector2.Zero, Vector2.UnitY * 200 }) + Path = new SliderPath(PathType.Linear, new[] { Vector2.Zero, Vector2.UnitY * 200 }) } } } @@ -64,12 +64,15 @@ namespace osu.Game.Rulesets.Catch.Tests.Mods case 0: InputManager.MoveMouseTo(playfield.ScreenSpaceDrawQuad.Centre); break; + case 1: InputManager.MoveMouseTo(playfield.ScreenSpaceDrawQuad.BottomLeft); break; + case 2: InputManager.MoveMouseTo(playfield.ScreenSpaceDrawQuad.BottomRight); break; + case 3: InputManager.MoveMouseTo(playfield.ScreenSpaceDrawQuad.Centre); break; From c03cc754e3764bda4ae8b9394eedb846bfd48eef Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 24 Aug 2020 11:38:03 +0900 Subject: [PATCH 0680/1782] Move event attaching to ensure reporting is done at a high enough rate --- .../TestSceneHyperDash.cs | 39 ++++++++++--------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDash.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDash.cs index a12e4b69e3..db09b2bc6b 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDash.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDash.cs @@ -19,6 +19,9 @@ namespace osu.Game.Rulesets.Catch.Tests { protected override bool Autoplay => true; + private int hyperDashCount; + private bool inHyperDash; + [Test] public void TestHyperDash() { @@ -26,6 +29,22 @@ namespace osu.Game.Rulesets.Catch.Tests { inHyperDash = false; hyperDashCount = 0; + + // this needs to be done within the frame stable context due to how quickly hyperdash state changes occur. + Player.DrawableRuleset.FrameStableComponents.OnUpdate += d => + { + var catcher = Player.ChildrenOfType().FirstOrDefault()?.MovableCatcher; + + if (catcher == null) + return; + + if (catcher.HyperDashing != inHyperDash) + { + inHyperDash = catcher.HyperDashing; + if (catcher.HyperDashing) + hyperDashCount++; + } + }; }); AddAssert("First note is hyperdash", () => Beatmap.Value.Beatmap.HitObjects[0] is Fruit f && f.HyperDash); @@ -33,25 +52,7 @@ namespace osu.Game.Rulesets.Catch.Tests for (int i = 0; i < 9; i++) { int count = i + 1; - AddUntilStep("wait for next hyperdash", () => hyperDashCount >= count); - } - } - - private int hyperDashCount; - private bool inHyperDash; - - protected override void Update() - { - var catcher = Player.ChildrenOfType().FirstOrDefault()?.MovableCatcher; - - if (catcher == null) - return; - - if (catcher.HyperDashing != inHyperDash) - { - inHyperDash = catcher.HyperDashing; - if (catcher.HyperDashing) - hyperDashCount++; + AddUntilStep($"wait for hyperdash #{count}", () => hyperDashCount >= count); } } From dca307e93379e258c15b5423130014afb9f8f03a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 24 Aug 2020 13:02:39 +0900 Subject: [PATCH 0681/1782] Use beatmap directly in ReadyButton --- osu.Game/Screens/Multi/Match/Components/ReadyButton.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/osu.Game/Screens/Multi/Match/Components/ReadyButton.cs b/osu.Game/Screens/Multi/Match/Components/ReadyButton.cs index 384d3bd5a5..a64f24dd7e 100644 --- a/osu.Game/Screens/Multi/Match/Components/ReadyButton.cs +++ b/osu.Game/Screens/Multi/Match/Components/ReadyButton.cs @@ -10,7 +10,6 @@ using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; using osu.Game.Online.Multiplayer; -using osu.Game.Overlays; namespace osu.Game.Screens.Multi.Match.Components { @@ -27,9 +26,6 @@ namespace osu.Game.Screens.Multi.Match.Components [Resolved] private BeatmapManager beatmaps { get; set; } - [Resolved] - private MusicController musicController { get; set; } - private bool hasBeatmap; public ReadyButton() @@ -104,7 +100,7 @@ namespace osu.Game.Screens.Multi.Match.Components return; } - bool hasEnoughTime = DateTimeOffset.UtcNow.AddSeconds(30).AddMilliseconds(musicController.CurrentTrack.Length) < endDate.Value; + bool hasEnoughTime = DateTimeOffset.UtcNow.AddSeconds(30).AddMilliseconds(gameBeatmap.Value.Track.Length) < endDate.Value; Enabled.Value = hasBeatmap && hasEnoughTime; } From db45d9aa8a1f395c57f6e276feb62364845dab54 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 24 Aug 2020 22:11:04 +0900 Subject: [PATCH 0682/1782] Fix catch hyper dash colour defaults not being set correctly As the defaults were not set, if a skin happened to specify 0,0,0,0 it would be ignored due to the early returns in property setters. --- osu.Game.Rulesets.Catch/UI/CatcherTrailDisplay.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Catch/UI/CatcherTrailDisplay.cs b/osu.Game.Rulesets.Catch/UI/CatcherTrailDisplay.cs index bab3cb748b..4dcc533dac 100644 --- a/osu.Game.Rulesets.Catch/UI/CatcherTrailDisplay.cs +++ b/osu.Game.Rulesets.Catch/UI/CatcherTrailDisplay.cs @@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Catch.UI private readonly Container hyperDashTrails; private readonly Container endGlowSprites; - private Color4 hyperDashTrailsColour; + private Color4 hyperDashTrailsColour = Catcher.DEFAULT_HYPER_DASH_COLOUR; public Color4 HyperDashTrailsColour { @@ -39,7 +39,7 @@ namespace osu.Game.Rulesets.Catch.UI } } - private Color4 endGlowSpritesColour; + private Color4 endGlowSpritesColour = Catcher.DEFAULT_HYPER_DASH_COLOUR; public Color4 EndGlowSpritesColour { From 500cb0ccf5c5f6f09a3874fe98731c41382b6a3c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 24 Aug 2020 22:36:37 +0900 Subject: [PATCH 0683/1782] Fix legacy hit target being layered incorrectly --- .../Skinning/LegacyColumnBackground.cs | 41 +++++++++++++++---- .../Skinning/LegacyHitTarget.cs | 5 --- .../Skinning/LegacyStageBackground.cs | 6 ++- .../Skinning/ManiaLegacySkinTransformer.cs | 9 ++-- 4 files changed, 45 insertions(+), 16 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyColumnBackground.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyColumnBackground.cs index f9286b5095..543ea23db8 100644 --- a/osu.Game.Rulesets.Mania/Skinning/LegacyColumnBackground.cs +++ b/osu.Game.Rulesets.Mania/Skinning/LegacyColumnBackground.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -21,15 +22,20 @@ namespace osu.Game.Rulesets.Mania.Skinning private readonly IBindable direction = new Bindable(); private readonly bool isLastColumn; - private Container borderLineContainer; + [CanBeNull] + private readonly LegacyStageBackground stageBackground; + + private Container hitTargetContainer; private Container lightContainer; private Sprite light; + private Drawable hitTarget; private float hitPosition; - public LegacyColumnBackground(bool isLastColumn) + public LegacyColumnBackground(bool isLastColumn, [CanBeNull] LegacyStageBackground stageBackground) { this.isLastColumn = isLastColumn; + this.stageBackground = stageBackground; RelativeSizeAxes = Axes.Both; } @@ -47,6 +53,7 @@ namespace osu.Game.Rulesets.Mania.Skinning bool hasLeftLine = leftLineWidth > 0; bool hasRightLine = rightLineWidth > 0 && skin.GetConfig(LegacySkinConfiguration.LegacySetting.Version)?.Value >= 2.4m || isLastColumn; + bool hasHitTarget = Column.Index == 0 || stageBackground == null; hitPosition = GetColumnSkinConfig(skin, LegacyManiaSkinConfigurationLookups.HitPosition)?.Value ?? Stage.HIT_TARGET_POSITION; @@ -63,18 +70,29 @@ namespace osu.Game.Rulesets.Mania.Skinning Color4 lightColour = GetColumnSkinConfig(skin, LegacyManiaSkinConfigurationLookups.ColumnLightColour)?.Value ?? Color4.White; - InternalChildren = new Drawable[] + Drawable background; + + InternalChildren = new[] { - new Box + background = new Box { RelativeSizeAxes = Axes.Both, Colour = backgroundColour }, - borderLineContainer = new Container + hitTargetContainer = new Container { RelativeSizeAxes = Axes.Both, Children = new[] { + // In legacy skins, the hit target takes on the full stage size and is sandwiched between the column background and the column light. + // To simulate this effect in lazer's hierarchy, the hit target is added to the first column's background and manually extended to the full size of the stage. + // Adding to the first columns allows depth issues to be resolved - if it were added to the last column, the previous column lights would appear below it. + // This still means that the hit target will appear below the next column backgrounds, but that's a much easier problem to solve by proxying the backgrounds below. + hitTarget = new LegacyHitTarget + { + RelativeSizeAxes = Axes.Y, + Alpha = hasHitTarget ? 1 : 0 + }, new Box { RelativeSizeAxes = Axes.Y, @@ -113,6 +131,9 @@ namespace osu.Game.Rulesets.Mania.Skinning } }; + // Resolve depth issues with the hit target appearing under the next column backgrounds by proxying to the stage background (always below the columns). + stageBackground?.AddColumnBackground(background.CreateProxy()); + direction.BindTo(scrollingInfo.Direction); direction.BindValueChanged(onDirectionChanged, true); } @@ -124,17 +145,23 @@ namespace osu.Game.Rulesets.Mania.Skinning lightContainer.Anchor = Anchor.TopCentre; lightContainer.Scale = new Vector2(1, -1); - borderLineContainer.Padding = new MarginPadding { Top = hitPosition }; + hitTargetContainer.Padding = new MarginPadding { Top = hitPosition }; } else { lightContainer.Anchor = Anchor.BottomCentre; lightContainer.Scale = Vector2.One; - borderLineContainer.Padding = new MarginPadding { Bottom = hitPosition }; + hitTargetContainer.Padding = new MarginPadding { Bottom = hitPosition }; } } + protected override void Update() + { + base.Update(); + hitTarget.Width = stageBackground?.DrawWidth ?? DrawWidth; + } + public bool OnPressed(ManiaAction action) { if (action == Column.Action.Value) diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyHitTarget.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyHitTarget.cs index d055ef3480..1e1a9c2237 100644 --- a/osu.Game.Rulesets.Mania/Skinning/LegacyHitTarget.cs +++ b/osu.Game.Rulesets.Mania/Skinning/LegacyHitTarget.cs @@ -20,11 +20,6 @@ namespace osu.Game.Rulesets.Mania.Skinning private Container directionContainer; - public LegacyHitTarget() - { - RelativeSizeAxes = Axes.Both; - } - [BackgroundDependencyLoader] private void load(ISkinSource skin, IScrollingInfo scrollingInfo) { diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyStageBackground.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyStageBackground.cs index 7f5de601ca..3998f21c96 100644 --- a/osu.Game.Rulesets.Mania/Skinning/LegacyStageBackground.cs +++ b/osu.Game.Rulesets.Mania/Skinning/LegacyStageBackground.cs @@ -14,6 +14,7 @@ namespace osu.Game.Rulesets.Mania.Skinning { private Drawable leftSprite; private Drawable rightSprite; + private Container columnBackgroundContainer; public LegacyStageBackground() { @@ -44,7 +45,8 @@ namespace osu.Game.Rulesets.Mania.Skinning Origin = Anchor.TopLeft, X = -0.05f, Texture = skin.GetTexture(rightImage) - } + }, + columnBackgroundContainer = new Container { RelativeSizeAxes = Axes.Both } }; } @@ -58,5 +60,7 @@ namespace osu.Game.Rulesets.Mania.Skinning if (rightSprite?.Height > 0) rightSprite.Scale = new Vector2(1, DrawHeight / rightSprite.Height); } + + public void AddColumnBackground(Drawable background) => columnBackgroundContainer.Add(background); } } diff --git a/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs index e167135556..d928f1080f 100644 --- a/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs @@ -57,6 +57,8 @@ namespace osu.Game.Rulesets.Mania.Skinning /// private Lazy hasKeyTexture; + private LegacyStageBackground stageBackground; + public ManiaLegacySkinTransformer(ISkinSource source, IBeatmap beatmap) : base(source) { @@ -88,10 +90,11 @@ namespace osu.Game.Rulesets.Mania.Skinning switch (maniaComponent.Component) { case ManiaSkinComponents.ColumnBackground: - return new LegacyColumnBackground(maniaComponent.TargetColumn == beatmap.TotalColumns - 1); + return new LegacyColumnBackground(maniaComponent.TargetColumn == beatmap.TotalColumns - 1, stageBackground); case ManiaSkinComponents.HitTarget: - return new LegacyHitTarget(); + // Created within the column background, but should not fall back. See comments in LegacyColumnBackground. + return Drawable.Empty(); case ManiaSkinComponents.KeyArea: return new LegacyKeyArea(); @@ -112,7 +115,7 @@ namespace osu.Game.Rulesets.Mania.Skinning return new LegacyHitExplosion(); case ManiaSkinComponents.StageBackground: - return new LegacyStageBackground(); + return stageBackground = new LegacyStageBackground(); case ManiaSkinComponents.StageForeground: return new LegacyStageForeground(); From 60695bee8c25ce8a40894382d338ee7a883e96f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 24 Aug 2020 15:57:41 +0200 Subject: [PATCH 0684/1782] Remove fades when changing trail colour across skins --- osu.Game.Rulesets.Catch/UI/Catcher.cs | 5 +++-- osu.Game.Rulesets.Catch/UI/CatcherTrailDisplay.cs | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Catch/UI/Catcher.cs b/osu.Game.Rulesets.Catch/UI/Catcher.cs index a30e1b7b47..9289a6162c 100644 --- a/osu.Game.Rulesets.Catch/UI/Catcher.cs +++ b/osu.Game.Rulesets.Catch/UI/Catcher.cs @@ -285,8 +285,6 @@ namespace osu.Game.Rulesets.Catch.UI private void runHyperDashStateTransition(bool hyperDashing) { - trails.HyperDashTrailsColour = hyperDashColour; - trails.EndGlowSpritesColour = hyperDashEndGlowColour; updateTrailVisibility(); if (hyperDashing) @@ -403,6 +401,9 @@ namespace osu.Game.Rulesets.Catch.UI skin.GetConfig(CatchSkinColour.HyperDashAfterImage)?.Value ?? hyperDashColour; + trails.HyperDashTrailsColour = hyperDashColour; + trails.EndGlowSpritesColour = hyperDashEndGlowColour; + runHyperDashStateTransition(HyperDashing); } diff --git a/osu.Game.Rulesets.Catch/UI/CatcherTrailDisplay.cs b/osu.Game.Rulesets.Catch/UI/CatcherTrailDisplay.cs index 4dcc533dac..f7e9fd19a7 100644 --- a/osu.Game.Rulesets.Catch/UI/CatcherTrailDisplay.cs +++ b/osu.Game.Rulesets.Catch/UI/CatcherTrailDisplay.cs @@ -35,7 +35,7 @@ namespace osu.Game.Rulesets.Catch.UI return; hyperDashTrailsColour = value; - hyperDashTrails.FadeColour(hyperDashTrailsColour, Catcher.HYPER_DASH_TRANSITION_DURATION, Easing.OutQuint); + hyperDashTrails.Colour = hyperDashTrailsColour; } } @@ -50,7 +50,7 @@ namespace osu.Game.Rulesets.Catch.UI return; endGlowSpritesColour = value; - endGlowSprites.FadeColour(endGlowSpritesColour, Catcher.HYPER_DASH_TRANSITION_DURATION, Easing.OutQuint); + endGlowSprites.Colour = endGlowSpritesColour; } } From 77bf646ea09823b123ea44993adf6da3f3e6b320 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 24 Aug 2020 23:01:06 +0900 Subject: [PATCH 0685/1782] Move column lines to background layer --- .../Skinning/LegacyColumnBackground.cs | 40 +++++++++++-------- 1 file changed, 24 insertions(+), 16 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyColumnBackground.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyColumnBackground.cs index 543ea23db8..f22903accf 100644 --- a/osu.Game.Rulesets.Mania/Skinning/LegacyColumnBackground.cs +++ b/osu.Game.Rulesets.Mania/Skinning/LegacyColumnBackground.cs @@ -70,30 +70,27 @@ namespace osu.Game.Rulesets.Mania.Skinning Color4 lightColour = GetColumnSkinConfig(skin, LegacyManiaSkinConfigurationLookups.ColumnLightColour)?.Value ?? Color4.White; - Drawable background; + Container backgroundLayer; + Drawable leftLine; + Drawable rightLine; InternalChildren = new[] { - background = new Box + backgroundLayer = new Container { RelativeSizeAxes = Axes.Both, - Colour = backgroundColour + Child = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = backgroundColour + }, }, hitTargetContainer = new Container { RelativeSizeAxes = Axes.Both, Children = new[] { - // In legacy skins, the hit target takes on the full stage size and is sandwiched between the column background and the column light. - // To simulate this effect in lazer's hierarchy, the hit target is added to the first column's background and manually extended to the full size of the stage. - // Adding to the first columns allows depth issues to be resolved - if it were added to the last column, the previous column lights would appear below it. - // This still means that the hit target will appear below the next column backgrounds, but that's a much easier problem to solve by proxying the backgrounds below. - hitTarget = new LegacyHitTarget - { - RelativeSizeAxes = Axes.Y, - Alpha = hasHitTarget ? 1 : 0 - }, - new Box + leftLine = new Box { RelativeSizeAxes = Axes.Y, Width = leftLineWidth, @@ -101,7 +98,7 @@ namespace osu.Game.Rulesets.Mania.Skinning Colour = lineColour, Alpha = hasLeftLine ? 1 : 0 }, - new Box + rightLine = new Box { Anchor = Anchor.TopRight, Origin = Anchor.TopRight, @@ -110,7 +107,16 @@ namespace osu.Game.Rulesets.Mania.Skinning Scale = new Vector2(0.740f, 1), Colour = lineColour, Alpha = hasRightLine ? 1 : 0 - } + }, + // In legacy skins, the hit target takes on the full stage size and is sandwiched between the column background and the column light. + // To simulate this effect in lazer's hierarchy, the hit target is added to the first column's background and manually extended to the full size of the stage. + // Adding to the first columns allows depth issues to be resolved - if it were added to the last column, the previous column lights would appear below it. + // This still means that the hit target will appear below the next column backgrounds, but that's a much easier problem to solve by proxying the background layer below. + hitTarget = new LegacyHitTarget + { + RelativeSizeAxes = Axes.Y, + Alpha = hasHitTarget ? 1 : 0 + }, } }, lightContainer = new Container @@ -132,7 +138,9 @@ namespace osu.Game.Rulesets.Mania.Skinning }; // Resolve depth issues with the hit target appearing under the next column backgrounds by proxying to the stage background (always below the columns). - stageBackground?.AddColumnBackground(background.CreateProxy()); + backgroundLayer.Add(leftLine.CreateProxy()); + backgroundLayer.Add(rightLine.CreateProxy()); + stageBackground?.AddColumnBackground(backgroundLayer.CreateProxy()); direction.BindTo(scrollingInfo.Direction); direction.BindValueChanged(onDirectionChanged, true); From 018523a43a8c11243b6806fc4c4adf3384e24e6f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 25 Aug 2020 01:21:27 +0900 Subject: [PATCH 0686/1782] Rework to remove cross-class pollutions --- .../Beatmaps/StageDefinition.cs | 4 +- osu.Game.Rulesets.Mania/ManiaSkinComponent.cs | 11 +- .../Skinning/LegacyColumnBackground.cs | 96 +------------- .../Skinning/LegacyStageBackground.cs | 124 +++++++++++++++++- .../Skinning/ManiaLegacySkinTransformer.cs | 11 +- osu.Game.Rulesets.Mania/UI/Column.cs | 2 - osu.Game.Rulesets.Mania/UI/ColumnFlow.cs | 105 +++++++++++++++ osu.Game.Rulesets.Mania/UI/Stage.cs | 55 ++------ 8 files changed, 252 insertions(+), 156 deletions(-) create mode 100644 osu.Game.Rulesets.Mania/UI/ColumnFlow.cs diff --git a/osu.Game.Rulesets.Mania/Beatmaps/StageDefinition.cs b/osu.Game.Rulesets.Mania/Beatmaps/StageDefinition.cs index 2557f2acdf..3052fc7d34 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/StageDefinition.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/StageDefinition.cs @@ -21,14 +21,14 @@ namespace osu.Game.Rulesets.Mania.Beatmaps /// /// The 0-based column index. /// Whether the column is a special column. - public bool IsSpecialColumn(int column) => Columns % 2 == 1 && column == Columns / 2; + public readonly bool IsSpecialColumn(int column) => Columns % 2 == 1 && column == Columns / 2; /// /// Get the type of column given a column index. /// /// The 0-based column index. /// The type of the column. - public ColumnType GetTypeOfColumn(int column) + public readonly ColumnType GetTypeOfColumn(int column) { if (IsSpecialColumn(column)) return ColumnType.Special; diff --git a/osu.Game.Rulesets.Mania/ManiaSkinComponent.cs b/osu.Game.Rulesets.Mania/ManiaSkinComponent.cs index c0c8505f44..f078345fc1 100644 --- a/osu.Game.Rulesets.Mania/ManiaSkinComponent.cs +++ b/osu.Game.Rulesets.Mania/ManiaSkinComponent.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.Mania.Beatmaps; using osu.Game.Rulesets.Mania.UI; using osu.Game.Skinning; @@ -14,15 +15,23 @@ namespace osu.Game.Rulesets.Mania /// public readonly int? TargetColumn; + /// + /// The intended for this component. + /// May be null if the component is not a direct member of a . + /// + public readonly StageDefinition? StageDefinition; + /// /// Creates a new . /// /// The component. /// The intended index for this component. May be null if the component does not exist in a . - public ManiaSkinComponent(ManiaSkinComponents component, int? targetColumn = null) + /// The intended for this component. May be null if the component is not a direct member of a . + public ManiaSkinComponent(ManiaSkinComponents component, int? targetColumn = null, StageDefinition? stageDefinition = null) : base(component) { TargetColumn = targetColumn; + StageDefinition = stageDefinition; } protected override string RulesetPrefix => ManiaRuleset.SHORT_NAME; diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyColumnBackground.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyColumnBackground.cs index f22903accf..acae4cd6fb 100644 --- a/osu.Game.Rulesets.Mania/Skinning/LegacyColumnBackground.cs +++ b/osu.Game.Rulesets.Mania/Skinning/LegacyColumnBackground.cs @@ -1,15 +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 JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Bindings; -using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Skinning; using osuTK; @@ -20,22 +17,12 @@ namespace osu.Game.Rulesets.Mania.Skinning public class LegacyColumnBackground : LegacyManiaColumnElement, IKeyBindingHandler { private readonly IBindable direction = new Bindable(); - private readonly bool isLastColumn; - [CanBeNull] - private readonly LegacyStageBackground stageBackground; - - private Container hitTargetContainer; private Container lightContainer; private Sprite light; - private Drawable hitTarget; - private float hitPosition; - - public LegacyColumnBackground(bool isLastColumn, [CanBeNull] LegacyStageBackground stageBackground) + public LegacyColumnBackground() { - this.isLastColumn = isLastColumn; - this.stageBackground = stageBackground; RelativeSizeAxes = Axes.Both; } @@ -45,80 +32,14 @@ namespace osu.Game.Rulesets.Mania.Skinning string lightImage = skin.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.LightImage)?.Value ?? "mania-stage-light"; - float leftLineWidth = GetColumnSkinConfig(skin, LegacyManiaSkinConfigurationLookups.LeftLineWidth) - ?.Value ?? 1; - float rightLineWidth = GetColumnSkinConfig(skin, LegacyManiaSkinConfigurationLookups.RightLineWidth) - ?.Value ?? 1; - - bool hasLeftLine = leftLineWidth > 0; - bool hasRightLine = rightLineWidth > 0 && skin.GetConfig(LegacySkinConfiguration.LegacySetting.Version)?.Value >= 2.4m - || isLastColumn; - bool hasHitTarget = Column.Index == 0 || stageBackground == null; - - hitPosition = GetColumnSkinConfig(skin, LegacyManiaSkinConfigurationLookups.HitPosition)?.Value - ?? Stage.HIT_TARGET_POSITION; - float lightPosition = GetColumnSkinConfig(skin, LegacyManiaSkinConfigurationLookups.LightPosition)?.Value ?? 0; - Color4 lineColour = GetColumnSkinConfig(skin, LegacyManiaSkinConfigurationLookups.ColumnLineColour)?.Value - ?? Color4.White; - - Color4 backgroundColour = GetColumnSkinConfig(skin, LegacyManiaSkinConfigurationLookups.ColumnBackgroundColour)?.Value - ?? Color4.Black; - Color4 lightColour = GetColumnSkinConfig(skin, LegacyManiaSkinConfigurationLookups.ColumnLightColour)?.Value ?? Color4.White; - Container backgroundLayer; - Drawable leftLine; - Drawable rightLine; - InternalChildren = new[] { - backgroundLayer = new Container - { - RelativeSizeAxes = Axes.Both, - Child = new Box - { - RelativeSizeAxes = Axes.Both, - Colour = backgroundColour - }, - }, - hitTargetContainer = new Container - { - RelativeSizeAxes = Axes.Both, - Children = new[] - { - leftLine = new Box - { - RelativeSizeAxes = Axes.Y, - Width = leftLineWidth, - Scale = new Vector2(0.740f, 1), - Colour = lineColour, - Alpha = hasLeftLine ? 1 : 0 - }, - rightLine = new Box - { - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - RelativeSizeAxes = Axes.Y, - Width = rightLineWidth, - Scale = new Vector2(0.740f, 1), - Colour = lineColour, - Alpha = hasRightLine ? 1 : 0 - }, - // In legacy skins, the hit target takes on the full stage size and is sandwiched between the column background and the column light. - // To simulate this effect in lazer's hierarchy, the hit target is added to the first column's background and manually extended to the full size of the stage. - // Adding to the first columns allows depth issues to be resolved - if it were added to the last column, the previous column lights would appear below it. - // This still means that the hit target will appear below the next column backgrounds, but that's a much easier problem to solve by proxying the background layer below. - hitTarget = new LegacyHitTarget - { - RelativeSizeAxes = Axes.Y, - Alpha = hasHitTarget ? 1 : 0 - }, - } - }, lightContainer = new Container { Origin = Anchor.BottomCentre, @@ -137,11 +58,6 @@ namespace osu.Game.Rulesets.Mania.Skinning } }; - // Resolve depth issues with the hit target appearing under the next column backgrounds by proxying to the stage background (always below the columns). - backgroundLayer.Add(leftLine.CreateProxy()); - backgroundLayer.Add(rightLine.CreateProxy()); - stageBackground?.AddColumnBackground(backgroundLayer.CreateProxy()); - direction.BindTo(scrollingInfo.Direction); direction.BindValueChanged(onDirectionChanged, true); } @@ -152,24 +68,14 @@ namespace osu.Game.Rulesets.Mania.Skinning { lightContainer.Anchor = Anchor.TopCentre; lightContainer.Scale = new Vector2(1, -1); - - hitTargetContainer.Padding = new MarginPadding { Top = hitPosition }; } else { lightContainer.Anchor = Anchor.BottomCentre; lightContainer.Scale = Vector2.One; - - hitTargetContainer.Padding = new MarginPadding { Bottom = hitPosition }; } } - protected override void Update() - { - base.Update(); - hitTarget.Width = stageBackground?.DrawWidth ?? DrawWidth; - } - public bool OnPressed(ManiaAction action) { if (action == Column.Action.Value) diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyStageBackground.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyStageBackground.cs index 3998f21c96..675c154b82 100644 --- a/osu.Game.Rulesets.Mania/Skinning/LegacyStageBackground.cs +++ b/osu.Game.Rulesets.Mania/Skinning/LegacyStageBackground.cs @@ -2,22 +2,31 @@ // 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.Game.Rulesets.Mania.Beatmaps; +using osu.Game.Rulesets.Mania.UI; +using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Skinning; using osuTK; +using osuTK.Graphics; namespace osu.Game.Rulesets.Mania.Skinning { public class LegacyStageBackground : CompositeDrawable { + private readonly StageDefinition stageDefinition; + private Drawable leftSprite; private Drawable rightSprite; - private Container columnBackgroundContainer; + private ColumnFlow columnBackgrounds; - public LegacyStageBackground() + public LegacyStageBackground(StageDefinition stageDefinition) { + this.stageDefinition = stageDefinition; RelativeSizeAxes = Axes.Both; } @@ -46,8 +55,18 @@ namespace osu.Game.Rulesets.Mania.Skinning X = -0.05f, Texture = skin.GetTexture(rightImage) }, - columnBackgroundContainer = new Container { RelativeSizeAxes = Axes.Both } + columnBackgrounds = new ColumnFlow(stageDefinition) + { + RelativeSizeAxes = Axes.Y + }, + new HitTargetInsetContainer + { + Child = new LegacyHitTarget { RelativeSizeAxes = Axes.Both } + } }; + + for (int i = 0; i < stageDefinition.Columns; i++) + columnBackgrounds.SetColumn(i, new ColumnBackground(i, i == stageDefinition.Columns - 1)); } protected override void Update() @@ -61,6 +80,103 @@ namespace osu.Game.Rulesets.Mania.Skinning rightSprite.Scale = new Vector2(1, DrawHeight / rightSprite.Height); } - public void AddColumnBackground(Drawable background) => columnBackgroundContainer.Add(background); + private class ColumnBackground : CompositeDrawable + { + private readonly int columnIndex; + private readonly bool isLastColumn; + + public ColumnBackground(int columnIndex, bool isLastColumn) + { + this.columnIndex = columnIndex; + this.isLastColumn = isLastColumn; + + RelativeSizeAxes = Axes.Both; + } + + [BackgroundDependencyLoader] + private void load(ISkinSource skin) + { + float leftLineWidth = skin.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.LeftLineWidth, columnIndex)?.Value ?? 1; + float rightLineWidth = skin.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.RightLineWidth, columnIndex)?.Value ?? 1; + + bool hasLeftLine = leftLineWidth > 0; + bool hasRightLine = rightLineWidth > 0 && skin.GetConfig(LegacySkinConfiguration.LegacySetting.Version)?.Value >= 2.4m + || isLastColumn; + + Color4 lineColour = skin.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.ColumnLineColour, columnIndex)?.Value ?? Color4.White; + Color4 backgroundColour = skin.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.ColumnBackgroundColour, columnIndex)?.Value ?? Color4.Black; + + InternalChildren = new Drawable[] + { + new Container + { + RelativeSizeAxes = Axes.Both, + Child = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = backgroundColour + }, + }, + new HitTargetInsetContainer + { + RelativeSizeAxes = Axes.Both, + Children = new[] + { + new Box + { + RelativeSizeAxes = Axes.Y, + Width = leftLineWidth, + Scale = new Vector2(0.740f, 1), + Colour = lineColour, + Alpha = hasLeftLine ? 1 : 0 + }, + new Box + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + RelativeSizeAxes = Axes.Y, + Width = rightLineWidth, + Scale = new Vector2(0.740f, 1), + Colour = lineColour, + Alpha = hasRightLine ? 1 : 0 + }, + } + } + }; + } + } + + private class HitTargetInsetContainer : Container + { + private readonly IBindable direction = new Bindable(); + + protected override Container Content => content; + private readonly Container content; + + private float hitPosition; + + public HitTargetInsetContainer() + { + RelativeSizeAxes = Axes.Both; + + InternalChild = content = new Container { RelativeSizeAxes = Axes.Both }; + } + + [BackgroundDependencyLoader] + private void load(ISkinSource skin, IScrollingInfo scrollingInfo) + { + hitPosition = skin.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.HitPosition)?.Value ?? Stage.HIT_TARGET_POSITION; + + direction.BindTo(scrollingInfo.Direction); + direction.BindValueChanged(onDirectionChanged, true); + } + + private void onDirectionChanged(ValueChangedEvent direction) + { + content.Padding = direction.NewValue == ScrollingDirection.Up + ? new MarginPadding { Top = hitPosition } + : new MarginPadding { Bottom = hitPosition }; + } + } } } diff --git a/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs index d928f1080f..439e6f7df2 100644 --- a/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs @@ -9,6 +9,7 @@ using osu.Game.Beatmaps; using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Skinning; using System.Collections.Generic; +using System.Diagnostics; using osu.Framework.Audio.Sample; using osu.Game.Audio; using osu.Game.Rulesets.Objects.Legacy; @@ -57,8 +58,6 @@ namespace osu.Game.Rulesets.Mania.Skinning /// private Lazy hasKeyTexture; - private LegacyStageBackground stageBackground; - public ManiaLegacySkinTransformer(ISkinSource source, IBeatmap beatmap) : base(source) { @@ -90,10 +89,11 @@ namespace osu.Game.Rulesets.Mania.Skinning switch (maniaComponent.Component) { case ManiaSkinComponents.ColumnBackground: - return new LegacyColumnBackground(maniaComponent.TargetColumn == beatmap.TotalColumns - 1, stageBackground); + return new LegacyColumnBackground(); case ManiaSkinComponents.HitTarget: - // Created within the column background, but should not fall back. See comments in LegacyColumnBackground. + // Legacy skins sandwich the hit target between the column background and the column light. + // To preserve this ordering, it's created manually inside LegacyStageBackground. return Drawable.Empty(); case ManiaSkinComponents.KeyArea: @@ -115,7 +115,8 @@ namespace osu.Game.Rulesets.Mania.Skinning return new LegacyHitExplosion(); case ManiaSkinComponents.StageBackground: - return stageBackground = new LegacyStageBackground(); + Debug.Assert(maniaComponent.StageDefinition != null); + return new LegacyStageBackground(maniaComponent.StageDefinition.Value); case ManiaSkinComponents.StageForeground: return new LegacyStageForeground(); diff --git a/osu.Game.Rulesets.Mania/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs index 255ce4c064..de4648e4fa 100644 --- a/osu.Game.Rulesets.Mania/UI/Column.cs +++ b/osu.Game.Rulesets.Mania/UI/Column.cs @@ -68,8 +68,6 @@ namespace osu.Game.Rulesets.Mania.UI TopLevelContainer.Add(HitObjectArea.Explosions.CreateProxy()); } - public override Axes RelativeSizeAxes => Axes.Y; - public ColumnType ColumnType { get; set; } public bool IsSpecial => ColumnType == ColumnType.Special; diff --git a/osu.Game.Rulesets.Mania/UI/ColumnFlow.cs b/osu.Game.Rulesets.Mania/UI/ColumnFlow.cs new file mode 100644 index 0000000000..37ad5c609b --- /dev/null +++ b/osu.Game.Rulesets.Mania/UI/ColumnFlow.cs @@ -0,0 +1,105 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Rulesets.Mania.Beatmaps; +using osu.Game.Rulesets.Mania.Skinning; +using osu.Game.Skinning; + +namespace osu.Game.Rulesets.Mania.UI +{ + /// + /// A which flows its contents according to the s in a . + /// Content can be added to individual columns via . + /// + /// The type of content in each column. + public class ColumnFlow : CompositeDrawable + where TContent : Drawable + { + /// + /// All contents added to this . + /// + public IReadOnlyList Content => columns.Children.Select(c => c.Count == 0 ? null : (TContent)c.Child).ToList(); + + private readonly FillFlowContainer columns; + private readonly StageDefinition stageDefinition; + + public ColumnFlow(StageDefinition stageDefinition) + { + this.stageDefinition = stageDefinition; + + AutoSizeAxes = Axes.X; + + InternalChild = columns = new FillFlowContainer + { + RelativeSizeAxes = Axes.Y, + AutoSizeAxes = Axes.X, + Direction = FillDirection.Horizontal, + }; + + for (int i = 0; i < stageDefinition.Columns; i++) + columns.Add(new Container { RelativeSizeAxes = Axes.Y }); + } + + private ISkinSource currentSkin; + + [BackgroundDependencyLoader] + private void load(ISkinSource skin) + { + currentSkin = skin; + + skin.SourceChanged += onSkinChanged; + onSkinChanged(); + } + + private void onSkinChanged() + { + for (int i = 0; i < stageDefinition.Columns; i++) + { + if (i > 0) + { + float spacing = currentSkin.GetConfig( + new ManiaSkinConfigurationLookup(LegacyManiaSkinConfigurationLookups.ColumnSpacing, i - 1)) + ?.Value ?? Stage.COLUMN_SPACING; + + columns[i].Margin = new MarginPadding { Left = spacing }; + } + + float? width = currentSkin.GetConfig( + new ManiaSkinConfigurationLookup(LegacyManiaSkinConfigurationLookups.ColumnWidth, i)) + ?.Value; + + if (width == null) + // only used by default skin (legacy skins get defaults set in LegacyManiaSkinConfiguration) + columns[i].Width = stageDefinition.IsSpecialColumn(i) ? Column.SPECIAL_COLUMN_WIDTH : Column.COLUMN_WIDTH; + else + columns[i].Width = width.Value; + } + } + + /// + /// Sets the content of one of the columns of this . + /// + /// The index of the column to set the content of. + /// The content. + public void SetColumn(int column, TContent content) => columns[column].Child = content; + + public new MarginPadding Padding + { + get => base.Padding; + set => base.Padding = value; + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + if (currentSkin != null) + currentSkin.SourceChanged -= onSkinChanged; + } + } +} diff --git a/osu.Game.Rulesets.Mania/UI/Stage.cs b/osu.Game.Rulesets.Mania/UI/Stage.cs index 36780b0f80..5944c8c218 100644 --- a/osu.Game.Rulesets.Mania/UI/Stage.cs +++ b/osu.Game.Rulesets.Mania/UI/Stage.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Linq; -using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Pooling; @@ -11,7 +10,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.Mania.Skinning; using osu.Game.Rulesets.Mania.UI.Components; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.UI; @@ -31,8 +29,8 @@ namespace osu.Game.Rulesets.Mania.UI public const float HIT_TARGET_POSITION = 110; - public IReadOnlyList Columns => columnFlow.Children; - private readonly FillFlowContainer columnFlow; + public IReadOnlyList Columns => columnFlow.Content; + private readonly ColumnFlow columnFlow; private readonly JudgementContainer judgements; private readonly DrawablePool judgementPool; @@ -73,16 +71,13 @@ namespace osu.Game.Rulesets.Mania.UI AutoSizeAxes = Axes.X, Children = new Drawable[] { - new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.StageBackground), _ => new DefaultStageBackground()) + new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.StageBackground, stageDefinition: definition), _ => new DefaultStageBackground()) { RelativeSizeAxes = Axes.Both }, - columnFlow = new FillFlowContainer + columnFlow = new ColumnFlow(definition) { - Name = "Columns", RelativeSizeAxes = Axes.Y, - AutoSizeAxes = Axes.X, - Direction = FillDirection.Horizontal, Padding = new MarginPadding { Left = COLUMN_SPACING, Right = COLUMN_SPACING }, }, new Container @@ -102,7 +97,7 @@ namespace osu.Game.Rulesets.Mania.UI RelativeSizeAxes = Axes.Y, } }, - new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.StageForeground), _ => null) + new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.StageForeground, stageDefinition: definition), _ => null) { RelativeSizeAxes = Axes.Both }, @@ -123,6 +118,8 @@ namespace osu.Game.Rulesets.Mania.UI var columnType = definition.GetTypeOfColumn(i); var column = new Column(firstColumnIndex + i) { + RelativeSizeAxes = Axes.Both, + Width = 1, ColumnType = columnType, AccentColour = columnColours[columnType], Action = { Value = columnType == ColumnType.Special ? specialColumnStartAction++ : normalColumnStartAction++ } @@ -132,46 +129,10 @@ namespace osu.Game.Rulesets.Mania.UI } } - private ISkin currentSkin; - - [BackgroundDependencyLoader] - private void load(ISkinSource skin) - { - currentSkin = skin; - skin.SourceChanged += onSkinChanged; - - onSkinChanged(); - } - - private void onSkinChanged() - { - foreach (var col in columnFlow) - { - if (col.Index > 0) - { - float spacing = currentSkin.GetConfig( - new ManiaSkinConfigurationLookup(LegacyManiaSkinConfigurationLookups.ColumnSpacing, col.Index - 1)) - ?.Value ?? COLUMN_SPACING; - - col.Margin = new MarginPadding { Left = spacing }; - } - - float? width = currentSkin.GetConfig( - new ManiaSkinConfigurationLookup(LegacyManiaSkinConfigurationLookups.ColumnWidth, col.Index)) - ?.Value; - - if (width == null) - // only used by default skin (legacy skins get defaults set in LegacyManiaSkinConfiguration) - col.Width = col.IsSpecial ? Column.SPECIAL_COLUMN_WIDTH : Column.COLUMN_WIDTH; - else - col.Width = width.Value; - } - } - public void AddColumn(Column c) { topLevelContainer.Add(c.TopLevelContainer.CreateProxy()); - columnFlow.Add(c); + columnFlow.SetColumn(c.Index, c); AddNested(c); } From 50d5b020b7c579a7bea931d58267e793a90ce412 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 24 Aug 2020 20:19:33 +0200 Subject: [PATCH 0687/1782] Add failing test case --- .../TestSceneBeatmapSetOverlaySuccessRate.cs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlaySuccessRate.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlaySuccessRate.cs index 4cb22bf1fe..954eb74ad9 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlaySuccessRate.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlaySuccessRate.cs @@ -7,8 +7,10 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Testing; using osu.Framework.Utils; using osu.Game.Beatmaps; +using osu.Game.Graphics.UserInterface; using osu.Game.Overlays; using osu.Game.Overlays.BeatmapSet; using osu.Game.Screens.Select.Details; @@ -72,6 +74,20 @@ namespace osu.Game.Tests.Visual.Online }; } + [Test] + public void TestOnlyFailMetrics() + { + AddStep("set beatmap", () => successRate.Beatmap = new BeatmapInfo + { + Metrics = new BeatmapMetrics + { + Fails = Enumerable.Range(1, 100).ToArray(), + } + }); + AddAssert("graph max values correct", + () => successRate.ChildrenOfType().All(graph => graph.MaxValue == 100)); + } + private class GraphExposingSuccessRate : SuccessRate { public new FailRetryGraph Graph => base.Graph; From cc6ae8e3bd2a4ff21dee50dc7cd72f1663f32428 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 24 Aug 2020 20:41:31 +0200 Subject: [PATCH 0688/1782] Fix crash if only one count list is received from API --- .../Screens/Select/Details/FailRetryGraph.cs | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Select/Details/FailRetryGraph.cs b/osu.Game/Screens/Select/Details/FailRetryGraph.cs index 134fd0598a..7cc80acfd3 100644 --- a/osu.Game/Screens/Select/Details/FailRetryGraph.cs +++ b/osu.Game/Screens/Select/Details/FailRetryGraph.cs @@ -29,16 +29,30 @@ namespace osu.Game.Screens.Select.Details var retries = Metrics?.Retries ?? Array.Empty(); var fails = Metrics?.Fails ?? Array.Empty(); + var retriesAndFails = sumRetriesAndFails(retries, fails); - float maxValue = fails.Any() ? fails.Zip(retries, (fail, retry) => fail + retry).Max() : 0; + float maxValue = retriesAndFails.Any() ? retriesAndFails.Max() : 0; failGraph.MaxValue = maxValue; retryGraph.MaxValue = maxValue; - failGraph.Values = fails.Select(f => (float)f); - retryGraph.Values = retries.Zip(fails, (retry, fail) => retry + Math.Clamp(fail, 0, maxValue)); + failGraph.Values = fails.Select(v => (float)v); + retryGraph.Values = retriesAndFails.Select(v => (float)v); } } + private int[] sumRetriesAndFails(int[] retries, int[] fails) + { + var result = new int[Math.Max(retries.Length, fails.Length)]; + + for (int i = 0; i < retries.Length; ++i) + result[i] = retries[i]; + + for (int i = 0; i < fails.Length; ++i) + result[i] += fails[i]; + + return result; + } + public FailRetryGraph() { Children = new[] From 29b4d98aaccdbd2b4440289a04cb8247492a2092 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 24 Aug 2020 20:41:50 +0200 Subject: [PATCH 0689/1782] Show retry/fail graph when either list is present --- osu.Game/Screens/Select/BeatmapDetails.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/BeatmapDetails.cs b/osu.Game/Screens/Select/BeatmapDetails.cs index 9669a1391c..0ee52f3e48 100644 --- a/osu.Game/Screens/Select/BeatmapDetails.cs +++ b/osu.Game/Screens/Select/BeatmapDetails.cs @@ -236,7 +236,7 @@ namespace osu.Game.Screens.Select private void updateMetrics() { var hasRatings = beatmap?.BeatmapSet?.Metrics?.Ratings?.Any() ?? false; - var hasRetriesFails = (beatmap?.Metrics?.Retries?.Any() ?? false) && (beatmap?.Metrics.Fails?.Any() ?? false); + var hasRetriesFails = (beatmap?.Metrics?.Retries?.Any() ?? false) || (beatmap?.Metrics?.Fails?.Any() ?? false); if (hasRatings) { From dbf90551d65dda6b3bb8e03ad67bd75a124cd07b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 24 Aug 2020 20:47:29 +0200 Subject: [PATCH 0690/1782] Add coverage for empty metrics case --- .../Online/TestSceneBeatmapSetOverlaySuccessRate.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlaySuccessRate.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlaySuccessRate.cs index 954eb74ad9..fd5c188b94 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlaySuccessRate.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlaySuccessRate.cs @@ -88,6 +88,18 @@ namespace osu.Game.Tests.Visual.Online () => successRate.ChildrenOfType().All(graph => graph.MaxValue == 100)); } + [Test] + public void TestEmptyMetrics() + { + AddStep("set beatmap", () => successRate.Beatmap = new BeatmapInfo + { + Metrics = new BeatmapMetrics() + }); + + AddAssert("graph max values correct", + () => successRate.ChildrenOfType().All(graph => graph.MaxValue == 0)); + } + private class GraphExposingSuccessRate : SuccessRate { public new FailRetryGraph Graph => base.Graph; From 723e5cafb6d1610c1857e7036a10d514db1c47eb Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 25 Aug 2020 14:49:04 +0900 Subject: [PATCH 0691/1782] Fix column potentially added at wrong indices --- osu.Game.Rulesets.Mania/UI/Stage.cs | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Mania/UI/Stage.cs b/osu.Game.Rulesets.Mania/UI/Stage.cs index 5944c8c218..dfb1ee210d 100644 --- a/osu.Game.Rulesets.Mania/UI/Stage.cs +++ b/osu.Game.Rulesets.Mania/UI/Stage.cs @@ -36,7 +36,6 @@ namespace osu.Game.Rulesets.Mania.UI private readonly DrawablePool judgementPool; private readonly Drawable barLineContainer; - private readonly Container topLevelContainer; private readonly Dictionary columnColours = new Dictionary { @@ -60,6 +59,8 @@ namespace osu.Game.Rulesets.Mania.UI RelativeSizeAxes = Axes.Y; AutoSizeAxes = Axes.X; + Container topLevelContainer; + InternalChildren = new Drawable[] { judgementPool = new DrawablePool(2), @@ -116,6 +117,7 @@ namespace osu.Game.Rulesets.Mania.UI for (int i = 0; i < definition.Columns; i++) { var columnType = definition.GetTypeOfColumn(i); + var column = new Column(firstColumnIndex + i) { RelativeSizeAxes = Axes.Both, @@ -125,17 +127,12 @@ namespace osu.Game.Rulesets.Mania.UI Action = { Value = columnType == ColumnType.Special ? specialColumnStartAction++ : normalColumnStartAction++ } }; - AddColumn(column); + topLevelContainer.Add(column.TopLevelContainer.CreateProxy()); + columnFlow.SetColumn(i, column); + AddNested(column); } } - public void AddColumn(Column c) - { - topLevelContainer.Add(c.TopLevelContainer.CreateProxy()); - columnFlow.SetColumn(c.Index, c); - AddNested(c); - } - public override void Add(DrawableHitObject h) { var maniaObject = (ManiaHitObject)h.HitObject; From 7e9567dae978a1030807a33f97d8355c64719ba1 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 25 Aug 2020 14:49:29 +0900 Subject: [PATCH 0692/1782] Fix tests --- .../Skinning/TestSceneStageBackground.cs | 4 +++- .../Skinning/TestSceneStageForeground.cs | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneStageBackground.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneStageBackground.cs index 87c84cf89c..a15fb392d6 100644 --- a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneStageBackground.cs +++ b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneStageBackground.cs @@ -3,6 +3,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.UI.Components; using osu.Game.Skinning; @@ -13,7 +14,8 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning [BackgroundDependencyLoader] private void load() { - SetContents(() => new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.StageBackground), _ => new DefaultStageBackground()) + SetContents(() => new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.StageBackground, stageDefinition: new StageDefinition { Columns = 4 }), + _ => new DefaultStageBackground()) { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneStageForeground.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneStageForeground.cs index 4e99068ed5..bceee1c599 100644 --- a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneStageForeground.cs +++ b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneStageForeground.cs @@ -3,6 +3,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Skinning; namespace osu.Game.Rulesets.Mania.Tests.Skinning @@ -12,7 +13,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning [BackgroundDependencyLoader] private void load() { - SetContents(() => new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.StageForeground), _ => null) + SetContents(() => new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.StageForeground, stageDefinition: new StageDefinition { Columns = 4 }), _ => null) { Anchor = Anchor.Centre, Origin = Anchor.Centre, From ab8d9be095e4925d67cb1c06be49a2e92f24195f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 25 Aug 2020 15:16:41 +0900 Subject: [PATCH 0693/1782] Move out into a separate method --- .../Skinning/CatchLegacySkinTransformer.cs | 2 +- .../Skinning/LegacyFruitPiece.cs | 2 +- .../Skinning/LegacyColumnBackground.cs | 17 +++++-- .../Skinning/LegacyHitTarget.cs | 2 +- .../Skinning/LegacyMainCirclePiece.cs | 2 +- .../Skinning/LegacySliderBall.cs | 4 +- .../Skinning/LegacyCirclePiece.cs | 2 +- .../Skinning/LegacyDrumRoll.cs | 6 +-- osu.Game.Rulesets.Taiko/Skinning/LegacyHit.cs | 7 +-- .../Skinning/LegacyColourCompatibility.cs | 46 +++++++++++++++++++ osu.Game/Skinning/LegacySkinExtensions.cs | 31 ------------- 11 files changed, 73 insertions(+), 48 deletions(-) create mode 100644 osu.Game/Skinning/LegacyColourCompatibility.cs diff --git a/osu.Game.Rulesets.Catch/Skinning/CatchLegacySkinTransformer.cs b/osu.Game.Rulesets.Catch/Skinning/CatchLegacySkinTransformer.cs index 5abd87d6f4..ea2f031d65 100644 --- a/osu.Game.Rulesets.Catch/Skinning/CatchLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Catch/Skinning/CatchLegacySkinTransformer.cs @@ -66,7 +66,7 @@ namespace osu.Game.Rulesets.Catch.Skinning if (result == null) return null; - result.Value = result.Value.ToLegacyColour(); + result.Value = LegacyColourCompatibility.DisallowZeroAlpha(result.Value); return (IBindable)result; } diff --git a/osu.Game.Rulesets.Catch/Skinning/LegacyFruitPiece.cs b/osu.Game.Rulesets.Catch/Skinning/LegacyFruitPiece.cs index c9dd1d1f3e..381d066750 100644 --- a/osu.Game.Rulesets.Catch/Skinning/LegacyFruitPiece.cs +++ b/osu.Game.Rulesets.Catch/Skinning/LegacyFruitPiece.cs @@ -75,7 +75,7 @@ namespace osu.Game.Rulesets.Catch.Skinning { base.LoadComplete(); - accentColour.BindValueChanged(colour => colouredSprite.Colour = colour.NewValue.ToLegacyColour(), true); + accentColour.BindValueChanged(colour => colouredSprite.Colour = LegacyColourCompatibility.DisallowZeroAlpha(colour.NewValue), true); } } } diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyColumnBackground.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyColumnBackground.cs index 54a16b840f..da6075248a 100644 --- a/osu.Game.Rulesets.Mania/Skinning/LegacyColumnBackground.cs +++ b/osu.Game.Rulesets.Mania/Skinning/LegacyColumnBackground.cs @@ -58,14 +58,20 @@ namespace osu.Game.Rulesets.Mania.Skinning InternalChildren = new Drawable[] { - new Box { RelativeSizeAxes = Axes.Both }.WithLegacyColour(backgroundColour), + LegacyColourCompatibility.ApplyWithDoubledAlpha(new Box + { + RelativeSizeAxes = Axes.Both + }, backgroundColour), new Container { RelativeSizeAxes = Axes.Y, Width = leftLineWidth, Scale = new Vector2(0.740f, 1), Alpha = hasLeftLine ? 1 : 0, - Child = new Box { RelativeSizeAxes = Axes.Both }.WithLegacyColour(lineColour) + Child = LegacyColourCompatibility.ApplyWithDoubledAlpha(new Box + { + RelativeSizeAxes = Axes.Both + }, lineColour) }, new Container { @@ -75,7 +81,10 @@ namespace osu.Game.Rulesets.Mania.Skinning Width = rightLineWidth, Scale = new Vector2(0.740f, 1), Alpha = hasRightLine ? 1 : 0, - Child = new Box { RelativeSizeAxes = Axes.Both }.WithLegacyColour(lineColour) + Child = LegacyColourCompatibility.ApplyWithDoubledAlpha(new Box + { + RelativeSizeAxes = Axes.Both + }, lineColour) }, lightContainer = new Container { @@ -86,7 +95,7 @@ namespace osu.Game.Rulesets.Mania.Skinning { Anchor = Anchor.BottomCentre, Origin = Anchor.BottomCentre, - Colour = lightColour.ToLegacyColour(), + Colour = LegacyColourCompatibility.DisallowZeroAlpha(lightColour), Texture = skin.GetTexture(lightImage), RelativeSizeAxes = Axes.X, Width = 1, diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyHitTarget.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyHitTarget.cs index 2177eaa5e6..48504e6548 100644 --- a/osu.Game.Rulesets.Mania/Skinning/LegacyHitTarget.cs +++ b/osu.Game.Rulesets.Mania/Skinning/LegacyHitTarget.cs @@ -56,7 +56,7 @@ namespace osu.Game.Rulesets.Mania.Skinning Anchor = Anchor.CentreLeft, RelativeSizeAxes = Axes.X, Height = 1, - Colour = lineColour.ToLegacyColour(), + Colour = LegacyColourCompatibility.DisallowZeroAlpha(lineColour), Alpha = showJudgementLine ? 0.9f : 0 } } diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs b/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs index 8a6beddb51..d15a0a3203 100644 --- a/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs +++ b/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs @@ -106,7 +106,7 @@ namespace osu.Game.Rulesets.Osu.Skinning base.LoadComplete(); state.BindValueChanged(updateState, true); - accentColour.BindValueChanged(colour => hitCircleSprite.Colour = colour.NewValue.ToLegacyColour(), true); + accentColour.BindValueChanged(colour => hitCircleSprite.Colour = LegacyColourCompatibility.DisallowZeroAlpha(colour.NewValue), true); indexInCurrentCombo.BindValueChanged(index => hitCircleText.Text = (index.NewValue + 1).ToString(), true); } diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacySliderBall.cs b/osu.Game.Rulesets.Osu/Skinning/LegacySliderBall.cs index 27dec1b691..25ab96445a 100644 --- a/osu.Game.Rulesets.Osu/Skinning/LegacySliderBall.cs +++ b/osu.Game.Rulesets.Osu/Skinning/LegacySliderBall.cs @@ -39,11 +39,11 @@ namespace osu.Game.Rulesets.Osu.Skinning Texture = skin.GetTexture("sliderb-nd"), Colour = new Color4(5, 5, 5, 255), }, - animationContent.WithLegacyColour(ballColour).With(d => + LegacyColourCompatibility.ApplyWithDoubledAlpha(animationContent.With(d => { d.Anchor = Anchor.Centre; d.Origin = Anchor.Centre; - }), + }), ballColour), layerSpec = new Sprite { Anchor = Anchor.Centre, diff --git a/osu.Game.Rulesets.Taiko/Skinning/LegacyCirclePiece.cs b/osu.Game.Rulesets.Taiko/Skinning/LegacyCirclePiece.cs index ed69b529ed..9b73ccd248 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/LegacyCirclePiece.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/LegacyCirclePiece.cs @@ -90,7 +90,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning private void updateAccentColour() { - backgroundLayer.Colour = accentColour.ToLegacyColour(); + backgroundLayer.Colour = LegacyColourCompatibility.DisallowZeroAlpha(accentColour); } } } diff --git a/osu.Game.Rulesets.Taiko/Skinning/LegacyDrumRoll.cs b/osu.Game.Rulesets.Taiko/Skinning/LegacyDrumRoll.cs index 6bb8f9433e..025eff53d5 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/LegacyDrumRoll.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/LegacyDrumRoll.cs @@ -76,9 +76,9 @@ namespace osu.Game.Rulesets.Taiko.Skinning private void updateAccentColour() { - headCircle.AccentColour = accentColour.ToLegacyColour(); - body.Colour = accentColour.ToLegacyColour(); - end.Colour = accentColour.ToLegacyColour(); + headCircle.AccentColour = LegacyColourCompatibility.DisallowZeroAlpha(accentColour); + body.Colour = LegacyColourCompatibility.DisallowZeroAlpha(accentColour); + end.Colour = LegacyColourCompatibility.DisallowZeroAlpha(accentColour); } } } diff --git a/osu.Game.Rulesets.Taiko/Skinning/LegacyHit.cs b/osu.Game.Rulesets.Taiko/Skinning/LegacyHit.cs index f36aae205a..b11b64c22c 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/LegacyHit.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/LegacyHit.cs @@ -19,9 +19,10 @@ namespace osu.Game.Rulesets.Taiko.Skinning [BackgroundDependencyLoader] private void load() { - AccentColour = (component == TaikoSkinComponents.CentreHit - ? new Color4(235, 69, 44, 255) - : new Color4(67, 142, 172, 255)).ToLegacyColour(); + AccentColour = LegacyColourCompatibility.DisallowZeroAlpha( + component == TaikoSkinComponents.CentreHit + ? new Color4(235, 69, 44, 255) + : new Color4(67, 142, 172, 255)); } } } diff --git a/osu.Game/Skinning/LegacyColourCompatibility.cs b/osu.Game/Skinning/LegacyColourCompatibility.cs new file mode 100644 index 0000000000..b842b50426 --- /dev/null +++ b/osu.Game/Skinning/LegacyColourCompatibility.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 osu.Framework.Graphics; +using osuTK.Graphics; + +namespace osu.Game.Skinning +{ + /// + /// Compatibility methods to convert osu!stable colours to osu!lazer-compatible ones. Should be used for legacy skins only. + /// + public static class LegacyColourCompatibility + { + /// + /// Forces an alpha of 1 if a given is fully transparent. + /// + /// + /// This is equivalent to setting colour post-constructor in osu!stable. + /// + /// The to disallow zero alpha on. + /// The resultant . + public static Color4 DisallowZeroAlpha(Color4 colour) + { + if (colour.A == 0) + colour.A = 1; + return colour; + } + + /// + /// Applies a to a , doubling the alpha value into the property. + /// + /// + /// This is equivalent to setting colour in the constructor in osu!stable. + /// + /// The to apply the colour to. + /// The to apply. + /// The given . + public static T ApplyWithDoubledAlpha(T drawable, Color4 colour) + where T : Drawable + { + drawable.Alpha = colour.A; + drawable.Colour = DisallowZeroAlpha(colour); + return drawable; + } + } +} diff --git a/osu.Game/Skinning/LegacySkinExtensions.cs b/osu.Game/Skinning/LegacySkinExtensions.cs index 7420f82f04..bb46dc8b9f 100644 --- a/osu.Game/Skinning/LegacySkinExtensions.cs +++ b/osu.Game/Skinning/LegacySkinExtensions.cs @@ -9,7 +9,6 @@ using osu.Framework.Graphics.Animations; using osu.Framework.Graphics.OpenGL.Textures; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; -using osuTK.Graphics; using static osu.Game.Skinning.LegacySkinConfiguration; namespace osu.Game.Skinning @@ -63,36 +62,6 @@ namespace osu.Game.Skinning } } - /// - /// The resultant colour after setting a post-constructor colour in osu!stable. - /// - /// The to convert. - /// The converted . - public static Color4 ToLegacyColour(this Color4 colour) - { - if (colour.A == 0) - colour.A = 1; - return colour; - } - - /// - /// Equivalent of setting a colour in the constructor in osu!stable. - /// Doubles the alpha channel into and uses to set . - /// - /// - /// Beware: Any existing value in is overwritten. - /// - /// The to set the colour of. - /// The to set. - /// The given . - public static T WithLegacyColour(this T drawable, Color4 colour) - where T : Drawable - { - drawable.Alpha = colour.A; - drawable.Colour = ToLegacyColour(colour); - return drawable; - } - public class SkinnableTextureAnimation : TextureAnimation { [Resolved(canBeNull: true)] From 7a70d063428890ee92e843001b31c08433ebe3fc Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 25 Aug 2020 15:35:37 +0900 Subject: [PATCH 0694/1782] Add support for custom LightingN paths --- osu.Game/Skinning/LegacyManiaSkinDecoder.cs | 1 + osu.Game/Skinning/LegacySkin.cs | 3 +++ 2 files changed, 4 insertions(+) diff --git a/osu.Game/Skinning/LegacyManiaSkinDecoder.cs b/osu.Game/Skinning/LegacyManiaSkinDecoder.cs index aebc229f7c..1e6102eaa4 100644 --- a/osu.Game/Skinning/LegacyManiaSkinDecoder.cs +++ b/osu.Game/Skinning/LegacyManiaSkinDecoder.cs @@ -116,6 +116,7 @@ namespace osu.Game.Skinning case string _ when pair.Key.StartsWith("KeyImage"): case string _ when pair.Key.StartsWith("Hit"): case string _ when pair.Key.StartsWith("Stage"): + case string _ when pair.Key.StartsWith("Lighting"): currentConfig.ImageLookups[pair.Key] = pair.Value; break; } diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 02d07eee45..10fb476728 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -173,6 +173,9 @@ namespace osu.Game.Skinning case LegacyManiaSkinConfigurationLookups.ShowJudgementLine: return SkinUtils.As(new Bindable(existing.ShowJudgementLine)); + case LegacyManiaSkinConfigurationLookups.ExplosionImage: + return SkinUtils.As(getManiaImage(existing, "LightingN")); + case LegacyManiaSkinConfigurationLookups.ExplosionScale: Debug.Assert(maniaLookup.TargetColumn != null); From ff72ccabd8c15a98e597eb7464d6f5833cc122fd Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 25 Aug 2020 18:44:32 +0900 Subject: [PATCH 0695/1782] Rename method --- osu.Game.Rulesets.Mania/Skinning/LegacyStageBackground.cs | 2 +- osu.Game.Rulesets.Mania/UI/ColumnFlow.cs | 4 ++-- osu.Game.Rulesets.Mania/UI/Stage.cs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyStageBackground.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyStageBackground.cs index 675c154b82..19ec86b1ed 100644 --- a/osu.Game.Rulesets.Mania/Skinning/LegacyStageBackground.cs +++ b/osu.Game.Rulesets.Mania/Skinning/LegacyStageBackground.cs @@ -66,7 +66,7 @@ namespace osu.Game.Rulesets.Mania.Skinning }; for (int i = 0; i < stageDefinition.Columns; i++) - columnBackgrounds.SetColumn(i, new ColumnBackground(i, i == stageDefinition.Columns - 1)); + columnBackgrounds.SetContentForColumn(i, new ColumnBackground(i, i == stageDefinition.Columns - 1)); } protected override void Update() diff --git a/osu.Game.Rulesets.Mania/UI/ColumnFlow.cs b/osu.Game.Rulesets.Mania/UI/ColumnFlow.cs index 37ad5c609b..aef82d4c08 100644 --- a/osu.Game.Rulesets.Mania/UI/ColumnFlow.cs +++ b/osu.Game.Rulesets.Mania/UI/ColumnFlow.cs @@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Mania.UI { /// /// A which flows its contents according to the s in a . - /// Content can be added to individual columns via . + /// Content can be added to individual columns via . /// /// The type of content in each column. public class ColumnFlow : CompositeDrawable @@ -86,7 +86,7 @@ namespace osu.Game.Rulesets.Mania.UI /// /// The index of the column to set the content of. /// The content. - public void SetColumn(int column, TContent content) => columns[column].Child = content; + public void SetContentForColumn(int column, TContent content) => columns[column].Child = content; public new MarginPadding Padding { diff --git a/osu.Game.Rulesets.Mania/UI/Stage.cs b/osu.Game.Rulesets.Mania/UI/Stage.cs index dfb1ee210d..f4b00ec476 100644 --- a/osu.Game.Rulesets.Mania/UI/Stage.cs +++ b/osu.Game.Rulesets.Mania/UI/Stage.cs @@ -128,7 +128,7 @@ namespace osu.Game.Rulesets.Mania.UI }; topLevelContainer.Add(column.TopLevelContainer.CreateProxy()); - columnFlow.SetColumn(i, column); + columnFlow.SetContentForColumn(i, column); AddNested(column); } } From 6c7475f085f1a06b238226d71f27e528b222fbf3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 25 Aug 2020 18:56:15 +0900 Subject: [PATCH 0696/1782] Fix snapped distances potentially exceeding the source distance This results in slider placement including "excess" length, where the curve is not applied to the placed path. This is generally not what we want. I considered adding a bool parameter (or enum) to change the floor/rounding mode, but on further examination I think this is what we always expect from this function. --- .../TestSceneHitObjectComposerDistanceSnapping.cs | 14 +++++++------- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 8 +++++++- osu.Game/Rulesets/Edit/IPositionSnapProvider.cs | 1 + 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/osu.Game.Tests/Editing/TestSceneHitObjectComposerDistanceSnapping.cs b/osu.Game.Tests/Editing/TestSceneHitObjectComposerDistanceSnapping.cs index 168ec0f09d..bd34eaff63 100644 --- a/osu.Game.Tests/Editing/TestSceneHitObjectComposerDistanceSnapping.cs +++ b/osu.Game.Tests/Editing/TestSceneHitObjectComposerDistanceSnapping.cs @@ -169,17 +169,17 @@ namespace osu.Game.Tests.Editing [Test] public void GetSnappedDistanceFromDistance() { - assertSnappedDistance(50, 100); + assertSnappedDistance(50, 0); assertSnappedDistance(100, 100); - assertSnappedDistance(150, 200); + assertSnappedDistance(150, 100); assertSnappedDistance(200, 200); - assertSnappedDistance(250, 300); + assertSnappedDistance(250, 200); AddStep("set slider multiplier = 2", () => composer.EditorBeatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier = 2); assertSnappedDistance(50, 0); - assertSnappedDistance(100, 200); - assertSnappedDistance(150, 200); + assertSnappedDistance(100, 0); + assertSnappedDistance(150, 0); assertSnappedDistance(200, 200); assertSnappedDistance(250, 200); @@ -190,8 +190,8 @@ namespace osu.Game.Tests.Editing }); assertSnappedDistance(50, 0); - assertSnappedDistance(100, 200); - assertSnappedDistance(150, 200); + assertSnappedDistance(100, 0); + assertSnappedDistance(150, 0); assertSnappedDistance(200, 200); assertSnappedDistance(250, 200); assertSnappedDistance(400, 400); diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index c25fb03fd0..d0b06ce0ad 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -293,7 +293,13 @@ namespace osu.Game.Rulesets.Edit public override float GetSnappedDistanceFromDistance(double referenceTime, float distance) { - var snappedEndTime = BeatSnapProvider.SnapTime(referenceTime + DistanceToDuration(referenceTime, distance), referenceTime); + double actualDuration = referenceTime + DistanceToDuration(referenceTime, distance); + + double snappedEndTime = BeatSnapProvider.SnapTime(actualDuration, referenceTime); + + // we don't want to exceed the actual duration and snap to a point in the future. + if (snappedEndTime > actualDuration) + snappedEndTime -= BeatSnapProvider.GetBeatLengthAtTime(referenceTime); return DurationToDistance(referenceTime, snappedEndTime - referenceTime); } diff --git a/osu.Game/Rulesets/Edit/IPositionSnapProvider.cs b/osu.Game/Rulesets/Edit/IPositionSnapProvider.cs index c854c06031..cce631464f 100644 --- a/osu.Game/Rulesets/Edit/IPositionSnapProvider.cs +++ b/osu.Game/Rulesets/Edit/IPositionSnapProvider.cs @@ -47,6 +47,7 @@ namespace osu.Game.Rulesets.Edit /// /// Converts an unsnapped distance to a snapped distance. + /// The returned distance will always be floored (as to never exceed the provided . /// /// The time of the timing point which resides in. /// The distance to convert. From c09cef4fca2d5264d96958cc388534012aa7ede0 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 25 Aug 2020 19:39:03 +0900 Subject: [PATCH 0697/1782] Apply post-merge fixes to LegacyStageBackground --- .../Skinning/LegacyStageBackground.cs | 29 ++++++++++--------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyStageBackground.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyStageBackground.cs index 19ec86b1ed..16a6123724 100644 --- a/osu.Game.Rulesets.Mania/Skinning/LegacyStageBackground.cs +++ b/osu.Game.Rulesets.Mania/Skinning/LegacyStageBackground.cs @@ -108,37 +108,38 @@ namespace osu.Game.Rulesets.Mania.Skinning InternalChildren = new Drawable[] { - new Container + LegacyColourCompatibility.ApplyWithDoubledAlpha(new Box { - RelativeSizeAxes = Axes.Both, - Child = new Box - { - RelativeSizeAxes = Axes.Both, - Colour = backgroundColour - }, - }, + RelativeSizeAxes = Axes.Both + }, backgroundColour), new HitTargetInsetContainer { RelativeSizeAxes = Axes.Both, Children = new[] { - new Box + new Container { RelativeSizeAxes = Axes.Y, Width = leftLineWidth, Scale = new Vector2(0.740f, 1), - Colour = lineColour, - Alpha = hasLeftLine ? 1 : 0 + Alpha = hasLeftLine ? 1 : 0, + Child = LegacyColourCompatibility.ApplyWithDoubledAlpha(new Box + { + RelativeSizeAxes = Axes.Both + }, lineColour) }, - new Box + new Container { Anchor = Anchor.TopRight, Origin = Anchor.TopRight, RelativeSizeAxes = Axes.Y, Width = rightLineWidth, Scale = new Vector2(0.740f, 1), - Colour = lineColour, - Alpha = hasRightLine ? 1 : 0 + Alpha = hasRightLine ? 1 : 0, + Child = LegacyColourCompatibility.ApplyWithDoubledAlpha(new Box + { + RelativeSizeAxes = Axes.Both + }, lineColour) }, } } From 0800e4379689e8ac301ea8906f4a0e9ce7e518ce Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 25 Aug 2020 19:57:49 +0900 Subject: [PATCH 0698/1782] Remove padding from columns --- osu.Game.Rulesets.Mania/UI/Stage.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania/UI/Stage.cs b/osu.Game.Rulesets.Mania/UI/Stage.cs index f4b00ec476..e7a2de266d 100644 --- a/osu.Game.Rulesets.Mania/UI/Stage.cs +++ b/osu.Game.Rulesets.Mania/UI/Stage.cs @@ -79,7 +79,6 @@ namespace osu.Game.Rulesets.Mania.UI columnFlow = new ColumnFlow(definition) { RelativeSizeAxes = Axes.Y, - Padding = new MarginPadding { Left = COLUMN_SPACING, Right = COLUMN_SPACING }, }, new Container { From 127330b8f9bb7ff7a9d03ad3db5044c002404f37 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 25 Aug 2020 20:57:31 +0900 Subject: [PATCH 0699/1782] Add 1ms lenience to avoid potential precision issues --- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index d0b06ce0ad..f134db1ffe 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -25,7 +25,7 @@ using osu.Game.Screens.Edit.Components.RadioButtons; using osu.Game.Screens.Edit.Compose; using osu.Game.Screens.Edit.Compose.Components; using osuTK; -using Key = osuTK.Input.Key; +using osuTK.Input; namespace osu.Game.Rulesets.Edit { @@ -297,9 +297,12 @@ namespace osu.Game.Rulesets.Edit double snappedEndTime = BeatSnapProvider.SnapTime(actualDuration, referenceTime); + double beatLength = BeatSnapProvider.GetBeatLengthAtTime(referenceTime); + // we don't want to exceed the actual duration and snap to a point in the future. - if (snappedEndTime > actualDuration) - snappedEndTime -= BeatSnapProvider.GetBeatLengthAtTime(referenceTime); + // as we are snapping to beat length via SnapTime (which will round-to-nearest), check for snapping in the forward direction and reverse it. + if (snappedEndTime > actualDuration + 1) + snappedEndTime -= beatLength; return DurationToDistance(referenceTime, snappedEndTime - referenceTime); } From f09f882cc77f9264ad1292bc7df4c55ca3b548b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 25 Aug 2020 22:43:23 +0200 Subject: [PATCH 0700/1782] Add component for displaying simple statistics on result screen --- .../Ranking/TestSceneSimpleStatisticRow.cs | 68 ++++++++++ .../Ranking/Statistics/SimpleStatisticItem.cs | 80 ++++++++++++ .../Ranking/Statistics/SimpleStatisticRow.cs | 122 ++++++++++++++++++ 3 files changed, 270 insertions(+) create mode 100644 osu.Game.Tests/Visual/Ranking/TestSceneSimpleStatisticRow.cs create mode 100644 osu.Game/Screens/Ranking/Statistics/SimpleStatisticItem.cs create mode 100644 osu.Game/Screens/Ranking/Statistics/SimpleStatisticRow.cs diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneSimpleStatisticRow.cs b/osu.Game.Tests/Visual/Ranking/TestSceneSimpleStatisticRow.cs new file mode 100644 index 0000000000..aa569e47b1 --- /dev/null +++ b/osu.Game.Tests/Visual/Ranking/TestSceneSimpleStatisticRow.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.Linq; +using Humanizer; +using NUnit.Framework; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Utils; +using osu.Game.Screens.Ranking.Statistics; + +namespace osu.Game.Tests.Visual.Ranking +{ + public class TestSceneSimpleStatisticRow : OsuTestScene + { + private Container container; + + [SetUp] + public void SetUp() => Schedule(() => + { + Child = new Container + { + AutoSizeAxes = Axes.Y, + Width = 700, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4Extensions.FromHex("#333"), + }, + container = new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding(20) + } + } + }; + }); + + [Test] + public void TestEmpty() + { + AddStep("create with no items", + () => container.Add(new SimpleStatisticRow(2, Enumerable.Empty()))); + } + + [Test] + public void TestManyItems( + [Values(1, 2, 3, 4, 12)] int itemCount, + [Values(1, 3, 5)] int columnCount) + { + AddStep($"create with {"item".ToQuantity(itemCount)}", () => + { + var items = Enumerable.Range(1, itemCount) + .Select(i => new SimpleStatisticItem($"Statistic #{i}") + { + Value = RNG.Next(100) + }); + + container.Add(new SimpleStatisticRow(columnCount, items)); + }); + } + } +} diff --git a/osu.Game/Screens/Ranking/Statistics/SimpleStatisticItem.cs b/osu.Game/Screens/Ranking/Statistics/SimpleStatisticItem.cs new file mode 100644 index 0000000000..e6c4ab1c4e --- /dev/null +++ b/osu.Game/Screens/Ranking/Statistics/SimpleStatisticItem.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; +using osu.Framework.Graphics.Containers; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; + +namespace osu.Game.Screens.Ranking.Statistics +{ + /// + /// Represents a simple statistic item (one that only needs textual display). + /// Richer visualisations should be done with s. + /// + public abstract class SimpleStatisticItem : Container + { + /// + /// The text to display as the statistic's value. + /// + protected string Value + { + set => this.value.Text = value; + } + + private readonly OsuSpriteText value; + + /// + /// Creates a new simple statistic item. + /// + /// The name of the statistic. + protected SimpleStatisticItem(string name) + { + Name = name; + + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + + AddRange(new[] + { + new OsuSpriteText + { + Text = Name, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft + }, + value = new OsuSpriteText + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Font = OsuFont.Torus.With(weight: FontWeight.Bold) + } + }); + } + } + + /// + /// Strongly-typed generic specialisation for . + /// + public class SimpleStatisticItem : SimpleStatisticItem + { + /// + /// The statistic's value to be displayed. + /// + public new TValue Value + { + set => base.Value = DisplayValue(value); + } + + /// + /// Used to convert to a text representation. + /// Defaults to using . + /// + protected virtual string DisplayValue(TValue value) => value.ToString(); + + public SimpleStatisticItem(string name) + : base(name) + { + } + } +} diff --git a/osu.Game/Screens/Ranking/Statistics/SimpleStatisticRow.cs b/osu.Game/Screens/Ranking/Statistics/SimpleStatisticRow.cs new file mode 100644 index 0000000000..16501aae54 --- /dev/null +++ b/osu.Game/Screens/Ranking/Statistics/SimpleStatisticRow.cs @@ -0,0 +1,122 @@ +// Copyright (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; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; + +namespace osu.Game.Screens.Ranking.Statistics +{ + /// + /// Represents a statistic row with simple statistics (ones that only need textual display). + /// Richer visualisations should be done with s and s. + /// + public class SimpleStatisticRow : CompositeDrawable + { + private readonly SimpleStatisticItem[] items; + private readonly int columnCount; + + private FillFlowContainer[] columns; + + /// + /// Creates a statistic row for the supplied s. + /// + /// The number of columns to layout the into. + /// The s to display in this row. + public SimpleStatisticRow(int columnCount, IEnumerable items) + { + if (columnCount < 1) + throw new ArgumentOutOfRangeException(nameof(columnCount)); + + this.columnCount = columnCount; + this.items = items.ToArray(); + } + + [BackgroundDependencyLoader] + private void load() + { + columns = new FillFlowContainer[columnCount]; + + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + + InternalChild = new GridContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + RowDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize) + }, + ColumnDimensions = createColumnDimensions().ToArray(), + Content = new[] { createColumns().ToArray() } + }; + + for (int i = 0; i < items.Length; ++i) + columns[i % columnCount].Add(items[i]); + } + + private IEnumerable createColumnDimensions() + { + for (int column = 0; column < columnCount; ++column) + { + if (column > 0) + yield return new Dimension(GridSizeMode.Absolute, 30); + + yield return new Dimension(); + } + } + + private IEnumerable createColumns() + { + for (int column = 0; column < columnCount; ++column) + { + if (column > 0) + { + yield return new Spacer + { + Alpha = items.Length > column ? 1 : 0 + }; + } + + yield return columns[column] = createColumn(); + } + } + + private FillFlowContainer createColumn() => new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical + }; + + private class Spacer : CompositeDrawable + { + public Spacer() + { + RelativeSizeAxes = Axes.Both; + Padding = new MarginPadding { Vertical = 4 }; + + InternalChild = new CircularContainer + { + RelativeSizeAxes = Axes.Y, + Width = 3, + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + CornerRadius = 2, + Masking = true, + Child = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4Extensions.FromHex("#222") + } + }; + } + } + } +} From 2cf2ba8fc5cb0e1648756774d99e81b1f045c70b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 26 Aug 2020 14:24:04 +0900 Subject: [PATCH 0701/1782] Store computed accent colour to local --- osu.Game.Rulesets.Taiko/Skinning/LegacyDrumRoll.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Skinning/LegacyDrumRoll.cs b/osu.Game.Rulesets.Taiko/Skinning/LegacyDrumRoll.cs index 025eff53d5..5ab8e3a8c8 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/LegacyDrumRoll.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/LegacyDrumRoll.cs @@ -76,9 +76,11 @@ namespace osu.Game.Rulesets.Taiko.Skinning private void updateAccentColour() { - headCircle.AccentColour = LegacyColourCompatibility.DisallowZeroAlpha(accentColour); - body.Colour = LegacyColourCompatibility.DisallowZeroAlpha(accentColour); - end.Colour = LegacyColourCompatibility.DisallowZeroAlpha(accentColour); + var colour = LegacyColourCompatibility.DisallowZeroAlpha(accentColour); + + headCircle.AccentColour = colour; + body.Colour = colour; + end.Colour = colour; } } } From d057f5f4bce9297c9dc65d8eafc85cc585f604d6 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 26 Aug 2020 15:37:16 +0900 Subject: [PATCH 0702/1782] Implement mania "KeysUnderNotes" skin config --- osu.Game.Rulesets.Mania/Skinning/LegacyKeyArea.cs | 3 +++ osu.Game/Skinning/LegacyManiaSkinConfiguration.cs | 1 + osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs | 1 + osu.Game/Skinning/LegacyManiaSkinDecoder.cs | 4 ++++ osu.Game/Skinning/LegacySkin.cs | 3 +++ 5 files changed, 12 insertions(+) diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyKeyArea.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyKeyArea.cs index 44f3e7d7b3..b269ea25d4 100644 --- a/osu.Game.Rulesets.Mania/Skinning/LegacyKeyArea.cs +++ b/osu.Game.Rulesets.Mania/Skinning/LegacyKeyArea.cs @@ -65,6 +65,9 @@ namespace osu.Game.Rulesets.Mania.Skinning direction.BindTo(scrollingInfo.Direction); direction.BindValueChanged(onDirectionChanged, true); + + if (GetColumnSkinConfig(skin, LegacyManiaSkinConfigurationLookups.KeysUnderNotes)?.Value ?? false) + Column.UnderlayElements.Add(CreateProxy()); } private void onDirectionChanged(ValueChangedEvent direction) diff --git a/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs b/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs index af7d6007f3..a5cc899b53 100644 --- a/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs +++ b/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs @@ -35,6 +35,7 @@ namespace osu.Game.Skinning public float HitPosition = (480 - 402) * POSITION_SCALE_FACTOR; public float LightPosition = (480 - 413) * POSITION_SCALE_FACTOR; public bool ShowJudgementLine = true; + public bool KeysUnderNotes; public LegacyManiaSkinConfiguration(int keys) { diff --git a/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs b/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs index 4990ca8e60..890a0cc4ff 100644 --- a/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs +++ b/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs @@ -50,5 +50,6 @@ namespace osu.Game.Skinning Hit100, Hit50, Hit0, + KeysUnderNotes, } } diff --git a/osu.Game/Skinning/LegacyManiaSkinDecoder.cs b/osu.Game/Skinning/LegacyManiaSkinDecoder.cs index aebc229f7c..1ea120e8a4 100644 --- a/osu.Game/Skinning/LegacyManiaSkinDecoder.cs +++ b/osu.Game/Skinning/LegacyManiaSkinDecoder.cs @@ -97,6 +97,10 @@ namespace osu.Game.Skinning currentConfig.ShowJudgementLine = pair.Value == "1"; break; + case "KeysUnderNotes": + currentConfig.KeysUnderNotes = pair.Value == "1"; + break; + case "LightingNWidth": parseArrayValue(pair.Value, currentConfig.ExplosionWidth); break; diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 02d07eee45..13a43c8aae 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -255,6 +255,9 @@ namespace osu.Game.Skinning case LegacyManiaSkinConfigurationLookups.Hit300: case LegacyManiaSkinConfigurationLookups.Hit300g: return SkinUtils.As(getManiaImage(existing, maniaLookup.Lookup.ToString())); + + case LegacyManiaSkinConfigurationLookups.KeysUnderNotes: + return SkinUtils.As(new Bindable(existing.KeysUnderNotes)); } return null; From c50e495e035bad956cf1883f510a41c2eb3e867a Mon Sep 17 00:00:00 2001 From: Poliwrath Date: Wed, 26 Aug 2020 02:49:55 -0400 Subject: [PATCH 0703/1782] fix lingering small ring in circles! intro --- 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 6731fef6f7..98da31b93e 100644 --- a/osu.Game/Screens/Menu/IntroSequence.cs +++ b/osu.Game/Screens/Menu/IntroSequence.cs @@ -205,6 +205,7 @@ namespace osu.Game.Screens.Menu const int line_end_offset = 120; smallRing.Foreground.ResizeTo(1, line_duration, Easing.OutQuint); + smallRing.Delay(400).FadeOut(); lineTopLeft.MoveTo(new Vector2(-line_end_offset, -line_end_offset), line_duration, Easing.OutQuint); lineTopRight.MoveTo(new Vector2(line_end_offset, -line_end_offset), line_duration, Easing.OutQuint); From 97637bc747aaba00507f28f1c582e181fa7a7694 Mon Sep 17 00:00:00 2001 From: Poliwrath Date: Wed, 26 Aug 2020 01:59:53 -0400 Subject: [PATCH 0704/1782] remove new.ppy.sh from MessageFormatter --- osu.Game/Online/Chat/MessageFormatter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/Chat/MessageFormatter.cs b/osu.Game/Online/Chat/MessageFormatter.cs index 6af2561c89..648e4a762b 100644 --- a/osu.Game/Online/Chat/MessageFormatter.cs +++ b/osu.Game/Online/Chat/MessageFormatter.cs @@ -119,7 +119,7 @@ namespace osu.Game.Online.Chat case "http": case "https": // length > 3 since all these links need another argument to work - if (args.Length > 3 && (args[1] == "osu.ppy.sh" || args[1] == "new.ppy.sh")) + if (args.Length > 3 && args[1] == "osu.ppy.sh") { switch (args[2]) { From e6116890afbdcf93c7d032844b1670f9172a24ca Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 26 Aug 2020 20:00:38 +0900 Subject: [PATCH 0705/1782] Make hitobject tests display the column --- .../Skinning/ColumnTestContainer.cs | 24 +++++++++++-------- .../Skinning/ManiaHitObjectTestScene.cs | 4 ++-- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/ColumnTestContainer.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/ColumnTestContainer.cs index ff4865c71d..8ba58e3af3 100644 --- a/osu.Game.Rulesets.Mania.Tests/Skinning/ColumnTestContainer.cs +++ b/osu.Game.Rulesets.Mania.Tests/Skinning/ColumnTestContainer.cs @@ -22,18 +22,22 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning [Cached] private readonly Column column; - public ColumnTestContainer(int column, ManiaAction action) + public ColumnTestContainer(int column, ManiaAction action, bool showColumn = false) { - this.column = new Column(column) + InternalChildren = new[] { - Action = { Value = action }, - AccentColour = Color4.Orange, - ColumnType = column % 2 == 0 ? ColumnType.Even : ColumnType.Odd - }; - - InternalChild = content = new ManiaInputManager(new ManiaRuleset().RulesetInfo, 4) - { - RelativeSizeAxes = Axes.Both + this.column = new Column(column) + { + Action = { Value = action }, + AccentColour = Color4.Orange, + ColumnType = column % 2 == 0 ? ColumnType.Even : ColumnType.Odd, + Alpha = showColumn ? 1 : 0 + }, + content = new ManiaInputManager(new ManiaRuleset().RulesetInfo, 4) + { + RelativeSizeAxes = Axes.Both + }, + this.column.TopLevelContainer.CreateProxy() }; } } diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaHitObjectTestScene.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaHitObjectTestScene.cs index 18eebada00..d24c81dac6 100644 --- a/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaHitObjectTestScene.cs +++ b/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaHitObjectTestScene.cs @@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning Direction = FillDirection.Horizontal, Children = new Drawable[] { - new ColumnTestContainer(0, ManiaAction.Key1) + new ColumnTestContainer(0, ManiaAction.Key1, true) { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -45,7 +45,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning })); }) }, - new ColumnTestContainer(1, ManiaAction.Key2) + new ColumnTestContainer(1, ManiaAction.Key2, true) { Anchor = Anchor.Centre, Origin = Anchor.Centre, From c0c67c11b12d0df3b318c763f196f383f91f2f8c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 26 Aug 2020 20:21:41 +0900 Subject: [PATCH 0706/1782] Add parsing for hold note light/scale --- osu.Game/Skinning/LegacyManiaSkinConfiguration.cs | 2 ++ .../Skinning/LegacyManiaSkinConfigurationLookup.cs | 2 ++ osu.Game/Skinning/LegacyManiaSkinDecoder.cs | 4 ++++ osu.Game/Skinning/LegacySkin.cs | 14 ++++++++++++++ 4 files changed, 22 insertions(+) diff --git a/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs b/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs index af7d6007f3..18ae6acb38 100644 --- a/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs +++ b/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs @@ -31,6 +31,7 @@ namespace osu.Game.Skinning public readonly float[] ColumnSpacing; public readonly float[] ColumnWidth; public readonly float[] ExplosionWidth; + public readonly float[] HoldNoteLightWidth; public float HitPosition = (480 - 402) * POSITION_SCALE_FACTOR; public float LightPosition = (480 - 413) * POSITION_SCALE_FACTOR; @@ -44,6 +45,7 @@ namespace osu.Game.Skinning ColumnSpacing = new float[keys - 1]; ColumnWidth = new float[keys]; ExplosionWidth = new float[keys]; + HoldNoteLightWidth = new float[keys]; ColumnLineWidth.AsSpan().Fill(2); ColumnWidth.AsSpan().Fill(DEFAULT_COLUMN_SIZE); diff --git a/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs b/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs index 4990ca8e60..131c3fde34 100644 --- a/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs +++ b/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs @@ -34,6 +34,8 @@ namespace osu.Game.Skinning HoldNoteHeadImage, HoldNoteTailImage, HoldNoteBodyImage, + HoldNoteLightImage, + HoldNoteLightScale, ExplosionImage, ExplosionScale, ColumnLineColour, diff --git a/osu.Game/Skinning/LegacyManiaSkinDecoder.cs b/osu.Game/Skinning/LegacyManiaSkinDecoder.cs index 1e6102eaa4..ca492b5a21 100644 --- a/osu.Game/Skinning/LegacyManiaSkinDecoder.cs +++ b/osu.Game/Skinning/LegacyManiaSkinDecoder.cs @@ -101,6 +101,10 @@ namespace osu.Game.Skinning parseArrayValue(pair.Value, currentConfig.ExplosionWidth); break; + case "LightingLWidth": + parseArrayValue(pair.Value, currentConfig.HoldNoteLightWidth); + break; + case "WidthForNoteHeightScale": float minWidth = float.Parse(pair.Value, CultureInfo.InvariantCulture) * LegacyManiaSkinConfiguration.POSITION_SCALE_FACTOR; if (minWidth > 0) diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 10fb476728..628169584a 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -220,6 +220,20 @@ namespace osu.Game.Skinning Debug.Assert(maniaLookup.TargetColumn != null); return SkinUtils.As(getManiaImage(existing, $"NoteImage{maniaLookup.TargetColumn}L")); + case LegacyManiaSkinConfigurationLookups.HoldNoteLightImage: + return SkinUtils.As(getManiaImage(existing, "LightingL")); + + case LegacyManiaSkinConfigurationLookups.HoldNoteLightScale: + Debug.Assert(maniaLookup.TargetColumn != null); + + if (GetConfig(LegacySkinConfiguration.LegacySetting.Version)?.Value < 2.5m) + return SkinUtils.As(new Bindable(1)); + + if (existing.HoldNoteLightWidth[maniaLookup.TargetColumn.Value] != 0) + return SkinUtils.As(new Bindable(existing.HoldNoteLightWidth[maniaLookup.TargetColumn.Value] / LegacyManiaSkinConfiguration.DEFAULT_COLUMN_SIZE)); + + return SkinUtils.As(new Bindable(existing.ColumnWidth[maniaLookup.TargetColumn.Value] / LegacyManiaSkinConfiguration.DEFAULT_COLUMN_SIZE)); + case LegacyManiaSkinConfigurationLookups.KeyImage: Debug.Assert(maniaLookup.TargetColumn != null); return SkinUtils.As(getManiaImage(existing, $"KeyImage{maniaLookup.TargetColumn}")); From 9372c6eef6e90ff35d5bdd81d332dbded910416b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 26 Aug 2020 20:21:56 +0900 Subject: [PATCH 0707/1782] Implement hold note lighting --- .../Objects/Drawables/DrawableHoldNote.cs | 3 + .../Skinning/LegacyBodyPiece.cs | 124 +++++++++++++++--- 2 files changed, 112 insertions(+), 15 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs index a44f8a8886..4f29e0417e 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs @@ -159,7 +159,10 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables protected override void CheckForResult(bool userTriggered, double timeOffset) { if (Tail.AllJudged) + { ApplyResult(r => r.Type = HitResult.Perfect); + endHold(); + } if (Tail.Result.Type == HitResult.Miss) HasBroken = true; diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyBodyPiece.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyBodyPiece.cs index 9f716428c0..d922934532 100644 --- a/osu.Game.Rulesets.Mania/Skinning/LegacyBodyPiece.cs +++ b/osu.Game.Rulesets.Mania/Skinning/LegacyBodyPiece.cs @@ -1,12 +1,15 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Animations; +using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.OpenGL.Textures; using osu.Game.Rulesets.Mania.Objects.Drawables; +using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Skinning; @@ -19,7 +22,9 @@ namespace osu.Game.Rulesets.Mania.Skinning private readonly IBindable direction = new Bindable(); private readonly IBindable isHitting = new Bindable(); - private Drawable sprite; + private Drawable bodySprite; + private Drawable lightContainer; + private Drawable light; public LegacyBodyPiece() { @@ -32,7 +37,33 @@ namespace osu.Game.Rulesets.Mania.Skinning string imageName = GetColumnSkinConfig(skin, LegacyManiaSkinConfigurationLookups.HoldNoteBodyImage)?.Value ?? $"mania-note{FallbackColumnIndex}L"; - sprite = skin.GetAnimation(imageName, WrapMode.ClampToEdge, WrapMode.ClampToEdge, true, true).With(d => + string lightImage = GetColumnSkinConfig(skin, LegacyManiaSkinConfigurationLookups.HoldNoteLightImage)?.Value + ?? "lightingL"; + + float lightScale = GetColumnSkinConfig(skin, LegacyManiaSkinConfigurationLookups.HoldNoteLightScale)?.Value + ?? 1; + + // Create a temporary animation to retrieve the number of frames, in an effort to calculate the intended frame length. + // This animation is discarded and re-queried with the appropriate frame length afterwards. + var tmp = skin.GetAnimation(lightImage, true, false); + double frameLength = 0; + if (tmp is IFramedAnimation tmpAnimation && tmpAnimation.FrameCount > 0) + frameLength = Math.Max(1000 / 60.0, 170.0 / tmpAnimation.FrameCount); + + light = skin.GetAnimation(lightImage, true, true, frameLength: frameLength).With(d => + { + if (d == null) + return; + + d.Origin = Anchor.Centre; + d.Blending = BlendingParameters.Additive; + d.Scale = new Vector2(lightScale); + }); + + if (light != null) + lightContainer = new HitTargetInsetContainer { Child = light }; + + bodySprite = skin.GetAnimation(imageName, WrapMode.ClampToEdge, WrapMode.ClampToEdge, true, true).With(d => { if (d == null) return; @@ -47,8 +78,8 @@ namespace osu.Game.Rulesets.Mania.Skinning // Todo: Wrap }); - if (sprite != null) - InternalChild = sprite; + if (bodySprite != null) + InternalChild = bodySprite; direction.BindTo(scrollingInfo.Direction); direction.BindValueChanged(onDirectionChanged, true); @@ -60,27 +91,90 @@ namespace osu.Game.Rulesets.Mania.Skinning private void onIsHittingChanged(ValueChangedEvent isHitting) { - if (!(sprite is TextureAnimation animation)) - return; + if (bodySprite is TextureAnimation bodyAnimation) + { + bodyAnimation.GotoFrame(0); + bodyAnimation.IsPlaying = isHitting.NewValue; + } - animation.GotoFrame(0); - animation.IsPlaying = isHitting.NewValue; + if (lightContainer != null) + { + if (isHitting.NewValue) + { + Column.TopLevelContainer.Add(lightContainer); + + // The light must be seeked only after being loaded, otherwise a nullref happens (https://github.com/ppy/osu-framework/issues/3847). + if (light is TextureAnimation lightAnimation) + lightAnimation.GotoFrame(0); + } + else + Column.TopLevelContainer.Remove(lightContainer); + } } private void onDirectionChanged(ValueChangedEvent direction) { - if (sprite == null) - return; - if (direction.NewValue == ScrollingDirection.Up) { - sprite.Origin = Anchor.BottomCentre; - sprite.Scale = new Vector2(1, -1); + if (bodySprite != null) + { + bodySprite.Origin = Anchor.BottomCentre; + bodySprite.Scale = new Vector2(1, -1); + } + + if (light != null) + light.Anchor = Anchor.TopCentre; } else { - sprite.Origin = Anchor.TopCentre; - sprite.Scale = Vector2.One; + if (bodySprite != null) + { + bodySprite.Origin = Anchor.TopCentre; + bodySprite.Scale = Vector2.One; + } + + if (light != null) + light.Anchor = Anchor.BottomCentre; + } + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + lightContainer?.Expire(); + } + + private class HitTargetInsetContainer : Container + { + private readonly IBindable direction = new Bindable(); + + protected override Container Content => content; + private readonly Container content; + + private float hitPosition; + + public HitTargetInsetContainer() + { + RelativeSizeAxes = Axes.Both; + + InternalChild = content = new Container { RelativeSizeAxes = Axes.Both }; + } + + [BackgroundDependencyLoader] + private void load(ISkinSource skin, IScrollingInfo scrollingInfo) + { + hitPosition = skin.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.HitPosition)?.Value ?? Stage.HIT_TARGET_POSITION; + + direction.BindTo(scrollingInfo.Direction); + direction.BindValueChanged(onDirectionChanged, true); + } + + private void onDirectionChanged(ValueChangedEvent direction) + { + content.Padding = direction.NewValue == ScrollingDirection.Up + ? new MarginPadding { Top = hitPosition } + : new MarginPadding { Bottom = hitPosition }; } } } From 6fe1279e9deb3b3bea1ec7c53b2695fd47d6eb30 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 26 Aug 2020 20:23:01 +0900 Subject: [PATCH 0708/1782] Re-use existing inset container --- .../Skinning/HitTargetInsetContainer.cs | 46 +++++++++++++++++++ .../Skinning/LegacyBodyPiece.cs | 35 -------------- .../Skinning/LegacyStageBackground.cs | 35 -------------- 3 files changed, 46 insertions(+), 70 deletions(-) create mode 100644 osu.Game.Rulesets.Mania/Skinning/HitTargetInsetContainer.cs diff --git a/osu.Game.Rulesets.Mania/Skinning/HitTargetInsetContainer.cs b/osu.Game.Rulesets.Mania/Skinning/HitTargetInsetContainer.cs new file mode 100644 index 0000000000..c8b05ed2f8 --- /dev/null +++ b/osu.Game.Rulesets.Mania/Skinning/HitTargetInsetContainer.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 osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Rulesets.Mania.UI; +using osu.Game.Rulesets.UI.Scrolling; +using osu.Game.Skinning; + +namespace osu.Game.Rulesets.Mania.Skinning +{ + public class HitTargetInsetContainer : Container + { + private readonly IBindable direction = new Bindable(); + + protected override Container Content => content; + private readonly Container content; + + private float hitPosition; + + public HitTargetInsetContainer() + { + RelativeSizeAxes = Axes.Both; + + InternalChild = content = new Container { RelativeSizeAxes = Axes.Both }; + } + + [BackgroundDependencyLoader] + private void load(ISkinSource skin, IScrollingInfo scrollingInfo) + { + hitPosition = skin.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.HitPosition)?.Value ?? Stage.HIT_TARGET_POSITION; + + direction.BindTo(scrollingInfo.Direction); + direction.BindValueChanged(onDirectionChanged, true); + } + + private void onDirectionChanged(ValueChangedEvent direction) + { + content.Padding = direction.NewValue == ScrollingDirection.Up + ? new MarginPadding { Top = hitPosition } + : new MarginPadding { Bottom = hitPosition }; + } + } +} diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyBodyPiece.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyBodyPiece.cs index d922934532..338dd5bb1d 100644 --- a/osu.Game.Rulesets.Mania/Skinning/LegacyBodyPiece.cs +++ b/osu.Game.Rulesets.Mania/Skinning/LegacyBodyPiece.cs @@ -6,10 +6,8 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Animations; -using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.OpenGL.Textures; using osu.Game.Rulesets.Mania.Objects.Drawables; -using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Skinning; @@ -144,38 +142,5 @@ namespace osu.Game.Rulesets.Mania.Skinning lightContainer?.Expire(); } - - private class HitTargetInsetContainer : Container - { - private readonly IBindable direction = new Bindable(); - - protected override Container Content => content; - private readonly Container content; - - private float hitPosition; - - public HitTargetInsetContainer() - { - RelativeSizeAxes = Axes.Both; - - InternalChild = content = new Container { RelativeSizeAxes = Axes.Both }; - } - - [BackgroundDependencyLoader] - private void load(ISkinSource skin, IScrollingInfo scrollingInfo) - { - hitPosition = skin.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.HitPosition)?.Value ?? Stage.HIT_TARGET_POSITION; - - direction.BindTo(scrollingInfo.Direction); - direction.BindValueChanged(onDirectionChanged, true); - } - - private void onDirectionChanged(ValueChangedEvent direction) - { - content.Padding = direction.NewValue == ScrollingDirection.Up - ? new MarginPadding { Top = hitPosition } - : new MarginPadding { Bottom = hitPosition }; - } - } } } diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyStageBackground.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyStageBackground.cs index 19ec86b1ed..ead51d91d7 100644 --- a/osu.Game.Rulesets.Mania/Skinning/LegacyStageBackground.cs +++ b/osu.Game.Rulesets.Mania/Skinning/LegacyStageBackground.cs @@ -2,14 +2,12 @@ // 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.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.UI; -using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Skinning; using osuTK; using osuTK.Graphics; @@ -145,38 +143,5 @@ namespace osu.Game.Rulesets.Mania.Skinning }; } } - - private class HitTargetInsetContainer : Container - { - private readonly IBindable direction = new Bindable(); - - protected override Container Content => content; - private readonly Container content; - - private float hitPosition; - - public HitTargetInsetContainer() - { - RelativeSizeAxes = Axes.Both; - - InternalChild = content = new Container { RelativeSizeAxes = Axes.Both }; - } - - [BackgroundDependencyLoader] - private void load(ISkinSource skin, IScrollingInfo scrollingInfo) - { - hitPosition = skin.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.HitPosition)?.Value ?? Stage.HIT_TARGET_POSITION; - - direction.BindTo(scrollingInfo.Direction); - direction.BindValueChanged(onDirectionChanged, true); - } - - private void onDirectionChanged(ValueChangedEvent direction) - { - content.Padding = direction.NewValue == ScrollingDirection.Up - ? new MarginPadding { Top = hitPosition } - : new MarginPadding { Bottom = hitPosition }; - } - } } } From 157e1d89651a4866b1e413ee1f953eea5058e948 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 26 Aug 2020 20:46:12 +0900 Subject: [PATCH 0709/1782] Add fades --- .../Skinning/LegacyBodyPiece.cs | 24 +++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyBodyPiece.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyBodyPiece.cs index 338dd5bb1d..a1c2559386 100644 --- a/osu.Game.Rulesets.Mania/Skinning/LegacyBodyPiece.cs +++ b/osu.Game.Rulesets.Mania/Skinning/LegacyBodyPiece.cs @@ -59,7 +59,13 @@ namespace osu.Game.Rulesets.Mania.Skinning }); if (light != null) - lightContainer = new HitTargetInsetContainer { Child = light }; + { + lightContainer = new HitTargetInsetContainer + { + Alpha = 0, + Child = light + }; + } bodySprite = skin.GetAnimation(imageName, WrapMode.ClampToEdge, WrapMode.ClampToEdge, true, true).With(d => { @@ -99,14 +105,24 @@ namespace osu.Game.Rulesets.Mania.Skinning { if (isHitting.NewValue) { - Column.TopLevelContainer.Add(lightContainer); + // Clear the fade out and, more importantly, the removal. + lightContainer.ClearTransforms(); - // The light must be seeked only after being loaded, otherwise a nullref happens (https://github.com/ppy/osu-framework/issues/3847). + // Only add the container if the removal has taken place. + if (lightContainer.Parent == null) + Column.TopLevelContainer.Add(lightContainer); + + // The light must be seeked only after being loaded, otherwise a nullref occurs (https://github.com/ppy/osu-framework/issues/3847). if (light is TextureAnimation lightAnimation) lightAnimation.GotoFrame(0); + + lightContainer.FadeIn(80); } else - Column.TopLevelContainer.Remove(lightContainer); + { + lightContainer.FadeOut(120) + .OnComplete(d => Column.TopLevelContainer.Remove(d)); + } } } From f65991f31fbe30fcfd55a30e921b890b0709715d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 26 Aug 2020 23:28:58 +0900 Subject: [PATCH 0710/1782] Revert some usages based on review feedback --- osu.Game/Graphics/Containers/BeatSyncedContainer.cs | 8 ++------ osu.Game/Overlays/Music/PlaylistOverlay.cs | 9 +++------ .../Edit/Compose/Components/Timeline/Timeline.cs | 4 ---- osu.Game/Screens/Menu/LogoVisualisation.cs | 12 ++++-------- 4 files changed, 9 insertions(+), 24 deletions(-) diff --git a/osu.Game/Graphics/Containers/BeatSyncedContainer.cs b/osu.Game/Graphics/Containers/BeatSyncedContainer.cs index 69021e1634..1c9cdc174a 100644 --- a/osu.Game/Graphics/Containers/BeatSyncedContainer.cs +++ b/osu.Game/Graphics/Containers/BeatSyncedContainer.cs @@ -7,7 +7,6 @@ using osu.Framework.Bindables; using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Overlays; namespace osu.Game.Graphics.Containers { @@ -15,9 +14,6 @@ namespace osu.Game.Graphics.Containers { protected readonly IBindable Beatmap = new Bindable(); - [Resolved] - private MusicController musicController { get; set; } - private int lastBeat; private TimingControlPoint lastTimingPoint; @@ -58,9 +54,9 @@ namespace osu.Game.Graphics.Containers TimingControlPoint timingPoint = null; EffectControlPoint effectPoint = null; - if (musicController.TrackLoaded && Beatmap.Value.BeatmapLoaded) + if (Beatmap.Value.TrackLoaded && Beatmap.Value.BeatmapLoaded) { - track = musicController.CurrentTrack; + track = Beatmap.Value.Track; beatmap = Beatmap.Value.Beatmap; } diff --git a/osu.Game/Overlays/Music/PlaylistOverlay.cs b/osu.Game/Overlays/Music/PlaylistOverlay.cs index 7471e31923..b45d84049f 100644 --- a/osu.Game/Overlays/Music/PlaylistOverlay.cs +++ b/osu.Game/Overlays/Music/PlaylistOverlay.cs @@ -30,9 +30,6 @@ namespace osu.Game.Overlays.Music [Resolved] private BeatmapManager beatmaps { get; set; } - [Resolved] - private MusicController musicController { get; set; } - private FilterControl filter; private Playlist list; @@ -85,7 +82,7 @@ namespace osu.Game.Overlays.Music if (toSelect != null) { beatmap.Value = beatmaps.GetWorkingBeatmap(toSelect); - musicController.CurrentTrack.Restart(); + beatmap.Value.Track.Restart(); } }; } @@ -119,12 +116,12 @@ namespace osu.Game.Overlays.Music { if (set.ID == (beatmap.Value?.BeatmapSetInfo?.ID ?? -1)) { - musicController.CurrentTrack.Seek(0); + beatmap.Value?.Track.Seek(0); return; } beatmap.Value = beatmaps.GetWorkingBeatmap(set.Beatmaps.First()); - musicController.CurrentTrack.Restart(); + beatmap.Value.Track.Restart(); } } diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs index c617950c64..8c0e35b80e 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs @@ -11,7 +11,6 @@ using osu.Framework.Graphics.Audio; using osu.Framework.Input.Events; using osu.Game.Beatmaps; using osu.Game.Graphics; -using osu.Game.Overlays; using osu.Game.Rulesets.Edit; using osuTK; @@ -27,9 +26,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline [Resolved] private EditorClock editorClock { get; set; } - [Resolved] - private MusicController musicController { get; set; } - /// /// The timeline's scroll position in the last frame. /// diff --git a/osu.Game/Screens/Menu/LogoVisualisation.cs b/osu.Game/Screens/Menu/LogoVisualisation.cs index 4d95ee9b7b..ebbb19636c 100644 --- a/osu.Game/Screens/Menu/LogoVisualisation.cs +++ b/osu.Game/Screens/Menu/LogoVisualisation.cs @@ -20,7 +20,6 @@ using osu.Framework.Audio; using osu.Framework.Audio.Track; using osu.Framework.Bindables; using osu.Framework.Utils; -using osu.Game.Overlays; namespace osu.Game.Screens.Menu { @@ -75,9 +74,6 @@ namespace osu.Game.Screens.Menu /// public float Magnitude { get; set; } = 1; - [Resolved] - private MusicController musicController { get; set; } - private readonly float[] frequencyAmplitudes = new float[256]; private IShader shader; @@ -107,15 +103,15 @@ namespace osu.Game.Screens.Menu private void updateAmplitudes() { - var effect = beatmap.Value.BeatmapLoaded && musicController.TrackLoaded - ? beatmap.Value.Beatmap?.ControlPointInfo.EffectPointAt(musicController.CurrentTrack.CurrentTime) + var effect = beatmap.Value.BeatmapLoaded && beatmap.Value.TrackLoaded + ? beatmap.Value.Beatmap?.ControlPointInfo.EffectPointAt(beatmap.Value.Track.CurrentTime) : null; for (int i = 0; i < temporalAmplitudes.Length; i++) temporalAmplitudes[i] = 0; - if (musicController.TrackLoaded) - addAmplitudesFromSource(musicController.CurrentTrack); + if (beatmap.Value.TrackLoaded) + addAmplitudesFromSource(beatmap.Value.Track); foreach (var source in amplitudeSources) addAmplitudesFromSource(source); From fcf703864288ea6b974f21f8bf049cd254ab4036 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 27 Aug 2020 00:21:50 +0900 Subject: [PATCH 0711/1782] Fix a couple of missed cases --- osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs | 4 ++-- osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs index cd46e8c545..3d100e4b1c 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs @@ -65,7 +65,7 @@ namespace osu.Game.Rulesets.Osu.Tests { AddStep("enable autoplay", () => autoplay = true); base.SetUpSteps(); - AddUntilStep("wait for track to start running", () => MusicController.IsPlaying); + AddUntilStep("wait for track to start running", () => Beatmap.Value.Track.IsRunning); double startTime = hitObjects[sliderIndex].StartTime; retrieveDrawableSlider(sliderIndex); @@ -90,7 +90,7 @@ namespace osu.Game.Rulesets.Osu.Tests { AddStep("have autoplay", () => autoplay = true); base.SetUpSteps(); - AddUntilStep("wait for track to start running", () => MusicController.IsPlaying); + AddUntilStep("wait for track to start running", () => Beatmap.Value.Track.IsRunning); double startTime = hitObjects[sliderIndex].StartTime; retrieveDrawableSlider(sliderIndex); diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs index 3c559765d4..f7909071ea 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs @@ -48,7 +48,7 @@ namespace osu.Game.Rulesets.Osu.Tests { base.SetUpSteps(); - AddUntilStep("wait for track to start running", () => MusicController.IsPlaying); + AddUntilStep("wait for track to start running", () => Beatmap.Value.Track.IsRunning); AddStep("retrieve spinner", () => drawableSpinner = (DrawableSpinner)Player.DrawableRuleset.Playfield.AllHitObjects.First()); } From edc15c965cf6a29321cf84c1dcd1d7bb0fcdd17a Mon Sep 17 00:00:00 2001 From: Poliwrath Date: Wed, 26 Aug 2020 12:52:39 -0400 Subject: [PATCH 0712/1782] Update osu.Game/Screens/Menu/IntroSequence.cs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bartłomiej Dach --- osu.Game/Screens/Menu/IntroSequence.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Menu/IntroSequence.cs b/osu.Game/Screens/Menu/IntroSequence.cs index 98da31b93e..d92d38da45 100644 --- a/osu.Game/Screens/Menu/IntroSequence.cs +++ b/osu.Game/Screens/Menu/IntroSequence.cs @@ -205,7 +205,7 @@ namespace osu.Game.Screens.Menu const int line_end_offset = 120; smallRing.Foreground.ResizeTo(1, line_duration, Easing.OutQuint); - smallRing.Delay(400).FadeOut(); + smallRing.Delay(400).FadeColour(Color4.Black, 300); lineTopLeft.MoveTo(new Vector2(-line_end_offset, -line_end_offset), line_duration, Easing.OutQuint); lineTopRight.MoveTo(new Vector2(line_end_offset, -line_end_offset), line_duration, Easing.OutQuint); From 927a2a3d2df98bb3c93e9c4b8d7af6f35e71636e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 26 Aug 2020 19:19:42 +0200 Subject: [PATCH 0713/1782] Introduce IStatisticRow interface --- .../Ranking/Statistics/IStatisticRow.cs | 18 +++++++++++++ .../Ranking/Statistics/StatisticRow.cs | 26 +++++++++++++++++-- .../Ranking/Statistics/StatisticsPanel.cs | 24 +++-------------- 3 files changed, 45 insertions(+), 23 deletions(-) create mode 100644 osu.Game/Screens/Ranking/Statistics/IStatisticRow.cs diff --git a/osu.Game/Screens/Ranking/Statistics/IStatisticRow.cs b/osu.Game/Screens/Ranking/Statistics/IStatisticRow.cs new file mode 100644 index 0000000000..67224041d5 --- /dev/null +++ b/osu.Game/Screens/Ranking/Statistics/IStatisticRow.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.Framework.Graphics; + +namespace osu.Game.Screens.Ranking.Statistics +{ + /// + /// A row of statistics to be displayed on the results screen. + /// + public interface IStatisticRow + { + /// + /// Creates the visual representation of this row. + /// + Drawable CreateDrawableStatisticRow(); + } +} diff --git a/osu.Game/Screens/Ranking/Statistics/StatisticRow.cs b/osu.Game/Screens/Ranking/Statistics/StatisticRow.cs index e1ca9799a3..fff60cdcad 100644 --- a/osu.Game/Screens/Ranking/Statistics/StatisticRow.cs +++ b/osu.Game/Screens/Ranking/Statistics/StatisticRow.cs @@ -1,19 +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.Linq; using JetBrains.Annotations; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; namespace osu.Game.Screens.Ranking.Statistics { /// - /// A row of statistics to be displayed in the results screen. + /// A row of graphically detailed s to be displayed in the results screen. /// - public class StatisticRow + public class StatisticRow : IStatisticRow { /// /// The columns of this . /// [ItemNotNull] public StatisticItem[] Columns; + + public Drawable CreateDrawableStatisticRow() => new GridContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Content = new[] + { + Columns?.Select(c => new StatisticContainer(c) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }).Cast().ToArray() + }, + ColumnDimensions = Enumerable.Range(0, Columns?.Length ?? 0) + .Select(i => Columns[i].Dimension ?? new Dimension()).ToArray(), + RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize) } + }; } } diff --git a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs index 7f406331cd..fd62c9e7d9 100644 --- a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs +++ b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs @@ -96,27 +96,9 @@ namespace osu.Game.Screens.Ranking.Statistics Spacing = new Vector2(30, 15), }; - foreach (var row in newScore.Ruleset.CreateInstance().CreateStatisticsForScore(newScore, playableBeatmap)) - { - rows.Add(new GridContainer - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Content = new[] - { - row.Columns?.Select(c => new StatisticContainer(c) - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - }).Cast().ToArray() - }, - ColumnDimensions = Enumerable.Range(0, row.Columns?.Length ?? 0) - .Select(i => row.Columns[i].Dimension ?? new Dimension()).ToArray(), - RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize) } - }); - } + rows.AddRange(newScore.Ruleset.CreateInstance() + .CreateStatisticsForScore(newScore, playableBeatmap) + .Select(row => row.CreateDrawableStatisticRow())); LoadComponentAsync(rows, d => { From bbb3d7522e3e606c94d3564b28b9ec21c3865a51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 26 Aug 2020 19:24:12 +0200 Subject: [PATCH 0714/1782] Scope up return type to IStatisticRow --- osu.Game.Rulesets.Mania/ManiaRuleset.cs | 2 +- osu.Game.Rulesets.Osu/OsuRuleset.cs | 2 +- osu.Game.Rulesets.Taiko/TaikoRuleset.cs | 2 +- osu.Game/Rulesets/Ruleset.cs | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index 2795868c97..8cc635c316 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -314,7 +314,7 @@ namespace osu.Game.Rulesets.Mania return (PlayfieldType)Enum.GetValues(typeof(PlayfieldType)).Cast().OrderByDescending(i => i).First(v => variant >= v); } - public override StatisticRow[] CreateStatisticsForScore(ScoreInfo score, IBeatmap playableBeatmap) => new[] + public override IStatisticRow[] CreateStatisticsForScore(ScoreInfo score, IBeatmap playableBeatmap) => new IStatisticRow[] { new StatisticRow { diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index eaa5d8937a..298f1aec91 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -193,7 +193,7 @@ namespace osu.Game.Rulesets.Osu public override IRulesetConfigManager CreateConfig(SettingsStore settings) => new OsuRulesetConfigManager(settings, RulesetInfo); - public override StatisticRow[] CreateStatisticsForScore(ScoreInfo score, IBeatmap playableBeatmap) => new[] + public override IStatisticRow[] CreateStatisticsForScore(ScoreInfo score, IBeatmap playableBeatmap) => new IStatisticRow[] { new StatisticRow { diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index 2011842591..0125e0a3ad 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -161,7 +161,7 @@ namespace osu.Game.Rulesets.Taiko public override IConvertibleReplayFrame CreateConvertibleReplayFrame() => new TaikoReplayFrame(); - public override StatisticRow[] CreateStatisticsForScore(ScoreInfo score, IBeatmap playableBeatmap) => new[] + public override IStatisticRow[] CreateStatisticsForScore(ScoreInfo score, IBeatmap playableBeatmap) => new IStatisticRow[] { new StatisticRow { diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index 3a7f433a37..f82ecd842a 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -217,6 +217,6 @@ namespace osu.Game.Rulesets /// The , converted for this with all relevant s applied. /// The s to display. Each may contain 0 or more . [NotNull] - public virtual StatisticRow[] CreateStatisticsForScore(ScoreInfo score, IBeatmap playableBeatmap) => Array.Empty(); + public virtual IStatisticRow[] CreateStatisticsForScore(ScoreInfo score, IBeatmap playableBeatmap) => Array.Empty(); } } From f5e52c80b4db250bcb18df027a06095f35347508 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 26 Aug 2020 19:25:59 +0200 Subject: [PATCH 0715/1782] Rename {-> Drawable}SimpleStatisticRow --- ...atisticRow.cs => TestSceneDrawableSimpleStatisticRow.cs} | 6 +++--- ...{SimpleStatisticRow.cs => DrawableSimpleStatisticRow.cs} | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) rename osu.Game.Tests/Visual/Ranking/{TestSceneSimpleStatisticRow.cs => TestSceneDrawableSimpleStatisticRow.cs} (88%) rename osu.Game/Screens/Ranking/Statistics/{SimpleStatisticRow.cs => DrawableSimpleStatisticRow.cs} (96%) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneSimpleStatisticRow.cs b/osu.Game.Tests/Visual/Ranking/TestSceneDrawableSimpleStatisticRow.cs similarity index 88% rename from osu.Game.Tests/Visual/Ranking/TestSceneSimpleStatisticRow.cs rename to osu.Game.Tests/Visual/Ranking/TestSceneDrawableSimpleStatisticRow.cs index aa569e47b1..2b0ba30357 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneSimpleStatisticRow.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneDrawableSimpleStatisticRow.cs @@ -13,7 +13,7 @@ using osu.Game.Screens.Ranking.Statistics; namespace osu.Game.Tests.Visual.Ranking { - public class TestSceneSimpleStatisticRow : OsuTestScene + public class TestSceneDrawableSimpleStatisticRow : OsuTestScene { private Container container; @@ -45,7 +45,7 @@ namespace osu.Game.Tests.Visual.Ranking public void TestEmpty() { AddStep("create with no items", - () => container.Add(new SimpleStatisticRow(2, Enumerable.Empty()))); + () => container.Add(new DrawableSimpleStatisticRow(2, Enumerable.Empty()))); } [Test] @@ -61,7 +61,7 @@ namespace osu.Game.Tests.Visual.Ranking Value = RNG.Next(100) }); - container.Add(new SimpleStatisticRow(columnCount, items)); + container.Add(new DrawableSimpleStatisticRow(columnCount, items)); }); } } diff --git a/osu.Game/Screens/Ranking/Statistics/SimpleStatisticRow.cs b/osu.Game/Screens/Ranking/Statistics/DrawableSimpleStatisticRow.cs similarity index 96% rename from osu.Game/Screens/Ranking/Statistics/SimpleStatisticRow.cs rename to osu.Game/Screens/Ranking/Statistics/DrawableSimpleStatisticRow.cs index 16501aae54..a592724bc3 100644 --- a/osu.Game/Screens/Ranking/Statistics/SimpleStatisticRow.cs +++ b/osu.Game/Screens/Ranking/Statistics/DrawableSimpleStatisticRow.cs @@ -16,7 +16,7 @@ namespace osu.Game.Screens.Ranking.Statistics /// Represents a statistic row with simple statistics (ones that only need textual display). /// Richer visualisations should be done with s and s. /// - public class SimpleStatisticRow : CompositeDrawable + public class DrawableSimpleStatisticRow : CompositeDrawable { private readonly SimpleStatisticItem[] items; private readonly int columnCount; @@ -28,7 +28,7 @@ namespace osu.Game.Screens.Ranking.Statistics /// /// The number of columns to layout the into. /// The s to display in this row. - public SimpleStatisticRow(int columnCount, IEnumerable items) + public DrawableSimpleStatisticRow(int columnCount, IEnumerable items) { if (columnCount < 1) throw new ArgumentOutOfRangeException(nameof(columnCount)); From 7c3368ecbe1ed8c6af1bd684f5af13027049b548 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 26 Aug 2020 19:30:49 +0200 Subject: [PATCH 0716/1782] Reintroduce SimpleStatisticRow as a data class --- .../Statistics/DrawableSimpleStatisticRow.cs | 3 +- .../Ranking/Statistics/SimpleStatisticRow.cs | 34 +++++++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) create mode 100644 osu.Game/Screens/Ranking/Statistics/SimpleStatisticRow.cs diff --git a/osu.Game/Screens/Ranking/Statistics/DrawableSimpleStatisticRow.cs b/osu.Game/Screens/Ranking/Statistics/DrawableSimpleStatisticRow.cs index a592724bc3..7f69b323d8 100644 --- a/osu.Game/Screens/Ranking/Statistics/DrawableSimpleStatisticRow.cs +++ b/osu.Game/Screens/Ranking/Statistics/DrawableSimpleStatisticRow.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; @@ -28,7 +29,7 @@ namespace osu.Game.Screens.Ranking.Statistics /// /// The number of columns to layout the into. /// The s to display in this row. - public DrawableSimpleStatisticRow(int columnCount, IEnumerable items) + public DrawableSimpleStatisticRow(int columnCount, [ItemNotNull] IEnumerable items) { if (columnCount < 1) throw new ArgumentOutOfRangeException(nameof(columnCount)); diff --git a/osu.Game/Screens/Ranking/Statistics/SimpleStatisticRow.cs b/osu.Game/Screens/Ranking/Statistics/SimpleStatisticRow.cs new file mode 100644 index 0000000000..cd6afeb3a5 --- /dev/null +++ b/osu.Game/Screens/Ranking/Statistics/SimpleStatisticRow.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 JetBrains.Annotations; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; + +namespace osu.Game.Screens.Ranking.Statistics +{ + /// + /// Contains textual statistic data to display in a . + /// + public class SimpleStatisticRow : IStatisticRow + { + /// + /// The number of columns to layout the in. + /// + public int Columns { get; set; } + + /// + /// The s that this row should contain. + /// + [ItemNotNull] + public SimpleStatisticItem[] Items { get; set; } + + public Drawable CreateDrawableStatisticRow() => new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding(20), + Child = new DrawableSimpleStatisticRow(Columns, Items) + }; + } +} From 5973e2ce4e6d595a6f910b55250ed174a97db92d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 26 Aug 2020 21:20:43 +0200 Subject: [PATCH 0717/1782] Add component for unstable rate statistic --- .../Ranking/Statistics/UnstableRate.cs | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 osu.Game/Screens/Ranking/Statistics/UnstableRate.cs diff --git a/osu.Game/Screens/Ranking/Statistics/UnstableRate.cs b/osu.Game/Screens/Ranking/Statistics/UnstableRate.cs new file mode 100644 index 0000000000..5b368c3e8d --- /dev/null +++ b/osu.Game/Screens/Ranking/Statistics/UnstableRate.cs @@ -0,0 +1,39 @@ +// Copyright (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.Rulesets.Scoring; + +namespace osu.Game.Screens.Ranking.Statistics +{ + /// + /// Displays the unstable rate statistic for a given play. + /// + public class UnstableRate : SimpleStatisticItem + { + /// + /// Creates and computes an statistic. + /// + /// Sequence of s to calculate the unstable rate based on. + public UnstableRate(IEnumerable hitEvents) + : base("Unstable Rate") + { + var timeOffsets = hitEvents.Select(ev => ev.TimeOffset).ToArray(); + Value = 10 * standardDeviation(timeOffsets); + } + + private static double standardDeviation(double[] timeOffsets) + { + if (timeOffsets.Length == 0) + return double.NaN; + + var mean = timeOffsets.Average(); + var squares = timeOffsets.Select(offset => Math.Pow(offset - mean, 2)).Sum(); + return Math.Sqrt(squares / timeOffsets.Length); + } + + protected override string DisplayValue(double value) => double.IsNaN(value) ? "(not available)" : value.ToString("N2"); + } +} From 05e725d59fd5b7710e18f2fb414eb51e02d99e6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 26 Aug 2020 21:28:41 +0200 Subject: [PATCH 0718/1782] Add unstable rate statistic to rulesets in which it makes sense --- osu.Game.Rulesets.Mania/ManiaRuleset.cs | 8 ++++ osu.Game.Rulesets.Osu/OsuRuleset.cs | 50 ++++++++++++++++--------- osu.Game.Rulesets.Taiko/TaikoRuleset.cs | 31 ++++++++++----- 3 files changed, 62 insertions(+), 27 deletions(-) diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index 8cc635c316..490223b7a5 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -326,6 +326,14 @@ namespace osu.Game.Rulesets.Mania Height = 250 }), } + }, + new SimpleStatisticRow + { + Columns = 3, + Items = new SimpleStatisticItem[] + { + new UnstableRate(score.HitEvents) + } } }; } diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index 298f1aec91..dd950c60ec 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -193,30 +193,44 @@ namespace osu.Game.Rulesets.Osu public override IRulesetConfigManager CreateConfig(SettingsStore settings) => new OsuRulesetConfigManager(settings, RulesetInfo); - public override IStatisticRow[] CreateStatisticsForScore(ScoreInfo score, IBeatmap playableBeatmap) => new IStatisticRow[] + public override IStatisticRow[] CreateStatisticsForScore(ScoreInfo score, IBeatmap playableBeatmap) { - new StatisticRow + var timedHitEvents = score.HitEvents.Where(e => e.HitObject is HitCircle && !(e.HitObject is SliderTailCircle)).ToList(); + + return new IStatisticRow[] { - Columns = new[] + new StatisticRow { - new StatisticItem("Timing Distribution", new HitEventTimingDistributionGraph(score.HitEvents.Where(e => e.HitObject is HitCircle && !(e.HitObject is SliderTailCircle)).ToList()) + Columns = new[] { - RelativeSizeAxes = Axes.X, - Height = 250 - }), - } - }, - new StatisticRow - { - Columns = new[] + new StatisticItem("Timing Distribution", + new HitEventTimingDistributionGraph(timedHitEvents) + { + RelativeSizeAxes = Axes.X, + Height = 250 + }), + } + }, + new StatisticRow { - new StatisticItem("Accuracy Heatmap", new AccuracyHeatmap(score, playableBeatmap) + Columns = new[] { - RelativeSizeAxes = Axes.X, - Height = 250 - }), + new StatisticItem("Accuracy Heatmap", new AccuracyHeatmap(score, playableBeatmap) + { + RelativeSizeAxes = Axes.X, + Height = 250 + }), + } + }, + new SimpleStatisticRow + { + Columns = 3, + Items = new SimpleStatisticItem[] + { + new UnstableRate(timedHitEvents) + } } - } - }; + }; + } } } diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index 0125e0a3ad..938c038413 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -161,19 +161,32 @@ namespace osu.Game.Rulesets.Taiko public override IConvertibleReplayFrame CreateConvertibleReplayFrame() => new TaikoReplayFrame(); - public override IStatisticRow[] CreateStatisticsForScore(ScoreInfo score, IBeatmap playableBeatmap) => new IStatisticRow[] + public override IStatisticRow[] CreateStatisticsForScore(ScoreInfo score, IBeatmap playableBeatmap) { - new StatisticRow + var timedHitEvents = score.HitEvents.Where(e => e.HitObject is Hit).ToList(); + + return new IStatisticRow[] { - Columns = new[] + new StatisticRow { - new StatisticItem("Timing Distribution", new HitEventTimingDistributionGraph(score.HitEvents.Where(e => e.HitObject is Hit).ToList()) + Columns = new[] { - RelativeSizeAxes = Axes.X, - Height = 250 - }), + new StatisticItem("Timing Distribution", new HitEventTimingDistributionGraph(timedHitEvents) + { + RelativeSizeAxes = Axes.X, + Height = 250 + }), + } + }, + new SimpleStatisticRow + { + Columns = 3, + Items = new SimpleStatisticItem[] + { + new UnstableRate(timedHitEvents) + } } - } - }; + }; + } } } From d81d538b7e202362c8208f403c449bdc018cf443 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 26 Aug 2020 21:29:01 +0200 Subject: [PATCH 0719/1782] Move out row anchor/origin set to one central place --- osu.Game/Screens/Ranking/Statistics/StatisticRow.cs | 2 -- osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs | 6 +++++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Ranking/Statistics/StatisticRow.cs b/osu.Game/Screens/Ranking/Statistics/StatisticRow.cs index fff60cdcad..d5324e14f0 100644 --- a/osu.Game/Screens/Ranking/Statistics/StatisticRow.cs +++ b/osu.Game/Screens/Ranking/Statistics/StatisticRow.cs @@ -21,8 +21,6 @@ namespace osu.Game.Screens.Ranking.Statistics public Drawable CreateDrawableStatisticRow() => new GridContainer { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Content = new[] diff --git a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs index fd62c9e7d9..2f3304e810 100644 --- a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs +++ b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs @@ -98,7 +98,11 @@ namespace osu.Game.Screens.Ranking.Statistics rows.AddRange(newScore.Ruleset.CreateInstance() .CreateStatisticsForScore(newScore, playableBeatmap) - .Select(row => row.CreateDrawableStatisticRow())); + .Select(row => row.CreateDrawableStatisticRow().With(r => + { + r.Anchor = Anchor.TopCentre; + r.Origin = Anchor.TopCentre; + }))); LoadComponentAsync(rows, d => { From c3197da3dac494617364c88899eef62b5d7f9bcd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 26 Aug 2020 21:43:33 +0200 Subject: [PATCH 0720/1782] Adjust simple statistic item font sizes --- osu.Game/Screens/Ranking/Statistics/SimpleStatisticItem.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Ranking/Statistics/SimpleStatisticItem.cs b/osu.Game/Screens/Ranking/Statistics/SimpleStatisticItem.cs index e6c4ab1c4e..3d9ba2f225 100644 --- a/osu.Game/Screens/Ranking/Statistics/SimpleStatisticItem.cs +++ b/osu.Game/Screens/Ranking/Statistics/SimpleStatisticItem.cs @@ -41,13 +41,14 @@ namespace osu.Game.Screens.Ranking.Statistics { Text = Name, Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft + Origin = Anchor.CentreLeft, + Font = OsuFont.GetFont(size: 14) }, value = new OsuSpriteText { Anchor = Anchor.CentreRight, Origin = Anchor.CentreRight, - Font = OsuFont.Torus.With(weight: FontWeight.Bold) + Font = OsuFont.GetFont(size: 14, weight: FontWeight.Bold) } }); } From f8042e6fd311b4f6713e75c310de746d4ea5daf7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 26 Aug 2020 22:34:02 +0200 Subject: [PATCH 0721/1782] Add fade to prevent jarring transitions --- osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs index 2f3304e810..3b8f980070 100644 --- a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs +++ b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs @@ -94,6 +94,7 @@ namespace osu.Game.Screens.Ranking.Statistics RelativeSizeAxes = Axes.Both, Direction = FillDirection.Vertical, Spacing = new Vector2(30, 15), + Alpha = 0 }; rows.AddRange(newScore.Ruleset.CreateInstance() @@ -111,6 +112,7 @@ namespace osu.Game.Screens.Ranking.Statistics spinner.Hide(); content.Add(d); + d.FadeIn(250, Easing.OutQuint); }, localCancellationSource.Token); }), localCancellationSource.Token); } From deb172bb6ccab9cc46f015957eb45bb06ce1c04f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 27 Aug 2020 20:24:08 +0900 Subject: [PATCH 0722/1782] Implement basic mania hit order policy --- .../TestSceneOutOfOrderHits.cs | 124 ++++++++++++++++++ .../Objects/Drawables/DrawableHoldNote.cs | 3 + .../Drawables/DrawableManiaHitObject.cs | 9 ++ .../Objects/Drawables/DrawableNote.cs | 3 + osu.Game.Rulesets.Mania/UI/Column.cs | 10 ++ .../UI/OrderedHitPolicy.cs | 66 ++++++++++ 6 files changed, 215 insertions(+) create mode 100644 osu.Game.Rulesets.Mania.Tests/TestSceneOutOfOrderHits.cs create mode 100644 osu.Game.Rulesets.Mania/UI/OrderedHitPolicy.cs diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneOutOfOrderHits.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneOutOfOrderHits.cs new file mode 100644 index 0000000000..ed187e65bf --- /dev/null +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneOutOfOrderHits.cs @@ -0,0 +1,124 @@ +// Copyright (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.Extensions.TypeExtensions; +using osu.Framework.Screens; +using osu.Framework.Utils; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Replays; +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Mania.Beatmaps; +using osu.Game.Rulesets.Mania.Objects; +using osu.Game.Rulesets.Mania.Replays; +using osu.Game.Rulesets.Replays; +using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; +using osu.Game.Screens.Play; +using osu.Game.Tests.Visual; + +namespace osu.Game.Rulesets.Mania.Tests +{ + public class TestSceneOutOfOrderHits : RateAdjustedBeatmapTestScene + { + [Test] + public void TestPreviousHitWindowDoesNotExtendPastNextObject() + { + var objects = new List(); + var frames = new List(); + + for (int i = 0; i < 7; i++) + { + double time = 1000 + i * 100; + + objects.Add(new Note { StartTime = time }); + + if (i > 0) + { + frames.Add(new ManiaReplayFrame(time + 10, ManiaAction.Key1)); + frames.Add(new ManiaReplayFrame(time + 11)); + } + } + + performTest(objects, frames); + + addJudgementAssert(objects[0], HitResult.Miss); + + for (int i = 1; i < 7; i++) + { + addJudgementAssert(objects[i], HitResult.Perfect); + addJudgementOffsetAssert(objects[i], 10); + } + } + + private void addJudgementAssert(ManiaHitObject hitObject, HitResult result) + { + AddAssert($"({hitObject.GetType().ReadableName()} @ {hitObject.StartTime}) judgement is {result}", + () => judgementResults.Single(r => r.HitObject == hitObject).Type == result); + } + + private void addJudgementAssert(string name, Func hitObject, HitResult result) + { + AddAssert($"{name} judgement is {result}", + () => judgementResults.Single(r => r.HitObject == hitObject()).Type == result); + } + + private void addJudgementOffsetAssert(ManiaHitObject hitObject, double offset) + { + AddAssert($"({hitObject.GetType().ReadableName()} @ {hitObject.StartTime}) judged at {offset}", + () => Precision.AlmostEquals(judgementResults.Single(r => r.HitObject == hitObject).TimeOffset, offset, 100)); + } + + private ScoreAccessibleReplayPlayer currentPlayer; + private List judgementResults; + + private void performTest(List hitObjects, List frames) + { + AddStep("load player", () => + { + Beatmap.Value = CreateWorkingBeatmap(new ManiaBeatmap(new StageDefinition { Columns = 4 }) + { + HitObjects = hitObjects, + BeatmapInfo = + { + Ruleset = new ManiaRuleset().RulesetInfo + }, + }); + + Beatmap.Value.Beatmap.ControlPointInfo.Add(0, new DifficultyControlPoint { SpeedMultiplier = 0.1f }); + + var p = new ScoreAccessibleReplayPlayer(new Score { Replay = new Replay { Frames = frames } }); + + p.OnLoadComplete += _ => + { + p.ScoreProcessor.NewJudgement += result => + { + if (currentPlayer == p) judgementResults.Add(result); + }; + }; + + LoadScreen(currentPlayer = p); + judgementResults = new List(); + }); + + AddUntilStep("Beatmap at 0", () => Beatmap.Value.Track.CurrentTime == 0); + AddUntilStep("Wait until player is loaded", () => currentPlayer.IsCurrentScreen()); + AddUntilStep("Wait for completion", () => currentPlayer.ScoreProcessor.HasCompleted.Value); + } + + private class ScoreAccessibleReplayPlayer : ReplayPlayer + { + public new ScoreProcessor ScoreProcessor => base.ScoreProcessor; + + protected override bool PauseOnFocusLost => false; + + public ScoreAccessibleReplayPlayer(Score score) + : base(score, false, false) + { + } + } + } +} diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs index 0712026ca6..a04e5bc2f9 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs @@ -252,6 +252,9 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables if (action != Action.Value) return false; + if (CheckHittable?.Invoke(this, Time.Current) == false) + return false; + // The tail has a lenience applied to it which is factored into the miss window (i.e. the miss judgement will be delayed). // But the hold cannot ever be started within the late-lenience window, so we should skip trying to begin the hold during that time. // Note: Unlike below, we use the tail's start time to determine the time offset. diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs index ab76a5b8f8..0594d1e143 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.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 JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -8,6 +9,7 @@ using osu.Framework.Graphics; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Rulesets.Mania.UI; +using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Mania.Objects.Drawables { @@ -34,6 +36,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables } } + public Func CheckHittable; + protected DrawableManiaHitObject(ManiaHitObject hitObject) : base(hitObject) { @@ -124,6 +128,11 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables break; } } + + /// + /// Causes this to get missed, disregarding all conditions in implementations of . + /// + public void MissForcefully() => ApplyResult(r => r.Type = HitResult.Miss); } public abstract class DrawableManiaHitObject : DrawableManiaHitObject diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs index 9451bc4430..973dc06e05 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs @@ -64,6 +64,9 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables if (action != Action.Value) return false; + if (CheckHittable?.Invoke(this, Time.Current) == false) + return false; + return UpdateResult(true); } diff --git a/osu.Game.Rulesets.Mania/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs index de4648e4fa..9aabcc6699 100644 --- a/osu.Game.Rulesets.Mania/UI/Column.cs +++ b/osu.Game.Rulesets.Mania/UI/Column.cs @@ -17,6 +17,7 @@ using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Skinning; using osuTK; using osu.Game.Rulesets.Mania.Beatmaps; +using osu.Game.Rulesets.Mania.Objects.Drawables; namespace osu.Game.Rulesets.Mania.UI { @@ -36,6 +37,7 @@ namespace osu.Game.Rulesets.Mania.UI public readonly ColumnHitObjectArea HitObjectArea; internal readonly Container TopLevelContainer; private readonly DrawablePool hitExplosionPool; + private readonly OrderedHitPolicy hitPolicy; public Container UnderlayElements => HitObjectArea.UnderlayElements; @@ -65,6 +67,8 @@ namespace osu.Game.Rulesets.Mania.UI TopLevelContainer = new Container { RelativeSizeAxes = Axes.Both } }; + hitPolicy = new OrderedHitPolicy(HitObjectContainer); + TopLevelContainer.Add(HitObjectArea.Explosions.CreateProxy()); } @@ -90,6 +94,9 @@ namespace osu.Game.Rulesets.Mania.UI hitObject.AccentColour.Value = AccentColour; hitObject.OnNewResult += OnNewResult; + DrawableManiaHitObject maniaObject = (DrawableManiaHitObject)hitObject; + maniaObject.CheckHittable = hitPolicy.IsHittable; + HitObjectContainer.Add(hitObject); } @@ -104,6 +111,9 @@ namespace osu.Game.Rulesets.Mania.UI internal void OnNewResult(DrawableHitObject judgedObject, JudgementResult result) { + if (result.IsHit) + hitPolicy.HandleHit(judgedObject); + if (!result.IsHit || !judgedObject.DisplayResult || !DisplayJudgements.Value) return; diff --git a/osu.Game.Rulesets.Mania/UI/OrderedHitPolicy.cs b/osu.Game.Rulesets.Mania/UI/OrderedHitPolicy.cs new file mode 100644 index 0000000000..68183be89f --- /dev/null +++ b/osu.Game.Rulesets.Mania/UI/OrderedHitPolicy.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 osu.Framework.Extensions.IEnumerableExtensions; +using osu.Game.Rulesets.Mania.Objects.Drawables; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.UI; + +namespace osu.Game.Rulesets.Mania.UI +{ + public class OrderedHitPolicy + { + private readonly HitObjectContainer hitObjectContainer; + + public OrderedHitPolicy(HitObjectContainer hitObjectContainer) + { + this.hitObjectContainer = hitObjectContainer; + } + + public bool IsHittable(DrawableHitObject hitObject, double time) + { + var nextObject = hitObjectContainer.AliveObjects.GetNext(hitObject); + return nextObject == null || time < nextObject.HitObject.StartTime; + } + + /// + /// Handles a being hit to potentially miss all earlier s. + /// + /// The that was hit. + public void HandleHit(DrawableHitObject hitObject) + { + if (!IsHittable(hitObject, hitObject.HitObject.StartTime + hitObject.Result.TimeOffset)) + throw new InvalidOperationException($"A {hitObject} was hit before it became hittable!"); + + foreach (var obj in enumerateHitObjectsUpTo(hitObject.HitObject.StartTime)) + { + if (obj.Judged) + continue; + + ((DrawableManiaHitObject)obj).MissForcefully(); + } + } + + private IEnumerable enumerateHitObjectsUpTo(double targetTime) + { + foreach (var obj in hitObjectContainer.AliveObjects) + { + if (obj.HitObject.StartTime >= targetTime) + yield break; + + yield return obj; + + foreach (var nestedObj in obj.NestedHitObjects) + { + if (nestedObj.HitObject.StartTime >= targetTime) + break; + + yield return nestedObj; + } + } + } + } +} From 6f93df0b9dbd1317b072c61b124fb50b30017b41 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 27 Aug 2020 21:05:12 +0900 Subject: [PATCH 0723/1782] Fix ticks causing hold note misses --- osu.Game.Rulesets.Mania/UI/OrderedHitPolicy.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Mania/UI/OrderedHitPolicy.cs b/osu.Game.Rulesets.Mania/UI/OrderedHitPolicy.cs index 68183be89f..dfd5136e3e 100644 --- a/osu.Game.Rulesets.Mania/UI/OrderedHitPolicy.cs +++ b/osu.Game.Rulesets.Mania/UI/OrderedHitPolicy.cs @@ -48,14 +48,14 @@ namespace osu.Game.Rulesets.Mania.UI { foreach (var obj in hitObjectContainer.AliveObjects) { - if (obj.HitObject.StartTime >= targetTime) + if (obj.HitObject.GetEndTime() >= targetTime) yield break; yield return obj; foreach (var nestedObj in obj.NestedHitObjects) { - if (nestedObj.HitObject.StartTime >= targetTime) + if (nestedObj.HitObject.GetEndTime() >= targetTime) break; yield return nestedObj; From 7a5292936e57096e3521a1781d33d777fdc386d4 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 27 Aug 2020 21:15:05 +0900 Subject: [PATCH 0724/1782] Add some xmldocs --- osu.Game.Rulesets.Mania/UI/OrderedHitPolicy.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/osu.Game.Rulesets.Mania/UI/OrderedHitPolicy.cs b/osu.Game.Rulesets.Mania/UI/OrderedHitPolicy.cs index dfd5136e3e..0f9cd48dd8 100644 --- a/osu.Game.Rulesets.Mania/UI/OrderedHitPolicy.cs +++ b/osu.Game.Rulesets.Mania/UI/OrderedHitPolicy.cs @@ -11,6 +11,9 @@ using osu.Game.Rulesets.UI; namespace osu.Game.Rulesets.Mania.UI { + /// + /// Ensures that only the most recent is hittable, affectionately known as "note lock". + /// public class OrderedHitPolicy { private readonly HitObjectContainer hitObjectContainer; @@ -20,6 +23,15 @@ namespace osu.Game.Rulesets.Mania.UI this.hitObjectContainer = hitObjectContainer; } + /// + /// Determines whether a can be hit at a point in time. + /// + /// + /// Only the most recent can be hit, a previous hitobject's window cannot extend past the next one. + /// + /// The to check. + /// The time to check. + /// Whether can be hit at the given . public bool IsHittable(DrawableHitObject hitObject, double time) { var nextObject = hitObjectContainer.AliveObjects.GetNext(hitObject); From 29b29cde8e69330a1fdb62aec6ea802288242ec3 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 27 Aug 2020 23:09:54 +0900 Subject: [PATCH 0725/1782] Flip condition to reduce nesting --- .../Skinning/LegacyBodyPiece.cs | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyBodyPiece.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyBodyPiece.cs index a1c2559386..f2e92a7258 100644 --- a/osu.Game.Rulesets.Mania/Skinning/LegacyBodyPiece.cs +++ b/osu.Game.Rulesets.Mania/Skinning/LegacyBodyPiece.cs @@ -101,28 +101,28 @@ namespace osu.Game.Rulesets.Mania.Skinning bodyAnimation.IsPlaying = isHitting.NewValue; } - if (lightContainer != null) + if (lightContainer == null) + return; + + if (isHitting.NewValue) { - if (isHitting.NewValue) - { - // Clear the fade out and, more importantly, the removal. - lightContainer.ClearTransforms(); + // Clear the fade out and, more importantly, the removal. + lightContainer.ClearTransforms(); - // Only add the container if the removal has taken place. - if (lightContainer.Parent == null) - Column.TopLevelContainer.Add(lightContainer); + // Only add the container if the removal has taken place. + if (lightContainer.Parent == null) + Column.TopLevelContainer.Add(lightContainer); - // The light must be seeked only after being loaded, otherwise a nullref occurs (https://github.com/ppy/osu-framework/issues/3847). - if (light is TextureAnimation lightAnimation) - lightAnimation.GotoFrame(0); + // The light must be seeked only after being loaded, otherwise a nullref occurs (https://github.com/ppy/osu-framework/issues/3847). + if (light is TextureAnimation lightAnimation) + lightAnimation.GotoFrame(0); - lightContainer.FadeIn(80); - } - else - { - lightContainer.FadeOut(120) - .OnComplete(d => Column.TopLevelContainer.Remove(d)); - } + lightContainer.FadeIn(80); + } + else + { + lightContainer.FadeOut(120) + .OnComplete(d => Column.TopLevelContainer.Remove(d)); } } From 700219316519201aa90ec2327091244553b6e250 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 27 Aug 2020 23:16:54 +0900 Subject: [PATCH 0726/1782] Mark nullable members --- osu.Game.Rulesets.Mania/Skinning/LegacyBodyPiece.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyBodyPiece.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyBodyPiece.cs index f2e92a7258..c0f0fcb4af 100644 --- a/osu.Game.Rulesets.Mania/Skinning/LegacyBodyPiece.cs +++ b/osu.Game.Rulesets.Mania/Skinning/LegacyBodyPiece.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -20,8 +21,13 @@ namespace osu.Game.Rulesets.Mania.Skinning private readonly IBindable direction = new Bindable(); private readonly IBindable isHitting = new Bindable(); + [CanBeNull] private Drawable bodySprite; + + [CanBeNull] private Drawable lightContainer; + + [CanBeNull] private Drawable light; public LegacyBodyPiece() From 9d70b4af0922a20a1877df82bd745896d8b72132 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 27 Aug 2020 17:35:42 +0200 Subject: [PATCH 0727/1782] Add failing test case --- .../Formats/LegacyScoreDecoderTest.cs | 66 ++++++++++++++++++ .../Resources/Replays/mania-replay.osr | Bin 0 -> 1012 bytes 2 files changed, 66 insertions(+) create mode 100644 osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs create mode 100644 osu.Game.Tests/Resources/Replays/mania-replay.osr diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs new file mode 100644 index 0000000000..31c367aad1 --- /dev/null +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.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.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using osu.Game.Beatmaps; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Catch; +using osu.Game.Rulesets.Mania; +using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Scoring; +using osu.Game.Rulesets.Taiko; +using osu.Game.Scoring.Legacy; +using osu.Game.Tests.Resources; + +namespace osu.Game.Tests.Beatmaps.Formats +{ + [TestFixture] + public class LegacyScoreDecoderTest + { + [Test] + public void TestDecodeManiaReplay() + { + var decoder = new TestLegacyScoreDecoder(); + + using (var resourceStream = TestResources.OpenResource("Replays/mania-replay.osr")) + { + var score = decoder.Parse(resourceStream); + + Assert.AreEqual(3, score.ScoreInfo.Ruleset.ID); + + Assert.AreEqual(2, score.ScoreInfo.Statistics[HitResult.Great]); + Assert.AreEqual(1, score.ScoreInfo.Statistics[HitResult.Good]); + + Assert.AreEqual(829_931, score.ScoreInfo.TotalScore); + Assert.AreEqual(3, score.ScoreInfo.MaxCombo); + + Assert.That(score.Replay.Frames, Is.Not.Empty); + } + } + + private class TestLegacyScoreDecoder : LegacyScoreDecoder + { + private static readonly Dictionary rulesets = new Ruleset[] + { + new OsuRuleset(), + new TaikoRuleset(), + new CatchRuleset(), + new ManiaRuleset() + }.ToDictionary(ruleset => ((ILegacyRuleset)ruleset).LegacyID); + + protected override Ruleset GetRuleset(int rulesetId) => rulesets[rulesetId]; + + protected override WorkingBeatmap GetBeatmap(string md5Hash) => new TestWorkingBeatmap(new Beatmap + { + BeatmapInfo = new BeatmapInfo + { + MD5Hash = md5Hash, + Ruleset = new OsuRuleset().RulesetInfo, + BaseDifficulty = new BeatmapDifficulty() + } + }); + } + } +} diff --git a/osu.Game.Tests/Resources/Replays/mania-replay.osr b/osu.Game.Tests/Resources/Replays/mania-replay.osr new file mode 100644 index 0000000000000000000000000000000000000000..da1a7bdd28e5b89eddd9742bce2b27bc8e75e6f5 GIT binary patch literal 1012 zcmV2Fk>_^VL323Gh;F`G&3?}Wi$c+0000000031008T$3;+WF00000 z01FT?FgZ6ed@(FBI50Uld@(FxP`z80O4tZ&0{{SB001BWz5CvGFroXr>*+lrR^_@@;>&b?uOx<^mMIMT(+z#h{kY?X9fc(g(Lwebtx# z7Q6Lh=|qk7uqqmlbl#mwF~34O;`FY?03m(j9XrY3L)0)1?Fw}$4J_CNJKO;4?X$~q z;+wGa#wD!!0q2PlCzFrV3*3>Lz{D|fez@9JR^@L#NXJJJ;9{+KP~}bh@_eim%+NSk zzegnGWya)SS7pW`<9yI$g`b0t!?MZslqBX+4bVy#tZV0pDNGmeSXKuOXGQ|f3zg8y zGrMu+gxo2?L-AA5RBaNH&*&Dv9Dc!%ufdTEO? zp@}%SBej)D4N{g#xwHF)8|Ks=@OJ2^BGnjH?@$dDOeELCl()%CSSocJ#J0 zrMz`0akC4=AW51K>p71-r!saF*3AP)S9{~98w6pYSsUr1=*o_Q#S)6A-dHA@d9~(^0+ax|D@!;n;Z|| zFvVlqvtKZJf2x8EVoS#`3YPzS-*vaun`i1N*BG`MvK_+XgTPh^GgHpdy!e==KZIaT z3!YC}(8nGj*i;imyixwy9zyF`AooHY90sBzR$Obzb-Vi~&7$v#i3~s7*O{}|SLmeL zoMGZCnRwCc_XbwVIh5g#M@wt{FGra$aqhxs9vn18+0v`>fSkg3XKs=0k_AUDZ~8>a zDG78!sD4dHOSBcg!q83SKA#^J)eG3=oY`jU3QIF2QVgemNl8@7S4(W%aYN@e0jDu% zmqiUorSh0*IbBRsW~4cH;@gJJOutPU2TVP<=OEnHm=mZ{2D~6Z=U*j5r!6K@YHyj* zwhC8cbzc~TkcHU?&2W;Km_To^u4o{p7%^`#txPglTyDDFl%vh Date: Thu, 27 Aug 2020 17:40:22 +0200 Subject: [PATCH 0728/1782] Fix some legacy mania replays crashing on import --- osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs index a4a560c8e4..a3469f0965 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs @@ -241,12 +241,15 @@ namespace osu.Game.Scoring.Legacy } var diff = Parsing.ParseFloat(split[0]); + var mouseX = Parsing.ParseFloat(split[1], Parsing.MAX_COORDINATE_VALUE); + var mouseY = Parsing.ParseFloat(split[2], Parsing.MAX_COORDINATE_VALUE); lastTime += diff; - if (i == 0 && diff == 0) - // osu-stable adds a zero-time frame before potentially valid negative user frames. - // we need to ignore this. + if (i < 2 && mouseX == 256 && mouseY == -500) + // at the start of the replay, stable places two replay frames, at time 0 and SkipBoundary - 1, respectively. + // both frames use a position of (256, -500). + // ignore these frames as they serve no real purpose (and can even mislead ruleset-specific handlers - see mania) continue; // Todo: At some point we probably want to rewind and play back the negative-time frames @@ -255,8 +258,8 @@ namespace osu.Game.Scoring.Legacy continue; currentFrame = convertFrame(new LegacyReplayFrame(lastTime, - Parsing.ParseFloat(split[1], Parsing.MAX_COORDINATE_VALUE), - Parsing.ParseFloat(split[2], Parsing.MAX_COORDINATE_VALUE), + mouseX, + mouseY, (ReplayButtonState)Parsing.ParseInt(split[3])), currentFrame); replay.Frames.Add(currentFrame); From 37387d774165340e14c1e1ea493e23bb3aea693a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 27 Aug 2020 17:57:55 +0200 Subject: [PATCH 0729/1782] Add assertions to existing test to cover bug --- osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs index 31c367aad1..9c71466489 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Linq; using NUnit.Framework; +using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Rulesets; using osu.Game.Rulesets.Catch; @@ -11,6 +12,7 @@ using osu.Game.Rulesets.Mania; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Taiko; +using osu.Game.Scoring; using osu.Game.Scoring.Legacy; using osu.Game.Tests.Resources; @@ -35,6 +37,8 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.AreEqual(829_931, score.ScoreInfo.TotalScore); Assert.AreEqual(3, score.ScoreInfo.MaxCombo); + Assert.IsTrue(Precision.AlmostEquals(0.8889, score.ScoreInfo.Accuracy, 0.0001)); + Assert.AreEqual(ScoreRank.B, score.ScoreInfo.Rank); Assert.That(score.Replay.Frames, Is.Not.Empty); } From af59e2c17954ce8fbf3df20ed2a0b6d3eeb74fa1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 27 Aug 2020 18:05:06 +0200 Subject: [PATCH 0730/1782] Use extension methods instead of reading directly --- osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs index a3469f0965..97cb5ca7ab 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs @@ -13,7 +13,6 @@ using osu.Game.Replays.Legacy; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Replays; -using osu.Game.Rulesets.Scoring; using osu.Game.Users; using SharpCompress.Compressors.LZMA; @@ -123,12 +122,12 @@ namespace osu.Game.Scoring.Legacy protected void CalculateAccuracy(ScoreInfo score) { - score.Statistics.TryGetValue(HitResult.Miss, out int countMiss); - score.Statistics.TryGetValue(HitResult.Meh, out int count50); - score.Statistics.TryGetValue(HitResult.Good, out int count100); - score.Statistics.TryGetValue(HitResult.Great, out int count300); - score.Statistics.TryGetValue(HitResult.Perfect, out int countGeki); - score.Statistics.TryGetValue(HitResult.Ok, out int countKatu); + int countMiss = score.GetCountMiss() ?? 0; + int count50 = score.GetCount50() ?? 0; + int count100 = score.GetCount100() ?? 0; + int count300 = score.GetCount300() ?? 0; + int countGeki = score.GetCountGeki() ?? 0; + int countKatu = score.GetCountKatu() ?? 0; switch (score.Ruleset.ID) { From f152e1b924f23da565ce0a89fb19ca3f2f16ac50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 27 Aug 2020 20:07:30 +0200 Subject: [PATCH 0731/1782] Revert IStatisticRow changes --- osu.Game.Rulesets.Mania/ManiaRuleset.cs | 10 +------ osu.Game.Rulesets.Osu/OsuRuleset.cs | 12 ++------ osu.Game.Rulesets.Taiko/TaikoRuleset.cs | 12 ++------ osu.Game/Rulesets/Ruleset.cs | 2 +- .../Ranking/Statistics/IStatisticRow.cs | 18 ------------ .../Ranking/Statistics/SimpleStatisticRow.cs | 2 +- .../Ranking/Statistics/StatisticRow.cs | 24 ++-------------- .../Ranking/Statistics/StatisticsPanel.cs | 28 ++++++++++++++----- 8 files changed, 30 insertions(+), 78 deletions(-) delete mode 100644 osu.Game/Screens/Ranking/Statistics/IStatisticRow.cs diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index 490223b7a5..bbfc5739ec 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -314,7 +314,7 @@ namespace osu.Game.Rulesets.Mania return (PlayfieldType)Enum.GetValues(typeof(PlayfieldType)).Cast().OrderByDescending(i => i).First(v => variant >= v); } - public override IStatisticRow[] CreateStatisticsForScore(ScoreInfo score, IBeatmap playableBeatmap) => new IStatisticRow[] + public override StatisticRow[] CreateStatisticsForScore(ScoreInfo score, IBeatmap playableBeatmap) => new[] { new StatisticRow { @@ -327,14 +327,6 @@ namespace osu.Game.Rulesets.Mania }), } }, - new SimpleStatisticRow - { - Columns = 3, - Items = new SimpleStatisticItem[] - { - new UnstableRate(score.HitEvents) - } - } }; } diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index dd950c60ec..14e7b9e9a4 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -193,11 +193,11 @@ namespace osu.Game.Rulesets.Osu public override IRulesetConfigManager CreateConfig(SettingsStore settings) => new OsuRulesetConfigManager(settings, RulesetInfo); - public override IStatisticRow[] CreateStatisticsForScore(ScoreInfo score, IBeatmap playableBeatmap) + public override StatisticRow[] CreateStatisticsForScore(ScoreInfo score, IBeatmap playableBeatmap) { var timedHitEvents = score.HitEvents.Where(e => e.HitObject is HitCircle && !(e.HitObject is SliderTailCircle)).ToList(); - return new IStatisticRow[] + return new[] { new StatisticRow { @@ -222,14 +222,6 @@ namespace osu.Game.Rulesets.Osu }), } }, - new SimpleStatisticRow - { - Columns = 3, - Items = new SimpleStatisticItem[] - { - new UnstableRate(timedHitEvents) - } - } }; } } diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index 938c038413..367d991677 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -161,11 +161,11 @@ namespace osu.Game.Rulesets.Taiko public override IConvertibleReplayFrame CreateConvertibleReplayFrame() => new TaikoReplayFrame(); - public override IStatisticRow[] CreateStatisticsForScore(ScoreInfo score, IBeatmap playableBeatmap) + public override StatisticRow[] CreateStatisticsForScore(ScoreInfo score, IBeatmap playableBeatmap) { var timedHitEvents = score.HitEvents.Where(e => e.HitObject is Hit).ToList(); - return new IStatisticRow[] + return new[] { new StatisticRow { @@ -178,14 +178,6 @@ namespace osu.Game.Rulesets.Taiko }), } }, - new SimpleStatisticRow - { - Columns = 3, - Items = new SimpleStatisticItem[] - { - new UnstableRate(timedHitEvents) - } - } }; } } diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index f82ecd842a..3a7f433a37 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -217,6 +217,6 @@ namespace osu.Game.Rulesets /// The , converted for this with all relevant s applied. /// The s to display. Each may contain 0 or more . [NotNull] - public virtual IStatisticRow[] CreateStatisticsForScore(ScoreInfo score, IBeatmap playableBeatmap) => Array.Empty(); + public virtual StatisticRow[] CreateStatisticsForScore(ScoreInfo score, IBeatmap playableBeatmap) => Array.Empty(); } } diff --git a/osu.Game/Screens/Ranking/Statistics/IStatisticRow.cs b/osu.Game/Screens/Ranking/Statistics/IStatisticRow.cs deleted file mode 100644 index 67224041d5..0000000000 --- a/osu.Game/Screens/Ranking/Statistics/IStatisticRow.cs +++ /dev/null @@ -1,18 +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.Screens.Ranking.Statistics -{ - /// - /// A row of statistics to be displayed on the results screen. - /// - public interface IStatisticRow - { - /// - /// Creates the visual representation of this row. - /// - Drawable CreateDrawableStatisticRow(); - } -} diff --git a/osu.Game/Screens/Ranking/Statistics/SimpleStatisticRow.cs b/osu.Game/Screens/Ranking/Statistics/SimpleStatisticRow.cs index cd6afeb3a5..5c0cb5b116 100644 --- a/osu.Game/Screens/Ranking/Statistics/SimpleStatisticRow.cs +++ b/osu.Game/Screens/Ranking/Statistics/SimpleStatisticRow.cs @@ -10,7 +10,7 @@ namespace osu.Game.Screens.Ranking.Statistics /// /// Contains textual statistic data to display in a . /// - public class SimpleStatisticRow : IStatisticRow + public class SimpleStatisticRow { /// /// The number of columns to layout the in. diff --git a/osu.Game/Screens/Ranking/Statistics/StatisticRow.cs b/osu.Game/Screens/Ranking/Statistics/StatisticRow.cs index d5324e14f0..e1ca9799a3 100644 --- a/osu.Game/Screens/Ranking/Statistics/StatisticRow.cs +++ b/osu.Game/Screens/Ranking/Statistics/StatisticRow.cs @@ -1,39 +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 JetBrains.Annotations; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; namespace osu.Game.Screens.Ranking.Statistics { /// - /// A row of graphically detailed s to be displayed in the results screen. + /// A row of statistics to be displayed in the results screen. /// - public class StatisticRow : IStatisticRow + public class StatisticRow { /// /// The columns of this . /// [ItemNotNull] public StatisticItem[] Columns; - - public Drawable CreateDrawableStatisticRow() => new GridContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Content = new[] - { - Columns?.Select(c => new StatisticContainer(c) - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - }).Cast().ToArray() - }, - ColumnDimensions = Enumerable.Range(0, Columns?.Length ?? 0) - .Select(i => Columns[i].Dimension ?? new Dimension()).ToArray(), - RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize) } - }; } } diff --git a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs index 3b8f980070..128c6674e8 100644 --- a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs +++ b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs @@ -97,13 +97,27 @@ namespace osu.Game.Screens.Ranking.Statistics Alpha = 0 }; - rows.AddRange(newScore.Ruleset.CreateInstance() - .CreateStatisticsForScore(newScore, playableBeatmap) - .Select(row => row.CreateDrawableStatisticRow().With(r => - { - r.Anchor = Anchor.TopCentre; - r.Origin = Anchor.TopCentre; - }))); + foreach (var row in newScore.Ruleset.CreateInstance().CreateStatisticsForScore(newScore, playableBeatmap)) + { + rows.Add(new GridContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Content = new[] + { + row.Columns?.Select(c => new StatisticContainer(c) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }).Cast().ToArray() + }, + ColumnDimensions = Enumerable.Range(0, row.Columns?.Length ?? 0) + .Select(i => row.Columns[i].Dimension ?? new Dimension()).ToArray(), + RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize) } + }); + } LoadComponentAsync(rows, d => { From ce013ac9b4fe80910f187b2856985b695b93cf88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 27 Aug 2020 20:18:53 +0200 Subject: [PATCH 0732/1782] Make statistic header optional --- .../Ranking/Statistics/StatisticContainer.cs | 60 +++++++++++-------- .../Ranking/Statistics/StatisticItem.cs | 2 +- 2 files changed, 35 insertions(+), 27 deletions(-) diff --git a/osu.Game/Screens/Ranking/Statistics/StatisticContainer.cs b/osu.Game/Screens/Ranking/Statistics/StatisticContainer.cs index ed98698411..485d24d024 100644 --- a/osu.Game/Screens/Ranking/Statistics/StatisticContainer.cs +++ b/osu.Game/Screens/Ranking/Statistics/StatisticContainer.cs @@ -32,33 +32,9 @@ namespace osu.Game.Screens.Ranking.Statistics AutoSizeAxes = Axes.Y, Content = new[] { - new Drawable[] + new[] { - new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(5, 0), - Children = new Drawable[] - { - new Circle - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Height = 9, - Width = 4, - Colour = Color4Extensions.FromHex("#00FFAA") - }, - new OsuSpriteText - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Text = item.Name, - Font = OsuFont.GetFont(size: 14, weight: FontWeight.SemiBold), - } - } - } + createHeader(item) }, new Drawable[] { @@ -78,5 +54,37 @@ namespace osu.Game.Screens.Ranking.Statistics } }; } + + private static Drawable createHeader(StatisticItem item) + { + if (string.IsNullOrEmpty(item.Name)) + return Empty(); + + return new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(5, 0), + Children = new Drawable[] + { + new Circle + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Height = 9, + Width = 4, + Colour = Color4Extensions.FromHex("#00FFAA") + }, + new OsuSpriteText + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Text = item.Name, + Font = OsuFont.GetFont(size: 14, weight: FontWeight.SemiBold), + } + } + }; + } } } diff --git a/osu.Game/Screens/Ranking/Statistics/StatisticItem.cs b/osu.Game/Screens/Ranking/Statistics/StatisticItem.cs index e959ed24fc..4903983759 100644 --- a/osu.Game/Screens/Ranking/Statistics/StatisticItem.cs +++ b/osu.Game/Screens/Ranking/Statistics/StatisticItem.cs @@ -30,7 +30,7 @@ namespace osu.Game.Screens.Ranking.Statistics /// /// Creates a new , to be displayed inside a in the results screen. /// - /// The name of the item. + /// The name of the item. Can be to hide the item header. /// The content to be displayed. /// The of this item. This can be thought of as the column dimension of an encompassing . public StatisticItem([NotNull] string name, [NotNull] Drawable content, [CanBeNull] Dimension dimension = null) From ea1f07e311add124437f0346fbb85300c88ab699 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 27 Aug 2020 20:30:57 +0200 Subject: [PATCH 0733/1782] Simplify/rename SimpleStatisticRow mess --- ...ow.cs => TestSceneSimpleStatisticTable.cs} | 6 ++-- .../Ranking/Statistics/SimpleStatisticRow.cs | 34 ------------------- ...tatisticRow.cs => SimpleStatisticTable.cs} | 6 ++-- 3 files changed, 6 insertions(+), 40 deletions(-) rename osu.Game.Tests/Visual/Ranking/{TestSceneDrawableSimpleStatisticRow.cs => TestSceneSimpleStatisticTable.cs} (88%) delete mode 100644 osu.Game/Screens/Ranking/Statistics/SimpleStatisticRow.cs rename osu.Game/Screens/Ranking/Statistics/{DrawableSimpleStatisticRow.cs => SimpleStatisticTable.cs} (93%) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneDrawableSimpleStatisticRow.cs b/osu.Game.Tests/Visual/Ranking/TestSceneSimpleStatisticTable.cs similarity index 88% rename from osu.Game.Tests/Visual/Ranking/TestSceneDrawableSimpleStatisticRow.cs rename to osu.Game.Tests/Visual/Ranking/TestSceneSimpleStatisticTable.cs index 2b0ba30357..07a0bcc8d8 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneDrawableSimpleStatisticRow.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneSimpleStatisticTable.cs @@ -13,7 +13,7 @@ using osu.Game.Screens.Ranking.Statistics; namespace osu.Game.Tests.Visual.Ranking { - public class TestSceneDrawableSimpleStatisticRow : OsuTestScene + public class TestSceneSimpleStatisticTable : OsuTestScene { private Container container; @@ -45,7 +45,7 @@ namespace osu.Game.Tests.Visual.Ranking public void TestEmpty() { AddStep("create with no items", - () => container.Add(new DrawableSimpleStatisticRow(2, Enumerable.Empty()))); + () => container.Add(new SimpleStatisticTable(2, Enumerable.Empty()))); } [Test] @@ -61,7 +61,7 @@ namespace osu.Game.Tests.Visual.Ranking Value = RNG.Next(100) }); - container.Add(new DrawableSimpleStatisticRow(columnCount, items)); + container.Add(new SimpleStatisticTable(columnCount, items)); }); } } diff --git a/osu.Game/Screens/Ranking/Statistics/SimpleStatisticRow.cs b/osu.Game/Screens/Ranking/Statistics/SimpleStatisticRow.cs deleted file mode 100644 index 5c0cb5b116..0000000000 --- a/osu.Game/Screens/Ranking/Statistics/SimpleStatisticRow.cs +++ /dev/null @@ -1,34 +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 JetBrains.Annotations; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; - -namespace osu.Game.Screens.Ranking.Statistics -{ - /// - /// Contains textual statistic data to display in a . - /// - public class SimpleStatisticRow - { - /// - /// The number of columns to layout the in. - /// - public int Columns { get; set; } - - /// - /// The s that this row should contain. - /// - [ItemNotNull] - public SimpleStatisticItem[] Items { get; set; } - - public Drawable CreateDrawableStatisticRow() => new Container - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Padding = new MarginPadding(20), - Child = new DrawableSimpleStatisticRow(Columns, Items) - }; - } -} diff --git a/osu.Game/Screens/Ranking/Statistics/DrawableSimpleStatisticRow.cs b/osu.Game/Screens/Ranking/Statistics/SimpleStatisticTable.cs similarity index 93% rename from osu.Game/Screens/Ranking/Statistics/DrawableSimpleStatisticRow.cs rename to osu.Game/Screens/Ranking/Statistics/SimpleStatisticTable.cs index 7f69b323d8..8b503cc04e 100644 --- a/osu.Game/Screens/Ranking/Statistics/DrawableSimpleStatisticRow.cs +++ b/osu.Game/Screens/Ranking/Statistics/SimpleStatisticTable.cs @@ -14,10 +14,10 @@ using osu.Framework.Graphics.Shapes; namespace osu.Game.Screens.Ranking.Statistics { /// - /// Represents a statistic row with simple statistics (ones that only need textual display). + /// Represents a table with simple statistics (ones that only need textual display). /// Richer visualisations should be done with s and s. /// - public class DrawableSimpleStatisticRow : CompositeDrawable + public class SimpleStatisticTable : CompositeDrawable { private readonly SimpleStatisticItem[] items; private readonly int columnCount; @@ -29,7 +29,7 @@ namespace osu.Game.Screens.Ranking.Statistics /// /// The number of columns to layout the into. /// The s to display in this row. - public DrawableSimpleStatisticRow(int columnCount, [ItemNotNull] IEnumerable items) + public SimpleStatisticTable(int columnCount, [ItemNotNull] IEnumerable items) { if (columnCount < 1) throw new ArgumentOutOfRangeException(nameof(columnCount)); From 43d6d2b2e8845d073f78fa491d4f9946217828c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 27 Aug 2020 20:46:49 +0200 Subject: [PATCH 0734/1782] Add back unstable rate display --- osu.Game.Rulesets.Mania/ManiaRuleset.cs | 10 ++++++++++ osu.Game.Rulesets.Osu/OsuRuleset.cs | 10 ++++++++++ osu.Game.Rulesets.Taiko/TaikoRuleset.cs | 10 ++++++++++ 3 files changed, 30 insertions(+) diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index bbfc5739ec..f7098faa5d 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -327,6 +327,16 @@ namespace osu.Game.Rulesets.Mania }), } }, + new StatisticRow + { + Columns = new[] + { + new StatisticItem(string.Empty, new SimpleStatisticTable(3, new SimpleStatisticItem[] + { + new UnstableRate(score.HitEvents) + })) + } + } }; } diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index 14e7b9e9a4..f527eb2312 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -222,6 +222,16 @@ namespace osu.Game.Rulesets.Osu }), } }, + new StatisticRow + { + Columns = new[] + { + new StatisticItem(string.Empty, new SimpleStatisticTable(3, new SimpleStatisticItem[] + { + new UnstableRate(timedHitEvents) + })) + } + } }; } } diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index 367d991677..dbc32f2c3e 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -178,6 +178,16 @@ namespace osu.Game.Rulesets.Taiko }), } }, + new StatisticRow + { + Columns = new[] + { + new StatisticItem(string.Empty, new SimpleStatisticTable(3, new SimpleStatisticItem[] + { + new UnstableRate(timedHitEvents) + })) + } + } }; } } From 6846a245f42e60955ea20be79c7a2b43e334cbde Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 27 Aug 2020 20:51:28 +0200 Subject: [PATCH 0735/1782] Reapply lost anchoring fix --- osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs index 128c6674e8..c2ace6a04e 100644 --- a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs +++ b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs @@ -101,8 +101,8 @@ namespace osu.Game.Screens.Ranking.Statistics { rows.Add(new GridContainer { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Content = new[] From 1c1afa1c962b99fe082d8f7e1dfc06336922ec7f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 28 Aug 2020 19:16:20 +0900 Subject: [PATCH 0736/1782] Move MaxCombo to base DifficultyAttributes --- osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyAttributes.cs | 1 - osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs | 1 - osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyAttributes.cs | 1 - osu.Game/Rulesets/Difficulty/DifficultyAttributes.cs | 1 + 4 files changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyAttributes.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyAttributes.cs index 75f5b18607..fa9011d826 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyAttributes.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyAttributes.cs @@ -8,6 +8,5 @@ namespace osu.Game.Rulesets.Catch.Difficulty public class CatchDifficultyAttributes : DifficultyAttributes { public double ApproachRate; - public int MaxCombo; } } diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs index 6e991a1d08..a9879013f8 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs @@ -11,6 +11,5 @@ namespace osu.Game.Rulesets.Osu.Difficulty public double SpeedStrain; public double ApproachRate; public double OverallDifficulty; - public int MaxCombo; } } diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyAttributes.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyAttributes.cs index 75d3807bba..00ad956c8f 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyAttributes.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyAttributes.cs @@ -8,6 +8,5 @@ namespace osu.Game.Rulesets.Taiko.Difficulty public class TaikoDifficultyAttributes : DifficultyAttributes { public double GreatHitWindow; - public int MaxCombo; } } diff --git a/osu.Game/Rulesets/Difficulty/DifficultyAttributes.cs b/osu.Game/Rulesets/Difficulty/DifficultyAttributes.cs index b4b4bb9cd1..732dc772b7 100644 --- a/osu.Game/Rulesets/Difficulty/DifficultyAttributes.cs +++ b/osu.Game/Rulesets/Difficulty/DifficultyAttributes.cs @@ -12,6 +12,7 @@ namespace osu.Game.Rulesets.Difficulty public Skill[] Skills; public double StarRating; + public int MaxCombo; public DifficultyAttributes() { From 85bda29b71a790cb7675d46b86dce2462deae3c7 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 28 Aug 2020 19:16:24 +0900 Subject: [PATCH 0737/1782] Add mania max combo attribute --- osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs index 37cba1fd3c..b08c520c54 100644 --- a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs @@ -11,6 +11,7 @@ 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.Scoring; @@ -43,6 +44,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty 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.WindowFor(HitResult.Great)) / clockRate, + MaxCombo = beatmap.HitObjects.Sum(h => h is HoldNote ? 2 : 1), Skills = skills }; } From 4d15f0fe520f188c9219aa7e716679967d4c5f49 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 28 Aug 2020 19:16:46 +0900 Subject: [PATCH 0738/1782] Implement basic score recalculation --- osu.Game/Beatmaps/BeatmapDifficultyManager.cs | 10 ++++--- .../Online/Leaderboards/LeaderboardScore.cs | 4 +-- osu.Game/OsuGameBase.cs | 9 +++--- .../Overlays/BeatmapSet/Scores/ScoreTable.cs | 5 +++- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 13 +++++--- osu.Game/Scoring/ScoreManager.cs | 30 ++++++++++++++++++- 6 files changed, 55 insertions(+), 16 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapDifficultyManager.cs b/osu.Game/Beatmaps/BeatmapDifficultyManager.cs index b80b4e45ed..c02b6002d9 100644 --- a/osu.Game/Beatmaps/BeatmapDifficultyManager.cs +++ b/osu.Game/Beatmaps/BeatmapDifficultyManager.cs @@ -201,11 +201,11 @@ namespace osu.Game.Beatmaps var calculator = ruleset.CreateDifficultyCalculator(beatmapManager.GetWorkingBeatmap(beatmapInfo)); var attributes = calculator.Calculate(key.Mods); - return difficultyCache[key] = new StarDifficulty(attributes.StarRating); + return difficultyCache[key] = new StarDifficulty(attributes.StarRating, attributes.MaxCombo); } catch { - return difficultyCache[key] = new StarDifficulty(0); + return difficultyCache[key] = new StarDifficulty(); } } @@ -227,7 +227,7 @@ namespace osu.Game.Beatmaps if (beatmapInfo.ID == 0 || rulesetInfo.ID == null) { // If not, fall back to the existing star difficulty (e.g. from an online source). - existingDifficulty = new StarDifficulty(beatmapInfo.StarDifficulty); + existingDifficulty = new StarDifficulty(beatmapInfo.StarDifficulty, beatmapInfo.MaxCombo ?? 0); key = default; return true; @@ -292,10 +292,12 @@ namespace osu.Game.Beatmaps public readonly struct StarDifficulty { public readonly double Stars; + public readonly int MaxCombo; - public StarDifficulty(double stars) + public StarDifficulty(double stars, int maxCombo) { Stars = stars; + MaxCombo = maxCombo; // Todo: Add more members (BeatmapInfo.DifficultyRating? Attributes? Etc...) } diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs index 87b283f6b5..3c7ef73594 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs @@ -72,7 +72,7 @@ namespace osu.Game.Online.Leaderboards } [BackgroundDependencyLoader] - private void load(IAPIProvider api, OsuColour colour) + private void load(IAPIProvider api, OsuColour colour, ScoreManager scoreManager) { var user = score.User; @@ -194,7 +194,7 @@ namespace osu.Game.Online.Leaderboards { TextColour = Color4.White, GlowColour = Color4Extensions.FromHex(@"83ccfa"), - Text = score.TotalScore.ToString(@"N0"), + Text = scoreManager.GetTotalScore(score).ToString(@"N0"), Font = OsuFont.Numeric.With(size: 23), }, RankContainer = new Container diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 98f60d52d3..1d92315b1f 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -57,6 +57,8 @@ namespace osu.Game protected ScoreManager ScoreManager; + protected BeatmapDifficultyManager DifficultyManager; + protected SkinManager SkinManager; protected RulesetStore RulesetStore; @@ -194,7 +196,7 @@ namespace osu.Game 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, Storage, API, contextFactory, Host)); + dependencies.Cache(ScoreManager = new ScoreManager(RulesetStore, () => BeatmapManager, Storage, API, contextFactory, Host, () => DifficultyManager)); 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 @@ -218,9 +220,8 @@ namespace osu.Game ScoreManager.Undelete(getBeatmapScores(item), true); }); - var difficultyManager = new BeatmapDifficultyManager(); - dependencies.Cache(difficultyManager); - AddInternal(difficultyManager); + dependencies.Cache(DifficultyManager = new BeatmapDifficultyManager()); + AddInternal(DifficultyManager); dependencies.Cache(KeyBindingStore = new KeyBindingStore(contextFactory, RulesetStore)); dependencies.Cache(SettingsStore = new SettingsStore(contextFactory)); diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs index 097ca27bf7..a3500826a0 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs @@ -25,6 +25,9 @@ namespace osu.Game.Overlays.BeatmapSet.Scores private const float row_height = 22; private const int text_size = 12; + [Resolved] + private ScoreManager scoreManager { get; set; } + private readonly FillFlowContainer backgroundFlow; private Color4 highAccuracyColour; @@ -121,7 +124,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores new OsuSpriteText { Margin = new MarginPadding { Right = horizontal_inset }, - Text = $@"{score.TotalScore:N0}", + Text = $@"{scoreManager.GetTotalScore(score):N0}", Font = OsuFont.GetFont(size: text_size, weight: index == 0 ? FontWeight.Bold : FontWeight.Medium) }, new OsuSpriteText diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index eac47aa089..057b1820f7 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -203,19 +203,24 @@ namespace osu.Game.Rulesets.Scoring } private double getScore(ScoringMode mode) + { + return GetScore(baseScore / maxBaseScore, HighestCombo.Value / maxHighestCombo, bonusScore, mode); + } + + public double GetScore(double accuracyRatio, double comboRatio, double bonus, ScoringMode mode) { switch (mode) { default: case ScoringMode.Standardised: - double accuracyScore = accuracyPortion * baseScore / maxBaseScore; - double comboScore = comboPortion * HighestCombo.Value / maxHighestCombo; + double accuracyScore = accuracyPortion * accuracyRatio; + double comboScore = comboPortion * comboRatio; - return (max_score * (accuracyScore + comboScore) + bonusScore) * scoreMultiplier; + return (max_score * (accuracyScore + comboScore) + bonus) * 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) * scoreMultiplier / 25); + return bonus + (accuracyRatio * maxBaseScore) * (1 + Math.Max(0, (comboRatio * maxHighestCombo) - 1) * scoreMultiplier / 25); } } diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index d5bd486e43..a82970d3df 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Linq.Expressions; +using JetBrains.Annotations; using Microsoft.EntityFrameworkCore; using osu.Framework.Logging; using osu.Framework.Platform; @@ -15,6 +16,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.Scoring; using osu.Game.Scoring.Legacy; namespace osu.Game.Scoring @@ -30,11 +32,16 @@ namespace osu.Game.Scoring private readonly RulesetStore rulesets; private readonly Func beatmaps; - public ScoreManager(RulesetStore rulesets, Func beatmaps, Storage storage, IAPIProvider api, IDatabaseContextFactory contextFactory, IIpcHost importHost = null) + [CanBeNull] + private readonly Func difficulties; + + public ScoreManager(RulesetStore rulesets, Func beatmaps, Storage storage, IAPIProvider api, IDatabaseContextFactory contextFactory, IIpcHost importHost = null, + Func difficulties = null) : base(storage, contextFactory, api, new ScoreStore(contextFactory, storage), importHost) { this.rulesets = rulesets; this.beatmaps = beatmaps; + this.difficulties = difficulties; } protected override ScoreInfo CreateModel(ArchiveReader archive) @@ -72,5 +79,26 @@ namespace osu.Game.Scoring protected override bool CheckLocalAvailability(ScoreInfo model, IQueryable items) => base.CheckLocalAvailability(model, items) || (model.OnlineScoreID != null && items.Any(i => i.OnlineScoreID == model.OnlineScoreID)); + + public long GetTotalScore(ScoreInfo score) + { + int? beatmapMaxCombo = score.Beatmap.MaxCombo; + + if (beatmapMaxCombo == null) + { + if (score.Beatmap.ID == 0 || difficulties == null) + return score.TotalScore; // Can't do anything. + + // We can compute the max combo locally. + beatmapMaxCombo = difficulties().GetDifficulty(score.Beatmap, score.Ruleset, score.Mods).MaxCombo; + } + + var ruleset = score.Ruleset.CreateInstance(); + var scoreProcessor = ruleset.CreateScoreProcessor(); + + scoreProcessor.Mods.Value = score.Mods; + + return (long)Math.Round(scoreProcessor.GetScore(score.Accuracy, (double)score.MaxCombo / beatmapMaxCombo.Value, 0, ScoringMode.Standardised)); + } } } From 1e5e5cae0cbeee115aa09337051e22c943164893 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 28 Aug 2020 21:34:34 +0900 Subject: [PATCH 0739/1782] Add support for standardised -> classic changes --- .../Graphics/Sprites/GlowingSpriteText.cs | 7 +++ .../Online/Leaderboards/LeaderboardScore.cs | 2 +- osu.Game/OsuGameBase.cs | 2 +- .../Overlays/BeatmapSet/Scores/ScoreTable.cs | 2 +- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 8 +-- osu.Game/Scoring/ScoreManager.cs | 60 +++++++++++++++---- 6 files changed, 62 insertions(+), 19 deletions(-) diff --git a/osu.Game/Graphics/Sprites/GlowingSpriteText.cs b/osu.Game/Graphics/Sprites/GlowingSpriteText.cs index 4aea5aa518..85df2d167f 100644 --- a/osu.Game/Graphics/Sprites/GlowingSpriteText.cs +++ b/osu.Game/Graphics/Sprites/GlowingSpriteText.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.Colour; using osu.Framework.Graphics.Containers; @@ -55,6 +56,12 @@ namespace osu.Game.Graphics.Sprites set => spriteText.UseFullGlyphHeight = blurredText.UseFullGlyphHeight = value; } + public Bindable Current + { + get => spriteText.Current; + set => spriteText.Current = value; + } + public GlowingSpriteText() { AutoSizeAxes = Axes.Both; diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs index 3c7ef73594..24bb43f1b7 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs @@ -194,7 +194,7 @@ namespace osu.Game.Online.Leaderboards { TextColour = Color4.White, GlowColour = Color4Extensions.FromHex(@"83ccfa"), - Text = scoreManager.GetTotalScore(score).ToString(@"N0"), + Current = scoreManager.GetTotalScore(score), Font = OsuFont.Numeric.With(size: 23), }, RankContainer = new Container diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 1d92315b1f..3839d4e734 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -196,7 +196,7 @@ namespace osu.Game 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, Storage, API, contextFactory, Host, () => DifficultyManager)); + dependencies.Cache(ScoreManager = new ScoreManager(RulesetStore, () => BeatmapManager, Storage, API, contextFactory, Host, () => DifficultyManager, LocalConfig)); 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 diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs index a3500826a0..832ac75882 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs @@ -124,7 +124,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores new OsuSpriteText { Margin = new MarginPadding { Right = horizontal_inset }, - Text = $@"{scoreManager.GetTotalScore(score):N0}", + Current = scoreManager.GetTotalScore(score), Font = OsuFont.GetFont(size: text_size, weight: index == 0 ? FontWeight.Bold : FontWeight.Medium) }, new OsuSpriteText diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 057b1820f7..7d138bd878 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -204,10 +204,10 @@ namespace osu.Game.Rulesets.Scoring private double getScore(ScoringMode mode) { - return GetScore(baseScore / maxBaseScore, HighestCombo.Value / maxHighestCombo, bonusScore, mode); + return GetScore(mode, maxBaseScore, maxHighestCombo, baseScore / maxBaseScore, HighestCombo.Value / maxHighestCombo, bonusScore); } - public double GetScore(double accuracyRatio, double comboRatio, double bonus, ScoringMode mode) + public double GetScore(ScoringMode mode, double maxBaseScore, double maxHighestCombo, double accuracyRatio, double comboRatio, double bonusScore) { switch (mode) { @@ -216,11 +216,11 @@ namespace osu.Game.Rulesets.Scoring double accuracyScore = accuracyPortion * accuracyRatio; double comboScore = comboPortion * comboRatio; - return (max_score * (accuracyScore + comboScore) + bonus) * scoreMultiplier; + return (max_score * (accuracyScore + comboScore) + 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 bonus + (accuracyRatio * maxBaseScore) * (1 + Math.Max(0, (comboRatio * maxHighestCombo) - 1) * scoreMultiplier / 25); + return bonusScore + (accuracyRatio * maxBaseScore) * (1 + Math.Max(0, (comboRatio * maxHighestCombo) - 1) * scoreMultiplier / 25); } } diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index a82970d3df..134a41a7d4 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -8,9 +8,11 @@ using System.Linq; using System.Linq.Expressions; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore; +using osu.Framework.Bindables; using osu.Framework.Logging; using osu.Framework.Platform; using osu.Game.Beatmaps; +using osu.Game.Configuration; using osu.Game.Database; using osu.Game.IO.Archives; using osu.Game.Online.API; @@ -35,13 +37,17 @@ namespace osu.Game.Scoring [CanBeNull] private readonly Func difficulties; + [CanBeNull] + private readonly OsuConfigManager configManager; + public ScoreManager(RulesetStore rulesets, Func beatmaps, Storage storage, IAPIProvider api, IDatabaseContextFactory contextFactory, IIpcHost importHost = null, - Func difficulties = null) + Func difficulties = null, OsuConfigManager configManager = null) : base(storage, contextFactory, api, new ScoreStore(contextFactory, storage), importHost) { this.rulesets = rulesets; this.beatmaps = beatmaps; this.difficulties = difficulties; + this.configManager = configManager; } protected override ScoreInfo CreateModel(ArchiveReader archive) @@ -80,25 +86,55 @@ namespace osu.Game.Scoring => base.CheckLocalAvailability(model, items) || (model.OnlineScoreID != null && items.Any(i => i.OnlineScoreID == model.OnlineScoreID)); - public long GetTotalScore(ScoreInfo score) + public Bindable GetTotalScore(ScoreInfo score) { - int? beatmapMaxCombo = score.Beatmap.MaxCombo; + var bindable = new TotalScoreBindable(score, difficulties); + configManager?.BindWith(OsuSetting.ScoreDisplayMode, bindable.ScoringMode); + return bindable; + } - if (beatmapMaxCombo == null) + private class TotalScoreBindable : Bindable + { + public readonly Bindable ScoringMode = new Bindable(); + + private readonly ScoreInfo score; + private readonly Func difficulties; + + public TotalScoreBindable(ScoreInfo score, Func difficulties) { - if (score.Beatmap.ID == 0 || difficulties == null) - return score.TotalScore; // Can't do anything. + this.score = score; + this.difficulties = difficulties; - // We can compute the max combo locally. - beatmapMaxCombo = difficulties().GetDifficulty(score.Beatmap, score.Ruleset, score.Mods).MaxCombo; + ScoringMode.BindValueChanged(onScoringModeChanged, true); } - var ruleset = score.Ruleset.CreateInstance(); - var scoreProcessor = ruleset.CreateScoreProcessor(); + private void onScoringModeChanged(ValueChangedEvent mode) + { + int? beatmapMaxCombo = score.Beatmap.MaxCombo; - scoreProcessor.Mods.Value = score.Mods; + if (beatmapMaxCombo == null) + { + if (score.Beatmap.ID == 0 || difficulties == null) + { + // We don't have enough information (max combo) to compute the score, so let's use the provided score. + Value = score.TotalScore.ToString("N0"); + return; + } - return (long)Math.Round(scoreProcessor.GetScore(score.Accuracy, (double)score.MaxCombo / beatmapMaxCombo.Value, 0, ScoringMode.Standardised)); + // We can compute the max combo locally. + beatmapMaxCombo = difficulties().GetDifficulty(score.Beatmap, score.Ruleset, score.Mods).MaxCombo; + } + + var ruleset = score.Ruleset.CreateInstance(); + var scoreProcessor = ruleset.CreateScoreProcessor(); + + scoreProcessor.Mods.Value = score.Mods; + + double maxBaseScore = 300 * beatmapMaxCombo.Value; + double maxHighestCombo = beatmapMaxCombo.Value; + + Value = Math.Round(scoreProcessor.GetScore(mode.NewValue, maxBaseScore, maxHighestCombo, score.Accuracy, score.MaxCombo / maxHighestCombo, 0)).ToString("N0"); + } } } } From 39f8b5eb854df85ff989c71e57aad50dfb558bbf Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 28 Aug 2020 21:45:27 +0900 Subject: [PATCH 0740/1782] Use async difficulty calculation --- osu.Game/Scoring/ScoreManager.cs | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index 134a41a7d4..8d3872cda0 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -108,6 +108,8 @@ namespace osu.Game.Scoring ScoringMode.BindValueChanged(onScoringModeChanged, true); } + private IBindable difficultyBindable; + private void onScoringModeChanged(ValueChangedEvent mode) { int? beatmapMaxCombo = score.Beatmap.MaxCombo; @@ -121,19 +123,25 @@ namespace osu.Game.Scoring return; } - // We can compute the max combo locally. - beatmapMaxCombo = difficulties().GetDifficulty(score.Beatmap, score.Ruleset, score.Mods).MaxCombo; + // We can compute the max combo locally after the async beatmap difficulty computation. + difficultyBindable = difficulties().GetBindableDifficulty(score.Beatmap, score.Ruleset, score.Mods); + difficultyBindable.BindValueChanged(d => updateScore(d.NewValue.MaxCombo), true); } + else + updateScore(beatmapMaxCombo.Value); + } + private void updateScore(int beatmapMaxCombo) + { var ruleset = score.Ruleset.CreateInstance(); var scoreProcessor = ruleset.CreateScoreProcessor(); scoreProcessor.Mods.Value = score.Mods; - double maxBaseScore = 300 * beatmapMaxCombo.Value; - double maxHighestCombo = beatmapMaxCombo.Value; + double maxBaseScore = 300 * beatmapMaxCombo; + double maxHighestCombo = beatmapMaxCombo; - Value = Math.Round(scoreProcessor.GetScore(mode.NewValue, maxBaseScore, maxHighestCombo, score.Accuracy, score.MaxCombo / maxHighestCombo, 0)).ToString("N0"); + Value = Math.Round(scoreProcessor.GetScore(ScoringMode.Value, maxBaseScore, maxHighestCombo, score.Accuracy, score.MaxCombo / maxHighestCombo, 0)).ToString("N0"); } } } From 43c61e58308e2f411971a72da430292088d03487 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 28 Aug 2020 22:08:28 +0900 Subject: [PATCH 0741/1782] Re-query beatmap difficulty before computing --- osu.Game/Beatmaps/BeatmapDifficultyManager.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapDifficultyManager.cs b/osu.Game/Beatmaps/BeatmapDifficultyManager.cs index b80b4e45ed..490f1ba67c 100644 --- a/osu.Game/Beatmaps/BeatmapDifficultyManager.cs +++ b/osu.Game/Beatmaps/BeatmapDifficultyManager.cs @@ -89,8 +89,14 @@ namespace osu.Game.Beatmaps if (tryGetExisting(beatmapInfo, rulesetInfo, mods, out var existing, out var key)) return existing; - return await Task.Factory.StartNew(() => computeDifficulty(key, beatmapInfo, rulesetInfo), cancellationToken, - TaskCreationOptions.HideScheduler | TaskCreationOptions.RunContinuationsAsynchronously, updateScheduler); + return await Task.Factory.StartNew(() => + { + // Computation may have finished in a previous task. + if (tryGetExisting(beatmapInfo, rulesetInfo, mods, out existing, out _)) + return existing; + + return computeDifficulty(key, beatmapInfo, rulesetInfo); + }, cancellationToken, TaskCreationOptions.HideScheduler | TaskCreationOptions.RunContinuationsAsynchronously, updateScheduler); } /// From 436dbafe57614261e9380825aea13b802c9a6dbb Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 28 Aug 2020 22:12:17 +0900 Subject: [PATCH 0742/1782] Fix incorrect comparison for mods of different instances --- .../Beatmaps/BeatmapDifficultyManagerTest.cs | 32 +++++++++++++++++++ osu.Game/Beatmaps/BeatmapDifficultyManager.cs | 4 +-- 2 files changed, 34 insertions(+), 2 deletions(-) create mode 100644 osu.Game.Tests/Beatmaps/BeatmapDifficultyManagerTest.cs diff --git a/osu.Game.Tests/Beatmaps/BeatmapDifficultyManagerTest.cs b/osu.Game.Tests/Beatmaps/BeatmapDifficultyManagerTest.cs new file mode 100644 index 0000000000..0f6d956b3c --- /dev/null +++ b/osu.Game.Tests/Beatmaps/BeatmapDifficultyManagerTest.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 NUnit.Framework; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu.Mods; + +namespace osu.Game.Tests.Beatmaps +{ + [TestFixture] + public class BeatmapDifficultyManagerTest + { + [Test] + public void TestKeyEqualsWithDifferentModInstances() + { + var key1 = new BeatmapDifficultyManager.DifficultyCacheLookup(1234, 0, new Mod[] { new OsuModHardRock(), new OsuModHidden() }); + var key2 = new BeatmapDifficultyManager.DifficultyCacheLookup(1234, 0, new Mod[] { new OsuModHardRock(), new OsuModHidden() }); + + Assert.That(key1, Is.EqualTo(key2)); + } + + [Test] + public void TestKeyEqualsWithDifferentModOrder() + { + var key1 = new BeatmapDifficultyManager.DifficultyCacheLookup(1234, 0, new Mod[] { new OsuModHardRock(), new OsuModHidden() }); + var key2 = new BeatmapDifficultyManager.DifficultyCacheLookup(1234, 0, new Mod[] { new OsuModHidden(), new OsuModHardRock() }); + + Assert.That(key1, Is.EqualTo(key2)); + } + } +} diff --git a/osu.Game/Beatmaps/BeatmapDifficultyManager.cs b/osu.Game/Beatmaps/BeatmapDifficultyManager.cs index 490f1ba67c..0100c9b210 100644 --- a/osu.Game/Beatmaps/BeatmapDifficultyManager.cs +++ b/osu.Game/Beatmaps/BeatmapDifficultyManager.cs @@ -251,7 +251,7 @@ namespace osu.Game.Beatmaps updateScheduler?.Dispose(); } - private readonly struct DifficultyCacheLookup : IEquatable + public readonly struct DifficultyCacheLookup : IEquatable { public readonly int BeatmapId; public readonly int RulesetId; @@ -267,7 +267,7 @@ namespace osu.Game.Beatmaps public bool Equals(DifficultyCacheLookup other) => BeatmapId == other.BeatmapId && RulesetId == other.RulesetId - && Mods.SequenceEqual(other.Mods); + && Mods.Select(m => m.Acronym).SequenceEqual(other.Mods.Select(m => m.Acronym)); public override int GetHashCode() { From 8ffc4309fb14937c57dfad9270968d3488cbc91c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 28 Aug 2020 22:23:44 +0900 Subject: [PATCH 0743/1782] Fix possible NaN values --- osu.Game/Scoring/ScoreManager.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index 8d3872cda0..0165c5dc82 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -105,6 +105,8 @@ namespace osu.Game.Scoring this.score = score; this.difficulties = difficulties; + Value = "0"; + ScoringMode.BindValueChanged(onScoringModeChanged, true); } @@ -133,6 +135,12 @@ namespace osu.Game.Scoring private void updateScore(int beatmapMaxCombo) { + if (beatmapMaxCombo == 0) + { + Value = "0"; + return; + } + var ruleset = score.Ruleset.CreateInstance(); var scoreProcessor = ruleset.CreateScoreProcessor(); From d7bbb362bf5f0051bbecd4468e63be079a3252f3 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 28 Aug 2020 22:51:19 +0900 Subject: [PATCH 0744/1782] Separate bindables --- .../Online/Leaderboards/LeaderboardScore.cs | 2 +- .../Overlays/BeatmapSet/Scores/ScoreTable.cs | 2 +- osu.Game/Scoring/ScoreManager.cs | 26 ++++++++++++++----- 3 files changed, 22 insertions(+), 8 deletions(-) diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs index 24bb43f1b7..846bebe347 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs @@ -194,7 +194,7 @@ namespace osu.Game.Online.Leaderboards { TextColour = Color4.White, GlowColour = Color4Extensions.FromHex(@"83ccfa"), - Current = scoreManager.GetTotalScore(score), + Current = scoreManager.GetTotalScoreString(score), Font = OsuFont.Numeric.With(size: 23), }, RankContainer = new Container diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs index 832ac75882..6bebd98eef 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs @@ -124,7 +124,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores new OsuSpriteText { Margin = new MarginPadding { Right = horizontal_inset }, - Current = scoreManager.GetTotalScore(score), + Current = scoreManager.GetTotalScoreString(score), Font = OsuFont.GetFont(size: text_size, weight: index == 0 ? FontWeight.Bold : FontWeight.Medium) }, new OsuSpriteText diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index 0165c5dc82..1943cab992 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -86,14 +86,16 @@ namespace osu.Game.Scoring => base.CheckLocalAvailability(model, items) || (model.OnlineScoreID != null && items.Any(i => i.OnlineScoreID == model.OnlineScoreID)); - public Bindable GetTotalScore(ScoreInfo score) + public Bindable GetTotalScore(ScoreInfo score) { var bindable = new TotalScoreBindable(score, difficulties); configManager?.BindWith(OsuSetting.ScoreDisplayMode, bindable.ScoringMode); return bindable; } - private class TotalScoreBindable : Bindable + public Bindable GetTotalScoreString(ScoreInfo score) => new TotalScoreStringBindable(GetTotalScore(score)); + + private class TotalScoreBindable : Bindable { public readonly Bindable ScoringMode = new Bindable(); @@ -105,7 +107,7 @@ namespace osu.Game.Scoring this.score = score; this.difficulties = difficulties; - Value = "0"; + Value = 0; ScoringMode.BindValueChanged(onScoringModeChanged, true); } @@ -121,7 +123,7 @@ namespace osu.Game.Scoring if (score.Beatmap.ID == 0 || difficulties == null) { // We don't have enough information (max combo) to compute the score, so let's use the provided score. - Value = score.TotalScore.ToString("N0"); + Value = score.TotalScore; return; } @@ -137,7 +139,7 @@ namespace osu.Game.Scoring { if (beatmapMaxCombo == 0) { - Value = "0"; + Value = 0; return; } @@ -149,7 +151,19 @@ namespace osu.Game.Scoring double maxBaseScore = 300 * beatmapMaxCombo; double maxHighestCombo = beatmapMaxCombo; - Value = Math.Round(scoreProcessor.GetScore(ScoringMode.Value, maxBaseScore, maxHighestCombo, score.Accuracy, score.MaxCombo / maxHighestCombo, 0)).ToString("N0"); + Value = (long)Math.Round(scoreProcessor.GetScore(ScoringMode.Value, maxBaseScore, maxHighestCombo, score.Accuracy, score.MaxCombo / maxHighestCombo, 0)); + } + } + + private class TotalScoreStringBindable : Bindable + { + // ReSharper disable once PrivateFieldCanBeConvertedToLocalVariable (need to hold a reference) + private readonly IBindable totalScore; + + public TotalScoreStringBindable(IBindable totalScore) + { + this.totalScore = totalScore; + this.totalScore.BindValueChanged(v => Value = v.NewValue.ToString("N0"), true); } } } From ec2674e1ea60be3b7a649b38da3ff5ce39eadbe0 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 28 Aug 2020 22:51:39 +0900 Subject: [PATCH 0745/1782] Fix nullref with null beatmap --- osu.Game/Scoring/ScoreManager.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index 1943cab992..634cca159a 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -116,6 +116,12 @@ namespace osu.Game.Scoring private void onScoringModeChanged(ValueChangedEvent mode) { + if (score.Beatmap == null) + { + Value = score.TotalScore; + return; + } + int? beatmapMaxCombo = score.Beatmap.MaxCombo; if (beatmapMaxCombo == null) From c1838902a669f3e8fb7c4b0c1e25bf50dfa36c84 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 28 Aug 2020 22:51:48 +0900 Subject: [PATCH 0746/1782] Add to more places --- .../Graphics/UserInterface/RollingCounter.cs | 14 ++++++---- .../Scores/TopScoreStatisticsSection.cs | 28 ++++++++++++++++++- .../ContractedPanelMiddleContent.cs | 5 +++- .../Expanded/ExpandedPanelMiddleContent.cs | 9 +++--- 4 files changed, 45 insertions(+), 11 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/RollingCounter.cs b/osu.Game/Graphics/UserInterface/RollingCounter.cs index 6763198213..a469927595 100644 --- a/osu.Game/Graphics/UserInterface/RollingCounter.cs +++ b/osu.Game/Graphics/UserInterface/RollingCounter.cs @@ -9,16 +9,20 @@ using System; using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Graphics.UserInterface; namespace osu.Game.Graphics.UserInterface { - public abstract class RollingCounter : Container + public abstract class RollingCounter : Container, IHasCurrentValue where T : struct, IEquatable { - /// - /// The current value. - /// - public Bindable Current = new Bindable(); + private readonly BindableWithCurrent current = new BindableWithCurrent(); + + public Bindable Current + { + get => current.Current; + set => current.Current = value; + } private SpriteText displayedCountSpriteText; diff --git a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs index a92346e0fe..507c692eb1 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs @@ -4,6 +4,7 @@ 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; @@ -38,6 +39,9 @@ namespace osu.Game.Overlays.BeatmapSet.Scores private readonly FillFlowContainer statisticsColumns; private readonly ModsInfoColumn modsColumn; + [Resolved] + private ScoreManager scoreManager { get; set; } + public TopScoreStatisticsSection() { RelativeSizeAxes = Axes.X; @@ -87,6 +91,15 @@ namespace osu.Game.Overlays.BeatmapSet.Scores }; } + [BackgroundDependencyLoader] + private void load() + { + if (score != null) + totalScoreColumn.Current = scoreManager.GetTotalScoreString(score); + } + + private ScoreInfo score; + /// /// Sets the score to be displayed. /// @@ -94,7 +107,11 @@ namespace osu.Game.Overlays.BeatmapSet.Scores { set { - totalScoreColumn.Text = $@"{value.TotalScore:N0}"; + if (score == value) + return; + + score = value; + accuracyColumn.Text = value.DisplayAccuracy; maxComboColumn.Text = $@"{value.MaxCombo:N0}x"; ppColumn.Alpha = value.Beatmap?.Status == BeatmapSetOnlineStatus.Ranked ? 1 : 0; @@ -102,6 +119,9 @@ namespace osu.Game.Overlays.BeatmapSet.Scores statisticsColumns.ChildrenEnumerable = value.SortedStatistics.Select(kvp => createStatisticsColumn(kvp.Key, kvp.Value)); modsColumn.Mods = value.Mods; + + if (IsLoaded) + totalScoreColumn.Current = scoreManager.GetTotalScoreString(value); } } @@ -190,6 +210,12 @@ namespace osu.Game.Overlays.BeatmapSet.Scores { set => text.Text = value; } + + public Bindable Current + { + get => text.Current; + set => text.Current = value; + } } private class ModsInfoColumn : InfoColumn diff --git a/osu.Game/Screens/Ranking/Contracted/ContractedPanelMiddleContent.cs b/osu.Game/Screens/Ranking/Contracted/ContractedPanelMiddleContent.cs index 8cd0e7025e..b37b89e6c0 100644 --- a/osu.Game/Screens/Ranking/Contracted/ContractedPanelMiddleContent.cs +++ b/osu.Game/Screens/Ranking/Contracted/ContractedPanelMiddleContent.cs @@ -30,6 +30,9 @@ namespace osu.Game.Screens.Ranking.Contracted { private readonly ScoreInfo score; + [Resolved] + private ScoreManager scoreManager { get; set; } + /// /// Creates a new . /// @@ -160,7 +163,7 @@ namespace osu.Game.Screens.Ranking.Contracted { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Text = score.TotalScore.ToString("N0"), + Current = scoreManager.GetTotalScoreString(score), Font = OsuFont.GetFont(size: 20, weight: FontWeight.Medium, fixedWidth: true), Spacing = new Vector2(-1, 0) }, diff --git a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs index 01502c0913..3433410d3c 100644 --- a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs +++ b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs @@ -25,15 +25,16 @@ namespace osu.Game.Screens.Ranking.Expanded /// public class ExpandedPanelMiddleContent : CompositeDrawable { - private readonly ScoreInfo score; + private const float padding = 10; + private readonly ScoreInfo score; private readonly List statisticDisplays = new List(); private FillFlowContainer starAndModDisplay; - private RollingCounter scoreCounter; - private const float padding = 10; + [Resolved] + private ScoreManager scoreManager { get; set; } /// /// Creates a new . @@ -238,7 +239,7 @@ namespace osu.Game.Screens.Ranking.Expanded using (BeginDelayedSequence(AccuracyCircle.ACCURACY_TRANSFORM_DELAY, true)) { scoreCounter.FadeIn(); - scoreCounter.Current.Value = score.TotalScore; + scoreCounter.Current = scoreManager.GetTotalScore(score); double delay = 0; From da5853e7eb2c7dd95c8d5ca22ca4a0d4488ae731 Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Sat, 29 Aug 2020 10:25:43 +0200 Subject: [PATCH 0747/1782] Create a new BeatmapSetInfo when setting files --- .../Beatmaps/Formats/LegacyBeatmapEncoderTest.cs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs index f093180085..dee4626cd0 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs @@ -60,12 +60,15 @@ namespace osu.Game.Tests.Beatmaps.Formats using (var reader = new LineBufferedReader(stream)) { var beatmap = new LegacyBeatmapDecoder { ApplyOffsets = false }.Decode(reader); - beatmap.BeatmapInfo.BeatmapSet.Files = new List + beatmap.BeatmapInfo.BeatmapSet = new BeatmapSetInfo { - new BeatmapSetFileInfo + Files = new List { - Filename = name, - FileInfo = new osu.Game.IO.FileInfo { Hash = name } + new BeatmapSetFileInfo + { + Filename = name, + FileInfo = new osu.Game.IO.FileInfo { Hash = name } + } } }; From 4cb9e1d4438895b7176c72464fcd6c65e738ac71 Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Sat, 29 Aug 2020 10:33:43 +0200 Subject: [PATCH 0748/1782] Initial commit --- .../TestSceneLegacyBeatmapSkin.cs | 2 +- osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs | 9 ++++++--- osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs | 5 +++-- osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs | 2 +- osu.Game/Beatmaps/IWorkingBeatmap.cs | 2 +- osu.Game/Beatmaps/WorkingBeatmap.cs | 8 ++++---- osu.Game/Skinning/BeatmapSkinProvidingContainer.cs | 4 ++-- osu.Game/Skinning/DefaultBeatmapSkin.cs | 9 +++++++++ osu.Game/Skinning/IBeatmapSkin.cs | 9 +++++++++ osu.Game/Skinning/LegacyBeatmapSkin.cs | 2 +- osu.Game/Tests/Beatmaps/HitObjectSampleTest.cs | 2 +- 11 files changed, 38 insertions(+), 16 deletions(-) create mode 100644 osu.Game/Skinning/DefaultBeatmapSkin.cs create mode 100644 osu.Game/Skinning/IBeatmapSkin.cs diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneLegacyBeatmapSkin.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneLegacyBeatmapSkin.cs index 3ff37c4147..03d18cefef 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneLegacyBeatmapSkin.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneLegacyBeatmapSkin.cs @@ -110,7 +110,7 @@ namespace osu.Game.Rulesets.Osu.Tests this.hasColours = hasColours; } - protected override ISkin GetSkin() => new TestBeatmapSkin(BeatmapInfo, hasColours); + protected override IBeatmapSkin GetSkin() => new TestBeatmapSkin(BeatmapInfo, hasColours); } private class TestBeatmapSkin : LegacyBeatmapSkin diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs index 075bf314bc..c3c19de17c 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs @@ -80,15 +80,18 @@ namespace osu.Game.Rulesets.Osu.Tests public class CustomSkinWorkingBeatmap : ClockBackedTestWorkingBeatmap { - private readonly ISkinSource skin; + private readonly ISkinSource skinSource; public CustomSkinWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard, IFrameBasedClock frameBasedClock, AudioManager audio, ISkinSource skin) : base(beatmap, storyboard, frameBasedClock, audio) { - this.skin = skin; + if (!(skinSource is IBeatmapSkin)) + throw new ArgumentException("The provided skin source must be of type IBeatmapSkin."); + + skinSource = skin; } - protected override ISkin GetSkin() => skin; + protected override IBeatmapSkin GetSkin() => (IBeatmapSkin)skinSource; } public class SkinProvidingPlayer : TestPlayer diff --git a/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs b/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs index b30870d057..996d495e17 100644 --- a/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs +++ b/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs @@ -116,7 +116,8 @@ namespace osu.Game.Tests.Gameplay AddAssert("sample playback rate matches mod rates", () => sample.Channel.AggregateFrequency.Value == expectedRate); } - private class TestSkin : LegacySkin + // TODO: adding IBeatmapSkin changes are as minimal as possible, but this shouldn't exist or should be reworked to work with LegacyBeatmapSkin + private class TestSkin : LegacySkin, IBeatmapSkin { public TestSkin(string resourceName, AudioManager audioManager) : base(DefaultLegacySkin.Info, new TestResourceStore(resourceName), audioManager, "skin.ini") @@ -156,7 +157,7 @@ namespace osu.Game.Tests.Gameplay this.audio = audio; } - protected override ISkin GetSkin() => new TestSkin("test-sample", audio); + protected override IBeatmapSkin GetSkin() => new TestSkin("test-sample", audio); } private class TestDrawableStoryboardSample : DrawableStoryboardSample diff --git a/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs b/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs index 39c5ccab27..44728cc251 100644 --- a/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs @@ -140,7 +140,7 @@ namespace osu.Game.Beatmaps return storyboard; } - protected override ISkin GetSkin() + protected override IBeatmapSkin GetSkin() { try { diff --git a/osu.Game/Beatmaps/IWorkingBeatmap.cs b/osu.Game/Beatmaps/IWorkingBeatmap.cs index 31975157a0..dac9389822 100644 --- a/osu.Game/Beatmaps/IWorkingBeatmap.cs +++ b/osu.Game/Beatmaps/IWorkingBeatmap.cs @@ -44,7 +44,7 @@ namespace osu.Game.Beatmaps /// /// Retrieves the which this provides. /// - ISkin Skin { get; } + IBeatmapSkin Skin { get; } /// /// Constructs a playable from using the applicable converters for a specific . diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs index b4bcf285b9..163b62a55c 100644 --- a/osu.Game/Beatmaps/WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/WorkingBeatmap.cs @@ -44,7 +44,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++; } @@ -275,10 +275,10 @@ namespace osu.Game.Beatmaps private readonly RecyclableLazy storyboard; public bool SkinLoaded => skin.IsResultAvailable; - public ISkin Skin => skin.Value; + public IBeatmapSkin Skin => skin.Value; - protected virtual ISkin GetSkin() => new DefaultSkin(); - private readonly RecyclableLazy skin; + protected virtual IBeatmapSkin GetSkin() => new DefaultBeatmapSkin(); + 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/BeatmapSkinProvidingContainer.cs b/osu.Game/Skinning/BeatmapSkinProvidingContainer.cs index 40335db697..346bfe53b8 100644 --- a/osu.Game/Skinning/BeatmapSkinProvidingContainer.cs +++ b/osu.Game/Skinning/BeatmapSkinProvidingContainer.cs @@ -11,7 +11,7 @@ namespace osu.Game.Skinning /// /// A container which overrides existing skin options with beatmap-local values. /// - public class BeatmapSkinProvidingContainer : SkinProvidingContainer + public class BeatmapSkinProvidingContainer : SkinProvidingContainer, IBeatmapSkin { private readonly Bindable beatmapSkins = new Bindable(); private readonly Bindable beatmapHitsounds = new Bindable(); @@ -21,7 +21,7 @@ namespace osu.Game.Skinning protected override bool AllowTextureLookup(string componentName) => beatmapSkins.Value; protected override bool AllowSampleLookup(ISampleInfo componentName) => beatmapHitsounds.Value; - public BeatmapSkinProvidingContainer(ISkin skin) + public BeatmapSkinProvidingContainer(IBeatmapSkin skin) : base(skin) { } diff --git a/osu.Game/Skinning/DefaultBeatmapSkin.cs b/osu.Game/Skinning/DefaultBeatmapSkin.cs new file mode 100644 index 0000000000..7b5ccd45c3 --- /dev/null +++ b/osu.Game/Skinning/DefaultBeatmapSkin.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.Skinning +{ + public class DefaultBeatmapSkin : DefaultSkin, IBeatmapSkin + { + } +} diff --git a/osu.Game/Skinning/IBeatmapSkin.cs b/osu.Game/Skinning/IBeatmapSkin.cs new file mode 100644 index 0000000000..77c34b8ad7 --- /dev/null +++ b/osu.Game/Skinning/IBeatmapSkin.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.Skinning +{ + public interface IBeatmapSkin : ISkin + { + } +} diff --git a/osu.Game/Skinning/LegacyBeatmapSkin.cs b/osu.Game/Skinning/LegacyBeatmapSkin.cs index d647bc4a2d..d53349dd11 100644 --- a/osu.Game/Skinning/LegacyBeatmapSkin.cs +++ b/osu.Game/Skinning/LegacyBeatmapSkin.cs @@ -11,7 +11,7 @@ using osu.Game.Rulesets.Objects.Legacy; namespace osu.Game.Skinning { - public class LegacyBeatmapSkin : LegacySkin + public class LegacyBeatmapSkin : LegacySkin, IBeatmapSkin { protected override bool AllowManiaSkin => false; protected override bool UseCustomSampleBanks => true; diff --git a/osu.Game/Tests/Beatmaps/HitObjectSampleTest.cs b/osu.Game/Tests/Beatmaps/HitObjectSampleTest.cs index ab4fb38657..db080d889f 100644 --- a/osu.Game/Tests/Beatmaps/HitObjectSampleTest.cs +++ b/osu.Game/Tests/Beatmaps/HitObjectSampleTest.cs @@ -188,7 +188,7 @@ namespace osu.Game.Tests.Beatmaps this.resourceStore = resourceStore; } - protected override ISkin GetSkin() => new LegacyBeatmapSkin(skinBeatmapInfo, resourceStore, AudioManager); + protected override IBeatmapSkin GetSkin() => new LegacyBeatmapSkin(skinBeatmapInfo, resourceStore, AudioManager); } } } From 1b81415a16e82131bb170f5473202112a82182ab Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Sat, 29 Aug 2020 10:50:25 +0200 Subject: [PATCH 0749/1782] Correct comment --- osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs b/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs index 996d495e17..1e3755c186 100644 --- a/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs +++ b/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs @@ -116,7 +116,7 @@ namespace osu.Game.Tests.Gameplay AddAssert("sample playback rate matches mod rates", () => sample.Channel.AggregateFrequency.Value == expectedRate); } - // TODO: adding IBeatmapSkin changes are as minimal as possible, but this shouldn't exist or should be reworked to work with LegacyBeatmapSkin + // TODO: adding IBeatmapSkin to keep changes as minimal as possible, but this shouldn't exist or should be reworked to inherit LegacyBeatmapSkin private class TestSkin : LegacySkin, IBeatmapSkin { public TestSkin(string resourceName, AudioManager audioManager) From 08329aa382d7afd64e9ce5afe30a94d55d0557ec Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Sat, 29 Aug 2020 11:05:10 +0200 Subject: [PATCH 0750/1782] Remove comment again --- osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs b/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs index 1e3755c186..bc9528beb6 100644 --- a/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs +++ b/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs @@ -116,7 +116,6 @@ namespace osu.Game.Tests.Gameplay AddAssert("sample playback rate matches mod rates", () => sample.Channel.AggregateFrequency.Value == expectedRate); } - // TODO: adding IBeatmapSkin to keep changes as minimal as possible, but this shouldn't exist or should be reworked to inherit LegacyBeatmapSkin private class TestSkin : LegacySkin, IBeatmapSkin { public TestSkin(string resourceName, AudioManager audioManager) From 82acb3506cbc36a0546ff77fc76c20d8854bb2ed Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Sat, 29 Aug 2020 11:07:28 +0200 Subject: [PATCH 0751/1782] Add and change xmldocs --- osu.Game/Beatmaps/IWorkingBeatmap.cs | 2 +- osu.Game/Skinning/IBeatmapSkin.cs | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/IWorkingBeatmap.cs b/osu.Game/Beatmaps/IWorkingBeatmap.cs index dac9389822..aac41725a9 100644 --- a/osu.Game/Beatmaps/IWorkingBeatmap.cs +++ b/osu.Game/Beatmaps/IWorkingBeatmap.cs @@ -42,7 +42,7 @@ namespace osu.Game.Beatmaps Storyboard Storyboard { get; } /// - /// Retrieves the which this provides. + /// Retrieves the which this provides. /// IBeatmapSkin Skin { get; } diff --git a/osu.Game/Skinning/IBeatmapSkin.cs b/osu.Game/Skinning/IBeatmapSkin.cs index 77c34b8ad7..91caaed557 100644 --- a/osu.Game/Skinning/IBeatmapSkin.cs +++ b/osu.Game/Skinning/IBeatmapSkin.cs @@ -3,6 +3,9 @@ namespace osu.Game.Skinning { + /// + /// Marker interface for skins that originate from beatmaps. + /// public interface IBeatmapSkin : ISkin { } From 658a1d159f03df2a55339c5eab7e11085c75ac43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 29 Aug 2020 11:45:59 +0200 Subject: [PATCH 0752/1782] Add legacy flag value for mirror mod --- osu.Game/Beatmaps/Legacy/LegacyMods.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Beatmaps/Legacy/LegacyMods.cs b/osu.Game/Beatmaps/Legacy/LegacyMods.cs index 583e950e49..0e517ea3df 100644 --- a/osu.Game/Beatmaps/Legacy/LegacyMods.cs +++ b/osu.Game/Beatmaps/Legacy/LegacyMods.cs @@ -38,5 +38,6 @@ namespace osu.Game.Beatmaps.Legacy Key1 = 1 << 26, Key3 = 1 << 27, Key2 = 1 << 28, + Mirror = 1 << 30, } } From 58742afd99f131baeb23f0bc85b8161da1559847 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 29 Aug 2020 11:47:31 +0200 Subject: [PATCH 0753/1782] Add failing test case --- osu.Game.Rulesets.Mania.Tests/ManiaLegacyModConversionTest.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaLegacyModConversionTest.cs b/osu.Game.Rulesets.Mania.Tests/ManiaLegacyModConversionTest.cs index 957743c5f1..b22687a0a7 100644 --- a/osu.Game.Rulesets.Mania.Tests/ManiaLegacyModConversionTest.cs +++ b/osu.Game.Rulesets.Mania.Tests/ManiaLegacyModConversionTest.cs @@ -23,6 +23,7 @@ namespace osu.Game.Rulesets.Mania.Tests [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) })] + [TestCase(LegacyMods.Flashlight | LegacyMods.Mirror, new[] { typeof(ManiaModFlashlight), typeof(ManiaModMirror) })] public new void Test(LegacyMods legacyMods, Type[] expectedMods) => base.Test(legacyMods, expectedMods); protected override Ruleset CreateRuleset() => new ManiaRuleset(); From da82556f6b647dff1d17b384c4061d80665b0393 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 29 Aug 2020 11:49:17 +0200 Subject: [PATCH 0754/1782] Add two-way legacy conversions for mirror mod --- osu.Game.Rulesets.Mania/ManiaRuleset.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index f7098faa5d..37b34d1721 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -126,6 +126,9 @@ namespace osu.Game.Rulesets.Mania if (mods.HasFlag(LegacyMods.Random)) yield return new ManiaModRandom(); + + if (mods.HasFlag(LegacyMods.Mirror)) + yield return new ManiaModMirror(); } public override LegacyMods ConvertToLegacyMods(Mod[] mods) @@ -175,6 +178,10 @@ namespace osu.Game.Rulesets.Mania case ManiaModFadeIn _: value |= LegacyMods.FadeIn; break; + + case ManiaModMirror _: + value |= LegacyMods.Mirror; + break; } } From 9ce9ba3a0d1906448611749a0dc391dcf3944e3d Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Sat, 29 Aug 2020 13:50:29 +0200 Subject: [PATCH 0755/1782] Update TestSceneSkinFallbacks.cs --- .../TestSceneSkinFallbacks.cs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs index c3c19de17c..0fe8949360 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs @@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.Osu.Tests public TestSceneSkinFallbacks() { testUserSkin = new TestSource("user"); - testBeatmapSkin = new TestSource("beatmap"); + testBeatmapSkin = new BeatmapTestSource(); } [Test] @@ -85,7 +85,7 @@ namespace osu.Game.Rulesets.Osu.Tests public CustomSkinWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard, IFrameBasedClock frameBasedClock, AudioManager audio, ISkinSource skin) : base(beatmap, storyboard, frameBasedClock, audio) { - if (!(skinSource is IBeatmapSkin)) + if (!(skin is IBeatmapSkin)) throw new ArgumentException("The provided skin source must be of type IBeatmapSkin."); skinSource = skin; @@ -115,6 +115,14 @@ namespace osu.Game.Rulesets.Osu.Tests } } + public class BeatmapTestSource : TestSource, IBeatmapSkin + { + public BeatmapTestSource() + : base("beatmap") + { + } + } + public class TestSource : ISkinSource { private readonly string identifier; From 43e91877a71c3451f4b9617212dbd0c38f7bdb2f Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Sat, 29 Aug 2020 14:47:26 +0200 Subject: [PATCH 0756/1782] Scope and limit parameter to IBeatmapSkin --- .../TestSceneSkinFallbacks.cs | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs index 0fe8949360..64da80a88e 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs @@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Osu.Tests public class TestSceneSkinFallbacks : TestSceneOsuPlayer { private readonly TestSource testUserSkin; - private readonly TestSource testBeatmapSkin; + private readonly BeatmapTestSource testBeatmapSkin; public TestSceneSkinFallbacks() { @@ -80,18 +80,15 @@ namespace osu.Game.Rulesets.Osu.Tests public class CustomSkinWorkingBeatmap : ClockBackedTestWorkingBeatmap { - private readonly ISkinSource skinSource; + private readonly IBeatmapSkin skin; - public CustomSkinWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard, IFrameBasedClock frameBasedClock, AudioManager audio, ISkinSource skin) + public CustomSkinWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard, IFrameBasedClock frameBasedClock, AudioManager audio, IBeatmapSkin skin) : base(beatmap, storyboard, frameBasedClock, audio) { - if (!(skin is IBeatmapSkin)) - throw new ArgumentException("The provided skin source must be of type IBeatmapSkin."); - - skinSource = skin; + this.skin = skin; } - protected override IBeatmapSkin GetSkin() => (IBeatmapSkin)skinSource; + protected override IBeatmapSkin GetSkin() => skin; } public class SkinProvidingPlayer : TestPlayer @@ -115,7 +112,7 @@ namespace osu.Game.Rulesets.Osu.Tests } } - public class BeatmapTestSource : TestSource, IBeatmapSkin + private class BeatmapTestSource : TestSource, IBeatmapSkin { public BeatmapTestSource() : base("beatmap") From 69fae0f4122ad64b9c25bdc83ccd4e9cb00c34ba Mon Sep 17 00:00:00 2001 From: Joehu Date: Sat, 29 Aug 2020 09:30:56 -0700 Subject: [PATCH 0757/1782] Add failing replay download button test --- .../Visual/Ranking/TestSceneResultsScreen.cs | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs index 74808bc2f5..a86fa05129 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs @@ -13,6 +13,7 @@ using osu.Framework.Screens; using osu.Framework.Testing; using osu.Framework.Utils; using osu.Game.Beatmaps; +using osu.Game.Graphics.UserInterface; using osu.Game.Online.API; using osu.Game.Rulesets.Osu; using osu.Game.Scoring; @@ -212,6 +213,25 @@ namespace osu.Game.Tests.Visual.Ranking AddAssert("expanded panel still on screen", () => this.ChildrenOfType().Single(p => p.State == PanelState.Expanded).ScreenSpaceDrawQuad.TopLeft.X > 0); } + [Test] + public void TestDownloadButtonInitallyDisabled() + { + TestResultsScreen screen = null; + + AddStep("load results", () => Child = new TestResultsContainer(screen = createResultsScreen())); + + AddAssert("download button is disabled", () => !screen.ChildrenOfType().First().Enabled.Value); + + AddStep("click contracted panel", () => + { + var contractedPanel = this.ChildrenOfType().First(p => p.State == PanelState.Contracted && p.ScreenSpaceDrawQuad.TopLeft.X > screen.ScreenSpaceDrawQuad.TopLeft.X); + InputManager.MoveMouseTo(contractedPanel); + InputManager.Click(MouseButton.Left); + }); + + AddAssert("download button is enabled", () => screen.ChildrenOfType().First().Enabled.Value); + } + private class TestResultsContainer : Container { [Cached(typeof(Player))] @@ -255,6 +275,7 @@ namespace osu.Game.Tests.Visual.Ranking { var score = new TestScoreInfo(new OsuRuleset().RulesetInfo); score.TotalScore += 10 - i; + score.Hash = $"test{i}"; scores.Add(score); } From 0a643fd5e5a4f8b6e5b92f0a27ffe6995fc26b2c Mon Sep 17 00:00:00 2001 From: Joehu Date: Sat, 29 Aug 2020 09:33:01 -0700 Subject: [PATCH 0758/1782] Fix replay download button always being disabled when initial score's replay is unavailable --- .../Screens/Ranking/ReplayDownloadButton.cs | 40 ++++++++++++------- 1 file changed, 25 insertions(+), 15 deletions(-) diff --git a/osu.Game/Screens/Ranking/ReplayDownloadButton.cs b/osu.Game/Screens/Ranking/ReplayDownloadButton.cs index d0142e57fe..b76842f405 100644 --- a/osu.Game/Screens/Ranking/ReplayDownloadButton.cs +++ b/osu.Game/Screens/Ranking/ReplayDownloadButton.cs @@ -74,23 +74,33 @@ namespace osu.Game.Screens.Ranking { button.State.Value = state.NewValue; - switch (replayAvailability) - { - case ReplayAvailability.Local: - button.TooltipText = @"watch replay"; - break; - - case ReplayAvailability.Online: - button.TooltipText = @"download replay"; - break; - - default: - button.TooltipText = @"replay unavailable"; - break; - } + updateTooltip(); }, true); - button.Enabled.Value = replayAvailability != ReplayAvailability.NotAvailable; + Model.BindValueChanged(_ => + { + button.Enabled.Value = replayAvailability != ReplayAvailability.NotAvailable; + + updateTooltip(); + }, true); + } + + private void updateTooltip() + { + switch (replayAvailability) + { + case ReplayAvailability.Local: + button.TooltipText = @"watch replay"; + break; + + case ReplayAvailability.Online: + button.TooltipText = @"download replay"; + break; + + default: + button.TooltipText = @"replay unavailable"; + break; + } } private enum ReplayAvailability From 5949a281fc54e94b34843ec5d91d8b819b1851b4 Mon Sep 17 00:00:00 2001 From: Lucas A Date: Thu, 27 Aug 2020 19:29:18 +0200 Subject: [PATCH 0759/1782] Make Introduce bindable property OverlayActivationMode in OsuScreen --- osu.Game/OsuGame.cs | 5 ++++- osu.Game/Screens/IOsuScreen.cs | 4 ++-- osu.Game/Screens/OsuScreen.cs | 6 +++++- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 053eb01dcd..6a390942b7 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -972,9 +972,12 @@ namespace osu.Game break; } + if (current is IOsuScreen currentOsuScreen) + OverlayActivationMode.UnbindFrom(currentOsuScreen.OverlayActivationMode); + if (newScreen is IOsuScreen newOsuScreen) { - OverlayActivationMode.Value = newOsuScreen.InitialOverlayActivationMode; + OverlayActivationMode.BindTo(newOsuScreen.OverlayActivationMode); MusicController.AllowRateAdjustments = newOsuScreen.AllowRateAdjustments; diff --git a/osu.Game/Screens/IOsuScreen.cs b/osu.Game/Screens/IOsuScreen.cs index 761f842c22..c9dce310af 100644 --- a/osu.Game/Screens/IOsuScreen.cs +++ b/osu.Game/Screens/IOsuScreen.cs @@ -39,9 +39,9 @@ namespace osu.Game.Screens bool HideOverlaysOnEnter { get; } /// - /// Whether overlays should be able to be opened once this screen is entered or resumed. + /// Whether overlays should be able to be opened when this screen is current. /// - OverlayActivation InitialOverlayActivationMode { get; } + public Bindable OverlayActivationMode { get; } /// /// The amount of parallax to be applied while this screen is displayed. diff --git a/osu.Game/Screens/OsuScreen.cs b/osu.Game/Screens/OsuScreen.cs index 872a1cd39a..c687c34ce9 100644 --- a/osu.Game/Screens/OsuScreen.cs +++ b/osu.Game/Screens/OsuScreen.cs @@ -44,10 +44,12 @@ namespace osu.Game.Screens public virtual bool HideOverlaysOnEnter => false; /// - /// Whether overlays should be able to be opened once this screen is entered or resumed. + /// The initial initial overlay activation mode to use when this screen is entered for the first time. /// public virtual OverlayActivation InitialOverlayActivationMode => OverlayActivation.All; + public Bindable OverlayActivationMode { get; } + public virtual bool CursorVisible => true; protected new OsuGameBase Game => base.Game as OsuGameBase; @@ -138,6 +140,8 @@ namespace osu.Game.Screens { Anchor = Anchor.Centre; Origin = Anchor.Centre; + + OverlayActivationMode = new Bindable(InitialOverlayActivationMode); } [BackgroundDependencyLoader(true)] From ad223bc460ea0d1a6a4d07a86d17cfa3ad0b5b38 Mon Sep 17 00:00:00 2001 From: Lucas A Date: Thu, 27 Aug 2020 20:07:24 +0200 Subject: [PATCH 0760/1782] Make game bindable immutable. --- osu.Game.Tests/Visual/Menus/TestSceneToolbar.cs | 7 ++++++- osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs | 2 +- osu.Game/OsuGame.cs | 2 +- osu.Game/Overlays/Toolbar/Toolbar.cs | 2 +- osu.Game/Screens/Menu/ButtonSystem.cs | 3 --- 5 files changed, 9 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Visual/Menus/TestSceneToolbar.cs b/osu.Game.Tests/Visual/Menus/TestSceneToolbar.cs index f819ae4682..841860accb 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneToolbar.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneToolbar.cs @@ -91,7 +91,12 @@ namespace osu.Game.Tests.Visual.Menus public class TestToolbar : Toolbar { - public new Bindable OverlayActivationMode => base.OverlayActivationMode; + public TestToolbar() + { + base.OverlayActivationMode.BindTo(OverlayActivationMode); + } + + public new Bindable OverlayActivationMode { get; } = new Bindable(OverlayActivation.All); } } } diff --git a/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs b/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs index 93ac69bdbf..751ccc8f15 100644 --- a/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs +++ b/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs @@ -35,7 +35,7 @@ namespace osu.Game.Graphics.Containers [Resolved] private PreviewTrackManager previewTrackManager { get; set; } - protected readonly Bindable OverlayActivationMode = new Bindable(OverlayActivation.All); + protected readonly IBindable OverlayActivationMode = new Bindable(OverlayActivation.All); [BackgroundDependencyLoader(true)] private void load(AudioManager audio) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 6a390942b7..e6d96df927 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -88,7 +88,7 @@ namespace osu.Game private IdleTracker idleTracker; - public readonly Bindable OverlayActivationMode = new Bindable(); + public readonly IBindable OverlayActivationMode = new Bindable(); protected OsuScreenStack ScreenStack; diff --git a/osu.Game/Overlays/Toolbar/Toolbar.cs b/osu.Game/Overlays/Toolbar/Toolbar.cs index 3bf9e85428..393e349bd0 100644 --- a/osu.Game/Overlays/Toolbar/Toolbar.cs +++ b/osu.Game/Overlays/Toolbar/Toolbar.cs @@ -28,7 +28,7 @@ namespace osu.Game.Overlays.Toolbar private const double transition_time = 500; - protected readonly Bindable OverlayActivationMode = new Bindable(OverlayActivation.All); + protected readonly IBindable OverlayActivationMode = new Bindable(OverlayActivation.All); // Toolbar components like RulesetSelector should receive keyboard input events even when the toolbar is hidden. public override bool PropagateNonPositionalInputSubTree => true; diff --git a/osu.Game/Screens/Menu/ButtonSystem.cs b/osu.Game/Screens/Menu/ButtonSystem.cs index 5ba7a8ddc3..4becdd58cd 100644 --- a/osu.Game/Screens/Menu/ButtonSystem.cs +++ b/osu.Game/Screens/Menu/ButtonSystem.cs @@ -270,9 +270,6 @@ namespace osu.Game.Screens.Menu ButtonSystemState lastState = state; state = value; - if (game != null) - game.OverlayActivationMode.Value = state == ButtonSystemState.Exit ? OverlayActivation.Disabled : OverlayActivation.All; - updateLogoState(lastState); Logger.Log($"{nameof(ButtonSystem)}'s state changed from {lastState} to {state}"); From 8de7744b52e98d0e42a9b88ec08b10ac1a5c2f6f Mon Sep 17 00:00:00 2001 From: Lucas A Date: Fri, 28 Aug 2020 09:55:14 +0200 Subject: [PATCH 0761/1782] Add back disabling of overlays on exiting game. --- osu.Game/Screens/Menu/MainMenu.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index 57252d557e..859184834b 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -280,6 +280,7 @@ namespace osu.Game.Screens.Menu } buttons.State = ButtonSystemState.Exit; + OverlayActivationMode.Value = OverlayActivation.Disabled; songTicker.Hide(); From 03b7c8b88969ae337ac52bcddd56c0c5d034f111 Mon Sep 17 00:00:00 2001 From: Lucas A Date: Sat, 29 Aug 2020 19:39:50 +0200 Subject: [PATCH 0762/1782] Remove unneeded access modifier. --- osu.Game/Screens/IOsuScreen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/IOsuScreen.cs b/osu.Game/Screens/IOsuScreen.cs index c9dce310af..ead8e4bc22 100644 --- a/osu.Game/Screens/IOsuScreen.cs +++ b/osu.Game/Screens/IOsuScreen.cs @@ -41,7 +41,7 @@ namespace osu.Game.Screens /// /// Whether overlays should be able to be opened when this screen is current. /// - public Bindable OverlayActivationMode { get; } + Bindable OverlayActivationMode { get; } /// /// The amount of parallax to be applied while this screen is displayed. From e0eece11b1cde8e12856b33c5050335605b8c3cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 29 Aug 2020 20:13:03 +0200 Subject: [PATCH 0763/1782] Fix typo in test name --- osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs index a86fa05129..49fa581108 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs @@ -214,7 +214,7 @@ namespace osu.Game.Tests.Visual.Ranking } [Test] - public void TestDownloadButtonInitallyDisabled() + public void TestDownloadButtonInitiallyDisabled() { TestResultsScreen screen = null; From 13df0783fe091b3171cf37972df3aa5a62c0d267 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 29 Aug 2020 20:23:22 +0200 Subject: [PATCH 0764/1782] Use Single() instead of First() where applicable --- osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs index 49fa581108..03cb5fa3db 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs @@ -220,7 +220,7 @@ namespace osu.Game.Tests.Visual.Ranking AddStep("load results", () => Child = new TestResultsContainer(screen = createResultsScreen())); - AddAssert("download button is disabled", () => !screen.ChildrenOfType().First().Enabled.Value); + AddAssert("download button is disabled", () => !screen.ChildrenOfType().Single().Enabled.Value); AddStep("click contracted panel", () => { @@ -229,7 +229,7 @@ namespace osu.Game.Tests.Visual.Ranking InputManager.Click(MouseButton.Left); }); - AddAssert("download button is enabled", () => screen.ChildrenOfType().First().Enabled.Value); + AddAssert("download button is enabled", () => screen.ChildrenOfType().Single().Enabled.Value); } private class TestResultsContainer : Container From d22768a98cba3c9d312ba2408536905371840668 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 29 Aug 2020 23:20:59 +0200 Subject: [PATCH 0765/1782] Add scale specification to spinner scene for visibility --- osu.Game.Rulesets.Osu.Tests/TestSceneSpinner.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinner.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinner.cs index 47b3926ceb..94d1cb8864 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinner.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinner.cs @@ -9,6 +9,7 @@ using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; +using osuTK; namespace osu.Game.Rulesets.Osu.Tests { @@ -62,7 +63,8 @@ namespace osu.Game.Rulesets.Osu.Tests drawableSpinner = new TestDrawableSpinner(spinner, auto) { Anchor = Anchor.Centre, - Depth = depthIndex++ + Depth = depthIndex++, + Scale = new Vector2(0.75f) }; foreach (var mod in SelectedMods.Value.OfType()) From c9723e541a464c84b8e3e5c27e94af4f0c3fd32d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 29 Aug 2020 23:21:19 +0200 Subject: [PATCH 0766/1782] Add metrics skin resources for old style spinner --- .../metrics-skin/spinner-background@2x.png | Bin 0 -> 14515 bytes .../metrics-skin/spinner-circle@2x.png | Bin 0 -> 6561 bytes .../Resources/metrics-skin/spinner-metre@2x.png | Bin 0 -> 14516 bytes 3 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/metrics-skin/spinner-background@2x.png create mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/metrics-skin/spinner-circle@2x.png create mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/metrics-skin/spinner-metre@2x.png diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/metrics-skin/spinner-background@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/metrics-skin/spinner-background@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..4f50f638c5de06d711fc7d09a3a4ee13618cd59e GIT binary patch literal 14515 zcmeHNO)G>^6n=(b#_-liY7o6PHa3!?BrnZ0Grp3Pln{j-@{v*u+4u)Seu8Xl*pOm; z6rvQ$EbPd_Mxi)&ym$V7L$?+uU=Wd(Lz2dzRbVf`+Hi1HcG}{2hQ>)Qk~# zrVQ0L^+z&f4m1b-G6G-M4V|wtW{riqCxGlc^>bi#-6t1ali}8Y>(O1DpIcjAr!JWY z`x`oEZlCY>4U~EhteVUD)7h#1p{D5F$;S4OL(M7D_0jU90B@C|nG~>iHlwyCK|~OdfT^9pPhdQo`P&4{QZSASqk2k3_0NLiL%>(1 zdI7}$^nOZtpB`5+FPR$i_ml!#WwMmsqIiqWMJ&;OLvBKDLT;kAIlmi$EhYbp;`HoJ zo9P1Wis-|8N#D2}cJ4(K)Z>z#XtK;n^k7z)_Dw!W^Jpj#6xtFXD6}aE3T+93g6YG? s0<$YQg$)J9N5)6n0;Ja(3XG2_6dx^P)VSK|ID1mw4+q-(n~nX8-{y?j8UO$Q literal 0 HcmV?d00001 diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/metrics-skin/spinner-circle@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/metrics-skin/spinner-circle@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..daf28e09cbe6bbb7fc531b4773e4d381054e65cf GIT binary patch literal 6561 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 Date: Sat, 29 Aug 2020 23:29:29 +0200 Subject: [PATCH 0767/1782] Change structure of old style spinner to be closer to stable --- .../Skinning/LegacyOldStyleSpinner.cs | 85 +++++++++++-------- 1 file changed, 51 insertions(+), 34 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacyOldStyleSpinner.cs b/osu.Game.Rulesets.Osu/Skinning/LegacyOldStyleSpinner.cs index 81a0df5ea5..e157842fd1 100644 --- a/osu.Game.Rulesets.Osu/Skinning/LegacyOldStyleSpinner.cs +++ b/osu.Game.Rulesets.Osu/Skinning/LegacyOldStyleSpinner.cs @@ -22,11 +22,11 @@ namespace osu.Game.Rulesets.Osu.Skinning { private DrawableSpinner drawableSpinner; private Sprite disc; + private Sprite metreSprite; private Container metre; - private const float background_y_offset = 20; - private const float sprite_scale = 1 / 1.6f; + private const float final_metre_height = 692 * sprite_scale; [BackgroundDependencyLoader] private void load(ISkinSource source, DrawableHitObject drawableObject) @@ -35,50 +35,58 @@ namespace osu.Game.Rulesets.Osu.Skinning RelativeSizeAxes = Axes.Both; - InternalChildren = new Drawable[] + InternalChild = new Container { - new Sprite + // the old-style spinner relied heavily on absolute screen-space coordinate values. + // wrap everything in a container simulating absolute coords to preserve alignment + // as there are skins that depend on it. + Width = 640, + Height = 480, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Children = new Drawable[] { - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - Texture = source.GetTexture("spinner-background"), - Y = background_y_offset, - Scale = new Vector2(sprite_scale) - }, - disc = new Sprite - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Texture = source.GetTexture("spinner-circle"), - Scale = new Vector2(sprite_scale) - }, - metre = new Container - { - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - Y = background_y_offset, - Masking = true, - Child = new Sprite + new Sprite { - Texture = source.GetTexture("spinner-metre"), - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Texture = source.GetTexture("spinner-background"), + Scale = new Vector2(sprite_scale) }, - Scale = new Vector2(0.625f) + disc = new Sprite + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Texture = source.GetTexture("spinner-circle"), + Scale = new Vector2(sprite_scale) + }, + metre = new Container + { + AutoSizeAxes = Axes.Both, + // this anchor makes no sense, but that's what stable uses. + Anchor = Anchor.TopLeft, + Origin = Anchor.TopLeft, + // adjustment for stable (metre has additional offset) + Margin = new MarginPadding { Top = 20 }, + Masking = true, + Child = metreSprite = new Sprite + { + Texture = source.GetTexture("spinner-metre"), + Anchor = Anchor.TopLeft, + Origin = Anchor.TopLeft, + Scale = new Vector2(0.625f) + } + } } }; } - private Vector2 metreFinalSize; - protected override void LoadComplete() { base.LoadComplete(); this.FadeOut(); drawableSpinner.State.BindValueChanged(updateStateTransforms, true); - - metreFinalSize = metre.Size = metre.Child.Size; } private void updateStateTransforms(ValueChangedEvent state) @@ -93,7 +101,16 @@ namespace osu.Game.Rulesets.Osu.Skinning { base.Update(); disc.Rotation = drawableSpinner.RotationTracker.Rotation; - metre.Height = getMetreHeight(drawableSpinner.Progress); + + // careful: need to call this exactly once for all calculations in a frame + // as the function has a random factor in it + var metreHeight = getMetreHeight(drawableSpinner.Progress); + + // hack to make the metre blink up from below than down from above. + // move down the container to be able to apply masking for the metre, + // and then move the sprite back up the same amount to keep its position absolute. + metre.Y = final_metre_height - metreHeight; + metreSprite.Y = -metre.Y; } private const int total_bars = 10; @@ -108,7 +125,7 @@ namespace osu.Game.Rulesets.Osu.Skinning if (RNG.NextBool(((int)progress % 10) / 10f)) barCount++; - return (float)barCount / total_bars * metreFinalSize.Y; + return (float)barCount / total_bars * final_metre_height; } } } From e428144f736cdbbb818f9c001d31866fb975b1f2 Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Sun, 30 Aug 2020 11:34:50 +0200 Subject: [PATCH 0768/1782] Use IBeatmapSkin --- .../Beatmaps/Formats/LegacyBeatmapEncoderTest.cs | 4 ++-- osu.Game/Beatmaps/BeatmapManager.cs | 10 +--------- osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs | 9 +++------ 3 files changed, 6 insertions(+), 17 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs index dee4626cd0..8d5060e2fe 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs @@ -45,7 +45,7 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.IsTrue(decoded.beatmapSkin.Configuration.Equals(decodedAfterEncode.beatmapSkin.Configuration)); } - private void sort((IBeatmap beatmap, LegacyBeatmapSkin beatmapSkin) tuple) + private void sort((IBeatmap beatmap, IBeatmapSkin beatmapSkin) tuple) { // Sort control points to ensure a sane ordering, as they may be parsed in different orders. This works because each group contains only uniquely-typed control points. foreach (var g in tuple.beatmap.ControlPointInfo.Groups) @@ -77,7 +77,7 @@ namespace osu.Game.Tests.Beatmaps.Formats } } - private Stream encodeToLegacy((IBeatmap beatmap, LegacyBeatmapSkin beatmapSkin) fullBeatmap) + private Stream encodeToLegacy((IBeatmap beatmap, IBeatmapSkin beatmapSkin) fullBeatmap) { var (beatmap, beatmapSkin) = fullBeatmap; var stream = new MemoryStream(); diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 86d35749ac..89a776dd31 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -196,22 +196,14 @@ namespace osu.Game.Beatmaps /// The to save the content against. The file referenced by will be replaced. /// The content to write. /// The beatmap content to write, or null if not to be changed. - public void Save(BeatmapInfo info, IBeatmap beatmapContent, LegacyBeatmapSkin beatmapSkin = null) + public void Save(BeatmapInfo info, IBeatmap beatmapContent, IBeatmapSkin beatmapSkin = null) { var setInfo = QueryBeatmapSet(s => s.Beatmaps.Any(b => b.ID == info.ID)); using (var stream = new MemoryStream()) { using (var sw = new StreamWriter(stream, Encoding.UTF8, 1024, true)) - { - if (beatmapSkin == null) - { - var workingBeatmap = GetWorkingBeatmap(info); - beatmapSkin = (workingBeatmap.Skin is LegacyBeatmapSkin legacy) ? legacy : null; - } - new LegacyBeatmapEncoder(beatmapContent, beatmapSkin).Encode(sw); - } stream.Seek(0, SeekOrigin.Begin); diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs index 497c3c88d0..543d960300 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs @@ -25,14 +25,14 @@ namespace osu.Game.Beatmaps.Formats public const int LATEST_VERSION = 128; private readonly IBeatmap beatmap; - private readonly LegacyBeatmapSkin skin; + private readonly IBeatmapSkin skin; /// /// Creates a new . /// /// The beatmap to encode. /// An optional skin, for encoding the beatmap's combo colours. - public LegacyBeatmapEncoder(IBeatmap beatmap, [CanBeNull] LegacyBeatmapSkin skin) + public LegacyBeatmapEncoder(IBeatmap beatmap, [CanBeNull] IBeatmapSkin skin) { this.beatmap = beatmap; this.skin = skin; @@ -211,10 +211,7 @@ namespace osu.Game.Beatmaps.Formats private void handleComboColours(TextWriter writer) { - if (!(skin is LegacyBeatmapSkin legacySkin)) - return; - - var colours = legacySkin.GetConfig>(GlobalSkinColours.ComboColours)?.Value; + var colours = skin.GetConfig>(GlobalSkinColours.ComboColours)?.Value; if (colours == null || colours.Count == 0) return; From 08321d8dec457381c1e52cab064dd304e309a1ac Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Sun, 30 Aug 2020 11:37:43 +0200 Subject: [PATCH 0769/1782] Safe checking against ComboColours instead of CustomColours --- osu.Game/Skinning/SkinConfiguration.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Skinning/SkinConfiguration.cs b/osu.Game/Skinning/SkinConfiguration.cs index 2ac4dfa0c8..0f6162d6c4 100644 --- a/osu.Game/Skinning/SkinConfiguration.cs +++ b/osu.Game/Skinning/SkinConfiguration.cs @@ -51,6 +51,6 @@ namespace osu.Game.Skinning public readonly SortedDictionary ConfigDictionary = new SortedDictionary(); - public bool Equals(SkinConfiguration other) => other != null && ConfigDictionary.SequenceEqual(other.ConfigDictionary) && ComboColours.SequenceEqual(other.ComboColours) && CustomColours?.SequenceEqual(other.CustomColours) == true; + public bool Equals(SkinConfiguration other) => other != null && ConfigDictionary.SequenceEqual(other.ConfigDictionary) && ComboColours?.SequenceEqual(other.ComboColours) == true && CustomColours.SequenceEqual(other.CustomColours); } } From f5c82d41eb010912aebd758db6c4b3e9677ac01a Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Sun, 30 Aug 2020 16:06:48 +0200 Subject: [PATCH 0770/1782] Remove if-cast --- osu.Game/Screens/Edit/Editor.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 7bd6529897..273ae67ffd 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -65,7 +65,7 @@ namespace osu.Game.Screens.Edit private IBeatmap playableBeatmap; private EditorBeatmap editorBeatmap; private EditorChangeHandler changeHandler; - private LegacyBeatmapSkin beatmapSkin; + private IBeatmapSkin beatmapSkin; private DependencyContainer dependencies; @@ -106,7 +106,7 @@ namespace osu.Game.Screens.Edit AddInternal(editorBeatmap = new EditorBeatmap(playableBeatmap)); dependencies.CacheAs(editorBeatmap); - beatmapSkin = (Beatmap.Value.Skin is LegacyBeatmapSkin legacy) ? legacy : null; + beatmapSkin = Beatmap.Value.Skin; changeHandler = new EditorChangeHandler(editorBeatmap, beatmapSkin); dependencies.CacheAs(changeHandler); From b39ec74bb812f5c582936cb2ca9877700d786968 Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Sun, 30 Aug 2020 16:07:06 +0200 Subject: [PATCH 0771/1782] Scope down to IBeatmapSkin in EditorChangeHandler --- osu.Game/Screens/Edit/EditorChangeHandler.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/EditorChangeHandler.cs b/osu.Game/Screens/Edit/EditorChangeHandler.cs index 1d10eaf5cb..60d869ec82 100644 --- a/osu.Game/Screens/Edit/EditorChangeHandler.cs +++ b/osu.Game/Screens/Edit/EditorChangeHandler.cs @@ -26,7 +26,7 @@ namespace osu.Game.Screens.Edit private int currentState = -1; private readonly EditorBeatmap editorBeatmap; - private readonly LegacyBeatmapSkin beatmapSkin; + private readonly IBeatmapSkin beatmapSkin; private int bulkChangesStarted; private bool isRestoring; @@ -37,7 +37,7 @@ namespace osu.Game.Screens.Edit /// /// The to track the s of. /// The skin to track the inline skin configuration of. - public EditorChangeHandler(EditorBeatmap editorBeatmap, LegacyBeatmapSkin beatmapSkin) + public EditorChangeHandler(EditorBeatmap editorBeatmap, IBeatmapSkin beatmapSkin) { this.editorBeatmap = editorBeatmap; From 7e57af3ca437b5ae7a053051eb3b64cc1c0bc0e4 Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Sun, 30 Aug 2020 16:07:46 +0200 Subject: [PATCH 0772/1782] Return true if both ComboColours are null --- osu.Game/Skinning/SkinConfiguration.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Skinning/SkinConfiguration.cs b/osu.Game/Skinning/SkinConfiguration.cs index 0f6162d6c4..18d970dd64 100644 --- a/osu.Game/Skinning/SkinConfiguration.cs +++ b/osu.Game/Skinning/SkinConfiguration.cs @@ -51,6 +51,6 @@ namespace osu.Game.Skinning public readonly SortedDictionary ConfigDictionary = new SortedDictionary(); - public bool Equals(SkinConfiguration other) => other != null && ConfigDictionary.SequenceEqual(other.ConfigDictionary) && ComboColours?.SequenceEqual(other.ComboColours) == true && CustomColours.SequenceEqual(other.CustomColours); + public bool Equals(SkinConfiguration other) => other != null && ConfigDictionary.SequenceEqual(other.ConfigDictionary) && ((ComboColours == null && other.ComboColours == null) || ComboColours.SequenceEqual(other.ComboColours)) && CustomColours.SequenceEqual(other.CustomColours); } } From 1fdf8e62004f59ec098a793c6d1a8591cf053b37 Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Sun, 30 Aug 2020 16:07:58 +0200 Subject: [PATCH 0773/1782] Fix xmldoc in LegacyBeatmapEncoder --- osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs index 543d960300..8d7e509070 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs @@ -31,7 +31,7 @@ namespace osu.Game.Beatmaps.Formats /// Creates a new . /// /// The beatmap to encode. - /// An optional skin, for encoding the beatmap's combo colours. + /// The beatmap's skin, used for encoding combo colours. public LegacyBeatmapEncoder(IBeatmap beatmap, [CanBeNull] IBeatmapSkin skin) { this.beatmap = beatmap; From 919d7b77855435bc90df339ac3c939550145ca96 Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Sun, 30 Aug 2020 16:08:13 +0200 Subject: [PATCH 0774/1782] Remove redundant call to TestResources --- osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs index 8d5060e2fe..b25f2f1fd3 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs @@ -35,7 +35,7 @@ namespace osu.Game.Tests.Beatmaps.Formats [TestCaseSource(nameof(allBeatmaps))] public void TestEncodeDecodeStability(string name) { - var decoded = decodeFromLegacy(TestResources.GetStore().GetStream(name), name); + var decoded = decodeFromLegacy(beatmaps_resource_store.GetStream(name), name); var decodedAfterEncode = decodeFromLegacy(encodeToLegacy(decoded), name); sort(decoded); From 337037ab3b0f33474c1d9cc829b25b169a49337d Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Sun, 30 Aug 2020 16:08:52 +0200 Subject: [PATCH 0775/1782] Make test load actual beatmap's skin configuration --- .../Formats/LegacyBeatmapEncoderTest.cs | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs index b25f2f1fd3..bea21087c5 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs @@ -42,7 +42,7 @@ namespace osu.Game.Tests.Beatmaps.Formats sort(decodedAfterEncode); Assert.That(decodedAfterEncode.beatmap.Serialize(), Is.EqualTo(decoded.beatmap.Serialize())); - Assert.IsTrue(decoded.beatmapSkin.Configuration.Equals(decodedAfterEncode.beatmapSkin.Configuration)); + Assert.IsTrue(decodedAfterEncode.beatmapSkin.Configuration.Equals(decoded.beatmapSkin.Configuration)); } private void sort((IBeatmap beatmap, IBeatmapSkin beatmapSkin) tuple) @@ -55,11 +55,13 @@ namespace osu.Game.Tests.Beatmaps.Formats } } - private (IBeatmap beatmap, LegacyBeatmapSkin beatmapSkin) decodeFromLegacy(Stream stream, string name) + private (IBeatmap beatmap, TestLegacySkin beatmapSkin) decodeFromLegacy(Stream stream, string name) { using (var reader = new LineBufferedReader(stream)) { var beatmap = new LegacyBeatmapDecoder { ApplyOffsets = false }.Decode(reader); + + beatmap.BeatmapInfo.Path = name; beatmap.BeatmapInfo.BeatmapSet = new BeatmapSetInfo { Files = new List @@ -69,14 +71,22 @@ namespace osu.Game.Tests.Beatmaps.Formats Filename = name, FileInfo = new osu.Game.IO.FileInfo { Hash = name } } - } + }, }; - var beatmapSkin = new LegacyBeatmapSkin(beatmap.BeatmapInfo, beatmaps_resource_store, null); + var beatmapSkin = new TestLegacySkin(beatmap, beatmaps_resource_store, name); return (convert(beatmap), beatmapSkin); } } + private class TestLegacySkin : LegacySkin, IBeatmapSkin + { + public TestLegacySkin(Beatmap beatmap, IResourceStore storage, string fileName) + : base(new SkinInfo() { Name = "Test Skin", Creator = "Craftplacer" }, storage, null, fileName) + { + } + } + private Stream encodeToLegacy((IBeatmap beatmap, IBeatmapSkin beatmapSkin) fullBeatmap) { var (beatmap, beatmapSkin) = fullBeatmap; From 7e668fc31a619ca1d89dc6532898433bf40efe07 Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Sun, 30 Aug 2020 16:11:49 +0200 Subject: [PATCH 0776/1782] Update osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs Co-authored-by: Salman Ahmed --- osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs index 8d7e509070..cae6a43cd4 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs @@ -226,7 +226,8 @@ namespace osu.Game.Beatmaps.Formats writer.Write(FormattableString.Invariant($"{(byte)(comboColour.R * byte.MaxValue)},")); writer.Write(FormattableString.Invariant($"{(byte)(comboColour.G * byte.MaxValue)},")); writer.Write(FormattableString.Invariant($"{(byte)(comboColour.B * byte.MaxValue)},")); - writer.WriteLine(FormattableString.Invariant($"{(byte)(comboColour.A * byte.MaxValue)}")); + writer.Write(FormattableString.Invariant($"{(byte)(comboColour.A * byte.MaxValue)}")); + writer.WriteLine(); } } From 43d144b7c00756a0cb0d94a7c88750614fcd97f6 Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Sun, 30 Aug 2020 16:23:00 +0200 Subject: [PATCH 0777/1782] Remove empty argument list --- osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs index bea21087c5..dc91af72e8 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs @@ -82,7 +82,7 @@ namespace osu.Game.Tests.Beatmaps.Formats private class TestLegacySkin : LegacySkin, IBeatmapSkin { public TestLegacySkin(Beatmap beatmap, IResourceStore storage, string fileName) - : base(new SkinInfo() { Name = "Test Skin", Creator = "Craftplacer" }, storage, null, fileName) + : base(new SkinInfo { Name = "Test Skin", Creator = "Craftplacer" }, storage, null, fileName) { } } From db413686bbe7db6dadf72d71d54fe1def027427b Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Sun, 30 Aug 2020 21:12:45 +0200 Subject: [PATCH 0778/1782] Add BeatmapSkin to EditorBeatmap --- osu.Game.Tests/Editing/EditorChangeHandlerTest.cs | 6 +++--- osu.Game/Screens/Edit/Editor.cs | 10 +++------- osu.Game/Screens/Edit/EditorBeatmap.cs | 6 +++++- osu.Game/Screens/Edit/EditorChangeHandler.cs | 9 ++------- 4 files changed, 13 insertions(+), 18 deletions(-) diff --git a/osu.Game.Tests/Editing/EditorChangeHandlerTest.cs b/osu.Game.Tests/Editing/EditorChangeHandlerTest.cs index 6d708ce838..feda1ae0e9 100644 --- a/osu.Game.Tests/Editing/EditorChangeHandlerTest.cs +++ b/osu.Game.Tests/Editing/EditorChangeHandlerTest.cs @@ -13,7 +13,7 @@ namespace osu.Game.Tests.Editing [Test] public void TestSaveRestoreState() { - var handler = new EditorChangeHandler(new EditorBeatmap(new Beatmap()), null); + var handler = new EditorChangeHandler(new EditorBeatmap(new Beatmap())); Assert.That(handler.CanUndo.Value, Is.False); Assert.That(handler.CanRedo.Value, Is.False); @@ -32,7 +32,7 @@ namespace osu.Game.Tests.Editing [Test] public void TestMaxStatesSaved() { - var handler = new EditorChangeHandler(new EditorBeatmap(new Beatmap()), null); + var handler = new EditorChangeHandler(new EditorBeatmap(new Beatmap())); Assert.That(handler.CanUndo.Value, Is.False); @@ -53,7 +53,7 @@ namespace osu.Game.Tests.Editing [Test] public void TestMaxStatesExceeded() { - var handler = new EditorChangeHandler(new EditorBeatmap(new Beatmap()), null); + var handler = new EditorChangeHandler(new EditorBeatmap(new Beatmap())); Assert.That(handler.CanUndo.Value, Is.False); diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 273ae67ffd..b1f11d79f9 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -33,7 +33,6 @@ 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.Skinning; using osu.Game.Users; namespace osu.Game.Screens.Edit @@ -65,7 +64,6 @@ namespace osu.Game.Screens.Edit private IBeatmap playableBeatmap; private EditorBeatmap editorBeatmap; private EditorChangeHandler changeHandler; - private IBeatmapSkin beatmapSkin; private DependencyContainer dependencies; @@ -103,11 +101,9 @@ namespace osu.Game.Screens.Edit return; } - AddInternal(editorBeatmap = new EditorBeatmap(playableBeatmap)); + AddInternal(editorBeatmap = new EditorBeatmap(playableBeatmap, Beatmap.Value.Skin)); dependencies.CacheAs(editorBeatmap); - - beatmapSkin = Beatmap.Value.Skin; - changeHandler = new EditorChangeHandler(editorBeatmap, beatmapSkin); + changeHandler = new EditorChangeHandler(editorBeatmap); dependencies.CacheAs(changeHandler); EditorMenuBar menuBar; @@ -402,7 +398,7 @@ namespace osu.Game.Screens.Edit clock.SeekForward(!clock.IsRunning, amount); } - private void saveBeatmap() => beatmapManager.Save(playableBeatmap.BeatmapInfo, editorBeatmap, beatmapSkin); + private void saveBeatmap() => beatmapManager.Save(playableBeatmap.BeatmapInfo, editorBeatmap, editorBeatmap.BeatmapSkin); private void exportBeatmap() { diff --git a/osu.Game/Screens/Edit/EditorBeatmap.cs b/osu.Game/Screens/Edit/EditorBeatmap.cs index 23c8c9f605..a314d50e60 100644 --- a/osu.Game/Screens/Edit/EditorBeatmap.cs +++ b/osu.Game/Screens/Edit/EditorBeatmap.cs @@ -15,6 +15,7 @@ using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.Timing; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; +using osu.Game.Skinning; namespace osu.Game.Screens.Edit { @@ -47,6 +48,8 @@ namespace osu.Game.Screens.Edit public readonly IBeatmap PlayableBeatmap; + public readonly IBeatmapSkin BeatmapSkin; + [Resolved] private BindableBeatDivisor beatDivisor { get; set; } @@ -54,9 +57,10 @@ namespace osu.Game.Screens.Edit private readonly Dictionary> startTimeBindables = new Dictionary>(); - public EditorBeatmap(IBeatmap playableBeatmap) + public EditorBeatmap(IBeatmap playableBeatmap, IBeatmapSkin beatmapSkin = null) { PlayableBeatmap = playableBeatmap; + BeatmapSkin = beatmapSkin; beatmapProcessor = playableBeatmap.BeatmapInfo.Ruleset?.CreateInstance().CreateBeatmapProcessor(PlayableBeatmap); diff --git a/osu.Game/Screens/Edit/EditorChangeHandler.cs b/osu.Game/Screens/Edit/EditorChangeHandler.cs index 60d869ec82..927c823c64 100644 --- a/osu.Game/Screens/Edit/EditorChangeHandler.cs +++ b/osu.Game/Screens/Edit/EditorChangeHandler.cs @@ -8,7 +8,6 @@ using System.Text; using osu.Framework.Bindables; using osu.Game.Beatmaps.Formats; using osu.Game.Rulesets.Objects; -using osu.Game.Skinning; namespace osu.Game.Screens.Edit { @@ -26,7 +25,6 @@ namespace osu.Game.Screens.Edit private int currentState = -1; private readonly EditorBeatmap editorBeatmap; - private readonly IBeatmapSkin beatmapSkin; private int bulkChangesStarted; private bool isRestoring; @@ -36,8 +34,7 @@ namespace osu.Game.Screens.Edit /// Creates a new . /// /// The to track the s of. - /// The skin to track the inline skin configuration of. - public EditorChangeHandler(EditorBeatmap editorBeatmap, IBeatmapSkin beatmapSkin) + public EditorChangeHandler(EditorBeatmap editorBeatmap) { this.editorBeatmap = editorBeatmap; @@ -47,8 +44,6 @@ namespace osu.Game.Screens.Edit patcher = new LegacyEditorBeatmapPatcher(editorBeatmap); - this.beatmapSkin = beatmapSkin; - // Initial state. SaveState(); } @@ -90,7 +85,7 @@ namespace osu.Game.Screens.Edit using (var stream = new MemoryStream()) { using (var sw = new StreamWriter(stream, Encoding.UTF8, 1024, true)) - new LegacyBeatmapEncoder(editorBeatmap, beatmapSkin).Encode(sw); + new LegacyBeatmapEncoder(editorBeatmap, editorBeatmap.BeatmapSkin).Encode(sw); savedStates.Add(stream.ToArray()); } From 07f6a6817961c0426baacb14507df4d1e4937451 Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Sun, 30 Aug 2020 21:13:06 +0200 Subject: [PATCH 0779/1782] Update LegacyBeatmapEncoderTest.cs --- .../Formats/LegacyBeatmapEncoderTest.cs | 65 +++++++++++++++---- 1 file changed, 51 insertions(+), 14 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs index dc91af72e8..a8a3f266fc 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs @@ -7,8 +7,10 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; +using System.Threading.Tasks; using NUnit.Framework; using osu.Framework.Audio.Track; +using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics.Textures; using osu.Framework.IO.Stores; using osu.Game.Beatmaps; @@ -61,24 +63,59 @@ namespace osu.Game.Tests.Beatmaps.Formats { var beatmap = new LegacyBeatmapDecoder { ApplyOffsets = false }.Decode(reader); - beatmap.BeatmapInfo.Path = name; - beatmap.BeatmapInfo.BeatmapSet = new BeatmapSetInfo + using (var rs = new MemoryBeatmapResourceStore(stream, name)) { - Files = new List - { - new BeatmapSetFileInfo - { - Filename = name, - FileInfo = new osu.Game.IO.FileInfo { Hash = name } - } - }, - }; - - var beatmapSkin = new TestLegacySkin(beatmap, beatmaps_resource_store, name); - return (convert(beatmap), beatmapSkin); + var beatmapSkin = new TestLegacySkin(beatmap, rs, name); + return (convert(beatmap), beatmapSkin); + } } } + private class MemoryBeatmapResourceStore : IResourceStore + { + private readonly Stream beatmapData; + private readonly string beatmapName; + + public MemoryBeatmapResourceStore(Stream beatmapData, string beatmapName) + { + this.beatmapData = beatmapData; + this.beatmapName = beatmapName; + } + + public void Dispose() => beatmapData.Dispose(); + + public byte[] Get(string name) + { + if (name != beatmapName) + return null; + + byte[] buffer = new byte[beatmapData.Length]; + beatmapData.Read(buffer, 0, buffer.Length); + return buffer; + } + + public async Task GetAsync(string name) + { + if (name != beatmapName) + return null; + + byte[] buffer = new byte[beatmapData.Length]; + await beatmapData.ReadAsync(buffer.AsMemory()); + return buffer; + } + + public Stream GetStream(string name) + { + if (name != beatmapName) + return null; + + beatmapData.Seek(0, SeekOrigin.Begin); + return beatmapData; + } + + public IEnumerable GetAvailableResources() => beatmapName.Yield(); + } + private class TestLegacySkin : LegacySkin, IBeatmapSkin { public TestLegacySkin(Beatmap beatmap, IResourceStore storage, string fileName) From 8151aa6ed8b761f4b51ceb7345c0c9bb855d7ad1 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 31 Aug 2020 13:31:55 +0900 Subject: [PATCH 0780/1782] Remove unused method --- osu.Game.Rulesets.Mania.Tests/TestSceneOutOfOrderHits.cs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneOutOfOrderHits.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneOutOfOrderHits.cs index ed187e65bf..ab840e1c46 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneOutOfOrderHits.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneOutOfOrderHits.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 NUnit.Framework; @@ -60,12 +59,6 @@ namespace osu.Game.Rulesets.Mania.Tests () => judgementResults.Single(r => r.HitObject == hitObject).Type == result); } - private void addJudgementAssert(string name, Func hitObject, HitResult result) - { - AddAssert($"{name} judgement is {result}", - () => judgementResults.Single(r => r.HitObject == hitObject()).Type == result); - } - private void addJudgementOffsetAssert(ManiaHitObject hitObject, double offset) { AddAssert($"({hitObject.GetType().ReadableName()} @ {hitObject.StartTime}) judged at {offset}", From acbeb5406f320d5749e2301f3b471b14eb85b62c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 31 Aug 2020 13:33:41 +0900 Subject: [PATCH 0781/1782] Add/improve xmldoc --- .../Objects/Drawables/DrawableManiaHitObject.cs | 4 ++++ .../Objects/Drawables/DrawableOsuHitObject.cs | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs index 0594d1e143..e16413bce7 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs @@ -36,6 +36,10 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables } } + /// + /// Whether this can be hit, given a time value. + /// If non-null, judgements will be ignored (resulting in a shake) whilst the function returns false. + /// public Func CheckHittable; protected DrawableManiaHitObject(ManiaHitObject hitObject) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs index 8308c0c576..2946331bc6 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs @@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables protected override float SamplePlaybackPosition => HitObject.X / OsuPlayfield.BASE_SIZE.X; /// - /// Whether this can be hit. + /// Whether this can be hit, given a time value. /// If non-null, judgements will be ignored (resulting in a shake) whilst the function returns false. /// public Func CheckHittable; From abdb99192397e21964706822c9d9fa8948a54c8f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 31 Aug 2020 14:15:47 +0900 Subject: [PATCH 0782/1782] Hide misses from timing distribution graph --- .../TestSceneHitEventTimingDistributionGraph.cs | 12 ++++++++++++ .../Statistics/HitEventTimingDistributionGraph.cs | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneHitEventTimingDistributionGraph.cs b/osu.Game.Tests/Visual/Ranking/TestSceneHitEventTimingDistributionGraph.cs index 7ca1fc842f..144f8da2fa 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneHitEventTimingDistributionGraph.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneHitEventTimingDistributionGraph.cs @@ -35,6 +35,18 @@ namespace osu.Game.Tests.Visual.Ranking createTest(new List()); } + [Test] + public void TestMissesDontShow() + { + createTest(Enumerable.Range(0, 100).Select(i => + { + if (i % 2 == 0) + return new HitEvent(0, HitResult.Perfect, new HitCircle(), new HitCircle(), null); + + return new HitEvent(30, HitResult.Miss, new HitCircle(), new HitCircle(), null); + }).ToList()); + } + private void createTest(List events) => AddStep("create test", () => { Children = new Drawable[] diff --git a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs index 527da429ed..45fdc3ff33 100644 --- a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs +++ b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs @@ -48,7 +48,7 @@ namespace osu.Game.Screens.Ranking.Statistics /// The s to display the timing distribution of. public HitEventTimingDistributionGraph(IReadOnlyList hitEvents) { - this.hitEvents = hitEvents.Where(e => !(e.HitObject.HitWindows is HitWindows.EmptyHitWindows)).ToList(); + this.hitEvents = hitEvents.Where(e => !(e.HitObject.HitWindows is HitWindows.EmptyHitWindows) && e.Result != HitResult.Miss).ToList(); } [BackgroundDependencyLoader] From c3bfce6ccff2bd908a80e47614b1329d1f585e00 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 31 Aug 2020 15:03:41 +0900 Subject: [PATCH 0783/1782] Add star rating to beatmap wedge --- osu.Game/Screens/Select/BeatmapInfoWedge.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/osu.Game/Screens/Select/BeatmapInfoWedge.cs b/osu.Game/Screens/Select/BeatmapInfoWedge.cs index 27ce9e82dd..cb3a347af4 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedge.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedge.cs @@ -27,6 +27,7 @@ using osu.Framework.Logging; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.UI; +using osu.Game.Screens.Ranking.Expanded; namespace osu.Game.Screens.Select { @@ -224,8 +225,15 @@ namespace osu.Game.Screens.Select AutoSizeAxes = Axes.Both, Children = new Drawable[] { + new StarRatingDisplay(beatmapInfo) + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + }, StatusPill = new BeatmapSetOnlineStatusPill { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, TextSize = 11, TextPadding = new MarginPadding { Horizontal = 8, Vertical = 2 }, Status = beatmapInfo.Status, From 4736845318acac3c4e4da894500389a933bc8c22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 31 Aug 2020 10:56:06 +0200 Subject: [PATCH 0784/1782] Add spacing between star rating and beatmap status --- osu.Game/Screens/Select/BeatmapInfoWedge.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Screens/Select/BeatmapInfoWedge.cs b/osu.Game/Screens/Select/BeatmapInfoWedge.cs index cb3a347af4..518ad33529 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedge.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedge.cs @@ -229,6 +229,7 @@ namespace osu.Game.Screens.Select { Anchor = Anchor.CentreRight, Origin = Anchor.CentreRight, + Margin = new MarginPadding { Bottom = 5 } }, StatusPill = new BeatmapSetOnlineStatusPill { From bee01bdd38cf13bfeddac343c68ad315daef570f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 31 Aug 2020 18:01:16 +0900 Subject: [PATCH 0785/1782] Fix first scroll wheel in editor incorrectly advancing twice --- 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 d92f3922c3..e178459d5c 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -284,7 +284,7 @@ namespace osu.Game.Screens.Edit // this is a special case to handle the "pivot" scenario. // if we are precise scrolling in one direction then change our mind and scroll backwards, // the existing accumulation should be applied in the inverse direction to maintain responsiveness. - if (Math.Sign(scrollAccumulation) != scrollDirection) + if (scrollAccumulation != 0 && Math.Sign(scrollAccumulation) != scrollDirection) scrollAccumulation = scrollDirection * (precision - Math.Abs(scrollAccumulation)); scrollAccumulation += scrollComponent * (e.IsPrecise ? 0.1 : 1); From 7d273d631be6491d3ad6ae769770469ba0cb9214 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 31 Aug 2020 11:05:42 +0200 Subject: [PATCH 0786/1782] Do not show star difficulty on wedge if zero --- osu.Game/Screens/Select/BeatmapInfoWedge.cs | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapInfoWedge.cs b/osu.Game/Screens/Select/BeatmapInfoWedge.cs index 518ad33529..2b2c40411d 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedge.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedge.cs @@ -223,14 +223,13 @@ namespace osu.Game.Screens.Select Direction = FillDirection.Vertical, Padding = new MarginPadding { Top = 14, Right = shear_width / 2 }, AutoSizeAxes = Axes.Both, - Children = new Drawable[] + Children = new[] { - new StarRatingDisplay(beatmapInfo) + createStarRatingDisplay(beatmapInfo).With(display => { - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, - Margin = new MarginPadding { Bottom = 5 } - }, + display.Anchor = Anchor.CentreRight; + display.Origin = Anchor.CentreRight; + }), StatusPill = new BeatmapSetOnlineStatusPill { Anchor = Anchor.CentreRight, @@ -291,6 +290,13 @@ namespace osu.Game.Screens.Select StatusPill.Hide(); } + private static Drawable createStarRatingDisplay(BeatmapInfo beatmapInfo) => beatmapInfo.StarDifficulty > 0 + ? new StarRatingDisplay(beatmapInfo) + { + Margin = new MarginPadding { Bottom = 5 } + } + : Empty(); + private void setMetadata(string source) { ArtistLabel.Text = artistBinding.Value; From 8b7446c43f1a53bbf83804486289ee6e42c5ec8e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 31 Aug 2020 18:13:51 +0900 Subject: [PATCH 0787/1782] Fix RollingCounter not updating initial value if changed before loaded --- osu.Game/Graphics/UserInterface/RollingCounter.cs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/RollingCounter.cs b/osu.Game/Graphics/UserInterface/RollingCounter.cs index 6763198213..ceb388600e 100644 --- a/osu.Game/Graphics/UserInterface/RollingCounter.cs +++ b/osu.Game/Graphics/UserInterface/RollingCounter.cs @@ -65,12 +65,6 @@ namespace osu.Game.Graphics.UserInterface protected RollingCounter() { AutoSizeAxes = Axes.Both; - - Current.ValueChanged += val => - { - if (IsLoaded) - TransformCount(DisplayedCount, val.NewValue); - }; } [BackgroundDependencyLoader] @@ -81,6 +75,13 @@ namespace osu.Game.Graphics.UserInterface Child = displayedCountSpriteText; } + protected override void LoadComplete() + { + base.LoadComplete(); + + Current.BindValueChanged(val => TransformCount(DisplayedCount, val.NewValue), true); + } + /// /// Sets count value, bypassing rollover animation. /// From a171d0e292be37a8c85d1f5e40b933f9d07b7619 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 31 Aug 2020 18:14:22 +0900 Subject: [PATCH 0788/1782] Remove unused methods and classes --- .../UserInterface/PercentageCounter.cs | 5 --- .../Graphics/UserInterface/RollingCounter.cs | 2 -- .../Graphics/UserInterface/ScoreCounter.cs | 5 --- .../UserInterface/SimpleComboCounter.cs | 5 --- .../Screens/Play/HUD/ComboResultCounter.cs | 32 ------------------- .../Expanded/Statistics/AccuracyStatistic.cs | 3 -- .../Expanded/Statistics/CounterStatistic.cs | 3 -- .../Ranking/Expanded/TotalScoreCounter.cs | 3 -- 8 files changed, 58 deletions(-) delete mode 100644 osu.Game/Screens/Play/HUD/ComboResultCounter.cs diff --git a/osu.Game/Graphics/UserInterface/PercentageCounter.cs b/osu.Game/Graphics/UserInterface/PercentageCounter.cs index 3ea9c1053c..1ccf7798e5 100644 --- a/osu.Game/Graphics/UserInterface/PercentageCounter.cs +++ b/osu.Game/Graphics/UserInterface/PercentageCounter.cs @@ -40,10 +40,5 @@ namespace osu.Game.Graphics.UserInterface protected override OsuSpriteText CreateSpriteText() => base.CreateSpriteText().With(s => s.Font = s.Font.With(size: 20f, fixedWidth: true)); - - public override void Increment(double amount) - { - Current.Value += amount; - } } } diff --git a/osu.Game/Graphics/UserInterface/RollingCounter.cs b/osu.Game/Graphics/UserInterface/RollingCounter.cs index ceb388600e..ece1b8e22c 100644 --- a/osu.Game/Graphics/UserInterface/RollingCounter.cs +++ b/osu.Game/Graphics/UserInterface/RollingCounter.cs @@ -57,8 +57,6 @@ namespace osu.Game.Graphics.UserInterface } } - public abstract void Increment(T amount); - /// /// Skeleton of a numeric counter which value rolls over time. /// diff --git a/osu.Game/Graphics/UserInterface/ScoreCounter.cs b/osu.Game/Graphics/UserInterface/ScoreCounter.cs index faabe69f87..73bbe5f03e 100644 --- a/osu.Game/Graphics/UserInterface/ScoreCounter.cs +++ b/osu.Game/Graphics/UserInterface/ScoreCounter.cs @@ -51,10 +51,5 @@ namespace osu.Game.Graphics.UserInterface protected override OsuSpriteText CreateSpriteText() => base.CreateSpriteText().With(s => s.Font = s.Font.With(fixedWidth: true)); - - public override void Increment(double amount) - { - Current.Value += amount; - } } } diff --git a/osu.Game/Graphics/UserInterface/SimpleComboCounter.cs b/osu.Game/Graphics/UserInterface/SimpleComboCounter.cs index aac0166774..c9790aed46 100644 --- a/osu.Game/Graphics/UserInterface/SimpleComboCounter.cs +++ b/osu.Game/Graphics/UserInterface/SimpleComboCounter.cs @@ -33,11 +33,6 @@ namespace osu.Game.Graphics.UserInterface return Math.Abs(currentValue - newValue) * RollingDuration * 100.0f; } - public override void Increment(int amount) - { - Current.Value += amount; - } - protected override OsuSpriteText CreateSpriteText() => base.CreateSpriteText().With(s => s.Font = s.Font.With(size: 20f)); } diff --git a/osu.Game/Screens/Play/HUD/ComboResultCounter.cs b/osu.Game/Screens/Play/HUD/ComboResultCounter.cs deleted file mode 100644 index 7ae8bc0ddf..0000000000 --- a/osu.Game/Screens/Play/HUD/ComboResultCounter.cs +++ /dev/null @@ -1,32 +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.Graphics.UserInterface; - -namespace osu.Game.Screens.Play.HUD -{ - /// - /// Used to display combo with a roll-up animation in results screen. - /// - public class ComboResultCounter : RollingCounter - { - protected override double RollingDuration => 500; - protected override Easing RollingEasing => Easing.Out; - - protected override double GetProportionalDuration(long currentValue, long newValue) - { - return currentValue > newValue ? currentValue - newValue : newValue - currentValue; - } - - protected override string FormatCount(long count) - { - return $@"{count}x"; - } - - public override void Increment(long amount) - { - Current.Value += amount; - } - } -} diff --git a/osu.Game/Screens/Ranking/Expanded/Statistics/AccuracyStatistic.cs b/osu.Game/Screens/Ranking/Expanded/Statistics/AccuracyStatistic.cs index 6933456e7e..288a107874 100644 --- a/osu.Game/Screens/Ranking/Expanded/Statistics/AccuracyStatistic.cs +++ b/osu.Game/Screens/Ranking/Expanded/Statistics/AccuracyStatistic.cs @@ -46,9 +46,6 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics protected override string FormatCount(double count) => count.FormatAccuracy(); - public override void Increment(double amount) - => Current.Value += amount; - protected override OsuSpriteText CreateSpriteText() => base.CreateSpriteText().With(s => { s.Font = OsuFont.Torus.With(size: 20, fixedWidth: true); diff --git a/osu.Game/Screens/Ranking/Expanded/Statistics/CounterStatistic.cs b/osu.Game/Screens/Ranking/Expanded/Statistics/CounterStatistic.cs index 043a560d12..e820831809 100644 --- a/osu.Game/Screens/Ranking/Expanded/Statistics/CounterStatistic.cs +++ b/osu.Game/Screens/Ranking/Expanded/Statistics/CounterStatistic.cs @@ -49,9 +49,6 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics s.Font = OsuFont.Torus.With(size: 20, fixedWidth: true); s.Spacing = new Vector2(-2, 0); }); - - public override void Increment(int amount) - => Current.Value += amount; } } } diff --git a/osu.Game/Screens/Ranking/Expanded/TotalScoreCounter.cs b/osu.Game/Screens/Ranking/Expanded/TotalScoreCounter.cs index 7f6fd1eabe..65082d3fae 100644 --- a/osu.Game/Screens/Ranking/Expanded/TotalScoreCounter.cs +++ b/osu.Game/Screens/Ranking/Expanded/TotalScoreCounter.cs @@ -36,8 +36,5 @@ namespace osu.Game.Screens.Ranking.Expanded s.Font = OsuFont.Torus.With(size: 60, weight: FontWeight.Light, fixedWidth: true); s.Spacing = new Vector2(-5, 0); }); - - public override void Increment(long amount) - => Current.Value += amount; } } From dd093f44d8826af623ed5232e6a38d8f39b0ce20 Mon Sep 17 00:00:00 2001 From: Lucas A Date: Mon, 31 Aug 2020 11:16:13 +0200 Subject: [PATCH 0789/1782] Cast base immutable bindable to mutable for testing purposes and make InitialOverlayActivationMode property protected --- osu.Game.Tests/Visual/Menus/TestSceneToolbar.cs | 7 +------ osu.Game/Screens/OsuScreen.cs | 2 +- osu.Game/Screens/Play/Player.cs | 2 +- osu.Game/Screens/StartupScreen.cs | 2 +- 4 files changed, 4 insertions(+), 9 deletions(-) diff --git a/osu.Game.Tests/Visual/Menus/TestSceneToolbar.cs b/osu.Game.Tests/Visual/Menus/TestSceneToolbar.cs index 841860accb..2a4486812c 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneToolbar.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneToolbar.cs @@ -91,12 +91,7 @@ namespace osu.Game.Tests.Visual.Menus public class TestToolbar : Toolbar { - public TestToolbar() - { - base.OverlayActivationMode.BindTo(OverlayActivationMode); - } - - public new Bindable OverlayActivationMode { get; } = new Bindable(OverlayActivation.All); + public new Bindable OverlayActivationMode => base.OverlayActivationMode as Bindable; } } } diff --git a/osu.Game/Screens/OsuScreen.cs b/osu.Game/Screens/OsuScreen.cs index c687c34ce9..c10deaf1e5 100644 --- a/osu.Game/Screens/OsuScreen.cs +++ b/osu.Game/Screens/OsuScreen.cs @@ -46,7 +46,7 @@ namespace osu.Game.Screens /// /// The initial initial overlay activation mode to use when this screen is entered for the first time. /// - public virtual OverlayActivation InitialOverlayActivationMode => OverlayActivation.All; + protected virtual OverlayActivation InitialOverlayActivationMode => OverlayActivation.All; public Bindable OverlayActivationMode { get; } diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 541275cf55..0a5158c6dc 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -50,7 +50,7 @@ namespace osu.Game.Screens.Play public override bool HideOverlaysOnEnter => true; - public override OverlayActivation InitialOverlayActivationMode => OverlayActivation.UserTriggered; + protected override OverlayActivation InitialOverlayActivationMode => OverlayActivation.UserTriggered; /// /// Whether gameplay should pause when the game window focus is lost. diff --git a/osu.Game/Screens/StartupScreen.cs b/osu.Game/Screens/StartupScreen.cs index c3e36c8e9d..e5e134fd39 100644 --- a/osu.Game/Screens/StartupScreen.cs +++ b/osu.Game/Screens/StartupScreen.cs @@ -18,6 +18,6 @@ namespace osu.Game.Screens public override bool AllowRateAdjustments => false; - public override OverlayActivation InitialOverlayActivationMode => OverlayActivation.Disabled; + protected override OverlayActivation InitialOverlayActivationMode => OverlayActivation.Disabled; } } From d419fe4dbf5f54edddd67cfb4507150ae43c2f8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 31 Aug 2020 12:02:02 +0200 Subject: [PATCH 0790/1782] Remove note shaking mention that doesn't apply in mania --- .../Objects/Drawables/DrawableManiaHitObject.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs index e16413bce7..08c41b0d75 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs @@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables /// /// Whether this can be hit, given a time value. - /// If non-null, judgements will be ignored (resulting in a shake) whilst the function returns false. + /// If non-null, judgements will be ignored whilst the function returns false. /// public Func CheckHittable; From ed74c39b5587e2084dd9fe957aef6d4e9f422644 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 31 Aug 2020 19:54:22 +0900 Subject: [PATCH 0791/1782] Move UserTopScoreContainer into base leaderboard --- .../SongSelect/TestSceneBeatmapLeaderboard.cs | 61 ++++++------- .../TestSceneUserTopScoreContainer.cs | 91 +++++++++---------- .../API/Requests/Responses/APILegacyScores.cs | 9 ++ osu.Game/Online/Leaderboards/Leaderboard.cs | 21 ++++- .../Leaderboards/UserTopScoreContainer.cs | 26 ++---- .../Match/Components/MatchLeaderboard.cs | 2 + .../Select/Leaderboards/BeatmapLeaderboard.cs | 30 ++---- 7 files changed, 114 insertions(+), 126 deletions(-) rename osu.Game/{Screens/Select => Online}/Leaderboards/UserTopScoreContainer.cs (77%) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs index 48b718c04d..67cd720260 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs @@ -5,9 +5,9 @@ using System; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Game.Beatmaps; -using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Leaderboards; using osu.Game.Overlays; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Scoring; using osu.Game.Screens.Select.Leaderboards; @@ -53,53 +53,46 @@ namespace osu.Game.Tests.Visual.SongSelect private void showPersonalBestWithNullPosition() { - leaderboard.TopScore = new APILegacyUserTopScoreInfo + leaderboard.TopScore = new ScoreInfo { - Position = null, - Score = new APILegacyScoreInfo + Rank = ScoreRank.XH, + Accuracy = 1, + MaxCombo = 244, + TotalScore = 1707827, + Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock() }, + User = new User { - Rank = ScoreRank.XH, - Accuracy = 1, - MaxCombo = 244, - TotalScore = 1707827, - Mods = new[] { new OsuModHidden().Acronym, new OsuModHardRock().Acronym, }, - User = new User + Id = 6602580, + Username = @"waaiiru", + Country = new Country { - Id = 6602580, - Username = @"waaiiru", - Country = new Country - { - FullName = @"Spain", - FlagName = @"ES", - }, + FullName = @"Spain", + FlagName = @"ES", }, - } + }, }; } private void showPersonalBest() { - leaderboard.TopScore = new APILegacyUserTopScoreInfo + leaderboard.TopScore = new ScoreInfo { Position = 999, - Score = new APILegacyScoreInfo + Rank = ScoreRank.XH, + Accuracy = 1, + MaxCombo = 244, + TotalScore = 1707827, + Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, + User = new User { - Rank = ScoreRank.XH, - Accuracy = 1, - MaxCombo = 244, - TotalScore = 1707827, - Mods = new[] { new OsuModHidden().Acronym, new OsuModHardRock().Acronym, }, - User = new User + Id = 6602580, + Username = @"waaiiru", + Country = new Country { - Id = 6602580, - Username = @"waaiiru", - Country = new Country - { - FullName = @"Spain", - FlagName = @"ES", - }, + FullName = @"Spain", + FlagName = @"ES", }, - } + }, }; } diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneUserTopScoreContainer.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneUserTopScoreContainer.cs index 0598324110..b8b8792b9b 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneUserTopScoreContainer.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneUserTopScoreContainer.cs @@ -6,11 +6,11 @@ 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.Online.Leaderboards; using osu.Game.Overlays; +using osu.Game.Rulesets.Mods; using osu.Game.Scoring; using osu.Game.Rulesets.Osu.Mods; -using osu.Game.Screens.Select.Leaderboards; using osu.Game.Users; namespace osu.Game.Tests.Visual.SongSelect @@ -22,7 +22,7 @@ namespace osu.Game.Tests.Visual.SongSelect public TestSceneUserTopScoreContainer() { - UserTopScoreContainer topScoreContainer; + UserTopScoreContainer topScoreContainer; Add(dialogOverlay = new DialogOverlay { @@ -42,7 +42,7 @@ namespace osu.Game.Tests.Visual.SongSelect RelativeSizeAxes = Axes.Both, Colour = Color4.DarkGreen, }, - topScoreContainer = new UserTopScoreContainer + topScoreContainer = new UserTopScoreContainer(s => new LeaderboardScore(s, s.Position, false)) { Origin = Anchor.BottomCentre, Anchor = Anchor.BottomCentre, @@ -52,69 +52,60 @@ namespace osu.Game.Tests.Visual.SongSelect var scores = new[] { - new APILegacyUserTopScoreInfo + new ScoreInfo { Position = 999, - Score = new APILegacyScoreInfo + Rank = ScoreRank.XH, + Accuracy = 1, + MaxCombo = 244, + TotalScore = 1707827, + Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, + User = new User { - Rank = ScoreRank.XH, - Accuracy = 1, - MaxCombo = 244, - TotalScore = 1707827, - Mods = new[] { new OsuModHidden().Acronym, new OsuModHardRock().Acronym, }, - User = new User + Id = 6602580, + Username = @"waaiiru", + Country = new Country { - Id = 6602580, - Username = @"waaiiru", - Country = new Country - { - FullName = @"Spain", - FlagName = @"ES", - }, + FullName = @"Spain", + FlagName = @"ES", }, - } + }, }, - new APILegacyUserTopScoreInfo + new ScoreInfo { Position = 110000, - Score = new APILegacyScoreInfo + Rank = ScoreRank.X, + Accuracy = 1, + MaxCombo = 244, + TotalScore = 1707827, + User = new User { - Rank = ScoreRank.X, - Accuracy = 1, - MaxCombo = 244, - TotalScore = 1707827, - User = new User + Id = 4608074, + Username = @"Skycries", + Country = new Country { - Id = 4608074, - Username = @"Skycries", - Country = new Country - { - FullName = @"Brazil", - FlagName = @"BR", - }, + FullName = @"Brazil", + FlagName = @"BR", }, - } + }, }, - new APILegacyUserTopScoreInfo + new ScoreInfo { Position = 22333, - Score = new APILegacyScoreInfo + Rank = ScoreRank.S, + Accuracy = 1, + MaxCombo = 244, + TotalScore = 1707827, + User = new User { - Rank = ScoreRank.S, - Accuracy = 1, - MaxCombo = 244, - TotalScore = 1707827, - User = new User + Id = 1541390, + Username = @"Toukai", + Country = new Country { - Id = 1541390, - Username = @"Toukai", - Country = new Country - { - FullName = @"Canada", - FlagName = @"CA", - }, + FullName = @"Canada", + FlagName = @"CA", }, - } + }, } }; diff --git a/osu.Game/Online/API/Requests/Responses/APILegacyScores.cs b/osu.Game/Online/API/Requests/Responses/APILegacyScores.cs index 75be9171b0..009639c1dc 100644 --- a/osu.Game/Online/API/Requests/Responses/APILegacyScores.cs +++ b/osu.Game/Online/API/Requests/Responses/APILegacyScores.cs @@ -3,6 +3,8 @@ using System.Collections.Generic; using Newtonsoft.Json; +using osu.Game.Rulesets; +using osu.Game.Scoring; namespace osu.Game.Online.API.Requests.Responses { @@ -22,5 +24,12 @@ namespace osu.Game.Online.API.Requests.Responses [JsonProperty(@"score")] public APILegacyScoreInfo Score; + + public ScoreInfo CreateScoreInfo(RulesetStore rulesets) + { + var score = Score.CreateScoreInfo(rulesets); + score.Position = Position; + return score; + } } } diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs index 800029ceb9..003d90d400 100644 --- a/osu.Game/Online/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -27,6 +27,7 @@ namespace osu.Game.Online.Leaderboards private readonly OsuScrollContainer scrollContainer; private readonly Container placeholderContainer; + private readonly UserTopScoreContainer topScoreContainer; private FillFlowContainer scrollFlow; @@ -87,6 +88,21 @@ namespace osu.Game.Online.Leaderboards } } + public TScoreInfo TopScore + { + get => topScoreContainer.Score.Value; + set + { + if (value == null) + topScoreContainer.Hide(); + else + { + topScoreContainer.Show(); + topScoreContainer.Score.Value = value; + } + } + } + protected virtual FillFlowContainer CreateScoreFlow() => new FillFlowContainer { @@ -198,8 +214,9 @@ namespace osu.Game.Online.Leaderboards { AutoSizeAxes = Axes.Y, RelativeSizeAxes = Axes.X, + Child = topScoreContainer = new UserTopScoreContainer(CreateDrawableTopScore) }, - } + }, }, }, }, @@ -367,5 +384,7 @@ namespace osu.Game.Online.Leaderboards } protected abstract LeaderboardScore CreateDrawableScore(TScoreInfo model, int index); + + protected abstract LeaderboardScore CreateDrawableTopScore(TScoreInfo model); } } diff --git a/osu.Game/Screens/Select/Leaderboards/UserTopScoreContainer.cs b/osu.Game/Online/Leaderboards/UserTopScoreContainer.cs similarity index 77% rename from osu.Game/Screens/Select/Leaderboards/UserTopScoreContainer.cs rename to osu.Game/Online/Leaderboards/UserTopScoreContainer.cs index 8e10734454..ffa7fa2c0b 100644 --- a/osu.Game/Screens/Select/Leaderboards/UserTopScoreContainer.cs +++ b/osu.Game/Online/Leaderboards/UserTopScoreContainer.cs @@ -9,31 +9,28 @@ 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; using osu.Game.Rulesets; -using osu.Game.Scoring; using osuTK; -namespace osu.Game.Screens.Select.Leaderboards +namespace osu.Game.Online.Leaderboards { - public class UserTopScoreContainer : VisibilityContainer + public class UserTopScoreContainer : VisibilityContainer { private const int duration = 500; + public Bindable Score = new Bindable(); + private readonly Container scoreContainer; - - public Bindable Score = new Bindable(); - - public Action ScoreSelected; + private readonly Func createScoreDelegate; protected override bool StartHidden => true; [Resolved] private RulesetStore rulesets { get; set; } - public UserTopScoreContainer() + public UserTopScoreContainer(Func createScoreDelegate) { + this.createScoreDelegate = createScoreDelegate; RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; @@ -72,7 +69,7 @@ namespace osu.Game.Screens.Select.Leaderboards private CancellationTokenSource loadScoreCancellation; - private void onScoreChanged(ValueChangedEvent score) + private void onScoreChanged(ValueChangedEvent score) { var newScore = score.NewValue; @@ -82,12 +79,7 @@ namespace osu.Game.Screens.Select.Leaderboards if (newScore == null) return; - var scoreInfo = newScore.Score.CreateScoreInfo(rulesets); - - LoadComponentAsync(new LeaderboardScore(scoreInfo, newScore.Position, false) - { - Action = () => ScoreSelected?.Invoke(scoreInfo) - }, drawableScore => + LoadComponentAsync(createScoreDelegate(newScore), drawableScore => { scoreContainer.Child = drawableScore; drawableScore.FadeInFromZero(duration, Easing.OutQuint); diff --git a/osu.Game/Screens/Multi/Match/Components/MatchLeaderboard.cs b/osu.Game/Screens/Multi/Match/Components/MatchLeaderboard.cs index 1afbf5c32a..01137dad43 100644 --- a/osu.Game/Screens/Multi/Match/Components/MatchLeaderboard.cs +++ b/osu.Game/Screens/Multi/Match/Components/MatchLeaderboard.cs @@ -51,6 +51,8 @@ namespace osu.Game.Screens.Multi.Match.Components } protected override LeaderboardScore CreateDrawableScore(APIUserScoreAggregate model, int index) => new MatchLeaderboardScore(model, index); + + protected override LeaderboardScore CreateDrawableTopScore(APIUserScoreAggregate model) => new MatchLeaderboardScore(model, 0); } public enum MatchLeaderboardScope diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs index 8e85eb4eb2..a78d8e3be0 100644 --- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs @@ -9,7 +9,6 @@ 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; @@ -41,25 +40,8 @@ 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; - private IBindable> itemRemoved; /// @@ -101,11 +83,6 @@ namespace osu.Game.Screens.Select.Leaderboards UpdateScores(); }; - Content.Add(topScoreContainer = new UserTopScoreContainer - { - ScoreSelected = s => ScoreSelected?.Invoke(s) - }); - itemRemoved = scoreManager.ItemRemoved.GetBoundCopy(); itemRemoved.BindValueChanged(onScoreRemoved); } @@ -183,7 +160,7 @@ namespace osu.Game.Screens.Select.Leaderboards req.Success += r => { scoresCallback?.Invoke(r.Scores.Select(s => s.CreateScoreInfo(rulesets))); - TopScore = r.UserScore; + TopScore = r.UserScore.CreateScoreInfo(rulesets); }; return req; @@ -193,5 +170,10 @@ namespace osu.Game.Screens.Select.Leaderboards { Action = () => ScoreSelected?.Invoke(model) }; + + protected override LeaderboardScore CreateDrawableTopScore(ScoreInfo model) => new LeaderboardScore(model, model.Position, false) + { + Action = () => ScoreSelected?.Invoke(model) + }; } } From d1ceb81797a8bd19b931f3816c26502673b0d8be Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 31 Aug 2020 19:54:41 +0900 Subject: [PATCH 0792/1782] Rename request --- ...GetRoomScoresRequest.cs => GetRoomLeaderboardRequest.cs} | 6 ++---- osu.Game/Screens/Multi/Match/Components/MatchLeaderboard.cs | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) rename osu.Game/Online/Multiplayer/{GetRoomScoresRequest.cs => GetRoomLeaderboardRequest.cs} (65%) diff --git a/osu.Game/Online/Multiplayer/GetRoomScoresRequest.cs b/osu.Game/Online/Multiplayer/GetRoomLeaderboardRequest.cs similarity index 65% rename from osu.Game/Online/Multiplayer/GetRoomScoresRequest.cs rename to osu.Game/Online/Multiplayer/GetRoomLeaderboardRequest.cs index bc913030dd..37c21457bc 100644 --- a/osu.Game/Online/Multiplayer/GetRoomScoresRequest.cs +++ b/osu.Game/Online/Multiplayer/GetRoomLeaderboardRequest.cs @@ -1,17 +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 osu.Game.Online.API; -using osu.Game.Online.API.Requests.Responses; namespace osu.Game.Online.Multiplayer { - public class GetRoomScoresRequest : APIRequest> + public class GetRoomLeaderboardRequest : APIRequest { private readonly int roomId; - public GetRoomScoresRequest(int roomId) + public GetRoomLeaderboardRequest(int roomId) { this.roomId = roomId; } diff --git a/osu.Game/Screens/Multi/Match/Components/MatchLeaderboard.cs b/osu.Game/Screens/Multi/Match/Components/MatchLeaderboard.cs index 01137dad43..56381dccb6 100644 --- a/osu.Game/Screens/Multi/Match/Components/MatchLeaderboard.cs +++ b/osu.Game/Screens/Multi/Match/Components/MatchLeaderboard.cs @@ -39,7 +39,7 @@ namespace osu.Game.Screens.Multi.Match.Components if (roomId.Value == null) return null; - var req = new GetRoomScoresRequest(roomId.Value ?? 0); + var req = new GetRoomLeaderboardRequest(roomId.Value ?? 0); req.Success += r => { From 77698ec31e8cae2550c2dac2d68bae51ab2805a6 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 31 Aug 2020 19:54:57 +0900 Subject: [PATCH 0793/1782] Add support for showing own top score in timeshift --- osu.Game/Online/Multiplayer/APILeaderboard.cs | 18 ++++++++++++++++++ .../Multi/Match/Components/MatchLeaderboard.cs | 6 ++---- 2 files changed, 20 insertions(+), 4 deletions(-) create mode 100644 osu.Game/Online/Multiplayer/APILeaderboard.cs diff --git a/osu.Game/Online/Multiplayer/APILeaderboard.cs b/osu.Game/Online/Multiplayer/APILeaderboard.cs new file mode 100644 index 0000000000..96fe7cefb0 --- /dev/null +++ b/osu.Game/Online/Multiplayer/APILeaderboard.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 System.Collections.Generic; +using Newtonsoft.Json; +using osu.Game.Online.API.Requests.Responses; + +namespace osu.Game.Online.Multiplayer +{ + public class APILeaderboard + { + [JsonProperty("leaderboard")] + public List Leaderboard; + + [JsonProperty("own_score")] + public APIUserScoreAggregate OwnScore; + } +} diff --git a/osu.Game/Screens/Multi/Match/Components/MatchLeaderboard.cs b/osu.Game/Screens/Multi/Match/Components/MatchLeaderboard.cs index 56381dccb6..847f3a7b55 100644 --- a/osu.Game/Screens/Multi/Match/Components/MatchLeaderboard.cs +++ b/osu.Game/Screens/Multi/Match/Components/MatchLeaderboard.cs @@ -14,8 +14,6 @@ namespace osu.Game.Screens.Multi.Match.Components { public class MatchLeaderboard : Leaderboard { - public Action> ScoresLoaded; - [Resolved(typeof(Room), nameof(Room.RoomID))] private Bindable roomId { get; set; } @@ -43,8 +41,8 @@ namespace osu.Game.Screens.Multi.Match.Components req.Success += r => { - scoresCallback?.Invoke(r); - ScoresLoaded?.Invoke(r); + scoresCallback?.Invoke(r.Leaderboard); + TopScore = r.OwnScore; }; return req; From 6ed191786f978e3b6bb943bc8e33633ac17ff80e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 31 Aug 2020 20:01:59 +0900 Subject: [PATCH 0794/1782] Add support for position --- .../Online/API/Requests/Responses/APIUserScoreAggregate.cs | 4 ++++ osu.Game/Screens/Multi/Match/Components/MatchLeaderboard.cs | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/API/Requests/Responses/APIUserScoreAggregate.cs b/osu.Game/Online/API/Requests/Responses/APIUserScoreAggregate.cs index 0bba6a93bd..bcc8721400 100644 --- a/osu.Game/Online/API/Requests/Responses/APIUserScoreAggregate.cs +++ b/osu.Game/Online/API/Requests/Responses/APIUserScoreAggregate.cs @@ -33,6 +33,9 @@ namespace osu.Game.Online.API.Requests.Responses [JsonProperty("user")] public User User { get; set; } + [JsonProperty("position")] + public int? Position { get; set; } + public ScoreInfo CreateScoreInfo() => new ScoreInfo { @@ -40,6 +43,7 @@ namespace osu.Game.Online.API.Requests.Responses PP = PP, TotalScore = TotalScore, User = User, + Position = Position }; } } diff --git a/osu.Game/Screens/Multi/Match/Components/MatchLeaderboard.cs b/osu.Game/Screens/Multi/Match/Components/MatchLeaderboard.cs index 847f3a7b55..7d5968202c 100644 --- a/osu.Game/Screens/Multi/Match/Components/MatchLeaderboard.cs +++ b/osu.Game/Screens/Multi/Match/Components/MatchLeaderboard.cs @@ -50,7 +50,7 @@ namespace osu.Game.Screens.Multi.Match.Components protected override LeaderboardScore CreateDrawableScore(APIUserScoreAggregate model, int index) => new MatchLeaderboardScore(model, index); - protected override LeaderboardScore CreateDrawableTopScore(APIUserScoreAggregate model) => new MatchLeaderboardScore(model, 0); + protected override LeaderboardScore CreateDrawableTopScore(APIUserScoreAggregate model) => new MatchLeaderboardScore(model, model.Position ?? 0); } public enum MatchLeaderboardScope From d22de26afb354e82769fd2ffdd3a587ae1a32f04 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 31 Aug 2020 20:08:36 +0900 Subject: [PATCH 0795/1782] Add whitespace --- osu.Game/Online/Leaderboards/UserTopScoreContainer.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Online/Leaderboards/UserTopScoreContainer.cs b/osu.Game/Online/Leaderboards/UserTopScoreContainer.cs index ffa7fa2c0b..ab4210251e 100644 --- a/osu.Game/Online/Leaderboards/UserTopScoreContainer.cs +++ b/osu.Game/Online/Leaderboards/UserTopScoreContainer.cs @@ -31,6 +31,7 @@ namespace osu.Game.Online.Leaderboards public UserTopScoreContainer(Func createScoreDelegate) { this.createScoreDelegate = createScoreDelegate; + RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; From 8cf26979fb11ec81199cf87378b20134a809d816 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 31 Aug 2020 20:16:28 +0900 Subject: [PATCH 0796/1782] Allow null user score --- 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 a78d8e3be0..8ddae67dba 100644 --- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs @@ -160,7 +160,7 @@ namespace osu.Game.Screens.Select.Leaderboards req.Success += r => { scoresCallback?.Invoke(r.Scores.Select(s => s.CreateScoreInfo(rulesets))); - TopScore = r.UserScore.CreateScoreInfo(rulesets); + TopScore = r.UserScore?.CreateScoreInfo(rulesets); }; return req; From 61d580b6ba9841793e81600d0c228ea7848bff3e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 31 Aug 2020 20:17:23 +0900 Subject: [PATCH 0797/1782] Don't highlight top score --- osu.Game/Screens/Multi/Match/Components/MatchLeaderboard.cs | 2 +- .../Screens/Multi/Match/Components/MatchLeaderboardScore.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Multi/Match/Components/MatchLeaderboard.cs b/osu.Game/Screens/Multi/Match/Components/MatchLeaderboard.cs index 7d5968202c..50afbb39fe 100644 --- a/osu.Game/Screens/Multi/Match/Components/MatchLeaderboard.cs +++ b/osu.Game/Screens/Multi/Match/Components/MatchLeaderboard.cs @@ -50,7 +50,7 @@ namespace osu.Game.Screens.Multi.Match.Components protected override LeaderboardScore CreateDrawableScore(APIUserScoreAggregate model, int index) => new MatchLeaderboardScore(model, index); - protected override LeaderboardScore CreateDrawableTopScore(APIUserScoreAggregate model) => new MatchLeaderboardScore(model, model.Position ?? 0); + protected override LeaderboardScore CreateDrawableTopScore(APIUserScoreAggregate model) => new MatchLeaderboardScore(model, model.Position ?? 0, false); } public enum MatchLeaderboardScope diff --git a/osu.Game/Screens/Multi/Match/Components/MatchLeaderboardScore.cs b/osu.Game/Screens/Multi/Match/Components/MatchLeaderboardScore.cs index 73a40d9579..c4e2b332b3 100644 --- a/osu.Game/Screens/Multi/Match/Components/MatchLeaderboardScore.cs +++ b/osu.Game/Screens/Multi/Match/Components/MatchLeaderboardScore.cs @@ -14,8 +14,8 @@ namespace osu.Game.Screens.Multi.Match.Components { private readonly APIUserScoreAggregate score; - public MatchLeaderboardScore(APIUserScoreAggregate score, int rank) - : base(score.CreateScoreInfo(), rank) + public MatchLeaderboardScore(APIUserScoreAggregate score, int rank, bool allowHighlight = true) + : base(score.CreateScoreInfo(), rank, allowHighlight) { this.score = score; } From 5e77e8cfcf74e642c2076773799ab355097fa22b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 31 Aug 2020 20:21:57 +0900 Subject: [PATCH 0798/1782] Reduce min size of chat --- osu.Game/Screens/Multi/Match/MatchSubScreen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Multi/Match/MatchSubScreen.cs b/osu.Game/Screens/Multi/Match/MatchSubScreen.cs index 7c2d5cf85d..0d2adeb27c 100644 --- a/osu.Game/Screens/Multi/Match/MatchSubScreen.cs +++ b/osu.Game/Screens/Multi/Match/MatchSubScreen.cs @@ -172,7 +172,7 @@ namespace osu.Game.Screens.Multi.Match new Dimension(GridSizeMode.AutoSize), new Dimension(), new Dimension(GridSizeMode.AutoSize), - new Dimension(GridSizeMode.Relative, size: 0.4f, minSize: 240), + new Dimension(GridSizeMode.Relative, size: 0.4f, minSize: 120), } }, null From 3b22b891d13e9d53d0266d234363dfd2362436c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 31 Aug 2020 14:28:45 +0200 Subject: [PATCH 0799/1782] Add failing test cases --- .../NonVisual/Ranking/UnstableRateTest.cs | 43 +++++++++++++++++++ .../Ranking/Statistics/SimpleStatisticItem.cs | 9 +++- 2 files changed, 51 insertions(+), 1 deletion(-) create mode 100644 osu.Game.Tests/NonVisual/Ranking/UnstableRateTest.cs diff --git a/osu.Game.Tests/NonVisual/Ranking/UnstableRateTest.cs b/osu.Game.Tests/NonVisual/Ranking/UnstableRateTest.cs new file mode 100644 index 0000000000..bf4145754a --- /dev/null +++ b/osu.Game.Tests/NonVisual/Ranking/UnstableRateTest.cs @@ -0,0 +1,43 @@ +// Copyright (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.Utils; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Scoring; +using osu.Game.Screens.Ranking.Statistics; + +namespace osu.Game.Tests.NonVisual.Ranking +{ + [TestFixture] + public class UnstableRateTest + { + [Test] + public void TestDistributedHits() + { + var events = Enumerable.Range(-5, 11) + .Select(t => new HitEvent(t - 5, HitResult.Great, new HitObject(), null, null)); + + var unstableRate = new UnstableRate(events); + + Assert.IsTrue(Precision.AlmostEquals(unstableRate.Value, 10 * Math.Sqrt(10))); + } + + [Test] + public void TestMissesAndEmptyWindows() + { + var events = new[] + { + new HitEvent(-100, HitResult.Miss, new HitObject(), null, null), + new HitEvent(0, HitResult.Great, new HitObject(), null, null), + new HitEvent(200, HitResult.Meh, new HitObject { HitWindows = HitWindows.Empty }, null, null), + }; + + var unstableRate = new UnstableRate(events); + + Assert.IsTrue(Precision.AlmostEquals(0, unstableRate.Value)); + } + } +} diff --git a/osu.Game/Screens/Ranking/Statistics/SimpleStatisticItem.cs b/osu.Game/Screens/Ranking/Statistics/SimpleStatisticItem.cs index 3d9ba2f225..6fe7e4eda8 100644 --- a/osu.Game/Screens/Ranking/Statistics/SimpleStatisticItem.cs +++ b/osu.Game/Screens/Ranking/Statistics/SimpleStatisticItem.cs @@ -59,12 +59,19 @@ namespace osu.Game.Screens.Ranking.Statistics /// public class SimpleStatisticItem : SimpleStatisticItem { + private TValue value; + /// /// The statistic's value to be displayed. /// public new TValue Value { - set => base.Value = DisplayValue(value); + get => value; + set + { + this.value = value; + base.Value = DisplayValue(value); + } } /// From 3ca2a7767a04d2911d8244bb1d2755747e099a45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 31 Aug 2020 14:29:01 +0200 Subject: [PATCH 0800/1782] Exclude misses and empty window hits from UR calculation --- osu.Game/Screens/Ranking/Statistics/UnstableRate.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Ranking/Statistics/UnstableRate.cs b/osu.Game/Screens/Ranking/Statistics/UnstableRate.cs index 5b368c3e8d..18a2238784 100644 --- a/osu.Game/Screens/Ranking/Statistics/UnstableRate.cs +++ b/osu.Game/Screens/Ranking/Statistics/UnstableRate.cs @@ -20,7 +20,8 @@ namespace osu.Game.Screens.Ranking.Statistics public UnstableRate(IEnumerable hitEvents) : base("Unstable Rate") { - var timeOffsets = hitEvents.Select(ev => ev.TimeOffset).ToArray(); + var timeOffsets = hitEvents.Where(e => !(e.HitObject.HitWindows is HitWindows.EmptyHitWindows) && e.Result != HitResult.Miss) + .Select(ev => ev.TimeOffset).ToArray(); Value = 10 * standardDeviation(timeOffsets); } From 0980f97ea2c8da99030f6f9d7b16425c35865ba5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 31 Aug 2020 16:06:24 +0200 Subject: [PATCH 0801/1782] Replace precision check with absolute equality assert --- osu.Game.Tests/NonVisual/Ranking/UnstableRateTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/NonVisual/Ranking/UnstableRateTest.cs b/osu.Game.Tests/NonVisual/Ranking/UnstableRateTest.cs index bf4145754a..ad6f01881b 100644 --- a/osu.Game.Tests/NonVisual/Ranking/UnstableRateTest.cs +++ b/osu.Game.Tests/NonVisual/Ranking/UnstableRateTest.cs @@ -37,7 +37,7 @@ namespace osu.Game.Tests.NonVisual.Ranking var unstableRate = new UnstableRate(events); - Assert.IsTrue(Precision.AlmostEquals(0, unstableRate.Value)); + Assert.AreEqual(0, unstableRate.Value); } } } From fde4b03dabe1f58d871f7785f431a6c68f5b5ee5 Mon Sep 17 00:00:00 2001 From: Pavle Aleksov Date: Mon, 31 Aug 2020 16:21:00 +0200 Subject: [PATCH 0802/1782] added spinner duration check - skip HitObjectReplay if duration is 0 --- osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs b/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs index 4cb2cd6539..5a439734c6 100644 --- a/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs +++ b/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs @@ -156,6 +156,9 @@ namespace osu.Game.Rulesets.Osu.Replays // TODO: Shouldn't the spinner always spin in the same direction? if (h is Spinner) { + if ((h as Spinner).Duration == 0) + return; + calcSpinnerStartPosAndDirection(((OsuReplayFrame)Frames[^1]).Position, out startPosition, out spinnerDirection); Vector2 spinCentreOffset = SPINNER_CENTRE - ((OsuReplayFrame)Frames[^1]).Position; From 0655fc14737c76def98ebb375329044c4ff0392b Mon Sep 17 00:00:00 2001 From: PajLe Date: Mon, 31 Aug 2020 16:50:31 +0200 Subject: [PATCH 0803/1782] changed comparing Duration to autoplay's reactionTime instead of 0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bartłomiej Dach --- osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs b/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs index 5a439734c6..9ef2ff9ebb 100644 --- a/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs +++ b/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs @@ -154,9 +154,9 @@ namespace osu.Game.Rulesets.Osu.Replays // The startPosition for the slider should not be its .Position, but the point on the circle whose tangent crosses the current cursor position // We also modify spinnerDirection so it spins in the direction it enters the spin circle, to make a smooth transition. // TODO: Shouldn't the spinner always spin in the same direction? - if (h is Spinner) + if (h is Spinner spinner) { - if ((h as Spinner).Duration == 0) + if (spinner.Duration < reactionTime) return; calcSpinnerStartPosAndDirection(((OsuReplayFrame)Frames[^1]).Position, out startPosition, out spinnerDirection); From 69ec2a76ef1586f76bb26658fec7ab183cc7762e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 31 Aug 2020 17:20:45 +0200 Subject: [PATCH 0804/1782] Replace reaction time check with spins required check --- osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs b/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs index 9ef2ff9ebb..76b2631894 100644 --- a/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs +++ b/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs @@ -156,7 +156,8 @@ namespace osu.Game.Rulesets.Osu.Replays // TODO: Shouldn't the spinner always spin in the same direction? if (h is Spinner spinner) { - if (spinner.Duration < reactionTime) + // spinners with 0 spins required will auto-complete - don't bother + if (spinner.SpinsRequired == 0) return; calcSpinnerStartPosAndDirection(((OsuReplayFrame)Frames[^1]).Position, out startPosition, out spinnerDirection); From eafa97af17a1ef203ccd4f25759401ccce169cc0 Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Mon, 31 Aug 2020 17:23:42 +0200 Subject: [PATCH 0805/1782] Revert changes done to SkinConfiguration and IHasCustomColours --- osu.Game/Beatmaps/Formats/IHasCustomColours.cs | 2 +- osu.Game/Skinning/LegacyManiaSkinConfiguration.cs | 2 +- osu.Game/Skinning/SkinConfiguration.cs | 8 ++------ 3 files changed, 4 insertions(+), 8 deletions(-) diff --git a/osu.Game/Beatmaps/Formats/IHasCustomColours.cs b/osu.Game/Beatmaps/Formats/IHasCustomColours.cs index 1ac5ca83cb..dba3a37545 100644 --- a/osu.Game/Beatmaps/Formats/IHasCustomColours.cs +++ b/osu.Game/Beatmaps/Formats/IHasCustomColours.cs @@ -8,6 +8,6 @@ namespace osu.Game.Beatmaps.Formats { public interface IHasCustomColours { - IDictionary CustomColours { get; } + Dictionary CustomColours { get; } } } diff --git a/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs b/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs index 7972cc7d06..65d5851455 100644 --- a/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs +++ b/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs @@ -23,7 +23,7 @@ namespace osu.Game.Skinning public readonly int Keys; - public IDictionary CustomColours { get; } = new SortedDictionary(); + public Dictionary CustomColours { get; set; } = new Dictionary(); public Dictionary ImageLookups = new Dictionary(); diff --git a/osu.Game/Skinning/SkinConfiguration.cs b/osu.Game/Skinning/SkinConfiguration.cs index 18d970dd64..2857ad3824 100644 --- a/osu.Game/Skinning/SkinConfiguration.cs +++ b/osu.Game/Skinning/SkinConfiguration.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 System; using System.Collections.Generic; -using System.Linq; using osu.Game.Beatmaps.Formats; using osuTK.Graphics; @@ -12,7 +10,7 @@ namespace osu.Game.Skinning /// /// An empty skin configuration. /// - public class SkinConfiguration : IEquatable, IHasComboColours, IHasCustomColours + public class SkinConfiguration : IHasComboColours, IHasCustomColours { public readonly SkinInfo SkinInfo = new SkinInfo(); @@ -47,10 +45,8 @@ namespace osu.Game.Skinning public void AddComboColours(params Color4[] colours) => comboColours.AddRange(colours); - public IDictionary CustomColours { get; } = new SortedDictionary(); + public Dictionary CustomColours { get; set; } = new Dictionary(); public readonly SortedDictionary ConfigDictionary = new SortedDictionary(); - - public bool Equals(SkinConfiguration other) => other != null && ConfigDictionary.SequenceEqual(other.ConfigDictionary) && ((ComboColours == null && other.ComboColours == null) || ComboColours.SequenceEqual(other.ComboColours)) && CustomColours.SequenceEqual(other.CustomColours); } } From 1484e78654743b8e68ead43811ed5735d681b13d Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Mon, 31 Aug 2020 17:24:00 +0200 Subject: [PATCH 0806/1782] Update xmldoc --- osu.Game/Beatmaps/BeatmapManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 89a776dd31..f725d55970 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -195,7 +195,7 @@ namespace osu.Game.Beatmaps /// /// The to save the content against. The file referenced by will be replaced. /// The content to write. - /// The beatmap content to write, or null if not to be changed. + /// The beatmap content to write, null if to be omitted. public void Save(BeatmapInfo info, IBeatmap beatmapContent, IBeatmapSkin beatmapSkin = null) { var setInfo = QueryBeatmapSet(s => s.Beatmaps.Any(b => b.ID == info.ID)); From fb37a14d577416754f17a569b9658989d7327c07 Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Mon, 31 Aug 2020 17:24:03 +0200 Subject: [PATCH 0807/1782] Update LegacyBeatmapEncoder.cs --- osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs index cae6a43cd4..53ce1c831c 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs @@ -25,6 +25,8 @@ namespace osu.Game.Beatmaps.Formats public const int LATEST_VERSION = 128; private readonly IBeatmap beatmap; + + [CanBeNull] private readonly IBeatmapSkin skin; /// @@ -64,7 +66,7 @@ namespace osu.Game.Beatmaps.Formats handleControlPoints(writer); writer.WriteLine(); - handleComboColours(writer); + handleColours(writer); writer.WriteLine(); handleHitObjects(writer); @@ -209,9 +211,9 @@ namespace osu.Game.Beatmaps.Formats } } - private void handleComboColours(TextWriter writer) + private void handleColours(TextWriter writer) { - var colours = skin.GetConfig>(GlobalSkinColours.ComboColours)?.Value; + var colours = skin?.GetConfig>(GlobalSkinColours.ComboColours)?.Value; if (colours == null || colours.Count == 0) return; From a893aa8af86b5037dc1662adfe789112a61afea7 Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Mon, 31 Aug 2020 17:24:24 +0200 Subject: [PATCH 0808/1782] Cut down changes done to LegacyBeatmapEncoderTest --- .../Formats/LegacyBeatmapEncoderTest.cs | 71 +++++-------------- 1 file changed, 16 insertions(+), 55 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs index a8a3f266fc..bcc5970a27 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs @@ -7,10 +7,8 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; -using System.Threading.Tasks; using NUnit.Framework; using osu.Framework.Audio.Track; -using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics.Textures; using osu.Framework.IO.Stores; using osu.Game.Beatmaps; @@ -44,7 +42,19 @@ namespace osu.Game.Tests.Beatmaps.Formats sort(decodedAfterEncode); Assert.That(decodedAfterEncode.beatmap.Serialize(), Is.EqualTo(decoded.beatmap.Serialize())); - Assert.IsTrue(decodedAfterEncode.beatmapSkin.Configuration.Equals(decoded.beatmapSkin.Configuration)); + Assert.IsTrue(areComboColoursEqual(decodedAfterEncode.beatmapSkin.Configuration, decoded.beatmapSkin.Configuration)); + } + + private bool areComboColoursEqual(IHasComboColours a, IHasComboColours b) + { + // equal to null, no need to SequenceEqual + if (a.ComboColours == null && b.ComboColours == null) + return true; + + if (a.ComboColours == null || b.ComboColours == null) + return false; + + return a.ComboColours.SequenceEqual(b.ComboColours); } private void sort((IBeatmap beatmap, IBeatmapSkin beatmapSkin) tuple) @@ -62,63 +72,14 @@ namespace osu.Game.Tests.Beatmaps.Formats using (var reader = new LineBufferedReader(stream)) { var beatmap = new LegacyBeatmapDecoder { ApplyOffsets = false }.Decode(reader); - - using (var rs = new MemoryBeatmapResourceStore(stream, name)) - { - var beatmapSkin = new TestLegacySkin(beatmap, rs, name); - return (convert(beatmap), beatmapSkin); - } + var beatmapSkin = new TestLegacySkin(beatmaps_resource_store, name); + return (convert(beatmap), beatmapSkin); } } - private class MemoryBeatmapResourceStore : IResourceStore - { - private readonly Stream beatmapData; - private readonly string beatmapName; - - public MemoryBeatmapResourceStore(Stream beatmapData, string beatmapName) - { - this.beatmapData = beatmapData; - this.beatmapName = beatmapName; - } - - public void Dispose() => beatmapData.Dispose(); - - public byte[] Get(string name) - { - if (name != beatmapName) - return null; - - byte[] buffer = new byte[beatmapData.Length]; - beatmapData.Read(buffer, 0, buffer.Length); - return buffer; - } - - public async Task GetAsync(string name) - { - if (name != beatmapName) - return null; - - byte[] buffer = new byte[beatmapData.Length]; - await beatmapData.ReadAsync(buffer.AsMemory()); - return buffer; - } - - public Stream GetStream(string name) - { - if (name != beatmapName) - return null; - - beatmapData.Seek(0, SeekOrigin.Begin); - return beatmapData; - } - - public IEnumerable GetAvailableResources() => beatmapName.Yield(); - } - private class TestLegacySkin : LegacySkin, IBeatmapSkin { - public TestLegacySkin(Beatmap beatmap, IResourceStore storage, string fileName) + public TestLegacySkin(IResourceStore storage, string fileName) : base(new SkinInfo { Name = "Test Skin", Creator = "Craftplacer" }, storage, null, fileName) { } From a290f7eeec4ecefb46dc4288eb98f94c909f492b Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Mon, 31 Aug 2020 17:34:18 +0200 Subject: [PATCH 0809/1782] Revert left-over type change in SkinConfiguration --- osu.Game/Skinning/SkinConfiguration.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Skinning/SkinConfiguration.cs b/osu.Game/Skinning/SkinConfiguration.cs index 2857ad3824..a55870aa6d 100644 --- a/osu.Game/Skinning/SkinConfiguration.cs +++ b/osu.Game/Skinning/SkinConfiguration.cs @@ -47,6 +47,6 @@ namespace osu.Game.Skinning public Dictionary CustomColours { get; set; } = new Dictionary(); - public readonly SortedDictionary ConfigDictionary = new SortedDictionary(); + public readonly Dictionary ConfigDictionary = new Dictionary(); } } From 3cc169c933f2e4518fb9756d7905641d4fcf4167 Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Mon, 31 Aug 2020 17:48:36 +0200 Subject: [PATCH 0810/1782] Remove set from properties in SkinConfiguration classes I don't get why this wasn't resolved in the first place when this file was originally written. *sigh* --- osu.Game/Skinning/LegacyManiaSkinConfiguration.cs | 2 +- osu.Game/Skinning/SkinConfiguration.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs b/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs index 65d5851455..35a6140cbc 100644 --- a/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs +++ b/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs @@ -23,7 +23,7 @@ namespace osu.Game.Skinning public readonly int Keys; - public Dictionary CustomColours { get; set; } = new Dictionary(); + public Dictionary CustomColours { get; } = new Dictionary(); public Dictionary ImageLookups = new Dictionary(); diff --git a/osu.Game/Skinning/SkinConfiguration.cs b/osu.Game/Skinning/SkinConfiguration.cs index a55870aa6d..25a924c929 100644 --- a/osu.Game/Skinning/SkinConfiguration.cs +++ b/osu.Game/Skinning/SkinConfiguration.cs @@ -45,7 +45,7 @@ namespace osu.Game.Skinning public void AddComboColours(params Color4[] colours) => comboColours.AddRange(colours); - public Dictionary CustomColours { get; set; } = new Dictionary(); + public Dictionary CustomColours { get; } = new Dictionary(); public readonly Dictionary ConfigDictionary = new Dictionary(); } From 9b3a48ee5e3426c8474232518755b429d563ff5d Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Mon, 31 Aug 2020 18:29:46 +0200 Subject: [PATCH 0811/1782] Revert "Add marker interface for beatmap skins" --- .../TestSceneLegacyBeatmapSkin.cs | 2 +- .../TestSceneSkinFallbacks.cs | 18 +++++------------- .../Gameplay/TestSceneStoryboardSamples.cs | 4 ++-- .../Beatmaps/BeatmapManager_WorkingBeatmap.cs | 2 +- osu.Game/Beatmaps/IWorkingBeatmap.cs | 4 ++-- osu.Game/Beatmaps/WorkingBeatmap.cs | 8 ++++---- .../Skinning/BeatmapSkinProvidingContainer.cs | 4 ++-- osu.Game/Skinning/DefaultBeatmapSkin.cs | 9 --------- osu.Game/Skinning/IBeatmapSkin.cs | 12 ------------ osu.Game/Skinning/LegacyBeatmapSkin.cs | 2 +- osu.Game/Tests/Beatmaps/HitObjectSampleTest.cs | 2 +- 11 files changed, 19 insertions(+), 48 deletions(-) delete mode 100644 osu.Game/Skinning/DefaultBeatmapSkin.cs delete mode 100644 osu.Game/Skinning/IBeatmapSkin.cs diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneLegacyBeatmapSkin.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneLegacyBeatmapSkin.cs index 03d18cefef..3ff37c4147 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneLegacyBeatmapSkin.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneLegacyBeatmapSkin.cs @@ -110,7 +110,7 @@ namespace osu.Game.Rulesets.Osu.Tests this.hasColours = hasColours; } - protected override IBeatmapSkin GetSkin() => new TestBeatmapSkin(BeatmapInfo, hasColours); + protected override ISkin GetSkin() => new TestBeatmapSkin(BeatmapInfo, hasColours); } private class TestBeatmapSkin : LegacyBeatmapSkin diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs index 64da80a88e..075bf314bc 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs @@ -29,12 +29,12 @@ namespace osu.Game.Rulesets.Osu.Tests public class TestSceneSkinFallbacks : TestSceneOsuPlayer { private readonly TestSource testUserSkin; - private readonly BeatmapTestSource testBeatmapSkin; + private readonly TestSource testBeatmapSkin; public TestSceneSkinFallbacks() { testUserSkin = new TestSource("user"); - testBeatmapSkin = new BeatmapTestSource(); + testBeatmapSkin = new TestSource("beatmap"); } [Test] @@ -80,15 +80,15 @@ namespace osu.Game.Rulesets.Osu.Tests public class CustomSkinWorkingBeatmap : ClockBackedTestWorkingBeatmap { - private readonly IBeatmapSkin skin; + private readonly ISkinSource skin; - public CustomSkinWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard, IFrameBasedClock frameBasedClock, AudioManager audio, IBeatmapSkin skin) + public CustomSkinWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard, IFrameBasedClock frameBasedClock, AudioManager audio, ISkinSource skin) : base(beatmap, storyboard, frameBasedClock, audio) { this.skin = skin; } - protected override IBeatmapSkin GetSkin() => skin; + protected override ISkin GetSkin() => skin; } public class SkinProvidingPlayer : TestPlayer @@ -112,14 +112,6 @@ namespace osu.Game.Rulesets.Osu.Tests } } - private class BeatmapTestSource : TestSource, IBeatmapSkin - { - public BeatmapTestSource() - : base("beatmap") - { - } - } - public class TestSource : ISkinSource { private readonly string identifier; diff --git a/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs b/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs index bc9528beb6..b30870d057 100644 --- a/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs +++ b/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs @@ -116,7 +116,7 @@ namespace osu.Game.Tests.Gameplay AddAssert("sample playback rate matches mod rates", () => sample.Channel.AggregateFrequency.Value == expectedRate); } - private class TestSkin : LegacySkin, IBeatmapSkin + private class TestSkin : LegacySkin { public TestSkin(string resourceName, AudioManager audioManager) : base(DefaultLegacySkin.Info, new TestResourceStore(resourceName), audioManager, "skin.ini") @@ -156,7 +156,7 @@ namespace osu.Game.Tests.Gameplay this.audio = audio; } - protected override IBeatmapSkin GetSkin() => new TestSkin("test-sample", audio); + protected override ISkin GetSkin() => new TestSkin("test-sample", audio); } private class TestDrawableStoryboardSample : DrawableStoryboardSample diff --git a/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs b/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs index 44728cc251..39c5ccab27 100644 --- a/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs @@ -140,7 +140,7 @@ namespace osu.Game.Beatmaps return storyboard; } - protected override IBeatmapSkin GetSkin() + protected override ISkin GetSkin() { try { diff --git a/osu.Game/Beatmaps/IWorkingBeatmap.cs b/osu.Game/Beatmaps/IWorkingBeatmap.cs index aac41725a9..31975157a0 100644 --- a/osu.Game/Beatmaps/IWorkingBeatmap.cs +++ b/osu.Game/Beatmaps/IWorkingBeatmap.cs @@ -42,9 +42,9 @@ namespace osu.Game.Beatmaps Storyboard Storyboard { get; } /// - /// Retrieves the which this provides. + /// Retrieves the which this provides. /// - IBeatmapSkin Skin { get; } + ISkin Skin { get; } /// /// Constructs a playable from using the applicable converters for a specific . diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs index 163b62a55c..b4bcf285b9 100644 --- a/osu.Game/Beatmaps/WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/WorkingBeatmap.cs @@ -44,7 +44,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++; } @@ -275,10 +275,10 @@ namespace osu.Game.Beatmaps private readonly RecyclableLazy storyboard; public bool SkinLoaded => skin.IsResultAvailable; - public IBeatmapSkin Skin => skin.Value; + public ISkin Skin => skin.Value; - protected virtual IBeatmapSkin GetSkin() => new DefaultBeatmapSkin(); - 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/BeatmapSkinProvidingContainer.cs b/osu.Game/Skinning/BeatmapSkinProvidingContainer.cs index 346bfe53b8..40335db697 100644 --- a/osu.Game/Skinning/BeatmapSkinProvidingContainer.cs +++ b/osu.Game/Skinning/BeatmapSkinProvidingContainer.cs @@ -11,7 +11,7 @@ namespace osu.Game.Skinning /// /// A container which overrides existing skin options with beatmap-local values. /// - public class BeatmapSkinProvidingContainer : SkinProvidingContainer, IBeatmapSkin + public class BeatmapSkinProvidingContainer : SkinProvidingContainer { private readonly Bindable beatmapSkins = new Bindable(); private readonly Bindable beatmapHitsounds = new Bindable(); @@ -21,7 +21,7 @@ namespace osu.Game.Skinning protected override bool AllowTextureLookup(string componentName) => beatmapSkins.Value; protected override bool AllowSampleLookup(ISampleInfo componentName) => beatmapHitsounds.Value; - public BeatmapSkinProvidingContainer(IBeatmapSkin skin) + public BeatmapSkinProvidingContainer(ISkin skin) : base(skin) { } diff --git a/osu.Game/Skinning/DefaultBeatmapSkin.cs b/osu.Game/Skinning/DefaultBeatmapSkin.cs deleted file mode 100644 index 7b5ccd45c3..0000000000 --- a/osu.Game/Skinning/DefaultBeatmapSkin.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.Skinning -{ - public class DefaultBeatmapSkin : DefaultSkin, IBeatmapSkin - { - } -} diff --git a/osu.Game/Skinning/IBeatmapSkin.cs b/osu.Game/Skinning/IBeatmapSkin.cs deleted file mode 100644 index 91caaed557..0000000000 --- a/osu.Game/Skinning/IBeatmapSkin.cs +++ /dev/null @@ -1,12 +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.Skinning -{ - /// - /// Marker interface for skins that originate from beatmaps. - /// - public interface IBeatmapSkin : ISkin - { - } -} diff --git a/osu.Game/Skinning/LegacyBeatmapSkin.cs b/osu.Game/Skinning/LegacyBeatmapSkin.cs index d53349dd11..d647bc4a2d 100644 --- a/osu.Game/Skinning/LegacyBeatmapSkin.cs +++ b/osu.Game/Skinning/LegacyBeatmapSkin.cs @@ -11,7 +11,7 @@ using osu.Game.Rulesets.Objects.Legacy; namespace osu.Game.Skinning { - public class LegacyBeatmapSkin : LegacySkin, IBeatmapSkin + public class LegacyBeatmapSkin : LegacySkin { protected override bool AllowManiaSkin => false; protected override bool UseCustomSampleBanks => true; diff --git a/osu.Game/Tests/Beatmaps/HitObjectSampleTest.cs b/osu.Game/Tests/Beatmaps/HitObjectSampleTest.cs index db080d889f..ab4fb38657 100644 --- a/osu.Game/Tests/Beatmaps/HitObjectSampleTest.cs +++ b/osu.Game/Tests/Beatmaps/HitObjectSampleTest.cs @@ -188,7 +188,7 @@ namespace osu.Game.Tests.Beatmaps this.resourceStore = resourceStore; } - protected override IBeatmapSkin GetSkin() => new LegacyBeatmapSkin(skinBeatmapInfo, resourceStore, AudioManager); + protected override ISkin GetSkin() => new LegacyBeatmapSkin(skinBeatmapInfo, resourceStore, AudioManager); } } } From 2e2f26449d1304e6bcd0af00a7aa2e130bb9919d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 31 Aug 2020 19:23:19 +0200 Subject: [PATCH 0812/1782] Change anchoring to TopRight --- 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 2b2c40411d..44d7d0f765 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedge.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedge.cs @@ -227,13 +227,13 @@ namespace osu.Game.Screens.Select { createStarRatingDisplay(beatmapInfo).With(display => { - display.Anchor = Anchor.CentreRight; - display.Origin = Anchor.CentreRight; + display.Anchor = Anchor.TopRight; + display.Origin = Anchor.TopRight; }), StatusPill = new BeatmapSetOnlineStatusPill { - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, TextSize = 11, TextPadding = new MarginPadding { Horizontal = 8, Vertical = 2 }, Status = beatmapInfo.Status, From 876fd21230a401d0d75877a3d681d167c2f38a9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 31 Aug 2020 19:31:47 +0200 Subject: [PATCH 0813/1782] Apply shear to right-anchored items --- osu.Game/Screens/Select/BeatmapInfoWedge.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Screens/Select/BeatmapInfoWedge.cs b/osu.Game/Screens/Select/BeatmapInfoWedge.cs index 44d7d0f765..ad977c70b5 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedge.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedge.cs @@ -223,17 +223,20 @@ namespace osu.Game.Screens.Select Direction = FillDirection.Vertical, Padding = new MarginPadding { Top = 14, Right = shear_width / 2 }, AutoSizeAxes = Axes.Both, + Shear = wedged_container_shear, Children = new[] { createStarRatingDisplay(beatmapInfo).With(display => { display.Anchor = Anchor.TopRight; display.Origin = Anchor.TopRight; + display.Shear = -wedged_container_shear; }), StatusPill = new BeatmapSetOnlineStatusPill { Anchor = Anchor.TopRight, Origin = Anchor.TopRight, + Shear = -wedged_container_shear, TextSize = 11, TextPadding = new MarginPadding { Horizontal = 8, Vertical = 2 }, Status = beatmapInfo.Status, From c8aa197e5b472f9b3389382106253d0eeea61cb0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 1 Sep 2020 11:36:18 +0900 Subject: [PATCH 0814/1782] 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 1a76a24496..d4a6d6759e 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,6 +52,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index d1e2033596..5cc2f61e86 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -24,7 +24,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 9b25eaab41..e7addc1c2c 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -80,7 +80,7 @@ - + From d45a1521a1e6441ec47f391c2575c5ac79239fb8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 1 Sep 2020 11:56:23 +0900 Subject: [PATCH 0815/1782] Update BindableList usages --- osu.Game/Overlays/ChatOverlay.cs | 63 ++++++++++--------- .../Sections/Graphics/LayoutSettings.cs | 3 +- .../Screens/Edit/Timing/ControlPointTable.cs | 3 +- osu.Game/Screens/Edit/Timing/TimingScreen.cs | 3 +- .../Multi/Lounge/Components/RoomsContainer.cs | 18 +++++- 5 files changed, 53 insertions(+), 37 deletions(-) diff --git a/osu.Game/Overlays/ChatOverlay.cs b/osu.Game/Overlays/ChatOverlay.cs index 5ba55f6d45..692175603c 100644 --- a/osu.Game/Overlays/ChatOverlay.cs +++ b/osu.Game/Overlays/ChatOverlay.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using System.Collections.Specialized; using System.Linq; using osuTK; using osuTK.Graphics; @@ -218,14 +219,13 @@ namespace osu.Game.Overlays Schedule(() => { // TODO: consider scheduling bindable callbacks to not perform when overlay is not present. - channelManager.JoinedChannels.ItemsAdded += onChannelAddedToJoinedChannels; - channelManager.JoinedChannels.ItemsRemoved += onChannelRemovedFromJoinedChannels; + channelManager.JoinedChannels.CollectionChanged += joinedChannelsChanged; + foreach (Channel channel in channelManager.JoinedChannels) ChannelTabControl.AddChannel(channel); - channelManager.AvailableChannels.ItemsAdded += availableChannelsChanged; - channelManager.AvailableChannels.ItemsRemoved += availableChannelsChanged; - ChannelSelectionOverlay.UpdateAvailableChannels(channelManager.AvailableChannels); + channelManager.AvailableChannels.CollectionChanged += availableChannelsChanged; + availableChannelsChanged(null, null); currentChannel = channelManager.CurrentChannel.GetBoundCopy(); currentChannel.BindValueChanged(currentChannelChanged, true); @@ -384,34 +384,41 @@ namespace osu.Game.Overlays base.PopOut(); } - private void onChannelAddedToJoinedChannels(IEnumerable channels) + private void joinedChannelsChanged(object sender, NotifyCollectionChangedEventArgs args) { - foreach (Channel channel in channels) - ChannelTabControl.AddChannel(channel); - } - - private void onChannelRemovedFromJoinedChannels(IEnumerable channels) - { - foreach (Channel channel in channels) + switch (args.Action) { - ChannelTabControl.RemoveChannel(channel); + case NotifyCollectionChangedAction.Add: + foreach (Channel channel in args.NewItems.Cast()) + ChannelTabControl.AddChannel(channel); + break; - var loaded = loadedChannels.Find(c => c.Channel == channel); + case NotifyCollectionChangedAction.Remove: + foreach (Channel channel in args.OldItems.Cast()) + { + ChannelTabControl.RemoveChannel(channel); - if (loaded != null) - { - loadedChannels.Remove(loaded); + var loaded = loadedChannels.Find(c => c.Channel == channel); - // 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(); - } + 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(); + } + } + + break; } } - private void availableChannelsChanged(IEnumerable channels) - => ChannelSelectionOverlay.UpdateAvailableChannels(channelManager.AvailableChannels); + private void availableChannelsChanged(object sender, NotifyCollectionChangedEventArgs args) + { + ChannelSelectionOverlay.UpdateAvailableChannels(channelManager.AvailableChannels); + } protected override void Dispose(bool isDisposing) { @@ -420,10 +427,8 @@ namespace osu.Game.Overlays if (channelManager != null) { channelManager.CurrentChannel.ValueChanged -= currentChannelChanged; - channelManager.JoinedChannels.ItemsAdded -= onChannelAddedToJoinedChannels; - channelManager.JoinedChannels.ItemsRemoved -= onChannelRemovedFromJoinedChannels; - channelManager.AvailableChannels.ItemsAdded -= availableChannelsChanged; - channelManager.AvailableChannels.ItemsRemoved -= availableChannelsChanged; + channelManager.JoinedChannels.CollectionChanged -= joinedChannelsChanged; + channelManager.AvailableChannels.CollectionChanged -= availableChannelsChanged; } } diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs index 00b7643332..4312b319c0 100644 --- a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs @@ -163,8 +163,7 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics scalingSettings.ForEach(s => s.TransferValueOnCommit = mode.NewValue == ScalingMode.Everything); }, true); - windowModes.ItemsAdded += _ => windowModesChanged(); - windowModes.ItemsRemoved += _ => windowModesChanged(); + windowModes.CollectionChanged += (sender, args) => windowModesChanged(); windowModesChanged(); } diff --git a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs index 5c59cfbfe8..c0c0bcead2 100644 --- a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs +++ b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs @@ -112,8 +112,7 @@ namespace osu.Game.Screens.Edit.Timing }; controlPoints = group.ControlPoints.GetBoundCopy(); - controlPoints.ItemsAdded += _ => createChildren(); - controlPoints.ItemsRemoved += _ => createChildren(); + controlPoints.CollectionChanged += (_, __) => createChildren(); createChildren(); } diff --git a/osu.Game/Screens/Edit/Timing/TimingScreen.cs b/osu.Game/Screens/Edit/Timing/TimingScreen.cs index a08a660e7e..8c40c8e721 100644 --- a/osu.Game/Screens/Edit/Timing/TimingScreen.cs +++ b/osu.Game/Screens/Edit/Timing/TimingScreen.cs @@ -124,8 +124,7 @@ namespace osu.Game.Screens.Edit.Timing selectedGroup.BindValueChanged(selected => { deleteButton.Enabled.Value = selected.NewValue != null; }, true); controlGroups = Beatmap.Value.Beatmap.ControlPointInfo.Groups.GetBoundCopy(); - controlGroups.ItemsAdded += _ => createContent(); - controlGroups.ItemsRemoved += _ => createContent(); + controlGroups.CollectionChanged += (sender, args) => createContent(); createContent(); } diff --git a/osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs b/osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs index 447c99039a..321d7b0a19 100644 --- a/osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs +++ b/osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Collections.Specialized; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -53,8 +54,7 @@ namespace osu.Game.Screens.Multi.Lounge.Components protected override void LoadComplete() { - rooms.ItemsAdded += addRooms; - rooms.ItemsRemoved += removeRooms; + rooms.CollectionChanged += roomsChanged; roomManager.RoomsUpdated += updateSorting; rooms.BindTo(roomManager.Rooms); @@ -82,6 +82,20 @@ namespace osu.Game.Screens.Multi.Lounge.Components }); } + private void roomsChanged(object sender, NotifyCollectionChangedEventArgs args) + { + switch (args.Action) + { + case NotifyCollectionChangedAction.Add: + addRooms(args.NewItems.Cast()); + break; + + case NotifyCollectionChangedAction.Remove: + removeRooms(args.OldItems.Cast()); + break; + } + } + private void addRooms(IEnumerable rooms) { foreach (var room in rooms) From d1f79a6a488a9b8f07f88e35d7b96ac71192a458 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 24 Aug 2020 20:00:24 +0900 Subject: [PATCH 0816/1782] Fix potentially incorrect zoom level getting set on very short audio track --- osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs index 717d60b4f3..ce2954f301 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs @@ -68,7 +68,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline }, true); } - private float getZoomLevelForVisibleMilliseconds(double milliseconds) => (float)(track.Length / milliseconds); + private float getZoomLevelForVisibleMilliseconds(double milliseconds) => Math.Max(1, (float)(track.Length / milliseconds)); /// /// The timeline's scroll position in the last frame. From 9e3b809cab6f61489f90379327928768778672fb Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 1 Sep 2020 15:42:47 +0900 Subject: [PATCH 0817/1782] Rename to user_score to match API --- osu.Game/Online/Multiplayer/APILeaderboard.cs | 4 ++-- osu.Game/Screens/Multi/Match/Components/MatchLeaderboard.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Online/Multiplayer/APILeaderboard.cs b/osu.Game/Online/Multiplayer/APILeaderboard.cs index 96fe7cefb0..65863d6e0e 100644 --- a/osu.Game/Online/Multiplayer/APILeaderboard.cs +++ b/osu.Game/Online/Multiplayer/APILeaderboard.cs @@ -12,7 +12,7 @@ namespace osu.Game.Online.Multiplayer [JsonProperty("leaderboard")] public List Leaderboard; - [JsonProperty("own_score")] - public APIUserScoreAggregate OwnScore; + [JsonProperty("user_score")] + public APIUserScoreAggregate UserScore; } } diff --git a/osu.Game/Screens/Multi/Match/Components/MatchLeaderboard.cs b/osu.Game/Screens/Multi/Match/Components/MatchLeaderboard.cs index 50afbb39fe..5bf61eb4ee 100644 --- a/osu.Game/Screens/Multi/Match/Components/MatchLeaderboard.cs +++ b/osu.Game/Screens/Multi/Match/Components/MatchLeaderboard.cs @@ -42,7 +42,7 @@ namespace osu.Game.Screens.Multi.Match.Components req.Success += r => { scoresCallback?.Invoke(r.Leaderboard); - TopScore = r.OwnScore; + TopScore = r.UserScore; }; return req; From 26b4226b5538c9f95657fdc985b9a873c763b4f1 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 1 Sep 2020 16:55:10 +0900 Subject: [PATCH 0818/1782] Fix ModTimeRamp not working --- osu.Game/Screens/Play/Player.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 9be4fd6a65..07be482529 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -52,6 +52,9 @@ namespace osu.Game.Screens.Play public override OverlayActivation InitialOverlayActivationMode => OverlayActivation.UserTriggered; + // We are managing our own adjustments (see OnEntering/OnExiting). + public override bool AllowRateAdjustments => false; + /// /// Whether gameplay should pause when the game window focus is lost. /// @@ -627,6 +630,10 @@ namespace osu.Game.Screens.Play foreach (var mod in Mods.Value.OfType()) mod.ApplyToHUD(HUDOverlay); + + Beatmap.Value.Track.ResetSpeedAdjustments(); + foreach (var mod in Mods.Value.OfType()) + mod.ApplyToTrack(Beatmap.Value.Track); } public override void OnSuspending(IScreen next) @@ -660,6 +667,8 @@ namespace osu.Game.Screens.Play // as we are no longer the current screen, we cannot guarantee the track is still usable. GameplayClockContainer?.StopUsingBeatmapClock(); + Beatmap.Value.Track.ResetSpeedAdjustments(); + fadeOut(); return base.OnExiting(next); } From 7e1844ed773368a4b932ea0e2d7fc87fa0fc53b4 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 1 Sep 2020 18:07:19 +0900 Subject: [PATCH 0819/1782] Fix track adjusments being reset incorrectly --- osu.Game/Screens/Play/Player.cs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 07be482529..82c446f5e4 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -80,6 +80,9 @@ namespace osu.Game.Screens.Play [Resolved] private IAPIProvider api { get; set; } + [Resolved] + private MusicController musicController { get; set; } + private SampleChannel sampleRestart; public BreakOverlay BreakOverlay; @@ -631,9 +634,12 @@ namespace osu.Game.Screens.Play foreach (var mod in Mods.Value.OfType()) mod.ApplyToHUD(HUDOverlay); - Beatmap.Value.Track.ResetSpeedAdjustments(); + // Our mods are local copies of the global mods so they need to be re-applied to the track. + // This is done through the music controller (for now), because resetting speed adjustments on the beatmap track also removes adjustments provided by DrawableTrack. + // Todo: In the future, player will receive in a track and will probably not have to worry about this... + musicController.ResetTrackAdjustments(); foreach (var mod in Mods.Value.OfType()) - mod.ApplyToTrack(Beatmap.Value.Track); + mod.ApplyToTrack(musicController.CurrentTrack); } public override void OnSuspending(IScreen next) @@ -667,7 +673,7 @@ namespace osu.Game.Screens.Play // as we are no longer the current screen, we cannot guarantee the track is still usable. GameplayClockContainer?.StopUsingBeatmapClock(); - Beatmap.Value.Track.ResetSpeedAdjustments(); + musicController.ResetTrackAdjustments(); fadeOut(); return base.OnExiting(next); From e4cb7eb964b6b5b7e1072c0a1efa9d613351da59 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 1 Sep 2020 17:28:41 +0900 Subject: [PATCH 0820/1782] Initial structure --- osu.Game/Collections/CollectionManager.cs | 93 +++++++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 osu.Game/Collections/CollectionManager.cs diff --git a/osu.Game/Collections/CollectionManager.cs b/osu.Game/Collections/CollectionManager.cs new file mode 100644 index 0000000000..1058e7b5b8 --- /dev/null +++ b/osu.Game/Collections/CollectionManager.cs @@ -0,0 +1,93 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Threading.Tasks; +using osu.Framework.Logging; +using osu.Framework.Platform; +using osu.Game.Beatmaps; +using osu.Game.IO.Legacy; + +namespace osu.Game.Collections +{ + public class CollectionManager + { + private const string import_from_stable_path = "collection.db"; + + private readonly BeatmapManager beatmaps; + + public CollectionManager(BeatmapManager beatmaps) + { + this.beatmaps = beatmaps; + } + + /// + /// Set a storage with access to an osu-stable install for import purposes. + /// + public Func GetStableStorage { private get; set; } + + /// + /// This is a temporary method and will likely be replaced by a full-fledged (and more correctly placed) migration process in the future. + /// + public Task ImportFromStableAsync() + { + var stable = GetStableStorage?.Invoke(); + + if (stable == null) + { + Logger.Log("No osu!stable installation available!", LoggingTarget.Information, LogLevel.Error); + return Task.CompletedTask; + } + + if (!stable.ExistsDirectory(import_from_stable_path)) + { + // This handles situations like when the user does not have a Skins folder + Logger.Log($"No {import_from_stable_path} folder available in osu!stable installation", LoggingTarget.Information, LogLevel.Error); + return Task.CompletedTask; + } + + return Task.Run(async () => await Import(GetStableImportPaths(GetStableStorage()).Select(f => stable.GetFullPath(f)).ToArray())); + } + + private List readCollection(Stream stream) + { + var result = new List(); + + using (var reader = new SerializationReader(stream)) + { + reader.ReadInt32(); // Version + + int collectionCount = reader.ReadInt32(); + result.Capacity = collectionCount; + + for (int i = 0; i < collectionCount; i++) + { + var collection = new BeatmapCollection { Name = reader.ReadString() }; + + int mapCount = reader.ReadInt32(); + collection.Beatmaps.Capacity = mapCount; + + for (int j = 0; j < mapCount; j++) + { + string checksum = reader.ReadString(); + + var beatmap = beatmaps.QueryBeatmap(b => b.MD5Hash == checksum); + if (beatmap != null) + collection.Beatmaps.Add(beatmap); + } + } + } + + return result; + } + } + + public class BeatmapCollection + { + public string Name; + + public readonly List Beatmaps = new List(); + } +} From 78648cb90d409878171b9b9cc500a4d9adae28cc Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 1 Sep 2020 19:33:06 +0900 Subject: [PATCH 0821/1782] Add reading from local file --- osu.Game/Collections/CollectionManager.cs | 65 +++++++++++++---------- osu.Game/OsuGameBase.cs | 7 +++ 2 files changed, 45 insertions(+), 27 deletions(-) diff --git a/osu.Game/Collections/CollectionManager.cs b/osu.Game/Collections/CollectionManager.cs index 1058e7b5b8..302d892efb 100644 --- a/osu.Game/Collections/CollectionManager.cs +++ b/osu.Game/Collections/CollectionManager.cs @@ -4,23 +4,33 @@ using System; using System.Collections.Generic; using System.IO; -using System.Threading.Tasks; -using osu.Framework.Logging; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics.Containers; using osu.Framework.Platform; using osu.Game.Beatmaps; using osu.Game.IO.Legacy; namespace osu.Game.Collections { - public class CollectionManager + public class CollectionManager : CompositeDrawable { - private const string import_from_stable_path = "collection.db"; + private const string database_name = "collection.db"; - private readonly BeatmapManager beatmaps; + public IBindableList Collections => collections; + private readonly BindableList collections = new BindableList(); - public CollectionManager(BeatmapManager beatmaps) + [Resolved] + private BeatmapManager beatmaps { get; set; } + + [BackgroundDependencyLoader] + private void load(GameHost host) { - this.beatmaps = beatmaps; + if (host.Storage.Exists(database_name)) + { + using (var stream = host.Storage.GetStream(database_name)) + collections.AddRange(readCollection(stream)); + } } /// @@ -31,26 +41,25 @@ namespace osu.Game.Collections /// /// This is a temporary method and will likely be replaced by a full-fledged (and more correctly placed) migration process in the future. /// - public Task ImportFromStableAsync() - { - var stable = GetStableStorage?.Invoke(); - - if (stable == null) - { - Logger.Log("No osu!stable installation available!", LoggingTarget.Information, LogLevel.Error); - return Task.CompletedTask; - } - - if (!stable.ExistsDirectory(import_from_stable_path)) - { - // This handles situations like when the user does not have a Skins folder - Logger.Log($"No {import_from_stable_path} folder available in osu!stable installation", LoggingTarget.Information, LogLevel.Error); - return Task.CompletedTask; - } - - return Task.Run(async () => await Import(GetStableImportPaths(GetStableStorage()).Select(f => stable.GetFullPath(f)).ToArray())); - } - + // public Task ImportFromStableAsync() + // { + // var stable = GetStableStorage?.Invoke(); + // + // if (stable == null) + // { + // Logger.Log("No osu!stable installation available!", LoggingTarget.Information, LogLevel.Error); + // return Task.CompletedTask; + // } + // + // if (!stable.ExistsDirectory(database_name)) + // { + // // This handles situations like when the user does not have a Skins folder + // Logger.Log($"No {database_name} folder available in osu!stable installation", LoggingTarget.Information, LogLevel.Error); + // return Task.CompletedTask; + // } + // + // return Task.Run(async () => await Import(GetStableImportPaths(GetStableStorage()).Select(f => stable.GetFullPath(f)).ToArray())); + // } private List readCollection(Stream stream) { var result = new List(); @@ -77,6 +86,8 @@ namespace osu.Game.Collections if (beatmap != null) collection.Beatmaps.Add(beatmap); } + + result.Add(collection); } } diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 98f60d52d3..3ba164e87f 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -26,6 +26,7 @@ using osu.Framework.Graphics.Textures; using osu.Framework.Input; using osu.Framework.Logging; using osu.Game.Audio; +using osu.Game.Collections; using osu.Game.Database; using osu.Game.Input; using osu.Game.Input.Bindings; @@ -55,6 +56,8 @@ namespace osu.Game protected BeatmapManager BeatmapManager; + protected CollectionManager CollectionManager; + protected ScoreManager ScoreManager; protected SkinManager SkinManager; @@ -222,6 +225,10 @@ namespace osu.Game dependencies.Cache(difficultyManager); AddInternal(difficultyManager); + var collectionManager = new CollectionManager(); + dependencies.Cache(collectionManager); + AddInternal(collectionManager); + dependencies.Cache(KeyBindingStore = new KeyBindingStore(contextFactory, RulesetStore)); dependencies.Cache(SettingsStore = new SettingsStore(contextFactory)); dependencies.Cache(RulesetConfigCache = new RulesetConfigCache(SettingsStore)); From c2ade44656c3ea7d397d39652c77a70056d8fe1c Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Tue, 1 Sep 2020 17:58:06 +0200 Subject: [PATCH 0822/1782] Change types back --- osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs | 6 +++--- osu.Game/Beatmaps/BeatmapManager.cs | 2 +- osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs | 4 ++-- osu.Game/Screens/Edit/EditorBeatmap.cs | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs index bcc5970a27..613db79242 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs @@ -57,7 +57,7 @@ namespace osu.Game.Tests.Beatmaps.Formats return a.ComboColours.SequenceEqual(b.ComboColours); } - private void sort((IBeatmap beatmap, IBeatmapSkin beatmapSkin) tuple) + private void sort((IBeatmap beatmap, ISkin beatmapSkin) tuple) { // Sort control points to ensure a sane ordering, as they may be parsed in different orders. This works because each group contains only uniquely-typed control points. foreach (var g in tuple.beatmap.ControlPointInfo.Groups) @@ -77,7 +77,7 @@ namespace osu.Game.Tests.Beatmaps.Formats } } - private class TestLegacySkin : LegacySkin, IBeatmapSkin + private class TestLegacySkin : LegacySkin, ISkin { public TestLegacySkin(IResourceStore storage, string fileName) : base(new SkinInfo { Name = "Test Skin", Creator = "Craftplacer" }, storage, null, fileName) @@ -85,7 +85,7 @@ namespace osu.Game.Tests.Beatmaps.Formats } } - private Stream encodeToLegacy((IBeatmap beatmap, IBeatmapSkin beatmapSkin) fullBeatmap) + private Stream encodeToLegacy((IBeatmap beatmap, ISkin beatmapSkin) fullBeatmap) { var (beatmap, beatmapSkin) = fullBeatmap; var stream = new MemoryStream(); diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index f725d55970..a96af68714 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -196,7 +196,7 @@ namespace osu.Game.Beatmaps /// The to save the content against. The file referenced by will be replaced. /// The content to write. /// The beatmap content to write, null if to be omitted. - public void Save(BeatmapInfo info, IBeatmap beatmapContent, IBeatmapSkin beatmapSkin = null) + public void Save(BeatmapInfo info, IBeatmap beatmapContent, ISkin beatmapSkin = null) { var setInfo = QueryBeatmapSet(s => s.Beatmaps.Any(b => b.ID == info.ID)); diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs index 53ce1c831c..80a4d6dea4 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs @@ -27,14 +27,14 @@ namespace osu.Game.Beatmaps.Formats private readonly IBeatmap beatmap; [CanBeNull] - private readonly IBeatmapSkin skin; + private readonly ISkin skin; /// /// Creates a new . /// /// The beatmap to encode. /// The beatmap's skin, used for encoding combo colours. - public LegacyBeatmapEncoder(IBeatmap beatmap, [CanBeNull] IBeatmapSkin skin) + public LegacyBeatmapEncoder(IBeatmap beatmap, [CanBeNull] ISkin skin) { this.beatmap = beatmap; this.skin = skin; diff --git a/osu.Game/Screens/Edit/EditorBeatmap.cs b/osu.Game/Screens/Edit/EditorBeatmap.cs index a314d50e60..061009e519 100644 --- a/osu.Game/Screens/Edit/EditorBeatmap.cs +++ b/osu.Game/Screens/Edit/EditorBeatmap.cs @@ -48,7 +48,7 @@ namespace osu.Game.Screens.Edit public readonly IBeatmap PlayableBeatmap; - public readonly IBeatmapSkin BeatmapSkin; + public readonly ISkin BeatmapSkin; [Resolved] private BindableBeatDivisor beatDivisor { get; set; } @@ -57,7 +57,7 @@ namespace osu.Game.Screens.Edit private readonly Dictionary> startTimeBindables = new Dictionary>(); - public EditorBeatmap(IBeatmap playableBeatmap, IBeatmapSkin beatmapSkin = null) + public EditorBeatmap(IBeatmap playableBeatmap, ISkin beatmapSkin = null) { PlayableBeatmap = playableBeatmap; BeatmapSkin = beatmapSkin; From 2a7259f7aa76d1dac116af9b6d0f016ab15db2bb Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Tue, 1 Sep 2020 18:15:46 +0200 Subject: [PATCH 0823/1782] Update LegacyBeatmapEncoderTest.cs --- osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs index 613db79242..6e103af3f0 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs @@ -77,7 +77,7 @@ namespace osu.Game.Tests.Beatmaps.Formats } } - private class TestLegacySkin : LegacySkin, ISkin + private class TestLegacySkin : LegacySkin { public TestLegacySkin(IResourceStore storage, string fileName) : base(new SkinInfo { Name = "Test Skin", Creator = "Craftplacer" }, storage, null, fileName) From ba8a4eb6f0ffd493b346701a8d1886796f6736c5 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 29 Aug 2020 23:14:29 +0300 Subject: [PATCH 0824/1782] Move osu!catch combo counter display to inside CatcherArea --- osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs | 19 ++----------------- osu.Game.Rulesets.Catch/UI/CatcherArea.cs | 17 ++++++++++++++++- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs index d4a1740c12..409ea6dbc6 100644 --- a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs @@ -28,7 +28,8 @@ namespace osu.Game.Rulesets.Catch.UI public const float CENTER_X = WIDTH / 2; internal readonly CatcherArea CatcherArea; - private readonly CatchComboDisplay comboDisplay; + + private CatchComboDisplay comboDisplay => CatcherArea.ComboDisplay; public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => // only check the X position; handle all vertical space. @@ -49,22 +50,12 @@ namespace osu.Game.Rulesets.Catch.UI Origin = Anchor.TopLeft, }; - comboDisplay = new CatchComboDisplay - { - RelativeSizeAxes = Axes.None, - AutoSizeAxes = Axes.Both, - Anchor = Anchor.CentreLeft, - Origin = Anchor.Centre, - Y = 30f, - }; - InternalChildren = new[] { explodingFruitContainer, CatcherArea.MovableCatcher.CreateProxiedContent(), HitObjectContainer, CatcherArea, - comboDisplay, }; } @@ -81,12 +72,6 @@ namespace osu.Game.Rulesets.Catch.UI fruit.CheckPosition = CheckIfWeCanCatch; } - protected override void Update() - { - base.Update(); - comboDisplay.X = CatcherArea.MovableCatcher.X; - } - private void onNewResult(DrawableHitObject judgedObject, JudgementResult result) { var catchObject = (DrawableCatchHitObject)judgedObject; diff --git a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs index 03ebf01b9b..9cfb9f41d7 100644 --- a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs +++ b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs @@ -23,6 +23,7 @@ namespace osu.Game.Rulesets.Catch.UI public Func> CreateDrawableRepresentation; public readonly Catcher MovableCatcher; + internal readonly CatchComboDisplay ComboDisplay; public Container ExplodingFruitTarget { @@ -34,7 +35,19 @@ namespace osu.Game.Rulesets.Catch.UI public CatcherArea(BeatmapDifficulty difficulty = null) { Size = new Vector2(CatchPlayfield.WIDTH, CATCHER_SIZE); - Child = MovableCatcher = new Catcher(this, difficulty) { X = CatchPlayfield.CENTER_X }; + Children = new Drawable[] + { + ComboDisplay = new CatchComboDisplay + { + RelativeSizeAxes = Axes.None, + AutoSizeAxes = Axes.Both, + Anchor = Anchor.TopLeft, + Origin = Anchor.Centre, + Margin = new MarginPadding { Bottom = 350f }, + X = CatchPlayfield.CENTER_X + }, + MovableCatcher = new Catcher(this, difficulty) { X = CatchPlayfield.CENTER_X }, + }; } public void OnResult(DrawableCatchHitObject fruit, JudgementResult result) @@ -105,6 +118,8 @@ namespace osu.Game.Rulesets.Catch.UI if (state?.CatcherX != null) MovableCatcher.X = state.CatcherX.Value; + + ComboDisplay.X = MovableCatcher.X; } } } From a0a45010080308898c088430c8aa3e66db4ce98d Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 29 Aug 2020 23:23:36 +0300 Subject: [PATCH 0825/1782] Merge remote-tracking branch 'upstream/master' into catch-combo-counter --- .../Mods/TestSceneCatchModRelax.cs | 84 ++++++++++++ .../TestSceneHyperDash.cs | 42 ++++-- .../Beatmaps/CatchBeatmapProcessor.cs | 6 + osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs | 2 +- .../Skinning/CatchLegacySkinTransformer.cs | 8 +- .../Skinning/LegacyFruitPiece.cs | 3 +- osu.Game.Rulesets.Catch/UI/Catcher.cs | 9 +- .../UI/CatcherTrailDisplay.cs | 8 +- .../Skinning/ColumnTestContainer.cs | 24 ++-- .../Skinning/ManiaHitObjectTestScene.cs | 4 +- .../Skinning/TestSceneStageBackground.cs | 4 +- .../Skinning/TestSceneStageForeground.cs | 3 +- .../Beatmaps/StageDefinition.cs | 4 +- osu.Game.Rulesets.Mania/ManiaRuleset.cs | 10 ++ osu.Game.Rulesets.Mania/ManiaSkinComponent.cs | 11 +- .../Objects/Drawables/DrawableHoldNote.cs | 92 ++++++++++++- .../Objects/Drawables/DrawableHoldNoteHead.cs | 8 ++ .../Drawables/DrawableManiaHitObject.cs | 2 +- .../Skinning/HitTargetInsetContainer.cs | 46 +++++++ .../Skinning/LegacyBodyPiece.cs | 109 ++++++++++++++-- .../Skinning/LegacyColumnBackground.cs | 65 +-------- .../Skinning/LegacyHitTarget.cs | 7 +- .../Skinning/LegacyKeyArea.cs | 3 + .../Skinning/LegacyStageBackground.cs | 88 ++++++++++++- .../Skinning/ManiaLegacySkinTransformer.cs | 10 +- osu.Game.Rulesets.Mania/UI/Column.cs | 2 - osu.Game.Rulesets.Mania/UI/ColumnFlow.cs | 105 +++++++++++++++ osu.Game.Rulesets.Mania/UI/Stage.cs | 69 ++-------- osu.Game.Rulesets.Osu/OsuRuleset.cs | 52 +++++--- .../Skinning/LegacyMainCirclePiece.cs | 3 +- .../Skinning/LegacySliderBall.cs | 6 +- .../Skinning/LegacyCirclePiece.cs | 2 +- .../Skinning/LegacyDrumRoll.cs | 8 +- osu.Game.Rulesets.Taiko/Skinning/LegacyHit.cs | 8 +- osu.Game.Rulesets.Taiko/TaikoRuleset.cs | 33 +++-- .../Beatmaps/BeatmapDifficultyManagerTest.cs | 32 +++++ .../Formats/LegacyScoreDecoderTest.cs | 70 ++++++++++ ...tSceneHitObjectComposerDistanceSnapping.cs | 14 +- .../Filtering/FilterQueryParserTest.cs | 16 ++- .../Resources/Replays/mania-replay.osr | Bin 0 -> 1012 bytes .../Resources/skin-zero-alpha-colour.ini | 5 - osu.Game.Tests/Skins/LegacySkinDecoderTest.cs | 10 -- .../TestSceneBeatmapSetOverlaySuccessRate.cs | 28 ++++ .../Ranking/TestSceneSimpleStatisticTable.cs | 68 ++++++++++ osu.Game/Beatmaps/BeatmapDifficultyManager.cs | 14 +- osu.Game/Beatmaps/Formats/LegacyDecoder.cs | 4 - osu.Game/Online/Chat/MessageFormatter.cs | 2 +- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 13 +- .../Rulesets/Edit/IPositionSnapProvider.cs | 1 + osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs | 26 ++-- osu.Game/Screens/Menu/IntroSequence.cs | 1 + .../Ranking/Statistics/SimpleStatisticItem.cs | 81 ++++++++++++ .../Statistics/SimpleStatisticTable.cs | 123 ++++++++++++++++++ .../Ranking/Statistics/StatisticContainer.cs | 60 +++++---- .../Ranking/Statistics/StatisticItem.cs | 2 +- .../Ranking/Statistics/StatisticsPanel.cs | 6 +- .../Ranking/Statistics/UnstableRate.cs | 39 ++++++ osu.Game/Screens/Select/BeatmapDetails.cs | 2 +- .../Screens/Select/Details/FailRetryGraph.cs | 20 ++- osu.Game/Screens/Select/FilterQueryParser.cs | 3 +- .../Skinning/LegacyColourCompatibility.cs | 46 +++++++ .../Skinning/LegacyManiaSkinConfiguration.cs | 3 + .../LegacyManiaSkinConfigurationLookup.cs | 3 + osu.Game/Skinning/LegacyManiaSkinDecoder.cs | 9 ++ osu.Game/Skinning/LegacySkin.cs | 20 +++ 65 files changed, 1352 insertions(+), 309 deletions(-) create mode 100644 osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModRelax.cs create mode 100644 osu.Game.Rulesets.Mania/Skinning/HitTargetInsetContainer.cs create mode 100644 osu.Game.Rulesets.Mania/UI/ColumnFlow.cs create mode 100644 osu.Game.Tests/Beatmaps/BeatmapDifficultyManagerTest.cs create mode 100644 osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs create mode 100644 osu.Game.Tests/Resources/Replays/mania-replay.osr delete mode 100644 osu.Game.Tests/Resources/skin-zero-alpha-colour.ini create mode 100644 osu.Game.Tests/Visual/Ranking/TestSceneSimpleStatisticTable.cs create mode 100644 osu.Game/Screens/Ranking/Statistics/SimpleStatisticItem.cs create mode 100644 osu.Game/Screens/Ranking/Statistics/SimpleStatisticTable.cs create mode 100644 osu.Game/Screens/Ranking/Statistics/UnstableRate.cs create mode 100644 osu.Game/Skinning/LegacyColourCompatibility.cs diff --git a/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModRelax.cs b/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModRelax.cs new file mode 100644 index 0000000000..1eb0975010 --- /dev/null +++ b/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModRelax.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.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using osu.Framework.Testing; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Catch.Mods; +using osu.Game.Rulesets.Catch.Objects; +using osu.Game.Rulesets.Catch.UI; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; +using osu.Game.Tests.Visual; +using osuTK; + +namespace osu.Game.Rulesets.Catch.Tests.Mods +{ + public class TestSceneCatchModRelax : ModTestScene + { + protected override Ruleset CreatePlayerRuleset() => new CatchRuleset(); + + [Test] + public void TestModRelax() => CreateModTest(new ModTestData + { + Mod = new CatchModRelax(), + Autoplay = false, + PassCondition = passCondition, + Beatmap = new Beatmap + { + HitObjects = new List + { + new Fruit + { + X = CatchPlayfield.CENTER_X, + StartTime = 0 + }, + new Fruit + { + X = 0, + StartTime = 250 + }, + new Fruit + { + X = CatchPlayfield.WIDTH, + StartTime = 500 + }, + new JuiceStream + { + X = CatchPlayfield.CENTER_X, + StartTime = 750, + Path = new SliderPath(PathType.Linear, new[] { Vector2.Zero, Vector2.UnitY * 200 }) + } + } + } + }); + + private bool passCondition() + { + var playfield = this.ChildrenOfType().Single(); + + switch (Player.ScoreProcessor.Combo.Value) + { + case 0: + InputManager.MoveMouseTo(playfield.ScreenSpaceDrawQuad.Centre); + break; + + case 1: + InputManager.MoveMouseTo(playfield.ScreenSpaceDrawQuad.BottomLeft); + break; + + case 2: + InputManager.MoveMouseTo(playfield.ScreenSpaceDrawQuad.BottomRight); + break; + + case 3: + InputManager.MoveMouseTo(playfield.ScreenSpaceDrawQuad.Centre); + break; + } + + return Player.ScoreProcessor.Combo.Value >= 6; + } + } +} diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDash.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDash.cs index 6dab2a0b56..db09b2bc6b 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDash.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDash.cs @@ -19,25 +19,43 @@ namespace osu.Game.Rulesets.Catch.Tests { protected override bool Autoplay => true; + private int hyperDashCount; + private bool inHyperDash; + [Test] public void TestHyperDash() { - AddAssert("First note is hyperdash", () => Beatmap.Value.Beatmap.HitObjects[0] is Fruit f && f.HyperDash); - AddUntilStep("wait for right movement", () => getCatcher().Scale.X > 0); // don't check hyperdashing as it happens too fast. - - AddUntilStep("wait for left movement", () => getCatcher().Scale.X < 0); - - for (int i = 0; i < 3; i++) + AddStep("reset count", () => { - AddUntilStep("wait for right hyperdash", () => getCatcher().Scale.X > 0 && getCatcher().HyperDashing); - AddUntilStep("wait for left hyperdash", () => getCatcher().Scale.X < 0 && getCatcher().HyperDashing); + inHyperDash = false; + hyperDashCount = 0; + + // this needs to be done within the frame stable context due to how quickly hyperdash state changes occur. + Player.DrawableRuleset.FrameStableComponents.OnUpdate += d => + { + var catcher = Player.ChildrenOfType().FirstOrDefault()?.MovableCatcher; + + if (catcher == null) + return; + + if (catcher.HyperDashing != inHyperDash) + { + inHyperDash = catcher.HyperDashing; + if (catcher.HyperDashing) + hyperDashCount++; + } + }; + }); + + AddAssert("First note is hyperdash", () => Beatmap.Value.Beatmap.HitObjects[0] is Fruit f && f.HyperDash); + + for (int i = 0; i < 9; i++) + { + int count = i + 1; + AddUntilStep($"wait for hyperdash #{count}", () => hyperDashCount >= count); } - - AddUntilStep("wait for right hyperdash", () => getCatcher().Scale.X > 0 && getCatcher().HyperDashing); } - private Catcher getCatcher() => Player.ChildrenOfType().First().MovableCatcher; - protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) { var beatmap = new Beatmap diff --git a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs index 15e6e98f5a..a08c5b6fb1 100644 --- a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs +++ b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs @@ -212,6 +212,12 @@ namespace osu.Game.Rulesets.Catch.Beatmaps objectWithDroplets.Sort((h1, h2) => h1.StartTime.CompareTo(h2.StartTime)); double halfCatcherWidth = Catcher.CalculateCatchWidth(beatmap.BeatmapInfo.BaseDifficulty) / 2; + + // Todo: This is wrong. osu!stable calculated hyperdashes using the full catcher size, excluding the margins. + // This should theoretically cause impossible scenarios, but practically, likely due to the size of the playfield, it doesn't seem possible. + // For now, to bring gameplay (and diffcalc!) completely in-line with stable, this code also uses the full catcher size. + halfCatcherWidth /= Catcher.ALLOWED_CATCH_RANGE; + int lastDirection = 0; double lastExcess = halfCatcherWidth; diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs b/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs index c1d24395e4..1e42c6a240 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs @@ -52,7 +52,7 @@ namespace osu.Game.Rulesets.Catch.Mods protected override bool OnMouseMove(MouseMoveEvent e) { - catcher.UpdatePosition(e.MousePosition.X / DrawSize.X); + catcher.UpdatePosition(e.MousePosition.X / DrawSize.X * CatchPlayfield.WIDTH); return base.OnMouseMove(e); } } diff --git a/osu.Game.Rulesets.Catch/Skinning/CatchLegacySkinTransformer.cs b/osu.Game.Rulesets.Catch/Skinning/CatchLegacySkinTransformer.cs index 28cd0fb65b..47224bd195 100644 --- a/osu.Game.Rulesets.Catch/Skinning/CatchLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Catch/Skinning/CatchLegacySkinTransformer.cs @@ -6,6 +6,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Skinning; using osuTK; +using osuTK.Graphics; using static osu.Game.Skinning.LegacySkinConfiguration; namespace osu.Game.Rulesets.Catch.Skinning @@ -71,7 +72,12 @@ namespace osu.Game.Rulesets.Catch.Skinning switch (lookup) { case CatchSkinColour colour: - return Source.GetConfig(new SkinCustomColourLookup(colour)); + var result = (Bindable)Source.GetConfig(new SkinCustomColourLookup(colour)); + if (result == null) + return null; + + result.Value = LegacyColourCompatibility.DisallowZeroAlpha(result.Value); + return (IBindable)result; } return Source.GetConfig(lookup); diff --git a/osu.Game.Rulesets.Catch/Skinning/LegacyFruitPiece.cs b/osu.Game.Rulesets.Catch/Skinning/LegacyFruitPiece.cs index 5be54d3882..381d066750 100644 --- a/osu.Game.Rulesets.Catch/Skinning/LegacyFruitPiece.cs +++ b/osu.Game.Rulesets.Catch/Skinning/LegacyFruitPiece.cs @@ -40,7 +40,6 @@ namespace osu.Game.Rulesets.Catch.Skinning colouredSprite = new Sprite { Texture = skin.GetTexture(lookupName), - Colour = drawableObject.AccentColour.Value, Anchor = Anchor.Centre, Origin = Anchor.Centre, }, @@ -76,7 +75,7 @@ namespace osu.Game.Rulesets.Catch.Skinning { base.LoadComplete(); - accentColour.BindValueChanged(colour => colouredSprite.Colour = colour.NewValue, true); + accentColour.BindValueChanged(colour => colouredSprite.Colour = LegacyColourCompatibility.DisallowZeroAlpha(colour.NewValue), true); } } } diff --git a/osu.Game.Rulesets.Catch/UI/Catcher.cs b/osu.Game.Rulesets.Catch/UI/Catcher.cs index 952ff6b0ce..9289a6162c 100644 --- a/osu.Game.Rulesets.Catch/UI/Catcher.cs +++ b/osu.Game.Rulesets.Catch/UI/Catcher.cs @@ -64,7 +64,7 @@ namespace osu.Game.Rulesets.Catch.UI /// /// The width of the catcher which can receive fruit. Equivalent to "catchMargin" in osu-stable. /// - private const float allowed_catch_range = 0.8f; + public const float ALLOWED_CATCH_RANGE = 0.8f; /// /// The drawable catcher for . @@ -166,7 +166,7 @@ namespace osu.Game.Rulesets.Catch.UI /// /// The scale of the catcher. internal static float CalculateCatchWidth(Vector2 scale) - => CatcherArea.CATCHER_SIZE * Math.Abs(scale.X) * allowed_catch_range; + => CatcherArea.CATCHER_SIZE * Math.Abs(scale.X) * ALLOWED_CATCH_RANGE; /// /// Calculates the width of the area used for attempting catches in gameplay. @@ -285,8 +285,6 @@ namespace osu.Game.Rulesets.Catch.UI private void runHyperDashStateTransition(bool hyperDashing) { - trails.HyperDashTrailsColour = hyperDashColour; - trails.EndGlowSpritesColour = hyperDashEndGlowColour; updateTrailVisibility(); if (hyperDashing) @@ -403,6 +401,9 @@ namespace osu.Game.Rulesets.Catch.UI skin.GetConfig(CatchSkinColour.HyperDashAfterImage)?.Value ?? hyperDashColour; + trails.HyperDashTrailsColour = hyperDashColour; + trails.EndGlowSpritesColour = hyperDashEndGlowColour; + runHyperDashStateTransition(HyperDashing); } diff --git a/osu.Game.Rulesets.Catch/UI/CatcherTrailDisplay.cs b/osu.Game.Rulesets.Catch/UI/CatcherTrailDisplay.cs index bab3cb748b..f7e9fd19a7 100644 --- a/osu.Game.Rulesets.Catch/UI/CatcherTrailDisplay.cs +++ b/osu.Game.Rulesets.Catch/UI/CatcherTrailDisplay.cs @@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Catch.UI private readonly Container hyperDashTrails; private readonly Container endGlowSprites; - private Color4 hyperDashTrailsColour; + private Color4 hyperDashTrailsColour = Catcher.DEFAULT_HYPER_DASH_COLOUR; public Color4 HyperDashTrailsColour { @@ -35,11 +35,11 @@ namespace osu.Game.Rulesets.Catch.UI return; hyperDashTrailsColour = value; - hyperDashTrails.FadeColour(hyperDashTrailsColour, Catcher.HYPER_DASH_TRANSITION_DURATION, Easing.OutQuint); + hyperDashTrails.Colour = hyperDashTrailsColour; } } - private Color4 endGlowSpritesColour; + private Color4 endGlowSpritesColour = Catcher.DEFAULT_HYPER_DASH_COLOUR; public Color4 EndGlowSpritesColour { @@ -50,7 +50,7 @@ namespace osu.Game.Rulesets.Catch.UI return; endGlowSpritesColour = value; - endGlowSprites.FadeColour(endGlowSpritesColour, Catcher.HYPER_DASH_TRANSITION_DURATION, Easing.OutQuint); + endGlowSprites.Colour = endGlowSpritesColour; } } diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/ColumnTestContainer.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/ColumnTestContainer.cs index ff4865c71d..8ba58e3af3 100644 --- a/osu.Game.Rulesets.Mania.Tests/Skinning/ColumnTestContainer.cs +++ b/osu.Game.Rulesets.Mania.Tests/Skinning/ColumnTestContainer.cs @@ -22,18 +22,22 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning [Cached] private readonly Column column; - public ColumnTestContainer(int column, ManiaAction action) + public ColumnTestContainer(int column, ManiaAction action, bool showColumn = false) { - this.column = new Column(column) + InternalChildren = new[] { - Action = { Value = action }, - AccentColour = Color4.Orange, - ColumnType = column % 2 == 0 ? ColumnType.Even : ColumnType.Odd - }; - - InternalChild = content = new ManiaInputManager(new ManiaRuleset().RulesetInfo, 4) - { - RelativeSizeAxes = Axes.Both + this.column = new Column(column) + { + Action = { Value = action }, + AccentColour = Color4.Orange, + ColumnType = column % 2 == 0 ? ColumnType.Even : ColumnType.Odd, + Alpha = showColumn ? 1 : 0 + }, + content = new ManiaInputManager(new ManiaRuleset().RulesetInfo, 4) + { + RelativeSizeAxes = Axes.Both + }, + this.column.TopLevelContainer.CreateProxy() }; } } diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaHitObjectTestScene.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaHitObjectTestScene.cs index 18eebada00..d24c81dac6 100644 --- a/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaHitObjectTestScene.cs +++ b/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaHitObjectTestScene.cs @@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning Direction = FillDirection.Horizontal, Children = new Drawable[] { - new ColumnTestContainer(0, ManiaAction.Key1) + new ColumnTestContainer(0, ManiaAction.Key1, true) { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -45,7 +45,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning })); }) }, - new ColumnTestContainer(1, ManiaAction.Key2) + new ColumnTestContainer(1, ManiaAction.Key2, true) { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneStageBackground.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneStageBackground.cs index 87c84cf89c..a15fb392d6 100644 --- a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneStageBackground.cs +++ b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneStageBackground.cs @@ -3,6 +3,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.UI.Components; using osu.Game.Skinning; @@ -13,7 +14,8 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning [BackgroundDependencyLoader] private void load() { - SetContents(() => new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.StageBackground), _ => new DefaultStageBackground()) + SetContents(() => new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.StageBackground, stageDefinition: new StageDefinition { Columns = 4 }), + _ => new DefaultStageBackground()) { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneStageForeground.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneStageForeground.cs index 4e99068ed5..bceee1c599 100644 --- a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneStageForeground.cs +++ b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneStageForeground.cs @@ -3,6 +3,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Skinning; namespace osu.Game.Rulesets.Mania.Tests.Skinning @@ -12,7 +13,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning [BackgroundDependencyLoader] private void load() { - SetContents(() => new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.StageForeground), _ => null) + SetContents(() => new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.StageForeground, stageDefinition: new StageDefinition { Columns = 4 }), _ => null) { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game.Rulesets.Mania/Beatmaps/StageDefinition.cs b/osu.Game.Rulesets.Mania/Beatmaps/StageDefinition.cs index 2557f2acdf..3052fc7d34 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/StageDefinition.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/StageDefinition.cs @@ -21,14 +21,14 @@ namespace osu.Game.Rulesets.Mania.Beatmaps /// /// The 0-based column index. /// Whether the column is a special column. - public bool IsSpecialColumn(int column) => Columns % 2 == 1 && column == Columns / 2; + public readonly bool IsSpecialColumn(int column) => Columns % 2 == 1 && column == Columns / 2; /// /// Get the type of column given a column index. /// /// The 0-based column index. /// The type of the column. - public ColumnType GetTypeOfColumn(int column) + public readonly ColumnType GetTypeOfColumn(int column) { if (IsSpecialColumn(column)) return ColumnType.Special; diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index 2795868c97..f7098faa5d 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -326,6 +326,16 @@ namespace osu.Game.Rulesets.Mania Height = 250 }), } + }, + new StatisticRow + { + Columns = new[] + { + new StatisticItem(string.Empty, new SimpleStatisticTable(3, new SimpleStatisticItem[] + { + new UnstableRate(score.HitEvents) + })) + } } }; } diff --git a/osu.Game.Rulesets.Mania/ManiaSkinComponent.cs b/osu.Game.Rulesets.Mania/ManiaSkinComponent.cs index c0c8505f44..f078345fc1 100644 --- a/osu.Game.Rulesets.Mania/ManiaSkinComponent.cs +++ b/osu.Game.Rulesets.Mania/ManiaSkinComponent.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.Mania.Beatmaps; using osu.Game.Rulesets.Mania.UI; using osu.Game.Skinning; @@ -14,15 +15,23 @@ namespace osu.Game.Rulesets.Mania /// public readonly int? TargetColumn; + /// + /// The intended for this component. + /// May be null if the component is not a direct member of a . + /// + public readonly StageDefinition? StageDefinition; + /// /// Creates a new . /// /// The component. /// The intended index for this component. May be null if the component does not exist in a . - public ManiaSkinComponent(ManiaSkinComponents component, int? targetColumn = null) + /// The intended for this component. May be null if the component is not a direct member of a . + public ManiaSkinComponent(ManiaSkinComponents component, int? targetColumn = null, StageDefinition? stageDefinition = null) : base(component) { TargetColumn = targetColumn; + StageDefinition = stageDefinition; } protected override string RulesetPrefix => ManiaRuleset.SHORT_NAME; diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs index a44f8a8886..549a71daaa 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; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces; @@ -32,6 +33,16 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables private readonly Container tailContainer; private readonly Container tickContainer; + /// + /// Contains the size of the hold note covering the whole head/tail bounds. The size of this container changes as the hold note is being pressed. + /// + private readonly Container sizingContainer; + + /// + /// Contains the contents of the hold note that should be masked as the hold note is being pressed. Follows changes in the size of . + /// + private readonly Container maskingContainer; + private readonly SkinnableDrawable bodyPiece; /// @@ -44,24 +55,54 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables /// public bool HasBroken { get; private set; } + /// + /// Whether the hold note has been released potentially without having caused a break. + /// + private double? releaseTime; + public DrawableHoldNote(HoldNote hitObject) : base(hitObject) { RelativeSizeAxes = Axes.X; + Container maskedContents; + AddRangeInternal(new Drawable[] { + sizingContainer = new Container + { + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + maskingContainer = new Container + { + RelativeSizeAxes = Axes.Both, + Child = maskedContents = new Container + { + RelativeSizeAxes = Axes.Both, + Masking = true, + } + }, + headContainer = new Container { RelativeSizeAxes = Axes.Both } + } + }, bodyPiece = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.HoldNoteBody, hitObject.Column), _ => new DefaultBodyPiece { - RelativeSizeAxes = Axes.Both + RelativeSizeAxes = Axes.Both, }) { RelativeSizeAxes = Axes.X }, tickContainer = new Container { RelativeSizeAxes = Axes.Both }, - headContainer = new Container { RelativeSizeAxes = Axes.Both }, tailContainer = new Container { RelativeSizeAxes = Axes.Both }, }); + + maskedContents.AddRange(new[] + { + bodyPiece.CreateProxy(), + tickContainer.CreateProxy(), + tailContainer.CreateProxy(), + }); } protected override void AddNestedHitObject(DrawableHitObject hitObject) @@ -127,7 +168,16 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables { base.OnDirectionChanged(e); - bodyPiece.Anchor = bodyPiece.Origin = e.NewValue == ScrollingDirection.Up ? Anchor.TopLeft : Anchor.BottomLeft; + if (e.NewValue == ScrollingDirection.Up) + { + bodyPiece.Anchor = bodyPiece.Origin = Anchor.TopLeft; + sizingContainer.Anchor = sizingContainer.Origin = Anchor.BottomLeft; + } + else + { + bodyPiece.Anchor = bodyPiece.Origin = Anchor.BottomLeft; + sizingContainer.Anchor = sizingContainer.Origin = Anchor.TopLeft; + } } public override void PlaySamples() @@ -145,9 +195,38 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables { base.Update(); - // Make the body piece not lie under the head note + if (Time.Current < releaseTime) + releaseTime = null; + + // Pad the full size container so its contents (i.e. the masking container) reach under the tail. + // This is required for the tail to not be masked away, since it lies outside the bounds of the hold note. + sizingContainer.Padding = new MarginPadding + { + Top = Direction.Value == ScrollingDirection.Down ? -Tail.Height : 0, + Bottom = Direction.Value == ScrollingDirection.Up ? -Tail.Height : 0, + }; + + // Pad the masking container to the starting position of the body piece (half-way under the head). + // This is required to make the body start getting masked immediately as soon as the note is held. + maskingContainer.Padding = new MarginPadding + { + Top = Direction.Value == ScrollingDirection.Up ? Head.Height / 2 : 0, + Bottom = Direction.Value == ScrollingDirection.Down ? Head.Height / 2 : 0, + }; + + // Position and resize the body to lie half-way under the head and the tail notes. bodyPiece.Y = (Direction.Value == ScrollingDirection.Up ? 1 : -1) * Head.Height / 2; bodyPiece.Height = DrawHeight - Head.Height / 2 + Tail.Height / 2; + + // As the note is being held, adjust the size of the sizing container. This has two effects: + // 1. The contained masking container will mask the body and ticks. + // 2. The head note will move along with the new "head position" in the container. + if (Head.IsHit && releaseTime == null) + { + // How far past the hit target this hold note is. Always a positive value. + float yOffset = Math.Max(0, Direction.Value == ScrollingDirection.Up ? -Y : Y); + sizingContainer.Height = Math.Clamp(1 - yOffset / DrawHeight, 0, 1); + } } protected override void UpdateStateTransforms(ArmedState state) @@ -159,7 +238,10 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables protected override void CheckForResult(bool userTriggered, double timeOffset) { if (Tail.AllJudged) + { ApplyResult(r => r.Type = HitResult.Perfect); + endHold(); + } if (Tail.Result.Type == HitResult.Miss) HasBroken = true; @@ -212,6 +294,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables // If the key has been released too early, the user should not receive full score for the release if (!Tail.IsHit) HasBroken = true; + + releaseTime = Time.Current; } private void endHold() diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteHead.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteHead.cs index a73fe259e4..cd56b81e10 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteHead.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteHead.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.Objects.Drawables; + namespace osu.Game.Rulesets.Mania.Objects.Drawables { /// @@ -17,6 +19,12 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables public void UpdateResult() => base.UpdateResult(true); + protected override void UpdateStateTransforms(ArmedState state) + { + // This hitobject should never expire, so this is just a safe maximum. + LifetimeEnd = LifetimeStart + 30000; + } + public override bool OnPressed(ManiaAction action) => false; // Handled by the hold note public override void OnReleased(ManiaAction action) diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs index a44d8b09aa..ab76a5b8f8 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs @@ -120,7 +120,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables break; case ArmedState.Hit: - this.FadeOut(150, Easing.OutQuint); + this.FadeOut(); break; } } diff --git a/osu.Game.Rulesets.Mania/Skinning/HitTargetInsetContainer.cs b/osu.Game.Rulesets.Mania/Skinning/HitTargetInsetContainer.cs new file mode 100644 index 0000000000..c8b05ed2f8 --- /dev/null +++ b/osu.Game.Rulesets.Mania/Skinning/HitTargetInsetContainer.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 osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Rulesets.Mania.UI; +using osu.Game.Rulesets.UI.Scrolling; +using osu.Game.Skinning; + +namespace osu.Game.Rulesets.Mania.Skinning +{ + public class HitTargetInsetContainer : Container + { + private readonly IBindable direction = new Bindable(); + + protected override Container Content => content; + private readonly Container content; + + private float hitPosition; + + public HitTargetInsetContainer() + { + RelativeSizeAxes = Axes.Both; + + InternalChild = content = new Container { RelativeSizeAxes = Axes.Both }; + } + + [BackgroundDependencyLoader] + private void load(ISkinSource skin, IScrollingInfo scrollingInfo) + { + hitPosition = skin.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.HitPosition)?.Value ?? Stage.HIT_TARGET_POSITION; + + direction.BindTo(scrollingInfo.Direction); + direction.BindValueChanged(onDirectionChanged, true); + } + + private void onDirectionChanged(ValueChangedEvent direction) + { + content.Padding = direction.NewValue == ScrollingDirection.Up + ? new MarginPadding { Top = hitPosition } + : new MarginPadding { Bottom = hitPosition }; + } + } +} diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyBodyPiece.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyBodyPiece.cs index 9f716428c0..c0f0fcb4af 100644 --- a/osu.Game.Rulesets.Mania/Skinning/LegacyBodyPiece.cs +++ b/osu.Game.Rulesets.Mania/Skinning/LegacyBodyPiece.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 JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -19,7 +21,14 @@ namespace osu.Game.Rulesets.Mania.Skinning private readonly IBindable direction = new Bindable(); private readonly IBindable isHitting = new Bindable(); - private Drawable sprite; + [CanBeNull] + private Drawable bodySprite; + + [CanBeNull] + private Drawable lightContainer; + + [CanBeNull] + private Drawable light; public LegacyBodyPiece() { @@ -32,7 +41,39 @@ namespace osu.Game.Rulesets.Mania.Skinning string imageName = GetColumnSkinConfig(skin, LegacyManiaSkinConfigurationLookups.HoldNoteBodyImage)?.Value ?? $"mania-note{FallbackColumnIndex}L"; - sprite = skin.GetAnimation(imageName, WrapMode.ClampToEdge, WrapMode.ClampToEdge, true, true).With(d => + string lightImage = GetColumnSkinConfig(skin, LegacyManiaSkinConfigurationLookups.HoldNoteLightImage)?.Value + ?? "lightingL"; + + float lightScale = GetColumnSkinConfig(skin, LegacyManiaSkinConfigurationLookups.HoldNoteLightScale)?.Value + ?? 1; + + // Create a temporary animation to retrieve the number of frames, in an effort to calculate the intended frame length. + // This animation is discarded and re-queried with the appropriate frame length afterwards. + var tmp = skin.GetAnimation(lightImage, true, false); + double frameLength = 0; + if (tmp is IFramedAnimation tmpAnimation && tmpAnimation.FrameCount > 0) + frameLength = Math.Max(1000 / 60.0, 170.0 / tmpAnimation.FrameCount); + + light = skin.GetAnimation(lightImage, true, true, frameLength: frameLength).With(d => + { + if (d == null) + return; + + d.Origin = Anchor.Centre; + d.Blending = BlendingParameters.Additive; + d.Scale = new Vector2(lightScale); + }); + + if (light != null) + { + lightContainer = new HitTargetInsetContainer + { + Alpha = 0, + Child = light + }; + } + + bodySprite = skin.GetAnimation(imageName, WrapMode.ClampToEdge, WrapMode.ClampToEdge, true, true).With(d => { if (d == null) return; @@ -47,8 +88,8 @@ namespace osu.Game.Rulesets.Mania.Skinning // Todo: Wrap }); - if (sprite != null) - InternalChild = sprite; + if (bodySprite != null) + InternalChild = bodySprite; direction.BindTo(scrollingInfo.Direction); direction.BindValueChanged(onDirectionChanged, true); @@ -60,28 +101,68 @@ namespace osu.Game.Rulesets.Mania.Skinning private void onIsHittingChanged(ValueChangedEvent isHitting) { - if (!(sprite is TextureAnimation animation)) + if (bodySprite is TextureAnimation bodyAnimation) + { + bodyAnimation.GotoFrame(0); + bodyAnimation.IsPlaying = isHitting.NewValue; + } + + if (lightContainer == null) return; - animation.GotoFrame(0); - animation.IsPlaying = isHitting.NewValue; + if (isHitting.NewValue) + { + // Clear the fade out and, more importantly, the removal. + lightContainer.ClearTransforms(); + + // Only add the container if the removal has taken place. + if (lightContainer.Parent == null) + Column.TopLevelContainer.Add(lightContainer); + + // The light must be seeked only after being loaded, otherwise a nullref occurs (https://github.com/ppy/osu-framework/issues/3847). + if (light is TextureAnimation lightAnimation) + lightAnimation.GotoFrame(0); + + lightContainer.FadeIn(80); + } + else + { + lightContainer.FadeOut(120) + .OnComplete(d => Column.TopLevelContainer.Remove(d)); + } } private void onDirectionChanged(ValueChangedEvent direction) { - if (sprite == null) - return; - if (direction.NewValue == ScrollingDirection.Up) { - sprite.Origin = Anchor.BottomCentre; - sprite.Scale = new Vector2(1, -1); + if (bodySprite != null) + { + bodySprite.Origin = Anchor.BottomCentre; + bodySprite.Scale = new Vector2(1, -1); + } + + if (light != null) + light.Anchor = Anchor.TopCentre; } else { - sprite.Origin = Anchor.TopCentre; - sprite.Scale = Vector2.One; + if (bodySprite != null) + { + bodySprite.Origin = Anchor.TopCentre; + bodySprite.Scale = Vector2.One; + } + + if (light != null) + light.Anchor = Anchor.BottomCentre; } } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + lightContainer?.Expire(); + } } } diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyColumnBackground.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyColumnBackground.cs index f9286b5095..3bf51b3073 100644 --- a/osu.Game.Rulesets.Mania/Skinning/LegacyColumnBackground.cs +++ b/osu.Game.Rulesets.Mania/Skinning/LegacyColumnBackground.cs @@ -5,10 +5,8 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Bindings; -using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Skinning; using osuTK; @@ -19,17 +17,12 @@ namespace osu.Game.Rulesets.Mania.Skinning public class LegacyColumnBackground : LegacyManiaColumnElement, IKeyBindingHandler { private readonly IBindable direction = new Bindable(); - private readonly bool isLastColumn; - private Container borderLineContainer; private Container lightContainer; private Sprite light; - private float hitPosition; - - public LegacyColumnBackground(bool isLastColumn) + public LegacyColumnBackground() { - this.isLastColumn = isLastColumn; RelativeSizeAxes = Axes.Both; } @@ -39,62 +32,14 @@ namespace osu.Game.Rulesets.Mania.Skinning string lightImage = skin.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.LightImage)?.Value ?? "mania-stage-light"; - float leftLineWidth = GetColumnSkinConfig(skin, LegacyManiaSkinConfigurationLookups.LeftLineWidth) - ?.Value ?? 1; - float rightLineWidth = GetColumnSkinConfig(skin, LegacyManiaSkinConfigurationLookups.RightLineWidth) - ?.Value ?? 1; - - bool hasLeftLine = leftLineWidth > 0; - bool hasRightLine = rightLineWidth > 0 && skin.GetConfig(LegacySkinConfiguration.LegacySetting.Version)?.Value >= 2.4m - || isLastColumn; - - hitPosition = GetColumnSkinConfig(skin, LegacyManiaSkinConfigurationLookups.HitPosition)?.Value - ?? Stage.HIT_TARGET_POSITION; - float lightPosition = GetColumnSkinConfig(skin, LegacyManiaSkinConfigurationLookups.LightPosition)?.Value ?? 0; - Color4 lineColour = GetColumnSkinConfig(skin, LegacyManiaSkinConfigurationLookups.ColumnLineColour)?.Value - ?? Color4.White; - - Color4 backgroundColour = GetColumnSkinConfig(skin, LegacyManiaSkinConfigurationLookups.ColumnBackgroundColour)?.Value - ?? Color4.Black; - Color4 lightColour = GetColumnSkinConfig(skin, LegacyManiaSkinConfigurationLookups.ColumnLightColour)?.Value ?? Color4.White; - InternalChildren = new Drawable[] + InternalChildren = new[] { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = backgroundColour - }, - borderLineContainer = new Container - { - RelativeSizeAxes = Axes.Both, - Children = new[] - { - new Box - { - RelativeSizeAxes = Axes.Y, - Width = leftLineWidth, - Scale = new Vector2(0.740f, 1), - Colour = lineColour, - Alpha = hasLeftLine ? 1 : 0 - }, - new Box - { - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - RelativeSizeAxes = Axes.Y, - Width = rightLineWidth, - Scale = new Vector2(0.740f, 1), - Colour = lineColour, - Alpha = hasRightLine ? 1 : 0 - } - } - }, lightContainer = new Container { Origin = Anchor.BottomCentre, @@ -104,7 +49,7 @@ namespace osu.Game.Rulesets.Mania.Skinning { Anchor = Anchor.BottomCentre, Origin = Anchor.BottomCentre, - Colour = lightColour, + Colour = LegacyColourCompatibility.DisallowZeroAlpha(lightColour), Texture = skin.GetTexture(lightImage), RelativeSizeAxes = Axes.X, Width = 1, @@ -123,15 +68,11 @@ namespace osu.Game.Rulesets.Mania.Skinning { lightContainer.Anchor = Anchor.TopCentre; lightContainer.Scale = new Vector2(1, -1); - - borderLineContainer.Padding = new MarginPadding { Top = hitPosition }; } else { lightContainer.Anchor = Anchor.BottomCentre; lightContainer.Scale = Vector2.One; - - borderLineContainer.Padding = new MarginPadding { Bottom = hitPosition }; } } diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyHitTarget.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyHitTarget.cs index d055ef3480..6eced571d2 100644 --- a/osu.Game.Rulesets.Mania/Skinning/LegacyHitTarget.cs +++ b/osu.Game.Rulesets.Mania/Skinning/LegacyHitTarget.cs @@ -20,11 +20,6 @@ namespace osu.Game.Rulesets.Mania.Skinning private Container directionContainer; - public LegacyHitTarget() - { - RelativeSizeAxes = Axes.Both; - } - [BackgroundDependencyLoader] private void load(ISkinSource skin, IScrollingInfo scrollingInfo) { @@ -56,7 +51,7 @@ namespace osu.Game.Rulesets.Mania.Skinning Anchor = Anchor.CentreLeft, RelativeSizeAxes = Axes.X, Height = 1, - Colour = lineColour, + Colour = LegacyColourCompatibility.DisallowZeroAlpha(lineColour), Alpha = showJudgementLine ? 0.9f : 0 } } diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyKeyArea.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyKeyArea.cs index 44f3e7d7b3..b269ea25d4 100644 --- a/osu.Game.Rulesets.Mania/Skinning/LegacyKeyArea.cs +++ b/osu.Game.Rulesets.Mania/Skinning/LegacyKeyArea.cs @@ -65,6 +65,9 @@ namespace osu.Game.Rulesets.Mania.Skinning direction.BindTo(scrollingInfo.Direction); direction.BindValueChanged(onDirectionChanged, true); + + if (GetColumnSkinConfig(skin, LegacyManiaSkinConfigurationLookups.KeysUnderNotes)?.Value ?? false) + Column.UnderlayElements.Add(CreateProxy()); } private void onDirectionChanged(ValueChangedEvent direction) diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyStageBackground.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyStageBackground.cs index 7f5de601ca..b0bab8e760 100644 --- a/osu.Game.Rulesets.Mania/Skinning/LegacyStageBackground.cs +++ b/osu.Game.Rulesets.Mania/Skinning/LegacyStageBackground.cs @@ -4,19 +4,27 @@ 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.Game.Rulesets.Mania.Beatmaps; +using osu.Game.Rulesets.Mania.UI; using osu.Game.Skinning; using osuTK; +using osuTK.Graphics; namespace osu.Game.Rulesets.Mania.Skinning { public class LegacyStageBackground : CompositeDrawable { + private readonly StageDefinition stageDefinition; + private Drawable leftSprite; private Drawable rightSprite; + private ColumnFlow columnBackgrounds; - public LegacyStageBackground() + public LegacyStageBackground(StageDefinition stageDefinition) { + this.stageDefinition = stageDefinition; RelativeSizeAxes = Axes.Both; } @@ -44,8 +52,19 @@ namespace osu.Game.Rulesets.Mania.Skinning Origin = Anchor.TopLeft, X = -0.05f, Texture = skin.GetTexture(rightImage) + }, + columnBackgrounds = new ColumnFlow(stageDefinition) + { + RelativeSizeAxes = Axes.Y + }, + new HitTargetInsetContainer + { + Child = new LegacyHitTarget { RelativeSizeAxes = Axes.Both } } }; + + for (int i = 0; i < stageDefinition.Columns; i++) + columnBackgrounds.SetContentForColumn(i, new ColumnBackground(i, i == stageDefinition.Columns - 1)); } protected override void Update() @@ -58,5 +77,72 @@ namespace osu.Game.Rulesets.Mania.Skinning if (rightSprite?.Height > 0) rightSprite.Scale = new Vector2(1, DrawHeight / rightSprite.Height); } + + private class ColumnBackground : CompositeDrawable + { + private readonly int columnIndex; + private readonly bool isLastColumn; + + public ColumnBackground(int columnIndex, bool isLastColumn) + { + this.columnIndex = columnIndex; + this.isLastColumn = isLastColumn; + + RelativeSizeAxes = Axes.Both; + } + + [BackgroundDependencyLoader] + private void load(ISkinSource skin) + { + float leftLineWidth = skin.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.LeftLineWidth, columnIndex)?.Value ?? 1; + float rightLineWidth = skin.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.RightLineWidth, columnIndex)?.Value ?? 1; + + bool hasLeftLine = leftLineWidth > 0; + bool hasRightLine = rightLineWidth > 0 && skin.GetConfig(LegacySkinConfiguration.LegacySetting.Version)?.Value >= 2.4m + || isLastColumn; + + Color4 lineColour = skin.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.ColumnLineColour, columnIndex)?.Value ?? Color4.White; + Color4 backgroundColour = skin.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.ColumnBackgroundColour, columnIndex)?.Value ?? Color4.Black; + + InternalChildren = new Drawable[] + { + LegacyColourCompatibility.ApplyWithDoubledAlpha(new Box + { + RelativeSizeAxes = Axes.Both + }, backgroundColour), + new HitTargetInsetContainer + { + RelativeSizeAxes = Axes.Both, + Children = new[] + { + new Container + { + RelativeSizeAxes = Axes.Y, + Width = leftLineWidth, + Scale = new Vector2(0.740f, 1), + Alpha = hasLeftLine ? 1 : 0, + Child = LegacyColourCompatibility.ApplyWithDoubledAlpha(new Box + { + RelativeSizeAxes = Axes.Both + }, lineColour) + }, + new Container + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + RelativeSizeAxes = Axes.Y, + Width = rightLineWidth, + Scale = new Vector2(0.740f, 1), + Alpha = hasRightLine ? 1 : 0, + Child = LegacyColourCompatibility.ApplyWithDoubledAlpha(new Box + { + RelativeSizeAxes = Axes.Both + }, lineColour) + }, + } + } + }; + } + } } } diff --git a/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs index e167135556..439e6f7df2 100644 --- a/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs @@ -9,6 +9,7 @@ using osu.Game.Beatmaps; using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Skinning; using System.Collections.Generic; +using System.Diagnostics; using osu.Framework.Audio.Sample; using osu.Game.Audio; using osu.Game.Rulesets.Objects.Legacy; @@ -88,10 +89,12 @@ namespace osu.Game.Rulesets.Mania.Skinning switch (maniaComponent.Component) { case ManiaSkinComponents.ColumnBackground: - return new LegacyColumnBackground(maniaComponent.TargetColumn == beatmap.TotalColumns - 1); + return new LegacyColumnBackground(); case ManiaSkinComponents.HitTarget: - return new LegacyHitTarget(); + // Legacy skins sandwich the hit target between the column background and the column light. + // To preserve this ordering, it's created manually inside LegacyStageBackground. + return Drawable.Empty(); case ManiaSkinComponents.KeyArea: return new LegacyKeyArea(); @@ -112,7 +115,8 @@ namespace osu.Game.Rulesets.Mania.Skinning return new LegacyHitExplosion(); case ManiaSkinComponents.StageBackground: - return new LegacyStageBackground(); + Debug.Assert(maniaComponent.StageDefinition != null); + return new LegacyStageBackground(maniaComponent.StageDefinition.Value); case ManiaSkinComponents.StageForeground: return new LegacyStageForeground(); diff --git a/osu.Game.Rulesets.Mania/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs index 255ce4c064..de4648e4fa 100644 --- a/osu.Game.Rulesets.Mania/UI/Column.cs +++ b/osu.Game.Rulesets.Mania/UI/Column.cs @@ -68,8 +68,6 @@ namespace osu.Game.Rulesets.Mania.UI TopLevelContainer.Add(HitObjectArea.Explosions.CreateProxy()); } - public override Axes RelativeSizeAxes => Axes.Y; - public ColumnType ColumnType { get; set; } public bool IsSpecial => ColumnType == ColumnType.Special; diff --git a/osu.Game.Rulesets.Mania/UI/ColumnFlow.cs b/osu.Game.Rulesets.Mania/UI/ColumnFlow.cs new file mode 100644 index 0000000000..aef82d4c08 --- /dev/null +++ b/osu.Game.Rulesets.Mania/UI/ColumnFlow.cs @@ -0,0 +1,105 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Rulesets.Mania.Beatmaps; +using osu.Game.Rulesets.Mania.Skinning; +using osu.Game.Skinning; + +namespace osu.Game.Rulesets.Mania.UI +{ + /// + /// A which flows its contents according to the s in a . + /// Content can be added to individual columns via . + /// + /// The type of content in each column. + public class ColumnFlow : CompositeDrawable + where TContent : Drawable + { + /// + /// All contents added to this . + /// + public IReadOnlyList Content => columns.Children.Select(c => c.Count == 0 ? null : (TContent)c.Child).ToList(); + + private readonly FillFlowContainer columns; + private readonly StageDefinition stageDefinition; + + public ColumnFlow(StageDefinition stageDefinition) + { + this.stageDefinition = stageDefinition; + + AutoSizeAxes = Axes.X; + + InternalChild = columns = new FillFlowContainer + { + RelativeSizeAxes = Axes.Y, + AutoSizeAxes = Axes.X, + Direction = FillDirection.Horizontal, + }; + + for (int i = 0; i < stageDefinition.Columns; i++) + columns.Add(new Container { RelativeSizeAxes = Axes.Y }); + } + + private ISkinSource currentSkin; + + [BackgroundDependencyLoader] + private void load(ISkinSource skin) + { + currentSkin = skin; + + skin.SourceChanged += onSkinChanged; + onSkinChanged(); + } + + private void onSkinChanged() + { + for (int i = 0; i < stageDefinition.Columns; i++) + { + if (i > 0) + { + float spacing = currentSkin.GetConfig( + new ManiaSkinConfigurationLookup(LegacyManiaSkinConfigurationLookups.ColumnSpacing, i - 1)) + ?.Value ?? Stage.COLUMN_SPACING; + + columns[i].Margin = new MarginPadding { Left = spacing }; + } + + float? width = currentSkin.GetConfig( + new ManiaSkinConfigurationLookup(LegacyManiaSkinConfigurationLookups.ColumnWidth, i)) + ?.Value; + + if (width == null) + // only used by default skin (legacy skins get defaults set in LegacyManiaSkinConfiguration) + columns[i].Width = stageDefinition.IsSpecialColumn(i) ? Column.SPECIAL_COLUMN_WIDTH : Column.COLUMN_WIDTH; + else + columns[i].Width = width.Value; + } + } + + /// + /// Sets the content of one of the columns of this . + /// + /// The index of the column to set the content of. + /// The content. + public void SetContentForColumn(int column, TContent content) => columns[column].Child = content; + + public new MarginPadding Padding + { + get => base.Padding; + set => base.Padding = value; + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + if (currentSkin != null) + currentSkin.SourceChanged -= onSkinChanged; + } + } +} diff --git a/osu.Game.Rulesets.Mania/UI/Stage.cs b/osu.Game.Rulesets.Mania/UI/Stage.cs index 36780b0f80..e7a2de266d 100644 --- a/osu.Game.Rulesets.Mania/UI/Stage.cs +++ b/osu.Game.Rulesets.Mania/UI/Stage.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Linq; -using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Pooling; @@ -11,7 +10,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.Mania.Skinning; using osu.Game.Rulesets.Mania.UI.Components; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.UI; @@ -31,14 +29,13 @@ namespace osu.Game.Rulesets.Mania.UI public const float HIT_TARGET_POSITION = 110; - public IReadOnlyList Columns => columnFlow.Children; - private readonly FillFlowContainer columnFlow; + public IReadOnlyList Columns => columnFlow.Content; + private readonly ColumnFlow columnFlow; private readonly JudgementContainer judgements; private readonly DrawablePool judgementPool; private readonly Drawable barLineContainer; - private readonly Container topLevelContainer; private readonly Dictionary columnColours = new Dictionary { @@ -62,6 +59,8 @@ namespace osu.Game.Rulesets.Mania.UI RelativeSizeAxes = Axes.Y; AutoSizeAxes = Axes.X; + Container topLevelContainer; + InternalChildren = new Drawable[] { judgementPool = new DrawablePool(2), @@ -73,17 +72,13 @@ namespace osu.Game.Rulesets.Mania.UI AutoSizeAxes = Axes.X, Children = new Drawable[] { - new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.StageBackground), _ => new DefaultStageBackground()) + new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.StageBackground, stageDefinition: definition), _ => new DefaultStageBackground()) { RelativeSizeAxes = Axes.Both }, - columnFlow = new FillFlowContainer + columnFlow = new ColumnFlow(definition) { - Name = "Columns", RelativeSizeAxes = Axes.Y, - AutoSizeAxes = Axes.X, - Direction = FillDirection.Horizontal, - Padding = new MarginPadding { Left = COLUMN_SPACING, Right = COLUMN_SPACING }, }, new Container { @@ -102,7 +97,7 @@ namespace osu.Game.Rulesets.Mania.UI RelativeSizeAxes = Axes.Y, } }, - new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.StageForeground), _ => null) + new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.StageForeground, stageDefinition: definition), _ => null) { RelativeSizeAxes = Axes.Both }, @@ -121,60 +116,22 @@ namespace osu.Game.Rulesets.Mania.UI for (int i = 0; i < definition.Columns; i++) { var columnType = definition.GetTypeOfColumn(i); + var column = new Column(firstColumnIndex + i) { + RelativeSizeAxes = Axes.Both, + Width = 1, ColumnType = columnType, AccentColour = columnColours[columnType], Action = { Value = columnType == ColumnType.Special ? specialColumnStartAction++ : normalColumnStartAction++ } }; - AddColumn(column); + topLevelContainer.Add(column.TopLevelContainer.CreateProxy()); + columnFlow.SetContentForColumn(i, column); + AddNested(column); } } - private ISkin currentSkin; - - [BackgroundDependencyLoader] - private void load(ISkinSource skin) - { - currentSkin = skin; - skin.SourceChanged += onSkinChanged; - - onSkinChanged(); - } - - private void onSkinChanged() - { - foreach (var col in columnFlow) - { - if (col.Index > 0) - { - float spacing = currentSkin.GetConfig( - new ManiaSkinConfigurationLookup(LegacyManiaSkinConfigurationLookups.ColumnSpacing, col.Index - 1)) - ?.Value ?? COLUMN_SPACING; - - col.Margin = new MarginPadding { Left = spacing }; - } - - float? width = currentSkin.GetConfig( - new ManiaSkinConfigurationLookup(LegacyManiaSkinConfigurationLookups.ColumnWidth, col.Index)) - ?.Value; - - if (width == null) - // only used by default skin (legacy skins get defaults set in LegacyManiaSkinConfiguration) - col.Width = col.IsSpecial ? Column.SPECIAL_COLUMN_WIDTH : Column.COLUMN_WIDTH; - else - col.Width = width.Value; - } - } - - public void AddColumn(Column c) - { - topLevelContainer.Add(c.TopLevelContainer.CreateProxy()); - columnFlow.Add(c); - AddNested(c); - } - public override void Add(DrawableHitObject h) { var maniaObject = (ManiaHitObject)h.HitObject; diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index eaa5d8937a..f527eb2312 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -193,30 +193,46 @@ namespace osu.Game.Rulesets.Osu public override IRulesetConfigManager CreateConfig(SettingsStore settings) => new OsuRulesetConfigManager(settings, RulesetInfo); - public override StatisticRow[] CreateStatisticsForScore(ScoreInfo score, IBeatmap playableBeatmap) => new[] + public override StatisticRow[] CreateStatisticsForScore(ScoreInfo score, IBeatmap playableBeatmap) { - new StatisticRow + var timedHitEvents = score.HitEvents.Where(e => e.HitObject is HitCircle && !(e.HitObject is SliderTailCircle)).ToList(); + + return new[] { - Columns = new[] + new StatisticRow { - new StatisticItem("Timing Distribution", new HitEventTimingDistributionGraph(score.HitEvents.Where(e => e.HitObject is HitCircle && !(e.HitObject is SliderTailCircle)).ToList()) + Columns = new[] { - RelativeSizeAxes = Axes.X, - Height = 250 - }), - } - }, - new StatisticRow - { - Columns = new[] + new StatisticItem("Timing Distribution", + new HitEventTimingDistributionGraph(timedHitEvents) + { + RelativeSizeAxes = Axes.X, + Height = 250 + }), + } + }, + new StatisticRow { - new StatisticItem("Accuracy Heatmap", new AccuracyHeatmap(score, playableBeatmap) + Columns = new[] { - RelativeSizeAxes = Axes.X, - Height = 250 - }), + new StatisticItem("Accuracy Heatmap", new AccuracyHeatmap(score, playableBeatmap) + { + RelativeSizeAxes = Axes.X, + Height = 250 + }), + } + }, + new StatisticRow + { + Columns = new[] + { + new StatisticItem(string.Empty, new SimpleStatisticTable(3, new SimpleStatisticItem[] + { + new UnstableRate(timedHitEvents) + })) + } } - } - }; + }; + } } } diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs b/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs index 0ab3e8825b..d15a0a3203 100644 --- a/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs +++ b/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs @@ -59,7 +59,6 @@ namespace osu.Game.Rulesets.Osu.Skinning hitCircleSprite = new Sprite { Texture = getTextureWithFallback(string.Empty), - Colour = drawableObject.AccentColour.Value, Anchor = Anchor.Centre, Origin = Anchor.Centre, }, @@ -107,7 +106,7 @@ namespace osu.Game.Rulesets.Osu.Skinning base.LoadComplete(); state.BindValueChanged(updateState, true); - accentColour.BindValueChanged(colour => hitCircleSprite.Colour = colour.NewValue, true); + accentColour.BindValueChanged(colour => hitCircleSprite.Colour = LegacyColourCompatibility.DisallowZeroAlpha(colour.NewValue), true); indexInCurrentCombo.BindValueChanged(index => hitCircleText.Text = (index.NewValue + 1).ToString(), true); } diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacySliderBall.cs b/osu.Game.Rulesets.Osu/Skinning/LegacySliderBall.cs index 0f586034d5..25ab96445a 100644 --- a/osu.Game.Rulesets.Osu/Skinning/LegacySliderBall.cs +++ b/osu.Game.Rulesets.Osu/Skinning/LegacySliderBall.cs @@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Osu.Skinning [BackgroundDependencyLoader] private void load(ISkinSource skin, DrawableHitObject drawableObject) { - animationContent.Colour = skin.GetConfig(OsuSkinColour.SliderBall)?.Value ?? Color4.White; + var ballColour = skin.GetConfig(OsuSkinColour.SliderBall)?.Value ?? Color4.White; InternalChildren = new[] { @@ -39,11 +39,11 @@ namespace osu.Game.Rulesets.Osu.Skinning Texture = skin.GetTexture("sliderb-nd"), Colour = new Color4(5, 5, 5, 255), }, - animationContent.With(d => + LegacyColourCompatibility.ApplyWithDoubledAlpha(animationContent.With(d => { d.Anchor = Anchor.Centre; d.Origin = Anchor.Centre; - }), + }), ballColour), layerSpec = new Sprite { Anchor = Anchor.Centre, diff --git a/osu.Game.Rulesets.Taiko/Skinning/LegacyCirclePiece.cs b/osu.Game.Rulesets.Taiko/Skinning/LegacyCirclePiece.cs index bfcf268c3d..9b73ccd248 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/LegacyCirclePiece.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/LegacyCirclePiece.cs @@ -90,7 +90,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning private void updateAccentColour() { - backgroundLayer.Colour = accentColour; + backgroundLayer.Colour = LegacyColourCompatibility.DisallowZeroAlpha(accentColour); } } } diff --git a/osu.Game.Rulesets.Taiko/Skinning/LegacyDrumRoll.cs b/osu.Game.Rulesets.Taiko/Skinning/LegacyDrumRoll.cs index 8223e3bc01..5ab8e3a8c8 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/LegacyDrumRoll.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/LegacyDrumRoll.cs @@ -76,9 +76,11 @@ namespace osu.Game.Rulesets.Taiko.Skinning private void updateAccentColour() { - headCircle.AccentColour = accentColour; - body.Colour = accentColour; - end.Colour = accentColour; + var colour = LegacyColourCompatibility.DisallowZeroAlpha(accentColour); + + headCircle.AccentColour = colour; + body.Colour = colour; + end.Colour = colour; } } } diff --git a/osu.Game.Rulesets.Taiko/Skinning/LegacyHit.cs b/osu.Game.Rulesets.Taiko/Skinning/LegacyHit.cs index 656728f6e4..b11b64c22c 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/LegacyHit.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/LegacyHit.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Game.Skinning; using osuTK.Graphics; namespace osu.Game.Rulesets.Taiko.Skinning @@ -18,9 +19,10 @@ namespace osu.Game.Rulesets.Taiko.Skinning [BackgroundDependencyLoader] private void load() { - AccentColour = component == TaikoSkinComponents.CentreHit - ? new Color4(235, 69, 44, 255) - : new Color4(67, 142, 172, 255); + AccentColour = LegacyColourCompatibility.DisallowZeroAlpha( + component == TaikoSkinComponents.CentreHit + ? new Color4(235, 69, 44, 255) + : new Color4(67, 142, 172, 255)); } } } diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index 2011842591..dbc32f2c3e 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -161,19 +161,34 @@ namespace osu.Game.Rulesets.Taiko public override IConvertibleReplayFrame CreateConvertibleReplayFrame() => new TaikoReplayFrame(); - public override StatisticRow[] CreateStatisticsForScore(ScoreInfo score, IBeatmap playableBeatmap) => new[] + public override StatisticRow[] CreateStatisticsForScore(ScoreInfo score, IBeatmap playableBeatmap) { - new StatisticRow + var timedHitEvents = score.HitEvents.Where(e => e.HitObject is Hit).ToList(); + + return new[] { - Columns = new[] + new StatisticRow { - new StatisticItem("Timing Distribution", new HitEventTimingDistributionGraph(score.HitEvents.Where(e => e.HitObject is Hit).ToList()) + Columns = new[] { - RelativeSizeAxes = Axes.X, - Height = 250 - }), + new StatisticItem("Timing Distribution", new HitEventTimingDistributionGraph(timedHitEvents) + { + RelativeSizeAxes = Axes.X, + Height = 250 + }), + } + }, + new StatisticRow + { + Columns = new[] + { + new StatisticItem(string.Empty, new SimpleStatisticTable(3, new SimpleStatisticItem[] + { + new UnstableRate(timedHitEvents) + })) + } } - } - }; + }; + } } } diff --git a/osu.Game.Tests/Beatmaps/BeatmapDifficultyManagerTest.cs b/osu.Game.Tests/Beatmaps/BeatmapDifficultyManagerTest.cs new file mode 100644 index 0000000000..0f6d956b3c --- /dev/null +++ b/osu.Game.Tests/Beatmaps/BeatmapDifficultyManagerTest.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 NUnit.Framework; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu.Mods; + +namespace osu.Game.Tests.Beatmaps +{ + [TestFixture] + public class BeatmapDifficultyManagerTest + { + [Test] + public void TestKeyEqualsWithDifferentModInstances() + { + var key1 = new BeatmapDifficultyManager.DifficultyCacheLookup(1234, 0, new Mod[] { new OsuModHardRock(), new OsuModHidden() }); + var key2 = new BeatmapDifficultyManager.DifficultyCacheLookup(1234, 0, new Mod[] { new OsuModHardRock(), new OsuModHidden() }); + + Assert.That(key1, Is.EqualTo(key2)); + } + + [Test] + public void TestKeyEqualsWithDifferentModOrder() + { + var key1 = new BeatmapDifficultyManager.DifficultyCacheLookup(1234, 0, new Mod[] { new OsuModHardRock(), new OsuModHidden() }); + var key2 = new BeatmapDifficultyManager.DifficultyCacheLookup(1234, 0, new Mod[] { new OsuModHidden(), new OsuModHardRock() }); + + Assert.That(key1, Is.EqualTo(key2)); + } + } +} diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs new file mode 100644 index 0000000000..9c71466489 --- /dev/null +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.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.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using osu.Framework.Utils; +using osu.Game.Beatmaps; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Catch; +using osu.Game.Rulesets.Mania; +using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Scoring; +using osu.Game.Rulesets.Taiko; +using osu.Game.Scoring; +using osu.Game.Scoring.Legacy; +using osu.Game.Tests.Resources; + +namespace osu.Game.Tests.Beatmaps.Formats +{ + [TestFixture] + public class LegacyScoreDecoderTest + { + [Test] + public void TestDecodeManiaReplay() + { + var decoder = new TestLegacyScoreDecoder(); + + using (var resourceStream = TestResources.OpenResource("Replays/mania-replay.osr")) + { + var score = decoder.Parse(resourceStream); + + Assert.AreEqual(3, score.ScoreInfo.Ruleset.ID); + + Assert.AreEqual(2, score.ScoreInfo.Statistics[HitResult.Great]); + Assert.AreEqual(1, score.ScoreInfo.Statistics[HitResult.Good]); + + Assert.AreEqual(829_931, score.ScoreInfo.TotalScore); + Assert.AreEqual(3, score.ScoreInfo.MaxCombo); + Assert.IsTrue(Precision.AlmostEquals(0.8889, score.ScoreInfo.Accuracy, 0.0001)); + Assert.AreEqual(ScoreRank.B, score.ScoreInfo.Rank); + + Assert.That(score.Replay.Frames, Is.Not.Empty); + } + } + + private class TestLegacyScoreDecoder : LegacyScoreDecoder + { + private static readonly Dictionary rulesets = new Ruleset[] + { + new OsuRuleset(), + new TaikoRuleset(), + new CatchRuleset(), + new ManiaRuleset() + }.ToDictionary(ruleset => ((ILegacyRuleset)ruleset).LegacyID); + + protected override Ruleset GetRuleset(int rulesetId) => rulesets[rulesetId]; + + protected override WorkingBeatmap GetBeatmap(string md5Hash) => new TestWorkingBeatmap(new Beatmap + { + BeatmapInfo = new BeatmapInfo + { + MD5Hash = md5Hash, + Ruleset = new OsuRuleset().RulesetInfo, + BaseDifficulty = new BeatmapDifficulty() + } + }); + } + } +} diff --git a/osu.Game.Tests/Editing/TestSceneHitObjectComposerDistanceSnapping.cs b/osu.Game.Tests/Editing/TestSceneHitObjectComposerDistanceSnapping.cs index 168ec0f09d..bd34eaff63 100644 --- a/osu.Game.Tests/Editing/TestSceneHitObjectComposerDistanceSnapping.cs +++ b/osu.Game.Tests/Editing/TestSceneHitObjectComposerDistanceSnapping.cs @@ -169,17 +169,17 @@ namespace osu.Game.Tests.Editing [Test] public void GetSnappedDistanceFromDistance() { - assertSnappedDistance(50, 100); + assertSnappedDistance(50, 0); assertSnappedDistance(100, 100); - assertSnappedDistance(150, 200); + assertSnappedDistance(150, 100); assertSnappedDistance(200, 200); - assertSnappedDistance(250, 300); + assertSnappedDistance(250, 200); AddStep("set slider multiplier = 2", () => composer.EditorBeatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier = 2); assertSnappedDistance(50, 0); - assertSnappedDistance(100, 200); - assertSnappedDistance(150, 200); + assertSnappedDistance(100, 0); + assertSnappedDistance(150, 0); assertSnappedDistance(200, 200); assertSnappedDistance(250, 200); @@ -190,8 +190,8 @@ namespace osu.Game.Tests.Editing }); assertSnappedDistance(50, 0); - assertSnappedDistance(100, 200); - assertSnappedDistance(150, 200); + assertSnappedDistance(100, 0); + assertSnappedDistance(150, 0); assertSnappedDistance(200, 200); assertSnappedDistance(250, 200); assertSnappedDistance(400, 400); diff --git a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs index 7b2913b817..d15682b1eb 100644 --- a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs +++ b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs @@ -60,7 +60,7 @@ namespace osu.Game.Tests.NonVisual.Filtering } [Test] - public void TestApplyDrainRateQueries() + public void TestApplyDrainRateQueriesByDrKeyword() { const string query = "dr>2 quite specific dr<:6"; var filterCriteria = new FilterCriteria(); @@ -73,6 +73,20 @@ namespace osu.Game.Tests.NonVisual.Filtering Assert.Less(filterCriteria.DrainRate.Min, 6.1f); } + [Test] + public void TestApplyDrainRateQueriesByHpKeyword() + { + const string query = "hp>2 quite specific hp<=6"; + var filterCriteria = new FilterCriteria(); + FilterQueryParser.ApplyQueries(filterCriteria, query); + Assert.AreEqual("quite specific", filterCriteria.SearchText.Trim()); + Assert.AreEqual(2, filterCriteria.SearchTerms.Length); + 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] public void TestApplyBPMQueries() { diff --git a/osu.Game.Tests/Resources/Replays/mania-replay.osr b/osu.Game.Tests/Resources/Replays/mania-replay.osr new file mode 100644 index 0000000000000000000000000000000000000000..da1a7bdd28e5b89eddd9742bce2b27bc8e75e6f5 GIT binary patch literal 1012 zcmV2Fk>_^VL323Gh;F`G&3?}Wi$c+0000000031008T$3;+WF00000 z01FT?FgZ6ed@(FBI50Uld@(FxP`z80O4tZ&0{{SB001BWz5CvGFroXr>*+lrR^_@@;>&b?uOx<^mMIMT(+z#h{kY?X9fc(g(Lwebtx# z7Q6Lh=|qk7uqqmlbl#mwF~34O;`FY?03m(j9XrY3L)0)1?Fw}$4J_CNJKO;4?X$~q z;+wGa#wD!!0q2PlCzFrV3*3>Lz{D|fez@9JR^@L#NXJJJ;9{+KP~}bh@_eim%+NSk zzegnGWya)SS7pW`<9yI$g`b0t!?MZslqBX+4bVy#tZV0pDNGmeSXKuOXGQ|f3zg8y zGrMu+gxo2?L-AA5RBaNH&*&Dv9Dc!%ufdTEO? zp@}%SBej)D4N{g#xwHF)8|Ks=@OJ2^BGnjH?@$dDOeELCl()%CSSocJ#J0 zrMz`0akC4=AW51K>p71-r!saF*3AP)S9{~98w6pYSsUr1=*o_Q#S)6A-dHA@d9~(^0+ax|D@!;n;Z|| zFvVlqvtKZJf2x8EVoS#`3YPzS-*vaun`i1N*BG`MvK_+XgTPh^GgHpdy!e==KZIaT z3!YC}(8nGj*i;imyixwy9zyF`AooHY90sBzR$Obzb-Vi~&7$v#i3~s7*O{}|SLmeL zoMGZCnRwCc_XbwVIh5g#M@wt{FGra$aqhxs9vn18+0v`>fSkg3XKs=0k_AUDZ~8>a zDG78!sD4dHOSBcg!q83SKA#^J)eG3=oY`jU3QIF2QVgemNl8@7S4(W%aYN@e0jDu% zmqiUorSh0*IbBRsW~4cH;@gJJOutPU2TVP<=OEnHm=mZ{2D~6Z=U*j5r!6K@YHyj* zwhC8cbzc~TkcHU?&2W;Km_To^u4o{p7%^`#txPglTyDDFl%vh successRate.Beatmap = new BeatmapInfo + { + Metrics = new BeatmapMetrics + { + Fails = Enumerable.Range(1, 100).ToArray(), + } + }); + AddAssert("graph max values correct", + () => successRate.ChildrenOfType().All(graph => graph.MaxValue == 100)); + } + + [Test] + public void TestEmptyMetrics() + { + AddStep("set beatmap", () => successRate.Beatmap = new BeatmapInfo + { + Metrics = new BeatmapMetrics() + }); + + AddAssert("graph max values correct", + () => successRate.ChildrenOfType().All(graph => graph.MaxValue == 0)); + } + private class GraphExposingSuccessRate : SuccessRate { public new FailRetryGraph Graph => base.Graph; diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneSimpleStatisticTable.cs b/osu.Game.Tests/Visual/Ranking/TestSceneSimpleStatisticTable.cs new file mode 100644 index 0000000000..07a0bcc8d8 --- /dev/null +++ b/osu.Game.Tests/Visual/Ranking/TestSceneSimpleStatisticTable.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.Linq; +using Humanizer; +using NUnit.Framework; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Utils; +using osu.Game.Screens.Ranking.Statistics; + +namespace osu.Game.Tests.Visual.Ranking +{ + public class TestSceneSimpleStatisticTable : OsuTestScene + { + private Container container; + + [SetUp] + public void SetUp() => Schedule(() => + { + Child = new Container + { + AutoSizeAxes = Axes.Y, + Width = 700, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4Extensions.FromHex("#333"), + }, + container = new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding(20) + } + } + }; + }); + + [Test] + public void TestEmpty() + { + AddStep("create with no items", + () => container.Add(new SimpleStatisticTable(2, Enumerable.Empty()))); + } + + [Test] + public void TestManyItems( + [Values(1, 2, 3, 4, 12)] int itemCount, + [Values(1, 3, 5)] int columnCount) + { + AddStep($"create with {"item".ToQuantity(itemCount)}", () => + { + var items = Enumerable.Range(1, itemCount) + .Select(i => new SimpleStatisticItem($"Statistic #{i}") + { + Value = RNG.Next(100) + }); + + container.Add(new SimpleStatisticTable(columnCount, items)); + }); + } + } +} diff --git a/osu.Game/Beatmaps/BeatmapDifficultyManager.cs b/osu.Game/Beatmaps/BeatmapDifficultyManager.cs index b80b4e45ed..0100c9b210 100644 --- a/osu.Game/Beatmaps/BeatmapDifficultyManager.cs +++ b/osu.Game/Beatmaps/BeatmapDifficultyManager.cs @@ -89,8 +89,14 @@ namespace osu.Game.Beatmaps if (tryGetExisting(beatmapInfo, rulesetInfo, mods, out var existing, out var key)) return existing; - return await Task.Factory.StartNew(() => computeDifficulty(key, beatmapInfo, rulesetInfo), cancellationToken, - TaskCreationOptions.HideScheduler | TaskCreationOptions.RunContinuationsAsynchronously, updateScheduler); + return await Task.Factory.StartNew(() => + { + // Computation may have finished in a previous task. + if (tryGetExisting(beatmapInfo, rulesetInfo, mods, out existing, out _)) + return existing; + + return computeDifficulty(key, beatmapInfo, rulesetInfo); + }, cancellationToken, TaskCreationOptions.HideScheduler | TaskCreationOptions.RunContinuationsAsynchronously, updateScheduler); } /// @@ -245,7 +251,7 @@ namespace osu.Game.Beatmaps updateScheduler?.Dispose(); } - private readonly struct DifficultyCacheLookup : IEquatable + public readonly struct DifficultyCacheLookup : IEquatable { public readonly int BeatmapId; public readonly int RulesetId; @@ -261,7 +267,7 @@ namespace osu.Game.Beatmaps public bool Equals(DifficultyCacheLookup other) => BeatmapId == other.BeatmapId && RulesetId == other.RulesetId - && Mods.SequenceEqual(other.Mods); + && Mods.Select(m => m.Acronym).SequenceEqual(other.Mods.Select(m => m.Acronym)); public override int GetHashCode() { diff --git a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs index 44ef9bcacc..c15240a4f6 100644 --- a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs @@ -104,10 +104,6 @@ namespace osu.Game.Beatmaps.Formats try { byte alpha = split.Length == 4 ? byte.Parse(split[3]) : (byte)255; - - if (alpha == 0) - alpha = 255; - colour = new Color4(byte.Parse(split[0]), byte.Parse(split[1]), byte.Parse(split[2]), alpha); } catch diff --git a/osu.Game/Online/Chat/MessageFormatter.cs b/osu.Game/Online/Chat/MessageFormatter.cs index 6af2561c89..648e4a762b 100644 --- a/osu.Game/Online/Chat/MessageFormatter.cs +++ b/osu.Game/Online/Chat/MessageFormatter.cs @@ -119,7 +119,7 @@ namespace osu.Game.Online.Chat case "http": case "https": // length > 3 since all these links need another argument to work - if (args.Length > 3 && (args[1] == "osu.ppy.sh" || args[1] == "new.ppy.sh")) + if (args.Length > 3 && args[1] == "osu.ppy.sh") { switch (args[2]) { diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index c25fb03fd0..f134db1ffe 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -25,7 +25,7 @@ using osu.Game.Screens.Edit.Components.RadioButtons; using osu.Game.Screens.Edit.Compose; using osu.Game.Screens.Edit.Compose.Components; using osuTK; -using Key = osuTK.Input.Key; +using osuTK.Input; namespace osu.Game.Rulesets.Edit { @@ -293,7 +293,16 @@ namespace osu.Game.Rulesets.Edit public override float GetSnappedDistanceFromDistance(double referenceTime, float distance) { - var snappedEndTime = BeatSnapProvider.SnapTime(referenceTime + DistanceToDuration(referenceTime, distance), referenceTime); + double actualDuration = referenceTime + DistanceToDuration(referenceTime, distance); + + double snappedEndTime = BeatSnapProvider.SnapTime(actualDuration, referenceTime); + + double beatLength = BeatSnapProvider.GetBeatLengthAtTime(referenceTime); + + // we don't want to exceed the actual duration and snap to a point in the future. + // as we are snapping to beat length via SnapTime (which will round-to-nearest), check for snapping in the forward direction and reverse it. + if (snappedEndTime > actualDuration + 1) + snappedEndTime -= beatLength; return DurationToDistance(referenceTime, snappedEndTime - referenceTime); } diff --git a/osu.Game/Rulesets/Edit/IPositionSnapProvider.cs b/osu.Game/Rulesets/Edit/IPositionSnapProvider.cs index c854c06031..cce631464f 100644 --- a/osu.Game/Rulesets/Edit/IPositionSnapProvider.cs +++ b/osu.Game/Rulesets/Edit/IPositionSnapProvider.cs @@ -47,6 +47,7 @@ namespace osu.Game.Rulesets.Edit /// /// Converts an unsnapped distance to a snapped distance. + /// The returned distance will always be floored (as to never exceed the provided . /// /// The time of the timing point which resides in. /// The distance to convert. diff --git a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs index a4a560c8e4..97cb5ca7ab 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs @@ -13,7 +13,6 @@ using osu.Game.Replays.Legacy; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Replays; -using osu.Game.Rulesets.Scoring; using osu.Game.Users; using SharpCompress.Compressors.LZMA; @@ -123,12 +122,12 @@ namespace osu.Game.Scoring.Legacy protected void CalculateAccuracy(ScoreInfo score) { - score.Statistics.TryGetValue(HitResult.Miss, out int countMiss); - score.Statistics.TryGetValue(HitResult.Meh, out int count50); - score.Statistics.TryGetValue(HitResult.Good, out int count100); - score.Statistics.TryGetValue(HitResult.Great, out int count300); - score.Statistics.TryGetValue(HitResult.Perfect, out int countGeki); - score.Statistics.TryGetValue(HitResult.Ok, out int countKatu); + int countMiss = score.GetCountMiss() ?? 0; + int count50 = score.GetCount50() ?? 0; + int count100 = score.GetCount100() ?? 0; + int count300 = score.GetCount300() ?? 0; + int countGeki = score.GetCountGeki() ?? 0; + int countKatu = score.GetCountKatu() ?? 0; switch (score.Ruleset.ID) { @@ -241,12 +240,15 @@ namespace osu.Game.Scoring.Legacy } var diff = Parsing.ParseFloat(split[0]); + var mouseX = Parsing.ParseFloat(split[1], Parsing.MAX_COORDINATE_VALUE); + var mouseY = Parsing.ParseFloat(split[2], Parsing.MAX_COORDINATE_VALUE); lastTime += diff; - if (i == 0 && diff == 0) - // osu-stable adds a zero-time frame before potentially valid negative user frames. - // we need to ignore this. + if (i < 2 && mouseX == 256 && mouseY == -500) + // at the start of the replay, stable places two replay frames, at time 0 and SkipBoundary - 1, respectively. + // both frames use a position of (256, -500). + // ignore these frames as they serve no real purpose (and can even mislead ruleset-specific handlers - see mania) continue; // Todo: At some point we probably want to rewind and play back the negative-time frames @@ -255,8 +257,8 @@ namespace osu.Game.Scoring.Legacy continue; currentFrame = convertFrame(new LegacyReplayFrame(lastTime, - Parsing.ParseFloat(split[1], Parsing.MAX_COORDINATE_VALUE), - Parsing.ParseFloat(split[2], Parsing.MAX_COORDINATE_VALUE), + mouseX, + mouseY, (ReplayButtonState)Parsing.ParseInt(split[3])), currentFrame); replay.Frames.Add(currentFrame); diff --git a/osu.Game/Screens/Menu/IntroSequence.cs b/osu.Game/Screens/Menu/IntroSequence.cs index 6731fef6f7..d92d38da45 100644 --- a/osu.Game/Screens/Menu/IntroSequence.cs +++ b/osu.Game/Screens/Menu/IntroSequence.cs @@ -205,6 +205,7 @@ namespace osu.Game.Screens.Menu const int line_end_offset = 120; smallRing.Foreground.ResizeTo(1, line_duration, Easing.OutQuint); + smallRing.Delay(400).FadeColour(Color4.Black, 300); lineTopLeft.MoveTo(new Vector2(-line_end_offset, -line_end_offset), line_duration, Easing.OutQuint); lineTopRight.MoveTo(new Vector2(line_end_offset, -line_end_offset), line_duration, Easing.OutQuint); diff --git a/osu.Game/Screens/Ranking/Statistics/SimpleStatisticItem.cs b/osu.Game/Screens/Ranking/Statistics/SimpleStatisticItem.cs new file mode 100644 index 0000000000..3d9ba2f225 --- /dev/null +++ b/osu.Game/Screens/Ranking/Statistics/SimpleStatisticItem.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.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; + +namespace osu.Game.Screens.Ranking.Statistics +{ + /// + /// Represents a simple statistic item (one that only needs textual display). + /// Richer visualisations should be done with s. + /// + public abstract class SimpleStatisticItem : Container + { + /// + /// The text to display as the statistic's value. + /// + protected string Value + { + set => this.value.Text = value; + } + + private readonly OsuSpriteText value; + + /// + /// Creates a new simple statistic item. + /// + /// The name of the statistic. + protected SimpleStatisticItem(string name) + { + Name = name; + + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + + AddRange(new[] + { + new OsuSpriteText + { + Text = Name, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Font = OsuFont.GetFont(size: 14) + }, + value = new OsuSpriteText + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Font = OsuFont.GetFont(size: 14, weight: FontWeight.Bold) + } + }); + } + } + + /// + /// Strongly-typed generic specialisation for . + /// + public class SimpleStatisticItem : SimpleStatisticItem + { + /// + /// The statistic's value to be displayed. + /// + public new TValue Value + { + set => base.Value = DisplayValue(value); + } + + /// + /// Used to convert to a text representation. + /// Defaults to using . + /// + protected virtual string DisplayValue(TValue value) => value.ToString(); + + public SimpleStatisticItem(string name) + : base(name) + { + } + } +} diff --git a/osu.Game/Screens/Ranking/Statistics/SimpleStatisticTable.cs b/osu.Game/Screens/Ranking/Statistics/SimpleStatisticTable.cs new file mode 100644 index 0000000000..8b503cc04e --- /dev/null +++ b/osu.Game/Screens/Ranking/Statistics/SimpleStatisticTable.cs @@ -0,0 +1,123 @@ +// Copyright (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; +using osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; + +namespace osu.Game.Screens.Ranking.Statistics +{ + /// + /// Represents a table with simple statistics (ones that only need textual display). + /// Richer visualisations should be done with s and s. + /// + public class SimpleStatisticTable : CompositeDrawable + { + private readonly SimpleStatisticItem[] items; + private readonly int columnCount; + + private FillFlowContainer[] columns; + + /// + /// Creates a statistic row for the supplied s. + /// + /// The number of columns to layout the into. + /// The s to display in this row. + public SimpleStatisticTable(int columnCount, [ItemNotNull] IEnumerable items) + { + if (columnCount < 1) + throw new ArgumentOutOfRangeException(nameof(columnCount)); + + this.columnCount = columnCount; + this.items = items.ToArray(); + } + + [BackgroundDependencyLoader] + private void load() + { + columns = new FillFlowContainer[columnCount]; + + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + + InternalChild = new GridContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + RowDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize) + }, + ColumnDimensions = createColumnDimensions().ToArray(), + Content = new[] { createColumns().ToArray() } + }; + + for (int i = 0; i < items.Length; ++i) + columns[i % columnCount].Add(items[i]); + } + + private IEnumerable createColumnDimensions() + { + for (int column = 0; column < columnCount; ++column) + { + if (column > 0) + yield return new Dimension(GridSizeMode.Absolute, 30); + + yield return new Dimension(); + } + } + + private IEnumerable createColumns() + { + for (int column = 0; column < columnCount; ++column) + { + if (column > 0) + { + yield return new Spacer + { + Alpha = items.Length > column ? 1 : 0 + }; + } + + yield return columns[column] = createColumn(); + } + } + + private FillFlowContainer createColumn() => new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical + }; + + private class Spacer : CompositeDrawable + { + public Spacer() + { + RelativeSizeAxes = Axes.Both; + Padding = new MarginPadding { Vertical = 4 }; + + InternalChild = new CircularContainer + { + RelativeSizeAxes = Axes.Y, + Width = 3, + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + CornerRadius = 2, + Masking = true, + Child = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4Extensions.FromHex("#222") + } + }; + } + } + } +} diff --git a/osu.Game/Screens/Ranking/Statistics/StatisticContainer.cs b/osu.Game/Screens/Ranking/Statistics/StatisticContainer.cs index ed98698411..485d24d024 100644 --- a/osu.Game/Screens/Ranking/Statistics/StatisticContainer.cs +++ b/osu.Game/Screens/Ranking/Statistics/StatisticContainer.cs @@ -32,33 +32,9 @@ namespace osu.Game.Screens.Ranking.Statistics AutoSizeAxes = Axes.Y, Content = new[] { - new Drawable[] + new[] { - new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(5, 0), - Children = new Drawable[] - { - new Circle - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Height = 9, - Width = 4, - Colour = Color4Extensions.FromHex("#00FFAA") - }, - new OsuSpriteText - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Text = item.Name, - Font = OsuFont.GetFont(size: 14, weight: FontWeight.SemiBold), - } - } - } + createHeader(item) }, new Drawable[] { @@ -78,5 +54,37 @@ namespace osu.Game.Screens.Ranking.Statistics } }; } + + private static Drawable createHeader(StatisticItem item) + { + if (string.IsNullOrEmpty(item.Name)) + return Empty(); + + return new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(5, 0), + Children = new Drawable[] + { + new Circle + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Height = 9, + Width = 4, + Colour = Color4Extensions.FromHex("#00FFAA") + }, + new OsuSpriteText + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Text = item.Name, + Font = OsuFont.GetFont(size: 14, weight: FontWeight.SemiBold), + } + } + }; + } } } diff --git a/osu.Game/Screens/Ranking/Statistics/StatisticItem.cs b/osu.Game/Screens/Ranking/Statistics/StatisticItem.cs index e959ed24fc..4903983759 100644 --- a/osu.Game/Screens/Ranking/Statistics/StatisticItem.cs +++ b/osu.Game/Screens/Ranking/Statistics/StatisticItem.cs @@ -30,7 +30,7 @@ namespace osu.Game.Screens.Ranking.Statistics /// /// Creates a new , to be displayed inside a in the results screen. /// - /// The name of the item. + /// The name of the item. Can be to hide the item header. /// The content to be displayed. /// The of this item. This can be thought of as the column dimension of an encompassing . public StatisticItem([NotNull] string name, [NotNull] Drawable content, [CanBeNull] Dimension dimension = null) diff --git a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs index 7f406331cd..c2ace6a04e 100644 --- a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs +++ b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs @@ -94,14 +94,15 @@ namespace osu.Game.Screens.Ranking.Statistics RelativeSizeAxes = Axes.Both, Direction = FillDirection.Vertical, Spacing = new Vector2(30, 15), + Alpha = 0 }; foreach (var row in newScore.Ruleset.CreateInstance().CreateStatisticsForScore(newScore, playableBeatmap)) { rows.Add(new GridContainer { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Content = new[] @@ -125,6 +126,7 @@ namespace osu.Game.Screens.Ranking.Statistics spinner.Hide(); content.Add(d); + d.FadeIn(250, Easing.OutQuint); }, localCancellationSource.Token); }), localCancellationSource.Token); } diff --git a/osu.Game/Screens/Ranking/Statistics/UnstableRate.cs b/osu.Game/Screens/Ranking/Statistics/UnstableRate.cs new file mode 100644 index 0000000000..5b368c3e8d --- /dev/null +++ b/osu.Game/Screens/Ranking/Statistics/UnstableRate.cs @@ -0,0 +1,39 @@ +// Copyright (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.Rulesets.Scoring; + +namespace osu.Game.Screens.Ranking.Statistics +{ + /// + /// Displays the unstable rate statistic for a given play. + /// + public class UnstableRate : SimpleStatisticItem + { + /// + /// Creates and computes an statistic. + /// + /// Sequence of s to calculate the unstable rate based on. + public UnstableRate(IEnumerable hitEvents) + : base("Unstable Rate") + { + var timeOffsets = hitEvents.Select(ev => ev.TimeOffset).ToArray(); + Value = 10 * standardDeviation(timeOffsets); + } + + private static double standardDeviation(double[] timeOffsets) + { + if (timeOffsets.Length == 0) + return double.NaN; + + var mean = timeOffsets.Average(); + var squares = timeOffsets.Select(offset => Math.Pow(offset - mean, 2)).Sum(); + return Math.Sqrt(squares / timeOffsets.Length); + } + + protected override string DisplayValue(double value) => double.IsNaN(value) ? "(not available)" : value.ToString("N2"); + } +} diff --git a/osu.Game/Screens/Select/BeatmapDetails.cs b/osu.Game/Screens/Select/BeatmapDetails.cs index 9669a1391c..0ee52f3e48 100644 --- a/osu.Game/Screens/Select/BeatmapDetails.cs +++ b/osu.Game/Screens/Select/BeatmapDetails.cs @@ -236,7 +236,7 @@ namespace osu.Game.Screens.Select private void updateMetrics() { var hasRatings = beatmap?.BeatmapSet?.Metrics?.Ratings?.Any() ?? false; - var hasRetriesFails = (beatmap?.Metrics?.Retries?.Any() ?? false) && (beatmap?.Metrics.Fails?.Any() ?? false); + var hasRetriesFails = (beatmap?.Metrics?.Retries?.Any() ?? false) || (beatmap?.Metrics?.Fails?.Any() ?? false); if (hasRatings) { diff --git a/osu.Game/Screens/Select/Details/FailRetryGraph.cs b/osu.Game/Screens/Select/Details/FailRetryGraph.cs index 134fd0598a..7cc80acfd3 100644 --- a/osu.Game/Screens/Select/Details/FailRetryGraph.cs +++ b/osu.Game/Screens/Select/Details/FailRetryGraph.cs @@ -29,16 +29,30 @@ namespace osu.Game.Screens.Select.Details var retries = Metrics?.Retries ?? Array.Empty(); var fails = Metrics?.Fails ?? Array.Empty(); + var retriesAndFails = sumRetriesAndFails(retries, fails); - float maxValue = fails.Any() ? fails.Zip(retries, (fail, retry) => fail + retry).Max() : 0; + float maxValue = retriesAndFails.Any() ? retriesAndFails.Max() : 0; failGraph.MaxValue = maxValue; retryGraph.MaxValue = maxValue; - failGraph.Values = fails.Select(f => (float)f); - retryGraph.Values = retries.Zip(fails, (retry, fail) => retry + Math.Clamp(fail, 0, maxValue)); + failGraph.Values = fails.Select(v => (float)v); + retryGraph.Values = retriesAndFails.Select(v => (float)v); } } + private int[] sumRetriesAndFails(int[] retries, int[] fails) + { + var result = new int[Math.Max(retries.Length, fails.Length)]; + + for (int i = 0; i < retries.Length; ++i) + result[i] = retries[i]; + + for (int i = 0; i < fails.Length; ++i) + result[i] += fails[i]; + + return result; + } + public FailRetryGraph() { Children = new[] diff --git a/osu.Game/Screens/Select/FilterQueryParser.cs b/osu.Game/Screens/Select/FilterQueryParser.cs index 89afc729fe..39fa4f777d 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|creator|artist)(?[=:><]+)(?("".*"")|(\S*))", + @"\b(?stars|ar|dr|hp|cs|divisor|length|objects|bpm|status|creator|artist)(?[=:><]+)(?("".*"")|(\S*))", RegexOptions.Compiled | RegexOptions.IgnoreCase); internal static void ApplyQueries(FilterCriteria criteria, string query) @@ -43,6 +43,7 @@ namespace osu.Game.Screens.Select break; case "dr" when parseFloatWithPoint(value, out var dr): + case "hp" when parseFloatWithPoint(value, out dr): updateCriteriaRange(ref criteria.DrainRate, op, dr, 0.1f / 2); break; diff --git a/osu.Game/Skinning/LegacyColourCompatibility.cs b/osu.Game/Skinning/LegacyColourCompatibility.cs new file mode 100644 index 0000000000..b842b50426 --- /dev/null +++ b/osu.Game/Skinning/LegacyColourCompatibility.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 osu.Framework.Graphics; +using osuTK.Graphics; + +namespace osu.Game.Skinning +{ + /// + /// Compatibility methods to convert osu!stable colours to osu!lazer-compatible ones. Should be used for legacy skins only. + /// + public static class LegacyColourCompatibility + { + /// + /// Forces an alpha of 1 if a given is fully transparent. + /// + /// + /// This is equivalent to setting colour post-constructor in osu!stable. + /// + /// The to disallow zero alpha on. + /// The resultant . + public static Color4 DisallowZeroAlpha(Color4 colour) + { + if (colour.A == 0) + colour.A = 1; + return colour; + } + + /// + /// Applies a to a , doubling the alpha value into the property. + /// + /// + /// This is equivalent to setting colour in the constructor in osu!stable. + /// + /// The to apply the colour to. + /// The to apply. + /// The given . + public static T ApplyWithDoubledAlpha(T drawable, Color4 colour) + where T : Drawable + { + drawable.Alpha = colour.A; + drawable.Colour = DisallowZeroAlpha(colour); + return drawable; + } + } +} diff --git a/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs b/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs index af7d6007f3..65d5851455 100644 --- a/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs +++ b/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs @@ -31,10 +31,12 @@ namespace osu.Game.Skinning public readonly float[] ColumnSpacing; public readonly float[] ColumnWidth; public readonly float[] ExplosionWidth; + public readonly float[] HoldNoteLightWidth; public float HitPosition = (480 - 402) * POSITION_SCALE_FACTOR; public float LightPosition = (480 - 413) * POSITION_SCALE_FACTOR; public bool ShowJudgementLine = true; + public bool KeysUnderNotes; public LegacyManiaSkinConfiguration(int keys) { @@ -44,6 +46,7 @@ namespace osu.Game.Skinning ColumnSpacing = new float[keys - 1]; ColumnWidth = new float[keys]; ExplosionWidth = new float[keys]; + HoldNoteLightWidth = new float[keys]; ColumnLineWidth.AsSpan().Fill(2); ColumnWidth.AsSpan().Fill(DEFAULT_COLUMN_SIZE); diff --git a/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs b/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs index 4990ca8e60..a99710ea96 100644 --- a/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs +++ b/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs @@ -34,6 +34,8 @@ namespace osu.Game.Skinning HoldNoteHeadImage, HoldNoteTailImage, HoldNoteBodyImage, + HoldNoteLightImage, + HoldNoteLightScale, ExplosionImage, ExplosionScale, ColumnLineColour, @@ -50,5 +52,6 @@ namespace osu.Game.Skinning Hit100, Hit50, Hit0, + KeysUnderNotes, } } diff --git a/osu.Game/Skinning/LegacyManiaSkinDecoder.cs b/osu.Game/Skinning/LegacyManiaSkinDecoder.cs index aebc229f7c..a9d88e77ad 100644 --- a/osu.Game/Skinning/LegacyManiaSkinDecoder.cs +++ b/osu.Game/Skinning/LegacyManiaSkinDecoder.cs @@ -97,10 +97,18 @@ namespace osu.Game.Skinning currentConfig.ShowJudgementLine = pair.Value == "1"; break; + case "KeysUnderNotes": + currentConfig.KeysUnderNotes = pair.Value == "1"; + break; + case "LightingNWidth": parseArrayValue(pair.Value, currentConfig.ExplosionWidth); break; + case "LightingLWidth": + parseArrayValue(pair.Value, currentConfig.HoldNoteLightWidth); + break; + case "WidthForNoteHeightScale": float minWidth = float.Parse(pair.Value, CultureInfo.InvariantCulture) * LegacyManiaSkinConfiguration.POSITION_SCALE_FACTOR; if (minWidth > 0) @@ -116,6 +124,7 @@ namespace osu.Game.Skinning case string _ when pair.Key.StartsWith("KeyImage"): case string _ when pair.Key.StartsWith("Hit"): case string _ when pair.Key.StartsWith("Stage"): + case string _ when pair.Key.StartsWith("Lighting"): currentConfig.ImageLookups[pair.Key] = pair.Value; break; } diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 02d07eee45..5caf07b554 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -173,6 +173,9 @@ namespace osu.Game.Skinning case LegacyManiaSkinConfigurationLookups.ShowJudgementLine: return SkinUtils.As(new Bindable(existing.ShowJudgementLine)); + case LegacyManiaSkinConfigurationLookups.ExplosionImage: + return SkinUtils.As(getManiaImage(existing, "LightingN")); + case LegacyManiaSkinConfigurationLookups.ExplosionScale: Debug.Assert(maniaLookup.TargetColumn != null); @@ -217,6 +220,20 @@ namespace osu.Game.Skinning Debug.Assert(maniaLookup.TargetColumn != null); return SkinUtils.As(getManiaImage(existing, $"NoteImage{maniaLookup.TargetColumn}L")); + case LegacyManiaSkinConfigurationLookups.HoldNoteLightImage: + return SkinUtils.As(getManiaImage(existing, "LightingL")); + + case LegacyManiaSkinConfigurationLookups.HoldNoteLightScale: + Debug.Assert(maniaLookup.TargetColumn != null); + + if (GetConfig(LegacySkinConfiguration.LegacySetting.Version)?.Value < 2.5m) + return SkinUtils.As(new Bindable(1)); + + if (existing.HoldNoteLightWidth[maniaLookup.TargetColumn.Value] != 0) + return SkinUtils.As(new Bindable(existing.HoldNoteLightWidth[maniaLookup.TargetColumn.Value] / LegacyManiaSkinConfiguration.DEFAULT_COLUMN_SIZE)); + + return SkinUtils.As(new Bindable(existing.ColumnWidth[maniaLookup.TargetColumn.Value] / LegacyManiaSkinConfiguration.DEFAULT_COLUMN_SIZE)); + case LegacyManiaSkinConfigurationLookups.KeyImage: Debug.Assert(maniaLookup.TargetColumn != null); return SkinUtils.As(getManiaImage(existing, $"KeyImage{maniaLookup.TargetColumn}")); @@ -255,6 +272,9 @@ namespace osu.Game.Skinning case LegacyManiaSkinConfigurationLookups.Hit300: case LegacyManiaSkinConfigurationLookups.Hit300g: return SkinUtils.As(getManiaImage(existing, maniaLookup.Lookup.ToString())); + + case LegacyManiaSkinConfigurationLookups.KeysUnderNotes: + return SkinUtils.As(new Bindable(existing.KeysUnderNotes)); } return null; From 112ecf085d6a44b59be0c12f131d7fee2a20d4f1 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Tue, 1 Sep 2020 17:19:04 +0000 Subject: [PATCH 0826/1782] Bump Sentry from 2.1.5 to 2.1.6 Bumps [Sentry](https://github.com/getsentry/sentry-dotnet) from 2.1.5 to 2.1.6. - [Release notes](https://github.com/getsentry/sentry-dotnet/releases) - [Commits](https://github.com/getsentry/sentry-dotnet/compare/2.1.5...2.1.6) 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 d1e2033596..c021770d7b 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -26,7 +26,7 @@ - + From 846189659b935cf970a22fa17e23a63f1353604a Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Tue, 1 Sep 2020 17:19:29 +0000 Subject: [PATCH 0827/1782] Bump Microsoft.Build.Traversal from 2.0.52 to 2.1.1 Bumps [Microsoft.Build.Traversal](https://github.com/Microsoft/MSBuildSdks) from 2.0.52 to 2.1.1. - [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.52...Microsoft.Build.Traversal.2.1.1) 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 233a040d18..a9a531f59c 100644 --- a/global.json +++ b/global.json @@ -5,6 +5,6 @@ "version": "3.1.100" }, "msbuild-sdks": { - "Microsoft.Build.Traversal": "2.0.52" + "Microsoft.Build.Traversal": "2.1.1" } } \ No newline at end of file From 66c0d12da619a86b43cbbb594987c59712a8acff Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Tue, 1 Sep 2020 17:19:46 +0000 Subject: [PATCH 0828/1782] Bump Microsoft.NET.Test.Sdk from 16.7.0 to 16.7.1 Bumps [Microsoft.NET.Test.Sdk](https://github.com/microsoft/vstest) from 16.7.0 to 16.7.1. - [Release notes](https://github.com/microsoft/vstest/releases) - [Commits](https://github.com/microsoft/vstest/compare/v16.7.0...v16.7.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 f9d56dfa78..dfe3bf8af4 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 ed00ed0b4c..892f27d27f 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 f3837ea6b1..3639c3616f 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 e896606ee8..b59f3a4344 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 d767973528..c692bcd5e4 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 95f5deb2cc..5d55196dcf 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 8bf679db8be69f18ac06aba4972a14bcf5e8f513 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 2 Sep 2020 13:17:17 +0900 Subject: [PATCH 0829/1782] Fix nullref in date text box --- osu.Game.Tournament/Components/DateTextBox.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tournament/Components/DateTextBox.cs b/osu.Game.Tournament/Components/DateTextBox.cs index ee7e350970..aee5241e35 100644 --- a/osu.Game.Tournament/Components/DateTextBox.cs +++ b/osu.Game.Tournament/Components/DateTextBox.cs @@ -22,11 +22,12 @@ namespace osu.Game.Tournament.Components } // hold a reference to the provided bindable so we don't have to in every settings section. - private Bindable bindable; + private Bindable bindable = new Bindable(); public DateTextBox() { base.Bindable = new Bindable(); + ((OsuTextBox)Control).OnCommit = (sender, newText) => { try From 8f90cc182099075a3651d22d1c31b9303aabd6ed Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 2 Sep 2020 13:21:51 +0900 Subject: [PATCH 0830/1782] Add test --- .../Components/TestSceneDateTextBox.cs | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 osu.Game.Tournament.Tests/Components/TestSceneDateTextBox.cs diff --git a/osu.Game.Tournament.Tests/Components/TestSceneDateTextBox.cs b/osu.Game.Tournament.Tests/Components/TestSceneDateTextBox.cs new file mode 100644 index 0000000000..33165d385a --- /dev/null +++ b/osu.Game.Tournament.Tests/Components/TestSceneDateTextBox.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 NUnit.Framework; +using osu.Game.Tests.Visual; +using osu.Game.Tournament.Components; +using osuTK; +using osuTK.Input; + +namespace osu.Game.Tournament.Tests.Components +{ + public class TestSceneDateTextBox : OsuManualInputManagerTestScene + { + private DateTextBox textBox; + + [SetUp] + public void Setup() => Schedule(() => + { + Child = textBox = new DateTextBox + { + Width = 0.3f + }; + }); + + [Test] + public void TestCommitWithoutSettingBindable() + { + AddStep("click textbox", () => + { + InputManager.MoveMouseTo(textBox); + InputManager.Click(MouseButton.Left); + }); + + AddStep("unfocus", () => + { + InputManager.MoveMouseTo(Vector2.Zero); + InputManager.Click(MouseButton.Left); + }); + } + } +} From f793bf66e5e34c94c374b4f19391c83ce08fd361 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 2 Sep 2020 14:25:24 +0900 Subject: [PATCH 0831/1782] Remove rate adjustment from player test scene --- osu.Game/Tests/Visual/PlayerTestScene.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Tests/Visual/PlayerTestScene.cs b/osu.Game/Tests/Visual/PlayerTestScene.cs index 7d06c99133..2c46e7f6d3 100644 --- a/osu.Game/Tests/Visual/PlayerTestScene.cs +++ b/osu.Game/Tests/Visual/PlayerTestScene.cs @@ -29,8 +29,6 @@ namespace osu.Game.Tests.Visual { Dependencies.Cache(LocalConfig = new OsuConfigManager(LocalStorage)); LocalConfig.GetBindable(OsuSetting.DimLevel).Value = 1.0; - - MusicController.AllowRateAdjustments = true; } [SetUpSteps] From 7a6e02c558cffb2eaa5f665611f6ce778359d035 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 2 Sep 2020 14:28:31 +0900 Subject: [PATCH 0832/1782] Allow null rank --- osu.Game/Screens/Multi/Match/Components/MatchLeaderboard.cs | 2 +- .../Screens/Multi/Match/Components/MatchLeaderboardScore.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Multi/Match/Components/MatchLeaderboard.cs b/osu.Game/Screens/Multi/Match/Components/MatchLeaderboard.cs index 5bf61eb4ee..f2409d64e7 100644 --- a/osu.Game/Screens/Multi/Match/Components/MatchLeaderboard.cs +++ b/osu.Game/Screens/Multi/Match/Components/MatchLeaderboard.cs @@ -50,7 +50,7 @@ namespace osu.Game.Screens.Multi.Match.Components protected override LeaderboardScore CreateDrawableScore(APIUserScoreAggregate model, int index) => new MatchLeaderboardScore(model, index); - protected override LeaderboardScore CreateDrawableTopScore(APIUserScoreAggregate model) => new MatchLeaderboardScore(model, model.Position ?? 0, false); + protected override LeaderboardScore CreateDrawableTopScore(APIUserScoreAggregate model) => new MatchLeaderboardScore(model, model.Position, false); } public enum MatchLeaderboardScope diff --git a/osu.Game/Screens/Multi/Match/Components/MatchLeaderboardScore.cs b/osu.Game/Screens/Multi/Match/Components/MatchLeaderboardScore.cs index c4e2b332b3..1fabdbb86a 100644 --- a/osu.Game/Screens/Multi/Match/Components/MatchLeaderboardScore.cs +++ b/osu.Game/Screens/Multi/Match/Components/MatchLeaderboardScore.cs @@ -14,7 +14,7 @@ namespace osu.Game.Screens.Multi.Match.Components { private readonly APIUserScoreAggregate score; - public MatchLeaderboardScore(APIUserScoreAggregate score, int rank, bool allowHighlight = true) + public MatchLeaderboardScore(APIUserScoreAggregate score, int? rank, bool allowHighlight = true) : base(score.CreateScoreInfo(), rank, allowHighlight) { this.score = score; From bff652a26f4755e3b18546af495d8bab778ef67f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 2 Sep 2020 14:29:46 +0900 Subject: [PATCH 0833/1782] Persist nulls to the top score bindable --- osu.Game/Online/Leaderboards/Leaderboard.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs index 003d90d400..db0f835c67 100644 --- a/osu.Game/Online/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -93,13 +93,12 @@ namespace osu.Game.Online.Leaderboards get => topScoreContainer.Score.Value; set { + topScoreContainer.Score.Value = value; + if (value == null) topScoreContainer.Hide(); else - { topScoreContainer.Show(); - topScoreContainer.Score.Value = value; - } } } From 5195da3ceb6abc93db51e711c94086a009bf197d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 2 Sep 2020 15:18:51 +0900 Subject: [PATCH 0834/1782] Add message box in bracket editor explaining how to get started --- .../Screens/Editors/LadderEditorScreen.cs | 12 ++++++ osu.Game.Tournament/TournamentGame.cs | 25 +----------- osu.Game.Tournament/WarningBox.cs | 40 +++++++++++++++++++ 3 files changed, 53 insertions(+), 24 deletions(-) create mode 100644 osu.Game.Tournament/WarningBox.cs diff --git a/osu.Game.Tournament/Screens/Editors/LadderEditorScreen.cs b/osu.Game.Tournament/Screens/Editors/LadderEditorScreen.cs index f3eecf8afe..efec4cffdd 100644 --- a/osu.Game.Tournament/Screens/Editors/LadderEditorScreen.cs +++ b/osu.Game.Tournament/Screens/Editors/LadderEditorScreen.cs @@ -26,6 +26,8 @@ namespace osu.Game.Tournament.Screens.Editors [Cached] private LadderEditorInfo editorInfo = new LadderEditorInfo(); + private WarningBox rightClickMessage; + protected override bool DrawLoserPaths => true; [BackgroundDependencyLoader] @@ -37,6 +39,16 @@ namespace osu.Game.Tournament.Screens.Editors Origin = Anchor.TopRight, Margin = new MarginPadding(5) }); + + AddInternal(rightClickMessage = new WarningBox("Right click to place and link matches")); + + LadderInfo.Matches.CollectionChanged += (_, __) => updateMessage(); + updateMessage(); + } + + private void updateMessage() + { + rightClickMessage.Alpha = LadderInfo.Matches.Count > 0 ? 0 : 1; } public void BeginJoin(TournamentMatch match, bool losers) diff --git a/osu.Game.Tournament/TournamentGame.cs b/osu.Game.Tournament/TournamentGame.cs index 307ee1c773..bbe4a53d8f 100644 --- a/osu.Game.Tournament/TournamentGame.cs +++ b/osu.Game.Tournament/TournamentGame.cs @@ -87,30 +87,7 @@ namespace osu.Game.Tournament }, } }, - 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 TournamentSpriteText - { - Text = "Please make the window wider", - Font = OsuFont.Torus.With(weight: FontWeight.Bold), - Colour = Color4.White, - Padding = new MarginPadding(20) - } - } - }, + heightWarning = new WarningBox("Please make the window wider"), new OsuContextMenuContainer { RelativeSizeAxes = Axes.Both, diff --git a/osu.Game.Tournament/WarningBox.cs b/osu.Game.Tournament/WarningBox.cs new file mode 100644 index 0000000000..814482aea4 --- /dev/null +++ b/osu.Game.Tournament/WarningBox.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; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; +using osuTK.Graphics; + +namespace osu.Game.Tournament +{ + internal class WarningBox : Container + { + public WarningBox(string text) + { + 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 TournamentSpriteText + { + Text = text, + Font = OsuFont.Torus.With(weight: FontWeight.Bold), + Colour = Color4.White, + Padding = new MarginPadding(20) + } + }; + } + } +} From 555b2196b734cbce88a2acd2a0832367bf1c33d4 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 2 Sep 2020 15:23:50 +0900 Subject: [PATCH 0835/1782] Add xmldoc to MusicController.ResetTrackAdjustments() --- osu.Game/Overlays/MusicController.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index 6d5b5d43cd..c831584248 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -382,6 +382,12 @@ namespace osu.Game.Overlays } } + /// + /// Resets the speed adjustments currently applied on and applies the mod adjustments if is true. + /// + /// + /// Does not reset speed adjustments applied directly to the beatmap track. + /// public void ResetTrackAdjustments() { CurrentTrack.ResetSpeedAdjustments(); From 6a765d2d765dd01f4bd145560d6b30542aae7813 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 2 Sep 2020 20:04:56 +0900 Subject: [PATCH 0836/1782] Add smooth fading between audio tracks on transition --- osu.Game/Overlays/MusicController.cs | 6 +++++- osu.Game/Screens/Menu/IntroScreen.cs | 7 +++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index c831584248..17877a69a5 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Linq; using JetBrains.Annotations; using osu.Framework.Allocation; +using osu.Framework.Audio; using osu.Framework.Audio.Track; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -341,10 +342,13 @@ namespace osu.Game.Overlays // but the mutation of the hierarchy is scheduled to avoid exceptions. Schedule(() => { - lastTrack.Expire(); + lastTrack.VolumeTo(0, 500, Easing.Out).Expire(); if (queuedTrack == CurrentTrack) + { AddInternal(queuedTrack); + queuedTrack.VolumeTo(0).Then().VolumeTo(1, 300, Easing.Out); + } else { // If the track has changed since the call to changeTrack, it is safe to dispose the diff --git a/osu.Game/Screens/Menu/IntroScreen.cs b/osu.Game/Screens/Menu/IntroScreen.cs index 884cbfe107..473e6b0364 100644 --- a/osu.Game/Screens/Menu/IntroScreen.cs +++ b/osu.Game/Screens/Menu/IntroScreen.cs @@ -12,6 +12,7 @@ using osu.Framework.Screens; using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.IO.Archives; +using osu.Game.Overlays; using osu.Game.Screens.Backgrounds; using osu.Game.Skinning; using osuTK; @@ -60,6 +61,9 @@ namespace osu.Game.Screens.Menu [Resolved] private AudioManager audio { get; set; } + [Resolved] + private MusicController musicController { get; set; } + /// /// Whether the is provided by osu! resources, rather than a user beatmap. /// Only valid during or after . @@ -167,6 +171,9 @@ namespace osu.Game.Screens.Menu Track = initialBeatmap.Track; UsingThemedIntro = !initialBeatmap.Track.IsDummyDevice; + // ensure the track starts at maximum volume + musicController.CurrentTrack.FinishTransforms(); + logo.MoveTo(new Vector2(0.5f)); logo.ScaleTo(Vector2.One); logo.Hide(); From 4459287b35f1a85461661f5d98042dbb2334b066 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 2 Sep 2020 20:25:12 +0900 Subject: [PATCH 0837/1782] Add filter control test scene --- .../SongSelect/TestSceneFilterControl.cs | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs new file mode 100644 index 0000000000..f89300661c --- /dev/null +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.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.Graphics; +using osu.Game.Screens.Select; + +namespace osu.Game.Tests.Visual.SongSelect +{ + public class TestSceneFilterControl : OsuTestScene + { + public TestSceneFilterControl() + { + Child = new FilterControl + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.X, + Height = FilterControl.HEIGHT, + }; + } + } +} From bb090a55e03b35c47192ef9c6f8bce46a84c74e3 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 2 Sep 2020 20:25:25 +0900 Subject: [PATCH 0838/1782] Add dropdown to filter control --- osu.Game/Screens/Select/FilterControl.cs | 196 +++++++++++++++++------ 1 file changed, 150 insertions(+), 46 deletions(-) diff --git a/osu.Game/Screens/Select/FilterControl.cs b/osu.Game/Screens/Select/FilterControl.cs index e111ec4b15..24ea4946af 100644 --- a/osu.Game/Screens/Select/FilterControl.cs +++ b/osu.Game/Screens/Select/FilterControl.cs @@ -2,12 +2,17 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Linq; +using JetBrains.Annotations; 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.UserInterface; using osu.Framework.Input.Events; +using osu.Game.Beatmaps; +using osu.Game.Collections; using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; @@ -21,7 +26,8 @@ namespace osu.Game.Screens.Select { public class FilterControl : Container { - public const float HEIGHT = 100; + public const float HEIGHT = 2 * side_margin + 85; + private const float side_margin = 20; public Action FilterChanged; @@ -41,6 +47,7 @@ namespace osu.Game.Screens.Select Sort = sortMode.Value, AllowConvertedBeatmaps = showConverted.Value, Ruleset = ruleset.Value, + Collection = collectionDropdown?.Current.Value }; if (!minimumStars.IsDefault) @@ -54,6 +61,7 @@ namespace osu.Game.Screens.Select } private SeekLimitedSearchTextBox searchTextBox; + private CollectionFilterDropdown collectionDropdown; public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => base.ReceivePositionalInputAt(screenSpacePos) || sortTabs.ReceivePositionalInputAt(screenSpacePos); @@ -90,73 +98,169 @@ namespace osu.Game.Screens.Select }, new Container { - Padding = new MarginPadding(20), + Padding = new MarginPadding(side_margin), RelativeSizeAxes = Axes.Both, Width = 0.5f, Anchor = Anchor.TopRight, Origin = Anchor.TopRight, - Children = new Drawable[] + Child = new GridContainer { - searchTextBox = new SeekLimitedSearchTextBox { RelativeSizeAxes = Axes.X }, - new Box + RelativeSizeAxes = Axes.Both, + RowDimensions = new[] { - RelativeSizeAxes = Axes.X, - Height = 1, - Colour = OsuColour.Gray(80), - Origin = Anchor.BottomLeft, - Anchor = Anchor.BottomLeft, + new Dimension(GridSizeMode.Absolute, 60), + new Dimension(GridSizeMode.Absolute, 5), + new Dimension(GridSizeMode.Absolute, 20), }, - new FillFlowContainer + Content = new[] { - Anchor = Anchor.BottomRight, - Origin = Anchor.BottomRight, - Direction = FillDirection.Horizontal, - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Spacing = new Vector2(OsuTabControl.HORIZONTAL_SPACING, 0), - Children = new Drawable[] + new Drawable[] { - new OsuTabControlCheckbox + new Container { - Text = "Show converted", - Current = config.GetBindable(OsuSetting.ShowConvertedBeatmaps), - Anchor = Anchor.BottomRight, - Origin = Anchor.BottomRight, - }, - sortTabs = new OsuTabControl + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + searchTextBox = new SeekLimitedSearchTextBox { RelativeSizeAxes = Axes.X }, + new Box + { + RelativeSizeAxes = Axes.X, + Height = 1, + Colour = OsuColour.Gray(80), + Origin = Anchor.BottomLeft, + Anchor = Anchor.BottomLeft, + }, + new FillFlowContainer + { + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomRight, + Direction = FillDirection.Horizontal, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Spacing = new Vector2(OsuTabControl.HORIZONTAL_SPACING, 0), + Children = new Drawable[] + { + new OsuTabControlCheckbox + { + Text = "Show converted", + Current = config.GetBindable(OsuSetting.ShowConvertedBeatmaps), + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomRight, + }, + sortTabs = new OsuTabControl + { + RelativeSizeAxes = Axes.X, + Width = 0.5f, + Height = 24, + AutoSort = true, + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomRight, + AccentColour = colours.GreenLight, + Current = { BindTarget = sortMode } + }, + new OsuSpriteText + { + Text = "Sort by", + Font = OsuFont.GetFont(size: 14), + Margin = new MarginPadding(5), + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomRight, + }, + } + }, + } + } + }, + null, + new Drawable[] + { + new Container { - RelativeSizeAxes = Axes.X, - Width = 0.5f, - Height = 24, - AutoSort = true, - Anchor = Anchor.BottomRight, - Origin = Anchor.BottomRight, - AccentColour = colours.GreenLight, - Current = { BindTarget = sortMode } - }, - new OsuSpriteText - { - Text = "Sort by", - Font = OsuFont.GetFont(size: 14), - Margin = new MarginPadding(5), - Anchor = Anchor.BottomRight, - Origin = Anchor.BottomRight, - }, - } - }, + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + collectionDropdown = new CollectionFilterDropdown + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + RelativeSizeAxes = Axes.X, + Width = 0.4f, + } + } + } + }, + } } } }; - searchTextBox.Current.ValueChanged += _ => FilterChanged?.Invoke(CreateCriteria()); + collectionDropdown.Current.ValueChanged += _ => updateCriteria(); + searchTextBox.Current.ValueChanged += _ => updateCriteria(); updateCriteria(); } + private class CollectionFilterDropdown : OsuDropdown + { + private readonly IBindableList collections = new BindableList(); + private readonly BindableList filters = new BindableList(); + + public CollectionFilterDropdown() + { + ItemSource = filters; + } + + [BackgroundDependencyLoader] + private void load(CollectionManager collectionManager) + { + collections.BindTo(collectionManager.Collections); + collections.CollectionChanged += (_, __) => updateItems(); + updateItems(); + } + + private void updateItems() + { + var selectedItem = SelectedItem?.Value?.Collection; + + filters.Clear(); + filters.Add(new CollectionFilter(null)); + filters.AddRange(collections.Select(c => new CollectionFilter(c))); + + Current.Value = filters.FirstOrDefault(f => f.Collection == selectedItem) ?? filters[0]; + } + + protected override string GenerateItemText(CollectionFilter item) => item.Collection?.Name ?? "All beatmaps"; + + protected override DropdownHeader CreateHeader() => new CollectionDropdownHeader(); + + private class CollectionDropdownHeader : OsuDropdownHeader + { + public CollectionDropdownHeader() + { + Height = 25; + Icon.Size = new Vector2(16); + Foreground.Padding = new MarginPadding { Top = 4, Bottom = 4, Left = 8, Right = 4 }; + } + } + } + + public class CollectionFilter + { + [CanBeNull] + public readonly BeatmapCollection Collection; + + public CollectionFilter([CanBeNull] BeatmapCollection collection) + { + Collection = collection; + } + + public virtual bool ContainsBeatmap(BeatmapInfo beatmap) + => Collection?.Beatmaps.Any(b => b.Equals(beatmap)) ?? true; + } + public void Deactivate() { searchTextBox.ReadOnly = true; - searchTextBox.HoldFocus = false; if (searchTextBox.HasFocus) GetContainingInputManager().ChangeFocus(searchTextBox); From 9dde37fe40b7a49e5743844de3bb42007d49ea9e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 2 Sep 2020 20:25:36 +0900 Subject: [PATCH 0839/1782] Hook up collection filter --- osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs | 3 +++ osu.Game/Screens/Select/FilterCriteria.cs | 2 ++ 2 files changed, 5 insertions(+) diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs index ed54c158db..8e5655e514 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs @@ -60,6 +60,9 @@ namespace osu.Game.Screens.Select.Carousel match &= terms.Any(term => term.IndexOf(criteriaTerm, StringComparison.InvariantCultureIgnoreCase) >= 0); } + if (match) + match &= criteria.Collection?.ContainsBeatmap(Beatmap) ?? true; + Filtered.Value = !match; } diff --git a/osu.Game/Screens/Select/FilterCriteria.cs b/osu.Game/Screens/Select/FilterCriteria.cs index 18be4fcac8..5a5c0e1b50 100644 --- a/osu.Game/Screens/Select/FilterCriteria.cs +++ b/osu.Game/Screens/Select/FilterCriteria.cs @@ -51,6 +51,8 @@ namespace osu.Game.Screens.Select } } + public FilterControl.CollectionFilter Collection; + public struct OptionalRange : IEquatable> where T : struct { From 6d5e155106d721940bb6a9f56822511986b86853 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 2 Sep 2020 20:44:26 +0900 Subject: [PATCH 0840/1782] Change to BindableList to notify of changes --- osu.Game/Collections/CollectionManager.cs | 4 +-- osu.Game/Screens/Select/FilterControl.cs | 41 ++++++++++++++++++++--- 2 files changed, 38 insertions(+), 7 deletions(-) diff --git a/osu.Game/Collections/CollectionManager.cs b/osu.Game/Collections/CollectionManager.cs index 302d892efb..3ba604cc39 100644 --- a/osu.Game/Collections/CollectionManager.cs +++ b/osu.Game/Collections/CollectionManager.cs @@ -74,9 +74,7 @@ namespace osu.Game.Collections for (int i = 0; i < collectionCount; i++) { var collection = new BeatmapCollection { Name = reader.ReadString() }; - int mapCount = reader.ReadInt32(); - collection.Beatmaps.Capacity = mapCount; for (int j = 0; j < mapCount; j++) { @@ -99,6 +97,6 @@ namespace osu.Game.Collections { public string Name; - public readonly List Beatmaps = new List(); + public readonly BindableList Beatmaps = new BindableList(); } } diff --git a/osu.Game/Screens/Select/FilterControl.cs b/osu.Game/Screens/Select/FilterControl.cs index 24ea4946af..58c27751fd 100644 --- a/osu.Game/Screens/Select/FilterControl.cs +++ b/osu.Game/Screens/Select/FilterControl.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Specialized; using System.Linq; using JetBrains.Annotations; using osu.Framework.Allocation; @@ -214,11 +215,21 @@ namespace osu.Game.Screens.Select private void load(CollectionManager collectionManager) { collections.BindTo(collectionManager.Collections); - collections.CollectionChanged += (_, __) => updateItems(); - updateItems(); + collections.CollectionChanged += (_, __) => collectionsChanged(); + collectionsChanged(); } - private void updateItems() + protected override void LoadComplete() + { + base.LoadComplete(); + + Current.BindValueChanged(filterChanged); + } + + /// + /// Occurs when a collection has been added or removed. + /// + private void collectionsChanged() { var selectedItem = SelectedItem?.Value?.Collection; @@ -226,7 +237,29 @@ namespace osu.Game.Screens.Select filters.Add(new CollectionFilter(null)); filters.AddRange(collections.Select(c => new CollectionFilter(c))); - Current.Value = filters.FirstOrDefault(f => f.Collection == selectedItem) ?? filters[0]; + Current.Value = filters.SingleOrDefault(f => f.Collection == selectedItem) ?? filters[0]; + } + + /// + /// Occurs when the selection has changed. + /// + private void filterChanged(ValueChangedEvent filter) + { + if (filter.OldValue?.Collection != null) + filter.OldValue.Collection.Beatmaps.CollectionChanged -= filterBeatmapsChanged; + + if (filter.NewValue?.Collection != null) + filter.NewValue.Collection.Beatmaps.CollectionChanged += filterBeatmapsChanged; + } + + /// + /// Occurs when the beatmaps contained by a have changed. + /// + private void filterBeatmapsChanged(object sender, NotifyCollectionChangedEventArgs e) + { + // The filtered beatmaps have changed, without the filter having changed itself. So a change in filter must be notified. + // Note that this does NOT propagate to bound bindables, so the FilterControl must bind directly to the value change event of this bindable. + Current.TriggerChange(); } protected override string GenerateItemText(CollectionFilter item) => item.Collection?.Name ?? "All beatmaps"; From 094ddecc9510f5e5e020c24b5952f979ad673741 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 2 Sep 2020 21:08:31 +0900 Subject: [PATCH 0841/1782] Add dropdowns to carousel items --- .../Carousel/DrawableCarouselBeatmap.cs | 26 ++++++++++ .../Carousel/DrawableCarouselBeatmapSet.cs | 48 +++++++++++++++++++ 2 files changed, 74 insertions(+) diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs index c559b4f8f5..760b888288 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Threading; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -17,6 +18,7 @@ using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; +using osu.Game.Collections; using osu.Game.Graphics; using osu.Game.Graphics.Backgrounds; using osu.Game.Graphics.Sprites; @@ -46,6 +48,9 @@ namespace osu.Game.Screens.Select.Carousel [Resolved] private BeatmapDifficultyManager difficultyManager { get; set; } + [Resolved] + private CollectionManager collectionManager { get; set; } + private IBindable starDifficultyBindable; private CancellationTokenSource starDifficultyCancellationSource; @@ -219,10 +224,31 @@ namespace osu.Game.Screens.Select.Carousel if (beatmap.OnlineBeatmapID.HasValue && beatmapOverlay != null) items.Add(new OsuMenuItem("Details", MenuItemType.Standard, () => beatmapOverlay.FetchAndShowBeatmap(beatmap.OnlineBeatmapID.Value))); + items.Add(new OsuMenuItem("Add to...") + { + Items = collectionManager.Collections.Take(3).Select(createCollectionMenuItem) + .Append(new OsuMenuItem("More...", MenuItemType.Standard, () => { })) + .ToArray() + }); + return items.ToArray(); } } + private MenuItem createCollectionMenuItem(BeatmapCollection collection) + { + return new ToggleMenuItem(collection.Name, MenuItemType.Standard, s => + { + if (s) + collection.Beatmaps.Add(beatmap); + else + collection.Beatmaps.Remove(beatmap); + }) + { + State = { Value = collection.Beatmaps.Contains(beatmap) } + }; + } + protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index 5acb6d1946..66851657bc 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -16,6 +16,7 @@ using osu.Framework.Input.Events; using osu.Framework.Localisation; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; +using osu.Game.Collections; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; @@ -34,6 +35,9 @@ namespace osu.Game.Screens.Select.Carousel [Resolved(CanBeNull = true)] private DialogOverlay dialogOverlay { get; set; } + [Resolved] + private CollectionManager collectionManager { get; set; } + private readonly BeatmapSetInfo beatmapSet; public DrawableCarouselBeatmapSet(CarouselBeatmapSet set) @@ -141,10 +145,54 @@ namespace osu.Game.Screens.Select.Carousel if (dialogOverlay != null) items.Add(new OsuMenuItem("Delete", MenuItemType.Destructive, () => dialogOverlay.Push(new BeatmapDeleteDialog(beatmapSet)))); + items.Add(new OsuMenuItem("Add all to...") + { + Items = collectionManager.Collections.Take(3).Select(createCollectionMenuItem) + .Append(new OsuMenuItem("More...", MenuItemType.Standard, () => { })) + .ToArray() + }); + return items.ToArray(); } } + private MenuItem createCollectionMenuItem(BeatmapCollection collection) + { + TernaryState state; + + var countExisting = beatmapSet.Beatmaps.Count(b => collection.Beatmaps.Contains(b)); + + if (countExisting == beatmapSet.Beatmaps.Count) + state = TernaryState.True; + else if (countExisting > 0) + state = TernaryState.Indeterminate; + else + state = TernaryState.False; + + return new TernaryStateMenuItem(collection.Name, MenuItemType.Standard, s => + { + foreach (var b in beatmapSet.Beatmaps) + { + switch (s) + { + case TernaryState.True: + if (collection.Beatmaps.Contains(b)) + continue; + + collection.Beatmaps.Add(b); + break; + + case TernaryState.False: + collection.Beatmaps.Remove(b); + break; + } + } + }) + { + State = { Value = state } + }; + } + private class PanelBackground : BufferedContainer { public PanelBackground(WorkingBeatmap working) From d363a5d164755423b23c5bd1c3531605d0866f2b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 2 Sep 2020 21:19:15 +0900 Subject: [PATCH 0842/1782] Add basic ordering --- osu.Game/Collections/CollectionManager.cs | 9 +++++++++ .../Screens/Select/Carousel/DrawableCarouselBeatmap.cs | 2 +- .../Select/Carousel/DrawableCarouselBeatmapSet.cs | 2 +- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/osu.Game/Collections/CollectionManager.cs b/osu.Game/Collections/CollectionManager.cs index 3ba604cc39..c18cc30427 100644 --- a/osu.Game/Collections/CollectionManager.cs +++ b/osu.Game/Collections/CollectionManager.cs @@ -98,5 +98,14 @@ namespace osu.Game.Collections public string Name; public readonly BindableList Beatmaps = new BindableList(); + + public DateTimeOffset LastModifyTime { get; private set; } + + public BeatmapCollection() + { + LastModifyTime = DateTimeOffset.UtcNow; + + Beatmaps.CollectionChanged += (_, __) => LastModifyTime = DateTimeOffset.Now; + } } } diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs index 760b888288..1bd4447248 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs @@ -226,7 +226,7 @@ namespace osu.Game.Screens.Select.Carousel items.Add(new OsuMenuItem("Add to...") { - Items = collectionManager.Collections.Take(3).Select(createCollectionMenuItem) + Items = collectionManager.Collections.OrderByDescending(c => c.LastModifyTime).Take(3).Select(createCollectionMenuItem) .Append(new OsuMenuItem("More...", MenuItemType.Standard, () => { })) .ToArray() }); diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index 66851657bc..e05b5ee951 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -147,7 +147,7 @@ namespace osu.Game.Screens.Select.Carousel items.Add(new OsuMenuItem("Add all to...") { - Items = collectionManager.Collections.Take(3).Select(createCollectionMenuItem) + Items = collectionManager.Collections.OrderByDescending(c => c.LastModifyTime).Take(3).Select(createCollectionMenuItem) .Append(new OsuMenuItem("More...", MenuItemType.Standard, () => { })) .ToArray() }); From 5ebead2bfd27a6df4c7150625f9d9e0da38b1116 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 2 Sep 2020 21:44:07 +0900 Subject: [PATCH 0843/1782] Prevent ValueChanged binds to external bindable --- osu.Game/Screens/Select/FilterControl.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Select/FilterControl.cs b/osu.Game/Screens/Select/FilterControl.cs index 58c27751fd..d49d0c57e6 100644 --- a/osu.Game/Screens/Select/FilterControl.cs +++ b/osu.Game/Screens/Select/FilterControl.cs @@ -204,6 +204,7 @@ namespace osu.Game.Screens.Select private class CollectionFilterDropdown : OsuDropdown { private readonly IBindableList collections = new BindableList(); + private readonly IBindableList beatmaps = new BindableList(); private readonly BindableList filters = new BindableList(); public CollectionFilterDropdown() @@ -223,7 +224,8 @@ namespace osu.Game.Screens.Select { base.LoadComplete(); - Current.BindValueChanged(filterChanged); + beatmaps.CollectionChanged += filterBeatmapsChanged; + Current.BindValueChanged(filterChanged, true); } /// @@ -246,10 +248,10 @@ namespace osu.Game.Screens.Select private void filterChanged(ValueChangedEvent filter) { if (filter.OldValue?.Collection != null) - filter.OldValue.Collection.Beatmaps.CollectionChanged -= filterBeatmapsChanged; + beatmaps.UnbindFrom(filter.OldValue.Collection.Beatmaps); if (filter.NewValue?.Collection != null) - filter.NewValue.Collection.Beatmaps.CollectionChanged += filterBeatmapsChanged; + beatmaps.BindTo(filter.NewValue.Collection.Beatmaps); } /// From 02a908752f61509ed45df94d6db41712647f11d3 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 2 Sep 2020 21:52:56 +0900 Subject: [PATCH 0844/1782] Fix stackoverflow --- 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 d49d0c57e6..fcaacef97b 100644 --- a/osu.Game/Screens/Select/FilterControl.cs +++ b/osu.Game/Screens/Select/FilterControl.cs @@ -224,7 +224,6 @@ namespace osu.Game.Screens.Select { base.LoadComplete(); - beatmaps.CollectionChanged += filterBeatmapsChanged; Current.BindValueChanged(filterChanged, true); } @@ -247,11 +246,15 @@ namespace osu.Game.Screens.Select /// private void filterChanged(ValueChangedEvent filter) { + beatmaps.CollectionChanged -= filterBeatmapsChanged; + if (filter.OldValue?.Collection != null) beatmaps.UnbindFrom(filter.OldValue.Collection.Beatmaps); if (filter.NewValue?.Collection != null) beatmaps.BindTo(filter.NewValue.Collection.Beatmaps); + + beatmaps.CollectionChanged += filterBeatmapsChanged; } /// From 686257167223fec69675cbe5d0b6c2999132c68f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 2 Sep 2020 22:02:50 +0900 Subject: [PATCH 0845/1782] Fix IconButton sometimes not recolourising --- osu.Game/Graphics/UserInterface/IconButton.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Graphics/UserInterface/IconButton.cs b/osu.Game/Graphics/UserInterface/IconButton.cs index d7e5666545..858f517985 100644 --- a/osu.Game/Graphics/UserInterface/IconButton.cs +++ b/osu.Game/Graphics/UserInterface/IconButton.cs @@ -24,7 +24,7 @@ namespace osu.Game.Graphics.UserInterface set { iconColour = value; - icon.Colour = value; + icon.FadeColour(value); } } From 661eac8f1dd0731fe0476038be5a3c37b72d3b95 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 2 Sep 2020 22:03:38 +0900 Subject: [PATCH 0846/1782] Add add/remove button to dropdown items --- osu.Game/Screens/Select/FilterControl.cs | 82 ++++++++++++++++++++++++ 1 file changed, 82 insertions(+) diff --git a/osu.Game/Screens/Select/FilterControl.cs b/osu.Game/Screens/Select/FilterControl.cs index fcaacef97b..31c3bd6d1c 100644 --- a/osu.Game/Screens/Select/FilterControl.cs +++ b/osu.Game/Screens/Select/FilterControl.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Specialized; +using System.Diagnostics; using System.Linq; using JetBrains.Annotations; using osu.Framework.Allocation; @@ -10,12 +11,14 @@ 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.UserInterface; using osu.Framework.Input.Events; using osu.Game.Beatmaps; using osu.Game.Collections; using osu.Game.Configuration; using osu.Game.Graphics; +using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets; @@ -271,6 +274,8 @@ namespace osu.Game.Screens.Select protected override DropdownHeader CreateHeader() => new CollectionDropdownHeader(); + protected override DropdownMenu CreateMenu() => new CollectionDropdownMenu(); + private class CollectionDropdownHeader : OsuDropdownHeader { public CollectionDropdownHeader() @@ -280,6 +285,83 @@ namespace osu.Game.Screens.Select Foreground.Padding = new MarginPadding { Top = 4, Bottom = 4, Left = 8, Right = 4 }; } } + + private class CollectionDropdownMenu : OsuDropdownMenu + { + protected override DrawableDropdownMenuItem CreateDrawableDropdownMenuItem(MenuItem item) => new CollectionDropdownMenuItem(item); + } + + private class CollectionDropdownMenuItem : OsuDropdownMenu.DrawableOsuDropdownMenuItem + { + [Resolved] + private OsuColour colours { get; set; } + + [Resolved] + private IBindable beatmap { get; set; } + + [CanBeNull] + private readonly BindableList collectionBeatmaps; + + private IconButton addOrRemoveButton; + + public CollectionDropdownMenuItem(MenuItem item) + : base(item) + { + collectionBeatmaps = ((DropdownMenuItem)item).Value.Collection?.Beatmaps.GetBoundCopy(); + } + + [BackgroundDependencyLoader] + private void load() + { + AddRangeInternal(new Drawable[] + { + addOrRemoveButton = new IconButton + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + X = -OsuScrollContainer.SCROLL_BAR_HEIGHT, + Scale = new Vector2(0.75f), + Alpha = collectionBeatmaps == null ? 0 : 1, + Action = addOrRemove + } + }); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + if (collectionBeatmaps != null) + { + collectionBeatmaps.CollectionChanged += (_, __) => collectionChanged(); + collectionChanged(); + } + } + + private void collectionChanged() + { + Debug.Assert(collectionBeatmaps != null); + + if (collectionBeatmaps.Contains(beatmap.Value.BeatmapInfo)) + { + addOrRemoveButton.Icon = FontAwesome.Solid.MinusSquare; + addOrRemoveButton.IconColour = colours.Red; + } + else + { + addOrRemoveButton.Icon = FontAwesome.Solid.PlusSquare; + addOrRemoveButton.IconColour = colours.Green; + } + } + + private void addOrRemove() + { + Debug.Assert(collectionBeatmaps != null); + + if (!collectionBeatmaps.Remove(beatmap.Value.BeatmapInfo)) + collectionBeatmaps.Add(beatmap.Value.BeatmapInfo); + } + } } public class CollectionFilter From 7fcbc3a814b24c78f8a2facf901112ec0e0bc561 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 2 Sep 2020 22:06:17 +0900 Subject: [PATCH 0847/1782] Respond to changes in beatmap --- 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 31c3bd6d1c..a8f9835240 100644 --- a/osu.Game/Screens/Select/FilterControl.cs +++ b/osu.Game/Screens/Select/FilterControl.cs @@ -334,7 +334,7 @@ namespace osu.Game.Screens.Select if (collectionBeatmaps != null) { collectionBeatmaps.CollectionChanged += (_, __) => collectionChanged(); - collectionChanged(); + beatmap.BindValueChanged(_ => collectionChanged(), true); } } From d83264f5385d2890f4b9ccd4e021ac77498e1d84 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 2 Sep 2020 22:56:13 +0900 Subject: [PATCH 0848/1782] Add max height --- osu.Game/Screens/Select/FilterControl.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game/Screens/Select/FilterControl.cs b/osu.Game/Screens/Select/FilterControl.cs index a8f9835240..0ae5e70fc2 100644 --- a/osu.Game/Screens/Select/FilterControl.cs +++ b/osu.Game/Screens/Select/FilterControl.cs @@ -288,6 +288,11 @@ namespace osu.Game.Screens.Select private class CollectionDropdownMenu : OsuDropdownMenu { + public CollectionDropdownMenu() + { + MaxHeight = 200; + } + protected override DrawableDropdownMenuItem CreateDrawableDropdownMenuItem(MenuItem item) => new CollectionDropdownMenuItem(item); } From b7adb4b1fd1c17b0044cb572b329fd7c96f1780f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 2 Sep 2020 23:31:37 +0900 Subject: [PATCH 0849/1782] Add background save support + read safety --- osu.Game/Collections/CollectionManager.cs | 130 +++++++++++++++++++--- 1 file changed, 112 insertions(+), 18 deletions(-) diff --git a/osu.Game/Collections/CollectionManager.cs b/osu.Game/Collections/CollectionManager.cs index c18cc30427..3f89866749 100644 --- a/osu.Game/Collections/CollectionManager.cs +++ b/osu.Game/Collections/CollectionManager.cs @@ -4,9 +4,12 @@ using System; using System.Collections.Generic; using System.IO; +using System.Threading; +using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics.Containers; +using osu.Framework.Logging; using osu.Framework.Platform; using osu.Game.Beatmaps; using osu.Game.IO.Legacy; @@ -15,8 +18,16 @@ namespace osu.Game.Collections { public class CollectionManager : CompositeDrawable { + /// + /// Database version in YYYYMMDD format (matching stable). + /// + private const int database_version = 30000000; + private const string database_name = "collection.db"; + [Resolved] + private GameHost host { get; set; } + public IBindableList Collections => collections; private readonly BindableList collections = new BindableList(); @@ -24,13 +35,17 @@ namespace osu.Game.Collections private BeatmapManager beatmaps { get; set; } [BackgroundDependencyLoader] - private void load(GameHost host) + private void load() { if (host.Storage.Exists(database_name)) { using (var stream = host.Storage.GetStream(database_name)) collections.AddRange(readCollection(stream)); } + + foreach (var c in collections) + c.Changed += backgroundSave; + collections.CollectionChanged += (_, __) => backgroundSave(); } /// @@ -64,37 +79,112 @@ namespace osu.Game.Collections { var result = new List(); - using (var reader = new SerializationReader(stream)) + try { - reader.ReadInt32(); // Version - - int collectionCount = reader.ReadInt32(); - result.Capacity = collectionCount; - - for (int i = 0; i < collectionCount; i++) + using (var sr = new SerializationReader(stream)) { - var collection = new BeatmapCollection { Name = reader.ReadString() }; - int mapCount = reader.ReadInt32(); + sr.ReadInt32(); // Version - for (int j = 0; j < mapCount; j++) + int collectionCount = sr.ReadInt32(); + result.Capacity = collectionCount; + + for (int i = 0; i < collectionCount; i++) { - string checksum = reader.ReadString(); + var collection = new BeatmapCollection { Name = sr.ReadString() }; + int mapCount = sr.ReadInt32(); - var beatmap = beatmaps.QueryBeatmap(b => b.MD5Hash == checksum); - if (beatmap != null) - collection.Beatmaps.Add(beatmap); + for (int j = 0; j < mapCount; j++) + { + string checksum = sr.ReadString(); + + var beatmap = beatmaps.QueryBeatmap(b => b.MD5Hash == checksum); + if (beatmap != null) + collection.Beatmaps.Add(beatmap); + } + + result.Add(collection); } - - result.Add(collection); } } + catch (Exception e) + { + Logger.Error(e, "Failed to read collections"); + } return result; } + + private readonly object saveLock = new object(); + private int lastSave; + private int saveFailures; + + /// + /// Perform a save with debounce. + /// + private void backgroundSave() + { + var current = Interlocked.Increment(ref lastSave); + Task.Delay(100).ContinueWith(task => + { + if (current != lastSave) + return; + + if (!save()) + backgroundSave(); + }); + } + + private bool save() + { + lock (saveLock) + { + Interlocked.Increment(ref lastSave); + + try + { + // This is NOT thread-safe!! + + using (var sw = new SerializationWriter(host.Storage.GetStream(database_name, FileAccess.Write))) + { + sw.Write(database_version); + sw.Write(collections.Count); + + foreach (var c in collections) + { + sw.Write(c.Name); + sw.Write(c.Beatmaps.Count); + + foreach (var b in c.Beatmaps) + sw.Write(b.MD5Hash); + } + } + + saveFailures = 0; + return true; + } + catch (Exception e) + { + // Since this code is not thread-safe, we may run into random exceptions (such as collection enumeration or out of range indexing). + // Failures are thus only alerted if they exceed a threshold to indicate "actual" errors. + if (++saveFailures >= 10) + { + Logger.Error(e, "Failed to save collections"); + saveFailures = 0; + } + } + + return false; + } + } } public class BeatmapCollection { + /// + /// Invoked whenever any change occurs on this . + /// + public event Action Changed; + public string Name; public readonly BindableList Beatmaps = new BindableList(); @@ -105,7 +195,11 @@ namespace osu.Game.Collections { LastModifyTime = DateTimeOffset.UtcNow; - Beatmaps.CollectionChanged += (_, __) => LastModifyTime = DateTimeOffset.Now; + Beatmaps.CollectionChanged += (_, __) => + { + LastModifyTime = DateTimeOffset.Now; + Changed?.Invoke(); + }; } } } From fd3ab417312e56ded809bb98a86f54f23ddaa0df Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 2 Sep 2020 23:32:08 +0900 Subject: [PATCH 0850/1782] Save on disposal --- osu.Game/Collections/CollectionManager.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game/Collections/CollectionManager.cs b/osu.Game/Collections/CollectionManager.cs index 3f89866749..5427f6a489 100644 --- a/osu.Game/Collections/CollectionManager.cs +++ b/osu.Game/Collections/CollectionManager.cs @@ -176,6 +176,12 @@ namespace osu.Game.Collections return false; } } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + save(); + } } public class BeatmapCollection From fca03242644bf67d679158588b4224ac0b1bfd57 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 2 Sep 2020 23:34:38 +0900 Subject: [PATCH 0851/1782] Disallow being able to add dummy beatmap --- osu.Game/Screens/Select/FilterControl.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Screens/Select/FilterControl.cs b/osu.Game/Screens/Select/FilterControl.cs index 0ae5e70fc2..0b85ae0e6a 100644 --- a/osu.Game/Screens/Select/FilterControl.cs +++ b/osu.Game/Screens/Select/FilterControl.cs @@ -347,6 +347,8 @@ namespace osu.Game.Screens.Select { Debug.Assert(collectionBeatmaps != null); + addOrRemoveButton.Enabled.Value = !beatmap.IsDefault; + if (collectionBeatmaps.Contains(beatmap.Value.BeatmapInfo)) { addOrRemoveButton.Icon = FontAwesome.Solid.MinusSquare; From ae1de1adcb7b95e84b60f28d84cf67a21f9034f0 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 2 Sep 2020 23:42:44 +0900 Subject: [PATCH 0852/1782] Adjust to prevent runaway errors --- osu.Game/Collections/CollectionManager.cs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/osu.Game/Collections/CollectionManager.cs b/osu.Game/Collections/CollectionManager.cs index 5427f6a489..1388a27806 100644 --- a/osu.Game/Collections/CollectionManager.cs +++ b/osu.Game/Collections/CollectionManager.cs @@ -159,18 +159,16 @@ namespace osu.Game.Collections } } - saveFailures = 0; + if (saveFailures < 10) + saveFailures = 0; return true; } catch (Exception e) { // Since this code is not thread-safe, we may run into random exceptions (such as collection enumeration or out of range indexing). - // Failures are thus only alerted if they exceed a threshold to indicate "actual" errors. - if (++saveFailures >= 10) - { + // Failures are thus only alerted if they exceed a threshold (once) to indicate "actual" errors having occurred. + if (++saveFailures == 10) Logger.Error(e, "Failed to save collections"); - saveFailures = 0; - } } return false; From 39c304d008ca981ffae95004db43c57789f57435 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 2 Sep 2020 23:47:42 +0900 Subject: [PATCH 0853/1782] Adjust error messages --- osu.Game/Collections/CollectionManager.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Collections/CollectionManager.cs b/osu.Game/Collections/CollectionManager.cs index 1388a27806..e6eed40dfc 100644 --- a/osu.Game/Collections/CollectionManager.cs +++ b/osu.Game/Collections/CollectionManager.cs @@ -108,7 +108,7 @@ namespace osu.Game.Collections } catch (Exception e) { - Logger.Error(e, "Failed to read collections"); + Logger.Error(e, "Failed to read collection database."); } return result; @@ -168,7 +168,7 @@ namespace osu.Game.Collections // Since this code is not thread-safe, we may run into random exceptions (such as collection enumeration or out of range indexing). // Failures are thus only alerted if they exceed a threshold (once) to indicate "actual" errors having occurred. if (++saveFailures == 10) - Logger.Error(e, "Failed to save collections"); + Logger.Error(e, "Failed to save collection database!"); } return false; From a56f9d6770e0ea9f3392a59dd2a2e99706c2654e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 3 Sep 2020 00:08:33 +0900 Subject: [PATCH 0854/1782] Implement collection import --- osu.Game/Collections/CollectionManager.cs | 88 +++++++++++++------ osu.Game/OsuGame.cs | 2 + osu.Game/OsuGameBase.cs | 5 +- .../Sections/Maintenance/GeneralSettings.cs | 45 +++++++--- 4 files changed, 99 insertions(+), 41 deletions(-) diff --git a/osu.Game/Collections/CollectionManager.cs b/osu.Game/Collections/CollectionManager.cs index e6eed40dfc..20bf96da9d 100644 --- a/osu.Game/Collections/CollectionManager.cs +++ b/osu.Game/Collections/CollectionManager.cs @@ -4,8 +4,10 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; using System.Threading; using System.Threading.Tasks; +using osu.Framework; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics.Containers; @@ -25,12 +27,13 @@ namespace osu.Game.Collections private const string database_name = "collection.db"; + public readonly BindableList Collections = new BindableList(); + + public bool SupportsImportFromStable => RuntimeInfo.IsDesktop; + [Resolved] private GameHost host { get; set; } - public IBindableList Collections => collections; - private readonly BindableList collections = new BindableList(); - [Resolved] private BeatmapManager beatmaps { get; set; } @@ -40,12 +43,12 @@ namespace osu.Game.Collections if (host.Storage.Exists(database_name)) { using (var stream = host.Storage.GetStream(database_name)) - collections.AddRange(readCollection(stream)); + importCollections(readCollections(stream)); } - foreach (var c in collections) + foreach (var c in Collections) c.Changed += backgroundSave; - collections.CollectionChanged += (_, __) => backgroundSave(); + Collections.CollectionChanged += (_, __) => backgroundSave(); } /// @@ -56,26 +59,55 @@ namespace osu.Game.Collections /// /// This is a temporary method and will likely be replaced by a full-fledged (and more correctly placed) migration process in the future. /// - // public Task ImportFromStableAsync() - // { - // var stable = GetStableStorage?.Invoke(); - // - // if (stable == null) - // { - // Logger.Log("No osu!stable installation available!", LoggingTarget.Information, LogLevel.Error); - // return Task.CompletedTask; - // } - // - // if (!stable.ExistsDirectory(database_name)) - // { - // // This handles situations like when the user does not have a Skins folder - // Logger.Log($"No {database_name} folder available in osu!stable installation", LoggingTarget.Information, LogLevel.Error); - // return Task.CompletedTask; - // } - // - // return Task.Run(async () => await Import(GetStableImportPaths(GetStableStorage()).Select(f => stable.GetFullPath(f)).ToArray())); - // } - private List readCollection(Stream stream) + public Task ImportFromStableAsync() + { + var stable = GetStableStorage?.Invoke(); + + if (stable == null) + { + Logger.Log("No osu!stable installation available!", LoggingTarget.Information, LogLevel.Error); + return Task.CompletedTask; + } + + if (!stable.Exists(database_name)) + { + // This handles situations like when the user does not have a collections.db file + Logger.Log($"No {database_name} available in osu!stable installation", LoggingTarget.Information, LogLevel.Error); + return Task.CompletedTask; + } + + return Task.Run(() => + { + var storage = GetStableStorage(); + + if (storage.Exists(database_name)) + { + using (var stream = storage.GetStream(database_name)) + { + var collection = readCollections(stream); + Schedule(() => importCollections(collection)); + } + } + }); + } + + private void importCollections(List newCollections) + { + foreach (var newCol in newCollections) + { + var existing = Collections.FirstOrDefault(c => c.Name == newCol.Name); + if (existing == null) + Collections.Add(existing = new BeatmapCollection { Name = newCol.Name }); + + foreach (var newBeatmap in newCol.Beatmaps) + { + if (!existing.Beatmaps.Contains(newBeatmap)) + existing.Beatmaps.Add(newBeatmap); + } + } + } + + private List readCollections(Stream stream) { var result = new List(); @@ -147,9 +179,9 @@ namespace osu.Game.Collections using (var sw = new SerializationWriter(host.Storage.GetStream(database_name, FileAccess.Write))) { sw.Write(database_version); - sw.Write(collections.Count); + sw.Write(Collections.Count); - foreach (var c in collections) + foreach (var c in Collections) { sw.Write(c.Name); sw.Write(c.Beatmaps.Count); diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 053eb01dcd..5008a3cf3b 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -549,6 +549,8 @@ namespace osu.Game ScoreManager.GetStableStorage = GetStorageForStableInstall; ScoreManager.PresentImport = items => PresentScore(items.First()); + CollectionManager.GetStableStorage = GetStorageForStableInstall; + Container logoContainer; BackButton.Receptor receptor; diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 3ba164e87f..d512f57ca5 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -225,9 +225,8 @@ namespace osu.Game dependencies.Cache(difficultyManager); AddInternal(difficultyManager); - var collectionManager = new CollectionManager(); - dependencies.Cache(collectionManager); - AddInternal(collectionManager); + dependencies.Cache(CollectionManager = new CollectionManager()); + AddInternal(CollectionManager); dependencies.Cache(KeyBindingStore = new KeyBindingStore(contextFactory, RulesetStore)); dependencies.Cache(SettingsStore = new SettingsStore(contextFactory)); diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs index 832673703b..21a5ed6b31 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs @@ -6,6 +6,7 @@ using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Game.Beatmaps; +using osu.Game.Collections; using osu.Game.Graphics.UserInterface; using osu.Game.Scoring; using osu.Game.Skinning; @@ -19,6 +20,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance private TriangleButton importBeatmapsButton; private TriangleButton importScoresButton; private TriangleButton importSkinsButton; + private TriangleButton importCollectionsButton; private TriangleButton deleteBeatmapsButton; private TriangleButton deleteScoresButton; private TriangleButton deleteSkinsButton; @@ -26,7 +28,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance private TriangleButton undeleteButton; [BackgroundDependencyLoader] - private void load(BeatmapManager beatmaps, ScoreManager scores, SkinManager skins, DialogOverlay dialogOverlay) + private void load(BeatmapManager beatmaps, ScoreManager scores, SkinManager skins, CollectionManager collections, DialogOverlay dialogOverlay) { if (beatmaps.SupportsImportFromStable) { @@ -93,20 +95,43 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance }); } - AddRange(new Drawable[] + Add(deleteSkinsButton = new DangerousSettingsButton { - deleteSkinsButton = new DangerousSettingsButton + Text = "Delete ALL skins", + Action = () => { - Text = "Delete ALL skins", + dialogOverlay?.Push(new DeleteAllBeatmapsDialog(() => + { + deleteSkinsButton.Enabled.Value = false; + Task.Run(() => skins.Delete(skins.GetAllUserSkins())).ContinueWith(t => Schedule(() => deleteSkinsButton.Enabled.Value = true)); + })); + } + }); + + if (collections.SupportsImportFromStable) + { + Add(importCollectionsButton = new SettingsButton + { + Text = "Import collections from stable", Action = () => { - dialogOverlay?.Push(new DeleteAllBeatmapsDialog(() => - { - deleteSkinsButton.Enabled.Value = false; - Task.Run(() => skins.Delete(skins.GetAllUserSkins())).ContinueWith(t => Schedule(() => deleteSkinsButton.Enabled.Value = true)); - })); + importCollectionsButton.Enabled.Value = false; + collections.ImportFromStableAsync().ContinueWith(t => Schedule(() => importCollectionsButton.Enabled.Value = true)); } - }, + }); + } + + Add(new DangerousSettingsButton + { + Text = "Delete ALL collections", + Action = () => + { + dialogOverlay?.Push(new DeleteAllBeatmapsDialog(() => collections.Collections.Clear())); + } + }); + + AddRange(new Drawable[] + { restoreButton = new SettingsButton { Text = "Restore all hidden difficulties", From 3fc6a74fdfa1bfd30c05b4748b7712c97530a923 Mon Sep 17 00:00:00 2001 From: Lucas A Date: Wed, 2 Sep 2020 19:55:46 +0200 Subject: [PATCH 0855/1782] Expose an immutable bindable in interface. --- osu.Game/Screens/IOsuScreen.cs | 2 +- osu.Game/Screens/OsuScreen.cs | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/IOsuScreen.cs b/osu.Game/Screens/IOsuScreen.cs index ead8e4bc22..e19037c2c4 100644 --- a/osu.Game/Screens/IOsuScreen.cs +++ b/osu.Game/Screens/IOsuScreen.cs @@ -41,7 +41,7 @@ namespace osu.Game.Screens /// /// Whether overlays should be able to be opened when this screen is current. /// - Bindable OverlayActivationMode { get; } + IBindable OverlayActivationMode { get; } /// /// The amount of parallax to be applied while this screen is displayed. diff --git a/osu.Game/Screens/OsuScreen.cs b/osu.Game/Screens/OsuScreen.cs index c10deaf1e5..cb8f2d21fe 100644 --- a/osu.Game/Screens/OsuScreen.cs +++ b/osu.Game/Screens/OsuScreen.cs @@ -48,7 +48,9 @@ namespace osu.Game.Screens /// protected virtual OverlayActivation InitialOverlayActivationMode => OverlayActivation.All; - public Bindable OverlayActivationMode { get; } + protected readonly Bindable OverlayActivationMode; + + IBindable IOsuScreen.OverlayActivationMode => OverlayActivationMode; public virtual bool CursorVisible => true; From 754274a146522ce3a8e69bda0ff4541819b539a2 Mon Sep 17 00:00:00 2001 From: Lucas A Date: Wed, 2 Sep 2020 20:55:26 +0200 Subject: [PATCH 0856/1782] Fix and add XMLDoc --- osu.Game/OsuGame.cs | 3 +++ osu.Game/Screens/OsuScreen.cs | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index e6d96df927..31926a6845 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -88,6 +88,9 @@ namespace osu.Game private IdleTracker idleTracker; + /// + /// Whether overlays should be able to be opened game-wide. Value is sourced from the current active screen. + /// public readonly IBindable OverlayActivationMode = new Bindable(); protected OsuScreenStack ScreenStack; diff --git a/osu.Game/Screens/OsuScreen.cs b/osu.Game/Screens/OsuScreen.cs index cb8f2d21fe..a44d14fb5c 100644 --- a/osu.Game/Screens/OsuScreen.cs +++ b/osu.Game/Screens/OsuScreen.cs @@ -44,7 +44,7 @@ namespace osu.Game.Screens public virtual bool HideOverlaysOnEnter => false; /// - /// The initial initial overlay activation mode to use when this screen is entered for the first time. + /// The initial overlay activation mode to use when this screen is entered for the first time. /// protected virtual OverlayActivation InitialOverlayActivationMode => OverlayActivation.All; From e7eaaf8b02efd43448b6dbbb11b4e98e7062aca7 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 3 Sep 2020 04:42:05 +0300 Subject: [PATCH 0857/1782] Bring legacy slider border width closer to osu!stable --- osu.Game.Rulesets.Osu/Skinning/LegacySliderBody.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacySliderBody.cs b/osu.Game.Rulesets.Osu/Skinning/LegacySliderBody.cs index 21df49d80b..28277ac443 100644 --- a/osu.Game.Rulesets.Osu/Skinning/LegacySliderBody.cs +++ b/osu.Game.Rulesets.Osu/Skinning/LegacySliderBody.cs @@ -18,6 +18,8 @@ namespace osu.Game.Rulesets.Osu.Skinning { private const float shadow_portion = 1 - (OsuLegacySkinTransformer.LEGACY_CIRCLE_RADIUS / OsuHitObject.OBJECT_RADIUS); + protected new float CalculatedBorderPortion => base.CalculatedBorderPortion * 0.77f; + 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 5180d71fd9fda153ac8c895b5fad8cc7c345d0f7 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 3 Sep 2020 06:09:52 +0300 Subject: [PATCH 0858/1782] Attach an inline comment explaining how the value was reached --- osu.Game.Rulesets.Osu/Skinning/LegacySliderBody.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacySliderBody.cs b/osu.Game.Rulesets.Osu/Skinning/LegacySliderBody.cs index 28277ac443..aad8b189d9 100644 --- a/osu.Game.Rulesets.Osu/Skinning/LegacySliderBody.cs +++ b/osu.Game.Rulesets.Osu/Skinning/LegacySliderBody.cs @@ -18,7 +18,9 @@ namespace osu.Game.Rulesets.Osu.Skinning { private const float shadow_portion = 1 - (OsuLegacySkinTransformer.LEGACY_CIRCLE_RADIUS / OsuHitObject.OBJECT_RADIUS); - protected new float CalculatedBorderPortion => base.CalculatedBorderPortion * 0.77f; + protected new float CalculatedBorderPortion + // Roughly matches osu!stable's slider border portions. + => base.CalculatedBorderPortion * 0.77f; public new Color4 AccentColour => new Color4(base.AccentColour.R, base.AccentColour.G, base.AccentColour.B, base.AccentColour.A * 0.70f); From 547c8090e5a5ae5d3c232debdfe5cbb95ce2f8ca Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 3 Sep 2020 13:13:48 +0900 Subject: [PATCH 0859/1782] Improve game exit music fade --- osu.Game/Screens/Menu/IntroScreen.cs | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Menu/IntroScreen.cs b/osu.Game/Screens/Menu/IntroScreen.cs index 473e6b0364..bed8dbcdcb 100644 --- a/osu.Game/Screens/Menu/IntroScreen.cs +++ b/osu.Game/Screens/Menu/IntroScreen.cs @@ -46,8 +46,6 @@ namespace osu.Game.Screens.Menu protected ITrack Track { get; private set; } - private readonly BindableDouble exitingVolumeFade = new BindableDouble(1); - private const int exit_delay = 3000; private SampleChannel seeya; @@ -127,17 +125,29 @@ namespace osu.Game.Screens.Menu this.FadeIn(300); double fadeOutTime = exit_delay; + // we also handle the exit transition. if (MenuVoice.Value) + { seeya.Play(); + + // if playing the outro voice, we have more time to have fun with the background track. + // initially fade to almost silent then ramp out over the remaining time. + const double initial_fade = 200; + musicController.CurrentTrack + .VolumeTo(0.03f, initial_fade).Then() + .VolumeTo(0, fadeOutTime - initial_fade, Easing.In); + } else + { fadeOutTime = 500; - audio.AddAdjustment(AdjustableProperty.Volume, exitingVolumeFade); - this.TransformBindableTo(exitingVolumeFade, 0, fadeOutTime).OnComplete(_ => this.Exit()); + // if outro voice is turned off, just do a simple fade out. + musicController.CurrentTrack.VolumeTo(0, fadeOutTime, Easing.Out); + } //don't want to fade out completely else we will stop running updates. - Game.FadeTo(0.01f, fadeOutTime); + Game.FadeTo(0.01f, fadeOutTime).OnComplete(_ => this.Exit()); base.OnResuming(last); } From 2f42c57f4b7cbe4a9ae4f2ab0ed5fb5d2e65a53d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 3 Sep 2020 13:15:16 +0900 Subject: [PATCH 0860/1782] Add safeties to ensure the current track doesn't loop or change --- osu.Game/Screens/Menu/IntroScreen.cs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Menu/IntroScreen.cs b/osu.Game/Screens/Menu/IntroScreen.cs index bed8dbcdcb..1df5c503d6 100644 --- a/osu.Game/Screens/Menu/IntroScreen.cs +++ b/osu.Game/Screens/Menu/IntroScreen.cs @@ -126,6 +126,12 @@ namespace osu.Game.Screens.Menu double fadeOutTime = exit_delay; + var track = musicController.CurrentTrack; + + // ensure the track doesn't change or loop as we are exiting. + track.Looping = false; + Beatmap.Disabled = true; + // we also handle the exit transition. if (MenuVoice.Value) { @@ -134,16 +140,16 @@ namespace osu.Game.Screens.Menu // if playing the outro voice, we have more time to have fun with the background track. // initially fade to almost silent then ramp out over the remaining time. const double initial_fade = 200; - musicController.CurrentTrack - .VolumeTo(0.03f, initial_fade).Then() - .VolumeTo(0, fadeOutTime - initial_fade, Easing.In); + track + .VolumeTo(0.03f, initial_fade).Then() + .VolumeTo(0, fadeOutTime - initial_fade, Easing.In); } else { fadeOutTime = 500; // if outro voice is turned off, just do a simple fade out. - musicController.CurrentTrack.VolumeTo(0, fadeOutTime, Easing.Out); + track.VolumeTo(0, fadeOutTime, Easing.Out); } //don't want to fade out completely else we will stop running updates. From e0328445709f5218befafe0eb091ab7d0e1e738a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 24 Aug 2020 19:38:05 +0900 Subject: [PATCH 0861/1782] Start with a fresh beatmap when entering editor from main menu --- osu.Game/Beatmaps/BeatmapManager.cs | 25 +++++++++++++++++++ .../Beatmaps/BeatmapManager_WorkingBeatmap.cs | 3 +++ .../Menus/ScreenSelectionTabControl.cs | 2 -- osu.Game/Screens/Edit/Editor.cs | 9 +++++++ osu.Game/Screens/Menu/MainMenu.cs | 6 ++++- 5 files changed, 42 insertions(+), 3 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 0cadcf5947..9289ed29d9 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -27,6 +27,7 @@ using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Rulesets; using osu.Game.Rulesets.Objects; +using osu.Game.Users; using Decoder = osu.Game.Beatmaps.Formats.Decoder; namespace osu.Game.Beatmaps @@ -94,6 +95,30 @@ namespace osu.Game.Beatmaps protected override bool ShouldDeleteArchive(string path) => Path.GetExtension(path)?.ToLowerInvariant() == ".osz"; + public WorkingBeatmap CreateNew(RulesetInfo ruleset) + { + var set = new BeatmapSetInfo + { + Metadata = new BeatmapMetadata + { + Artist = "unknown", + Title = "unknown", + Author = User.SYSTEM_USER, + }, + Beatmaps = new List + { + new BeatmapInfo + { + BaseDifficulty = new BeatmapDifficulty(), + Ruleset = ruleset + } + } + }; + + var working = Import(set).Result; + return GetWorkingBeatmap(working.Beatmaps.First()); + } + protected override async Task Populate(BeatmapSetInfo beatmapSet, ArchiveReader archive, CancellationToken cancellationToken = default) { if (archive != null) diff --git a/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs b/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs index 92199789ec..63b1647f33 100644 --- a/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs @@ -33,6 +33,9 @@ namespace osu.Game.Beatmaps protected override IBeatmap GetBeatmap() { + if (BeatmapInfo.Path == null) + return BeatmapInfo.Ruleset.CreateInstance().CreateBeatmapConverter(new Beatmap()).Beatmap; + try { using (var stream = new LineBufferedReader(store.GetStream(getPathForFile(BeatmapInfo.Path)))) diff --git a/osu.Game/Screens/Edit/Components/Menus/ScreenSelectionTabControl.cs b/osu.Game/Screens/Edit/Components/Menus/ScreenSelectionTabControl.cs index 089da4f222..b8bc5cdf36 100644 --- a/osu.Game/Screens/Edit/Components/Menus/ScreenSelectionTabControl.cs +++ b/osu.Game/Screens/Edit/Components/Menus/ScreenSelectionTabControl.cs @@ -32,8 +32,6 @@ namespace osu.Game.Screens.Edit.Components.Menus Height = 1, Colour = Color4.White.Opacity(0.2f), }); - - Current.Value = EditorScreenMode.Compose; } [BackgroundDependencyLoader] diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index e178459d5c..dfba5f457b 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -89,6 +89,14 @@ namespace osu.Game.Screens.Edit // todo: remove caching of this and consume via editorBeatmap? dependencies.Cache(beatDivisor); + bool isNewBeatmap = false; + + if (Beatmap.Value is DummyWorkingBeatmap) + { + isNewBeatmap = true; + Beatmap.Value = beatmapManager.CreateNew(Ruleset.Value); + } + try { playableBeatmap = Beatmap.Value.GetPlayableBeatmap(Beatmap.Value.BeatmapInfo.Ruleset); @@ -148,6 +156,7 @@ namespace osu.Game.Screens.Edit Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, RelativeSizeAxes = Axes.Both, + Mode = { Value = isNewBeatmap ? EditorScreenMode.SongSetup : EditorScreenMode.Compose }, Items = new[] { new MenuItem("File") diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index fac9e9eb49..e0ac19cbaf 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -98,7 +98,11 @@ namespace osu.Game.Screens.Menu { buttons = new ButtonSystem { - OnEdit = delegate { this.Push(new Editor()); }, + OnEdit = delegate + { + Beatmap.SetDefault(); + this.Push(new Editor()); + }, OnSolo = onSolo, OnMulti = delegate { this.Push(new Multiplayer()); }, OnExit = confirmAndExit, From faf9b0a52864b2d5f8beedb12ab02cefb6bdb15a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 1 Sep 2020 15:48:13 +0900 Subject: [PATCH 0862/1782] Fix hard crash when trying to retrieve a beatmap's track when no file is present --- osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs b/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs index 63b1647f33..9e4c29a8d4 100644 --- a/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs @@ -70,6 +70,9 @@ namespace osu.Game.Beatmaps protected override Track GetBeatmapTrack() { + if (Metadata?.AudioFile == null) + return null; + try { return trackStore.Get(getPathForFile(Metadata.AudioFile)); @@ -83,6 +86,9 @@ namespace osu.Game.Beatmaps protected override Waveform GetWaveform() { + if (Metadata?.AudioFile == null) + return null; + try { var trackData = store.GetStream(getPathForFile(Metadata.AudioFile)); From 218cc39a4c46ce467264d11600a15b1e169795de Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 1 Sep 2020 15:49:21 +0900 Subject: [PATCH 0863/1782] Avoid throwing exceptions when MutatePath is called with null path --- osu.Game/IO/WrappedStorage.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/IO/WrappedStorage.cs b/osu.Game/IO/WrappedStorage.cs index 1dd3afbfae..766e36ef14 100644 --- a/osu.Game/IO/WrappedStorage.cs +++ b/osu.Game/IO/WrappedStorage.cs @@ -25,7 +25,7 @@ namespace osu.Game.IO this.subPath = subPath; } - protected virtual string MutatePath(string path) => !string.IsNullOrEmpty(subPath) ? Path.Combine(subPath, path) : path; + protected virtual string MutatePath(string path) => !string.IsNullOrEmpty(subPath) && !string.IsNullOrEmpty(path) ? Path.Combine(subPath, path) : path; protected virtual void ChangeTargetStorage(Storage newStorage) { From e80ef341d2663ea07c272154b8ddb1e0bde13467 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 1 Sep 2020 15:50:08 +0900 Subject: [PATCH 0864/1782] Allow UpdateFile to be called when a previous file doesn't exist --- osu.Game/Database/ArchiveModelManager.cs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index 915d980d24..49d7edd56c 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -397,15 +397,24 @@ namespace osu.Game.Database } } + /// + /// Update an existing file, or create a new entry if not already part of the 's files. + /// + /// The item to operate on. + /// The file model to be updated or added. + /// The new file contents. public void UpdateFile(TModel model, TFileModel file, Stream contents) { using (var usage = ContextFactory.GetForWrite()) { // Dereference the existing file info, since the file model will be removed. - Files.Dereference(file.FileInfo); + if (file.FileInfo != null) + { + Files.Dereference(file.FileInfo); - // Remove the file model. - usage.Context.Set().Remove(file); + // Remove the file model. + usage.Context.Set().Remove(file); + } // Add the new file info and containing file model. model.Files.Remove(file); From e337e6b3b0d804c40aac0667471c52a205584a20 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 1 Sep 2020 18:55:49 +0900 Subject: [PATCH 0865/1782] Use a more correct filename when saving --- osu.Game/Beatmaps/BeatmapManager.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 9289ed29d9..fee7a71df4 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -237,10 +237,20 @@ namespace osu.Game.Beatmaps using (ContextFactory.GetForWrite()) { var beatmapInfo = setInfo.Beatmaps.Single(b => b.ID == info.ID); + var metadata = beatmapInfo.Metadata ?? setInfo.Metadata; + + // metadata may have changed; update the path with the standard format. + beatmapInfo.Path = $"{metadata.Artist} - {metadata.Title} ({metadata.Author}) [{beatmapInfo.Version}]"; + beatmapInfo.MD5Hash = stream.ComputeMD5Hash(); + var fileInfo = setInfo.Files.SingleOrDefault(f => string.Equals(f.Filename, beatmapInfo.Path, StringComparison.OrdinalIgnoreCase)) ?? new BeatmapSetFileInfo + { + Filename = beatmapInfo.Path + }; + stream.Seek(0, SeekOrigin.Begin); - UpdateFile(setInfo, setInfo.Files.Single(f => string.Equals(f.Filename, info.Path, StringComparison.OrdinalIgnoreCase)), stream); + UpdateFile(setInfo, fileInfo, stream); } } From d849f7f2b5b83b76b1e2a1803aadcbb10f6e3d1a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 1 Sep 2020 18:56:49 +0900 Subject: [PATCH 0866/1782] Use the local user's username when saving a new beatmap --- osu.Game/Beatmaps/BeatmapManager.cs | 18 +++++++++++------- osu.Game/Screens/Edit/Editor.cs | 14 +++++++++++++- 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index fee7a71df4..325e6c6e98 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -97,20 +97,24 @@ namespace osu.Game.Beatmaps public WorkingBeatmap CreateNew(RulesetInfo ruleset) { + var metadata = new BeatmapMetadata + { + Artist = "artist", + Title = "title", + Author = User.SYSTEM_USER, + }; + var set = new BeatmapSetInfo { - Metadata = new BeatmapMetadata - { - Artist = "unknown", - Title = "unknown", - Author = User.SYSTEM_USER, - }, + Metadata = metadata, Beatmaps = new List { new BeatmapInfo { BaseDifficulty = new BeatmapDifficulty(), - Ruleset = ruleset + Ruleset = ruleset, + Metadata = metadata, + Version = "difficulty" } } }; diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index dfba5f457b..b8c1932186 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -28,6 +28,7 @@ using osu.Framework.Logging; using osu.Game.Beatmaps; using osu.Game.Graphics.Cursor; using osu.Game.Input.Bindings; +using osu.Game.Online.API; using osu.Game.Rulesets.Edit; using osu.Game.Screens.Edit.Compose; using osu.Game.Screens.Edit.Setup; @@ -72,6 +73,9 @@ namespace osu.Game.Screens.Edit protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) => dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); + [Resolved] + private IAPIProvider api { get; set; } + [BackgroundDependencyLoader] private void load(OsuColour colours, GameHost host) { @@ -95,6 +99,7 @@ namespace osu.Game.Screens.Edit { isNewBeatmap = true; Beatmap.Value = beatmapManager.CreateNew(Ruleset.Value); + Beatmap.Value.BeatmapSetInfo.Metadata.Author = api.LocalUser.Value; } try @@ -408,7 +413,14 @@ namespace osu.Game.Screens.Edit clock.SeekForward(!clock.IsRunning, amount); } - private void saveBeatmap() => beatmapManager.Save(playableBeatmap.BeatmapInfo, editorBeatmap); + private void saveBeatmap() + { + // apply any set-level metadata changes. + beatmapManager.Update(playableBeatmap.BeatmapInfo.BeatmapSet); + + // save the loaded beatmap's data stream. + beatmapManager.Save(playableBeatmap.BeatmapInfo, editorBeatmap); + } private void exportBeatmap() { From 0530c4b8a740d63f6c4c06faab5dac23cf517c63 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 3 Sep 2020 14:58:22 +0900 Subject: [PATCH 0867/1782] 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 d4a6d6759e..2d3bfaf7ce 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,6 +52,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 2a592108b7..166910b165 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -24,7 +24,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index e7addc1c2c..51f8141bac 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -80,7 +80,7 @@ - + From dceae21bbf4083f912b61b5fcb9d4b3b9422ffb0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 3 Sep 2020 15:46:56 +0900 Subject: [PATCH 0868/1782] Centralise fetching of overlay component titles and textures --- .../BeatmapListing/BeatmapListingHeader.cs | 3 ++- osu.Game/Overlays/BeatmapSetOverlay.cs | 4 +++- .../Overlays/Changelog/ChangelogHeader.cs | 1 + osu.Game/Overlays/ChangelogOverlay.cs | 9 ++++---- osu.Game/Overlays/ChatOverlay.cs | 6 ++++- .../Dashboard/DashboardOverlayHeader.cs | 3 ++- osu.Game/Overlays/DashboardOverlay.cs | 2 +- osu.Game/Overlays/FullscreenOverlay.cs | 8 ++++++- osu.Game/Overlays/INamedOverlayComponent.cs | 14 ++++++++++++ osu.Game/Overlays/News/NewsHeader.cs | 1 + osu.Game/Overlays/NewsOverlay.cs | 5 +++-- osu.Game/Overlays/NotificationOverlay.cs | 6 ++++- osu.Game/Overlays/NowPlayingOverlay.cs | 6 ++++- osu.Game/Overlays/OverlayHeader.cs | 4 +++- osu.Game/Overlays/OverlayTitle.cs | 22 +++++++++++++------ .../Rankings/RankingsOverlayHeader.cs | 1 + osu.Game/Overlays/RankingsOverlay.cs | 2 +- .../SearchableList/SearchableListOverlay.cs | 2 +- osu.Game/Overlays/SettingsOverlay.cs | 6 ++++- .../Toolbar/ToolbarBeatmapListingButton.cs | 5 ----- osu.Game/Overlays/Toolbar/ToolbarButton.cs | 11 ---------- .../Toolbar/ToolbarChangelogButton.cs | 8 ------- .../Overlays/Toolbar/ToolbarChatButton.cs | 5 ----- .../Overlays/Toolbar/ToolbarHomeButton.cs | 3 +-- .../Overlays/Toolbar/ToolbarMusicButton.cs | 5 ----- .../Overlays/Toolbar/ToolbarNewsButton.cs | 8 ------- .../Toolbar/ToolbarNotificationButton.cs | 5 ----- .../Toolbar/ToolbarOverlayToggleButton.cs | 16 ++++++++++++++ .../Overlays/Toolbar/ToolbarRankingsButton.cs | 8 ------- .../Overlays/Toolbar/ToolbarSettingsButton.cs | 5 ----- .../Overlays/Toolbar/ToolbarSocialButton.cs | 5 ----- osu.Game/Overlays/UserProfileOverlay.cs | 4 ++-- 32 files changed, 98 insertions(+), 95 deletions(-) create mode 100644 osu.Game/Overlays/INamedOverlayComponent.cs diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapListingHeader.cs b/osu.Game/Overlays/BeatmapListing/BeatmapListingHeader.cs index 1bab200fec..1cf86b78cf 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapListingHeader.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapListingHeader.cs @@ -12,7 +12,8 @@ namespace osu.Game.Overlays.BeatmapListing public BeatmapListingTitle() { Title = "beatmap listing"; - IconTexture = "Icons/changelog"; + Description = "Browse for new beatmaps"; + IconTexture = "Icons/Hexacons/music"; } } } diff --git a/osu.Game/Overlays/BeatmapSetOverlay.cs b/osu.Game/Overlays/BeatmapSetOverlay.cs index 3e23442023..2dfd1fa20f 100644 --- a/osu.Game/Overlays/BeatmapSetOverlay.cs +++ b/osu.Game/Overlays/BeatmapSetOverlay.cs @@ -24,7 +24,9 @@ namespace osu.Game.Overlays public const float X_PADDING = 40; public const float Y_PADDING = 25; public const float RIGHT_WIDTH = 275; - protected readonly Header Header; + + //todo: should be an OverlayHeader? or maybe not? + protected new readonly Header Header; [Resolved] private RulesetStore rulesets { get; set; } diff --git a/osu.Game/Overlays/Changelog/ChangelogHeader.cs b/osu.Game/Overlays/Changelog/ChangelogHeader.cs index 050bdea03a..35a4fc7014 100644 --- a/osu.Game/Overlays/Changelog/ChangelogHeader.cs +++ b/osu.Game/Overlays/Changelog/ChangelogHeader.cs @@ -115,6 +115,7 @@ namespace osu.Game.Overlays.Changelog public ChangelogHeaderTitle() { Title = "changelog"; + Description = "Track recent dev updates in the osu! ecosystem"; IconTexture = "Icons/changelog"; } } diff --git a/osu.Game/Overlays/ChangelogOverlay.cs b/osu.Game/Overlays/ChangelogOverlay.cs index 726be9e194..e9520906ea 100644 --- a/osu.Game/Overlays/ChangelogOverlay.cs +++ b/osu.Game/Overlays/ChangelogOverlay.cs @@ -25,10 +25,10 @@ namespace osu.Game.Overlays { public readonly Bindable Current = new Bindable(); - protected ChangelogHeader Header; - private Container content; + protected new ChangelogHeader Header; + private SampleChannel sampleBack; private List builds; @@ -61,9 +61,10 @@ namespace osu.Game.Overlays Direction = FillDirection.Vertical, Children = new Drawable[] { - Header = new ChangelogHeader + base.Header = Header = new ChangelogHeader { ListingSelected = ShowListing, + Build = { BindTarget = Current } }, content = new Container { @@ -77,8 +78,6 @@ namespace osu.Game.Overlays sampleBack = audio.Samples.Get(@"UI/generic-select-soft"); - Header.Build.BindTo(Current); - Current.BindValueChanged(e => { if (e.NewValue != null) diff --git a/osu.Game/Overlays/ChatOverlay.cs b/osu.Game/Overlays/ChatOverlay.cs index 692175603c..8e34f5d2c0 100644 --- a/osu.Game/Overlays/ChatOverlay.cs +++ b/osu.Game/Overlays/ChatOverlay.cs @@ -26,8 +26,12 @@ using osu.Framework.Graphics.Sprites; namespace osu.Game.Overlays { - public class ChatOverlay : OsuFocusedOverlayContainer + public class ChatOverlay : OsuFocusedOverlayContainer, INamedOverlayComponent { + public string IconTexture => "Icons/chat"; + public string Title => "Chat"; + public string Description => "Join the real-time discussion"; + private const float textbox_height = 60; private const float channel_selection_min_height = 0.3f; diff --git a/osu.Game/Overlays/Dashboard/DashboardOverlayHeader.cs b/osu.Game/Overlays/Dashboard/DashboardOverlayHeader.cs index 9ee679a866..1330a44374 100644 --- a/osu.Game/Overlays/Dashboard/DashboardOverlayHeader.cs +++ b/osu.Game/Overlays/Dashboard/DashboardOverlayHeader.cs @@ -12,7 +12,8 @@ namespace osu.Game.Overlays.Dashboard public DashboardTitle() { Title = "dashboard"; - IconTexture = "Icons/changelog"; + Description = "View your friends and other top level information"; + IconTexture = "Icons/hexacons/dashboard"; } } } diff --git a/osu.Game/Overlays/DashboardOverlay.cs b/osu.Game/Overlays/DashboardOverlay.cs index e3a4b0e152..68eb35c7da 100644 --- a/osu.Game/Overlays/DashboardOverlay.cs +++ b/osu.Game/Overlays/DashboardOverlay.cs @@ -50,7 +50,7 @@ namespace osu.Game.Overlays Direction = FillDirection.Vertical, Children = new Drawable[] { - header = new DashboardOverlayHeader + Header = header = new DashboardOverlayHeader { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, diff --git a/osu.Game/Overlays/FullscreenOverlay.cs b/osu.Game/Overlays/FullscreenOverlay.cs index 3464ce6086..6d0441ff46 100644 --- a/osu.Game/Overlays/FullscreenOverlay.cs +++ b/osu.Game/Overlays/FullscreenOverlay.cs @@ -12,8 +12,14 @@ using osuTK.Graphics; namespace osu.Game.Overlays { - public abstract class FullscreenOverlay : WaveOverlayContainer, IOnlineComponent + public abstract class FullscreenOverlay : WaveOverlayContainer, IOnlineComponent, INamedOverlayComponent { + public virtual string IconTexture => Header?.Title.IconTexture ?? string.Empty; + public virtual string Title => Header?.Title.Title ?? string.Empty; + public virtual string Description => Header?.Title.Description ?? string.Empty; + + public OverlayHeader Header { get; protected set; } + [Resolved] protected IAPIProvider API { get; private set; } diff --git a/osu.Game/Overlays/INamedOverlayComponent.cs b/osu.Game/Overlays/INamedOverlayComponent.cs new file mode 100644 index 0000000000..38fb8679a0 --- /dev/null +++ b/osu.Game/Overlays/INamedOverlayComponent.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.Overlays +{ + public interface INamedOverlayComponent + { + string IconTexture { get; } + + string Title { get; } + + string Description { get; } + } +} diff --git a/osu.Game/Overlays/News/NewsHeader.cs b/osu.Game/Overlays/News/NewsHeader.cs index ddada2bdaf..f85d765d46 100644 --- a/osu.Game/Overlays/News/NewsHeader.cs +++ b/osu.Game/Overlays/News/NewsHeader.cs @@ -57,6 +57,7 @@ namespace osu.Game.Overlays.News public NewsHeaderTitle() { Title = "news"; + Description = "Get up-to-date on community happenings"; IconTexture = "Icons/news"; } } diff --git a/osu.Game/Overlays/NewsOverlay.cs b/osu.Game/Overlays/NewsOverlay.cs index 09fb445b1f..bc3e080158 100644 --- a/osu.Game/Overlays/NewsOverlay.cs +++ b/osu.Game/Overlays/NewsOverlay.cs @@ -19,7 +19,6 @@ namespace osu.Game.Overlays private Container content; private LoadingLayer loading; - private NewsHeader header; private OverlayScrollContainer scrollFlow; public NewsOverlay() @@ -48,7 +47,7 @@ namespace osu.Game.Overlays Direction = FillDirection.Vertical, Children = new Drawable[] { - header = new NewsHeader + Header = new NewsHeader { ShowFrontPage = ShowFrontPage }, @@ -110,6 +109,8 @@ namespace osu.Game.Overlays cancellationToken?.Cancel(); loading.Show(); + var header = (NewsHeader)Header; + if (e.NewValue == null) { header.SetFrontPage(); diff --git a/osu.Game/Overlays/NotificationOverlay.cs b/osu.Game/Overlays/NotificationOverlay.cs index 41160d10ec..6bdacb9c5e 100644 --- a/osu.Game/Overlays/NotificationOverlay.cs +++ b/osu.Game/Overlays/NotificationOverlay.cs @@ -16,8 +16,12 @@ using osu.Framework.Threading; namespace osu.Game.Overlays { - public class NotificationOverlay : OsuFocusedOverlayContainer + public class NotificationOverlay : OsuFocusedOverlayContainer, INamedOverlayComponent { + public string IconTexture => "Icons/Hexacons/"; + public string Title => "Notifications"; + public string Description => "Waiting for 'ya"; + private const float width = 320; public const float TRANSITION_LENGTH = 600; diff --git a/osu.Game/Overlays/NowPlayingOverlay.cs b/osu.Game/Overlays/NowPlayingOverlay.cs index af692486b7..f19f7bbc61 100644 --- a/osu.Game/Overlays/NowPlayingOverlay.cs +++ b/osu.Game/Overlays/NowPlayingOverlay.cs @@ -25,8 +25,12 @@ using osuTK.Graphics; namespace osu.Game.Overlays { - public class NowPlayingOverlay : OsuFocusedOverlayContainer + public class NowPlayingOverlay : OsuFocusedOverlayContainer, INamedOverlayComponent { + public string IconTexture => "Icons/Hexacons/music"; + public string Title => "Now playing"; + public string Description => "Manage the currently playing track"; + private const float player_height = 130; private const float transition_length = 800; private const float progress_height = 10; diff --git a/osu.Game/Overlays/OverlayHeader.cs b/osu.Game/Overlays/OverlayHeader.cs index cc7f798c4a..fed1e57686 100644 --- a/osu.Game/Overlays/OverlayHeader.cs +++ b/osu.Game/Overlays/OverlayHeader.cs @@ -12,6 +12,8 @@ namespace osu.Game.Overlays { public abstract class OverlayHeader : Container { + public OverlayTitle Title { get; } + private float contentSidePadding; /// @@ -73,7 +75,7 @@ namespace osu.Game.Overlays AutoSizeAxes = Axes.Y, Children = new[] { - CreateTitle().With(title => + Title = CreateTitle().With(title => { title.Anchor = Anchor.CentreLeft; title.Origin = Anchor.CentreLeft; diff --git a/osu.Game/Overlays/OverlayTitle.cs b/osu.Game/Overlays/OverlayTitle.cs index 1c9567428c..17eeece1f8 100644 --- a/osu.Game/Overlays/OverlayTitle.cs +++ b/osu.Game/Overlays/OverlayTitle.cs @@ -12,19 +12,27 @@ using osuTK; namespace osu.Game.Overlays { - public abstract class OverlayTitle : CompositeDrawable + public abstract class OverlayTitle : CompositeDrawable, INamedOverlayComponent { - private readonly OsuSpriteText title; + private readonly OsuSpriteText titleText; private readonly Container icon; - protected string Title + private string title; + + public string Title { - set => title.Text = value; + get => title; + protected set => titleText.Text = title = value; } - protected string IconTexture + public string Description { get; protected set; } + + private string iconTexture; + + public string IconTexture { - set => icon.Child = new OverlayTitleIcon(value); + get => iconTexture; + protected set => icon.Child = new OverlayTitleIcon(iconTexture = value); } protected OverlayTitle() @@ -45,7 +53,7 @@ namespace osu.Game.Overlays Margin = new MarginPadding { Horizontal = 5 }, // compensates for osu-web sprites having around 5px of whitespace on each side Size = new Vector2(30) }, - title = new OsuSpriteText + titleText = new OsuSpriteText { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game/Overlays/Rankings/RankingsOverlayHeader.cs b/osu.Game/Overlays/Rankings/RankingsOverlayHeader.cs index e30c6f07a8..b12294c6c1 100644 --- a/osu.Game/Overlays/Rankings/RankingsOverlayHeader.cs +++ b/osu.Game/Overlays/Rankings/RankingsOverlayHeader.cs @@ -30,6 +30,7 @@ namespace osu.Game.Overlays.Rankings public RankingsTitle() { Title = "ranking"; + Description = "Find out who's the best right now"; IconTexture = "Icons/rankings"; } } diff --git a/osu.Game/Overlays/RankingsOverlay.cs b/osu.Game/Overlays/RankingsOverlay.cs index 7b200d4226..6e8a7d8554 100644 --- a/osu.Game/Overlays/RankingsOverlay.cs +++ b/osu.Game/Overlays/RankingsOverlay.cs @@ -55,7 +55,7 @@ namespace osu.Game.Overlays Direction = FillDirection.Vertical, Children = new Drawable[] { - header = new RankingsOverlayHeader + Header = header = new RankingsOverlayHeader { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, diff --git a/osu.Game/Overlays/SearchableList/SearchableListOverlay.cs b/osu.Game/Overlays/SearchableList/SearchableListOverlay.cs index 4ab2de06b6..da2066e677 100644 --- a/osu.Game/Overlays/SearchableList/SearchableListOverlay.cs +++ b/osu.Game/Overlays/SearchableList/SearchableListOverlay.cs @@ -29,7 +29,7 @@ namespace osu.Game.Overlays.SearchableList { private readonly Container scrollContainer; - protected readonly SearchableListHeader Header; + protected new readonly SearchableListHeader Header; protected readonly SearchableListFilterControl Filter; protected readonly FillFlowContainer ScrollFlow; diff --git a/osu.Game/Overlays/SettingsOverlay.cs b/osu.Game/Overlays/SettingsOverlay.cs index bb84de5d3a..9a7937dfce 100644 --- a/osu.Game/Overlays/SettingsOverlay.cs +++ b/osu.Game/Overlays/SettingsOverlay.cs @@ -13,8 +13,12 @@ using osu.Framework.Bindables; namespace osu.Game.Overlays { - public class SettingsOverlay : SettingsPanel + public class SettingsOverlay : SettingsPanel, INamedOverlayComponent { + public string IconTexture => "Icons/Hexacons/settings"; + public string Title => "Settings"; + public string Description => "Change your settings"; + protected override IEnumerable CreateSections() => new SettingsSection[] { new GeneralSection(), diff --git a/osu.Game/Overlays/Toolbar/ToolbarBeatmapListingButton.cs b/osu.Game/Overlays/Toolbar/ToolbarBeatmapListingButton.cs index cde305fffd..0363873326 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarBeatmapListingButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarBeatmapListingButton.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; -using osu.Game.Graphics; using osu.Game.Input.Bindings; namespace osu.Game.Overlays.Toolbar @@ -11,10 +10,6 @@ namespace osu.Game.Overlays.Toolbar { public ToolbarBeatmapListingButton() { - SetIcon(OsuIcon.ChevronDownCircle); - TooltipMain = "Beatmap listing"; - TooltipSub = "Browse for new beatmaps"; - Hotkey = GlobalAction.ToggleDirect; } diff --git a/osu.Game/Overlays/Toolbar/ToolbarButton.cs b/osu.Game/Overlays/Toolbar/ToolbarButton.cs index 0afc6642b2..5d402c9a23 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarButton.cs @@ -35,17 +35,6 @@ namespace osu.Game.Overlays.Toolbar IconContainer.Show(); } - public void SetIcon(IconUsage icon) => SetIcon(new SpriteIcon - { - Size = new Vector2(20), - Icon = icon - }); - - public IconUsage Icon - { - set => SetIcon(value); - } - public string Text { get => DrawableText.Text; diff --git a/osu.Game/Overlays/Toolbar/ToolbarChangelogButton.cs b/osu.Game/Overlays/Toolbar/ToolbarChangelogButton.cs index c88b418853..23f8b141b2 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarChangelogButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarChangelogButton.cs @@ -2,19 +2,11 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; -using osu.Framework.Graphics.Sprites; namespace osu.Game.Overlays.Toolbar { public class ToolbarChangelogButton : ToolbarOverlayToggleButton { - public ToolbarChangelogButton() - { - SetIcon(FontAwesome.Solid.Bullhorn); - TooltipMain = "Changelog"; - TooltipSub = "Track recent dev updates in the osu! ecosystem"; - } - [BackgroundDependencyLoader(true)] private void load(ChangelogOverlay changelog) { diff --git a/osu.Game/Overlays/Toolbar/ToolbarChatButton.cs b/osu.Game/Overlays/Toolbar/ToolbarChatButton.cs index dee4be0c1f..f9a66ae7bb 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarChatButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarChatButton.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.Sprites; using osu.Game.Input.Bindings; namespace osu.Game.Overlays.Toolbar @@ -11,10 +10,6 @@ namespace osu.Game.Overlays.Toolbar { public ToolbarChatButton() { - SetIcon(FontAwesome.Solid.Comments); - TooltipMain = "Chat"; - TooltipSub = "Join the real-time discussion"; - Hotkey = GlobalAction.ToggleChat; } diff --git a/osu.Game/Overlays/Toolbar/ToolbarHomeButton.cs b/osu.Game/Overlays/Toolbar/ToolbarHomeButton.cs index 4845c9a99f..08ba65fc47 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarHomeButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarHomeButton.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.Graphics.Sprites; using osu.Game.Input.Bindings; namespace osu.Game.Overlays.Toolbar @@ -10,7 +9,7 @@ namespace osu.Game.Overlays.Toolbar { public ToolbarHomeButton() { - Icon = FontAwesome.Solid.Home; + // todo: icon TooltipMain = "Home"; TooltipSub = "Return to the main menu"; diff --git a/osu.Game/Overlays/Toolbar/ToolbarMusicButton.cs b/osu.Game/Overlays/Toolbar/ToolbarMusicButton.cs index f9aa2de497..0f5e8e5456 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarMusicButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarMusicButton.cs @@ -3,7 +3,6 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; -using osu.Framework.Graphics.Sprites; using osu.Game.Input.Bindings; namespace osu.Game.Overlays.Toolbar @@ -14,10 +13,6 @@ namespace osu.Game.Overlays.Toolbar public ToolbarMusicButton() { - Icon = FontAwesome.Solid.Music; - TooltipMain = "Now playing"; - TooltipSub = "Manage the currently playing track"; - Hotkey = GlobalAction.ToggleNowPlaying; } diff --git a/osu.Game/Overlays/Toolbar/ToolbarNewsButton.cs b/osu.Game/Overlays/Toolbar/ToolbarNewsButton.cs index 106c67a041..0ba2935c80 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarNewsButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarNewsButton.cs @@ -2,19 +2,11 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; -using osu.Framework.Graphics.Sprites; namespace osu.Game.Overlays.Toolbar { public class ToolbarNewsButton : ToolbarOverlayToggleButton { - public ToolbarNewsButton() - { - Icon = FontAwesome.Solid.Newspaper; - TooltipMain = "News"; - TooltipSub = "Get up-to-date on community happenings"; - } - [BackgroundDependencyLoader(true)] private void load(NewsOverlay news) { diff --git a/osu.Game/Overlays/Toolbar/ToolbarNotificationButton.cs b/osu.Game/Overlays/Toolbar/ToolbarNotificationButton.cs index a699fd907f..79d0fc74c1 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarNotificationButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarNotificationButton.cs @@ -6,7 +6,6 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Input.Bindings; @@ -25,10 +24,6 @@ namespace osu.Game.Overlays.Toolbar public ToolbarNotificationButton() { - Icon = FontAwesome.Solid.Bars; - TooltipMain = "Notifications"; - TooltipSub = "Waiting for 'ya"; - Hotkey = GlobalAction.ToggleNotifications; Add(countDisplay = new CountCircle diff --git a/osu.Game/Overlays/Toolbar/ToolbarOverlayToggleButton.cs b/osu.Game/Overlays/Toolbar/ToolbarOverlayToggleButton.cs index 36387bb00d..a76ca26a47 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarOverlayToggleButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarOverlayToggleButton.cs @@ -1,11 +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.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.Textures; using osu.Game.Graphics; namespace osu.Game.Overlays.Toolbar @@ -18,6 +21,9 @@ namespace osu.Game.Overlays.Toolbar private readonly Bindable overlayState = new Bindable(); + [Resolved] + private TextureStore textures { get; set; } + public OverlayContainer StateContainer { get => stateContainer; @@ -32,6 +38,16 @@ namespace osu.Game.Overlays.Toolbar Action = stateContainer.ToggleVisibility; overlayState.BindTo(stateContainer.State); } + + if (stateContainer is INamedOverlayComponent named) + { + TooltipMain = named.Title; + TooltipSub = named.Description; + SetIcon(new Sprite + { + Texture = textures.Get(named.IconTexture), + }); + } } } diff --git a/osu.Game/Overlays/Toolbar/ToolbarRankingsButton.cs b/osu.Game/Overlays/Toolbar/ToolbarRankingsButton.cs index c026ce99fe..22a01bcdb5 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarRankingsButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarRankingsButton.cs @@ -2,19 +2,11 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; -using osu.Framework.Graphics.Sprites; namespace osu.Game.Overlays.Toolbar { public class ToolbarRankingsButton : ToolbarOverlayToggleButton { - public ToolbarRankingsButton() - { - SetIcon(FontAwesome.Regular.ChartBar); - TooltipMain = "Ranking"; - TooltipSub = "Find out who's the best right now"; - } - [BackgroundDependencyLoader(true)] private void load(RankingsOverlay rankings) { diff --git a/osu.Game/Overlays/Toolbar/ToolbarSettingsButton.cs b/osu.Game/Overlays/Toolbar/ToolbarSettingsButton.cs index ed2a23ec2a..4051a2a194 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarSettingsButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarSettingsButton.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.Sprites; using osu.Game.Input.Bindings; namespace osu.Game.Overlays.Toolbar @@ -11,10 +10,6 @@ namespace osu.Game.Overlays.Toolbar { public ToolbarSettingsButton() { - Icon = FontAwesome.Solid.Cog; - TooltipMain = "Settings"; - TooltipSub = "Change your settings"; - Hotkey = GlobalAction.ToggleSettings; } diff --git a/osu.Game/Overlays/Toolbar/ToolbarSocialButton.cs b/osu.Game/Overlays/Toolbar/ToolbarSocialButton.cs index 6faa58c559..e62c7bc807 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarSocialButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarSocialButton.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.Sprites; using osu.Game.Input.Bindings; namespace osu.Game.Overlays.Toolbar @@ -11,10 +10,6 @@ namespace osu.Game.Overlays.Toolbar { public ToolbarSocialButton() { - Icon = FontAwesome.Solid.Users; - TooltipMain = "Friends"; - TooltipSub = "Interact with those close to you"; - Hotkey = GlobalAction.ToggleSocial; } diff --git a/osu.Game/Overlays/UserProfileOverlay.cs b/osu.Game/Overlays/UserProfileOverlay.cs index b4c8a2d3ca..625758614e 100644 --- a/osu.Game/Overlays/UserProfileOverlay.cs +++ b/osu.Game/Overlays/UserProfileOverlay.cs @@ -22,7 +22,7 @@ namespace osu.Game.Overlays private ProfileSection lastSection; private ProfileSection[] sections; private GetUserRequest userReq; - protected ProfileHeader Header; + protected new ProfileHeader Header; private ProfileSectionsContainer sectionsContainer; private ProfileSectionTabControl tabs; @@ -77,7 +77,7 @@ namespace osu.Game.Overlays Add(sectionsContainer = new ProfileSectionsContainer { - ExpandableHeader = Header = new ProfileHeader(), + ExpandableHeader = base.Header = Header = new ProfileHeader(), FixedHeader = tabs, HeaderBackground = new Box { From dbf44fbaf265b2bf7142e57370e5764d7f7269ca Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 3 Sep 2020 16:26:09 +0900 Subject: [PATCH 0869/1782] Update names and icons to match new designs --- osu.Game/Overlays/BeatmapSet/BeatmapSetHeader.cs | 2 +- osu.Game/Overlays/Changelog/ChangelogHeader.cs | 2 +- osu.Game/Overlays/ChatOverlay.cs | 4 ++-- osu.Game/Overlays/Dashboard/DashboardOverlayHeader.cs | 4 ++-- osu.Game/Overlays/News/NewsHeader.cs | 2 +- osu.Game/Overlays/NowPlayingOverlay.cs | 2 +- osu.Game/Overlays/Profile/ProfileHeader.cs | 2 +- osu.Game/Overlays/Rankings/RankingsOverlayHeader.cs | 2 +- 8 files changed, 10 insertions(+), 10 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/BeatmapSetHeader.cs b/osu.Game/Overlays/BeatmapSet/BeatmapSetHeader.cs index 4626589d81..329045c743 100644 --- a/osu.Game/Overlays/BeatmapSet/BeatmapSetHeader.cs +++ b/osu.Game/Overlays/BeatmapSet/BeatmapSetHeader.cs @@ -25,7 +25,7 @@ namespace osu.Game.Overlays.BeatmapSet public BeatmapHeaderTitle() { Title = "beatmap info"; - IconTexture = "Icons/changelog"; + IconTexture = "Icons/Hexacons/music"; } } } diff --git a/osu.Game/Overlays/Changelog/ChangelogHeader.cs b/osu.Game/Overlays/Changelog/ChangelogHeader.cs index 35a4fc7014..bdc59297bb 100644 --- a/osu.Game/Overlays/Changelog/ChangelogHeader.cs +++ b/osu.Game/Overlays/Changelog/ChangelogHeader.cs @@ -116,7 +116,7 @@ namespace osu.Game.Overlays.Changelog { Title = "changelog"; Description = "Track recent dev updates in the osu! ecosystem"; - IconTexture = "Icons/changelog"; + IconTexture = "Icons/Hexacons/devtools"; } } } diff --git a/osu.Game/Overlays/ChatOverlay.cs b/osu.Game/Overlays/ChatOverlay.cs index 8e34f5d2c0..bcc2227be8 100644 --- a/osu.Game/Overlays/ChatOverlay.cs +++ b/osu.Game/Overlays/ChatOverlay.cs @@ -28,8 +28,8 @@ namespace osu.Game.Overlays { public class ChatOverlay : OsuFocusedOverlayContainer, INamedOverlayComponent { - public string IconTexture => "Icons/chat"; - public string Title => "Chat"; + public string IconTexture => "Icons/Hexacons/messaging"; + public string Title => "chat"; public string Description => "Join the real-time discussion"; private const float textbox_height = 60; diff --git a/osu.Game/Overlays/Dashboard/DashboardOverlayHeader.cs b/osu.Game/Overlays/Dashboard/DashboardOverlayHeader.cs index 1330a44374..a964d84c4f 100644 --- a/osu.Game/Overlays/Dashboard/DashboardOverlayHeader.cs +++ b/osu.Game/Overlays/Dashboard/DashboardOverlayHeader.cs @@ -12,8 +12,8 @@ namespace osu.Game.Overlays.Dashboard public DashboardTitle() { Title = "dashboard"; - Description = "View your friends and other top level information"; - IconTexture = "Icons/hexacons/dashboard"; + Description = "View your friends and other information"; + IconTexture = "Icons/Hexacons/social"; } } } diff --git a/osu.Game/Overlays/News/NewsHeader.cs b/osu.Game/Overlays/News/NewsHeader.cs index f85d765d46..38ac519387 100644 --- a/osu.Game/Overlays/News/NewsHeader.cs +++ b/osu.Game/Overlays/News/NewsHeader.cs @@ -58,7 +58,7 @@ namespace osu.Game.Overlays.News { Title = "news"; Description = "Get up-to-date on community happenings"; - IconTexture = "Icons/news"; + IconTexture = "Icons/Hexacons/news"; } } } diff --git a/osu.Game/Overlays/NowPlayingOverlay.cs b/osu.Game/Overlays/NowPlayingOverlay.cs index f19f7bbc61..d1df1fa936 100644 --- a/osu.Game/Overlays/NowPlayingOverlay.cs +++ b/osu.Game/Overlays/NowPlayingOverlay.cs @@ -28,7 +28,7 @@ namespace osu.Game.Overlays public class NowPlayingOverlay : OsuFocusedOverlayContainer, INamedOverlayComponent { public string IconTexture => "Icons/Hexacons/music"; - public string Title => "Now playing"; + public string Title => "now playing"; public string Description => "Manage the currently playing track"; private const float player_height = 130; diff --git a/osu.Game/Overlays/Profile/ProfileHeader.cs b/osu.Game/Overlays/Profile/ProfileHeader.cs index 55474c9d3e..c947ef0781 100644 --- a/osu.Game/Overlays/Profile/ProfileHeader.cs +++ b/osu.Game/Overlays/Profile/ProfileHeader.cs @@ -97,7 +97,7 @@ namespace osu.Game.Overlays.Profile public ProfileHeaderTitle() { Title = "player info"; - IconTexture = "Icons/profile"; + IconTexture = "Icons/Hexacons/profile"; } } diff --git a/osu.Game/Overlays/Rankings/RankingsOverlayHeader.cs b/osu.Game/Overlays/Rankings/RankingsOverlayHeader.cs index b12294c6c1..7039ab8214 100644 --- a/osu.Game/Overlays/Rankings/RankingsOverlayHeader.cs +++ b/osu.Game/Overlays/Rankings/RankingsOverlayHeader.cs @@ -31,7 +31,7 @@ namespace osu.Game.Overlays.Rankings { Title = "ranking"; Description = "Find out who's the best right now"; - IconTexture = "Icons/rankings"; + IconTexture = "Icons/Hexacons/rankings"; } } } From 7bcbac6f45000482ed566b7be6b221eef639d05b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 3 Sep 2020 16:27:31 +0900 Subject: [PATCH 0870/1782] Move header setting to FullscreenOverlay --- .../Online/TestSceneFullscreenOverlay.cs | 6 ++--- osu.Game/Overlays/BeatmapListingOverlay.cs | 6 ++--- osu.Game/Overlays/BeatmapSetOverlay.cs | 4 ++-- osu.Game/Overlays/ChangelogOverlay.cs | 14 +++++------ osu.Game/Overlays/DashboardOverlay.cs | 23 +++++++++---------- osu.Game/Overlays/FullscreenOverlay.cs | 9 +++++--- osu.Game/Overlays/NewsOverlay.cs | 16 ++++++------- osu.Game/Overlays/OverlayScrollContainer.cs | 2 +- osu.Game/Overlays/OverlayView.cs | 2 +- osu.Game/Overlays/RankingsOverlay.cs | 23 +++++++++---------- osu.Game/Overlays/UserProfileOverlay.cs | 7 +++--- 11 files changed, 54 insertions(+), 58 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneFullscreenOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneFullscreenOverlay.cs index e60adcee34..8f20bcdcc1 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneFullscreenOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneFullscreenOverlay.cs @@ -12,7 +12,7 @@ namespace osu.Game.Tests.Visual.Online [TestFixture] public class TestSceneFullscreenOverlay : OsuTestScene { - private FullscreenOverlay overlay; + private FullscreenOverlay overlay; protected override void LoadComplete() { @@ -38,10 +38,10 @@ namespace osu.Game.Tests.Visual.Online AddAssert("fire count 3", () => fireCount == 3); } - private class TestFullscreenOverlay : FullscreenOverlay + private class TestFullscreenOverlay : FullscreenOverlay { public TestFullscreenOverlay() - : base(OverlayColourScheme.Pink) + : base(OverlayColourScheme.Pink, null) { Children = new Drawable[] { diff --git a/osu.Game/Overlays/BeatmapListingOverlay.cs b/osu.Game/Overlays/BeatmapListingOverlay.cs index 225a8a0578..144af91145 100644 --- a/osu.Game/Overlays/BeatmapListingOverlay.cs +++ b/osu.Game/Overlays/BeatmapListingOverlay.cs @@ -24,7 +24,7 @@ using osuTK; namespace osu.Game.Overlays { - public class BeatmapListingOverlay : FullscreenOverlay + public class BeatmapListingOverlay : FullscreenOverlay { [Resolved] private PreviewTrackManager previewTrackManager { get; set; } @@ -38,7 +38,7 @@ namespace osu.Game.Overlays private OverlayScrollContainer resultScrollContainer; public BeatmapListingOverlay() - : base(OverlayColourScheme.Blue) + : base(OverlayColourScheme.Blue, new BeatmapListingHeader()) { } @@ -65,7 +65,7 @@ namespace osu.Game.Overlays Direction = FillDirection.Vertical, Children = new Drawable[] { - new BeatmapListingHeader(), + Header, filterControl = new BeatmapListingFilterControl { SearchStarted = onSearchStarted, diff --git a/osu.Game/Overlays/BeatmapSetOverlay.cs b/osu.Game/Overlays/BeatmapSetOverlay.cs index 2dfd1fa20f..bbec62a85a 100644 --- a/osu.Game/Overlays/BeatmapSetOverlay.cs +++ b/osu.Game/Overlays/BeatmapSetOverlay.cs @@ -19,7 +19,7 @@ using osuTK; namespace osu.Game.Overlays { - public class BeatmapSetOverlay : FullscreenOverlay + public class BeatmapSetOverlay : FullscreenOverlay // we don't provide a standard header for now. { public const float X_PADDING = 40; public const float Y_PADDING = 25; @@ -39,7 +39,7 @@ namespace osu.Game.Overlays private readonly Box background; public BeatmapSetOverlay() - : base(OverlayColourScheme.Blue) + : base(OverlayColourScheme.Blue, null) { OverlayScrollContainer scroll; Info info; diff --git a/osu.Game/Overlays/ChangelogOverlay.cs b/osu.Game/Overlays/ChangelogOverlay.cs index e9520906ea..c7e9a86fa4 100644 --- a/osu.Game/Overlays/ChangelogOverlay.cs +++ b/osu.Game/Overlays/ChangelogOverlay.cs @@ -21,14 +21,12 @@ using osu.Game.Overlays.Changelog; namespace osu.Game.Overlays { - public class ChangelogOverlay : FullscreenOverlay + public class ChangelogOverlay : FullscreenOverlay { public readonly Bindable Current = new Bindable(); private Container content; - protected new ChangelogHeader Header; - private SampleChannel sampleBack; private List builds; @@ -36,7 +34,7 @@ namespace osu.Game.Overlays protected List Streams; public ChangelogOverlay() - : base(OverlayColourScheme.Purple) + : base(OverlayColourScheme.Purple, new ChangelogHeader()) { } @@ -61,11 +59,11 @@ namespace osu.Game.Overlays Direction = FillDirection.Vertical, Children = new Drawable[] { - base.Header = Header = new ChangelogHeader + Header.With(h => { - ListingSelected = ShowListing, - Build = { BindTarget = Current } - }, + h.ListingSelected = ShowListing; + h.Build.BindTarget = Current; + }), content = new Container { RelativeSizeAxes = Axes.X, diff --git a/osu.Game/Overlays/DashboardOverlay.cs b/osu.Game/Overlays/DashboardOverlay.cs index 68eb35c7da..8135b83a03 100644 --- a/osu.Game/Overlays/DashboardOverlay.cs +++ b/osu.Game/Overlays/DashboardOverlay.cs @@ -15,17 +15,21 @@ using osu.Game.Overlays.Dashboard.Friends; namespace osu.Game.Overlays { - public class DashboardOverlay : FullscreenOverlay + public class DashboardOverlay : FullscreenOverlay { private CancellationTokenSource cancellationToken; private Container content; - private DashboardOverlayHeader header; private LoadingLayer loading; private OverlayScrollContainer scrollFlow; public DashboardOverlay() - : base(OverlayColourScheme.Purple) + : base(OverlayColourScheme.Purple, new DashboardOverlayHeader + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Depth = -float.MaxValue + }) { } @@ -50,12 +54,7 @@ namespace osu.Game.Overlays Direction = FillDirection.Vertical, Children = new Drawable[] { - Header = header = new DashboardOverlayHeader - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Depth = -float.MaxValue - }, + Header, content = new Container { RelativeSizeAxes = Axes.X, @@ -72,7 +71,7 @@ namespace osu.Game.Overlays { base.LoadComplete(); - header.Current.BindValueChanged(onTabChanged); + Header.Current.BindValueChanged(onTabChanged); } private bool displayUpdateRequired = true; @@ -84,7 +83,7 @@ namespace osu.Game.Overlays // We don't want to create a new display on every call, only when exiting from fully closed state. if (displayUpdateRequired) { - header.Current.TriggerChange(); + Header.Current.TriggerChange(); displayUpdateRequired = false; } } @@ -136,7 +135,7 @@ namespace osu.Game.Overlays if (State.Value == Visibility.Hidden) return; - header.Current.TriggerChange(); + Header.Current.TriggerChange(); } protected override void Dispose(bool isDisposing) diff --git a/osu.Game/Overlays/FullscreenOverlay.cs b/osu.Game/Overlays/FullscreenOverlay.cs index 6d0441ff46..bd6b07c65f 100644 --- a/osu.Game/Overlays/FullscreenOverlay.cs +++ b/osu.Game/Overlays/FullscreenOverlay.cs @@ -12,13 +12,14 @@ using osuTK.Graphics; namespace osu.Game.Overlays { - public abstract class FullscreenOverlay : WaveOverlayContainer, IOnlineComponent, INamedOverlayComponent + public abstract class FullscreenOverlay : WaveOverlayContainer, IOnlineComponent, INamedOverlayComponent + where T : OverlayHeader { public virtual string IconTexture => Header?.Title.IconTexture ?? string.Empty; public virtual string Title => Header?.Title.Title ?? string.Empty; public virtual string Description => Header?.Title.Description ?? string.Empty; - public OverlayHeader Header { get; protected set; } + public T Header { get; } [Resolved] protected IAPIProvider API { get; private set; } @@ -26,8 +27,10 @@ namespace osu.Game.Overlays [Cached] protected readonly OverlayColourProvider ColourProvider; - protected FullscreenOverlay(OverlayColourScheme colourScheme) + protected FullscreenOverlay(OverlayColourScheme colourScheme, T header) { + Header = header; + ColourProvider = new OverlayColourProvider(colourScheme); RelativeSizeAxes = Axes.Both; diff --git a/osu.Game/Overlays/NewsOverlay.cs b/osu.Game/Overlays/NewsOverlay.cs index bc3e080158..c8c1db012f 100644 --- a/osu.Game/Overlays/NewsOverlay.cs +++ b/osu.Game/Overlays/NewsOverlay.cs @@ -13,7 +13,7 @@ using osu.Game.Overlays.News.Displays; namespace osu.Game.Overlays { - public class NewsOverlay : FullscreenOverlay + public class NewsOverlay : FullscreenOverlay { private readonly Bindable article = new Bindable(null); @@ -22,7 +22,7 @@ namespace osu.Game.Overlays private OverlayScrollContainer scrollFlow; public NewsOverlay() - : base(OverlayColourScheme.Purple) + : base(OverlayColourScheme.Purple, new NewsHeader()) { } @@ -47,10 +47,10 @@ namespace osu.Game.Overlays Direction = FillDirection.Vertical, Children = new Drawable[] { - Header = new NewsHeader + Header.With(h => { - ShowFrontPage = ShowFrontPage - }, + h.ShowFrontPage = ShowFrontPage; + }), content = new Container { RelativeSizeAxes = Axes.X, @@ -109,16 +109,14 @@ namespace osu.Game.Overlays cancellationToken?.Cancel(); loading.Show(); - var header = (NewsHeader)Header; - if (e.NewValue == null) { - header.SetFrontPage(); + Header.SetFrontPage(); LoadDisplay(new FrontPageDisplay()); return; } - header.SetArticle(e.NewValue); + Header.SetArticle(e.NewValue); LoadDisplay(Empty()); } diff --git a/osu.Game/Overlays/OverlayScrollContainer.cs b/osu.Game/Overlays/OverlayScrollContainer.cs index e7415e6f74..b67d5db1a4 100644 --- a/osu.Game/Overlays/OverlayScrollContainer.cs +++ b/osu.Game/Overlays/OverlayScrollContainer.cs @@ -17,7 +17,7 @@ using osuTK.Graphics; namespace osu.Game.Overlays { /// - /// which provides . Mostly used in . + /// which provides . Mostly used in . /// public class OverlayScrollContainer : OsuScrollContainer { diff --git a/osu.Game/Overlays/OverlayView.cs b/osu.Game/Overlays/OverlayView.cs index 3e2c54c726..312271316a 100644 --- a/osu.Game/Overlays/OverlayView.cs +++ b/osu.Game/Overlays/OverlayView.cs @@ -9,7 +9,7 @@ using osu.Game.Online.API; namespace osu.Game.Overlays { /// - /// A subview containing online content, to be displayed inside a . + /// A subview containing online content, to be displayed inside a . /// /// /// Automatically performs a data fetch on load. diff --git a/osu.Game/Overlays/RankingsOverlay.cs b/osu.Game/Overlays/RankingsOverlay.cs index 6e8a7d8554..ae6d49960a 100644 --- a/osu.Game/Overlays/RankingsOverlay.cs +++ b/osu.Game/Overlays/RankingsOverlay.cs @@ -17,17 +17,16 @@ using osu.Game.Overlays.Rankings.Tables; namespace osu.Game.Overlays { - public class RankingsOverlay : FullscreenOverlay + public class RankingsOverlay : FullscreenOverlay { - protected Bindable Country => header.Country; + protected Bindable Country => Header.Country; - protected Bindable Scope => header.Current; + protected Bindable Scope => Header.Current; private readonly OverlayScrollContainer scrollFlow; private readonly Container contentContainer; private readonly LoadingLayer loading; private readonly Box background; - private readonly RankingsOverlayHeader header; private APIRequest lastRequest; private CancellationTokenSource cancellationToken; @@ -36,7 +35,12 @@ namespace osu.Game.Overlays private IAPIProvider api { get; set; } public RankingsOverlay() - : base(OverlayColourScheme.Green) + : base(OverlayColourScheme.Green, new RankingsOverlayHeader + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Depth = -float.MaxValue + }) { Children = new Drawable[] { @@ -55,12 +59,7 @@ namespace osu.Game.Overlays Direction = FillDirection.Vertical, Children = new Drawable[] { - Header = header = new RankingsOverlayHeader - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Depth = -float.MaxValue - }, + Header, new Container { RelativeSizeAxes = Axes.X, @@ -97,7 +96,7 @@ namespace osu.Game.Overlays { base.LoadComplete(); - header.Ruleset.BindTo(ruleset); + Header.Ruleset.BindTo(ruleset); Country.BindValueChanged(_ => { diff --git a/osu.Game/Overlays/UserProfileOverlay.cs b/osu.Game/Overlays/UserProfileOverlay.cs index 625758614e..965ad790ed 100644 --- a/osu.Game/Overlays/UserProfileOverlay.cs +++ b/osu.Game/Overlays/UserProfileOverlay.cs @@ -17,19 +17,18 @@ using osuTK; namespace osu.Game.Overlays { - public class UserProfileOverlay : FullscreenOverlay + public class UserProfileOverlay : FullscreenOverlay { private ProfileSection lastSection; private ProfileSection[] sections; private GetUserRequest userReq; - protected new ProfileHeader Header; private ProfileSectionsContainer sectionsContainer; private ProfileSectionTabControl tabs; public const float CONTENT_X_MARGIN = 70; public UserProfileOverlay() - : base(OverlayColourScheme.Pink) + : base(OverlayColourScheme.Pink, new ProfileHeader()) { } @@ -77,7 +76,7 @@ namespace osu.Game.Overlays Add(sectionsContainer = new ProfileSectionsContainer { - ExpandableHeader = base.Header = Header = new ProfileHeader(), + ExpandableHeader = Header, FixedHeader = tabs, HeaderBackground = new Box { From 942276d88f10e83fb8eb1ddb4894583f94876a0f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 3 Sep 2020 16:28:14 +0900 Subject: [PATCH 0871/1782] Remove outdated SearchableList classes --- .../Chat/Selection/ChannelSelectionOverlay.cs | 2 +- .../SearchableListFilterControl.cs | 2 +- .../SearchableList/SearchableListHeader.cs | 82 ----------- .../SearchableList/SearchableListOverlay.cs | 128 ------------------ osu.Game/Overlays/WaveOverlayContainer.cs | 2 + osu.Game/Screens/Multi/Header.cs | 4 +- .../Screens/Multi/Lounge/LoungeSubScreen.cs | 5 +- .../Match/Components/MatchSettingsOverlay.cs | 4 +- 8 files changed, 10 insertions(+), 219 deletions(-) delete mode 100644 osu.Game/Overlays/SearchableList/SearchableListHeader.cs delete mode 100644 osu.Game/Overlays/SearchableList/SearchableListOverlay.cs diff --git a/osu.Game/Overlays/Chat/Selection/ChannelSelectionOverlay.cs b/osu.Game/Overlays/Chat/Selection/ChannelSelectionOverlay.cs index b46ca6b040..be9ecc6746 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 const float WIDTH_PADDING = 170; + public new const float WIDTH_PADDING = 170; private const float transition_duration = 500; diff --git a/osu.Game/Overlays/SearchableList/SearchableListFilterControl.cs b/osu.Game/Overlays/SearchableList/SearchableListFilterControl.cs index e0163b5b0c..1990674aa9 100644 --- a/osu.Game/Overlays/SearchableList/SearchableListFilterControl.cs +++ b/osu.Game/Overlays/SearchableList/SearchableListFilterControl.cs @@ -36,7 +36,7 @@ namespace osu.Game.Overlays.SearchableList /// /// The amount of padding added to content (does not affect background or tab control strip). /// - protected virtual float ContentHorizontalPadding => SearchableListOverlay.WIDTH_PADDING; + protected virtual float ContentHorizontalPadding => WaveOverlayContainer.WIDTH_PADDING; protected SearchableListFilterControl() { diff --git a/osu.Game/Overlays/SearchableList/SearchableListHeader.cs b/osu.Game/Overlays/SearchableList/SearchableListHeader.cs deleted file mode 100644 index 66fedf0a56..0000000000 --- a/osu.Game/Overlays/SearchableList/SearchableListHeader.cs +++ /dev/null @@ -1,82 +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 osuTK; -using osuTK.Graphics; -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Game.Graphics; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.Sprites; - -namespace osu.Game.Overlays.SearchableList -{ - public abstract class SearchableListHeader : Container - where T : struct, Enum - { - public readonly HeaderTabControl Tabs; - - protected abstract Color4 BackgroundColour { get; } - protected abstract T DefaultTab { get; } - protected abstract Drawable CreateHeaderText(); - protected abstract IconUsage Icon { get; } - - protected SearchableListHeader() - { - RelativeSizeAxes = Axes.X; - Height = 90; - - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = BackgroundColour, - }, - new Container - { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Left = SearchableListOverlay.WIDTH_PADDING, Right = SearchableListOverlay.WIDTH_PADDING }, - Children = new Drawable[] - { - new FillFlowContainer - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.BottomLeft, - Position = new Vector2(-35f, 5f), - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(10f, 0f), - Children = new[] - { - new SpriteIcon - { - Size = new Vector2(25), - Icon = Icon, - }, - CreateHeaderText(), - }, - }, - Tabs = new HeaderTabControl - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - RelativeSizeAxes = Axes.X, - }, - }, - }, - }; - - Tabs.Current.Value = DefaultTab; - Tabs.Current.TriggerChange(); - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - Tabs.StripColour = colours.Green; - } - } -} diff --git a/osu.Game/Overlays/SearchableList/SearchableListOverlay.cs b/osu.Game/Overlays/SearchableList/SearchableListOverlay.cs deleted file mode 100644 index da2066e677..0000000000 --- a/osu.Game/Overlays/SearchableList/SearchableListOverlay.cs +++ /dev/null @@ -1,128 +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 osuTK.Graphics; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Input.Events; -using osu.Game.Graphics.Backgrounds; -using osu.Game.Graphics.Cursor; - -namespace osu.Game.Overlays.SearchableList -{ - public abstract class SearchableListOverlay : FullscreenOverlay - { - public const float WIDTH_PADDING = 80; - - protected SearchableListOverlay(OverlayColourScheme colourScheme) - : base(colourScheme) - { - } - } - - public abstract class SearchableListOverlay : SearchableListOverlay - where THeader : struct, Enum - where TTab : struct, Enum - where TCategory : struct, Enum - { - private readonly Container scrollContainer; - - protected new 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 SearchableListOverlay(OverlayColourScheme colourScheme) - : base(colourScheme) - { - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = BackgroundColour, - }, - new Container - { - RelativeSizeAxes = Axes.Both, - Masking = true, - Children = new[] - { - new Triangles - { - RelativeSizeAxes = Axes.Both, - TriangleScale = 5, - ColourLight = TrianglesColourLight, - ColourDark = TrianglesColourDark, - }, - }, - }, - scrollContainer = new Container - { - RelativeSizeAxes = Axes.Both, - Child = new OsuContextMenuContainer - { - RelativeSizeAxes = Axes.Both, - Masking = true, - Child = new OverlayScrollContainer - { - RelativeSizeAxes = Axes.Both, - ScrollbarVisible = false, - Child = ScrollFlow = new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Padding = new MarginPadding { Horizontal = 10, Bottom = 50 }, - Direction = FillDirection.Vertical, - }, - }, - }, - }, - new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, - Children = new Drawable[] - { - Header = CreateHeader(), - Filter = CreateFilterControl(), - }, - }, - }; - } - - protected override void Update() - { - base.Update(); - - scrollContainer.Padding = new MarginPadding { Top = Header.Height + Filter.Height }; - } - - protected override void OnFocus(FocusEvent e) - { - Filter.Search.TakeFocus(); - } - - protected override void PopIn() - { - base.PopIn(); - - Filter.Search.HoldFocus = true; - } - - protected override void PopOut() - { - base.PopOut(); - - Filter.Search.HoldFocus = false; - } - } -} diff --git a/osu.Game/Overlays/WaveOverlayContainer.cs b/osu.Game/Overlays/WaveOverlayContainer.cs index 5c87096dd4..d0fa9987d5 100644 --- a/osu.Game/Overlays/WaveOverlayContainer.cs +++ b/osu.Game/Overlays/WaveOverlayContainer.cs @@ -14,6 +14,8 @@ namespace osu.Game.Overlays protected override bool BlockNonPositionalInput => true; protected override Container Content => Waves; + public const float WIDTH_PADDING = 80; + protected override bool StartHidden => true; protected WaveOverlayContainer() diff --git a/osu.Game/Screens/Multi/Header.cs b/osu.Game/Screens/Multi/Header.cs index 653cb3791a..cd8695286b 100644 --- a/osu.Game/Screens/Multi/Header.cs +++ b/osu.Game/Screens/Multi/Header.cs @@ -11,8 +11,8 @@ using osu.Framework.Graphics.UserInterface; using osu.Framework.Screens; using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; -using osu.Game.Overlays.SearchableList; using osu.Game.Graphics.Sprites; +using osu.Game.Overlays; using osuTK; using osuTK.Graphics; @@ -42,7 +42,7 @@ namespace osu.Game.Screens.Multi Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Left = SearchableListOverlay.WIDTH_PADDING + OsuScreen.HORIZONTAL_OVERFLOW_PADDING }, + Padding = new MarginPadding { Left = WaveOverlayContainer.WIDTH_PADDING + OsuScreen.HORIZONTAL_OVERFLOW_PADDING }, Children = new Drawable[] { title = new MultiHeaderTitle diff --git a/osu.Game/Screens/Multi/Lounge/LoungeSubScreen.cs b/osu.Game/Screens/Multi/Lounge/LoungeSubScreen.cs index f9c5fd13a4..a1e99c83b2 100644 --- a/osu.Game/Screens/Multi/Lounge/LoungeSubScreen.cs +++ b/osu.Game/Screens/Multi/Lounge/LoungeSubScreen.cs @@ -12,7 +12,6 @@ using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osu.Game.Online.Multiplayer; using osu.Game.Overlays; -using osu.Game.Overlays.SearchableList; using osu.Game.Screens.Multi.Lounge.Components; using osu.Game.Screens.Multi.Match; @@ -102,8 +101,8 @@ namespace osu.Game.Screens.Multi.Lounge content.Padding = new MarginPadding { Top = Filter.DrawHeight, - Left = SearchableListOverlay.WIDTH_PADDING - DrawableRoom.SELECTION_BORDER_WIDTH + HORIZONTAL_OVERFLOW_PADDING, - Right = SearchableListOverlay.WIDTH_PADDING + HORIZONTAL_OVERFLOW_PADDING, + Left = WaveOverlayContainer.WIDTH_PADDING - DrawableRoom.SELECTION_BORDER_WIDTH + HORIZONTAL_OVERFLOW_PADDING, + Right = WaveOverlayContainer.WIDTH_PADDING + HORIZONTAL_OVERFLOW_PADDING, }; } diff --git a/osu.Game/Screens/Multi/Match/Components/MatchSettingsOverlay.cs b/osu.Game/Screens/Multi/Match/Components/MatchSettingsOverlay.cs index 49a0fc434b..caefc194b1 100644 --- a/osu.Game/Screens/Multi/Match/Components/MatchSettingsOverlay.cs +++ b/osu.Game/Screens/Multi/Match/Components/MatchSettingsOverlay.cs @@ -14,7 +14,7 @@ using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Online.Multiplayer; -using osu.Game.Overlays.SearchableList; +using osu.Game.Overlays; using osuTK; using osuTK.Graphics; @@ -117,7 +117,7 @@ namespace osu.Game.Screens.Multi.Match.Components { new Container { - Padding = new MarginPadding { Horizontal = SearchableListOverlay.WIDTH_PADDING }, + Padding = new MarginPadding { Horizontal = WaveOverlayContainer.WIDTH_PADDING }, RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Children = new Drawable[] From 98c5a04a09b715de91ed305c4313e6d2055db963 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 3 Sep 2020 16:28:53 +0900 Subject: [PATCH 0872/1782] Update home button --- osu.Game/Overlays/Toolbar/ToolbarButton.cs | 9 +++++++++ osu.Game/Overlays/Toolbar/ToolbarHomeButton.cs | 12 +++++++++--- .../Overlays/Toolbar/ToolbarOverlayToggleButton.cs | 11 +---------- 3 files changed, 19 insertions(+), 13 deletions(-) diff --git a/osu.Game/Overlays/Toolbar/ToolbarButton.cs b/osu.Game/Overlays/Toolbar/ToolbarButton.cs index 5d402c9a23..d0787664a0 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarButton.cs @@ -35,6 +35,15 @@ namespace osu.Game.Overlays.Toolbar IconContainer.Show(); } + [Resolved] + private TextureStore textures { get; set; } + + public void SetIcon(string texture) => + SetIcon(new Sprite + { + Texture = textures.Get(texture), + }); + public string Text { get => DrawableText.Text; diff --git a/osu.Game/Overlays/Toolbar/ToolbarHomeButton.cs b/osu.Game/Overlays/Toolbar/ToolbarHomeButton.cs index 08ba65fc47..6b2c24c0f3 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarHomeButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarHomeButton.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.Game.Input.Bindings; namespace osu.Game.Overlays.Toolbar @@ -9,11 +10,16 @@ namespace osu.Game.Overlays.Toolbar { public ToolbarHomeButton() { - // todo: icon + Width *= 1.4f; + Hotkey = GlobalAction.Home; + } + + [BackgroundDependencyLoader] + private void load() + { TooltipMain = "Home"; TooltipSub = "Return to the main menu"; - - Hotkey = GlobalAction.Home; + SetIcon("Icons/Hexacons/home"); } } } diff --git a/osu.Game/Overlays/Toolbar/ToolbarOverlayToggleButton.cs b/osu.Game/Overlays/Toolbar/ToolbarOverlayToggleButton.cs index a76ca26a47..0dea71cc08 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarOverlayToggleButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarOverlayToggleButton.cs @@ -1,14 +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.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.Textures; using osu.Game.Graphics; namespace osu.Game.Overlays.Toolbar @@ -21,9 +18,6 @@ namespace osu.Game.Overlays.Toolbar private readonly Bindable overlayState = new Bindable(); - [Resolved] - private TextureStore textures { get; set; } - public OverlayContainer StateContainer { get => stateContainer; @@ -43,10 +37,7 @@ namespace osu.Game.Overlays.Toolbar { TooltipMain = named.Title; TooltipSub = named.Description; - SetIcon(new Sprite - { - Texture = textures.Get(named.IconTexture), - }); + SetIcon(named.IconTexture); } } } From 2fac0a180e0640362b8ce0294e261b3ac2134d5b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 3 Sep 2020 16:29:15 +0900 Subject: [PATCH 0873/1782] Adjust toolbar button sizing --- osu.Game/OsuGame.cs | 4 ++-- osu.Game/Overlays/Settings/Sidebar.cs | 7 +++---- osu.Game/Overlays/Toolbar/ToolbarButton.cs | 7 +++---- osu.Game/Overlays/Toolbar/ToolbarRulesetSelector.cs | 2 +- osu.Game/Overlays/Toolbar/ToolbarRulesetTabButton.cs | 6 ------ osu.Game/Overlays/Toolbar/ToolbarSettingsButton.cs | 1 + 6 files changed, 10 insertions(+), 17 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 6b8e70e546..164a40c6a5 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -698,9 +698,9 @@ namespace osu.Game float offset = 0; if (Settings.State.Value == Visibility.Visible) - offset += ToolbarButton.WIDTH / 2; + offset += Toolbar.HEIGHT / 2; if (notifications.State.Value == Visibility.Visible) - offset -= ToolbarButton.WIDTH / 2; + offset -= Toolbar.HEIGHT / 2; screenContainer.MoveToX(offset, SettingsPanel.TRANSITION_LENGTH, Easing.OutQuint); } diff --git a/osu.Game/Overlays/Settings/Sidebar.cs b/osu.Game/Overlays/Settings/Sidebar.cs index 358f94b659..3783d15e5a 100644 --- a/osu.Game/Overlays/Settings/Sidebar.cs +++ b/osu.Game/Overlays/Settings/Sidebar.cs @@ -4,22 +4,21 @@ using System; using System.Linq; using osu.Framework; -using osuTK; -using osuTK.Graphics; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; using osu.Framework.Threading; using osu.Game.Graphics.Containers; -using osu.Game.Overlays.Toolbar; +using osuTK; +using osuTK.Graphics; namespace osu.Game.Overlays.Settings { public class Sidebar : Container, IStateful { private readonly FillFlowContainer content; - public const float DEFAULT_WIDTH = ToolbarButton.WIDTH; + public const float DEFAULT_WIDTH = Toolbar.Toolbar.HEIGHT; public const int EXPANDED_WIDTH = 200; public event Action StateChanged; diff --git a/osu.Game/Overlays/Toolbar/ToolbarButton.cs b/osu.Game/Overlays/Toolbar/ToolbarButton.cs index d0787664a0..49b9c62d85 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarButton.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.Framework.Graphics.Textures; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Game.Graphics; @@ -25,8 +26,6 @@ namespace osu.Game.Overlays.Toolbar { public abstract class ToolbarButton : OsuClickableContainer, IKeyBindingHandler { - public const float WIDTH = Toolbar.HEIGHT * 1.4f; - protected GlobalAction? Hotkey { get; set; } public void SetIcon(Drawable icon) @@ -80,7 +79,7 @@ namespace osu.Game.Overlays.Toolbar protected ToolbarButton() : base(HoverSampleSet.Loud) { - Width = WIDTH; + Width = Toolbar.HEIGHT; RelativeSizeAxes = Axes.Y; Children = new Drawable[] @@ -114,7 +113,7 @@ namespace osu.Game.Overlays.Toolbar { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, - Size = new Vector2(20), + Size = new Vector2(26), Alpha = 0, }, DrawableText = new OsuSpriteText diff --git a/osu.Game/Overlays/Toolbar/ToolbarRulesetSelector.cs b/osu.Game/Overlays/Toolbar/ToolbarRulesetSelector.cs index 422bf00c6d..905d5b44c6 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarRulesetSelector.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarRulesetSelector.cs @@ -37,7 +37,7 @@ namespace osu.Game.Overlays.Toolbar }, ModeButtonLine = new Container { - Size = new Vector2(ToolbarButton.WIDTH, 3), + Size = new Vector2(Toolbar.HEIGHT, 3), Anchor = Anchor.BottomLeft, Origin = Anchor.TopLeft, Masking = true, diff --git a/osu.Game/Overlays/Toolbar/ToolbarRulesetTabButton.cs b/osu.Game/Overlays/Toolbar/ToolbarRulesetTabButton.cs index a5194ea752..754b679599 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarRulesetTabButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarRulesetTabButton.cs @@ -65,12 +65,6 @@ namespace osu.Game.Overlays.Toolbar Parent.Click(); return base.OnClick(e); } - - protected override void LoadComplete() - { - base.LoadComplete(); - IconContainer.Scale *= 1.4f; - } } } } diff --git a/osu.Game/Overlays/Toolbar/ToolbarSettingsButton.cs b/osu.Game/Overlays/Toolbar/ToolbarSettingsButton.cs index 4051a2a194..c53f4a55d9 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarSettingsButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarSettingsButton.cs @@ -10,6 +10,7 @@ namespace osu.Game.Overlays.Toolbar { public ToolbarSettingsButton() { + Width *= 1.4f; Hotkey = GlobalAction.ToggleSettings; } From 0d1674ca5e28fce53ab48fe478e44faa376b7caa Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 3 Sep 2020 16:32:43 +0900 Subject: [PATCH 0874/1782] Combine settings strings to read from same location --- osu.Game/Overlays/SettingsOverlay.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/SettingsOverlay.cs b/osu.Game/Overlays/SettingsOverlay.cs index 9a7937dfce..0532a031f3 100644 --- a/osu.Game/Overlays/SettingsOverlay.cs +++ b/osu.Game/Overlays/SettingsOverlay.cs @@ -16,8 +16,8 @@ namespace osu.Game.Overlays public class SettingsOverlay : SettingsPanel, INamedOverlayComponent { public string IconTexture => "Icons/Hexacons/settings"; - public string Title => "Settings"; - public string Description => "Change your settings"; + public string Title => "settings"; + public string Description => "Change the way osu! behaves"; protected override IEnumerable CreateSections() => new SettingsSection[] { @@ -34,7 +34,7 @@ namespace osu.Game.Overlays private readonly List subPanels = new List(); - protected override Drawable CreateHeader() => new SettingsHeader("settings", "Change the way osu! behaves"); + protected override Drawable CreateHeader() => new SettingsHeader(Title, Description); protected override Drawable CreateFooter() => new SettingsFooter(); public SettingsOverlay() From f5a73130e1aa32ca1001669f4118a15decfce8c4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 3 Sep 2020 16:34:47 +0900 Subject: [PATCH 0875/1782] Fix regression in sidebar button sizing --- osu.Game/Overlays/Settings/Sidebar.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Settings/Sidebar.cs b/osu.Game/Overlays/Settings/Sidebar.cs index 3783d15e5a..031ecaae46 100644 --- a/osu.Game/Overlays/Settings/Sidebar.cs +++ b/osu.Game/Overlays/Settings/Sidebar.cs @@ -18,7 +18,7 @@ namespace osu.Game.Overlays.Settings public class Sidebar : Container, IStateful { private readonly FillFlowContainer content; - public const float DEFAULT_WIDTH = Toolbar.Toolbar.HEIGHT; + public const float DEFAULT_WIDTH = Toolbar.Toolbar.HEIGHT * 1.4f; public const int EXPANDED_WIDTH = 200; public event Action StateChanged; From 99e34d85621b283476376d72d8cb70c3f9a50eb8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 3 Sep 2020 17:05:45 +0900 Subject: [PATCH 0876/1782] Update with missing icons --- osu.Game/Overlays/BeatmapListing/BeatmapListingHeader.cs | 2 +- osu.Game/Overlays/BeatmapSet/BeatmapSetHeader.cs | 2 +- osu.Game/Overlays/NotificationOverlay.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapListingHeader.cs b/osu.Game/Overlays/BeatmapListing/BeatmapListingHeader.cs index 1cf86b78cf..b7f511271c 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapListingHeader.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapListingHeader.cs @@ -13,7 +13,7 @@ namespace osu.Game.Overlays.BeatmapListing { Title = "beatmap listing"; Description = "Browse for new beatmaps"; - IconTexture = "Icons/Hexacons/music"; + IconTexture = "Icons/Hexacons/beatmap"; } } } diff --git a/osu.Game/Overlays/BeatmapSet/BeatmapSetHeader.cs b/osu.Game/Overlays/BeatmapSet/BeatmapSetHeader.cs index 329045c743..6511b15fc8 100644 --- a/osu.Game/Overlays/BeatmapSet/BeatmapSetHeader.cs +++ b/osu.Game/Overlays/BeatmapSet/BeatmapSetHeader.cs @@ -25,7 +25,7 @@ namespace osu.Game.Overlays.BeatmapSet public BeatmapHeaderTitle() { Title = "beatmap info"; - IconTexture = "Icons/Hexacons/music"; + IconTexture = "Icons/Hexacons/beatmap"; } } } diff --git a/osu.Game/Overlays/NotificationOverlay.cs b/osu.Game/Overlays/NotificationOverlay.cs index 6bdacb9c5e..b7d916c48f 100644 --- a/osu.Game/Overlays/NotificationOverlay.cs +++ b/osu.Game/Overlays/NotificationOverlay.cs @@ -18,7 +18,7 @@ namespace osu.Game.Overlays { public class NotificationOverlay : OsuFocusedOverlayContainer, INamedOverlayComponent { - public string IconTexture => "Icons/Hexacons/"; + public string IconTexture => "Icons/Hexacons/notification"; public string Title => "Notifications"; public string Description => "Waiting for 'ya"; From d55c9c3cc2e6f951ff5bf8776c9cff638f3f5726 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 3 Sep 2020 17:11:34 +0900 Subject: [PATCH 0877/1782] Fix UserProfile weirdness --- osu.Game/Graphics/Containers/SectionsContainer.cs | 5 ++++- osu.Game/Overlays/UserProfileOverlay.cs | 3 +++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game/Graphics/Containers/SectionsContainer.cs b/osu.Game/Graphics/Containers/SectionsContainer.cs index d739f56828..f32f8e0c67 100644 --- a/osu.Game/Graphics/Containers/SectionsContainer.cs +++ b/osu.Game/Graphics/Containers/SectionsContainer.cs @@ -26,8 +26,11 @@ namespace osu.Game.Graphics.Containers { if (value == expandableHeader) return; - expandableHeader?.Expire(); + if (expandableHeader != null) + RemoveInternal(expandableHeader); + expandableHeader = value; + if (value == null) return; AddInternal(expandableHeader); diff --git a/osu.Game/Overlays/UserProfileOverlay.cs b/osu.Game/Overlays/UserProfileOverlay.cs index 965ad790ed..2b316c0e34 100644 --- a/osu.Game/Overlays/UserProfileOverlay.cs +++ b/osu.Game/Overlays/UserProfileOverlay.cs @@ -44,6 +44,9 @@ namespace osu.Game.Overlays if (user.Id == Header?.User.Value?.Id) return; + if (sectionsContainer != null) + sectionsContainer.ExpandableHeader = null; + userReq?.Cancel(); Clear(); lastSection = null; From 72cb65c22f55c89677a4bf3a466e082788831a99 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 3 Sep 2020 17:51:54 +0900 Subject: [PATCH 0878/1782] Update and add missing beatmap statistic icons to info wedge --- .../Beatmaps/CatchBeatmap.cs | 7 +++--- .../Beatmaps/ManiaBeatmap.cs | 5 ++-- osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmap.cs | 7 +++--- .../Beatmaps/TaikoBeatmap.cs | 7 +++--- osu.Game/Beatmaps/BeatmapStatistic.cs | 18 ++++++++++++- osu.Game/Beatmaps/BeatmapStatisticSprite.cs | 25 +++++++++++++++++++ osu.Game/Screens/Select/BeatmapInfoWedge.cs | 16 +++++++++--- 7 files changed, 65 insertions(+), 20 deletions(-) create mode 100644 osu.Game/Beatmaps/BeatmapStatisticSprite.cs diff --git a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmap.cs b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmap.cs index 18cc300ff9..5dc19ce15b 100644 --- a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmap.cs +++ b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmap.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Linq; -using osu.Framework.Graphics.Sprites; using osu.Game.Beatmaps; using osu.Game.Rulesets.Catch.Objects; @@ -23,19 +22,19 @@ namespace osu.Game.Rulesets.Catch.Beatmaps { Name = @"Fruit Count", Content = fruits.ToString(), - Icon = FontAwesome.Regular.Circle + CreateIcon = () => new BeatmapStatisticSprite("circles"), }, new BeatmapStatistic { Name = @"Juice Stream Count", Content = juiceStreams.ToString(), - Icon = FontAwesome.Regular.Circle + CreateIcon = () => new BeatmapStatisticSprite("sliders"), }, new BeatmapStatistic { Name = @"Banana Shower Count", Content = bananaShowers.ToString(), - Icon = FontAwesome.Regular.Circle + CreateIcon = () => new BeatmapStatisticSprite("spinners"), } }; } diff --git a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmap.cs b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmap.cs index dc24a344e9..f6b460f269 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmap.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmap.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Linq; -using osu.Framework.Graphics.Sprites; using osu.Game.Beatmaps; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.UI; @@ -41,14 +40,14 @@ namespace osu.Game.Rulesets.Mania.Beatmaps new BeatmapStatistic { Name = @"Note Count", + CreateIcon = () => new BeatmapStatisticSprite("circles"), Content = notes.ToString(), - Icon = FontAwesome.Regular.Circle }, new BeatmapStatistic { Name = @"Hold Note Count", + CreateIcon = () => new BeatmapStatisticSprite("sliders"), Content = holdnotes.ToString(), - Icon = FontAwesome.Regular.Circle }, }; } diff --git a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmap.cs b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmap.cs index 491d82b89e..513a9254ec 100644 --- a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmap.cs +++ b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmap.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Linq; -using osu.Framework.Graphics.Sprites; using osu.Game.Beatmaps; using osu.Game.Rulesets.Osu.Objects; @@ -23,19 +22,19 @@ namespace osu.Game.Rulesets.Osu.Beatmaps { Name = @"Circle Count", Content = circles.ToString(), - Icon = FontAwesome.Regular.Circle + CreateIcon = () => new BeatmapStatisticSprite("circles"), }, new BeatmapStatistic { Name = @"Slider Count", Content = sliders.ToString(), - Icon = FontAwesome.Regular.Circle + CreateIcon = () => new BeatmapStatisticSprite("sliders"), }, new BeatmapStatistic { Name = @"Spinner Count", Content = spinners.ToString(), - Icon = FontAwesome.Regular.Circle + CreateIcon = () => new BeatmapStatisticSprite("spinners"), } }; } diff --git a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmap.cs b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmap.cs index b595f43fbb..c0f8af4fff 100644 --- a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmap.cs +++ b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmap.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Linq; -using osu.Framework.Graphics.Sprites; using osu.Game.Beatmaps; using osu.Game.Rulesets.Taiko.Objects; @@ -22,20 +21,20 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps new BeatmapStatistic { Name = @"Hit Count", + CreateIcon = () => new BeatmapStatisticSprite("circles"), Content = hits.ToString(), - Icon = FontAwesome.Regular.Circle }, new BeatmapStatistic { Name = @"Drumroll Count", + CreateIcon = () => new BeatmapStatisticSprite("sliders"), Content = drumrolls.ToString(), - Icon = FontAwesome.Regular.Circle }, new BeatmapStatistic { Name = @"Swell Count", + CreateIcon = () => new BeatmapStatisticSprite("spinners"), Content = swells.ToString(), - Icon = FontAwesome.Regular.Circle } }; } diff --git a/osu.Game/Beatmaps/BeatmapStatistic.cs b/osu.Game/Beatmaps/BeatmapStatistic.cs index 0745ec5222..5a466c24be 100644 --- a/osu.Game/Beatmaps/BeatmapStatistic.cs +++ b/osu.Game/Beatmaps/BeatmapStatistic.cs @@ -1,14 +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 osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; namespace osu.Game.Beatmaps { public class BeatmapStatistic { - public IconUsage Icon; + [Obsolete("Use CreateIcon instead")] // can be removed 20210203 + public IconUsage Icon = FontAwesome.Regular.QuestionCircle; + + /// + /// A function to create the icon for display purposes. + /// + public Func CreateIcon; + public string Content; public string Name; + + public BeatmapStatistic() + { +#pragma warning disable 618 + CreateIcon = () => new SpriteIcon { Icon = Icon }; +#pragma warning restore 618 + } } } diff --git a/osu.Game/Beatmaps/BeatmapStatisticSprite.cs b/osu.Game/Beatmaps/BeatmapStatisticSprite.cs new file mode 100644 index 0000000000..1cb0bacf0f --- /dev/null +++ b/osu.Game/Beatmaps/BeatmapStatisticSprite.cs @@ -0,0 +1,25 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Textures; + +namespace osu.Game.Beatmaps +{ + public class BeatmapStatisticSprite : Sprite + { + private readonly string iconName; + + public BeatmapStatisticSprite(string iconName) + { + this.iconName = iconName; + } + + [BackgroundDependencyLoader] + private void load(TextureStore textures) + { + Texture = textures.Get($"Icons/BeatmapDetails/{iconName}"); + } + } +} diff --git a/osu.Game/Screens/Select/BeatmapInfoWedge.cs b/osu.Game/Screens/Select/BeatmapInfoWedge.cs index ad977c70b5..4ef074b967 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedge.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedge.cs @@ -318,14 +318,14 @@ namespace osu.Game.Screens.Select labels.Add(new InfoLabel(new BeatmapStatistic { Name = "Length", - Icon = FontAwesome.Regular.Clock, + CreateIcon = () => new BeatmapStatisticSprite("length"), Content = TimeSpan.FromMilliseconds(b.BeatmapInfo.Length).ToString(@"m\:ss"), })); labels.Add(new InfoLabel(new BeatmapStatistic { Name = "BPM", - Icon = FontAwesome.Regular.Circle, + CreateIcon = () => new BeatmapStatisticSprite("bpm"), Content = getBPMRange(b), })); @@ -418,10 +418,18 @@ namespace osu.Game.Screens.Select Anchor = Anchor.Centre, Origin = Anchor.Centre, RelativeSizeAxes = Axes.Both, - Scale = new Vector2(0.8f), Colour = Color4Extensions.FromHex(@"f7dd55"), - Icon = statistic.Icon, + Icon = FontAwesome.Regular.Circle, + Scale = new Vector2(0.8f) }, + statistic.CreateIcon().With(i => + { + i.Anchor = Anchor.Centre; + i.Origin = Anchor.Centre; + i.RelativeSizeAxes = Axes.Both; + i.Size = new Vector2(1.2f); + i.Colour = Color4Extensions.FromHex(@"f7dd55"); + }), } }, new OsuSpriteText From 58e84760b959362ee49fd98d36b8222f30298f70 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 3 Sep 2020 19:17:07 +0900 Subject: [PATCH 0879/1782] Fix path empty string check causing regression in behaviour --- osu.Game/IO/WrappedStorage.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game/IO/WrappedStorage.cs b/osu.Game/IO/WrappedStorage.cs index 766e36ef14..5b2549d2ee 100644 --- a/osu.Game/IO/WrappedStorage.cs +++ b/osu.Game/IO/WrappedStorage.cs @@ -25,7 +25,13 @@ namespace osu.Game.IO this.subPath = subPath; } - protected virtual string MutatePath(string path) => !string.IsNullOrEmpty(subPath) && !string.IsNullOrEmpty(path) ? Path.Combine(subPath, path) : path; + protected virtual string MutatePath(string path) + { + if (path == null) + return null; + + return !string.IsNullOrEmpty(subPath) ? Path.Combine(subPath, path) : path; + } protected virtual void ChangeTargetStorage(Storage newStorage) { From a555407f3772353b2b4f8b9b3a93179ec2212585 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 3 Sep 2020 19:20:42 +0900 Subject: [PATCH 0880/1782] Fix various test failures due to missing beatmap info in empty beatmap --- osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs | 2 +- osu.Game/Beatmaps/WorkingBeatmap.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs b/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs index 9e4c29a8d4..11b6ad8c29 100644 --- a/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs @@ -34,7 +34,7 @@ namespace osu.Game.Beatmaps protected override IBeatmap GetBeatmap() { if (BeatmapInfo.Path == null) - return BeatmapInfo.Ruleset.CreateInstance().CreateBeatmapConverter(new Beatmap()).Beatmap; + return BeatmapInfo?.Ruleset.CreateInstance().CreateBeatmapConverter(new Beatmap { BeatmapInfo = BeatmapInfo }).Beatmap; try { diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs index 6a161e6e04..aefeb48453 100644 --- a/osu.Game/Beatmaps/WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/WorkingBeatmap.cs @@ -53,7 +53,7 @@ namespace osu.Game.Beatmaps { const double excess_length = 1000; - var lastObject = Beatmap.HitObjects.LastOrDefault(); + var lastObject = Beatmap?.HitObjects.LastOrDefault(); double length; From 1c1c583d3b9bf0ed2b306f7fef8ebae0bd11f1b7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 3 Sep 2020 19:31:40 +0900 Subject: [PATCH 0881/1782] Fix regression in file update logic (filename set too early) --- 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 325e6c6e98..1456e7487b 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -243,15 +243,15 @@ namespace osu.Game.Beatmaps var beatmapInfo = setInfo.Beatmaps.Single(b => b.ID == info.ID); var metadata = beatmapInfo.Metadata ?? setInfo.Metadata; + // grab the original file (or create a new one if not found). + var fileInfo = setInfo.Files.SingleOrDefault(f => string.Equals(f.Filename, beatmapInfo.Path, StringComparison.OrdinalIgnoreCase)) ?? new BeatmapSetFileInfo(); + // metadata may have changed; update the path with the standard format. beatmapInfo.Path = $"{metadata.Artist} - {metadata.Title} ({metadata.Author}) [{beatmapInfo.Version}]"; - beatmapInfo.MD5Hash = stream.ComputeMD5Hash(); - var fileInfo = setInfo.Files.SingleOrDefault(f => string.Equals(f.Filename, beatmapInfo.Path, StringComparison.OrdinalIgnoreCase)) ?? new BeatmapSetFileInfo - { - Filename = beatmapInfo.Path - }; + // update existing or populate new file's filename. + fileInfo.Filename = beatmapInfo.Path; stream.Seek(0, SeekOrigin.Begin); UpdateFile(setInfo, fileInfo, stream); From c9a73926a68cd4104eb719f8b365be601c67373c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 3 Sep 2020 19:38:01 +0900 Subject: [PATCH 0882/1782] Add basic test coverage --- .../Beatmaps/IO/ImportBeatmapTest.cs | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs index 0151678db3..4547613b5a 100644 --- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs @@ -15,6 +15,7 @@ using osu.Framework.Extensions; using osu.Framework.Logging; using osu.Game.Beatmaps; using osu.Game.IO; +using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Tests.Resources; using SharpCompress.Archives; @@ -756,6 +757,63 @@ namespace osu.Game.Tests.Beatmaps.IO } } + [Test] + public void TestCreateNewEmptyBeatmap() + { + using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestUpdateBeatmapFile))) + { + try + { + var osu = loadOsu(host); + var manager = osu.Dependencies.Get(); + + var working = osu.Dependencies.Get().CreateNew(new OsuRuleset().RulesetInfo); + + manager.Save(working.BeatmapInfo, working.Beatmap); + + var retrievedSet = manager.GetAllUsableBeatmapSets()[0]; + + // Check that the new file is referenced correctly by attempting a retrieval + Beatmap updatedBeatmap = (Beatmap)manager.GetWorkingBeatmap(retrievedSet.Beatmaps[0]).Beatmap; + Assert.That(updatedBeatmap.HitObjects.Count, Is.EqualTo(0)); + } + finally + { + host.Exit(); + } + } + } + + [Test] + public void TestCreateNewBeatmapWithObject() + { + using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestUpdateBeatmapFile))) + { + try + { + var osu = loadOsu(host); + var manager = osu.Dependencies.Get(); + + var working = osu.Dependencies.Get().CreateNew(new OsuRuleset().RulesetInfo); + + ((Beatmap)working.Beatmap).HitObjects.Add(new HitCircle { StartTime = 5000 }); + + manager.Save(working.BeatmapInfo, working.Beatmap); + + var retrievedSet = manager.GetAllUsableBeatmapSets()[0]; + + // Check that the new file is referenced correctly by attempting a retrieval + Beatmap updatedBeatmap = (Beatmap)manager.GetWorkingBeatmap(retrievedSet.Beatmaps[0]).Beatmap; + Assert.That(updatedBeatmap.HitObjects.Count, Is.EqualTo(1)); + Assert.That(updatedBeatmap.HitObjects[0].StartTime, Is.EqualTo(5000)); + } + finally + { + host.Exit(); + } + } + } + public static async Task LoadOszIntoOsu(OsuGameBase osu, string path = null, bool virtualTrack = false) { var temp = path ?? TestResources.GetTestBeatmapForImport(virtualTrack); From d32b77f045a120ee21645baff1209f5c9215118c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 3 Sep 2020 21:33:25 +0900 Subject: [PATCH 0883/1782] Add missing extension to filename Co-authored-by: Dan Balasescu --- osu.Game/Beatmaps/BeatmapManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 1456e7487b..12add76b46 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -247,7 +247,7 @@ namespace osu.Game.Beatmaps var fileInfo = setInfo.Files.SingleOrDefault(f => string.Equals(f.Filename, beatmapInfo.Path, StringComparison.OrdinalIgnoreCase)) ?? new BeatmapSetFileInfo(); // metadata may have changed; update the path with the standard format. - beatmapInfo.Path = $"{metadata.Artist} - {metadata.Title} ({metadata.Author}) [{beatmapInfo.Version}]"; + beatmapInfo.Path = $"{metadata.Artist} - {metadata.Title} ({metadata.Author}) [{beatmapInfo.Version}].osu"; beatmapInfo.MD5Hash = stream.ComputeMD5Hash(); // update existing or populate new file's filename. From ebed7d09e3412206660666a3231b406e4550206d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 3 Sep 2020 21:56:36 +0900 Subject: [PATCH 0884/1782] 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 2d3bfaf7ce..a41c1a5864 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -51,7 +51,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 166910b165..d79806883e 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 51f8141bac..16a8a1acb7 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -71,7 +71,7 @@ - + From 18927304f10bd912fc9a09cb22aaed3dd38974d0 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 3 Sep 2020 16:29:25 +0300 Subject: [PATCH 0885/1782] Move adjustment to LegacySkinConfiguration as a default value --- osu.Game.Rulesets.Osu/Skinning/LegacySliderBody.cs | 4 ---- osu.Game/Skinning/LegacySkinConfiguration.cs | 9 +++++++++ 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacySliderBody.cs b/osu.Game.Rulesets.Osu/Skinning/LegacySliderBody.cs index aad8b189d9..21df49d80b 100644 --- a/osu.Game.Rulesets.Osu/Skinning/LegacySliderBody.cs +++ b/osu.Game.Rulesets.Osu/Skinning/LegacySliderBody.cs @@ -18,10 +18,6 @@ namespace osu.Game.Rulesets.Osu.Skinning { private const float shadow_portion = 1 - (OsuLegacySkinTransformer.LEGACY_CIRCLE_RADIUS / OsuHitObject.OBJECT_RADIUS); - protected new float CalculatedBorderPortion - // Roughly matches osu!stable's slider border portions. - => base.CalculatedBorderPortion * 0.77f; - 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) diff --git a/osu.Game/Skinning/LegacySkinConfiguration.cs b/osu.Game/Skinning/LegacySkinConfiguration.cs index 41b7aea34b..b980d727ed 100644 --- a/osu.Game/Skinning/LegacySkinConfiguration.cs +++ b/osu.Game/Skinning/LegacySkinConfiguration.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.Globalization; + namespace osu.Game.Skinning { public class LegacySkinConfiguration : SkinConfiguration @@ -12,6 +14,13 @@ namespace osu.Game.Skinning /// public decimal? LegacyVersion { get; internal set; } + public LegacySkinConfiguration() + { + // Roughly matches osu!stable's slider border portions. + // Can't use nameof(SliderBorderSize) as the lookup enum is declared in the osu! ruleset. + ConfigDictionary["SliderBorderSize"] = 0.77f.ToString(CultureInfo.InvariantCulture); + } + public enum LegacySetting { Version, From d6b46936a0f7100617815b67130252f413ef03d9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 4 Sep 2020 12:55:28 +0900 Subject: [PATCH 0886/1782] Adjust sizing to match updated textures with less padding --- osu.Game/Screens/Select/BeatmapInfoWedge.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapInfoWedge.cs b/osu.Game/Screens/Select/BeatmapInfoWedge.cs index 4ef074b967..b3bf306431 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedge.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedge.cs @@ -420,15 +420,15 @@ namespace osu.Game.Screens.Select RelativeSizeAxes = Axes.Both, Colour = Color4Extensions.FromHex(@"f7dd55"), Icon = FontAwesome.Regular.Circle, - Scale = new Vector2(0.8f) + Size = new Vector2(0.8f) }, statistic.CreateIcon().With(i => { i.Anchor = Anchor.Centre; i.Origin = Anchor.Centre; i.RelativeSizeAxes = Axes.Both; - i.Size = new Vector2(1.2f); i.Colour = Color4Extensions.FromHex(@"f7dd55"); + i.Size = new Vector2(0.8f); }), } }, From 9d2dff2cb871403637511e2d7545dfad89d59c68 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 4 Sep 2020 12:55:39 +0900 Subject: [PATCH 0887/1782] Add scale to allow legacy icons to display correctly sized --- osu.Game/Beatmaps/BeatmapStatistic.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/BeatmapStatistic.cs b/osu.Game/Beatmaps/BeatmapStatistic.cs index 5a466c24be..15036d1cd6 100644 --- a/osu.Game/Beatmaps/BeatmapStatistic.cs +++ b/osu.Game/Beatmaps/BeatmapStatistic.cs @@ -4,6 +4,7 @@ using System; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; +using osuTK; namespace osu.Game.Beatmaps { @@ -23,7 +24,7 @@ namespace osu.Game.Beatmaps public BeatmapStatistic() { #pragma warning disable 618 - CreateIcon = () => new SpriteIcon { Icon = Icon }; + CreateIcon = () => new SpriteIcon { Icon = Icon, Scale = new Vector2(0.6f) }; #pragma warning restore 618 } } From cd253ab055e97b67cdc7b59a2fcea6bdeb971a39 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 4 Sep 2020 13:05:39 +0900 Subject: [PATCH 0888/1782] Further tweaks to get closer to design originals --- osu.Game/Beatmaps/BeatmapStatistic.cs | 2 +- osu.Game/Screens/Select/BeatmapInfoWedge.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapStatistic.cs b/osu.Game/Beatmaps/BeatmapStatistic.cs index 15036d1cd6..825bb08246 100644 --- a/osu.Game/Beatmaps/BeatmapStatistic.cs +++ b/osu.Game/Beatmaps/BeatmapStatistic.cs @@ -24,7 +24,7 @@ namespace osu.Game.Beatmaps public BeatmapStatistic() { #pragma warning disable 618 - CreateIcon = () => new SpriteIcon { Icon = Icon, Scale = new Vector2(0.6f) }; + CreateIcon = () => new SpriteIcon { Icon = Icon, Scale = new Vector2(0.7f) }; #pragma warning restore 618 } } diff --git a/osu.Game/Screens/Select/BeatmapInfoWedge.cs b/osu.Game/Screens/Select/BeatmapInfoWedge.cs index b3bf306431..400f3e3063 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedge.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedge.cs @@ -428,7 +428,7 @@ namespace osu.Game.Screens.Select i.Origin = Anchor.Centre; i.RelativeSizeAxes = Axes.Both; i.Colour = Color4Extensions.FromHex(@"f7dd55"); - i.Size = new Vector2(0.8f); + i.Size = new Vector2(0.64f); }), } }, From f14a82e3a927950e23631ed1c5eeb430c94c8957 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 4 Sep 2020 13:13:53 +0900 Subject: [PATCH 0889/1782] Remove unnecessary conversion --- osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs b/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs index 11b6ad8c29..362c99ea3f 100644 --- a/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs @@ -34,7 +34,7 @@ namespace osu.Game.Beatmaps protected override IBeatmap GetBeatmap() { if (BeatmapInfo.Path == null) - return BeatmapInfo?.Ruleset.CreateInstance().CreateBeatmapConverter(new Beatmap { BeatmapInfo = BeatmapInfo }).Beatmap; + return new Beatmap { BeatmapInfo = BeatmapInfo }; try { From d3fbc7cc53ea92a28e76ed07f11a6edc855c584e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 4 Sep 2020 13:16:35 +0900 Subject: [PATCH 0890/1782] Use more direct reference in tests --- osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs index 4547613b5a..6c60d7b467 100644 --- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs @@ -767,7 +767,7 @@ namespace osu.Game.Tests.Beatmaps.IO var osu = loadOsu(host); var manager = osu.Dependencies.Get(); - var working = osu.Dependencies.Get().CreateNew(new OsuRuleset().RulesetInfo); + var working = manager.CreateNew(new OsuRuleset().RulesetInfo); manager.Save(working.BeatmapInfo, working.Beatmap); @@ -794,7 +794,7 @@ namespace osu.Game.Tests.Beatmaps.IO var osu = loadOsu(host); var manager = osu.Dependencies.Get(); - var working = osu.Dependencies.Get().CreateNew(new OsuRuleset().RulesetInfo); + var working = manager.CreateNew(new OsuRuleset().RulesetInfo); ((Beatmap)working.Beatmap).HitObjects.Add(new HitCircle { StartTime = 5000 }); From fba253f131e7f5d279a0f011d404ef7685ab2c1c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 4 Sep 2020 13:17:43 +0900 Subject: [PATCH 0891/1782] Take user argument in CreateNew method parameters --- osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs | 5 +++-- osu.Game/Beatmaps/BeatmapManager.cs | 4 ++-- osu.Game/Screens/Edit/Editor.cs | 3 +-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs index 6c60d7b467..cef8105490 100644 --- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs @@ -18,6 +18,7 @@ using osu.Game.IO; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Tests.Resources; +using osu.Game.Users; using SharpCompress.Archives; using SharpCompress.Archives.Zip; using SharpCompress.Common; @@ -767,7 +768,7 @@ namespace osu.Game.Tests.Beatmaps.IO var osu = loadOsu(host); var manager = osu.Dependencies.Get(); - var working = manager.CreateNew(new OsuRuleset().RulesetInfo); + var working = manager.CreateNew(new OsuRuleset().RulesetInfo, User.SYSTEM_USER); manager.Save(working.BeatmapInfo, working.Beatmap); @@ -794,7 +795,7 @@ namespace osu.Game.Tests.Beatmaps.IO var osu = loadOsu(host); var manager = osu.Dependencies.Get(); - var working = manager.CreateNew(new OsuRuleset().RulesetInfo); + var working = manager.CreateNew(new OsuRuleset().RulesetInfo, User.SYSTEM_USER); ((Beatmap)working.Beatmap).HitObjects.Add(new HitCircle { StartTime = 5000 }); diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 12add76b46..844af31a16 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -95,13 +95,13 @@ namespace osu.Game.Beatmaps protected override bool ShouldDeleteArchive(string path) => Path.GetExtension(path)?.ToLowerInvariant() == ".osz"; - public WorkingBeatmap CreateNew(RulesetInfo ruleset) + public WorkingBeatmap CreateNew(RulesetInfo ruleset, User user) { var metadata = new BeatmapMetadata { Artist = "artist", Title = "title", - Author = User.SYSTEM_USER, + Author = user, }; var set = new BeatmapSetInfo diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index b8c1932186..ef497e3246 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -98,8 +98,7 @@ namespace osu.Game.Screens.Edit if (Beatmap.Value is DummyWorkingBeatmap) { isNewBeatmap = true; - Beatmap.Value = beatmapManager.CreateNew(Ruleset.Value); - Beatmap.Value.BeatmapSetInfo.Metadata.Author = api.LocalUser.Value; + Beatmap.Value = beatmapManager.CreateNew(Ruleset.Value, api.LocalUser.Value); } try From 7c99f66cf518fe4696ac33c5a4e2ad1a01a7825f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 4 Sep 2020 14:13:42 +0900 Subject: [PATCH 0892/1782] 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 a41c1a5864..7dfda5babb 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -51,7 +51,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index d79806883e..cd7dcbb8db 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 16a8a1acb7..284b717a0f 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -71,7 +71,7 @@ - + From 25e142965d925ef6936b6424175b3736041dbcfd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 4 Sep 2020 15:01:32 +0900 Subject: [PATCH 0893/1782] Strongly type and expose default beatmap information icon implementations for other rulesets --- .../Beatmaps/CatchBeatmap.cs | 6 +-- .../Beatmaps/ManiaBeatmap.cs | 4 +- osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmap.cs | 6 +-- .../Beatmaps/TaikoBeatmap.cs | 6 +-- osu.Game/Beatmaps/BeatmapStatistic.cs | 2 +- osu.Game/Beatmaps/BeatmapStatisticIcon.cs | 43 +++++++++++++++++++ osu.Game/Beatmaps/BeatmapStatisticSprite.cs | 25 ----------- osu.Game/Screens/Select/BeatmapInfoWedge.cs | 4 +- 8 files changed, 57 insertions(+), 39 deletions(-) create mode 100644 osu.Game/Beatmaps/BeatmapStatisticIcon.cs delete mode 100644 osu.Game/Beatmaps/BeatmapStatisticSprite.cs diff --git a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmap.cs b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmap.cs index 5dc19ce15b..f009c10a9c 100644 --- a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmap.cs +++ b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmap.cs @@ -22,19 +22,19 @@ namespace osu.Game.Rulesets.Catch.Beatmaps { Name = @"Fruit Count", Content = fruits.ToString(), - CreateIcon = () => new BeatmapStatisticSprite("circles"), + CreateIcon = () => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Circles), }, new BeatmapStatistic { Name = @"Juice Stream Count", Content = juiceStreams.ToString(), - CreateIcon = () => new BeatmapStatisticSprite("sliders"), + CreateIcon = () => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Sliders), }, new BeatmapStatistic { Name = @"Banana Shower Count", Content = bananaShowers.ToString(), - CreateIcon = () => new BeatmapStatisticSprite("spinners"), + CreateIcon = () => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Spinners), } }; } diff --git a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmap.cs b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmap.cs index f6b460f269..d1d5adea75 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmap.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmap.cs @@ -40,13 +40,13 @@ namespace osu.Game.Rulesets.Mania.Beatmaps new BeatmapStatistic { Name = @"Note Count", - CreateIcon = () => new BeatmapStatisticSprite("circles"), + CreateIcon = () => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Circles), Content = notes.ToString(), }, new BeatmapStatistic { Name = @"Hold Note Count", - CreateIcon = () => new BeatmapStatisticSprite("sliders"), + CreateIcon = () => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Sliders), Content = holdnotes.ToString(), }, }; diff --git a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmap.cs b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmap.cs index 513a9254ec..2d3cc3c103 100644 --- a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmap.cs +++ b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmap.cs @@ -22,19 +22,19 @@ namespace osu.Game.Rulesets.Osu.Beatmaps { Name = @"Circle Count", Content = circles.ToString(), - CreateIcon = () => new BeatmapStatisticSprite("circles"), + CreateIcon = () => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Circles), }, new BeatmapStatistic { Name = @"Slider Count", Content = sliders.ToString(), - CreateIcon = () => new BeatmapStatisticSprite("sliders"), + CreateIcon = () => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Sliders), }, new BeatmapStatistic { Name = @"Spinner Count", Content = spinners.ToString(), - CreateIcon = () => new BeatmapStatisticSprite("spinners"), + CreateIcon = () => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Spinners), } }; } diff --git a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmap.cs b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmap.cs index c0f8af4fff..16a0726c8c 100644 --- a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmap.cs +++ b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmap.cs @@ -21,19 +21,19 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps new BeatmapStatistic { Name = @"Hit Count", - CreateIcon = () => new BeatmapStatisticSprite("circles"), + CreateIcon = () => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Circles), Content = hits.ToString(), }, new BeatmapStatistic { Name = @"Drumroll Count", - CreateIcon = () => new BeatmapStatisticSprite("sliders"), + CreateIcon = () => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Sliders), Content = drumrolls.ToString(), }, new BeatmapStatistic { Name = @"Swell Count", - CreateIcon = () => new BeatmapStatisticSprite("spinners"), + CreateIcon = () => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Spinners), Content = swells.ToString(), } }; diff --git a/osu.Game/Beatmaps/BeatmapStatistic.cs b/osu.Game/Beatmaps/BeatmapStatistic.cs index 825bb08246..9d87a20d60 100644 --- a/osu.Game/Beatmaps/BeatmapStatistic.cs +++ b/osu.Game/Beatmaps/BeatmapStatistic.cs @@ -14,7 +14,7 @@ namespace osu.Game.Beatmaps public IconUsage Icon = FontAwesome.Regular.QuestionCircle; /// - /// A function to create the icon for display purposes. + /// A function to create the icon for display purposes. Use default icons available via whenever possible for conformity. /// public Func CreateIcon; diff --git a/osu.Game/Beatmaps/BeatmapStatisticIcon.cs b/osu.Game/Beatmaps/BeatmapStatisticIcon.cs new file mode 100644 index 0000000000..181fb540df --- /dev/null +++ b/osu.Game/Beatmaps/BeatmapStatisticIcon.cs @@ -0,0 +1,43 @@ +// 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.Graphics.Sprites; +using osu.Framework.Graphics.Textures; + +namespace osu.Game.Beatmaps +{ + /// + /// A default implementation of an icon used to represent beatmap statistics. + /// + public class BeatmapStatisticIcon : Sprite + { + private readonly BeatmapStatisticsIconType iconType; + + public BeatmapStatisticIcon(BeatmapStatisticsIconType iconType) + { + this.iconType = iconType; + } + + [BackgroundDependencyLoader] + private void load(TextureStore textures) + { + Texture = textures.Get($"Icons/BeatmapDetails/{iconType.ToString().Kebaberize()}"); + } + } + + public enum BeatmapStatisticsIconType + { + Accuracy, + ApproachRate, + Bpm, + Circles, + HpDrain, + Length, + OverallDifficulty, + Size, + Sliders, + Spinners, + } +} diff --git a/osu.Game/Beatmaps/BeatmapStatisticSprite.cs b/osu.Game/Beatmaps/BeatmapStatisticSprite.cs deleted file mode 100644 index 1cb0bacf0f..0000000000 --- a/osu.Game/Beatmaps/BeatmapStatisticSprite.cs +++ /dev/null @@ -1,25 +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.Sprites; -using osu.Framework.Graphics.Textures; - -namespace osu.Game.Beatmaps -{ - public class BeatmapStatisticSprite : Sprite - { - private readonly string iconName; - - public BeatmapStatisticSprite(string iconName) - { - this.iconName = iconName; - } - - [BackgroundDependencyLoader] - private void load(TextureStore textures) - { - Texture = textures.Get($"Icons/BeatmapDetails/{iconName}"); - } - } -} diff --git a/osu.Game/Screens/Select/BeatmapInfoWedge.cs b/osu.Game/Screens/Select/BeatmapInfoWedge.cs index 400f3e3063..2a3eb8c67a 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedge.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedge.cs @@ -318,14 +318,14 @@ namespace osu.Game.Screens.Select labels.Add(new InfoLabel(new BeatmapStatistic { Name = "Length", - CreateIcon = () => new BeatmapStatisticSprite("length"), + CreateIcon = () => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Length), Content = TimeSpan.FromMilliseconds(b.BeatmapInfo.Length).ToString(@"m\:ss"), })); labels.Add(new InfoLabel(new BeatmapStatistic { Name = "BPM", - CreateIcon = () => new BeatmapStatisticSprite("bpm"), + CreateIcon = () => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Bpm), Content = getBPMRange(b), })); From 4399f5976c2e380937311e925652c4a4be60accc Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 4 Sep 2020 15:20:55 +0900 Subject: [PATCH 0894/1782] Fix global mods being retained by rooms --- .../Multiplayer/TestSceneMatchSongSelect.cs | 22 +++++++++++++++++++ osu.Game/Screens/Select/MatchSongSelect.cs | 4 +--- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchSongSelect.cs index 3d225aa0a9..faea32f90f 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchSongSelect.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchSongSelect.cs @@ -162,6 +162,28 @@ namespace osu.Game.Tests.Visual.Multiplayer AddAssert("item 2 has rate 2", () => Precision.AlmostEquals(2, ((OsuModDoubleTime)Room.Playlist.Last().RequiredMods[0]).SpeedChange.Value)); } + /// + /// Tests that the global mod instances are not retained by the rooms, as global mod instances are retained and re-used by the mod select overlay. + /// + [Test] + public void TestGlobalModInstancesNotRetained() + { + OsuModDoubleTime mod = null; + + AddStep("set dt mod and store", () => + { + SelectedMods.Value = new[] { new OsuModDoubleTime() }; + + // Mod select overlay replaces our mod. + mod = (OsuModDoubleTime)SelectedMods.Value[0]; + }); + + AddStep("create item", () => songSelect.BeatmapDetails.CreateNewItem()); + + AddStep("change stored mod rate", () => mod.SpeedChange.Value = 2); + AddAssert("item has rate 1.5", () => Precision.AlmostEquals(1.5, ((OsuModDoubleTime)Room.Playlist.First().RequiredMods[0]).SpeedChange.Value)); + } + private class TestMatchSongSelect : MatchSongSelect { public new MatchBeatmapDetailArea BeatmapDetails => (MatchBeatmapDetailArea)base.BeatmapDetails; diff --git a/osu.Game/Screens/Select/MatchSongSelect.cs b/osu.Game/Screens/Select/MatchSongSelect.cs index 96a48fa3ac..8692833a21 100644 --- a/osu.Game/Screens/Select/MatchSongSelect.cs +++ b/osu.Game/Screens/Select/MatchSongSelect.cs @@ -76,9 +76,7 @@ namespace osu.Game.Screens.Select item.Ruleset.Value = Ruleset.Value; item.RequiredMods.Clear(); - item.RequiredMods.AddRange(Mods.Value); - - Mods.Value = Mods.Value.Select(m => m.CreateCopy()).ToArray(); + item.RequiredMods.AddRange(Mods.Value.Select(m => m.CreateCopy())); } } } From 0b3f2fe7df1d956d6cf3d53a263461957cb580ae Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 4 Sep 2020 15:21:48 +0900 Subject: [PATCH 0895/1782] 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 a41c1a5864..a096370a05 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,6 +52,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index d79806883e..cb8e30a084 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -24,7 +24,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 16a8a1acb7..72b17d216d 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -80,7 +80,7 @@ - + From a15653c77cf79c1a6fc8628979d30c2dbb95492e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 4 Sep 2020 16:15:57 +0900 Subject: [PATCH 0896/1782] Fix potential hard crash if ruleset settings fail to construct --- .../Overlays/Settings/Sections/GameplaySection.cs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Settings/Sections/GameplaySection.cs b/osu.Game/Overlays/Settings/Sections/GameplaySection.cs index aca507f20a..c09e3a227d 100644 --- a/osu.Game/Overlays/Settings/Sections/GameplaySection.cs +++ b/osu.Game/Overlays/Settings/Sections/GameplaySection.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.Allocation; using osu.Framework.Graphics; using osu.Game.Overlays.Settings.Sections.Gameplay; using osu.Game.Rulesets; using System.Linq; using osu.Framework.Graphics.Sprites; +using osu.Framework.Logging; namespace osu.Game.Overlays.Settings.Sections { @@ -35,8 +37,18 @@ namespace osu.Game.Overlays.Settings.Sections foreach (Ruleset ruleset in rulesets.AvailableRulesets.Select(info => info.CreateInstance())) { SettingsSubsection section = ruleset.CreateSettings(); + if (section != null) - Add(section); + { + try + { + Add(section); + } + catch (Exception e) + { + Logger.Error(e, $"Failed to load ruleset settings"); + } + } } } } From 54013790fc9c4d86612c9bcce0609c76aeaada9d Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 4 Sep 2020 09:45:24 +0300 Subject: [PATCH 0897/1782] Fix MusicController raising TrackChanged event twice --- 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 17877a69a5..119aad5226 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -279,6 +279,10 @@ namespace osu.Game.Overlays private void changeBeatmap(WorkingBeatmap newWorking) { + // The provided beatmap is same as current, no need to do any changes. + if (newWorking == current) + return; + var lastWorking = current; TrackChangeDirection direction = TrackChangeDirection.None; From 42895e27b6f1188e6e623d5a921c99f4b4f0038f Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 4 Sep 2020 10:10:14 +0300 Subject: [PATCH 0898/1782] Expose track change results on the methods --- osu.Game/Overlays/MusicController.cs | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index 119aad5226..ea72ef0b84 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -207,7 +207,18 @@ namespace osu.Game.Overlays /// /// Play the previous track or restart the current track if it's current time below . /// - public void PreviousTrack() => Schedule(() => prev()); + /// + /// Invoked when the operation has been performed successfully. + /// The result isn't returned directly to the caller because + /// the operation is scheduled and isn't performed immediately. + /// + /// A of the operation. + public ScheduledDelegate PreviousTrack(Action onSuccess = null) => Schedule(() => + { + PreviousTrackResult res = prev(); + if (res != PreviousTrackResult.None) + onSuccess?.Invoke(res); + }); /// /// Play the previous track or restart the current track if it's current time below . @@ -243,7 +254,18 @@ namespace osu.Game.Overlays /// /// Play the next random or playlist track. /// - public void NextTrack() => Schedule(() => next()); + /// + /// Invoked when the operation has been performed successfully. + /// The result isn't returned directly to the caller because + /// the operation is scheduled and isn't performed immediately. + /// + /// A of the operation. + public ScheduledDelegate NextTrack(Action onSuccess = null) => Schedule(() => + { + bool res = next(); + if (res) + onSuccess?.Invoke(); + }); private bool next() { From 001509df55015a32b5b6f6d582c805f0a4f459f8 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 4 Sep 2020 10:22:37 +0300 Subject: [PATCH 0899/1782] Move music global action handling to an own component Due to requiring components that are added at an OsuGame-level --- osu.Game/OsuGame.cs | 2 + osu.Game/Overlays/Music/MusicActionHandler.cs | 82 +++++++++++++++++++ osu.Game/Overlays/MusicController.cs | 56 +------------ 3 files changed, 85 insertions(+), 55 deletions(-) create mode 100644 osu.Game/Overlays/Music/MusicActionHandler.cs diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 164a40c6a5..a73469d836 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -38,6 +38,7 @@ using osu.Game.Input; using osu.Game.Overlays.Notifications; using osu.Game.Input.Bindings; using osu.Game.Online.Chat; +using osu.Game.Overlays.Music; using osu.Game.Skinning; using osuTK.Graphics; using osu.Game.Overlays.Volume; @@ -647,6 +648,7 @@ namespace osu.Game chatOverlay.State.ValueChanged += state => channelManager.HighPollRate.Value = state.NewValue == Visibility.Visible; Add(externalLinkOpener = new ExternalLinkOpener()); + Add(new MusicActionHandler()); // side overlays which cancel each other. var singleDisplaySideOverlays = new OverlayContainer[] { Settings, notifications }; diff --git a/osu.Game/Overlays/Music/MusicActionHandler.cs b/osu.Game/Overlays/Music/MusicActionHandler.cs new file mode 100644 index 0000000000..17aa4c1d32 --- /dev/null +++ b/osu.Game/Overlays/Music/MusicActionHandler.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 osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Input.Bindings; +using osu.Game.Beatmaps; +using osu.Game.Input.Bindings; +using osu.Game.Overlays.OSD; + +namespace osu.Game.Overlays.Music +{ + /// + /// Handles relating to music playback, and displays a via the cached accordingly. + /// + public class MusicActionHandler : Component, IKeyBindingHandler + { + [Resolved] + private IBindable beatmap { get; set; } + + [Resolved] + private MusicController musicController { get; set; } + + [Resolved] + private OnScreenDisplay onScreenDisplay { get; set; } + + public bool OnPressed(GlobalAction action) + { + if (beatmap.Disabled) + return false; + + switch (action) + { + case GlobalAction.MusicPlay: + if (musicController.TogglePause()) + onScreenDisplay.Display(new MusicActionToast(musicController.IsPlaying ? "Play track" : "Pause track")); + + return true; + + case GlobalAction.MusicNext: + musicController.NextTrack(() => + { + onScreenDisplay.Display(new MusicActionToast("Next track")); + }).RunTask(); + + return true; + + case GlobalAction.MusicPrev: + musicController.PreviousTrack(res => + { + switch (res) + { + case PreviousTrackResult.Restart: + onScreenDisplay.Display(new MusicActionToast("Restart track")); + break; + + case PreviousTrackResult.Previous: + onScreenDisplay.Display(new MusicActionToast("Previous track")); + break; + } + }).RunTask(); + + return true; + } + + return false; + } + + public void OnReleased(GlobalAction action) + { + } + + private class MusicActionToast : Toast + { + public MusicActionToast(string action) + : base("Music Playback", action, string.Empty) + { + } + } + } +} diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index ea72ef0b84..69722a8c0c 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -12,12 +12,9 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Audio; using osu.Framework.Graphics.Containers; -using osu.Framework.Input.Bindings; using osu.Framework.Utils; 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 @@ -25,7 +22,7 @@ namespace osu.Game.Overlays /// /// Handles playback of the global music track. /// - public class MusicController : CompositeDrawable, IKeyBindingHandler + public class MusicController : CompositeDrawable { [Resolved] private BeatmapManager beatmaps { get; set; } @@ -62,9 +59,6 @@ namespace osu.Game.Overlays [Resolved] private IBindable> mods { get; set; } - [Resolved(canBeNull: true)] - private OnScreenDisplay onScreenDisplay { get; set; } - [NotNull] public DrawableTrack CurrentTrack { get; private set; } = new DrawableTrack(new TrackVirtual(1000)); @@ -428,54 +422,6 @@ namespace osu.Game.Overlays mod.ApplyToTrack(CurrentTrack); } } - - public bool OnPressed(GlobalAction action) - { - if (beatmap.Disabled) - return false; - - switch (action) - { - case GlobalAction.MusicPlay: - if (TogglePause()) - onScreenDisplay?.Display(new MusicControllerToast(IsPlaying ? "Play track" : "Pause track")); - return true; - - case GlobalAction.MusicNext: - if (next()) - onScreenDisplay?.Display(new MusicControllerToast("Next track")); - - return true; - - case GlobalAction.MusicPrev: - switch (prev()) - { - case PreviousTrackResult.Restart: - onScreenDisplay?.Display(new MusicControllerToast("Restart track")); - break; - - case PreviousTrackResult.Previous: - onScreenDisplay?.Display(new MusicControllerToast("Previous track")); - break; - } - - return true; - } - - return false; - } - - public void OnReleased(GlobalAction action) - { - } - - public class MusicControllerToast : Toast - { - public MusicControllerToast(string action) - : base("Music Playback", action, string.Empty) - { - } - } } public enum TrackChangeDirection From 4d9a06bde993bd416604e0b13e71a4fedf72873f Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 4 Sep 2020 10:23:06 +0300 Subject: [PATCH 0900/1782] Expose the global binding container to OsuGameTestScene --- osu.Game.Tests/Visual/Navigation/OsuGameTestScene.cs | 3 +++ osu.Game/OsuGameBase.cs | 10 +++++----- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/Navigation/OsuGameTestScene.cs b/osu.Game.Tests/Visual/Navigation/OsuGameTestScene.cs index c4acf4f7da..e29d23ba75 100644 --- a/osu.Game.Tests/Visual/Navigation/OsuGameTestScene.cs +++ b/osu.Game.Tests/Visual/Navigation/OsuGameTestScene.cs @@ -14,6 +14,7 @@ using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Graphics.UserInterface; +using osu.Game.Input.Bindings; using osu.Game.Online.API; using osu.Game.Overlays; using osu.Game.Rulesets; @@ -109,6 +110,8 @@ namespace osu.Game.Tests.Visual.Navigation public new OsuConfigManager LocalConfig => base.LocalConfig; + public new GlobalActionContainer GlobalBinding => base.GlobalBinding; + public new Bindable Beatmap => base.Beatmap; public new Bindable Ruleset => base.Ruleset; diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 51b9b7278d..8e01bda6ec 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -64,6 +64,8 @@ namespace osu.Game protected FileStore FileStore; + protected GlobalActionContainer GlobalBinding; + protected KeyBindingStore KeyBindingStore; protected SettingsStore SettingsStore; @@ -250,10 +252,8 @@ namespace osu.Game AddInternal(apiAccess); AddInternal(RulesetConfigCache); - GlobalActionContainer globalBinding; - MenuCursorContainer = new MenuCursorContainer { RelativeSizeAxes = Axes.Both }; - MenuCursorContainer.Child = globalBinding = new GlobalActionContainer(this) + MenuCursorContainer.Child = GlobalBinding = new GlobalActionContainer(this) { RelativeSizeAxes = Axes.Both, Child = content = new OsuTooltipContainer(MenuCursorContainer.Cursor) { RelativeSizeAxes = Axes.Both } @@ -261,8 +261,8 @@ namespace osu.Game base.Content.Add(CreateScalingContainer().WithChild(MenuCursorContainer)); - KeyBindingStore.Register(globalBinding); - dependencies.Cache(globalBinding); + KeyBindingStore.Register(GlobalBinding); + dependencies.Cache(GlobalBinding); PreviewTrackManager previewTrackManager; dependencies.Cache(previewTrackManager = new PreviewTrackManager()); From 314031d56d342f722d998d0897769ea2de4bde9c Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 4 Sep 2020 10:23:34 +0300 Subject: [PATCH 0901/1782] Add test cases ensuring music actions are handled from a game instance --- .../Menus/TestSceneMusicActionHandling.cs | 80 +++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 osu.Game.Tests/Visual/Menus/TestSceneMusicActionHandling.cs diff --git a/osu.Game.Tests/Visual/Menus/TestSceneMusicActionHandling.cs b/osu.Game.Tests/Visual/Menus/TestSceneMusicActionHandling.cs new file mode 100644 index 0000000000..9121309489 --- /dev/null +++ b/osu.Game.Tests/Visual/Menus/TestSceneMusicActionHandling.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 System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using osu.Game.Beatmaps; +using osu.Game.Input.Bindings; +using osu.Game.Overlays; +using osu.Game.Tests.Resources; +using osu.Game.Tests.Visual.Navigation; + +namespace osu.Game.Tests.Visual.Menus +{ + public class TestSceneMusicActionHandling : OsuGameTestScene + { + [Test] + public void TestMusicPlayAction() + { + AddStep("ensure playing something", () => Game.MusicController.EnsurePlayingSomething()); + AddStep("trigger music playback toggle action", () => Game.GlobalBinding.TriggerPressed(GlobalAction.MusicPlay)); + AddAssert("music paused", () => !Game.MusicController.IsPlaying && Game.MusicController.IsUserPaused); + AddStep("trigger music playback toggle action", () => Game.GlobalBinding.TriggerPressed(GlobalAction.MusicPlay)); + AddAssert("music resumed", () => Game.MusicController.IsPlaying && !Game.MusicController.IsUserPaused); + } + + [Test] + public void TestMusicNavigationActions() + { + int importId = 0; + Queue<(WorkingBeatmap working, TrackChangeDirection dir)> trackChangeQueue = null; + + // ensure we have at least two beatmaps available to identify the direction the music controller navigated to. + AddRepeatStep("import beatmap", () => Game.BeatmapManager.Import(new BeatmapSetInfo + { + Beatmaps = new List + { + new BeatmapInfo + { + BaseDifficulty = new BeatmapDifficulty(), + } + }, + Metadata = new BeatmapMetadata + { + Artist = $"a test map {importId++}", + Title = "title", + } + }).Wait(), 5); + + AddStep("import beatmap with track", () => + { + var setWithTrack = Game.BeatmapManager.Import(TestResources.GetTestBeatmapForImport()).Result; + Beatmap.Value = Game.BeatmapManager.GetWorkingBeatmap(setWithTrack.Beatmaps.First()); + }); + + AddStep("bind to track change", () => + { + trackChangeQueue = new Queue<(WorkingBeatmap working, TrackChangeDirection dir)>(); + Game.MusicController.TrackChanged += (working, dir) => trackChangeQueue.Enqueue((working, dir)); + }); + + AddStep("seek track to 6 second", () => Game.MusicController.SeekTo(6000)); + AddUntilStep("wait for current time to update", () => Game.MusicController.CurrentTrack.CurrentTime > 5000); + + AddStep("trigger music prev action", () => Game.GlobalBinding.TriggerPressed(GlobalAction.MusicPrev)); + AddAssert("no track change", () => trackChangeQueue.Count == 0); + AddUntilStep("track restarted", () => Game.MusicController.CurrentTrack.CurrentTime < 5000); + + AddStep("trigger music prev action", () => Game.GlobalBinding.TriggerPressed(GlobalAction.MusicPrev)); + AddAssert("track changed to previous", () => + trackChangeQueue.Count == 1 && + trackChangeQueue.Dequeue().dir == TrackChangeDirection.Prev); + + AddStep("trigger music next action", () => Game.GlobalBinding.TriggerPressed(GlobalAction.MusicNext)); + AddAssert("track changed to next", () => + trackChangeQueue.Count == 1 && + trackChangeQueue.Dequeue().dir == TrackChangeDirection.Next); + } + } +} From 0500f24a1dbdede80d403e1f9b03fea9cfabc901 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 4 Sep 2020 10:24:07 +0300 Subject: [PATCH 0902/1782] Remove now-redundant test case --- .../TestSceneNowPlayingOverlay.cs | 58 +------------------ 1 file changed, 1 insertion(+), 57 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneNowPlayingOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneNowPlayingOverlay.cs index c14a1ddbf2..475ab0c414 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneNowPlayingOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneNowPlayingOverlay.cs @@ -1,18 +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.Collections.Generic; -using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; -using osu.Framework.Audio; using osu.Framework.Graphics; -using osu.Framework.Platform; -using osu.Game.Beatmaps; using osu.Game.Overlays; -using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; -using osu.Game.Tests.Resources; namespace osu.Game.Tests.Visual.UserInterface { @@ -24,14 +17,9 @@ namespace osu.Game.Tests.Visual.UserInterface private NowPlayingOverlay nowPlayingOverlay; - private RulesetStore rulesets; - [BackgroundDependencyLoader] - private void load(AudioManager audio, GameHost host) + private void load() { - Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); - Dependencies.Cache(manager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, host, Beatmap.Default)); - Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo); nowPlayingOverlay = new NowPlayingOverlay @@ -51,49 +39,5 @@ namespace osu.Game.Tests.Visual.UserInterface AddToggleStep(@"toggle beatmap lock", state => Beatmap.Disabled = state); AddStep(@"hide", () => nowPlayingOverlay.Hide()); } - - private BeatmapManager manager { get; set; } - - private int importId; - - [Test] - public void TestPrevTrackBehavior() - { - // ensure we have at least two beatmaps available. - AddRepeatStep("import beatmap", () => manager.Import(new BeatmapSetInfo - { - Beatmaps = new List - { - new BeatmapInfo - { - BaseDifficulty = new BeatmapDifficulty(), - } - }, - Metadata = new BeatmapMetadata - { - Artist = $"a test map {importId++}", - Title = "title", - } - }).Wait(), 5); - - WorkingBeatmap currentBeatmap = null; - - AddStep("import beatmap with track", () => - { - var setWithTrack = manager.Import(TestResources.GetTestBeatmapForImport()).Result; - Beatmap.Value = currentBeatmap = manager.GetWorkingBeatmap(setWithTrack.Beatmaps.First()); - }); - - AddStep(@"Seek track to 6 second", () => musicController.SeekTo(6000)); - AddUntilStep(@"Wait for current time to update", () => musicController.CurrentTrack.CurrentTime > 5000); - - AddStep(@"Set previous", () => musicController.PreviousTrack()); - - AddAssert(@"Check beatmap didn't change", () => currentBeatmap == Beatmap.Value); - AddUntilStep("Wait for current time to update", () => musicController.CurrentTrack.CurrentTime < 5000); - - AddStep(@"Set previous", () => musicController.PreviousTrack()); - AddAssert(@"Check beatmap did change", () => currentBeatmap != Beatmap.Value); - } } } From 644f3375ac2210428cf7dfbd8afb58fb9e0dd0aa Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 4 Sep 2020 16:28:19 +0900 Subject: [PATCH 0903/1782] Also catch exceptions in the construction call --- .../Settings/Sections/GameplaySection.cs | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/GameplaySection.cs b/osu.Game/Overlays/Settings/Sections/GameplaySection.cs index c09e3a227d..f76b8e085b 100644 --- a/osu.Game/Overlays/Settings/Sections/GameplaySection.cs +++ b/osu.Game/Overlays/Settings/Sections/GameplaySection.cs @@ -36,18 +36,16 @@ namespace osu.Game.Overlays.Settings.Sections { foreach (Ruleset ruleset in rulesets.AvailableRulesets.Select(info => info.CreateInstance())) { - SettingsSubsection section = ruleset.CreateSettings(); - - if (section != null) + try { - try - { + SettingsSubsection section = ruleset.CreateSettings(); + + if (section != null) Add(section); - } - catch (Exception e) - { - Logger.Error(e, $"Failed to load ruleset settings"); - } + } + catch (Exception e) + { + Logger.Error(e, $"Failed to load ruleset settings"); } } } From ab057e6c654886d961b5abd7b2bdd795a93ed28f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 4 Sep 2020 16:28:35 +0900 Subject: [PATCH 0904/1782] Remove unnecessary string interpolation --- osu.Game/Overlays/Settings/Sections/GameplaySection.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Settings/Sections/GameplaySection.cs b/osu.Game/Overlays/Settings/Sections/GameplaySection.cs index f76b8e085b..e5cebd28e2 100644 --- a/osu.Game/Overlays/Settings/Sections/GameplaySection.cs +++ b/osu.Game/Overlays/Settings/Sections/GameplaySection.cs @@ -45,7 +45,7 @@ namespace osu.Game.Overlays.Settings.Sections } catch (Exception e) { - Logger.Error(e, $"Failed to load ruleset settings"); + Logger.Error(e, "Failed to load ruleset settings"); } } } From 65d541456ab8a408095c1b028930f72dfc72f902 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 4 Sep 2020 11:11:07 +0300 Subject: [PATCH 0905/1782] Slight rewording --- 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 119aad5226..74a438a124 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -279,7 +279,7 @@ namespace osu.Game.Overlays private void changeBeatmap(WorkingBeatmap newWorking) { - // The provided beatmap is same as current, no need to do any changes. + // If the provided beatmap is same as current, then there is no need to do any changes. if (newWorking == current) return; From 4236e5fe71ceec15ffd6881532d8fda26dbd8f38 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 4 Sep 2020 11:31:54 +0300 Subject: [PATCH 0906/1782] Replace useless "matching-code" comment with explanation of how it could happen Co-authored-by: Dean Herbert --- osu.Game/Overlays/MusicController.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index 74a438a124..edef4d8589 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -279,7 +279,8 @@ namespace osu.Game.Overlays private void changeBeatmap(WorkingBeatmap newWorking) { - // If the provided beatmap is same as current, then there is no need to do any changes. + // This method can potentially be triggered multiple times as it is eagerly fired in next() / prev() to ensure correct execution order + // (changeBeatmap must be called before consumers receive the bindable changed event, which is not the case when called from the bindable itself). if (newWorking == current) return; From 3239576a239922e8dad80f61bff40a1b42205618 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 4 Sep 2020 11:50:49 +0300 Subject: [PATCH 0907/1782] Minor rewording of new comment Co-authored-by: Dean Herbert --- 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 edef4d8589..31bd80d6f3 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -280,7 +280,7 @@ namespace osu.Game.Overlays private void changeBeatmap(WorkingBeatmap newWorking) { // This method can potentially be triggered multiple times as it is eagerly fired in next() / prev() to ensure correct execution order - // (changeBeatmap must be called before consumers receive the bindable changed event, which is not the case when called from the bindable itself). + // (changeBeatmap must be called before consumers receive the bindable changed event, which is not the case when the local beatmap bindable is updated directly). if (newWorking == current) return; From 569a56eccb85307cd3e4c080cb22492a116c2480 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 4 Sep 2020 13:33:23 +0300 Subject: [PATCH 0908/1782] Revert "Move adjustment to LegacySkinConfiguration as a default value" This reverts commit 18927304f10bd912fc9a09cb22aaed3dd38974d0. --- osu.Game.Rulesets.Osu/Skinning/LegacySliderBody.cs | 4 ++++ osu.Game/Skinning/LegacySkinConfiguration.cs | 9 --------- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacySliderBody.cs b/osu.Game.Rulesets.Osu/Skinning/LegacySliderBody.cs index 21df49d80b..aad8b189d9 100644 --- a/osu.Game.Rulesets.Osu/Skinning/LegacySliderBody.cs +++ b/osu.Game.Rulesets.Osu/Skinning/LegacySliderBody.cs @@ -18,6 +18,10 @@ namespace osu.Game.Rulesets.Osu.Skinning { private const float shadow_portion = 1 - (OsuLegacySkinTransformer.LEGACY_CIRCLE_RADIUS / OsuHitObject.OBJECT_RADIUS); + protected new float CalculatedBorderPortion + // Roughly matches osu!stable's slider border portions. + => base.CalculatedBorderPortion * 0.77f; + 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) diff --git a/osu.Game/Skinning/LegacySkinConfiguration.cs b/osu.Game/Skinning/LegacySkinConfiguration.cs index b980d727ed..41b7aea34b 100644 --- a/osu.Game/Skinning/LegacySkinConfiguration.cs +++ b/osu.Game/Skinning/LegacySkinConfiguration.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.Globalization; - namespace osu.Game.Skinning { public class LegacySkinConfiguration : SkinConfiguration @@ -14,13 +12,6 @@ namespace osu.Game.Skinning /// public decimal? LegacyVersion { get; internal set; } - public LegacySkinConfiguration() - { - // Roughly matches osu!stable's slider border portions. - // Can't use nameof(SliderBorderSize) as the lookup enum is declared in the osu! ruleset. - ConfigDictionary["SliderBorderSize"] = 0.77f.ToString(CultureInfo.InvariantCulture); - } - public enum LegacySetting { Version, From 1143d5d9928d58fb2dd058b2b1dca31f1b868281 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 4 Sep 2020 20:34:26 +0900 Subject: [PATCH 0909/1782] Update class exclusion for dynamic compilation --- osu.Game.Rulesets.Catch/CatchRuleset.cs | 2 -- osu.Game.Rulesets.Mania/ManiaRuleset.cs | 2 -- osu.Game.Rulesets.Osu/OsuRuleset.cs | 2 -- osu.Game.Rulesets.Taiko/TaikoRuleset.cs | 2 -- .../Navigation/TestScenePresentScore.cs | 1 + osu.Game/Beatmaps/BeatmapInfo.cs | 2 ++ osu.Game/Beatmaps/BeatmapMetadata.cs | 2 ++ osu.Game/Beatmaps/BeatmapSetInfo.cs | 2 ++ osu.Game/Beatmaps/WorkingBeatmap.cs | 2 ++ osu.Game/Configuration/OsuConfigManager.cs | 2 ++ osu.Game/Input/GameIdleTracker.cs | 25 +++++++++++++++++++ osu.Game/OsuGame.cs | 24 ------------------ osu.Game/Rulesets/Mods/Mod.cs | 2 ++ osu.Game/Rulesets/Ruleset.cs | 2 ++ osu.Game/Rulesets/RulesetInfo.cs | 2 ++ osu.Game/Screens/ScorePresentType.cs | 11 ++++++++ osu.Game/Tests/Visual/OsuTestScene.cs | 1 + 17 files changed, 54 insertions(+), 32 deletions(-) create mode 100644 osu.Game/Input/GameIdleTracker.cs create mode 100644 osu.Game/Screens/ScorePresentType.cs diff --git a/osu.Game.Rulesets.Catch/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs index 9437023c70..ca75a816f1 100644 --- a/osu.Game.Rulesets.Catch/CatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs @@ -21,13 +21,11 @@ using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; using System; -using osu.Framework.Testing; using osu.Game.Rulesets.Catch.Skinning; using osu.Game.Skinning; namespace osu.Game.Rulesets.Catch { - [ExcludeFromDynamicCompile] public class CatchRuleset : Ruleset, ILegacyRuleset { public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList mods = null) => new DrawableCatchRuleset(this, beatmap, mods); diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index 37b34d1721..71ac85dd1b 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -12,7 +12,6 @@ using System.Linq; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Bindings; -using osu.Framework.Testing; using osu.Game.Graphics; using osu.Game.Rulesets.Mania.Replays; using osu.Game.Rulesets.Replays.Types; @@ -35,7 +34,6 @@ using osu.Game.Screens.Ranking.Statistics; namespace osu.Game.Rulesets.Mania { - [ExcludeFromDynamicCompile] public class ManiaRuleset : Ruleset, ILegacyRuleset { /// diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index f527eb2312..7f4a0dcbbb 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -30,14 +30,12 @@ using osu.Game.Scoring; using osu.Game.Skinning; using System; using System.Linq; -using osu.Framework.Testing; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Statistics; using osu.Game.Screens.Ranking.Statistics; namespace osu.Game.Rulesets.Osu { - [ExcludeFromDynamicCompile] public class OsuRuleset : Ruleset, ILegacyRuleset { public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList mods = null) => new DrawableOsuRuleset(this, beatmap, mods); diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index dbc32f2c3e..9d485e3f20 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -22,7 +22,6 @@ using osu.Game.Rulesets.Taiko.Scoring; using osu.Game.Scoring; using System; using System.Linq; -using osu.Framework.Testing; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Taiko.Edit; using osu.Game.Rulesets.Taiko.Objects; @@ -32,7 +31,6 @@ using osu.Game.Skinning; namespace osu.Game.Rulesets.Taiko { - [ExcludeFromDynamicCompile] public class TaikoRuleset : Ruleset, ILegacyRuleset { public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList mods = null) => new DrawableTaikoRuleset(this, beatmap, mods); diff --git a/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs b/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs index b2e18849c9..a899d072ac 100644 --- a/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs +++ b/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs @@ -12,6 +12,7 @@ using osu.Game.Rulesets; using osu.Game.Rulesets.Mania; using osu.Game.Rulesets.Osu; using osu.Game.Scoring; +using osu.Game.Screens; using osu.Game.Screens.Menu; using osu.Game.Screens.Play; using osu.Game.Screens.Ranking; diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index 3860f12baa..c5be5810e9 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -7,6 +7,7 @@ using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using System.Linq; using Newtonsoft.Json; +using osu.Framework.Testing; using osu.Game.Database; using osu.Game.IO.Serialization; using osu.Game.Rulesets; @@ -14,6 +15,7 @@ using osu.Game.Scoring; namespace osu.Game.Beatmaps { + [ExcludeFromDynamicCompile] [Serializable] public class BeatmapInfo : IEquatable, IJsonSerializable, IHasPrimaryKey { diff --git a/osu.Game/Beatmaps/BeatmapMetadata.cs b/osu.Game/Beatmaps/BeatmapMetadata.cs index 775d78f1fb..39b3c23ddd 100644 --- a/osu.Game/Beatmaps/BeatmapMetadata.cs +++ b/osu.Game/Beatmaps/BeatmapMetadata.cs @@ -6,11 +6,13 @@ using System.Collections.Generic; using System.ComponentModel.DataAnnotations.Schema; using System.Linq; using Newtonsoft.Json; +using osu.Framework.Testing; using osu.Game.Database; using osu.Game.Users; namespace osu.Game.Beatmaps { + [ExcludeFromDynamicCompile] [Serializable] public class BeatmapMetadata : IEquatable, IHasPrimaryKey { diff --git a/osu.Game/Beatmaps/BeatmapSetInfo.cs b/osu.Game/Beatmaps/BeatmapSetInfo.cs index a8b83dca38..b76d780860 100644 --- a/osu.Game/Beatmaps/BeatmapSetInfo.cs +++ b/osu.Game/Beatmaps/BeatmapSetInfo.cs @@ -5,10 +5,12 @@ using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations.Schema; using System.Linq; +using osu.Framework.Testing; using osu.Game.Database; namespace osu.Game.Beatmaps { + [ExcludeFromDynamicCompile] public class BeatmapSetInfo : IHasPrimaryKey, IHasFiles, ISoftDelete, IEquatable { public int ID { get; set; } diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs index 6a161e6e04..19b54e1783 100644 --- a/osu.Game/Beatmaps/WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/WorkingBeatmap.cs @@ -13,6 +13,7 @@ using osu.Framework.Audio.Track; using osu.Framework.Graphics.Textures; using osu.Framework.Logging; using osu.Framework.Statistics; +using osu.Framework.Testing; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects.Types; @@ -22,6 +23,7 @@ using osu.Game.Storyboards; namespace osu.Game.Beatmaps { + [ExcludeFromDynamicCompile] public abstract class WorkingBeatmap : IWorkingBeatmap { public readonly BeatmapInfo BeatmapInfo; diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index a8a8794320..e5432cb84e 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -6,6 +6,7 @@ using osu.Framework.Configuration; using osu.Framework.Configuration.Tracking; using osu.Framework.Extensions; using osu.Framework.Platform; +using osu.Framework.Testing; using osu.Game.Overlays; using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Select; @@ -13,6 +14,7 @@ using osu.Game.Screens.Select.Filter; namespace osu.Game.Configuration { + [ExcludeFromDynamicCompile] public class OsuConfigManager : IniConfigManager { protected override void InitialiseDefaults() diff --git a/osu.Game/Input/GameIdleTracker.cs b/osu.Game/Input/GameIdleTracker.cs new file mode 100644 index 0000000000..260be7e5c9 --- /dev/null +++ b/osu.Game/Input/GameIdleTracker.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.Input; + +namespace osu.Game.Input +{ + public class GameIdleTracker : IdleTracker + { + private InputManager inputManager; + + public GameIdleTracker(int time) + : base(time) + { + } + + protected override void LoadComplete() + { + base.LoadComplete(); + inputManager = GetContainingInputManager(); + } + + protected override bool AllowIdle => inputManager.FocusedDrawable == null; + } +} diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 164a40c6a5..a8722d03ab 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -718,24 +718,6 @@ namespace osu.Game overlayContent.ChangeChildDepth(overlay, (float)-Clock.CurrentTime); } - public class GameIdleTracker : IdleTracker - { - private InputManager inputManager; - - public GameIdleTracker(int time) - : base(time) - { - } - - protected override void LoadComplete() - { - base.LoadComplete(); - inputManager = GetContainingInputManager(); - } - - protected override bool AllowIdle => inputManager.FocusedDrawable == null; - } - private void forwardLoggedErrorsToNotifications() { int recentLogCount = 0; @@ -991,10 +973,4 @@ namespace osu.Game Exit(); } } - - public enum ScorePresentType - { - Results, - Gameplay - } } diff --git a/osu.Game/Rulesets/Mods/Mod.cs b/osu.Game/Rulesets/Mods/Mod.cs index 52ffa0ad2a..b8dc7a2661 100644 --- a/osu.Game/Rulesets/Mods/Mod.cs +++ b/osu.Game/Rulesets/Mods/Mod.cs @@ -9,6 +9,7 @@ using System.Reflection; using Newtonsoft.Json; using osu.Framework.Bindables; using osu.Framework.Graphics.Sprites; +using osu.Framework.Testing; using osu.Game.Configuration; using osu.Game.IO.Serialization; using osu.Game.Rulesets.UI; @@ -18,6 +19,7 @@ namespace osu.Game.Rulesets.Mods /// /// The base class for gameplay modifiers. /// + [ExcludeFromDynamicCompile] public abstract class Mod : IMod, IJsonSerializable { /// diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index 3a7f433a37..915544d010 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -23,10 +23,12 @@ using osu.Game.Scoring; using osu.Game.Skinning; using osu.Game.Users; using JetBrains.Annotations; +using osu.Framework.Testing; using osu.Game.Screens.Ranking.Statistics; namespace osu.Game.Rulesets { + [ExcludeFromDynamicCompile] public abstract class Ruleset { public RulesetInfo RulesetInfo { get; internal set; } diff --git a/osu.Game/Rulesets/RulesetInfo.cs b/osu.Game/Rulesets/RulesetInfo.cs index 2e32b96084..d5aca8c650 100644 --- a/osu.Game/Rulesets/RulesetInfo.cs +++ b/osu.Game/Rulesets/RulesetInfo.cs @@ -5,9 +5,11 @@ using System; using System.Diagnostics.CodeAnalysis; using System.Linq; using Newtonsoft.Json; +using osu.Framework.Testing; namespace osu.Game.Rulesets { + [ExcludeFromDynamicCompile] public class RulesetInfo : IEquatable { public int? ID { get; set; } diff --git a/osu.Game/Screens/ScorePresentType.cs b/osu.Game/Screens/ScorePresentType.cs new file mode 100644 index 0000000000..3216f92091 --- /dev/null +++ b/osu.Game/Screens/ScorePresentType.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.Screens +{ + public enum ScorePresentType + { + Results, + Gameplay + } +} diff --git a/osu.Game/Tests/Visual/OsuTestScene.cs b/osu.Game/Tests/Visual/OsuTestScene.cs index 756074c0b3..4db5139813 100644 --- a/osu.Game/Tests/Visual/OsuTestScene.cs +++ b/osu.Game/Tests/Visual/OsuTestScene.cs @@ -30,6 +30,7 @@ using osu.Game.Tests.Beatmaps; namespace osu.Game.Tests.Visual { + [ExcludeFromDynamicCompile] public abstract class OsuTestScene : TestScene { protected Bindable Beatmap { get; private set; } From ebd11ae0b7a3afe13d0054ed845dc42d9efa944c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Sat, 5 Sep 2020 03:52:07 +0900 Subject: [PATCH 0910/1782] Add a collection management dialog --- .../Collections/TestSceneCollectionDialog.cs | 26 +++ osu.Game/Collections/CollectionDialog.cs | 125 ++++++++++++++ osu.Game/Collections/CollectionList.cs | 27 ++++ osu.Game/Collections/CollectionListItem.cs | 152 ++++++++++++++++++ .../Collections/DeleteCollectionDialog.cs | 36 +++++ osu.Game/OsuGame.cs | 2 + .../Carousel/DrawableCarouselBeatmap.cs | 14 +- .../Carousel/DrawableCarouselBeatmapSet.cs | 14 +- 8 files changed, 384 insertions(+), 12 deletions(-) create mode 100644 osu.Game.Tests/Visual/Collections/TestSceneCollectionDialog.cs create mode 100644 osu.Game/Collections/CollectionDialog.cs create mode 100644 osu.Game/Collections/CollectionList.cs create mode 100644 osu.Game/Collections/CollectionListItem.cs create mode 100644 osu.Game/Collections/DeleteCollectionDialog.cs diff --git a/osu.Game.Tests/Visual/Collections/TestSceneCollectionDialog.cs b/osu.Game.Tests/Visual/Collections/TestSceneCollectionDialog.cs new file mode 100644 index 0000000000..f810361610 --- /dev/null +++ b/osu.Game.Tests/Visual/Collections/TestSceneCollectionDialog.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.Game.Collections; +using osu.Game.Overlays; + +namespace osu.Game.Tests.Visual.Collections +{ + public class TestSceneCollectionDialog : OsuTestScene + { + [Cached] + private DialogOverlay dialogOverlay; + + public TestSceneCollectionDialog() + { + Children = new Drawable[] + { + new CollectionDialog { State = { Value = Visibility.Visible } }, + dialogOverlay = new DialogOverlay() + }; + } + } +} diff --git a/osu.Game/Collections/CollectionDialog.cs b/osu.Game/Collections/CollectionDialog.cs new file mode 100644 index 0000000000..e911b507e5 --- /dev/null +++ b/osu.Game/Collections/CollectionDialog.cs @@ -0,0 +1,125 @@ +// 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 osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osuTK; + +namespace osu.Game.Collections +{ + public class CollectionDialog : OsuFocusedOverlayContainer + { + private const double enter_duration = 500; + private const double exit_duration = 200; + + [Resolved] + private CollectionManager collectionManager { get; set; } + + public CollectionDialog() + { + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + + RelativeSizeAxes = Axes.Both; + Size = new Vector2(0.5f, 0.8f); + + Masking = true; + CornerRadius = 10; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + Children = new Drawable[] + { + new Box + { + Colour = colours.GreySeafoamDark, + RelativeSizeAxes = Axes.Both, + }, + new Container + { + RelativeSizeAxes = Axes.Both, + Child = new GridContainer + { + RelativeSizeAxes = Axes.Both, + RowDimensions = new[] + { + new Dimension(GridSizeMode.Absolute, 50), + new Dimension(), + new Dimension(GridSizeMode.Absolute, 50), + }, + Content = new[] + { + new Drawable[] + { + new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = "Manage collections", + Font = OsuFont.GetFont(size: 30), + } + }, + new Drawable[] + { + new Container + { + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colours.GreySeafoamDarker + }, + new CollectionList + { + RelativeSizeAxes = Axes.Both, + Items = { BindTarget = collectionManager.Collections } + } + } + } + }, + new Drawable[] + { + new OsuButton + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Size = Vector2.One, + Padding = new MarginPadding(10), + Text = "Create new collection", + Action = () => collectionManager.Collections.Add(new BeatmapCollection { Name = "My new collection" }) + }, + }, + } + } + } + }; + } + + protected override void PopIn() + { + base.PopIn(); + + this.FadeIn(enter_duration, Easing.OutQuint); + this.ScaleTo(0.9f).Then().ScaleTo(1f, enter_duration, Easing.OutElastic); + } + + protected override void PopOut() + { + base.PopOut(); + + this.FadeOut(exit_duration, Easing.OutQuint); + this.ScaleTo(0.9f, exit_duration); + } + } +} diff --git a/osu.Game/Collections/CollectionList.cs b/osu.Game/Collections/CollectionList.cs new file mode 100644 index 0000000000..990f82e702 --- /dev/null +++ b/osu.Game/Collections/CollectionList.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.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Graphics.Containers; +using osuTK; + +namespace osu.Game.Collections +{ + public class CollectionList : OsuRearrangeableListContainer + { + protected override ScrollContainer CreateScrollContainer() => base.CreateScrollContainer().With(d => + { + d.ScrollbarVisible = false; + }); + + protected override FillFlowContainer> CreateListFillFlowContainer() => new FillFlowContainer> + { + LayoutDuration = 200, + LayoutEasing = Easing.OutQuint, + Spacing = new Vector2(0, 2) + }; + + protected override OsuRearrangeableListItem CreateOsuDrawable(BeatmapCollection item) => new CollectionListItem(item); + } +} diff --git a/osu.Game/Collections/CollectionListItem.cs b/osu.Game/Collections/CollectionListItem.cs new file mode 100644 index 0000000000..527ed57cd0 --- /dev/null +++ b/osu.Game/Collections/CollectionListItem.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; +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.Sprites; +using osu.Framework.Input.Events; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.UserInterface; +using osu.Game.Overlays; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Collections +{ + public class CollectionListItem : OsuRearrangeableListItem + { + private const float item_height = 35; + + public CollectionListItem(BeatmapCollection item) + : base(item) + { + Padding = new MarginPadding { Right = 20 }; + } + + protected override Drawable CreateContent() => new ItemContent(Model); + + private class ItemContent : CircularContainer + { + private readonly BeatmapCollection collection; + + private ItemTextBox textBox; + + public ItemContent(BeatmapCollection collection) + { + this.collection = collection; + + RelativeSizeAxes = Axes.X; + Height = item_height; + Masking = true; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + Children = new Drawable[] + { + new DeleteButton(collection) + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + IsTextBoxHovered = v => textBox.ReceivePositionalInputAt(v) + }, + new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Right = item_height / 2 }, + Children = new Drawable[] + { + textBox = new ItemTextBox + { + RelativeSizeAxes = Axes.Both, + Size = Vector2.One, + CornerRadius = item_height / 2, + Text = collection.Name + }, + } + }, + }; + } + } + + private class ItemTextBox : OsuTextBox + { + protected override float LeftRightPadding => item_height / 2; + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + BackgroundUnfocused = colours.GreySeafoamDarker.Darken(0.5f); + BackgroundFocused = colours.GreySeafoam; + } + } + + private class DeleteButton : CompositeDrawable + { + public Func IsTextBoxHovered; + + [Resolved(CanBeNull = true)] + private DialogOverlay dialogOverlay { get; set; } + + private readonly BeatmapCollection collection; + + private Drawable background; + + public DeleteButton(BeatmapCollection collection) + { + this.collection = collection; + RelativeSizeAxes = Axes.Both; + FillMode = FillMode.Fit; + + Alpha = 0.1f; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + InternalChildren = new[] + { + background = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colours.Red + }, + new SpriteIcon + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + X = -6, + Size = new Vector2(10), + Icon = FontAwesome.Solid.Trash + } + }; + } + + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => base.ReceivePositionalInputAt(screenSpacePos) && !IsTextBoxHovered(screenSpacePos); + + protected override bool OnHover(HoverEvent e) + { + this.FadeTo(1f, 100, Easing.Out); + return false; + } + + protected override void OnHoverLost(HoverLostEvent e) + { + this.FadeTo(0.1f, 100); + } + + protected override bool OnClick(ClickEvent e) + { + background.FlashColour(Color4.White, 150); + dialogOverlay?.Push(new DeleteCollectionDialog(collection)); + return true; + } + } + } +} diff --git a/osu.Game/Collections/DeleteCollectionDialog.cs b/osu.Game/Collections/DeleteCollectionDialog.cs new file mode 100644 index 0000000000..a7677e9de2 --- /dev/null +++ b/osu.Game/Collections/DeleteCollectionDialog.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.Allocation; +using osu.Framework.Graphics.Sprites; +using osu.Game.Overlays.Dialog; + +namespace osu.Game.Collections +{ + public class DeleteCollectionDialog : PopupDialog + { + [Resolved] + private CollectionManager collectionManager { get; set; } + + public DeleteCollectionDialog(BeatmapCollection collection) + { + HeaderText = "Confirm deletion of"; + BodyText = collection.Name; + + Icon = FontAwesome.Regular.TrashAlt; + + Buttons = new PopupDialogButton[] + { + new PopupDialogOkButton + { + Text = @"Yes. Go for it.", + Action = () => collectionManager.Collections.Remove(collection) + }, + new PopupDialogCancelButton + { + Text = @"No! Abort mission!", + }, + }; + } + } +} diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 5008a3cf3b..1f69888d2e 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -31,6 +31,7 @@ using osu.Framework.Input.Events; using osu.Framework.Platform; using osu.Framework.Threading; using osu.Game.Beatmaps; +using osu.Game.Collections; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; @@ -632,6 +633,7 @@ namespace osu.Game loadComponentSingleFile(CreateUpdateManager(), Add, true); // overlay elements + loadComponentSingleFile(new CollectionDialog(), overlayContent.Add, true); loadComponentSingleFile(beatmapListing = new BeatmapListingOverlay(), overlayContent.Add, true); loadComponentSingleFile(dashboard = new DashboardOverlay(), overlayContent.Add, true); loadComponentSingleFile(news = new NewsOverlay(), overlayContent.Add, true); diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs index 1bd4447248..fe4c95f4e3 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs @@ -51,6 +51,9 @@ namespace osu.Game.Screens.Select.Carousel [Resolved] private CollectionManager collectionManager { get; set; } + [Resolved(CanBeNull = true)] + private CollectionDialog collectionDialog { get; set; } + private IBindable starDifficultyBindable; private CancellationTokenSource starDifficultyCancellationSource; @@ -224,12 +227,11 @@ namespace osu.Game.Screens.Select.Carousel if (beatmap.OnlineBeatmapID.HasValue && beatmapOverlay != null) items.Add(new OsuMenuItem("Details", MenuItemType.Standard, () => beatmapOverlay.FetchAndShowBeatmap(beatmap.OnlineBeatmapID.Value))); - items.Add(new OsuMenuItem("Add to...") - { - Items = collectionManager.Collections.OrderByDescending(c => c.LastModifyTime).Take(3).Select(createCollectionMenuItem) - .Append(new OsuMenuItem("More...", MenuItemType.Standard, () => { })) - .ToArray() - }); + var collectionItems = collectionManager.Collections.OrderByDescending(c => c.LastModifyTime).Take(3).Select(createCollectionMenuItem).ToList(); + if (collectionDialog != null) + collectionItems.Add(new OsuMenuItem("More...", MenuItemType.Standard, collectionDialog.Show)); + + items.Add(new OsuMenuItem("Add to...") { Items = collectionItems }); return items.ToArray(); } diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index e05b5ee951..8a9b0dc5b3 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -38,6 +38,9 @@ namespace osu.Game.Screens.Select.Carousel [Resolved] private CollectionManager collectionManager { get; set; } + [Resolved(CanBeNull = true)] + private CollectionDialog collectionDialog { get; set; } + private readonly BeatmapSetInfo beatmapSet; public DrawableCarouselBeatmapSet(CarouselBeatmapSet set) @@ -145,12 +148,11 @@ namespace osu.Game.Screens.Select.Carousel if (dialogOverlay != null) items.Add(new OsuMenuItem("Delete", MenuItemType.Destructive, () => dialogOverlay.Push(new BeatmapDeleteDialog(beatmapSet)))); - items.Add(new OsuMenuItem("Add all to...") - { - Items = collectionManager.Collections.OrderByDescending(c => c.LastModifyTime).Take(3).Select(createCollectionMenuItem) - .Append(new OsuMenuItem("More...", MenuItemType.Standard, () => { })) - .ToArray() - }); + var collectionItems = collectionManager.Collections.OrderByDescending(c => c.LastModifyTime).Take(3).Select(createCollectionMenuItem).ToList(); + if (collectionDialog != null) + collectionItems.Add(new OsuMenuItem("More...", MenuItemType.Standard, collectionDialog.Show)); + + items.Add(new OsuMenuItem("Add all to...") { Items = collectionItems }); return items.ToArray(); } From 345fb9d8e02f057c33a8430126463285c9a27c1e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Sat, 5 Sep 2020 03:55:43 +0900 Subject: [PATCH 0911/1782] Rename classes --- .../Visual/Collections/TestSceneCollectionDialog.cs | 2 +- .../{CollectionManager.cs => BeatmapCollectionManager.cs} | 2 +- osu.Game/Collections/DeleteCollectionDialog.cs | 2 +- .../{CollectionList.cs => DrawableCollectionList.cs} | 4 ++-- ...ollectionListItem.cs => DrawableCollectionListItem.cs} | 4 ++-- .../{CollectionDialog.cs => ManageCollectionDialog.cs} | 8 ++++---- osu.Game/OsuGame.cs | 2 +- osu.Game/OsuGameBase.cs | 4 ++-- .../Settings/Sections/Maintenance/GeneralSettings.cs | 8 ++++---- .../Screens/Select/Carousel/DrawableCarouselBeatmap.cs | 8 ++++---- .../Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs | 8 ++++---- osu.Game/Screens/Select/FilterControl.cs | 2 +- 12 files changed, 27 insertions(+), 27 deletions(-) rename osu.Game/Collections/{CollectionManager.cs => BeatmapCollectionManager.cs} (99%) rename osu.Game/Collections/{CollectionList.cs => DrawableCollectionList.cs} (83%) rename osu.Game/Collections/{CollectionListItem.cs => DrawableCollectionListItem.cs} (96%) rename osu.Game/Collections/{CollectionDialog.cs => ManageCollectionDialog.cs} (94%) diff --git a/osu.Game.Tests/Visual/Collections/TestSceneCollectionDialog.cs b/osu.Game.Tests/Visual/Collections/TestSceneCollectionDialog.cs index f810361610..247d27f67a 100644 --- a/osu.Game.Tests/Visual/Collections/TestSceneCollectionDialog.cs +++ b/osu.Game.Tests/Visual/Collections/TestSceneCollectionDialog.cs @@ -18,7 +18,7 @@ namespace osu.Game.Tests.Visual.Collections { Children = new Drawable[] { - new CollectionDialog { State = { Value = Visibility.Visible } }, + new ManageCollectionDialog { State = { Value = Visibility.Visible } }, dialogOverlay = new DialogOverlay() }; } diff --git a/osu.Game/Collections/CollectionManager.cs b/osu.Game/Collections/BeatmapCollectionManager.cs similarity index 99% rename from osu.Game/Collections/CollectionManager.cs rename to osu.Game/Collections/BeatmapCollectionManager.cs index 20bf96da9d..0b066708d8 100644 --- a/osu.Game/Collections/CollectionManager.cs +++ b/osu.Game/Collections/BeatmapCollectionManager.cs @@ -18,7 +18,7 @@ using osu.Game.IO.Legacy; namespace osu.Game.Collections { - public class CollectionManager : CompositeDrawable + public class BeatmapCollectionManager : CompositeDrawable { /// /// Database version in YYYYMMDD format (matching stable). diff --git a/osu.Game/Collections/DeleteCollectionDialog.cs b/osu.Game/Collections/DeleteCollectionDialog.cs index a7677e9de2..81bedca638 100644 --- a/osu.Game/Collections/DeleteCollectionDialog.cs +++ b/osu.Game/Collections/DeleteCollectionDialog.cs @@ -10,7 +10,7 @@ namespace osu.Game.Collections public class DeleteCollectionDialog : PopupDialog { [Resolved] - private CollectionManager collectionManager { get; set; } + private BeatmapCollectionManager collectionManager { get; set; } public DeleteCollectionDialog(BeatmapCollection collection) { diff --git a/osu.Game/Collections/CollectionList.cs b/osu.Game/Collections/DrawableCollectionList.cs similarity index 83% rename from osu.Game/Collections/CollectionList.cs rename to osu.Game/Collections/DrawableCollectionList.cs index 990f82e702..ab146c17b6 100644 --- a/osu.Game/Collections/CollectionList.cs +++ b/osu.Game/Collections/DrawableCollectionList.cs @@ -8,7 +8,7 @@ using osuTK; namespace osu.Game.Collections { - public class CollectionList : OsuRearrangeableListContainer + public class DrawableCollectionList : OsuRearrangeableListContainer { protected override ScrollContainer CreateScrollContainer() => base.CreateScrollContainer().With(d => { @@ -22,6 +22,6 @@ namespace osu.Game.Collections Spacing = new Vector2(0, 2) }; - protected override OsuRearrangeableListItem CreateOsuDrawable(BeatmapCollection item) => new CollectionListItem(item); + protected override OsuRearrangeableListItem CreateOsuDrawable(BeatmapCollection item) => new DrawableCollectionListItem(item); } } diff --git a/osu.Game/Collections/CollectionListItem.cs b/osu.Game/Collections/DrawableCollectionListItem.cs similarity index 96% rename from osu.Game/Collections/CollectionListItem.cs rename to osu.Game/Collections/DrawableCollectionListItem.cs index 527ed57cd0..67756cdb43 100644 --- a/osu.Game/Collections/CollectionListItem.cs +++ b/osu.Game/Collections/DrawableCollectionListItem.cs @@ -18,11 +18,11 @@ using osuTK.Graphics; namespace osu.Game.Collections { - public class CollectionListItem : OsuRearrangeableListItem + public class DrawableCollectionListItem : OsuRearrangeableListItem { private const float item_height = 35; - public CollectionListItem(BeatmapCollection item) + public DrawableCollectionListItem(BeatmapCollection item) : base(item) { Padding = new MarginPadding { Right = 20 }; diff --git a/osu.Game/Collections/CollectionDialog.cs b/osu.Game/Collections/ManageCollectionDialog.cs similarity index 94% rename from osu.Game/Collections/CollectionDialog.cs rename to osu.Game/Collections/ManageCollectionDialog.cs index e911b507e5..6a0d815e43 100644 --- a/osu.Game/Collections/CollectionDialog.cs +++ b/osu.Game/Collections/ManageCollectionDialog.cs @@ -13,15 +13,15 @@ using osuTK; namespace osu.Game.Collections { - public class CollectionDialog : OsuFocusedOverlayContainer + public class ManageCollectionDialog : OsuFocusedOverlayContainer { private const double enter_duration = 500; private const double exit_duration = 200; [Resolved] - private CollectionManager collectionManager { get; set; } + private BeatmapCollectionManager collectionManager { get; set; } - public CollectionDialog() + public ManageCollectionDialog() { Anchor = Anchor.Centre; Origin = Anchor.Centre; @@ -79,7 +79,7 @@ namespace osu.Game.Collections RelativeSizeAxes = Axes.Both, Colour = colours.GreySeafoamDarker }, - new CollectionList + new DrawableCollectionList { RelativeSizeAxes = Axes.Both, Items = { BindTarget = collectionManager.Collections } diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 1f69888d2e..2f8f1b2f17 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -633,7 +633,7 @@ namespace osu.Game loadComponentSingleFile(CreateUpdateManager(), Add, true); // overlay elements - loadComponentSingleFile(new CollectionDialog(), overlayContent.Add, true); + loadComponentSingleFile(new ManageCollectionDialog(), overlayContent.Add, true); loadComponentSingleFile(beatmapListing = new BeatmapListingOverlay(), overlayContent.Add, true); loadComponentSingleFile(dashboard = new DashboardOverlay(), overlayContent.Add, true); loadComponentSingleFile(news = new NewsOverlay(), overlayContent.Add, true); diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index d512f57ca5..a7efecadfd 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -56,7 +56,7 @@ namespace osu.Game protected BeatmapManager BeatmapManager; - protected CollectionManager CollectionManager; + protected BeatmapCollectionManager CollectionManager; protected ScoreManager ScoreManager; @@ -225,7 +225,7 @@ namespace osu.Game dependencies.Cache(difficultyManager); AddInternal(difficultyManager); - dependencies.Cache(CollectionManager = new CollectionManager()); + dependencies.Cache(CollectionManager = new BeatmapCollectionManager()); AddInternal(CollectionManager); dependencies.Cache(KeyBindingStore = new KeyBindingStore(contextFactory, RulesetStore)); diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs index 21a5ed6b31..74f9920ae0 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs @@ -28,7 +28,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance private TriangleButton undeleteButton; [BackgroundDependencyLoader] - private void load(BeatmapManager beatmaps, ScoreManager scores, SkinManager skins, CollectionManager collections, DialogOverlay dialogOverlay) + private void load(BeatmapManager beatmaps, ScoreManager scores, SkinManager skins, BeatmapCollectionManager collectionManager, DialogOverlay dialogOverlay) { if (beatmaps.SupportsImportFromStable) { @@ -108,7 +108,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance } }); - if (collections.SupportsImportFromStable) + if (collectionManager.SupportsImportFromStable) { Add(importCollectionsButton = new SettingsButton { @@ -116,7 +116,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance Action = () => { importCollectionsButton.Enabled.Value = false; - collections.ImportFromStableAsync().ContinueWith(t => Schedule(() => importCollectionsButton.Enabled.Value = true)); + collectionManager.ImportFromStableAsync().ContinueWith(t => Schedule(() => importCollectionsButton.Enabled.Value = true)); } }); } @@ -126,7 +126,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance Text = "Delete ALL collections", Action = () => { - dialogOverlay?.Push(new DeleteAllBeatmapsDialog(() => collections.Collections.Clear())); + dialogOverlay?.Push(new DeleteAllBeatmapsDialog(() => collectionManager.Collections.Clear())); } }); diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs index fe4c95f4e3..8fd428d26e 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs @@ -49,10 +49,10 @@ namespace osu.Game.Screens.Select.Carousel private BeatmapDifficultyManager difficultyManager { get; set; } [Resolved] - private CollectionManager collectionManager { get; set; } + private BeatmapCollectionManager collectionManager { get; set; } [Resolved(CanBeNull = true)] - private CollectionDialog collectionDialog { get; set; } + private ManageCollectionDialog manageCollectionDialog { get; set; } private IBindable starDifficultyBindable; private CancellationTokenSource starDifficultyCancellationSource; @@ -228,8 +228,8 @@ namespace osu.Game.Screens.Select.Carousel items.Add(new OsuMenuItem("Details", MenuItemType.Standard, () => beatmapOverlay.FetchAndShowBeatmap(beatmap.OnlineBeatmapID.Value))); var collectionItems = collectionManager.Collections.OrderByDescending(c => c.LastModifyTime).Take(3).Select(createCollectionMenuItem).ToList(); - if (collectionDialog != null) - collectionItems.Add(new OsuMenuItem("More...", MenuItemType.Standard, collectionDialog.Show)); + if (manageCollectionDialog != null) + collectionItems.Add(new OsuMenuItem("More...", MenuItemType.Standard, manageCollectionDialog.Show)); items.Add(new OsuMenuItem("Add to...") { Items = collectionItems }); diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index 8a9b0dc5b3..12c6b320e9 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -36,10 +36,10 @@ namespace osu.Game.Screens.Select.Carousel private DialogOverlay dialogOverlay { get; set; } [Resolved] - private CollectionManager collectionManager { get; set; } + private BeatmapCollectionManager collectionManager { get; set; } [Resolved(CanBeNull = true)] - private CollectionDialog collectionDialog { get; set; } + private ManageCollectionDialog manageCollectionDialog { get; set; } private readonly BeatmapSetInfo beatmapSet; @@ -149,8 +149,8 @@ namespace osu.Game.Screens.Select.Carousel items.Add(new OsuMenuItem("Delete", MenuItemType.Destructive, () => dialogOverlay.Push(new BeatmapDeleteDialog(beatmapSet)))); var collectionItems = collectionManager.Collections.OrderByDescending(c => c.LastModifyTime).Take(3).Select(createCollectionMenuItem).ToList(); - if (collectionDialog != null) - collectionItems.Add(new OsuMenuItem("More...", MenuItemType.Standard, collectionDialog.Show)); + if (manageCollectionDialog != null) + collectionItems.Add(new OsuMenuItem("More...", MenuItemType.Standard, manageCollectionDialog.Show)); items.Add(new OsuMenuItem("Add all to...") { Items = collectionItems }); diff --git a/osu.Game/Screens/Select/FilterControl.cs b/osu.Game/Screens/Select/FilterControl.cs index 0b85ae0e6a..0db24f0738 100644 --- a/osu.Game/Screens/Select/FilterControl.cs +++ b/osu.Game/Screens/Select/FilterControl.cs @@ -216,7 +216,7 @@ namespace osu.Game.Screens.Select } [BackgroundDependencyLoader] - private void load(CollectionManager collectionManager) + private void load(BeatmapCollectionManager collectionManager) { collections.BindTo(collectionManager.Collections); collections.CollectionChanged += (_, __) => collectionsChanged(); From 4b4dd02942c6e8d1ec7faa6490cce8051958c47c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Sat, 5 Sep 2020 04:43:51 +0900 Subject: [PATCH 0912/1782] Make collection name a bindable --- osu.Game/Collections/BeatmapCollectionManager.cs | 8 ++++---- osu.Game/Collections/DeleteCollectionDialog.cs | 2 +- osu.Game/Collections/DrawableCollectionListItem.cs | 2 +- osu.Game/Collections/ManageCollectionDialog.cs | 5 ++++- .../Screens/Select/Carousel/DrawableCarouselBeatmap.cs | 2 +- .../Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs | 2 +- osu.Game/Screens/Select/FilterControl.cs | 2 +- 7 files changed, 13 insertions(+), 10 deletions(-) diff --git a/osu.Game/Collections/BeatmapCollectionManager.cs b/osu.Game/Collections/BeatmapCollectionManager.cs index 0b066708d8..3e5976300f 100644 --- a/osu.Game/Collections/BeatmapCollectionManager.cs +++ b/osu.Game/Collections/BeatmapCollectionManager.cs @@ -97,7 +97,7 @@ namespace osu.Game.Collections { var existing = Collections.FirstOrDefault(c => c.Name == newCol.Name); if (existing == null) - Collections.Add(existing = new BeatmapCollection { Name = newCol.Name }); + Collections.Add(existing = new BeatmapCollection { Name = { Value = newCol.Name.Value } }); foreach (var newBeatmap in newCol.Beatmaps) { @@ -122,7 +122,7 @@ namespace osu.Game.Collections for (int i = 0; i < collectionCount; i++) { - var collection = new BeatmapCollection { Name = sr.ReadString() }; + var collection = new BeatmapCollection { Name = { Value = sr.ReadString() } }; int mapCount = sr.ReadInt32(); for (int j = 0; j < mapCount; j++) @@ -183,7 +183,7 @@ namespace osu.Game.Collections foreach (var c in Collections) { - sw.Write(c.Name); + sw.Write(c.Name.Value); sw.Write(c.Beatmaps.Count); foreach (var b in c.Beatmaps) @@ -221,7 +221,7 @@ namespace osu.Game.Collections /// public event Action Changed; - public string Name; + public readonly Bindable Name = new Bindable(); public readonly BindableList Beatmaps = new BindableList(); diff --git a/osu.Game/Collections/DeleteCollectionDialog.cs b/osu.Game/Collections/DeleteCollectionDialog.cs index 81bedca638..f2b8de7c1e 100644 --- a/osu.Game/Collections/DeleteCollectionDialog.cs +++ b/osu.Game/Collections/DeleteCollectionDialog.cs @@ -15,7 +15,7 @@ namespace osu.Game.Collections public DeleteCollectionDialog(BeatmapCollection collection) { HeaderText = "Confirm deletion of"; - BodyText = collection.Name; + BodyText = collection.Name.Value; Icon = FontAwesome.Regular.TrashAlt; diff --git a/osu.Game/Collections/DrawableCollectionListItem.cs b/osu.Game/Collections/DrawableCollectionListItem.cs index 67756cdb43..7c1a2e1287 100644 --- a/osu.Game/Collections/DrawableCollectionListItem.cs +++ b/osu.Game/Collections/DrawableCollectionListItem.cs @@ -67,7 +67,7 @@ namespace osu.Game.Collections RelativeSizeAxes = Axes.Both, Size = Vector2.One, CornerRadius = item_height / 2, - Text = collection.Name + Current = collection.Name }, } }, diff --git a/osu.Game/Collections/ManageCollectionDialog.cs b/osu.Game/Collections/ManageCollectionDialog.cs index 6a0d815e43..1e222a9c71 100644 --- a/osu.Game/Collections/ManageCollectionDialog.cs +++ b/osu.Game/Collections/ManageCollectionDialog.cs @@ -97,7 +97,7 @@ namespace osu.Game.Collections Size = Vector2.One, Padding = new MarginPadding(10), Text = "Create new collection", - Action = () => collectionManager.Collections.Add(new BeatmapCollection { Name = "My new collection" }) + Action = () => collectionManager.Collections.Add(new BeatmapCollection { Name = { Value = "My new collection" } }) }, }, } @@ -120,6 +120,9 @@ namespace osu.Game.Collections this.FadeOut(exit_duration, Easing.OutQuint); this.ScaleTo(0.9f, exit_duration); + + // Ensure that textboxes commit + GetContainingInputManager()?.TriggerFocusContention(this); } } } diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs index 8fd428d26e..6c43bf5bed 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs @@ -239,7 +239,7 @@ namespace osu.Game.Screens.Select.Carousel private MenuItem createCollectionMenuItem(BeatmapCollection collection) { - return new ToggleMenuItem(collection.Name, MenuItemType.Standard, s => + return new ToggleMenuItem(collection.Name.Value, MenuItemType.Standard, s => { if (s) collection.Beatmaps.Add(beatmap); diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index 12c6b320e9..fc262730cb 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -171,7 +171,7 @@ namespace osu.Game.Screens.Select.Carousel else state = TernaryState.False; - return new TernaryStateMenuItem(collection.Name, MenuItemType.Standard, s => + return new TernaryStateMenuItem(collection.Name.Value, MenuItemType.Standard, s => { foreach (var b in beatmapSet.Beatmaps) { diff --git a/osu.Game/Screens/Select/FilterControl.cs b/osu.Game/Screens/Select/FilterControl.cs index 0db24f0738..a66bcfb3b1 100644 --- a/osu.Game/Screens/Select/FilterControl.cs +++ b/osu.Game/Screens/Select/FilterControl.cs @@ -270,7 +270,7 @@ namespace osu.Game.Screens.Select Current.TriggerChange(); } - protected override string GenerateItemText(CollectionFilter item) => item.Collection?.Name ?? "All beatmaps"; + protected override string GenerateItemText(CollectionFilter item) => item.Collection?.Name.Value ?? "All beatmaps"; protected override DropdownHeader CreateHeader() => new CollectionDropdownHeader(); From 33b76015d80df41618867a94227fbe2251bae200 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 6 Sep 2020 01:54:08 +0300 Subject: [PATCH 0913/1782] Fix MusicActionHandler unnecessarily depending on OnScreenDisplay's existance --- osu.Game/Overlays/Music/MusicActionHandler.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Overlays/Music/MusicActionHandler.cs b/osu.Game/Overlays/Music/MusicActionHandler.cs index 17aa4c1d32..cd8548c1c0 100644 --- a/osu.Game/Overlays/Music/MusicActionHandler.cs +++ b/osu.Game/Overlays/Music/MusicActionHandler.cs @@ -22,7 +22,7 @@ namespace osu.Game.Overlays.Music [Resolved] private MusicController musicController { get; set; } - [Resolved] + [Resolved(canBeNull: true)] private OnScreenDisplay onScreenDisplay { get; set; } public bool OnPressed(GlobalAction action) @@ -34,14 +34,14 @@ namespace osu.Game.Overlays.Music { case GlobalAction.MusicPlay: if (musicController.TogglePause()) - onScreenDisplay.Display(new MusicActionToast(musicController.IsPlaying ? "Play track" : "Pause track")); + onScreenDisplay?.Display(new MusicActionToast(musicController.IsPlaying ? "Play track" : "Pause track")); return true; case GlobalAction.MusicNext: musicController.NextTrack(() => { - onScreenDisplay.Display(new MusicActionToast("Next track")); + onScreenDisplay?.Display(new MusicActionToast("Next track")); }).RunTask(); return true; @@ -52,11 +52,11 @@ namespace osu.Game.Overlays.Music switch (res) { case PreviousTrackResult.Restart: - onScreenDisplay.Display(new MusicActionToast("Restart track")); + onScreenDisplay?.Display(new MusicActionToast("Restart track")); break; case PreviousTrackResult.Previous: - onScreenDisplay.Display(new MusicActionToast("Previous track")); + onScreenDisplay?.Display(new MusicActionToast("Previous track")); break; } }).RunTask(); From e37a3a84fd2e232d6f1eeb0f71eec3c64417256d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 5 Sep 2020 15:40:26 +0200 Subject: [PATCH 0914/1782] Use legible tuple member name --- .../Visual/Menus/TestSceneMusicActionHandling.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/Menus/TestSceneMusicActionHandling.cs b/osu.Game.Tests/Visual/Menus/TestSceneMusicActionHandling.cs index 9121309489..9ee86eaf78 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneMusicActionHandling.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneMusicActionHandling.cs @@ -28,7 +28,7 @@ namespace osu.Game.Tests.Visual.Menus public void TestMusicNavigationActions() { int importId = 0; - Queue<(WorkingBeatmap working, TrackChangeDirection dir)> trackChangeQueue = null; + Queue<(WorkingBeatmap working, TrackChangeDirection changeDirection)> trackChangeQueue = null; // ensure we have at least two beatmaps available to identify the direction the music controller navigated to. AddRepeatStep("import beatmap", () => Game.BeatmapManager.Import(new BeatmapSetInfo @@ -55,8 +55,8 @@ namespace osu.Game.Tests.Visual.Menus AddStep("bind to track change", () => { - trackChangeQueue = new Queue<(WorkingBeatmap working, TrackChangeDirection dir)>(); - Game.MusicController.TrackChanged += (working, dir) => trackChangeQueue.Enqueue((working, dir)); + trackChangeQueue = new Queue<(WorkingBeatmap, TrackChangeDirection)>(); + Game.MusicController.TrackChanged += (working, changeDirection) => trackChangeQueue.Enqueue((working, changeDirection)); }); AddStep("seek track to 6 second", () => Game.MusicController.SeekTo(6000)); @@ -69,12 +69,12 @@ namespace osu.Game.Tests.Visual.Menus AddStep("trigger music prev action", () => Game.GlobalBinding.TriggerPressed(GlobalAction.MusicPrev)); AddAssert("track changed to previous", () => trackChangeQueue.Count == 1 && - trackChangeQueue.Dequeue().dir == TrackChangeDirection.Prev); + trackChangeQueue.Dequeue().changeDirection == TrackChangeDirection.Prev); AddStep("trigger music next action", () => Game.GlobalBinding.TriggerPressed(GlobalAction.MusicNext)); AddAssert("track changed to next", () => trackChangeQueue.Count == 1 && - trackChangeQueue.Dequeue().dir == TrackChangeDirection.Next); + trackChangeQueue.Dequeue().changeDirection == TrackChangeDirection.Next); } } } From 8b1151284c507b12cbb56811a90018f916645873 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 5 Sep 2020 15:43:16 +0200 Subject: [PATCH 0915/1782] Simplify overly verbose step names --- .../Visual/Menus/TestSceneMusicActionHandling.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/Menus/TestSceneMusicActionHandling.cs b/osu.Game.Tests/Visual/Menus/TestSceneMusicActionHandling.cs index 9ee86eaf78..9b8ba47992 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneMusicActionHandling.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneMusicActionHandling.cs @@ -18,9 +18,9 @@ namespace osu.Game.Tests.Visual.Menus public void TestMusicPlayAction() { AddStep("ensure playing something", () => Game.MusicController.EnsurePlayingSomething()); - AddStep("trigger music playback toggle action", () => Game.GlobalBinding.TriggerPressed(GlobalAction.MusicPlay)); + AddStep("toggle playback", () => Game.GlobalBinding.TriggerPressed(GlobalAction.MusicPlay)); AddAssert("music paused", () => !Game.MusicController.IsPlaying && Game.MusicController.IsUserPaused); - AddStep("trigger music playback toggle action", () => Game.GlobalBinding.TriggerPressed(GlobalAction.MusicPlay)); + AddStep("toggle playback", () => Game.GlobalBinding.TriggerPressed(GlobalAction.MusicPlay)); AddAssert("music resumed", () => Game.MusicController.IsPlaying && !Game.MusicController.IsUserPaused); } @@ -62,16 +62,16 @@ namespace osu.Game.Tests.Visual.Menus AddStep("seek track to 6 second", () => Game.MusicController.SeekTo(6000)); AddUntilStep("wait for current time to update", () => Game.MusicController.CurrentTrack.CurrentTime > 5000); - AddStep("trigger music prev action", () => Game.GlobalBinding.TriggerPressed(GlobalAction.MusicPrev)); + AddStep("press previous", () => Game.GlobalBinding.TriggerPressed(GlobalAction.MusicPrev)); AddAssert("no track change", () => trackChangeQueue.Count == 0); AddUntilStep("track restarted", () => Game.MusicController.CurrentTrack.CurrentTime < 5000); - AddStep("trigger music prev action", () => Game.GlobalBinding.TriggerPressed(GlobalAction.MusicPrev)); + AddStep("press previous", () => Game.GlobalBinding.TriggerPressed(GlobalAction.MusicPrev)); AddAssert("track changed to previous", () => trackChangeQueue.Count == 1 && trackChangeQueue.Dequeue().changeDirection == TrackChangeDirection.Prev); - AddStep("trigger music next action", () => Game.GlobalBinding.TriggerPressed(GlobalAction.MusicNext)); + AddStep("press next", () => Game.GlobalBinding.TriggerPressed(GlobalAction.MusicNext)); AddAssert("track changed to next", () => trackChangeQueue.Count == 1 && trackChangeQueue.Dequeue().changeDirection == TrackChangeDirection.Next); From 2b16e2535304263507f338bad1591e4d25cf70e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 6 Sep 2020 18:44:41 +0200 Subject: [PATCH 0916/1782] Revert unnecessary passing down of tuple in test --- .../Beatmaps/Formats/LegacyBeatmapEncoderTest.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs index 6e103af3f0..17d910036f 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs @@ -38,8 +38,8 @@ namespace osu.Game.Tests.Beatmaps.Formats var decoded = decodeFromLegacy(beatmaps_resource_store.GetStream(name), name); var decodedAfterEncode = decodeFromLegacy(encodeToLegacy(decoded), name); - sort(decoded); - sort(decodedAfterEncode); + sort(decoded.beatmap); + sort(decodedAfterEncode.beatmap); Assert.That(decodedAfterEncode.beatmap.Serialize(), Is.EqualTo(decoded.beatmap.Serialize())); Assert.IsTrue(areComboColoursEqual(decodedAfterEncode.beatmapSkin.Configuration, decoded.beatmapSkin.Configuration)); @@ -57,10 +57,10 @@ namespace osu.Game.Tests.Beatmaps.Formats return a.ComboColours.SequenceEqual(b.ComboColours); } - private void sort((IBeatmap beatmap, ISkin beatmapSkin) tuple) + private void sort(IBeatmap beatmap) { // Sort control points to ensure a sane ordering, as they may be parsed in different orders. This works because each group contains only uniquely-typed control points. - foreach (var g in tuple.beatmap.ControlPointInfo.Groups) + foreach (var g in beatmap.ControlPointInfo.Groups) { ArrayList.Adapter((IList)g.ControlPoints).Sort( Comparer.Create((c1, c2) => string.Compare(c1.GetType().ToString(), c2.GetType().ToString(), StringComparison.Ordinal))); From b4b9c71f00eab44bb189a3d1936b71edc6219c03 Mon Sep 17 00:00:00 2001 From: Joehu Date: Sun, 6 Sep 2020 10:13:06 -0700 Subject: [PATCH 0917/1782] Make all toolbar tooltips lowercase --- osu.Game/Overlays/BeatmapListing/BeatmapListingHeader.cs | 2 +- osu.Game/Overlays/Changelog/ChangelogHeader.cs | 2 +- osu.Game/Overlays/Dashboard/DashboardOverlayHeader.cs | 2 +- osu.Game/Overlays/News/NewsHeader.cs | 2 +- osu.Game/Overlays/NotificationOverlay.cs | 4 ++-- osu.Game/Overlays/Rankings/RankingsOverlayHeader.cs | 2 +- osu.Game/Overlays/SettingsOverlay.cs | 2 +- osu.Game/Overlays/Toolbar/ToolbarHomeButton.cs | 4 ++-- osu.Game/Overlays/Toolbar/ToolbarRulesetTabButton.cs | 2 +- 9 files changed, 11 insertions(+), 11 deletions(-) diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapListingHeader.cs b/osu.Game/Overlays/BeatmapListing/BeatmapListingHeader.cs index b7f511271c..6a9a71210a 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapListingHeader.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapListingHeader.cs @@ -12,7 +12,7 @@ namespace osu.Game.Overlays.BeatmapListing public BeatmapListingTitle() { Title = "beatmap listing"; - Description = "Browse for new beatmaps"; + Description = "browse for new beatmaps"; IconTexture = "Icons/Hexacons/beatmap"; } } diff --git a/osu.Game/Overlays/Changelog/ChangelogHeader.cs b/osu.Game/Overlays/Changelog/ChangelogHeader.cs index bdc59297bb..f4be4328e7 100644 --- a/osu.Game/Overlays/Changelog/ChangelogHeader.cs +++ b/osu.Game/Overlays/Changelog/ChangelogHeader.cs @@ -115,7 +115,7 @@ namespace osu.Game.Overlays.Changelog public ChangelogHeaderTitle() { Title = "changelog"; - Description = "Track recent dev updates in the osu! ecosystem"; + Description = "track recent dev updates in the osu! ecosystem"; IconTexture = "Icons/Hexacons/devtools"; } } diff --git a/osu.Game/Overlays/Dashboard/DashboardOverlayHeader.cs b/osu.Game/Overlays/Dashboard/DashboardOverlayHeader.cs index a964d84c4f..36bf589877 100644 --- a/osu.Game/Overlays/Dashboard/DashboardOverlayHeader.cs +++ b/osu.Game/Overlays/Dashboard/DashboardOverlayHeader.cs @@ -12,7 +12,7 @@ namespace osu.Game.Overlays.Dashboard public DashboardTitle() { Title = "dashboard"; - Description = "View your friends and other information"; + Description = "view your friends and other information"; IconTexture = "Icons/Hexacons/social"; } } diff --git a/osu.Game/Overlays/News/NewsHeader.cs b/osu.Game/Overlays/News/NewsHeader.cs index 38ac519387..63174128e7 100644 --- a/osu.Game/Overlays/News/NewsHeader.cs +++ b/osu.Game/Overlays/News/NewsHeader.cs @@ -57,7 +57,7 @@ namespace osu.Game.Overlays.News public NewsHeaderTitle() { Title = "news"; - Description = "Get up-to-date on community happenings"; + Description = "get up-to-date on community happenings"; IconTexture = "Icons/Hexacons/news"; } } diff --git a/osu.Game/Overlays/NotificationOverlay.cs b/osu.Game/Overlays/NotificationOverlay.cs index b7d916c48f..b5714fbcae 100644 --- a/osu.Game/Overlays/NotificationOverlay.cs +++ b/osu.Game/Overlays/NotificationOverlay.cs @@ -19,8 +19,8 @@ namespace osu.Game.Overlays public class NotificationOverlay : OsuFocusedOverlayContainer, INamedOverlayComponent { public string IconTexture => "Icons/Hexacons/notification"; - public string Title => "Notifications"; - public string Description => "Waiting for 'ya"; + public string Title => "notifications"; + public string Description => "waiting for 'ya"; private const float width = 320; diff --git a/osu.Game/Overlays/Rankings/RankingsOverlayHeader.cs b/osu.Game/Overlays/Rankings/RankingsOverlayHeader.cs index 7039ab8214..92e22f5873 100644 --- a/osu.Game/Overlays/Rankings/RankingsOverlayHeader.cs +++ b/osu.Game/Overlays/Rankings/RankingsOverlayHeader.cs @@ -30,7 +30,7 @@ namespace osu.Game.Overlays.Rankings public RankingsTitle() { Title = "ranking"; - Description = "Find out who's the best right now"; + Description = "find out who's the best right now"; IconTexture = "Icons/Hexacons/rankings"; } } diff --git a/osu.Game/Overlays/SettingsOverlay.cs b/osu.Game/Overlays/SettingsOverlay.cs index 0532a031f3..e1bcdbbaf0 100644 --- a/osu.Game/Overlays/SettingsOverlay.cs +++ b/osu.Game/Overlays/SettingsOverlay.cs @@ -17,7 +17,7 @@ namespace osu.Game.Overlays { public string IconTexture => "Icons/Hexacons/settings"; public string Title => "settings"; - public string Description => "Change the way osu! behaves"; + public string Description => "change the way osu! behaves"; protected override IEnumerable CreateSections() => new SettingsSection[] { diff --git a/osu.Game/Overlays/Toolbar/ToolbarHomeButton.cs b/osu.Game/Overlays/Toolbar/ToolbarHomeButton.cs index 6b2c24c0f3..76fbd40d66 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarHomeButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarHomeButton.cs @@ -17,8 +17,8 @@ namespace osu.Game.Overlays.Toolbar [BackgroundDependencyLoader] private void load() { - TooltipMain = "Home"; - TooltipSub = "Return to the main menu"; + TooltipMain = "home"; + TooltipSub = "return to the main menu"; SetIcon("Icons/Hexacons/home"); } } diff --git a/osu.Game/Overlays/Toolbar/ToolbarRulesetTabButton.cs b/osu.Game/Overlays/Toolbar/ToolbarRulesetTabButton.cs index 754b679599..564fd65719 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarRulesetTabButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarRulesetTabButton.cs @@ -27,7 +27,7 @@ namespace osu.Game.Overlays.Toolbar var rInstance = value.CreateInstance(); ruleset.TooltipMain = rInstance.Description; - ruleset.TooltipSub = $"Play some {rInstance.Description}"; + ruleset.TooltipSub = $"play some {rInstance.Description}"; ruleset.SetIcon(rInstance.CreateIcon()); } From 8f8f907fc7c3dda37646e8e6a8eca762ca47d32a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 7 Sep 2020 13:27:28 +0900 Subject: [PATCH 0918/1782] Fix missed string --- osu.Game/Overlays/NowPlayingOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/NowPlayingOverlay.cs b/osu.Game/Overlays/NowPlayingOverlay.cs index d1df1fa936..55adf02a45 100644 --- a/osu.Game/Overlays/NowPlayingOverlay.cs +++ b/osu.Game/Overlays/NowPlayingOverlay.cs @@ -29,7 +29,7 @@ namespace osu.Game.Overlays { public string IconTexture => "Icons/Hexacons/music"; public string Title => "now playing"; - public string Description => "Manage the currently playing track"; + public string Description => "manage the currently playing track"; private const float player_height = 130; private const float transition_length = 800; From daff060c9a0ff86c12c662583b641871d23c8a5e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 7 Sep 2020 15:18:15 +0900 Subject: [PATCH 0919/1782] Hide the game-wide cursor on touch input --- osu.Game/Graphics/Cursor/MenuCursorContainer.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/Graphics/Cursor/MenuCursorContainer.cs b/osu.Game/Graphics/Cursor/MenuCursorContainer.cs index 3015c44613..8da80f25ff 100644 --- a/osu.Game/Graphics/Cursor/MenuCursorContainer.cs +++ b/osu.Game/Graphics/Cursor/MenuCursorContainer.cs @@ -5,6 +5,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; using osu.Framework.Input; +using osu.Framework.Input.StateChanges; namespace osu.Game.Graphics.Cursor { @@ -47,7 +48,10 @@ namespace osu.Game.Graphics.Cursor { base.Update(); - if (!CanShowCursor) + var lastMouseSource = GetContainingInputManager().CurrentState.Mouse.LastSource; + bool hasValidInput = lastMouseSource != null && !(lastMouseSource is ISourcedFromTouch); + + if (!hasValidInput || !CanShowCursor) { currentTarget?.Cursor?.Hide(); currentTarget = null; From 1a55d92c719c0d2db2eb0d2c977be242fe2afda8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 7 Sep 2020 15:31:05 +0900 Subject: [PATCH 0920/1782] Use local input manager --- osu.Game/Graphics/Cursor/MenuCursorContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Graphics/Cursor/MenuCursorContainer.cs b/osu.Game/Graphics/Cursor/MenuCursorContainer.cs index 8da80f25ff..4c7f7957e9 100644 --- a/osu.Game/Graphics/Cursor/MenuCursorContainer.cs +++ b/osu.Game/Graphics/Cursor/MenuCursorContainer.cs @@ -48,7 +48,7 @@ namespace osu.Game.Graphics.Cursor { base.Update(); - var lastMouseSource = GetContainingInputManager().CurrentState.Mouse.LastSource; + var lastMouseSource = inputManager.CurrentState.Mouse.LastSource; bool hasValidInput = lastMouseSource != null && !(lastMouseSource is ISourcedFromTouch); if (!hasValidInput || !CanShowCursor) From ecc9c2957ffa287c6eff47fcc49b7dd544f7d0f1 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 7 Sep 2020 16:30:05 +0900 Subject: [PATCH 0921/1782] Avoid float precision error in mania conversion --- .../Beatmaps/ManiaBeatmapConverter.cs | 5 ++-- .../Legacy/DistanceObjectPatternGenerator.cs | 23 +++++++++---------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs index b025ac7992..211905835c 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs @@ -5,7 +5,6 @@ using osu.Game.Rulesets.Mania.Objects; using System; using System.Linq; using System.Collections.Generic; -using osu.Framework.Utils; using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Rulesets.Objects; @@ -167,8 +166,10 @@ namespace osu.Game.Rulesets.Mania.Beatmaps var positionData = original as IHasPosition; - for (double time = original.StartTime; !Precision.DefinitelyBigger(time, generator.EndTime); time += generator.SegmentDuration) + for (int i = 0; i <= generator.SpanCount; i++) { + double time = original.StartTime + generator.SegmentDuration * i; + recordNote(time, positionData?.Position ?? Vector2.Zero); computeDensity(time); } diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs index d03eb0b3c9..fe146c5324 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs @@ -27,8 +27,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy public readonly double EndTime; public readonly double SegmentDuration; - - private readonly int spanCount; + public readonly int SpanCount; private PatternType convertType; @@ -42,20 +41,20 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy var distanceData = hitObject as IHasDistance; var repeatsData = hitObject as IHasRepeats; - spanCount = repeatsData?.SpanCount() ?? 1; + SpanCount = repeatsData?.SpanCount() ?? 1; TimingControlPoint timingPoint = beatmap.ControlPointInfo.TimingPointAt(hitObject.StartTime); DifficultyControlPoint difficultyPoint = beatmap.ControlPointInfo.DifficultyPointAt(hitObject.StartTime); // The true distance, accounting for any repeats - double distance = (distanceData?.Distance ?? 0) * spanCount; + double distance = (distanceData?.Distance ?? 0) * SpanCount; // The velocity of the osu! hit object - calculated as the velocity of a slider double osuVelocity = osu_base_scoring_distance * beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier * difficultyPoint.SpeedMultiplier / timingPoint.BeatLength; // The duration of the osu! hit object double osuDuration = distance / osuVelocity; EndTime = hitObject.StartTime + osuDuration; - SegmentDuration = (EndTime - HitObject.StartTime) / spanCount; + SegmentDuration = (EndTime - HitObject.StartTime) / SpanCount; } public override IEnumerable Generate() @@ -96,7 +95,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy return pattern; } - if (spanCount > 1) + if (SpanCount > 1) { if (SegmentDuration <= 90) return generateRandomHoldNotes(HitObject.StartTime, 1); @@ -104,7 +103,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy if (SegmentDuration <= 120) { convertType |= PatternType.ForceNotStack; - return generateRandomNotes(HitObject.StartTime, spanCount + 1); + return generateRandomNotes(HitObject.StartTime, SpanCount + 1); } if (SegmentDuration <= 160) @@ -117,7 +116,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy if (duration >= 4000) return generateNRandomNotes(HitObject.StartTime, 0.23, 0, 0); - if (SegmentDuration > 400 && spanCount < TotalColumns - 1 - RandomStart) + if (SegmentDuration > 400 && SpanCount < TotalColumns - 1 - RandomStart) return generateTiledHoldNotes(HitObject.StartTime); return generateHoldAndNormalNotes(HitObject.StartTime); @@ -251,7 +250,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy int column = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true); bool increasing = Random.NextDouble() > 0.5; - for (int i = 0; i <= spanCount; i++) + for (int i = 0; i <= SpanCount; i++) { addToPattern(pattern, column, startTime, startTime); startTime += SegmentDuration; @@ -302,7 +301,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy int nextColumn = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true); - for (int i = 0; i <= spanCount; i++) + for (int i = 0; i <= SpanCount; i++) { addToPattern(pattern, nextColumn, startTime, startTime); @@ -393,7 +392,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy var pattern = new Pattern(); - int columnRepeat = Math.Min(spanCount, TotalColumns); + int columnRepeat = Math.Min(SpanCount, TotalColumns); int nextColumn = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true); if (convertType.HasFlag(PatternType.ForceNotStack) && PreviousPattern.ColumnWithObjects < TotalColumns) @@ -447,7 +446,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy var rowPattern = new Pattern(); - for (int i = 0; i <= spanCount; i++) + for (int i = 0; i <= SpanCount; i++) { if (!(ignoreHead && startTime == HitObject.StartTime)) { From c72a8d475552049d602b69a8867ff5f5b440e081 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 7 Sep 2020 17:18:40 +0900 Subject: [PATCH 0922/1782] Add zero-length slider test --- .../ManiaBeatmapConversionTest.cs | 1 + ...ero-length-slider-expected-conversion.json | 14 +++++++++++++ .../Testing/Beatmaps/zero-length-slider.osu | 20 +++++++++++++++++++ 3 files changed, 35 insertions(+) create mode 100644 osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/zero-length-slider-expected-conversion.json create mode 100644 osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/zero-length-slider.osu diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs b/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs index d0ff1fab43..d1e1280c7f 100644 --- a/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs +++ b/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs @@ -19,6 +19,7 @@ namespace osu.Game.Rulesets.Mania.Tests protected override string ResourceAssembly => "osu.Game.Rulesets.Mania"; [TestCase("basic")] + [TestCase("zero-length-slider")] public void Test(string name) => base.Test(name); protected override IEnumerable CreateConvertValue(HitObject hitObject) diff --git a/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/zero-length-slider-expected-conversion.json b/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/zero-length-slider-expected-conversion.json new file mode 100644 index 0000000000..229760cd1c --- /dev/null +++ b/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/zero-length-slider-expected-conversion.json @@ -0,0 +1,14 @@ +{ + "Mappings": [{ + "RandomW": 3083084786, + "RandomX": 273326509, + "RandomY": 273553282, + "RandomZ": 2659838971, + "StartTime": 4836, + "Objects": [{ + "StartTime": 4836, + "EndTime": 4836, + "Column": 0 + }] + }] +} \ No newline at end of file diff --git a/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/zero-length-slider.osu b/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/zero-length-slider.osu new file mode 100644 index 0000000000..9b8ac1f9db --- /dev/null +++ b/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/zero-length-slider.osu @@ -0,0 +1,20 @@ +osu file format v14 + +[General] +StackLeniency: 0.7 +Mode: 0 + +[Difficulty] +HPDrainRate:1 +CircleSize:4 +OverallDifficulty:1 +ApproachRate:9 +SliderMultiplier:2.5 +SliderTickRate:0.5 + +[TimingPoints] +34,431.654676258993,4,1,0,50,1,0 +4782,-66.6666666666667,4,1,0,20,0,0 + +[HitObjects] +15,199,4836,22,0,L,1,46.8750017881394 \ No newline at end of file From 679dc34aa410ec3c6732c52b981537136f5ab0c7 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 7 Sep 2020 17:18:54 +0900 Subject: [PATCH 0923/1782] Add test timeouts --- osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs | 1 + osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs | 1 + osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs | 1 + osu.Game.Rulesets.Taiko.Tests/TaikoBeatmapConversionTest.cs | 1 + 4 files changed, 4 insertions(+) diff --git a/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs b/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs index 8c48158acd..466cbdaf8d 100644 --- a/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs +++ b/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs @@ -14,6 +14,7 @@ using osu.Game.Tests.Beatmaps; namespace osu.Game.Rulesets.Catch.Tests { [TestFixture] + [Timeout(10000)] public class CatchBeatmapConversionTest : BeatmapConversionTest { protected override string ResourceAssembly => "osu.Game.Rulesets.Catch"; diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs b/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs index d1e1280c7f..0c57267970 100644 --- a/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs +++ b/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs @@ -14,6 +14,7 @@ using osu.Game.Tests.Beatmaps; namespace osu.Game.Rulesets.Mania.Tests { [TestFixture] + [Timeout(10000)] public class ManiaBeatmapConversionTest : BeatmapConversionTest { protected override string ResourceAssembly => "osu.Game.Rulesets.Mania"; diff --git a/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs b/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs index cd3daf18a9..7d32895083 100644 --- a/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs +++ b/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs @@ -12,6 +12,7 @@ using osu.Game.Tests.Beatmaps; namespace osu.Game.Rulesets.Osu.Tests { [TestFixture] + [Timeout(10000)] public class OsuBeatmapConversionTest : BeatmapConversionTest { protected override string ResourceAssembly => "osu.Game.Rulesets.Osu"; diff --git a/osu.Game.Rulesets.Taiko.Tests/TaikoBeatmapConversionTest.cs b/osu.Game.Rulesets.Taiko.Tests/TaikoBeatmapConversionTest.cs index d0c57b20c0..5e550a5d03 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TaikoBeatmapConversionTest.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TaikoBeatmapConversionTest.cs @@ -12,6 +12,7 @@ using osu.Game.Tests.Beatmaps; namespace osu.Game.Rulesets.Taiko.Tests { [TestFixture] + [Timeout(10000)] public class TaikoBeatmapConversionTest : BeatmapConversionTest { protected override string ResourceAssembly => "osu.Game.Rulesets.Taiko"; From a8a0bfb8aa26012fd6d4d3343eadf58bede72752 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 7 Sep 2020 18:01:56 +0900 Subject: [PATCH 0924/1782] 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 f62ba48953..62397ca028 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,6 +52,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 526dca421a..1de0633d1f 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -24,7 +24,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 0cbbba70b9..7187b48907 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -80,7 +80,7 @@ - + From 6091714f158ff4675612d43de3bf1be99b1910f2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 7 Sep 2020 17:34:30 +0900 Subject: [PATCH 0925/1782] Limit BPM entry via slider to a sane range --- osu.Game/Screens/Edit/Timing/TimingSection.cs | 39 ++++++++++++++----- 1 file changed, 30 insertions(+), 9 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/TimingSection.cs b/osu.Game/Screens/Edit/Timing/TimingSection.cs index 906644ce14..8e6ea90797 100644 --- a/osu.Game/Screens/Edit/Timing/TimingSection.cs +++ b/osu.Game/Screens/Edit/Timing/TimingSection.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.Bindables; using osu.Framework.Graphics; @@ -12,7 +13,7 @@ namespace osu.Game.Screens.Edit.Timing { internal class TimingSection : Section { - private SettingsSlider bpm; + private SettingsSlider bpmSlider; private SettingsEnumDropdown timeSignature; [BackgroundDependencyLoader] @@ -20,7 +21,7 @@ namespace osu.Game.Screens.Edit.Timing { Flow.AddRange(new Drawable[] { - bpm = new BPMSlider + bpmSlider = new BPMSlider { Bindable = new TimingControlPoint().BeatLengthBindable, LabelText = "BPM", @@ -36,7 +37,7 @@ namespace osu.Game.Screens.Edit.Timing { if (point.NewValue != null) { - bpm.Bindable = point.NewValue.BeatLengthBindable; + bpmSlider.Bindable = point.NewValue.BeatLengthBindable; timeSignature.Bindable = point.NewValue.TimeSignatureBindable; } } @@ -54,6 +55,9 @@ namespace osu.Game.Screens.Edit.Timing private class BPMSlider : SettingsSlider { + private const double sane_minimum = 60; + private const double sane_maximum = 200; + private readonly BindableDouble beatLengthBindable = new BindableDouble(); private BindableDouble bpmBindable; @@ -63,22 +67,39 @@ namespace osu.Game.Screens.Edit.Timing get => base.Bindable; set { - // incoming will be beatlength - + // incoming will be beat length, not bpm beatLengthBindable.UnbindBindings(); beatLengthBindable.BindTo(value); - base.Bindable = bpmBindable = new BindableDouble(beatLengthToBpm(beatLengthBindable.Value)) + double initial = beatLengthToBpm(beatLengthBindable.Value); + + bpmBindable = new BindableDouble(initial) { - MinValue = beatLengthToBpm(beatLengthBindable.MaxValue), - MaxValue = beatLengthToBpm(beatLengthBindable.MinValue), Default = beatLengthToBpm(beatLengthBindable.Default), }; - bpmBindable.BindValueChanged(bpm => beatLengthBindable.Value = beatLengthToBpm(bpm.NewValue)); + updateCurrent(initial); + + bpmBindable.BindValueChanged(bpm => + { + updateCurrent(bpm.NewValue); + beatLengthBindable.Value = beatLengthToBpm(bpm.NewValue); + }); + + base.Bindable = bpmBindable; } } + private void updateCurrent(double newValue) + { + // we use a more sane range for the slider display unless overridden by the user. + // if a value comes in outside our range, we should expand temporarily. + bpmBindable.MinValue = Math.Min(newValue, sane_minimum); + bpmBindable.MaxValue = Math.Max(newValue, sane_maximum); + + bpmBindable.Value = newValue; + } + private double beatLengthToBpm(double beatLength) => 60000 / beatLength; } } From 86512d6e8d4ad6ba2d20c14bcc76a6fd2708f372 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 7 Sep 2020 17:39:13 +0900 Subject: [PATCH 0926/1782] Add BPM entry textbox --- osu.Game/Screens/Edit/Timing/TimingSection.cs | 81 ++++++++++++++----- 1 file changed, 60 insertions(+), 21 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/TimingSection.cs b/osu.Game/Screens/Edit/Timing/TimingSection.cs index 8e6ea90797..6fed4589ce 100644 --- a/osu.Game/Screens/Edit/Timing/TimingSection.cs +++ b/osu.Game/Screens/Edit/Timing/TimingSection.cs @@ -7,6 +7,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.Timing; +using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Overlays.Settings; namespace osu.Game.Screens.Edit.Timing @@ -15,16 +16,21 @@ namespace osu.Game.Screens.Edit.Timing { private SettingsSlider bpmSlider; private SettingsEnumDropdown timeSignature; + private BPMTextBox bpmTextEntry; [BackgroundDependencyLoader] private void load() { Flow.AddRange(new Drawable[] { + bpmTextEntry = new BPMTextBox + { + Bindable = new TimingControlPoint().BeatLengthBindable, + Label = "BPM", + }, bpmSlider = new BPMSlider { Bindable = new TimingControlPoint().BeatLengthBindable, - LabelText = "BPM", }, timeSignature = new SettingsEnumDropdown { @@ -38,6 +44,7 @@ namespace osu.Game.Screens.Edit.Timing if (point.NewValue != null) { bpmSlider.Bindable = point.NewValue.BeatLengthBindable; + bpmTextEntry.Bindable = point.NewValue.BeatLengthBindable; timeSignature.Bindable = point.NewValue.TimeSignatureBindable; } } @@ -53,14 +60,63 @@ namespace osu.Game.Screens.Edit.Timing }; } + private class BPMTextBox : LabelledTextBox + { + public BPMTextBox() + { + OnCommit += (val, isNew) => + { + if (!isNew) return; + + if (double.TryParse(Current.Value, out double doubleVal)) + { + try + { + beatLengthBindable.Value = beatLengthToBpm(doubleVal); + } + catch + { + // will restore the previous text value on failure. + beatLengthBindable.TriggerChange(); + } + } + }; + + beatLengthBindable.BindValueChanged(val => + { + Current.Value = beatLengthToBpm(val.NewValue).ToString(); + }); + } + + private readonly BindableDouble beatLengthBindable = new BindableDouble(); + + public Bindable Bindable + { + get => beatLengthBindable; + set + { + // incoming will be beat length, not bpm + beatLengthBindable.UnbindBindings(); + beatLengthBindable.BindTo(value); + } + } + } + private class BPMSlider : SettingsSlider { private const double sane_minimum = 60; private const double sane_maximum = 200; private readonly BindableDouble beatLengthBindable = new BindableDouble(); + private readonly BindableDouble bpmBindable = new BindableDouble(); - private BindableDouble bpmBindable; + public BPMSlider() + { + beatLengthBindable.BindValueChanged(beatLength => updateCurrent(beatLengthToBpm(beatLength.NewValue))); + bpmBindable.BindValueChanged(bpm => bpmBindable.Default = beatLengthBindable.Value = beatLengthToBpm(bpm.NewValue)); + + base.Bindable = bpmBindable; + } public override Bindable Bindable { @@ -70,23 +126,6 @@ namespace osu.Game.Screens.Edit.Timing // incoming will be beat length, not bpm beatLengthBindable.UnbindBindings(); beatLengthBindable.BindTo(value); - - double initial = beatLengthToBpm(beatLengthBindable.Value); - - bpmBindable = new BindableDouble(initial) - { - Default = beatLengthToBpm(beatLengthBindable.Default), - }; - - updateCurrent(initial); - - bpmBindable.BindValueChanged(bpm => - { - updateCurrent(bpm.NewValue); - beatLengthBindable.Value = beatLengthToBpm(bpm.NewValue); - }); - - base.Bindable = bpmBindable; } } @@ -99,8 +138,8 @@ namespace osu.Game.Screens.Edit.Timing bpmBindable.Value = newValue; } - - private double beatLengthToBpm(double beatLength) => 60000 / beatLength; } + + private static double beatLengthToBpm(double beatLength) => 60000 / beatLength; } } From 98676af7bb3b737aeb07f8fbb8f7a821e21372d8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 7 Sep 2020 18:18:34 +0900 Subject: [PATCH 0927/1782] Move default declarations for readability --- osu.Game/Screens/Edit/Timing/TimingSection.cs | 25 ++++++++----------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/TimingSection.cs b/osu.Game/Screens/Edit/Timing/TimingSection.cs index 6fed4589ce..0112471522 100644 --- a/osu.Game/Screens/Edit/Timing/TimingSection.cs +++ b/osu.Game/Screens/Edit/Timing/TimingSection.cs @@ -23,15 +23,8 @@ namespace osu.Game.Screens.Edit.Timing { Flow.AddRange(new Drawable[] { - bpmTextEntry = new BPMTextBox - { - Bindable = new TimingControlPoint().BeatLengthBindable, - Label = "BPM", - }, - bpmSlider = new BPMSlider - { - Bindable = new TimingControlPoint().BeatLengthBindable, - }, + bpmTextEntry = new BPMTextBox(), + bpmSlider = new BPMSlider(), timeSignature = new SettingsEnumDropdown { LabelText = "Time Signature" @@ -62,8 +55,12 @@ namespace osu.Game.Screens.Edit.Timing private class BPMTextBox : LabelledTextBox { + private readonly BindableNumber beatLengthBindable = new TimingControlPoint().BeatLengthBindable; + public BPMTextBox() { + Label = "BPM"; + OnCommit += (val, isNew) => { if (!isNew) return; @@ -84,12 +81,10 @@ namespace osu.Game.Screens.Edit.Timing beatLengthBindable.BindValueChanged(val => { - Current.Value = beatLengthToBpm(val.NewValue).ToString(); - }); + Current.Value = beatLengthToBpm(val.NewValue).ToString("N2"); + }, true); } - private readonly BindableDouble beatLengthBindable = new BindableDouble(); - public Bindable Bindable { get => beatLengthBindable; @@ -107,12 +102,12 @@ namespace osu.Game.Screens.Edit.Timing private const double sane_minimum = 60; private const double sane_maximum = 200; - private readonly BindableDouble beatLengthBindable = new BindableDouble(); + private readonly BindableNumber beatLengthBindable = new TimingControlPoint().BeatLengthBindable; private readonly BindableDouble bpmBindable = new BindableDouble(); public BPMSlider() { - beatLengthBindable.BindValueChanged(beatLength => updateCurrent(beatLengthToBpm(beatLength.NewValue))); + beatLengthBindable.BindValueChanged(beatLength => updateCurrent(beatLengthToBpm(beatLength.NewValue)), true); bpmBindable.BindValueChanged(bpm => bpmBindable.Default = beatLengthBindable.Value = beatLengthToBpm(bpm.NewValue)); base.Bindable = bpmBindable; From 1468b9589fd7e843cc53c27dcfa6cddcb7bcf1a3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 7 Sep 2020 18:20:47 +0900 Subject: [PATCH 0928/1782] Increase max sane BPM value --- osu.Game/Screens/Edit/Timing/TimingSection.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Timing/TimingSection.cs b/osu.Game/Screens/Edit/Timing/TimingSection.cs index 0112471522..879363ba08 100644 --- a/osu.Game/Screens/Edit/Timing/TimingSection.cs +++ b/osu.Game/Screens/Edit/Timing/TimingSection.cs @@ -100,7 +100,7 @@ namespace osu.Game.Screens.Edit.Timing private class BPMSlider : SettingsSlider { private const double sane_minimum = 60; - private const double sane_maximum = 200; + private const double sane_maximum = 240; private readonly BindableNumber beatLengthBindable = new TimingControlPoint().BeatLengthBindable; private readonly BindableDouble bpmBindable = new BindableDouble(); From b91a376f0a9438191b285510b88aa2c6b693632b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 7 Sep 2020 20:06:38 +0900 Subject: [PATCH 0929/1782] Split dropdown into separate file --- .../Select/CollectionFilterDropdown.cs | 188 ++++++++++++++++++ osu.Game/Screens/Select/FilterControl.cs | 172 ---------------- 2 files changed, 188 insertions(+), 172 deletions(-) create mode 100644 osu.Game/Screens/Select/CollectionFilterDropdown.cs diff --git a/osu.Game/Screens/Select/CollectionFilterDropdown.cs b/osu.Game/Screens/Select/CollectionFilterDropdown.cs new file mode 100644 index 0000000000..883c2c69f0 --- /dev/null +++ b/osu.Game/Screens/Select/CollectionFilterDropdown.cs @@ -0,0 +1,188 @@ +// 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.Specialized; +using System.Diagnostics; +using System.Linq; +using JetBrains.Annotations; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.UserInterface; +using osu.Game.Beatmaps; +using osu.Game.Collections; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.UserInterface; +using osuTK; + +namespace osu.Game.Screens.Select +{ + public class CollectionFilterDropdown : OsuDropdown + { + private readonly IBindableList collections = new BindableList(); + private readonly IBindableList beatmaps = new BindableList(); + private readonly BindableList filters = new BindableList(); + + public CollectionFilterDropdown() + { + ItemSource = filters; + } + + [BackgroundDependencyLoader] + private void load(BeatmapCollectionManager collectionManager) + { + collections.BindTo(collectionManager.Collections); + collections.CollectionChanged += (_, __) => collectionsChanged(); + collectionsChanged(); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + Current.BindValueChanged(filterChanged, true); + } + + /// + /// Occurs when a collection has been added or removed. + /// + private void collectionsChanged() + { + var selectedItem = SelectedItem?.Value?.Collection; + + filters.Clear(); + filters.Add(new FilterControl.CollectionFilter(null)); + filters.AddRange(collections.Select(c => new FilterControl.CollectionFilter(c))); + + Current.Value = filters.SingleOrDefault(f => f.Collection == selectedItem) ?? filters[0]; + } + + /// + /// Occurs when the selection has changed. + /// + private void filterChanged(ValueChangedEvent filter) + { + beatmaps.CollectionChanged -= filterBeatmapsChanged; + + if (filter.OldValue?.Collection != null) + beatmaps.UnbindFrom(filter.OldValue.Collection.Beatmaps); + + if (filter.NewValue?.Collection != null) + beatmaps.BindTo(filter.NewValue.Collection.Beatmaps); + + beatmaps.CollectionChanged += filterBeatmapsChanged; + } + + /// + /// Occurs when the beatmaps contained by a have changed. + /// + private void filterBeatmapsChanged(object sender, NotifyCollectionChangedEventArgs e) + { + // The filtered beatmaps have changed, without the filter having changed itself. So a change in filter must be notified. + // Note that this does NOT propagate to bound bindables, so the FilterControl must bind directly to the value change event of this bindable. + Current.TriggerChange(); + } + + protected override string GenerateItemText(FilterControl.CollectionFilter item) => item.Collection?.Name.Value ?? "All beatmaps"; + + protected override DropdownHeader CreateHeader() => new CollectionDropdownHeader(); + + protected override DropdownMenu CreateMenu() => new CollectionDropdownMenu(); + + private class CollectionDropdownHeader : OsuDropdownHeader + { + public CollectionDropdownHeader() + { + Height = 25; + Icon.Size = new Vector2(16); + Foreground.Padding = new MarginPadding { Top = 4, Bottom = 4, Left = 8, Right = 4 }; + } + } + + private class CollectionDropdownMenu : OsuDropdownMenu + { + public CollectionDropdownMenu() + { + MaxHeight = 200; + } + + protected override DrawableDropdownMenuItem CreateDrawableDropdownMenuItem(MenuItem item) => new CollectionDropdownMenuItem(item); + } + + private class CollectionDropdownMenuItem : OsuDropdownMenu.DrawableOsuDropdownMenuItem + { + [Resolved] + private OsuColour colours { get; set; } + + [Resolved] + private IBindable beatmap { get; set; } + + [CanBeNull] + private readonly BindableList collectionBeatmaps; + + private IconButton addOrRemoveButton; + + public CollectionDropdownMenuItem(MenuItem item) + : base(item) + { + collectionBeatmaps = ((DropdownMenuItem)item).Value.Collection?.Beatmaps.GetBoundCopy(); + } + + [BackgroundDependencyLoader] + private void load() + { + AddRangeInternal(new Drawable[] + { + addOrRemoveButton = new IconButton + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + X = -OsuScrollContainer.SCROLL_BAR_HEIGHT, + Scale = new Vector2(0.75f), + Alpha = collectionBeatmaps == null ? 0 : 1, + Action = addOrRemove + } + }); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + if (collectionBeatmaps != null) + { + collectionBeatmaps.CollectionChanged += (_, __) => collectionChanged(); + beatmap.BindValueChanged(_ => collectionChanged(), true); + } + } + + private void collectionChanged() + { + Debug.Assert(collectionBeatmaps != null); + + addOrRemoveButton.Enabled.Value = !beatmap.IsDefault; + + if (collectionBeatmaps.Contains(beatmap.Value.BeatmapInfo)) + { + addOrRemoveButton.Icon = FontAwesome.Solid.MinusSquare; + addOrRemoveButton.IconColour = colours.Red; + } + else + { + addOrRemoveButton.Icon = FontAwesome.Solid.PlusSquare; + addOrRemoveButton.IconColour = colours.Green; + } + } + + private void addOrRemove() + { + Debug.Assert(collectionBeatmaps != null); + + if (!collectionBeatmaps.Remove(beatmap.Value.BeatmapInfo)) + collectionBeatmaps.Add(beatmap.Value.BeatmapInfo); + } + } + } +} diff --git a/osu.Game/Screens/Select/FilterControl.cs b/osu.Game/Screens/Select/FilterControl.cs index a66bcfb3b1..706909e71e 100644 --- a/osu.Game/Screens/Select/FilterControl.cs +++ b/osu.Game/Screens/Select/FilterControl.cs @@ -2,8 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Collections.Specialized; -using System.Diagnostics; using System.Linq; using JetBrains.Annotations; using osu.Framework.Allocation; @@ -11,14 +9,11 @@ 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.UserInterface; using osu.Framework.Input.Events; using osu.Game.Beatmaps; using osu.Game.Collections; using osu.Game.Configuration; using osu.Game.Graphics; -using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets; @@ -204,173 +199,6 @@ namespace osu.Game.Screens.Select updateCriteria(); } - private class CollectionFilterDropdown : OsuDropdown - { - private readonly IBindableList collections = new BindableList(); - private readonly IBindableList beatmaps = new BindableList(); - private readonly BindableList filters = new BindableList(); - - public CollectionFilterDropdown() - { - ItemSource = filters; - } - - [BackgroundDependencyLoader] - private void load(BeatmapCollectionManager collectionManager) - { - collections.BindTo(collectionManager.Collections); - collections.CollectionChanged += (_, __) => collectionsChanged(); - collectionsChanged(); - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - Current.BindValueChanged(filterChanged, true); - } - - /// - /// Occurs when a collection has been added or removed. - /// - private void collectionsChanged() - { - var selectedItem = SelectedItem?.Value?.Collection; - - filters.Clear(); - filters.Add(new CollectionFilter(null)); - filters.AddRange(collections.Select(c => new CollectionFilter(c))); - - Current.Value = filters.SingleOrDefault(f => f.Collection == selectedItem) ?? filters[0]; - } - - /// - /// Occurs when the selection has changed. - /// - private void filterChanged(ValueChangedEvent filter) - { - beatmaps.CollectionChanged -= filterBeatmapsChanged; - - if (filter.OldValue?.Collection != null) - beatmaps.UnbindFrom(filter.OldValue.Collection.Beatmaps); - - if (filter.NewValue?.Collection != null) - beatmaps.BindTo(filter.NewValue.Collection.Beatmaps); - - beatmaps.CollectionChanged += filterBeatmapsChanged; - } - - /// - /// Occurs when the beatmaps contained by a have changed. - /// - private void filterBeatmapsChanged(object sender, NotifyCollectionChangedEventArgs e) - { - // The filtered beatmaps have changed, without the filter having changed itself. So a change in filter must be notified. - // Note that this does NOT propagate to bound bindables, so the FilterControl must bind directly to the value change event of this bindable. - Current.TriggerChange(); - } - - protected override string GenerateItemText(CollectionFilter item) => item.Collection?.Name.Value ?? "All beatmaps"; - - protected override DropdownHeader CreateHeader() => new CollectionDropdownHeader(); - - protected override DropdownMenu CreateMenu() => new CollectionDropdownMenu(); - - private class CollectionDropdownHeader : OsuDropdownHeader - { - public CollectionDropdownHeader() - { - Height = 25; - Icon.Size = new Vector2(16); - Foreground.Padding = new MarginPadding { Top = 4, Bottom = 4, Left = 8, Right = 4 }; - } - } - - private class CollectionDropdownMenu : OsuDropdownMenu - { - public CollectionDropdownMenu() - { - MaxHeight = 200; - } - - protected override DrawableDropdownMenuItem CreateDrawableDropdownMenuItem(MenuItem item) => new CollectionDropdownMenuItem(item); - } - - private class CollectionDropdownMenuItem : OsuDropdownMenu.DrawableOsuDropdownMenuItem - { - [Resolved] - private OsuColour colours { get; set; } - - [Resolved] - private IBindable beatmap { get; set; } - - [CanBeNull] - private readonly BindableList collectionBeatmaps; - - private IconButton addOrRemoveButton; - - public CollectionDropdownMenuItem(MenuItem item) - : base(item) - { - collectionBeatmaps = ((DropdownMenuItem)item).Value.Collection?.Beatmaps.GetBoundCopy(); - } - - [BackgroundDependencyLoader] - private void load() - { - AddRangeInternal(new Drawable[] - { - addOrRemoveButton = new IconButton - { - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, - X = -OsuScrollContainer.SCROLL_BAR_HEIGHT, - Scale = new Vector2(0.75f), - Alpha = collectionBeatmaps == null ? 0 : 1, - Action = addOrRemove - } - }); - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - if (collectionBeatmaps != null) - { - collectionBeatmaps.CollectionChanged += (_, __) => collectionChanged(); - beatmap.BindValueChanged(_ => collectionChanged(), true); - } - } - - private void collectionChanged() - { - Debug.Assert(collectionBeatmaps != null); - - addOrRemoveButton.Enabled.Value = !beatmap.IsDefault; - - if (collectionBeatmaps.Contains(beatmap.Value.BeatmapInfo)) - { - addOrRemoveButton.Icon = FontAwesome.Solid.MinusSquare; - addOrRemoveButton.IconColour = colours.Red; - } - else - { - addOrRemoveButton.Icon = FontAwesome.Solid.PlusSquare; - addOrRemoveButton.IconColour = colours.Green; - } - } - - private void addOrRemove() - { - Debug.Assert(collectionBeatmaps != null); - - if (!collectionBeatmaps.Remove(beatmap.Value.BeatmapInfo)) - collectionBeatmaps.Add(beatmap.Value.BeatmapInfo); - } - } - } - public class CollectionFilter { [CanBeNull] From 120dfd50a6c6ddb4f74f8cb63dd81c9026920672 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 7 Sep 2020 20:29:28 +0900 Subject: [PATCH 0930/1782] Fix collection names not updating in dropdown --- .../Select/CollectionFilterDropdown.cs | 47 ++++++++++++++++++- 1 file changed, 45 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Select/CollectionFilterDropdown.cs b/osu.Game/Screens/Select/CollectionFilterDropdown.cs index 883c2c69f0..6b5d63771f 100644 --- a/osu.Game/Screens/Select/CollectionFilterDropdown.cs +++ b/osu.Game/Screens/Select/CollectionFilterDropdown.cs @@ -87,18 +87,47 @@ namespace osu.Game.Screens.Select protected override string GenerateItemText(FilterControl.CollectionFilter item) => item.Collection?.Name.Value ?? "All beatmaps"; - protected override DropdownHeader CreateHeader() => new CollectionDropdownHeader(); + protected override DropdownHeader CreateHeader() => new CollectionDropdownHeader + { + SelectedItem = { BindTarget = Current } + }; protected override DropdownMenu CreateMenu() => new CollectionDropdownMenu(); private class CollectionDropdownHeader : OsuDropdownHeader { + public readonly Bindable SelectedItem = new Bindable(); + private readonly Bindable collectionName = new Bindable(); + + protected override string Label + { + get => base.Label; + set { } // See updateText(). + } + public CollectionDropdownHeader() { Height = 25; Icon.Size = new Vector2(16); Foreground.Padding = new MarginPadding { Top = 4, Bottom = 4, Left = 8, Right = 4 }; } + + protected override void LoadComplete() + { + base.LoadComplete(); + + SelectedItem.BindValueChanged(_ => updateBindable(), true); + } + + private void updateBindable() + { + collectionName.UnbindAll(); + collectionName.BindTo(SelectedItem.Value.Collection?.Name ?? new Bindable("All beatmaps")); + collectionName.BindValueChanged(_ => updateText(), true); + } + + // Dropdowns don't bind to value changes, so the real name is copied directly from the selected item here. + private void updateText() => base.Label = collectionName.Value; } private class CollectionDropdownMenu : OsuDropdownMenu @@ -113,6 +142,9 @@ namespace osu.Game.Screens.Select private class CollectionDropdownMenuItem : OsuDropdownMenu.DrawableOsuDropdownMenuItem { + [NotNull] + protected new FilterControl.CollectionFilter Item => ((DropdownMenuItem)base.Item).Value; + [Resolved] private OsuColour colours { get; set; } @@ -122,12 +154,17 @@ namespace osu.Game.Screens.Select [CanBeNull] private readonly BindableList collectionBeatmaps; + [NotNull] + private readonly Bindable collectionName; + private IconButton addOrRemoveButton; + private Content content; public CollectionDropdownMenuItem(MenuItem item) : base(item) { - collectionBeatmaps = ((DropdownMenuItem)item).Value.Collection?.Beatmaps.GetBoundCopy(); + collectionBeatmaps = Item.Collection?.Beatmaps.GetBoundCopy(); + collectionName = Item.Collection?.Name.GetBoundCopy() ?? new Bindable("All beatmaps"); } [BackgroundDependencyLoader] @@ -156,6 +193,10 @@ namespace osu.Game.Screens.Select collectionBeatmaps.CollectionChanged += (_, __) => collectionChanged(); beatmap.BindValueChanged(_ => collectionChanged(), true); } + + // Although the DrawableMenuItem binds to value changes of the item's text, the item is an internal implementation detail of Dropdown that has no knowledge + // of the underlying CollectionFilter value and its accompanying name, so the real name has to be copied here. Without this, the collection name wouldn't update when changed. + collectionName.BindValueChanged(name => content.Text = name.NewValue, true); } private void collectionChanged() @@ -183,6 +224,8 @@ namespace osu.Game.Screens.Select if (!collectionBeatmaps.Remove(beatmap.Value.BeatmapInfo)) collectionBeatmaps.Add(beatmap.Value.BeatmapInfo); } + + protected override Drawable CreateContent() => content = (Content)base.CreateContent(); } } } From c1d255a04c74c05949bfbe3d9b3ec784a4834047 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 7 Sep 2020 20:44:39 +0900 Subject: [PATCH 0931/1782] Split filter control into separate class --- osu.Game/Screens/Select/CollectionFilter.cs | 24 +++++++++++++++++++ .../Select/CollectionFilterDropdown.cs | 18 +++++++------- osu.Game/Screens/Select/FilterControl.cs | 18 -------------- osu.Game/Screens/Select/FilterCriteria.cs | 2 +- 4 files changed, 34 insertions(+), 28 deletions(-) create mode 100644 osu.Game/Screens/Select/CollectionFilter.cs diff --git a/osu.Game/Screens/Select/CollectionFilter.cs b/osu.Game/Screens/Select/CollectionFilter.cs new file mode 100644 index 0000000000..e1f19b41c3 --- /dev/null +++ b/osu.Game/Screens/Select/CollectionFilter.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 System.Linq; +using JetBrains.Annotations; +using osu.Game.Beatmaps; +using osu.Game.Collections; + +namespace osu.Game.Screens.Select +{ + public class CollectionFilter + { + [CanBeNull] + public readonly BeatmapCollection Collection; + + public CollectionFilter([CanBeNull] BeatmapCollection collection) + { + Collection = collection; + } + + public virtual bool ContainsBeatmap(BeatmapInfo beatmap) + => Collection?.Beatmaps.Any(b => b.Equals(beatmap)) ?? true; + } +} diff --git a/osu.Game/Screens/Select/CollectionFilterDropdown.cs b/osu.Game/Screens/Select/CollectionFilterDropdown.cs index 6b5d63771f..ae2f09e11a 100644 --- a/osu.Game/Screens/Select/CollectionFilterDropdown.cs +++ b/osu.Game/Screens/Select/CollectionFilterDropdown.cs @@ -19,11 +19,11 @@ using osuTK; namespace osu.Game.Screens.Select { - public class CollectionFilterDropdown : OsuDropdown + public class CollectionFilterDropdown : OsuDropdown { private readonly IBindableList collections = new BindableList(); private readonly IBindableList beatmaps = new BindableList(); - private readonly BindableList filters = new BindableList(); + private readonly BindableList filters = new BindableList(); public CollectionFilterDropdown() { @@ -53,16 +53,16 @@ namespace osu.Game.Screens.Select var selectedItem = SelectedItem?.Value?.Collection; filters.Clear(); - filters.Add(new FilterControl.CollectionFilter(null)); - filters.AddRange(collections.Select(c => new FilterControl.CollectionFilter(c))); + filters.Add(new CollectionFilter(null)); + filters.AddRange(collections.Select(c => new CollectionFilter(c))); Current.Value = filters.SingleOrDefault(f => f.Collection == selectedItem) ?? filters[0]; } /// - /// Occurs when the selection has changed. + /// Occurs when the selection has changed. /// - private void filterChanged(ValueChangedEvent filter) + private void filterChanged(ValueChangedEvent filter) { beatmaps.CollectionChanged -= filterBeatmapsChanged; @@ -85,7 +85,7 @@ namespace osu.Game.Screens.Select Current.TriggerChange(); } - protected override string GenerateItemText(FilterControl.CollectionFilter item) => item.Collection?.Name.Value ?? "All beatmaps"; + protected override string GenerateItemText(CollectionFilter item) => item.Collection?.Name.Value ?? "All beatmaps"; protected override DropdownHeader CreateHeader() => new CollectionDropdownHeader { @@ -96,7 +96,7 @@ namespace osu.Game.Screens.Select private class CollectionDropdownHeader : OsuDropdownHeader { - public readonly Bindable SelectedItem = new Bindable(); + public readonly Bindable SelectedItem = new Bindable(); private readonly Bindable collectionName = new Bindable(); protected override string Label @@ -143,7 +143,7 @@ namespace osu.Game.Screens.Select private class CollectionDropdownMenuItem : OsuDropdownMenu.DrawableOsuDropdownMenuItem { [NotNull] - protected new FilterControl.CollectionFilter Item => ((DropdownMenuItem)base.Item).Value; + protected new CollectionFilter Item => ((DropdownMenuItem)base.Item).Value; [Resolved] private OsuColour colours { get; set; } diff --git a/osu.Game/Screens/Select/FilterControl.cs b/osu.Game/Screens/Select/FilterControl.cs index 706909e71e..41ce0d65cd 100644 --- a/osu.Game/Screens/Select/FilterControl.cs +++ b/osu.Game/Screens/Select/FilterControl.cs @@ -2,16 +2,12 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Linq; -using JetBrains.Annotations; 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.Events; -using osu.Game.Beatmaps; -using osu.Game.Collections; using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; @@ -199,20 +195,6 @@ namespace osu.Game.Screens.Select updateCriteria(); } - public class CollectionFilter - { - [CanBeNull] - public readonly BeatmapCollection Collection; - - public CollectionFilter([CanBeNull] BeatmapCollection collection) - { - Collection = collection; - } - - public virtual bool ContainsBeatmap(BeatmapInfo beatmap) - => Collection?.Beatmaps.Any(b => b.Equals(beatmap)) ?? true; - } - public void Deactivate() { searchTextBox.ReadOnly = true; diff --git a/osu.Game/Screens/Select/FilterCriteria.cs b/osu.Game/Screens/Select/FilterCriteria.cs index 5a5c0e1b50..af4802f308 100644 --- a/osu.Game/Screens/Select/FilterCriteria.cs +++ b/osu.Game/Screens/Select/FilterCriteria.cs @@ -51,7 +51,7 @@ namespace osu.Game.Screens.Select } } - public FilterControl.CollectionFilter Collection; + public CollectionFilter Collection; public struct OptionalRange : IEquatable> where T : struct From 98e9c4dc256e3397d3ed2fa15c269bc7f0239943 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 7 Sep 2020 21:08:48 +0900 Subject: [PATCH 0932/1782] General refactorings --- .../Collections/TestSceneCollectionDialog.cs | 2 +- osu.Game/Collections/BeatmapCollection.cs | 47 +++++++++++++++++++ .../Collections/BeatmapCollectionManager.cs | 27 +---------- ...onDialog.cs => ManageCollectionsDialog.cs} | 4 +- osu.Game/OsuGame.cs | 2 +- .../Carousel/DrawableCarouselBeatmap.cs | 8 ++-- .../Carousel/DrawableCarouselBeatmapSet.cs | 8 ++-- osu.Game/Screens/Select/CollectionFilter.cs | 24 ++++++++++ .../Select/CollectionFilterDropdown.cs | 8 +++- osu.Game/Screens/Select/FilterCriteria.cs | 5 ++ 10 files changed, 95 insertions(+), 40 deletions(-) create mode 100644 osu.Game/Collections/BeatmapCollection.cs rename osu.Game/Collections/{ManageCollectionDialog.cs => ManageCollectionsDialog.cs} (97%) diff --git a/osu.Game.Tests/Visual/Collections/TestSceneCollectionDialog.cs b/osu.Game.Tests/Visual/Collections/TestSceneCollectionDialog.cs index 247d27f67a..5782e627ba 100644 --- a/osu.Game.Tests/Visual/Collections/TestSceneCollectionDialog.cs +++ b/osu.Game.Tests/Visual/Collections/TestSceneCollectionDialog.cs @@ -18,7 +18,7 @@ namespace osu.Game.Tests.Visual.Collections { Children = new Drawable[] { - new ManageCollectionDialog { State = { Value = Visibility.Visible } }, + new ManageCollectionsDialog { State = { Value = Visibility.Visible } }, dialogOverlay = new DialogOverlay() }; } diff --git a/osu.Game/Collections/BeatmapCollection.cs b/osu.Game/Collections/BeatmapCollection.cs new file mode 100644 index 0000000000..7e4b15ecf9 --- /dev/null +++ b/osu.Game/Collections/BeatmapCollection.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.Bindables; +using osu.Game.Beatmaps; + +namespace osu.Game.Collections +{ + /// + /// A collection of beatmaps grouped by a name. + /// + public class BeatmapCollection + { + /// + /// Invoked whenever any change occurs on this . + /// + public event Action Changed; + + /// + /// The collection's name. + /// + public readonly Bindable Name = new Bindable(); + + /// + /// The beatmaps contained by the collection. + /// + public readonly BindableList Beatmaps = new BindableList(); + + /// + /// The date when this collection was last modified. + /// + public DateTimeOffset LastModifyDate { get; private set; } = DateTimeOffset.UtcNow; + + public BeatmapCollection() + { + Beatmaps.CollectionChanged += (_, __) => onChange(); + Name.ValueChanged += _ => onChange(); + } + + private void onChange() + { + LastModifyDate = DateTimeOffset.Now; + Changed?.Invoke(); + } + } +} diff --git a/osu.Game/Collections/BeatmapCollectionManager.cs b/osu.Game/Collections/BeatmapCollectionManager.cs index 3e5976300f..ed07f0d3e2 100644 --- a/osu.Game/Collections/BeatmapCollectionManager.cs +++ b/osu.Game/Collections/BeatmapCollectionManager.cs @@ -21,7 +21,7 @@ namespace osu.Game.Collections public class BeatmapCollectionManager : CompositeDrawable { /// - /// Database version in YYYYMMDD format (matching stable). + /// Database version in stable-compatible YYYYMMDD format. /// private const int database_version = 30000000; @@ -213,29 +213,4 @@ namespace osu.Game.Collections save(); } } - - public class BeatmapCollection - { - /// - /// Invoked whenever any change occurs on this . - /// - public event Action Changed; - - public readonly Bindable Name = new Bindable(); - - public readonly BindableList Beatmaps = new BindableList(); - - public DateTimeOffset LastModifyTime { get; private set; } - - public BeatmapCollection() - { - LastModifyTime = DateTimeOffset.UtcNow; - - Beatmaps.CollectionChanged += (_, __) => - { - LastModifyTime = DateTimeOffset.Now; - Changed?.Invoke(); - }; - } - } } diff --git a/osu.Game/Collections/ManageCollectionDialog.cs b/osu.Game/Collections/ManageCollectionsDialog.cs similarity index 97% rename from osu.Game/Collections/ManageCollectionDialog.cs rename to osu.Game/Collections/ManageCollectionsDialog.cs index 1e222a9c71..f2aedb1c29 100644 --- a/osu.Game/Collections/ManageCollectionDialog.cs +++ b/osu.Game/Collections/ManageCollectionsDialog.cs @@ -13,7 +13,7 @@ using osuTK; namespace osu.Game.Collections { - public class ManageCollectionDialog : OsuFocusedOverlayContainer + public class ManageCollectionsDialog : OsuFocusedOverlayContainer { private const double enter_duration = 500; private const double exit_duration = 200; @@ -21,7 +21,7 @@ namespace osu.Game.Collections [Resolved] private BeatmapCollectionManager collectionManager { get; set; } - public ManageCollectionDialog() + public ManageCollectionsDialog() { Anchor = Anchor.Centre; Origin = Anchor.Centre; diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 701a65dbeb..8434ee11fa 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -618,7 +618,7 @@ namespace osu.Game loadComponentSingleFile(CreateUpdateManager(), Add, true); // overlay elements - loadComponentSingleFile(new ManageCollectionDialog(), overlayContent.Add, true); + loadComponentSingleFile(new ManageCollectionsDialog(), overlayContent.Add, true); loadComponentSingleFile(beatmapListing = new BeatmapListingOverlay(), overlayContent.Add, true); loadComponentSingleFile(dashboard = new DashboardOverlay(), overlayContent.Add, true); loadComponentSingleFile(news = new NewsOverlay(), overlayContent.Add, true); diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs index 6c43bf5bed..008cf85018 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs @@ -52,7 +52,7 @@ namespace osu.Game.Screens.Select.Carousel private BeatmapCollectionManager collectionManager { get; set; } [Resolved(CanBeNull = true)] - private ManageCollectionDialog manageCollectionDialog { get; set; } + private ManageCollectionsDialog manageCollectionsDialog { get; set; } private IBindable starDifficultyBindable; private CancellationTokenSource starDifficultyCancellationSource; @@ -227,9 +227,9 @@ namespace osu.Game.Screens.Select.Carousel if (beatmap.OnlineBeatmapID.HasValue && beatmapOverlay != null) items.Add(new OsuMenuItem("Details", MenuItemType.Standard, () => beatmapOverlay.FetchAndShowBeatmap(beatmap.OnlineBeatmapID.Value))); - var collectionItems = collectionManager.Collections.OrderByDescending(c => c.LastModifyTime).Take(3).Select(createCollectionMenuItem).ToList(); - if (manageCollectionDialog != null) - collectionItems.Add(new OsuMenuItem("More...", MenuItemType.Standard, manageCollectionDialog.Show)); + var collectionItems = collectionManager.Collections.OrderByDescending(c => c.LastModifyDate).Take(3).Select(createCollectionMenuItem).ToList(); + if (manageCollectionsDialog != null) + collectionItems.Add(new OsuMenuItem("More...", MenuItemType.Standard, manageCollectionsDialog.Show)); items.Add(new OsuMenuItem("Add to...") { Items = collectionItems }); diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index fc262730cb..fe0ad31b32 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -39,7 +39,7 @@ namespace osu.Game.Screens.Select.Carousel private BeatmapCollectionManager collectionManager { get; set; } [Resolved(CanBeNull = true)] - private ManageCollectionDialog manageCollectionDialog { get; set; } + private ManageCollectionsDialog manageCollectionsDialog { get; set; } private readonly BeatmapSetInfo beatmapSet; @@ -148,9 +148,9 @@ namespace osu.Game.Screens.Select.Carousel if (dialogOverlay != null) items.Add(new OsuMenuItem("Delete", MenuItemType.Destructive, () => dialogOverlay.Push(new BeatmapDeleteDialog(beatmapSet)))); - var collectionItems = collectionManager.Collections.OrderByDescending(c => c.LastModifyTime).Take(3).Select(createCollectionMenuItem).ToList(); - if (manageCollectionDialog != null) - collectionItems.Add(new OsuMenuItem("More...", MenuItemType.Standard, manageCollectionDialog.Show)); + var collectionItems = collectionManager.Collections.OrderByDescending(c => c.LastModifyDate).Take(3).Select(createCollectionMenuItem).ToList(); + if (manageCollectionsDialog != null) + collectionItems.Add(new OsuMenuItem("More...", MenuItemType.Standard, manageCollectionsDialog.Show)); items.Add(new OsuMenuItem("Add all to...") { Items = collectionItems }); diff --git a/osu.Game/Screens/Select/CollectionFilter.cs b/osu.Game/Screens/Select/CollectionFilter.cs index e1f19b41c3..7628ed391e 100644 --- a/osu.Game/Screens/Select/CollectionFilter.cs +++ b/osu.Game/Screens/Select/CollectionFilter.cs @@ -3,21 +3,45 @@ using System.Linq; using JetBrains.Annotations; +using osu.Framework.Bindables; using osu.Game.Beatmaps; using osu.Game.Collections; namespace osu.Game.Screens.Select { + /// + /// A filter. + /// public class CollectionFilter { + /// + /// The collection to filter beatmaps from. + /// May be null to not filter by collection (include all beatmaps). + /// [CanBeNull] public readonly BeatmapCollection Collection; + /// + /// The name of the collection. + /// + [NotNull] + public readonly Bindable CollectionName; + + /// + /// Creates a new . + /// + /// The collection to filter beatmaps from. public CollectionFilter([CanBeNull] BeatmapCollection collection) { Collection = collection; + CollectionName = Collection?.Name.GetBoundCopy() ?? new Bindable("All beatmaps"); } + /// + /// Whether the collection contains a given beatmap. + /// + /// The beatmap to check. + /// Whether contains . public virtual bool ContainsBeatmap(BeatmapInfo beatmap) => Collection?.Beatmaps.Any(b => b.Equals(beatmap)) ?? true; } diff --git a/osu.Game/Screens/Select/CollectionFilterDropdown.cs b/osu.Game/Screens/Select/CollectionFilterDropdown.cs index ae2f09e11a..02484f6c64 100644 --- a/osu.Game/Screens/Select/CollectionFilterDropdown.cs +++ b/osu.Game/Screens/Select/CollectionFilterDropdown.cs @@ -19,6 +19,9 @@ using osuTK; namespace osu.Game.Screens.Select { + /// + /// A dropdown to select the to filter beatmaps using. + /// public class CollectionFilterDropdown : OsuDropdown { private readonly IBindableList collections = new BindableList(); @@ -64,6 +67,7 @@ namespace osu.Game.Screens.Select /// private void filterChanged(ValueChangedEvent filter) { + // Binding the beatmaps will trigger a collection change event, which results in an infinite-loop. This is rebound later, when it's safe to do so. beatmaps.CollectionChanged -= filterBeatmapsChanged; if (filter.OldValue?.Collection != null) @@ -122,7 +126,7 @@ namespace osu.Game.Screens.Select private void updateBindable() { collectionName.UnbindAll(); - collectionName.BindTo(SelectedItem.Value.Collection?.Name ?? new Bindable("All beatmaps")); + collectionName.BindTo(SelectedItem.Value.CollectionName); collectionName.BindValueChanged(_ => updateText(), true); } @@ -164,7 +168,7 @@ namespace osu.Game.Screens.Select : base(item) { collectionBeatmaps = Item.Collection?.Beatmaps.GetBoundCopy(); - collectionName = Item.Collection?.Name.GetBoundCopy() ?? new Bindable("All beatmaps"); + collectionName = Item.CollectionName.GetBoundCopy(); } [BackgroundDependencyLoader] diff --git a/osu.Game/Screens/Select/FilterCriteria.cs b/osu.Game/Screens/Select/FilterCriteria.cs index af4802f308..acab982945 100644 --- a/osu.Game/Screens/Select/FilterCriteria.cs +++ b/osu.Game/Screens/Select/FilterCriteria.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using JetBrains.Annotations; using osu.Game.Beatmaps; using osu.Game.Rulesets; using osu.Game.Screens.Select.Filter; @@ -51,6 +52,10 @@ namespace osu.Game.Screens.Select } } + /// + /// The collection to filter beatmaps from. + /// + [CanBeNull] public CollectionFilter Collection; public struct OptionalRange : IEquatable> From ad625ecc7a199e3f93a4e827312d07b4fbeee957 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 7 Sep 2020 22:10:12 +0900 Subject: [PATCH 0933/1782] Add collection IO tests --- .../Collections/IO/ImportCollectionsTest.cs | 215 ++++++++++++++++++ .../Resources/Collections/collections.db | Bin 0 -> 473 bytes .../Collections/BeatmapCollectionManager.cs | 22 +- 3 files changed, 232 insertions(+), 5 deletions(-) create mode 100644 osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs create mode 100644 osu.Game.Tests/Resources/Collections/collections.db diff --git a/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs b/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs new file mode 100644 index 0000000000..7d772d3989 --- /dev/null +++ b/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs @@ -0,0 +1,215 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.IO; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Platform; +using osu.Game.Beatmaps; +using osu.Game.Collections; +using osu.Game.Tests.Resources; + +namespace osu.Game.Tests.Collections.IO +{ + [TestFixture] + public class ImportCollectionsTest + { + [Test] + public async Task TestImportEmptyDatabase() + { + using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportEmptyDatabase")) + { + try + { + var osu = await loadOsu(host); + + var collectionManager = osu.Dependencies.Get(); + await collectionManager.Import(new MemoryStream()); + + Assert.That(collectionManager.Collections.Count, Is.Zero); + } + finally + { + host.Exit(); + } + } + } + + [Test] + public async Task TestImportWithNoBeatmaps() + { + using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportWithNoBeatmaps")) + { + try + { + var osu = await loadOsu(host); + + var collectionManager = osu.Dependencies.Get(); + await collectionManager.Import(TestResources.OpenResource("Collections/collections.db")); + + Assert.That(collectionManager.Collections.Count, Is.EqualTo(2)); + + Assert.That(collectionManager.Collections[0].Name.Value, Is.EqualTo("First")); + Assert.That(collectionManager.Collections[0].Beatmaps.Count, Is.Zero); + + Assert.That(collectionManager.Collections[1].Name.Value, Is.EqualTo("Second")); + Assert.That(collectionManager.Collections[1].Beatmaps.Count, Is.Zero); + } + finally + { + host.Exit(); + } + } + } + + [Test] + public async Task TestImportWithBeatmaps() + { + using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportWithBeatmaps")) + { + try + { + var osu = await loadOsu(host, true); + + var collectionManager = osu.Dependencies.Get(); + await collectionManager.Import(TestResources.OpenResource("Collections/collections.db")); + + Assert.That(collectionManager.Collections.Count, Is.EqualTo(2)); + + Assert.That(collectionManager.Collections[0].Name.Value, Is.EqualTo("First")); + Assert.That(collectionManager.Collections[0].Beatmaps.Count, Is.EqualTo(1)); + + Assert.That(collectionManager.Collections[1].Name.Value, Is.EqualTo("Second")); + Assert.That(collectionManager.Collections[1].Beatmaps.Count, Is.EqualTo(12)); + } + finally + { + host.Exit(); + } + } + } + + [Test] + public async Task TestImportMalformedDatabase() + { + bool exceptionThrown = false; + UnhandledExceptionEventHandler setException = (_, __) => exceptionThrown = true; + + using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportMalformedDatabase")) + { + try + { + AppDomain.CurrentDomain.UnhandledException += setException; + + var osu = await loadOsu(host, true); + + var collectionManager = osu.Dependencies.Get(); + + using (var ms = new MemoryStream()) + { + using (var bw = new BinaryWriter(ms, Encoding.UTF8, true)) + { + for (int i = 0; i < 10000; i++) + bw.Write((byte)i); + } + + ms.Seek(0, SeekOrigin.Begin); + + await collectionManager.Import(ms); + } + + Assert.That(host.UpdateThread.Running, Is.True); + Assert.That(exceptionThrown, Is.False); + Assert.That(collectionManager.Collections.Count, Is.EqualTo(0)); + } + finally + { + host.Exit(); + AppDomain.CurrentDomain.UnhandledException -= setException; + } + } + } + + [Test] + public async Task TestSaveAndReload() + { + using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestSaveAndReload")) + { + try + { + var osu = await loadOsu(host, true); + + var collectionManager = osu.Dependencies.Get(); + await collectionManager.Import(TestResources.OpenResource("Collections/collections.db")); + + // Move first beatmap from second collection into the first. + collectionManager.Collections[0].Beatmaps.Add(collectionManager.Collections[1].Beatmaps[0]); + collectionManager.Collections[1].Beatmaps.RemoveAt(0); + + // Rename the second collecction. + collectionManager.Collections[1].Name.Value = "Another"; + } + finally + { + host.Exit(); + } + } + + using (HeadlessGameHost host = new HeadlessGameHost("TestSaveAndReload")) + { + try + { + var osu = await loadOsu(host, true); + + var collectionManager = osu.Dependencies.Get(); + + Assert.That(collectionManager.Collections.Count, Is.EqualTo(2)); + + Assert.That(collectionManager.Collections[0].Name.Value, Is.EqualTo("First")); + Assert.That(collectionManager.Collections[0].Beatmaps.Count, Is.EqualTo(2)); + + Assert.That(collectionManager.Collections[1].Name.Value, Is.EqualTo("Another")); + Assert.That(collectionManager.Collections[1].Beatmaps.Count, Is.EqualTo(11)); + } + finally + { + host.Exit(); + } + } + } + + private async Task loadOsu(GameHost host, bool withBeatmap = false) + { + var osu = new OsuGameBase(); + +#pragma warning disable 4014 + Task.Run(() => host.Run(osu)); +#pragma warning restore 4014 + + waitForOrAssert(() => osu.IsLoaded, @"osu! failed to start in a reasonable amount of time"); + + if (withBeatmap) + { + var beatmapFile = TestResources.GetTestBeatmapForImport(); + var beatmapManager = osu.Dependencies.Get(); + await beatmapManager.Import(beatmapFile); + } + + return osu; + } + + private void waitForOrAssert(Func result, string failureMessage, int timeout = 60000) + { + Task task = Task.Run(() => + { + while (!result()) Thread.Sleep(200); + }); + + Assert.IsTrue(task.Wait(timeout), failureMessage); + } + } +} diff --git a/osu.Game.Tests/Resources/Collections/collections.db b/osu.Game.Tests/Resources/Collections/collections.db new file mode 100644 index 0000000000000000000000000000000000000000..83e1c0f10a9058f3f376e75d3b5a88f671a95f63 GIT binary patch literal 473 zcmah_%W0lL4E!CiFJFrIO3>=Ld+;?4qyjxw;7bCryL3}or-29rgV2mL^ZCk8-yV<0 z_59=Q&-=&I7rdJX!-hP)U7D7xkTeI>lFo6x{M`BbSAGAty`>hfB!!t?h{`*ueUU(z zwqLg>Kq9oOjOI0BLY+xkyAg0+_rhpwBocF}ptLdFxMa3XucLvnYP9pNF;*O~cDSX^ zm8A@4W6w#hixgKP0&(j^RSP^kU2@%VUNsbpr9-6>Ab1NHf^gOz*PS64xg(D-ELaUW z$6;(dB@EZi9CA)@46-aEqHY}+Vltepue&O_nhD)wo)}crPm*6o_5?jw{+sW8lH + return Task.Run(async () => { var storage = GetStableStorage(); if (storage.Exists(database_name)) { using (var stream = storage.GetStream(database_name)) - { - var collection = readCollections(stream); - Schedule(() => importCollections(collection)); - } + await Import(stream); } }); } + public async Task Import(Stream stream) => await Task.Run(async () => + { + var collection = readCollections(stream); + bool importCompleted = false; + + Schedule(() => + { + importCollections(collection); + importCompleted = true; + }); + + while (!IsDisposed && !importCompleted) + await Task.Delay(10); + }); + private void importCollections(List newCollections) { foreach (var newCol in newCollections) From 0d5d293279eb96bcefc739c19fd69da4fcdcdad2 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 7 Sep 2020 22:47:19 +0900 Subject: [PATCH 0934/1782] Add manage collections dialog tests --- .../Collections/TestSceneCollectionDialog.cs | 26 --- .../TestSceneManageCollectionsDialog.cs | 198 ++++++++++++++++++ .../Collections/BeatmapCollectionManager.cs | 19 +- .../Collections/DrawableCollectionListItem.cs | 2 +- osu.Game/OsuGameBase.cs | 2 +- 5 files changed, 212 insertions(+), 35 deletions(-) delete mode 100644 osu.Game.Tests/Visual/Collections/TestSceneCollectionDialog.cs create mode 100644 osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs diff --git a/osu.Game.Tests/Visual/Collections/TestSceneCollectionDialog.cs b/osu.Game.Tests/Visual/Collections/TestSceneCollectionDialog.cs deleted file mode 100644 index 5782e627ba..0000000000 --- a/osu.Game.Tests/Visual/Collections/TestSceneCollectionDialog.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. - -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Game.Collections; -using osu.Game.Overlays; - -namespace osu.Game.Tests.Visual.Collections -{ - public class TestSceneCollectionDialog : OsuTestScene - { - [Cached] - private DialogOverlay dialogOverlay; - - public TestSceneCollectionDialog() - { - Children = new Drawable[] - { - new ManageCollectionsDialog { State = { Value = Visibility.Visible } }, - dialogOverlay = new DialogOverlay() - }; - } - } -} diff --git a/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs b/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs new file mode 100644 index 0000000000..2d6f8abd8b --- /dev/null +++ b/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.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 System.Linq; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.UserInterface; +using osu.Framework.Testing; +using osu.Game.Collections; +using osu.Game.Graphics.UserInterface; +using osu.Game.Overlays; +using osu.Game.Overlays.Dialog; +using osuTK; +using osuTK.Input; + +namespace osu.Game.Tests.Visual.Collections +{ + public class TestSceneManageCollectionsDialog : OsuManualInputManagerTestScene + { + [Cached] + private readonly DialogOverlay dialogOverlay; + + protected override Container Content => content; + + private readonly Container content; + + private BeatmapCollectionManager manager; + private ManageCollectionsDialog dialog; + + public TestSceneManageCollectionsDialog() + { + base.Content.AddRange(new Drawable[] + { + content = new Container { RelativeSizeAxes = Axes.Both }, + dialogOverlay = new DialogOverlay() + }); + } + + [BackgroundDependencyLoader] + private void load() + { + Dependencies.Cache(manager = new BeatmapCollectionManager(LocalStorage)); + Add(manager); + } + + [SetUp] + public void SetUp() => Schedule(() => + { + manager.Collections.Clear(); + Child = dialog = new ManageCollectionsDialog(); + }); + + [SetUpSteps] + public void SetUpSteps() + { + AddStep("show dialog", () => dialog.Show()); + } + + [Test] + public void TestHideDialog() + { + AddWaitStep("wait for animation", 3); + AddStep("hide dialog", () => dialog.Hide()); + } + + [Test] + public void TestAddCollectionExternal() + { + AddStep("add collection", () => manager.Collections.Add(new BeatmapCollection { Name = { Value = "First collection" } })); + assertCollectionCount(1); + assertCollectionName(0, "First collection"); + + AddStep("add another collection", () => manager.Collections.Add(new BeatmapCollection { Name = { Value = "Second collection" } })); + assertCollectionCount(2); + assertCollectionName(1, "Second collection"); + } + + [Test] + public void TestAddCollectionViaButton() + { + AddStep("press new collection button", () => + { + InputManager.MoveMouseTo(dialog.ChildrenOfType().Single()); + InputManager.Click(MouseButton.Left); + }); + + assertCollectionCount(1); + + AddStep("press again", () => + { + InputManager.MoveMouseTo(dialog.ChildrenOfType().Single()); + InputManager.Click(MouseButton.Left); + }); + + assertCollectionCount(2); + } + + [Test] + public void TestRemoveCollectionExternal() + { + AddStep("add two collections", () => manager.Collections.AddRange(new[] + { + new BeatmapCollection { Name = { Value = "1" } }, + new BeatmapCollection { Name = { Value = "2" } }, + })); + + AddStep("remove first collection", () => manager.Collections.RemoveAt(0)); + assertCollectionCount(1); + assertCollectionName(0, "2"); + } + + [Test] + public void TestRemoveCollectionViaButton() + { + AddStep("add two collections", () => manager.Collections.AddRange(new[] + { + new BeatmapCollection { Name = { Value = "1" } }, + new BeatmapCollection { Name = { Value = "2" } }, + })); + + AddStep("click first delete button", () => + { + InputManager.MoveMouseTo(dialog.ChildrenOfType().First(), new Vector2(5, 0)); + InputManager.Click(MouseButton.Left); + }); + + AddAssert("dialog displayed", () => dialogOverlay.CurrentDialog is DeleteCollectionDialog); + AddStep("click confirmation", () => + { + InputManager.MoveMouseTo(dialogOverlay.CurrentDialog.ChildrenOfType().First()); + InputManager.Click(MouseButton.Left); + }); + + assertCollectionCount(1); + assertCollectionName(0, "2"); + } + + [Test] + public void TestCollectionNotRemovedWhenDialogCancelled() + { + AddStep("add two collections", () => manager.Collections.AddRange(new[] + { + new BeatmapCollection { Name = { Value = "1" } }, + new BeatmapCollection { Name = { Value = "2" } }, + })); + + AddStep("click first delete button", () => + { + InputManager.MoveMouseTo(dialog.ChildrenOfType().First(), new Vector2(5, 0)); + InputManager.Click(MouseButton.Left); + }); + + AddAssert("dialog displayed", () => dialogOverlay.CurrentDialog is DeleteCollectionDialog); + AddStep("click confirmation", () => + { + InputManager.MoveMouseTo(dialogOverlay.CurrentDialog.ChildrenOfType().Last()); + InputManager.Click(MouseButton.Left); + }); + + assertCollectionCount(2); + } + + [Test] + public void TestCollectionRenamedExternal() + { + AddStep("add two collections", () => manager.Collections.AddRange(new[] + { + new BeatmapCollection { Name = { Value = "1" } }, + new BeatmapCollection { Name = { Value = "2" } }, + })); + + AddStep("change first collection name", () => manager.Collections[0].Name.Value = "First"); + + assertCollectionName(0, "First"); + } + + [Test] + public void TestCollectionRenamedOnTextChange() + { + AddStep("add two collections", () => manager.Collections.AddRange(new[] + { + new BeatmapCollection { Name = { Value = "1" } }, + new BeatmapCollection { Name = { Value = "2" } }, + })); + + AddStep("change first collection name", () => dialog.ChildrenOfType().First().Text = "First"); + AddAssert("collection has new name", () => manager.Collections[0].Name.Value == "First"); + } + + private void assertCollectionCount(int count) + => AddAssert($"{count} collections shown", () => dialog.ChildrenOfType().Count() == count); + + private void assertCollectionName(int index, string name) + => AddAssert($"item {index + 1} has correct name", () => dialog.ChildrenOfType().ElementAt(index).ChildrenOfType().First().Text == name); + } +} diff --git a/osu.Game/Collections/BeatmapCollectionManager.cs b/osu.Game/Collections/BeatmapCollectionManager.cs index 3d3e9e0e07..a553ac632e 100644 --- a/osu.Game/Collections/BeatmapCollectionManager.cs +++ b/osu.Game/Collections/BeatmapCollectionManager.cs @@ -37,12 +37,19 @@ namespace osu.Game.Collections [Resolved] private BeatmapManager beatmaps { get; set; } + private readonly Storage storage; + + public BeatmapCollectionManager(Storage storage) + { + this.storage = storage; + } + [BackgroundDependencyLoader] private void load() { - if (host.Storage.Exists(database_name)) + if (storage.Exists(database_name)) { - using (var stream = host.Storage.GetStream(database_name)) + using (var stream = storage.GetStream(database_name)) importCollections(readCollections(stream)); } @@ -78,11 +85,9 @@ namespace osu.Game.Collections return Task.Run(async () => { - var storage = GetStableStorage(); - - if (storage.Exists(database_name)) + if (stable.Exists(database_name)) { - using (var stream = storage.GetStream(database_name)) + using (var stream = stable.GetStream(database_name)) await Import(stream); } }); @@ -188,7 +193,7 @@ namespace osu.Game.Collections { // This is NOT thread-safe!! - using (var sw = new SerializationWriter(host.Storage.GetStream(database_name, FileAccess.Write))) + using (var sw = new SerializationWriter(storage.GetStream(database_name, FileAccess.Write))) { sw.Write(database_version); sw.Write(Collections.Count); diff --git a/osu.Game/Collections/DrawableCollectionListItem.cs b/osu.Game/Collections/DrawableCollectionListItem.cs index 7c1a2e1287..c7abf58d10 100644 --- a/osu.Game/Collections/DrawableCollectionListItem.cs +++ b/osu.Game/Collections/DrawableCollectionListItem.cs @@ -87,7 +87,7 @@ namespace osu.Game.Collections } } - private class DeleteButton : CompositeDrawable + public class DeleteButton : CompositeDrawable { public Func IsTextBoxHovered; diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 30494d18fb..3114727d54 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -228,7 +228,7 @@ namespace osu.Game dependencies.Cache(difficultyManager); AddInternal(difficultyManager); - dependencies.Cache(CollectionManager = new BeatmapCollectionManager()); + dependencies.Cache(CollectionManager = new BeatmapCollectionManager(Storage)); AddInternal(CollectionManager); dependencies.Cache(KeyBindingStore = new KeyBindingStore(contextFactory, RulesetStore)); From a1214512bc96532f984166b20b77ecaa56bd890d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 7 Sep 2020 23:57:49 +0900 Subject: [PATCH 0935/1782] Add filter control tests --- .../SongSelect/TestSceneFilterControl.cs | 213 +++++++++++++++++- .../Select/CollectionFilterDropdown.cs | 28 +-- 2 files changed, 225 insertions(+), 16 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs index f89300661c..fe1c194c5b 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs @@ -1,22 +1,231 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.UserInterface; +using osu.Framework.Platform; +using osu.Framework.Testing; +using osu.Game.Beatmaps; +using osu.Game.Collections; +using osu.Game.Graphics.UserInterface; +using osu.Game.Rulesets; using osu.Game.Screens.Select; +using osu.Game.Tests.Resources; +using osuTK.Input; namespace osu.Game.Tests.Visual.SongSelect { - public class TestSceneFilterControl : OsuTestScene + public class TestSceneFilterControl : OsuManualInputManagerTestScene { + protected override Container Content => content; + private readonly Container content; + + [Cached] + private readonly BeatmapCollectionManager collectionManager; + + private RulesetStore rulesets; + private BeatmapManager beatmapManager; + + private FilterControl control; + public TestSceneFilterControl() { - Child = new FilterControl + base.Content.AddRange(new Drawable[] + { + collectionManager = new BeatmapCollectionManager(LocalStorage), + content = new Container { RelativeSizeAxes = Axes.Both } + }); + } + + protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) + { + var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); + dependencies.Cache(collectionManager); + return dependencies; + } + + [BackgroundDependencyLoader] + private void load(GameHost host) + { + Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); + Dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, Audio, host, Beatmap.Default)); + + beatmapManager.Import(TestResources.GetTestBeatmapForImport()).Wait(); + } + + [SetUp] + public void SetUp() => Schedule(() => + { + collectionManager.Collections.Clear(); + + Child = control = new FilterControl { Anchor = Anchor.Centre, Origin = Anchor.Centre, RelativeSizeAxes = Axes.X, Height = FilterControl.HEIGHT, }; + }); + + [Test] + public void TestEmptyCollectionFilterContainsAllBeatmaps() + { + assertCollectionDropdownContains("All beatmaps"); + assertCollectionHeaderDisplays("All beatmaps"); } + + [Test] + public void TestCollectionAddedToDropdown() + { + AddStep("add collection", () => collectionManager.Collections.Add(new BeatmapCollection { Name = { Value = "1" } })); + AddStep("add collection", () => collectionManager.Collections.Add(new BeatmapCollection { Name = { Value = "2" } })); + assertCollectionDropdownContains("1"); + assertCollectionDropdownContains("2"); + } + + [Test] + public void TestCollectionRemovedFromDropdown() + { + AddStep("add collection", () => collectionManager.Collections.Add(new BeatmapCollection { Name = { Value = "1" } })); + AddStep("add collection", () => collectionManager.Collections.Add(new BeatmapCollection { Name = { Value = "2" } })); + AddStep("remove collection", () => collectionManager.Collections.RemoveAt(0)); + + assertCollectionDropdownContains("1", false); + assertCollectionDropdownContains("2"); + } + + [Test] + public void TestCollectionRenamed() + { + AddStep("add collection", () => collectionManager.Collections.Add(new BeatmapCollection { Name = { Value = "1" } })); + AddStep("select collection", () => + { + var dropdown = control.ChildrenOfType().Single(); + dropdown.Current.Value = dropdown.ItemSource.ElementAt(1); + }); + + AddStep("expand header", () => + { + InputManager.MoveMouseTo(control.ChildrenOfType().Single()); + InputManager.Click(MouseButton.Left); + }); + + AddStep("change name", () => collectionManager.Collections[0].Name.Value = "First"); + + assertCollectionDropdownContains("First"); + assertCollectionHeaderDisplays("First"); + } + + [Test] + public void TestAllBeatmapFilterDoesNotHaveAddButton() + { + AddAssert("'All beatmaps' filter does not have add button", () => !getCollectionDropdownItems().First().ChildrenOfType().Single().IsPresent); + } + + [Test] + public void TestCollectionFilterHasAddButton() + { + AddStep("expand header", () => + { + InputManager.MoveMouseTo(control.ChildrenOfType().Single()); + InputManager.Click(MouseButton.Left); + }); + + AddStep("add collection", () => collectionManager.Collections.Add(new BeatmapCollection { Name = { Value = "1" } })); + AddAssert("collection has add button", () => !getAddOrRemoveButton(0).IsPresent); + } + + [Test] + public void TestButtonDisabledAndEnabledWithBeatmapChanges() + { + AddStep("expand header", () => + { + InputManager.MoveMouseTo(control.ChildrenOfType().Single()); + InputManager.Click(MouseButton.Left); + }); + + AddStep("add collection", () => collectionManager.Collections.Add(new BeatmapCollection { Name = { Value = "1" } })); + AddAssert("button disabled", () => !getAddOrRemoveButton(1).Enabled.Value); + + AddStep("select available beatmap", () => Beatmap.Value = beatmapManager.GetWorkingBeatmap(beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps[0])); + AddAssert("button enabled", () => getAddOrRemoveButton(1).Enabled.Value); + + AddStep("set dummy beatmap", () => Beatmap.SetDefault()); + AddAssert("button enabled", () => !getAddOrRemoveButton(1).Enabled.Value); + } + + [Test] + public void TestButtonChangesWhenAddedAndRemovedFromCollection() + { + AddStep("expand header", () => + { + InputManager.MoveMouseTo(control.ChildrenOfType().Single()); + InputManager.Click(MouseButton.Left); + }); + + AddStep("select available beatmap", () => Beatmap.Value = beatmapManager.GetWorkingBeatmap(beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps[0])); + + AddStep("add collection", () => collectionManager.Collections.Add(new BeatmapCollection { Name = { Value = "1" } })); + AddAssert("button is plus", () => getAddOrRemoveButton(1).Icon.Equals(FontAwesome.Regular.PlusSquare)); + + AddStep("add beatmap to collection", () => collectionManager.Collections[0].Beatmaps.Add(Beatmap.Value.BeatmapInfo)); + AddAssert("button is minus", () => getAddOrRemoveButton(1).Icon.Equals(FontAwesome.Regular.MinusSquare)); + + AddStep("remove beatmap from collection", () => collectionManager.Collections[0].Beatmaps.Clear()); + AddAssert("button is plus", () => getAddOrRemoveButton(1).Icon.Equals(FontAwesome.Regular.PlusSquare)); + } + + [Test] + public void TestButtonAddsAndRemovesBeatmap() + { + AddStep("expand header", () => + { + InputManager.MoveMouseTo(control.ChildrenOfType().Single()); + InputManager.Click(MouseButton.Left); + }); + + AddStep("select available beatmap", () => Beatmap.Value = beatmapManager.GetWorkingBeatmap(beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps[0])); + + AddStep("add collection", () => collectionManager.Collections.Add(new BeatmapCollection { Name = { Value = "1" } })); + AddAssert("button is plus", () => getAddOrRemoveButton(1).Icon.Equals(FontAwesome.Regular.PlusSquare)); + + addClickAddOrRemoveButtonStep(1); + AddAssert("collection contains beatmap", () => collectionManager.Collections[0].Beatmaps.Contains(Beatmap.Value.BeatmapInfo)); + AddAssert("button is minus", () => getAddOrRemoveButton(1).Icon.Equals(FontAwesome.Regular.MinusSquare)); + + addClickAddOrRemoveButtonStep(1); + AddAssert("collection does not contain beatmap", () => !collectionManager.Collections[0].Beatmaps.Contains(Beatmap.Value.BeatmapInfo)); + AddAssert("button is plus", () => getAddOrRemoveButton(1).Icon.Equals(FontAwesome.Regular.PlusSquare)); + } + + private void assertCollectionHeaderDisplays(string collectionName, bool shouldDisplay = true) + => AddAssert($"collection dropdown header displays '{collectionName}'", + () => shouldDisplay == (control.ChildrenOfType().Single().ChildrenOfType().First().Text == collectionName)); + + private void assertCollectionDropdownContains(string collectionName, bool shouldContain = true) => + AddAssert($"collection dropdown {(shouldContain ? "contains" : "does not contain")} '{collectionName}'", + // A bit of a roundabout way of going about this, see: https://github.com/ppy/osu-framework/issues/3871 + https://github.com/ppy/osu-framework/issues/3872 + () => shouldContain == (getCollectionDropdownItems().Any(i => i.ChildrenOfType().OfType().First().Text == collectionName))); + + private IconButton getAddOrRemoveButton(int index) + => getCollectionDropdownItems().ElementAt(index).ChildrenOfType().Single(); + + private void addClickAddOrRemoveButtonStep(int index) + { + AddStep("click add or remove button", () => + { + InputManager.MoveMouseTo(getAddOrRemoveButton(index)); + InputManager.Click(MouseButton.Left); + }); + } + + private IEnumerable.DropdownMenu.DrawableDropdownMenuItem> getCollectionDropdownItems() + => control.ChildrenOfType().Single().ChildrenOfType.DropdownMenu.DrawableDropdownMenuItem>(); } } diff --git a/osu.Game/Screens/Select/CollectionFilterDropdown.cs b/osu.Game/Screens/Select/CollectionFilterDropdown.cs index 02484f6c64..2d30263d78 100644 --- a/osu.Game/Screens/Select/CollectionFilterDropdown.cs +++ b/osu.Game/Screens/Select/CollectionFilterDropdown.cs @@ -98,7 +98,7 @@ namespace osu.Game.Screens.Select protected override DropdownMenu CreateMenu() => new CollectionDropdownMenu(); - private class CollectionDropdownHeader : OsuDropdownHeader + public class CollectionDropdownHeader : OsuDropdownHeader { public readonly Bindable SelectedItem = new Bindable(); private readonly Bindable collectionName = new Bindable(); @@ -126,7 +126,10 @@ namespace osu.Game.Screens.Select private void updateBindable() { collectionName.UnbindAll(); - collectionName.BindTo(SelectedItem.Value.CollectionName); + + if (SelectedItem.Value != null) + collectionName.BindTo(SelectedItem.Value.CollectionName); + collectionName.BindValueChanged(_ => updateText(), true); } @@ -174,17 +177,14 @@ namespace osu.Game.Screens.Select [BackgroundDependencyLoader] private void load() { - AddRangeInternal(new Drawable[] + AddInternal(addOrRemoveButton = new IconButton { - addOrRemoveButton = new IconButton - { - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, - X = -OsuScrollContainer.SCROLL_BAR_HEIGHT, - Scale = new Vector2(0.75f), - Alpha = collectionBeatmaps == null ? 0 : 1, - Action = addOrRemove - } + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + X = -OsuScrollContainer.SCROLL_BAR_HEIGHT, + Scale = new Vector2(0.75f), + Alpha = collectionBeatmaps == null ? 0 : 1, + Action = addOrRemove }); } @@ -211,12 +211,12 @@ namespace osu.Game.Screens.Select if (collectionBeatmaps.Contains(beatmap.Value.BeatmapInfo)) { - addOrRemoveButton.Icon = FontAwesome.Solid.MinusSquare; + addOrRemoveButton.Icon = FontAwesome.Regular.MinusSquare; addOrRemoveButton.IconColour = colours.Red; } else { - addOrRemoveButton.Icon = FontAwesome.Solid.PlusSquare; + addOrRemoveButton.Icon = FontAwesome.Regular.PlusSquare; addOrRemoveButton.IconColour = colours.Green; } } From e37c04cb6d97c96df6d2858d4d8f31152ac3b1bc Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 8 Sep 2020 00:04:03 +0900 Subject: [PATCH 0936/1782] Change back to solid icon --- .../Visual/SongSelect/TestSceneFilterControl.cs | 12 ++++++------ osu.Game/Screens/Select/CollectionFilterDropdown.cs | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs index fe1c194c5b..89a9536a04 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs @@ -172,13 +172,13 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("select available beatmap", () => Beatmap.Value = beatmapManager.GetWorkingBeatmap(beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps[0])); AddStep("add collection", () => collectionManager.Collections.Add(new BeatmapCollection { Name = { Value = "1" } })); - AddAssert("button is plus", () => getAddOrRemoveButton(1).Icon.Equals(FontAwesome.Regular.PlusSquare)); + AddAssert("button is plus", () => getAddOrRemoveButton(1).Icon.Equals(FontAwesome.Solid.PlusSquare)); AddStep("add beatmap to collection", () => collectionManager.Collections[0].Beatmaps.Add(Beatmap.Value.BeatmapInfo)); - AddAssert("button is minus", () => getAddOrRemoveButton(1).Icon.Equals(FontAwesome.Regular.MinusSquare)); + AddAssert("button is minus", () => getAddOrRemoveButton(1).Icon.Equals(FontAwesome.Solid.MinusSquare)); AddStep("remove beatmap from collection", () => collectionManager.Collections[0].Beatmaps.Clear()); - AddAssert("button is plus", () => getAddOrRemoveButton(1).Icon.Equals(FontAwesome.Regular.PlusSquare)); + AddAssert("button is plus", () => getAddOrRemoveButton(1).Icon.Equals(FontAwesome.Solid.PlusSquare)); } [Test] @@ -193,15 +193,15 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("select available beatmap", () => Beatmap.Value = beatmapManager.GetWorkingBeatmap(beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps[0])); AddStep("add collection", () => collectionManager.Collections.Add(new BeatmapCollection { Name = { Value = "1" } })); - AddAssert("button is plus", () => getAddOrRemoveButton(1).Icon.Equals(FontAwesome.Regular.PlusSquare)); + AddAssert("button is plus", () => getAddOrRemoveButton(1).Icon.Equals(FontAwesome.Solid.PlusSquare)); addClickAddOrRemoveButtonStep(1); AddAssert("collection contains beatmap", () => collectionManager.Collections[0].Beatmaps.Contains(Beatmap.Value.BeatmapInfo)); - AddAssert("button is minus", () => getAddOrRemoveButton(1).Icon.Equals(FontAwesome.Regular.MinusSquare)); + AddAssert("button is minus", () => getAddOrRemoveButton(1).Icon.Equals(FontAwesome.Solid.MinusSquare)); addClickAddOrRemoveButtonStep(1); AddAssert("collection does not contain beatmap", () => !collectionManager.Collections[0].Beatmaps.Contains(Beatmap.Value.BeatmapInfo)); - AddAssert("button is plus", () => getAddOrRemoveButton(1).Icon.Equals(FontAwesome.Regular.PlusSquare)); + AddAssert("button is plus", () => getAddOrRemoveButton(1).Icon.Equals(FontAwesome.Solid.PlusSquare)); } private void assertCollectionHeaderDisplays(string collectionName, bool shouldDisplay = true) diff --git a/osu.Game/Screens/Select/CollectionFilterDropdown.cs b/osu.Game/Screens/Select/CollectionFilterDropdown.cs index 2d30263d78..e2e8fbe0ea 100644 --- a/osu.Game/Screens/Select/CollectionFilterDropdown.cs +++ b/osu.Game/Screens/Select/CollectionFilterDropdown.cs @@ -211,12 +211,12 @@ namespace osu.Game.Screens.Select if (collectionBeatmaps.Contains(beatmap.Value.BeatmapInfo)) { - addOrRemoveButton.Icon = FontAwesome.Regular.MinusSquare; + addOrRemoveButton.Icon = FontAwesome.Solid.MinusSquare; addOrRemoveButton.IconColour = colours.Red; } else { - addOrRemoveButton.Icon = FontAwesome.Regular.PlusSquare; + addOrRemoveButton.Icon = FontAwesome.Solid.PlusSquare; addOrRemoveButton.IconColour = colours.Green; } } From ca4423af74bd57088baa5b96abdf66d0fd6fc5ba Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 8 Sep 2020 00:07:12 +0900 Subject: [PATCH 0937/1782] Fix tests --- .../Visual/SongSelect/TestSceneFilterControl.cs | 15 +++++++-------- .../Screens/Select/CollectionFilterDropdown.cs | 4 ++-- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs index 89a9536a04..c2dd652b3a 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs @@ -151,13 +151,12 @@ namespace osu.Game.Tests.Visual.SongSelect }); AddStep("add collection", () => collectionManager.Collections.Add(new BeatmapCollection { Name = { Value = "1" } })); - AddAssert("button disabled", () => !getAddOrRemoveButton(1).Enabled.Value); AddStep("select available beatmap", () => Beatmap.Value = beatmapManager.GetWorkingBeatmap(beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps[0])); AddAssert("button enabled", () => getAddOrRemoveButton(1).Enabled.Value); AddStep("set dummy beatmap", () => Beatmap.SetDefault()); - AddAssert("button enabled", () => !getAddOrRemoveButton(1).Enabled.Value); + AddAssert("button disabled", () => !getAddOrRemoveButton(1).Enabled.Value); } [Test] @@ -172,13 +171,13 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("select available beatmap", () => Beatmap.Value = beatmapManager.GetWorkingBeatmap(beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps[0])); AddStep("add collection", () => collectionManager.Collections.Add(new BeatmapCollection { Name = { Value = "1" } })); - AddAssert("button is plus", () => getAddOrRemoveButton(1).Icon.Equals(FontAwesome.Solid.PlusSquare)); + AddAssert("button is plus", () => getAddOrRemoveButton(1).Icon.Equals(FontAwesome.Solid.PlusCircle)); AddStep("add beatmap to collection", () => collectionManager.Collections[0].Beatmaps.Add(Beatmap.Value.BeatmapInfo)); - AddAssert("button is minus", () => getAddOrRemoveButton(1).Icon.Equals(FontAwesome.Solid.MinusSquare)); + AddAssert("button is minus", () => getAddOrRemoveButton(1).Icon.Equals(FontAwesome.Solid.MinusCircle)); AddStep("remove beatmap from collection", () => collectionManager.Collections[0].Beatmaps.Clear()); - AddAssert("button is plus", () => getAddOrRemoveButton(1).Icon.Equals(FontAwesome.Solid.PlusSquare)); + AddAssert("button is plus", () => getAddOrRemoveButton(1).Icon.Equals(FontAwesome.Solid.PlusCircle)); } [Test] @@ -193,15 +192,15 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("select available beatmap", () => Beatmap.Value = beatmapManager.GetWorkingBeatmap(beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps[0])); AddStep("add collection", () => collectionManager.Collections.Add(new BeatmapCollection { Name = { Value = "1" } })); - AddAssert("button is plus", () => getAddOrRemoveButton(1).Icon.Equals(FontAwesome.Solid.PlusSquare)); + AddAssert("button is plus", () => getAddOrRemoveButton(1).Icon.Equals(FontAwesome.Solid.PlusCircle)); addClickAddOrRemoveButtonStep(1); AddAssert("collection contains beatmap", () => collectionManager.Collections[0].Beatmaps.Contains(Beatmap.Value.BeatmapInfo)); - AddAssert("button is minus", () => getAddOrRemoveButton(1).Icon.Equals(FontAwesome.Solid.MinusSquare)); + AddAssert("button is minus", () => getAddOrRemoveButton(1).Icon.Equals(FontAwesome.Solid.MinusCircle)); addClickAddOrRemoveButtonStep(1); AddAssert("collection does not contain beatmap", () => !collectionManager.Collections[0].Beatmaps.Contains(Beatmap.Value.BeatmapInfo)); - AddAssert("button is plus", () => getAddOrRemoveButton(1).Icon.Equals(FontAwesome.Solid.PlusSquare)); + AddAssert("button is plus", () => getAddOrRemoveButton(1).Icon.Equals(FontAwesome.Solid.PlusCircle)); } private void assertCollectionHeaderDisplays(string collectionName, bool shouldDisplay = true) diff --git a/osu.Game/Screens/Select/CollectionFilterDropdown.cs b/osu.Game/Screens/Select/CollectionFilterDropdown.cs index e2e8fbe0ea..18caae9545 100644 --- a/osu.Game/Screens/Select/CollectionFilterDropdown.cs +++ b/osu.Game/Screens/Select/CollectionFilterDropdown.cs @@ -211,12 +211,12 @@ namespace osu.Game.Screens.Select if (collectionBeatmaps.Contains(beatmap.Value.BeatmapInfo)) { - addOrRemoveButton.Icon = FontAwesome.Solid.MinusSquare; + addOrRemoveButton.Icon = FontAwesome.Solid.MinusCircle; addOrRemoveButton.IconColour = colours.Red; } else { - addOrRemoveButton.Icon = FontAwesome.Solid.PlusSquare; + addOrRemoveButton.Icon = FontAwesome.Solid.PlusCircle; addOrRemoveButton.IconColour = colours.Green; } } From 01c0b61b203df181285cc052fb73509073ffefbb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 8 Sep 2020 01:52:31 +0900 Subject: [PATCH 0938/1782] Fix incorrect test names --- osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs index cef8105490..0702b02bb1 100644 --- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs @@ -761,7 +761,7 @@ namespace osu.Game.Tests.Beatmaps.IO [Test] public void TestCreateNewEmptyBeatmap() { - using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestUpdateBeatmapFile))) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestCreateNewEmptyBeatmap))) { try { @@ -788,7 +788,7 @@ namespace osu.Game.Tests.Beatmaps.IO [Test] public void TestCreateNewBeatmapWithObject() { - using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestUpdateBeatmapFile))) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestCreateNewBeatmapWithObject))) { try { From 2b62579488c76a5f3bc65455085178912081e459 Mon Sep 17 00:00:00 2001 From: Joehu Date: Mon, 7 Sep 2020 10:18:22 -0700 Subject: [PATCH 0939/1782] Lowercase one more toolbar tooltip --- osu.Game/Overlays/ChatOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/ChatOverlay.cs b/osu.Game/Overlays/ChatOverlay.cs index bcc2227be8..25a59e9b25 100644 --- a/osu.Game/Overlays/ChatOverlay.cs +++ b/osu.Game/Overlays/ChatOverlay.cs @@ -30,7 +30,7 @@ namespace osu.Game.Overlays { public string IconTexture => "Icons/Hexacons/messaging"; public string Title => "chat"; - public string Description => "Join the real-time discussion"; + public string Description => "join the real-time discussion"; private const float textbox_height = 60; private const float channel_selection_min_height = 0.3f; From 3a24cc1aa976e746e57509a5b0ddb4f71eed4340 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 7 Sep 2020 22:13:29 +0300 Subject: [PATCH 0940/1782] Implement PaginatedContainerHeader component --- .../TestScenePaginatedContainerHeader.cs | 77 +++++++++++ .../Overlays/Profile/Sections/CounterPill.cs | 14 +- .../Sections/PaginatedContainerHeader.cs | 129 ++++++++++++++++++ 3 files changed, 208 insertions(+), 12 deletions(-) create mode 100644 osu.Game.Tests/Visual/UserInterface/TestScenePaginatedContainerHeader.cs create mode 100644 osu.Game/Overlays/Profile/Sections/PaginatedContainerHeader.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestScenePaginatedContainerHeader.cs b/osu.Game.Tests/Visual/UserInterface/TestScenePaginatedContainerHeader.cs new file mode 100644 index 0000000000..114a3af1d9 --- /dev/null +++ b/osu.Game.Tests/Visual/UserInterface/TestScenePaginatedContainerHeader.cs @@ -0,0 +1,77 @@ +using NUnit.Framework; +using osu.Game.Overlays.Profile.Sections; +using osu.Framework.Testing; +using System.Linq; +using osu.Framework.Graphics; +using osu.Game.Overlays; +using osu.Framework.Allocation; + +namespace osu.Game.Tests.Visual.UserInterface +{ + public class TestScenePaginatedContainerHeader : OsuTestScene + { + [Cached] + private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Pink); + + private PaginatedContainerHeader header; + + [Test] + public void TestHiddenCounter() + { + AddStep("Create header", () => createHeader("Header with hidden counter", CounterVisibilityState.AlwaysHidden)); + AddAssert("Value is 0", () => header.Current.Value == 0); + AddAssert("Counter is hidden", () => header.ChildrenOfType().First().Alpha == 0); + AddStep("Set count 10", () => header.Current.Value = 10); + AddAssert("Value is 10", () => header.Current.Value == 10); + AddAssert("Counter is hidden", () => header.ChildrenOfType().First().Alpha == 0); + } + + [Test] + public void TestVisibleCounter() + { + AddStep("Create header", () => createHeader("Header with visible counter", CounterVisibilityState.AlwaysVisible)); + AddAssert("Value is 0", () => header.Current.Value == 0); + AddAssert("Counter is visible", () => header.ChildrenOfType().First().Alpha == 1); + AddStep("Set count 10", () => header.Current.Value = 10); + AddAssert("Value is 10", () => header.Current.Value == 10); + AddAssert("Counter is visible", () => header.ChildrenOfType().First().Alpha == 1); + } + + [Test] + public void TestVisibleWhenZeroCounter() + { + AddStep("Create header", () => createHeader("Header with visible when zero counter", CounterVisibilityState.VisibleWhenNonZero)); + AddAssert("Value is 0", () => header.Current.Value == 0); + AddAssert("Counter is visible", () => header.ChildrenOfType().First().Alpha == 1); + AddStep("Set count 10", () => header.Current.Value = 10); + AddAssert("Value is 10", () => header.Current.Value == 10); + AddAssert("Counter is hidden", () => header.ChildrenOfType().First().Alpha == 0); + AddStep("Set count 0", () => header.Current.Value = 0); + AddAssert("Value is 0", () => header.Current.Value == 0); + AddAssert("Counter is visible", () => header.ChildrenOfType().First().Alpha == 1); + } + + [Test] + public void TestInitialVisibility() + { + AddStep("Create header with 0 value", () => createHeader("Header with visible when zero counter", CounterVisibilityState.VisibleWhenNonZero, 0)); + AddAssert("Value is 0", () => header.Current.Value == 0); + AddAssert("Counter is visible", () => header.ChildrenOfType().First().Alpha == 1); + + AddStep("Create header with 1 value", () => createHeader("Header with visible when zero counter", CounterVisibilityState.VisibleWhenNonZero, 1)); + AddAssert("Value is 1", () => header.Current.Value == 1); + AddAssert("Counter is hidden", () => header.ChildrenOfType().First().Alpha == 0); + } + + private void createHeader(string text, CounterVisibilityState state, int initialValue = 0) + { + Clear(); + Add(header = new PaginatedContainerHeader(text, state) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Current = { Value = initialValue } + }); + } + } +} diff --git a/osu.Game/Overlays/Profile/Sections/CounterPill.cs b/osu.Game/Overlays/Profile/Sections/CounterPill.cs index 52adefa4ad..131df105ad 100644 --- a/osu.Game/Overlays/Profile/Sections/CounterPill.cs +++ b/osu.Game/Overlays/Profile/Sections/CounterPill.cs @@ -13,8 +13,6 @@ namespace osu.Game.Overlays.Profile.Sections { public class CounterPill : CircularContainer { - private const int duration = 200; - public readonly BindableInt Current = new BindableInt(); private OsuSpriteText counter; @@ -23,7 +21,6 @@ namespace osu.Game.Overlays.Profile.Sections private void load(OverlayColourProvider colourProvider) { AutoSizeAxes = Axes.Both; - Alpha = 0; Masking = true; Children = new Drawable[] { @@ -36,8 +33,8 @@ namespace osu.Game.Overlays.Profile.Sections { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Margin = new MarginPadding { Horizontal = 10, Vertical = 5 }, - Font = OsuFont.GetFont(weight: FontWeight.Bold), + Margin = new MarginPadding { Horizontal = 10, Bottom = 1 }, + Font = OsuFont.GetFont(size: 14 * 0.8f, weight: FontWeight.Bold), Colour = colourProvider.Foreground1 } }; @@ -51,14 +48,7 @@ namespace osu.Game.Overlays.Profile.Sections private void onCurrentChanged(ValueChangedEvent value) { - if (value.NewValue == 0) - { - this.FadeOut(duration, Easing.OutQuint); - return; - } - counter.Text = value.NewValue.ToString("N0"); - this.FadeIn(duration, Easing.OutQuint); } } } diff --git a/osu.Game/Overlays/Profile/Sections/PaginatedContainerHeader.cs b/osu.Game/Overlays/Profile/Sections/PaginatedContainerHeader.cs new file mode 100644 index 0000000000..e965b83682 --- /dev/null +++ b/osu.Game/Overlays/Profile/Sections/PaginatedContainerHeader.cs @@ -0,0 +1,129 @@ +using osu.Framework.Allocation; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics; +using osu.Framework.Graphics.UserInterface; +using osu.Framework.Bindables; +using System; +using osu.Framework.Graphics.Shapes; +using osuTK; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics; + +namespace osu.Game.Overlays.Profile.Sections +{ + public class PaginatedContainerHeader : CompositeDrawable, IHasCurrentValue + { + public Bindable Current + { + get => current; + set + { + if (value == null) + throw new ArgumentNullException(nameof(value)); + + current.UnbindBindings(); + current.BindTo(value); + } + } + + private readonly Bindable current = new Bindable(); + + private readonly string text; + private readonly CounterVisibilityState counterState; + + private CounterPill counterPill; + + public PaginatedContainerHeader(string text, CounterVisibilityState counterState) + { + this.text = text; + this.counterState = counterState; + } + + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider) + { + AutoSizeAxes = Axes.Both; + Padding = new MarginPadding { Vertical = 10 }; + InternalChildren = new Drawable[] + { + new CircularContainer + { + RelativeSizeAxes = Axes.Y, + Height = 0.65f, + Width = 3, + Masking = true, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreRight, + Margin = new MarginPadding { Right = 10 }, + Child = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colourProvider.Highlight1 + } + }, + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(10, 0), + Children = new Drawable[] + { + new OsuSpriteText + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Text = text, + Font = OsuFont.GetFont(size: 14, weight: FontWeight.Bold), + }, + counterPill = new CounterPill + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Alpha = getInitialCounterAlpha(), + Current = { BindTarget = current } + } + } + } + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + current.BindValueChanged(onCurrentChanged); + } + + private float getInitialCounterAlpha() + { + switch (counterState) + { + case CounterVisibilityState.AlwaysHidden: + return 0; + + case CounterVisibilityState.AlwaysVisible: + return 1; + + case CounterVisibilityState.VisibleWhenNonZero: + return current.Value == 0 ? 1 : 0; + + default: + throw new NotImplementedException($"{counterState} has an incorrect value."); + } + } + + private void onCurrentChanged(ValueChangedEvent countValue) + { + if (counterState == CounterVisibilityState.VisibleWhenNonZero) + { + counterPill.Alpha = countValue.NewValue == 0 ? 1 : 0; + } + } + } + + public enum CounterVisibilityState + { + AlwaysHidden, + AlwaysVisible, + VisibleWhenNonZero + } +} From 33f14fe7b7b7f4c10b629b582dfd6e9c6219e8f7 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 7 Sep 2020 22:19:19 +0300 Subject: [PATCH 0941/1782] Remove no longer needed test --- .../Online/TestSceneProfileCounterPill.cs | 40 ------------------- 1 file changed, 40 deletions(-) delete mode 100644 osu.Game.Tests/Visual/Online/TestSceneProfileCounterPill.cs diff --git a/osu.Game.Tests/Visual/Online/TestSceneProfileCounterPill.cs b/osu.Game.Tests/Visual/Online/TestSceneProfileCounterPill.cs deleted file mode 100644 index eaa989f0de..0000000000 --- a/osu.Game.Tests/Visual/Online/TestSceneProfileCounterPill.cs +++ /dev/null @@ -1,40 +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 NUnit.Framework; -using osu.Framework.Allocation; -using osu.Framework.Bindables; -using osu.Framework.Graphics; -using osu.Game.Overlays; -using osu.Game.Overlays.Profile.Sections; - -namespace osu.Game.Tests.Visual.Online -{ - public class TestSceneProfileCounterPill : OsuTestScene - { - [Cached] - private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Red); - - 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); - } - } -} From 1c55039994e80d827d5739610d2e6b4710c9b04f Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 7 Sep 2020 22:24:10 +0300 Subject: [PATCH 0942/1782] Remove old header from PaginatedContainer --- .../Beatmaps/PaginatedBeatmapContainer.cs | 14 ++------- .../Profile/Sections/BeatmapsSection.cs | 10 +++---- .../PaginatedMostPlayedBeatmapContainer.cs | 3 +- .../Profile/Sections/HistoricalSection.cs | 2 +- .../Kudosu/PaginatedKudosuHistoryContainer.cs | 4 +-- .../Profile/Sections/KudosuSection.cs | 2 +- .../Profile/Sections/PaginatedContainer.cs | 29 +------------------ .../Sections/Ranks/PaginatedScoreContainer.cs | 11 ++----- .../Overlays/Profile/Sections/RanksSection.cs | 4 +-- .../PaginatedRecentActivityContainer.cs | 4 +-- .../Profile/Sections/RecentSection.cs | 2 +- 11 files changed, 20 insertions(+), 65 deletions(-) diff --git a/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs b/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs index 191f3c908a..1936cb6188 100644 --- a/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs @@ -18,8 +18,8 @@ namespace osu.Game.Overlays.Profile.Sections.Beatmaps private const float panel_padding = 10f; private readonly BeatmapSetType type; - public PaginatedBeatmapContainer(BeatmapSetType type, Bindable user, string header, string missing = "None... yet.") - : base(user, header, missing) + public PaginatedBeatmapContainer(BeatmapSetType type, Bindable user) + : base(user, "None... yet.") { this.type = type; @@ -38,15 +38,5 @@ namespace osu.Game.Overlays.Profile.Sections.Beatmaps Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, }; - - protected override int GetCount(User user) => type switch - { - 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/BeatmapsSection.cs b/osu.Game/Overlays/Profile/Sections/BeatmapsSection.cs index 37f017277f..156696da16 100644 --- a/osu.Game/Overlays/Profile/Sections/BeatmapsSection.cs +++ b/osu.Game/Overlays/Profile/Sections/BeatmapsSection.cs @@ -16,11 +16,11 @@ namespace osu.Game.Overlays.Profile.Sections { Children = new[] { - 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"), + new PaginatedBeatmapContainer(BeatmapSetType.Favourite, User), + new PaginatedBeatmapContainer(BeatmapSetType.RankedAndApproved, User), + new PaginatedBeatmapContainer(BeatmapSetType.Loved, User), + new PaginatedBeatmapContainer(BeatmapSetType.Unranked, User), + new PaginatedBeatmapContainer(BeatmapSetType.Graveyard, User) }; } } diff --git a/osu.Game/Overlays/Profile/Sections/Historical/PaginatedMostPlayedBeatmapContainer.cs b/osu.Game/Overlays/Profile/Sections/Historical/PaginatedMostPlayedBeatmapContainer.cs index 6e6d6272c7..f16842f4ab 100644 --- a/osu.Game/Overlays/Profile/Sections/Historical/PaginatedMostPlayedBeatmapContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Historical/PaginatedMostPlayedBeatmapContainer.cs @@ -15,10 +15,9 @@ namespace osu.Game.Overlays.Profile.Sections.Historical public class PaginatedMostPlayedBeatmapContainer : PaginatedContainer { public PaginatedMostPlayedBeatmapContainer(Bindable user) - : base(user, "Most Played Beatmaps", "No records. :(") + : base(user, "No records. :(") { ItemsPerPage = 5; - ItemsContainer.Direction = FillDirection.Vertical; } diff --git a/osu.Game/Overlays/Profile/Sections/HistoricalSection.cs b/osu.Game/Overlays/Profile/Sections/HistoricalSection.cs index 4bdd25ee66..3d1a1efe6e 100644 --- a/osu.Game/Overlays/Profile/Sections/HistoricalSection.cs +++ b/osu.Game/Overlays/Profile/Sections/HistoricalSection.cs @@ -19,7 +19,7 @@ namespace osu.Game.Overlays.Profile.Sections Children = new Drawable[] { new PaginatedMostPlayedBeatmapContainer(User), - new PaginatedScoreContainer(ScoreType.Recent, User, "Recent Plays (24h)", "No performance records. :("), + new PaginatedScoreContainer(ScoreType.Recent, User, "No performance records. :("), }; } } diff --git a/osu.Game/Overlays/Profile/Sections/Kudosu/PaginatedKudosuHistoryContainer.cs b/osu.Game/Overlays/Profile/Sections/Kudosu/PaginatedKudosuHistoryContainer.cs index 0e7cfc37c0..923316d8c5 100644 --- a/osu.Game/Overlays/Profile/Sections/Kudosu/PaginatedKudosuHistoryContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Kudosu/PaginatedKudosuHistoryContainer.cs @@ -13,8 +13,8 @@ namespace osu.Game.Overlays.Profile.Sections.Kudosu { public class PaginatedKudosuHistoryContainer : PaginatedContainer { - public PaginatedKudosuHistoryContainer(Bindable user, string header, string missing) - : base(user, header, missing) + public PaginatedKudosuHistoryContainer(Bindable user, string missing) + : base(user, missing) { ItemsPerPage = 5; } diff --git a/osu.Game/Overlays/Profile/Sections/KudosuSection.cs b/osu.Game/Overlays/Profile/Sections/KudosuSection.cs index 9ccce7d837..7e75e7e3e4 100644 --- a/osu.Game/Overlays/Profile/Sections/KudosuSection.cs +++ b/osu.Game/Overlays/Profile/Sections/KudosuSection.cs @@ -17,7 +17,7 @@ namespace osu.Game.Overlays.Profile.Sections Children = new Drawable[] { new KudosuInfo(User), - new PaginatedKudosuHistoryContainer(User, null, @"This user hasn't received any kudosu!"), + new PaginatedKudosuHistoryContainer(User, "This user hasn't received any kudosu!"), }; } } diff --git a/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs b/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs index 9720469548..87472e77ea 100644 --- a/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs @@ -24,7 +24,6 @@ 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; } @@ -36,7 +35,7 @@ namespace osu.Game.Overlays.Profile.Sections protected readonly FillFlowContainer ItemsContainer; protected RulesetStore Rulesets; - protected PaginatedContainer(Bindable user, string header, string missing) + protected PaginatedContainer(Bindable user, string missing) { User.BindTo(user); @@ -46,29 +45,6 @@ namespace osu.Game.Overlays.Profile.Sections Children = new Drawable[] { - new FillFlowContainer - { - 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 { AutoSizeAxes = Axes.Y, @@ -112,7 +88,6 @@ namespace osu.Game.Overlays.Profile.Sections if (e.NewValue != null) { showMore(); - count.Value = GetCount(e.NewValue); } } @@ -146,8 +121,6 @@ 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 64494f9814..2cefe45e4a 100644 --- a/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs @@ -17,25 +17,18 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks { private readonly ScoreType type; - public PaginatedScoreContainer(ScoreType type, Bindable user, string header, string missing) - : base(user, header, missing) + public PaginatedScoreContainer(ScoreType type, Bindable user, string missing) + : base(user, missing) { this.type = type; ItemsPerPage = 5; - ItemsContainer.Direction = FillDirection.Vertical; } protected override APIRequest> CreateRequest() => new GetUserScoresRequest(User.Value.Id, type, VisiblePages++, ItemsPerPage); - protected override int GetCount(User user) => type switch - { - ScoreType.Firsts => user.ScoresFirstCount, - _ => 0 - }; - protected override Drawable CreateDrawableItem(APILegacyScoreInfo model) { switch (type) diff --git a/osu.Game/Overlays/Profile/Sections/RanksSection.cs b/osu.Game/Overlays/Profile/Sections/RanksSection.cs index dbdff3a273..40bd050955 100644 --- a/osu.Game/Overlays/Profile/Sections/RanksSection.cs +++ b/osu.Game/Overlays/Profile/Sections/RanksSection.cs @@ -16,8 +16,8 @@ namespace osu.Game.Overlays.Profile.Sections { Children = new[] { - new PaginatedScoreContainer(ScoreType.Best, User, "Best Performance", "No performance records. :("), - new PaginatedScoreContainer(ScoreType.Firsts, User, "First Place Ranks", "No awesome performance records yet. :("), + new PaginatedScoreContainer(ScoreType.Best, User, "No performance records. :("), + new PaginatedScoreContainer(ScoreType.Firsts, User, "No awesome performance records yet. :("), }; } } diff --git a/osu.Game/Overlays/Profile/Sections/Recent/PaginatedRecentActivityContainer.cs b/osu.Game/Overlays/Profile/Sections/Recent/PaginatedRecentActivityContainer.cs index a37f398272..4c828ef0c1 100644 --- a/osu.Game/Overlays/Profile/Sections/Recent/PaginatedRecentActivityContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Recent/PaginatedRecentActivityContainer.cs @@ -14,8 +14,8 @@ namespace osu.Game.Overlays.Profile.Sections.Recent { public class PaginatedRecentActivityContainer : PaginatedContainer { - public PaginatedRecentActivityContainer(Bindable user, string header, string missing) - : base(user, header, missing) + public PaginatedRecentActivityContainer(Bindable user, string missing) + : base(user, missing) { ItemsPerPage = 10; ItemsContainer.Spacing = new Vector2(0, 8); diff --git a/osu.Game/Overlays/Profile/Sections/RecentSection.cs b/osu.Game/Overlays/Profile/Sections/RecentSection.cs index 8fcc5cc7c0..0c118b80b5 100644 --- a/osu.Game/Overlays/Profile/Sections/RecentSection.cs +++ b/osu.Game/Overlays/Profile/Sections/RecentSection.cs @@ -15,7 +15,7 @@ namespace osu.Game.Overlays.Profile.Sections { Children = new[] { - new PaginatedRecentActivityContainer(User, null, @"This user hasn't done anything notable recently!"), + new PaginatedRecentActivityContainer(User, "This user hasn't done anything notable recently!"), }; } } From b7bd084296a90a50672b7355b41842a405ad79d1 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 7 Sep 2020 22:30:43 +0300 Subject: [PATCH 0943/1782] Remove missing text where not needed --- .../Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs | 2 +- osu.Game/Overlays/Profile/Sections/HistoricalSection.cs | 2 +- .../Sections/Kudosu/PaginatedKudosuHistoryContainer.cs | 4 ++-- osu.Game/Overlays/Profile/Sections/KudosuSection.cs | 2 +- osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs | 7 +++++-- .../Profile/Sections/Ranks/PaginatedScoreContainer.cs | 4 ++-- osu.Game/Overlays/Profile/Sections/RanksSection.cs | 4 ++-- .../Sections/Recent/PaginatedRecentActivityContainer.cs | 4 ++-- osu.Game/Overlays/Profile/Sections/RecentSection.cs | 2 +- 9 files changed, 17 insertions(+), 14 deletions(-) diff --git a/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs b/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs index 1936cb6188..1f99b75909 100644 --- a/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs @@ -19,7 +19,7 @@ namespace osu.Game.Overlays.Profile.Sections.Beatmaps private readonly BeatmapSetType type; public PaginatedBeatmapContainer(BeatmapSetType type, Bindable user) - : base(user, "None... yet.") + : base(user) { this.type = type; diff --git a/osu.Game/Overlays/Profile/Sections/HistoricalSection.cs b/osu.Game/Overlays/Profile/Sections/HistoricalSection.cs index 3d1a1efe6e..e021f16e5e 100644 --- a/osu.Game/Overlays/Profile/Sections/HistoricalSection.cs +++ b/osu.Game/Overlays/Profile/Sections/HistoricalSection.cs @@ -19,7 +19,7 @@ namespace osu.Game.Overlays.Profile.Sections Children = new Drawable[] { new PaginatedMostPlayedBeatmapContainer(User), - new PaginatedScoreContainer(ScoreType.Recent, User, "No performance records. :("), + new PaginatedScoreContainer(ScoreType.Recent, User), }; } } diff --git a/osu.Game/Overlays/Profile/Sections/Kudosu/PaginatedKudosuHistoryContainer.cs b/osu.Game/Overlays/Profile/Sections/Kudosu/PaginatedKudosuHistoryContainer.cs index 923316d8c5..c823053c4b 100644 --- a/osu.Game/Overlays/Profile/Sections/Kudosu/PaginatedKudosuHistoryContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Kudosu/PaginatedKudosuHistoryContainer.cs @@ -13,8 +13,8 @@ namespace osu.Game.Overlays.Profile.Sections.Kudosu { public class PaginatedKudosuHistoryContainer : PaginatedContainer { - public PaginatedKudosuHistoryContainer(Bindable user, string missing) - : base(user, missing) + public PaginatedKudosuHistoryContainer(Bindable user) + : base(user, "This user hasn't received any kudosu!") { ItemsPerPage = 5; } diff --git a/osu.Game/Overlays/Profile/Sections/KudosuSection.cs b/osu.Game/Overlays/Profile/Sections/KudosuSection.cs index 7e75e7e3e4..a9e9952257 100644 --- a/osu.Game/Overlays/Profile/Sections/KudosuSection.cs +++ b/osu.Game/Overlays/Profile/Sections/KudosuSection.cs @@ -17,7 +17,7 @@ namespace osu.Game.Overlays.Profile.Sections Children = new Drawable[] { new KudosuInfo(User), - new PaginatedKudosuHistoryContainer(User, "This user hasn't received any kudosu!"), + new PaginatedKudosuHistoryContainer(User), }; } } diff --git a/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs b/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs index 87472e77ea..9ddca48298 100644 --- a/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs @@ -35,7 +35,7 @@ namespace osu.Game.Overlays.Profile.Sections protected readonly FillFlowContainer ItemsContainer; protected RulesetStore Rulesets; - protected PaginatedContainer(Bindable user, string missing) + protected PaginatedContainer(Bindable user, string missing = "") { User.BindTo(user); @@ -107,7 +107,10 @@ namespace osu.Game.Overlays.Profile.Sections { moreButton.Hide(); moreButton.IsLoading = false; - missingText.Show(); + + if (!string.IsNullOrEmpty(missingText.Text)) + missingText.Show(); + return; } diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs b/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs index 2cefe45e4a..fbf92fd2e6 100644 --- a/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs @@ -17,8 +17,8 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks { private readonly ScoreType type; - public PaginatedScoreContainer(ScoreType type, Bindable user, string missing) - : base(user, missing) + public PaginatedScoreContainer(ScoreType type, Bindable user) + : base(user) { this.type = type; diff --git a/osu.Game/Overlays/Profile/Sections/RanksSection.cs b/osu.Game/Overlays/Profile/Sections/RanksSection.cs index 40bd050955..18bf4f31d8 100644 --- a/osu.Game/Overlays/Profile/Sections/RanksSection.cs +++ b/osu.Game/Overlays/Profile/Sections/RanksSection.cs @@ -16,8 +16,8 @@ namespace osu.Game.Overlays.Profile.Sections { Children = new[] { - new PaginatedScoreContainer(ScoreType.Best, User, "No performance records. :("), - new PaginatedScoreContainer(ScoreType.Firsts, User, "No awesome performance records yet. :("), + new PaginatedScoreContainer(ScoreType.Best, User), + new PaginatedScoreContainer(ScoreType.Firsts, User), }; } } diff --git a/osu.Game/Overlays/Profile/Sections/Recent/PaginatedRecentActivityContainer.cs b/osu.Game/Overlays/Profile/Sections/Recent/PaginatedRecentActivityContainer.cs index 4c828ef0c1..a2f844503f 100644 --- a/osu.Game/Overlays/Profile/Sections/Recent/PaginatedRecentActivityContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Recent/PaginatedRecentActivityContainer.cs @@ -14,8 +14,8 @@ namespace osu.Game.Overlays.Profile.Sections.Recent { public class PaginatedRecentActivityContainer : PaginatedContainer { - public PaginatedRecentActivityContainer(Bindable user, string missing) - : base(user, missing) + public PaginatedRecentActivityContainer(Bindable user) + : base(user) { ItemsPerPage = 10; ItemsContainer.Spacing = new Vector2(0, 8); diff --git a/osu.Game/Overlays/Profile/Sections/RecentSection.cs b/osu.Game/Overlays/Profile/Sections/RecentSection.cs index 0c118b80b5..1e6cfcc9fd 100644 --- a/osu.Game/Overlays/Profile/Sections/RecentSection.cs +++ b/osu.Game/Overlays/Profile/Sections/RecentSection.cs @@ -15,7 +15,7 @@ namespace osu.Game.Overlays.Profile.Sections { Children = new[] { - new PaginatedRecentActivityContainer(User, "This user hasn't done anything notable recently!"), + new PaginatedRecentActivityContainer(User), }; } } From e39609d3ca6bf4b55009def24f13997c5ea7efc4 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 7 Sep 2020 23:08:50 +0300 Subject: [PATCH 0944/1782] Implement PaginatedContainerWithHeader component --- .../TestScenePaginatedContainerHeader.cs | 11 ++++-- .../Beatmaps/PaginatedBeatmapContainer.cs | 37 +++++++++++++++++-- .../Profile/Sections/BeatmapsSection.cs | 10 ++--- .../PaginatedMostPlayedBeatmapContainer.cs | 10 ++++- .../Profile/Sections/HistoricalSection.cs | 2 +- .../Profile/Sections/PaginatedContainer.cs | 33 +++++++++++------ .../Sections/PaginatedContainerHeader.cs | 11 ++++-- .../Sections/PaginatedContainerWithHeader.cs | 34 +++++++++++++++++ .../Sections/Ranks/PaginatedScoreContainer.cs | 24 ++++++++++-- .../Overlays/Profile/Sections/RanksSection.cs | 4 +- .../PaginatedRecentActivityContainer.cs | 8 +++- 11 files changed, 147 insertions(+), 37 deletions(-) create mode 100644 osu.Game/Overlays/Profile/Sections/PaginatedContainerWithHeader.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestScenePaginatedContainerHeader.cs b/osu.Game.Tests/Visual/UserInterface/TestScenePaginatedContainerHeader.cs index 114a3af1d9..2e9f919cfd 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestScenePaginatedContainerHeader.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestScenePaginatedContainerHeader.cs @@ -1,4 +1,7 @@ -using NUnit.Framework; +// 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.Overlays.Profile.Sections; using osu.Framework.Testing; using System.Linq; @@ -40,7 +43,7 @@ namespace osu.Game.Tests.Visual.UserInterface [Test] public void TestVisibleWhenZeroCounter() { - AddStep("Create header", () => createHeader("Header with visible when zero counter", CounterVisibilityState.VisibleWhenNonZero)); + AddStep("Create header", () => createHeader("Header with visible when zero counter", CounterVisibilityState.VisibleWhenZero)); AddAssert("Value is 0", () => header.Current.Value == 0); AddAssert("Counter is visible", () => header.ChildrenOfType().First().Alpha == 1); AddStep("Set count 10", () => header.Current.Value = 10); @@ -54,11 +57,11 @@ namespace osu.Game.Tests.Visual.UserInterface [Test] public void TestInitialVisibility() { - AddStep("Create header with 0 value", () => createHeader("Header with visible when zero counter", CounterVisibilityState.VisibleWhenNonZero, 0)); + AddStep("Create header with 0 value", () => createHeader("Header with visible when zero counter", CounterVisibilityState.VisibleWhenZero, 0)); AddAssert("Value is 0", () => header.Current.Value == 0); AddAssert("Counter is visible", () => header.ChildrenOfType().First().Alpha == 1); - AddStep("Create header with 1 value", () => createHeader("Header with visible when zero counter", CounterVisibilityState.VisibleWhenNonZero, 1)); + AddStep("Create header with 1 value", () => createHeader("Header with visible when zero counter", CounterVisibilityState.VisibleWhenZero, 1)); AddAssert("Value is 1", () => header.Current.Value == 1); AddAssert("Counter is hidden", () => header.ChildrenOfType().First().Alpha == 0); } diff --git a/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs b/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs index 1f99b75909..ea700a812f 100644 --- a/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Online.API; @@ -13,21 +14,49 @@ using osuTK; namespace osu.Game.Overlays.Profile.Sections.Beatmaps { - public class PaginatedBeatmapContainer : PaginatedContainer + public class PaginatedBeatmapContainer : PaginatedContainerWithHeader { private const float panel_padding = 10f; private readonly BeatmapSetType type; - public PaginatedBeatmapContainer(BeatmapSetType type, Bindable user) - : base(user) + public PaginatedBeatmapContainer(BeatmapSetType type, Bindable user, string headerText) + : base(user, headerText, CounterVisibilityState.AlwaysVisible) { this.type = type; - ItemsPerPage = 6; + } + [BackgroundDependencyLoader] + private void load() + { ItemsContainer.Spacing = new Vector2(panel_padding); } + protected override int GetCount(User user) + { + switch (type) + { + 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; + + default: + return 0; + } + } + + protected override APIRequest> CreateRequest() => new GetUserBeatmapsRequest(User.Value.Id, type, VisiblePages++, ItemsPerPage); diff --git a/osu.Game/Overlays/Profile/Sections/BeatmapsSection.cs b/osu.Game/Overlays/Profile/Sections/BeatmapsSection.cs index 156696da16..c283de42f3 100644 --- a/osu.Game/Overlays/Profile/Sections/BeatmapsSection.cs +++ b/osu.Game/Overlays/Profile/Sections/BeatmapsSection.cs @@ -16,11 +16,11 @@ namespace osu.Game.Overlays.Profile.Sections { Children = new[] { - new PaginatedBeatmapContainer(BeatmapSetType.Favourite, User), - new PaginatedBeatmapContainer(BeatmapSetType.RankedAndApproved, User), - new PaginatedBeatmapContainer(BeatmapSetType.Loved, User), - new PaginatedBeatmapContainer(BeatmapSetType.Unranked, User), - new PaginatedBeatmapContainer(BeatmapSetType.Graveyard, User) + 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") }; } } diff --git a/osu.Game/Overlays/Profile/Sections/Historical/PaginatedMostPlayedBeatmapContainer.cs b/osu.Game/Overlays/Profile/Sections/Historical/PaginatedMostPlayedBeatmapContainer.cs index f16842f4ab..ad35ea1460 100644 --- a/osu.Game/Overlays/Profile/Sections/Historical/PaginatedMostPlayedBeatmapContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Historical/PaginatedMostPlayedBeatmapContainer.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -12,12 +13,17 @@ using osu.Game.Users; namespace osu.Game.Overlays.Profile.Sections.Historical { - public class PaginatedMostPlayedBeatmapContainer : PaginatedContainer + public class PaginatedMostPlayedBeatmapContainer : PaginatedContainerWithHeader { public PaginatedMostPlayedBeatmapContainer(Bindable user) - : base(user, "No records. :(") + : base(user, "Most Played Beatmaps", CounterVisibilityState.AlwaysHidden, "No records. :(") { ItemsPerPage = 5; + } + + [BackgroundDependencyLoader] + private void load() + { ItemsContainer.Direction = FillDirection.Vertical; } diff --git a/osu.Game/Overlays/Profile/Sections/HistoricalSection.cs b/osu.Game/Overlays/Profile/Sections/HistoricalSection.cs index e021f16e5e..bfc47bd88c 100644 --- a/osu.Game/Overlays/Profile/Sections/HistoricalSection.cs +++ b/osu.Game/Overlays/Profile/Sections/HistoricalSection.cs @@ -19,7 +19,7 @@ namespace osu.Game.Overlays.Profile.Sections Children = new Drawable[] { new PaginatedMostPlayedBeatmapContainer(User), - new PaginatedScoreContainer(ScoreType.Recent, User), + new PaginatedScoreContainer(ScoreType.Recent, User, "Recent Plays (24h)", CounterVisibilityState.VisibleWhenZero), }; } } diff --git a/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs b/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs index 9ddca48298..1bc8ffe671 100644 --- a/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs @@ -20,11 +20,6 @@ namespace osu.Game.Overlays.Profile.Sections { public abstract class PaginatedContainer : FillFlowContainer { - private readonly ShowMoreButton moreButton; - private readonly OsuSpriteText missingText; - private APIRequest> retrievalRequest; - private CancellationTokenSource loadCancellation; - [Resolved] private IAPIProvider api { get; set; } @@ -32,19 +27,32 @@ namespace osu.Game.Overlays.Profile.Sections protected int ItemsPerPage; protected readonly Bindable User = new Bindable(); - protected readonly FillFlowContainer ItemsContainer; + protected FillFlowContainer ItemsContainer; protected RulesetStore Rulesets; + private APIRequest> retrievalRequest; + private CancellationTokenSource loadCancellation; + + private readonly string missing; + private ShowMoreButton moreButton; + private OsuSpriteText missingText; + protected PaginatedContainer(Bindable user, string missing = "") { + this.missing = missing; User.BindTo(user); + } + [BackgroundDependencyLoader] + private void load(RulesetStore rulesets) + { RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; Direction = FillDirection.Vertical; Children = new Drawable[] { + CreateHeaderContent, ItemsContainer = new FillFlowContainer { AutoSizeAxes = Axes.Y, @@ -66,11 +74,7 @@ namespace osu.Game.Overlays.Profile.Sections Alpha = 0, }, }; - } - [BackgroundDependencyLoader] - private void load(RulesetStore rulesets) - { Rulesets = rulesets; User.ValueChanged += onUserChanged; @@ -87,7 +91,7 @@ namespace osu.Game.Overlays.Profile.Sections if (e.NewValue != null) { - showMore(); + OnUserChanged(e.NewValue); } } @@ -124,6 +128,13 @@ namespace osu.Game.Overlays.Profile.Sections }, loadCancellation.Token); }); + protected virtual void OnUserChanged(User user) + { + showMore(); + } + + protected virtual Drawable CreateHeaderContent => Empty(); + protected abstract APIRequest> CreateRequest(); protected abstract Drawable CreateDrawableItem(TModel model); diff --git a/osu.Game/Overlays/Profile/Sections/PaginatedContainerHeader.cs b/osu.Game/Overlays/Profile/Sections/PaginatedContainerHeader.cs index e965b83682..4779b44eb0 100644 --- a/osu.Game/Overlays/Profile/Sections/PaginatedContainerHeader.cs +++ b/osu.Game/Overlays/Profile/Sections/PaginatedContainerHeader.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.Containers; using osu.Framework.Graphics; using osu.Framework.Graphics.UserInterface; @@ -103,7 +106,7 @@ namespace osu.Game.Overlays.Profile.Sections case CounterVisibilityState.AlwaysVisible: return 1; - case CounterVisibilityState.VisibleWhenNonZero: + case CounterVisibilityState.VisibleWhenZero: return current.Value == 0 ? 1 : 0; default: @@ -113,7 +116,7 @@ namespace osu.Game.Overlays.Profile.Sections private void onCurrentChanged(ValueChangedEvent countValue) { - if (counterState == CounterVisibilityState.VisibleWhenNonZero) + if (counterState == CounterVisibilityState.VisibleWhenZero) { counterPill.Alpha = countValue.NewValue == 0 ? 1 : 0; } @@ -124,6 +127,6 @@ namespace osu.Game.Overlays.Profile.Sections { AlwaysHidden, AlwaysVisible, - VisibleWhenNonZero + VisibleWhenZero } } diff --git a/osu.Game/Overlays/Profile/Sections/PaginatedContainerWithHeader.cs b/osu.Game/Overlays/Profile/Sections/PaginatedContainerWithHeader.cs new file mode 100644 index 0000000000..cf88b290ae --- /dev/null +++ b/osu.Game/Overlays/Profile/Sections/PaginatedContainerWithHeader.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.Bindables; +using osu.Framework.Graphics; +using osu.Game.Users; + +namespace osu.Game.Overlays.Profile.Sections +{ + public abstract class PaginatedContainerWithHeader : PaginatedContainer + { + private readonly string headerText; + private readonly CounterVisibilityState counterVisibilityState; + + private PaginatedContainerHeader header; + + public PaginatedContainerWithHeader(Bindable user, string headerText, CounterVisibilityState counterVisibilityState, string missing = "") + : base(user, missing) + { + this.headerText = headerText; + this.counterVisibilityState = counterVisibilityState; + } + + protected override Drawable CreateHeaderContent => header = new PaginatedContainerHeader(headerText, counterVisibilityState); + + protected override void OnUserChanged(User user) + { + base.OnUserChanged(user); + header.Current.Value = GetCount(user); + } + + protected virtual int GetCount(User user) => 0; + } +} diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs b/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs index fbf92fd2e6..f1cf3e632c 100644 --- a/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs @@ -10,22 +10,40 @@ using osu.Framework.Graphics; using osu.Game.Online.API.Requests.Responses; using System.Collections.Generic; using osu.Game.Online.API; +using osu.Framework.Allocation; namespace osu.Game.Overlays.Profile.Sections.Ranks { - public class PaginatedScoreContainer : PaginatedContainer + public class PaginatedScoreContainer : PaginatedContainerWithHeader { private readonly ScoreType type; - public PaginatedScoreContainer(ScoreType type, Bindable user) - : base(user) + public PaginatedScoreContainer(ScoreType type, Bindable user, string headerText, CounterVisibilityState counterVisibilityState, string missingText = "") + : base(user, headerText, counterVisibilityState, missingText) { this.type = type; ItemsPerPage = 5; + } + + [BackgroundDependencyLoader] + private void load() + { ItemsContainer.Direction = FillDirection.Vertical; } + protected override int GetCount(User user) + { + switch (type) + { + case ScoreType.Firsts: + return user.ScoresFirstCount; + + default: + return 0; + } + } + protected override APIRequest> CreateRequest() => new GetUserScoresRequest(User.Value.Id, type, VisiblePages++, ItemsPerPage); diff --git a/osu.Game/Overlays/Profile/Sections/RanksSection.cs b/osu.Game/Overlays/Profile/Sections/RanksSection.cs index 18bf4f31d8..e41e414893 100644 --- a/osu.Game/Overlays/Profile/Sections/RanksSection.cs +++ b/osu.Game/Overlays/Profile/Sections/RanksSection.cs @@ -16,8 +16,8 @@ namespace osu.Game.Overlays.Profile.Sections { Children = new[] { - new PaginatedScoreContainer(ScoreType.Best, User), - new PaginatedScoreContainer(ScoreType.Firsts, User), + new PaginatedScoreContainer(ScoreType.Best, User, "Best Performance", CounterVisibilityState.AlwaysHidden, "No performance records. :("), + new PaginatedScoreContainer(ScoreType.Firsts, User, "First Place Ranks", CounterVisibilityState.AlwaysVisible) }; } } diff --git a/osu.Game/Overlays/Profile/Sections/Recent/PaginatedRecentActivityContainer.cs b/osu.Game/Overlays/Profile/Sections/Recent/PaginatedRecentActivityContainer.cs index a2f844503f..adfe31109b 100644 --- a/osu.Game/Overlays/Profile/Sections/Recent/PaginatedRecentActivityContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Recent/PaginatedRecentActivityContainer.cs @@ -9,15 +9,21 @@ using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.API; using System.Collections.Generic; using osuTK; +using osu.Framework.Allocation; namespace osu.Game.Overlays.Profile.Sections.Recent { public class PaginatedRecentActivityContainer : PaginatedContainer { public PaginatedRecentActivityContainer(Bindable user) - : base(user) + : base(user, "This user hasn't done anything notable recently!") { ItemsPerPage = 10; + } + + [BackgroundDependencyLoader] + private void load() + { ItemsContainer.Spacing = new Vector2(0, 8); } From c72a192cb5f59c5e85ac7b1ec381f16ab760a0f9 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 7 Sep 2020 23:33:04 +0300 Subject: [PATCH 0945/1782] Fix recent plays counter is always zero --- .../Overlays/Profile/Sections/PaginatedContainer.cs | 6 ++++++ .../Profile/Sections/PaginatedContainerWithHeader.cs | 12 ++++++++++++ 2 files changed, 18 insertions(+) diff --git a/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs b/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs index 1bc8ffe671..9693c8b5f3 100644 --- a/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs @@ -107,6 +107,8 @@ namespace osu.Game.Overlays.Profile.Sections protected virtual void UpdateItems(List items) => Schedule(() => { + OnItemsReceived(items); + if (!items.Any() && VisiblePages == 1) { moreButton.Hide(); @@ -133,6 +135,10 @@ namespace osu.Game.Overlays.Profile.Sections showMore(); } + protected virtual void OnItemsReceived(List items) + { + } + protected virtual Drawable CreateHeaderContent => Empty(); protected abstract APIRequest> CreateRequest(); diff --git a/osu.Game/Overlays/Profile/Sections/PaginatedContainerWithHeader.cs b/osu.Game/Overlays/Profile/Sections/PaginatedContainerWithHeader.cs index cf88b290ae..f27ea7a626 100644 --- a/osu.Game/Overlays/Profile/Sections/PaginatedContainerWithHeader.cs +++ b/osu.Game/Overlays/Profile/Sections/PaginatedContainerWithHeader.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.Bindables; using osu.Framework.Graphics; using osu.Game.Users; @@ -29,6 +30,17 @@ namespace osu.Game.Overlays.Profile.Sections header.Current.Value = GetCount(user); } + protected override void OnItemsReceived(List items) + { + base.OnItemsReceived(items); + + if (counterVisibilityState == CounterVisibilityState.VisibleWhenZero) + { + header.Current.Value = items.Count; + header.Current.TriggerChange(); + } + } + protected virtual int GetCount(User user) => 0; } } From f88b2509f862062383e32bd1a78c585fcaa8fc09 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 7 Sep 2020 23:43:26 +0300 Subject: [PATCH 0946/1782] Fix ProfileSection header margin is too small --- osu.Game/Overlays/Profile/ProfileSection.cs | 2 +- .../Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs | 1 - osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs | 2 +- .../Overlays/Profile/Sections/PaginatedContainerWithHeader.cs | 2 +- 4 files changed, 3 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/Profile/ProfileSection.cs b/osu.Game/Overlays/Profile/ProfileSection.cs index 2e19ae4b64..21f7921da6 100644 --- a/osu.Game/Overlays/Profile/ProfileSection.cs +++ b/osu.Game/Overlays/Profile/ProfileSection.cs @@ -59,7 +59,7 @@ namespace osu.Game.Overlays.Profile { Horizontal = UserProfileOverlay.CONTENT_X_MARGIN, Top = 15, - Bottom = 10, + Bottom = 20, }, Children = new Drawable[] { diff --git a/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs b/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs index ea700a812f..d7c72131ea 100644 --- a/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs @@ -56,7 +56,6 @@ namespace osu.Game.Overlays.Profile.Sections.Beatmaps } } - protected override APIRequest> CreateRequest() => new GetUserBeatmapsRequest(User.Value.Id, type, VisiblePages++, ItemsPerPage); diff --git a/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs b/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs index 9693c8b5f3..c22e5660e6 100644 --- a/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs @@ -50,7 +50,7 @@ namespace osu.Game.Overlays.Profile.Sections AutoSizeAxes = Axes.Y; Direction = FillDirection.Vertical; - Children = new Drawable[] + Children = new[] { CreateHeaderContent, ItemsContainer = new FillFlowContainer diff --git a/osu.Game/Overlays/Profile/Sections/PaginatedContainerWithHeader.cs b/osu.Game/Overlays/Profile/Sections/PaginatedContainerWithHeader.cs index f27ea7a626..9d8ed89053 100644 --- a/osu.Game/Overlays/Profile/Sections/PaginatedContainerWithHeader.cs +++ b/osu.Game/Overlays/Profile/Sections/PaginatedContainerWithHeader.cs @@ -15,7 +15,7 @@ namespace osu.Game.Overlays.Profile.Sections private PaginatedContainerHeader header; - public PaginatedContainerWithHeader(Bindable user, string headerText, CounterVisibilityState counterVisibilityState, string missing = "") + protected PaginatedContainerWithHeader(Bindable user, string headerText, CounterVisibilityState counterVisibilityState, string missing = "") : base(user, missing) { this.headerText = headerText; From 1bc41bcfd7d61fb44e3a6fed3a1171664a1f658b Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 8 Sep 2020 00:04:14 +0300 Subject: [PATCH 0947/1782] Move scores counter logic to a better place --- .../Sections/PaginatedContainerWithHeader.cs | 18 +++--------------- .../Sections/Ranks/PaginatedScoreContainer.cs | 13 +++++++++++++ 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/osu.Game/Overlays/Profile/Sections/PaginatedContainerWithHeader.cs b/osu.Game/Overlays/Profile/Sections/PaginatedContainerWithHeader.cs index 9d8ed89053..32c589e342 100644 --- a/osu.Game/Overlays/Profile/Sections/PaginatedContainerWithHeader.cs +++ b/osu.Game/Overlays/Profile/Sections/PaginatedContainerWithHeader.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.Bindables; using osu.Framework.Graphics; using osu.Game.Users; @@ -13,7 +12,7 @@ namespace osu.Game.Overlays.Profile.Sections private readonly string headerText; private readonly CounterVisibilityState counterVisibilityState; - private PaginatedContainerHeader header; + protected PaginatedContainerHeader Header; protected PaginatedContainerWithHeader(Bindable user, string headerText, CounterVisibilityState counterVisibilityState, string missing = "") : base(user, missing) @@ -22,23 +21,12 @@ namespace osu.Game.Overlays.Profile.Sections this.counterVisibilityState = counterVisibilityState; } - protected override Drawable CreateHeaderContent => header = new PaginatedContainerHeader(headerText, counterVisibilityState); + protected override Drawable CreateHeaderContent => Header = new PaginatedContainerHeader(headerText, counterVisibilityState); protected override void OnUserChanged(User user) { base.OnUserChanged(user); - header.Current.Value = GetCount(user); - } - - protected override void OnItemsReceived(List items) - { - base.OnItemsReceived(items); - - if (counterVisibilityState == CounterVisibilityState.VisibleWhenZero) - { - header.Current.Value = items.Count; - header.Current.TriggerChange(); - } + Header.Current.Value = GetCount(user); } protected virtual int GetCount(User user) => 0; diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs b/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs index f1cf3e632c..0b2bddabbc 100644 --- a/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs @@ -44,6 +44,19 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks } } + protected override void OnItemsReceived(List items) + { + base.OnItemsReceived(items); + + if (type == ScoreType.Recent) + { + var count = items.Count; + + Header.Current.Value = count == 0 ? 0 : -1; + Header.Current.TriggerChange(); + } + } + protected override APIRequest> CreateRequest() => new GetUserScoresRequest(User.Value.Id, type, VisiblePages++, ItemsPerPage); From 5268eee0fb7a51866d78eec067a4d5e2e069f264 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 8 Sep 2020 11:31:42 +0900 Subject: [PATCH 0948/1782] Avoid requiring sending the calling method for CleanRunHeadlessGameHost --- .../Beatmaps/IO/ImportBeatmapTest.cs | 38 +++++++++---------- osu.Game.Tests/Scores/IO/ImportScoreTest.cs | 10 ++--- osu.Game/Tests/CleanRunHeadlessGameHost.cs | 12 +++++- 3 files changed, 34 insertions(+), 26 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs index 0702b02bb1..dd3dba1274 100644 --- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs @@ -34,7 +34,7 @@ namespace osu.Game.Tests.Beatmaps.IO public async Task TestImportWhenClosed() { // unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here. - using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportWhenClosed))) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) { try { @@ -51,7 +51,7 @@ namespace osu.Game.Tests.Beatmaps.IO public async Task TestImportThenDelete() { // unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here. - using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportThenDelete))) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) { try { @@ -72,7 +72,7 @@ namespace osu.Game.Tests.Beatmaps.IO public async Task TestImportThenImport() { // unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here. - using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportThenImport))) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) { try { @@ -98,7 +98,7 @@ namespace osu.Game.Tests.Beatmaps.IO [Test] public async Task TestImportThenImportWithReZip() { - using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportThenImportWithReZip))) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) { try { @@ -156,7 +156,7 @@ namespace osu.Game.Tests.Beatmaps.IO [Test] public async Task TestImportThenImportWithChangedFile() { - using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportThenImportWithChangedFile))) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) { try { @@ -207,7 +207,7 @@ namespace osu.Game.Tests.Beatmaps.IO [Test] public async Task TestImportThenImportWithDifferentFilename() { - using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportThenImportWithDifferentFilename))) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) { try { @@ -259,7 +259,7 @@ namespace osu.Game.Tests.Beatmaps.IO 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(nameof(TestImportCorruptThenImport))) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) { try { @@ -301,7 +301,7 @@ namespace osu.Game.Tests.Beatmaps.IO public async Task TestRollbackOnFailure() { // unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here. - using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestRollbackOnFailure))) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) { try { @@ -378,7 +378,7 @@ namespace osu.Game.Tests.Beatmaps.IO public async Task TestImportThenDeleteThenImport() { // unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here. - using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportThenDeleteThenImport))) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) { try { @@ -406,7 +406,7 @@ namespace osu.Game.Tests.Beatmaps.IO public async Task TestImportThenDeleteThenImportWithOnlineIDMismatch(bool set) { // unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here. - using (HeadlessGameHost host = new CleanRunHeadlessGameHost($"{nameof(TestImportThenDeleteThenImportWithOnlineIDMismatch)}-{set}")) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost(set.ToString())) { try { @@ -440,7 +440,7 @@ namespace osu.Game.Tests.Beatmaps.IO public async Task TestImportWithDuplicateBeatmapIDs() { // unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here. - using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportWithDuplicateBeatmapIDs))) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) { try { @@ -526,7 +526,7 @@ namespace osu.Game.Tests.Beatmaps.IO [Test] public async Task TestImportWhenFileOpen() { - using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportWhenFileOpen))) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) { try { @@ -548,7 +548,7 @@ namespace osu.Game.Tests.Beatmaps.IO [Test] public async Task TestImportWithDuplicateHashes() { - using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportWithDuplicateHashes))) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) { try { @@ -590,7 +590,7 @@ namespace osu.Game.Tests.Beatmaps.IO [Test] public async Task TestImportNestedStructure() { - using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportNestedStructure))) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) { try { @@ -635,7 +635,7 @@ namespace osu.Game.Tests.Beatmaps.IO [Test] public async Task TestImportWithIgnoredDirectoryInArchive() { - using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportWithIgnoredDirectoryInArchive))) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) { try { @@ -689,7 +689,7 @@ namespace osu.Game.Tests.Beatmaps.IO [Test] public async Task TestUpdateBeatmapInfo() { - using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestUpdateBeatmapInfo))) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) { try { @@ -719,7 +719,7 @@ namespace osu.Game.Tests.Beatmaps.IO [Test] public async Task TestUpdateBeatmapFile() { - using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestUpdateBeatmapFile))) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) { try { @@ -761,7 +761,7 @@ namespace osu.Game.Tests.Beatmaps.IO [Test] public void TestCreateNewEmptyBeatmap() { - using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestCreateNewEmptyBeatmap))) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) { try { @@ -788,7 +788,7 @@ namespace osu.Game.Tests.Beatmaps.IO [Test] public void TestCreateNewBeatmapWithObject() { - using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestCreateNewBeatmapWithObject))) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) { try { diff --git a/osu.Game.Tests/Scores/IO/ImportScoreTest.cs b/osu.Game.Tests/Scores/IO/ImportScoreTest.cs index 57f0d7e957..a4d20714fa 100644 --- a/osu.Game.Tests/Scores/IO/ImportScoreTest.cs +++ b/osu.Game.Tests/Scores/IO/ImportScoreTest.cs @@ -27,7 +27,7 @@ namespace osu.Game.Tests.Scores.IO [Test] public async Task TestBasicImport() { - using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestBasicImport")) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) { try { @@ -66,7 +66,7 @@ namespace osu.Game.Tests.Scores.IO [Test] public async Task TestImportMods() { - using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportMods")) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) { try { @@ -92,7 +92,7 @@ namespace osu.Game.Tests.Scores.IO [Test] public async Task TestImportStatistics() { - using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportStatistics")) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) { try { @@ -122,7 +122,7 @@ namespace osu.Game.Tests.Scores.IO [Test] public async Task TestImportWithDeletedBeatmapSet() { - using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportWithDeletedBeatmapSet")) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) { try { @@ -159,7 +159,7 @@ namespace osu.Game.Tests.Scores.IO [Test] public async Task TestOnlineScoreIsAvailableLocally() { - using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestOnlineScoreIsAvailableLocally")) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) { try { diff --git a/osu.Game/Tests/CleanRunHeadlessGameHost.cs b/osu.Game/Tests/CleanRunHeadlessGameHost.cs index bfbf7bb9da..baa7b27d28 100644 --- a/osu.Game/Tests/CleanRunHeadlessGameHost.cs +++ b/osu.Game/Tests/CleanRunHeadlessGameHost.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.Runtime.CompilerServices; using osu.Framework.Platform; namespace osu.Game.Tests @@ -10,8 +11,15 @@ namespace osu.Game.Tests /// public class CleanRunHeadlessGameHost : HeadlessGameHost { - public CleanRunHeadlessGameHost(string gameName = @"", bool bindIPC = false, bool realtime = true) - : base(gameName, bindIPC, realtime) + /// + /// Create a new instance. + /// + /// An optional suffix which will isolate this host from others called from the same method source. + /// Whether to bind IPC channels. + /// Whether the host should be forced to run in realtime, rather than accelerated test time. + /// The name of the calling method, used for test file isolation and clean-up. + public CleanRunHeadlessGameHost(string gameSuffix = @"", bool bindIPC = false, bool realtime = true, [CallerMemberName] string callingMethodName = @"") + : base(callingMethodName + gameSuffix, bindIPC, realtime) { } From 3e5ea6c42fc43c6a31635863481cb0186e799952 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 8 Sep 2020 11:59:47 +0900 Subject: [PATCH 0949/1782] Change "Add to" to "Collections" Doesn't make send to be 'add to' when it can also remove --- osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs | 2 +- osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs index 008cf85018..5618f8f97f 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs @@ -231,7 +231,7 @@ namespace osu.Game.Screens.Select.Carousel if (manageCollectionsDialog != null) collectionItems.Add(new OsuMenuItem("More...", MenuItemType.Standard, manageCollectionsDialog.Show)); - items.Add(new OsuMenuItem("Add to...") { Items = collectionItems }); + items.Add(new OsuMenuItem("Collections") { Items = collectionItems }); return items.ToArray(); } diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index fe0ad31b32..78ec59eb5b 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -152,7 +152,7 @@ namespace osu.Game.Screens.Select.Carousel if (manageCollectionsDialog != null) collectionItems.Add(new OsuMenuItem("More...", MenuItemType.Standard, manageCollectionsDialog.Show)); - items.Add(new OsuMenuItem("Add all to...") { Items = collectionItems }); + items.Add(new OsuMenuItem("Collections") { Items = collectionItems }); return items.ToArray(); } From b15bbc882af8150af0681dac588a18afe17bc61a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 8 Sep 2020 12:04:35 +0900 Subject: [PATCH 0950/1782] Move items up in menu --- .../Select/Carousel/DrawableCarouselBeatmap.cs | 6 +++--- .../Select/Carousel/DrawableCarouselBeatmapSet.cs | 11 +++++------ 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs index 5618f8f97f..28c3529fc0 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs @@ -221,9 +221,6 @@ namespace osu.Game.Screens.Select.Carousel if (editRequested != null) items.Add(new OsuMenuItem("Edit", MenuItemType.Standard, () => editRequested(beatmap))); - if (hideRequested != null) - items.Add(new OsuMenuItem("Hide", MenuItemType.Destructive, () => hideRequested(beatmap))); - if (beatmap.OnlineBeatmapID.HasValue && beatmapOverlay != null) items.Add(new OsuMenuItem("Details", MenuItemType.Standard, () => beatmapOverlay.FetchAndShowBeatmap(beatmap.OnlineBeatmapID.Value))); @@ -233,6 +230,9 @@ namespace osu.Game.Screens.Select.Carousel items.Add(new OsuMenuItem("Collections") { Items = collectionItems }); + if (hideRequested != null) + items.Add(new OsuMenuItem("Hide", MenuItemType.Destructive, () => hideRequested(beatmap))); + return items.ToArray(); } } diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index 78ec59eb5b..327cbc4765 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -142,18 +142,17 @@ namespace osu.Game.Screens.Select.Carousel if (beatmapSet.OnlineBeatmapSetID != null && viewDetails != null) items.Add(new OsuMenuItem("Details...", MenuItemType.Standard, () => viewDetails(beatmapSet.OnlineBeatmapSetID.Value))); - if (beatmapSet.Beatmaps.Any(b => b.Hidden)) - items.Add(new OsuMenuItem("Restore all hidden", MenuItemType.Standard, () => restoreHiddenRequested(beatmapSet))); - - if (dialogOverlay != null) - items.Add(new OsuMenuItem("Delete", MenuItemType.Destructive, () => dialogOverlay.Push(new BeatmapDeleteDialog(beatmapSet)))); - var collectionItems = collectionManager.Collections.OrderByDescending(c => c.LastModifyDate).Take(3).Select(createCollectionMenuItem).ToList(); if (manageCollectionsDialog != null) collectionItems.Add(new OsuMenuItem("More...", MenuItemType.Standard, manageCollectionsDialog.Show)); items.Add(new OsuMenuItem("Collections") { Items = collectionItems }); + if (beatmapSet.Beatmaps.Any(b => b.Hidden)) + items.Add(new OsuMenuItem("Restore all hidden", MenuItemType.Standard, () => restoreHiddenRequested(beatmapSet))); + + if (dialogOverlay != null) + items.Add(new OsuMenuItem("Delete", MenuItemType.Destructive, () => dialogOverlay.Push(new BeatmapDeleteDialog(beatmapSet)))); return items.ToArray(); } } From 8b770626fa877e08eef3baf54aa3657c84b4b664 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 8 Sep 2020 12:18:08 +0900 Subject: [PATCH 0951/1782] Add missing '...' from some popup menu items --- osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs | 2 +- osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs index 28c3529fc0..9f21ec1ad1 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs @@ -222,7 +222,7 @@ namespace osu.Game.Screens.Select.Carousel items.Add(new OsuMenuItem("Edit", MenuItemType.Standard, () => editRequested(beatmap))); if (beatmap.OnlineBeatmapID.HasValue && beatmapOverlay != null) - items.Add(new OsuMenuItem("Details", MenuItemType.Standard, () => beatmapOverlay.FetchAndShowBeatmap(beatmap.OnlineBeatmapID.Value))); + items.Add(new OsuMenuItem("Details...", MenuItemType.Standard, () => beatmapOverlay.FetchAndShowBeatmap(beatmap.OnlineBeatmapID.Value))); var collectionItems = collectionManager.Collections.OrderByDescending(c => c.LastModifyDate).Take(3).Select(createCollectionMenuItem).ToList(); if (manageCollectionsDialog != null) diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index 327cbc4765..19ecc277c4 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -152,7 +152,7 @@ namespace osu.Game.Screens.Select.Carousel items.Add(new OsuMenuItem("Restore all hidden", MenuItemType.Standard, () => restoreHiddenRequested(beatmapSet))); if (dialogOverlay != null) - items.Add(new OsuMenuItem("Delete", MenuItemType.Destructive, () => dialogOverlay.Push(new BeatmapDeleteDialog(beatmapSet)))); + items.Add(new OsuMenuItem("Delete...", MenuItemType.Destructive, () => dialogOverlay.Push(new BeatmapDeleteDialog(beatmapSet)))); return items.ToArray(); } } From ab58f60529d5a53cda28ee8cc010ac3f13d23cf2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 8 Sep 2020 12:47:21 +0900 Subject: [PATCH 0952/1782] Remove elasticity from dialog appearing --- osu.Game/Collections/ManageCollectionsDialog.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Collections/ManageCollectionsDialog.cs b/osu.Game/Collections/ManageCollectionsDialog.cs index f2aedb1c29..036a745913 100644 --- a/osu.Game/Collections/ManageCollectionsDialog.cs +++ b/osu.Game/Collections/ManageCollectionsDialog.cs @@ -111,7 +111,7 @@ namespace osu.Game.Collections base.PopIn(); this.FadeIn(enter_duration, Easing.OutQuint); - this.ScaleTo(0.9f).Then().ScaleTo(1f, enter_duration, Easing.OutElastic); + this.ScaleTo(0.9f).Then().ScaleTo(1f, enter_duration, Easing.OutQuint); } protected override void PopOut() From 3e96c6d036116cd8ef477dd529a26ccc90f90f6e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 8 Sep 2020 12:51:42 +0900 Subject: [PATCH 0953/1782] Improve paddings of collection management dialog --- osu.Game/Collections/DrawableCollectionList.cs | 3 ++- osu.Game/Collections/DrawableCollectionListItem.cs | 1 - 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Collections/DrawableCollectionList.cs b/osu.Game/Collections/DrawableCollectionList.cs index ab146c17b6..e8bde9066f 100644 --- a/osu.Game/Collections/DrawableCollectionList.cs +++ b/osu.Game/Collections/DrawableCollectionList.cs @@ -13,13 +13,14 @@ namespace osu.Game.Collections protected override ScrollContainer CreateScrollContainer() => base.CreateScrollContainer().With(d => { d.ScrollbarVisible = false; + d.Padding = new MarginPadding(10); }); protected override FillFlowContainer> CreateListFillFlowContainer() => new FillFlowContainer> { LayoutDuration = 200, LayoutEasing = Easing.OutQuint, - Spacing = new Vector2(0, 2) + Spacing = new Vector2(0, 5) }; protected override OsuRearrangeableListItem CreateOsuDrawable(BeatmapCollection item) => new DrawableCollectionListItem(item); diff --git a/osu.Game/Collections/DrawableCollectionListItem.cs b/osu.Game/Collections/DrawableCollectionListItem.cs index c7abf58d10..e11f14ccae 100644 --- a/osu.Game/Collections/DrawableCollectionListItem.cs +++ b/osu.Game/Collections/DrawableCollectionListItem.cs @@ -25,7 +25,6 @@ namespace osu.Game.Collections public DrawableCollectionListItem(BeatmapCollection item) : base(item) { - Padding = new MarginPadding { Right = 20 }; } protected override Drawable CreateContent() => new ItemContent(Model); From 0e93bbb62df3c7197513b414e2612559f0f5e2f9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 8 Sep 2020 13:02:58 +0900 Subject: [PATCH 0954/1782] Adjust sizing of delete button --- osu.Game/Collections/DrawableCollectionListItem.cs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/osu.Game/Collections/DrawableCollectionListItem.cs b/osu.Game/Collections/DrawableCollectionListItem.cs index e11f14ccae..9b7505f7c3 100644 --- a/osu.Game/Collections/DrawableCollectionListItem.cs +++ b/osu.Game/Collections/DrawableCollectionListItem.cs @@ -22,6 +22,8 @@ namespace osu.Game.Collections { private const float item_height = 35; + private const float button_width = item_height * 0.75f; + public DrawableCollectionListItem(BeatmapCollection item) : base(item) { @@ -58,7 +60,7 @@ namespace osu.Game.Collections new Container { RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Right = item_height / 2 }, + Padding = new MarginPadding { Right = button_width }, Children = new Drawable[] { textBox = new ItemTextBox @@ -100,8 +102,9 @@ namespace osu.Game.Collections public DeleteButton(BeatmapCollection collection) { this.collection = collection; - RelativeSizeAxes = Axes.Both; - FillMode = FillMode.Fit; + RelativeSizeAxes = Axes.Y; + + Width = button_width + item_height / 2; // add corner radius to cover with fill Alpha = 0.1f; } @@ -119,8 +122,8 @@ namespace osu.Game.Collections new SpriteIcon { Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, - X = -6, + Origin = Anchor.Centre, + X = -button_width * 0.6f, Size = new Vector2(10), Icon = FontAwesome.Solid.Trash } From 525026e7f09d788f5abdce40b546b0c7617569d5 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 8 Sep 2020 13:23:50 +0900 Subject: [PATCH 0955/1782] Fix tests failing due to timings --- .../TestSceneManageCollectionsDialog.cs | 26 ++++++++++++------- .../SongSelect/TestSceneFilterControl.cs | 1 - 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs b/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs index 2d6f8abd8b..fdaded6a5c 100644 --- a/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs +++ b/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs @@ -19,30 +19,30 @@ namespace osu.Game.Tests.Visual.Collections { public class TestSceneManageCollectionsDialog : OsuManualInputManagerTestScene { - [Cached] - private readonly DialogOverlay dialogOverlay; - protected override Container Content => content; private readonly Container content; + private readonly DialogOverlay dialogOverlay; + private readonly BeatmapCollectionManager manager; - private BeatmapCollectionManager manager; private ManageCollectionsDialog dialog; public TestSceneManageCollectionsDialog() { base.Content.AddRange(new Drawable[] { + manager = new BeatmapCollectionManager(LocalStorage), content = new Container { RelativeSizeAxes = Axes.Both }, dialogOverlay = new DialogOverlay() }); } - [BackgroundDependencyLoader] - private void load() + protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) { - Dependencies.Cache(manager = new BeatmapCollectionManager(LocalStorage)); - Add(manager); + var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); + dependencies.Cache(manager); + dependencies.Cache(dialogOverlay); + return dependencies; } [SetUp] @@ -120,6 +120,8 @@ namespace osu.Game.Tests.Visual.Collections new BeatmapCollection { Name = { Value = "2" } }, })); + assertCollectionCount(2); + AddStep("click first delete button", () => { InputManager.MoveMouseTo(dialog.ChildrenOfType().First(), new Vector2(5, 0)); @@ -146,6 +148,8 @@ namespace osu.Game.Tests.Visual.Collections new BeatmapCollection { Name = { Value = "2" } }, })); + assertCollectionCount(2); + AddStep("click first delete button", () => { InputManager.MoveMouseTo(dialog.ChildrenOfType().First(), new Vector2(5, 0)); @@ -185,14 +189,16 @@ namespace osu.Game.Tests.Visual.Collections new BeatmapCollection { Name = { Value = "2" } }, })); + assertCollectionCount(2); + AddStep("change first collection name", () => dialog.ChildrenOfType().First().Text = "First"); AddAssert("collection has new name", () => manager.Collections[0].Name.Value == "First"); } private void assertCollectionCount(int count) - => AddAssert($"{count} collections shown", () => dialog.ChildrenOfType().Count() == count); + => AddUntilStep($"{count} collections shown", () => dialog.ChildrenOfType().Count() == count); private void assertCollectionName(int index, string name) - => AddAssert($"item {index + 1} has correct name", () => dialog.ChildrenOfType().ElementAt(index).ChildrenOfType().First().Text == name); + => AddUntilStep($"item {index + 1} has correct name", () => dialog.ChildrenOfType().ElementAt(index).ChildrenOfType().First().Text == name); } } diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs index c2dd652b3a..dea1c4b9b4 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs @@ -26,7 +26,6 @@ namespace osu.Game.Tests.Visual.SongSelect protected override Container Content => content; private readonly Container content; - [Cached] private readonly BeatmapCollectionManager collectionManager; private RulesetStore rulesets; From 32e3f5d0919abc55ab518e6d4c6dfddd9d7feb5a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 8 Sep 2020 13:45:26 +0900 Subject: [PATCH 0956/1782] Adjust button styling --- .../Select/CollectionFilterDropdown.cs | 38 ++++++++++++------- 1 file changed, 25 insertions(+), 13 deletions(-) diff --git a/osu.Game/Screens/Select/CollectionFilterDropdown.cs b/osu.Game/Screens/Select/CollectionFilterDropdown.cs index 18caae9545..b0bd91b07d 100644 --- a/osu.Game/Screens/Select/CollectionFilterDropdown.cs +++ b/osu.Game/Screens/Select/CollectionFilterDropdown.cs @@ -10,6 +10,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.UserInterface; +using osu.Framework.Input.Events; using osu.Game.Beatmaps; using osu.Game.Collections; using osu.Game.Graphics; @@ -166,6 +167,7 @@ namespace osu.Game.Screens.Select private IconButton addOrRemoveButton; private Content content; + private bool beatmapInCollection; public CollectionDropdownMenuItem(MenuItem item) : base(item) @@ -182,9 +184,10 @@ namespace osu.Game.Screens.Select Anchor = Anchor.CentreRight, Origin = Anchor.CentreRight, X = -OsuScrollContainer.SCROLL_BAR_HEIGHT, - Scale = new Vector2(0.75f), + Scale = new Vector2(0.7f), + AlwaysPresent = true, Alpha = collectionBeatmaps == null ? 0 : 1, - Action = addOrRemove + Action = addOrRemove, }); } @@ -203,24 +206,33 @@ namespace osu.Game.Screens.Select collectionName.BindValueChanged(name => content.Text = name.NewValue, true); } + protected override bool OnHover(HoverEvent e) + { + updateButtonVisibility(); + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + updateButtonVisibility(); + base.OnHoverLost(e); + } + private void collectionChanged() { Debug.Assert(collectionBeatmaps != null); - addOrRemoveButton.Enabled.Value = !beatmap.IsDefault; + beatmapInCollection = collectionBeatmaps.Contains(beatmap.Value.BeatmapInfo); - if (collectionBeatmaps.Contains(beatmap.Value.BeatmapInfo)) - { - addOrRemoveButton.Icon = FontAwesome.Solid.MinusCircle; - addOrRemoveButton.IconColour = colours.Red; - } - else - { - addOrRemoveButton.Icon = FontAwesome.Solid.PlusCircle; - addOrRemoveButton.IconColour = colours.Green; - } + addOrRemoveButton.Enabled.Value = !beatmap.IsDefault; + addOrRemoveButton.Icon = beatmapInCollection ? FontAwesome.Solid.MinusSquare : FontAwesome.Solid.PlusSquare; + addOrRemoveButton.TooltipText = beatmapInCollection ? "Remove selected beatmap" : "Add selected beatmap"; + + updateButtonVisibility(); } + private void updateButtonVisibility() => addOrRemoveButton.Alpha = IsHovered || beatmapInCollection ? 1 : 0; + private void addOrRemove() { Debug.Assert(collectionBeatmaps != null); From 8a3c8a61854d123b74b23caf9b5c389727a59592 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 8 Sep 2020 14:03:49 +0900 Subject: [PATCH 0957/1782] Show button when selected or preselected --- osu.Game/Screens/Select/CollectionFilterDropdown.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/CollectionFilterDropdown.cs b/osu.Game/Screens/Select/CollectionFilterDropdown.cs index b0bd91b07d..c4b98fa854 100644 --- a/osu.Game/Screens/Select/CollectionFilterDropdown.cs +++ b/osu.Game/Screens/Select/CollectionFilterDropdown.cs @@ -231,7 +231,13 @@ namespace osu.Game.Screens.Select updateButtonVisibility(); } - private void updateButtonVisibility() => addOrRemoveButton.Alpha = IsHovered || beatmapInCollection ? 1 : 0; + protected override void OnSelectChange() + { + base.OnSelectChange(); + updateButtonVisibility(); + } + + private void updateButtonVisibility() => addOrRemoveButton.Alpha = IsHovered || IsPreSelected || beatmapInCollection ? 1 : 0; private void addOrRemove() { From c2da3d9c84edfb98b6ca09efa1d9505267e3ceb0 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 8 Sep 2020 14:36:38 +0900 Subject: [PATCH 0958/1782] Fix button input and tests --- .../SongSelect/TestSceneFilterControl.cs | 67 +++++++------------ .../Select/CollectionFilterDropdown.cs | 14 ++-- 2 files changed, 36 insertions(+), 45 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs index dea1c4b9b4..65b554b27b 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs @@ -109,11 +109,7 @@ namespace osu.Game.Tests.Visual.SongSelect dropdown.Current.Value = dropdown.ItemSource.ElementAt(1); }); - AddStep("expand header", () => - { - InputManager.MoveMouseTo(control.ChildrenOfType().Single()); - InputManager.Click(MouseButton.Left); - }); + addExpandHeaderStep(); AddStep("change name", () => collectionManager.Collections[0].Name.Value = "First"); @@ -124,30 +120,24 @@ namespace osu.Game.Tests.Visual.SongSelect [Test] public void TestAllBeatmapFilterDoesNotHaveAddButton() { - AddAssert("'All beatmaps' filter does not have add button", () => !getCollectionDropdownItems().First().ChildrenOfType().Single().IsPresent); + addExpandHeaderStep(); + AddStep("hover all beatmaps", () => InputManager.MoveMouseTo(getAddOrRemoveButton(0))); + AddAssert("'All beatmaps' filter does not have add button", () => !getAddOrRemoveButton(0).IsPresent); } [Test] public void TestCollectionFilterHasAddButton() { - AddStep("expand header", () => - { - InputManager.MoveMouseTo(control.ChildrenOfType().Single()); - InputManager.Click(MouseButton.Left); - }); - + addExpandHeaderStep(); AddStep("add collection", () => collectionManager.Collections.Add(new BeatmapCollection { Name = { Value = "1" } })); - AddAssert("collection has add button", () => !getAddOrRemoveButton(0).IsPresent); + AddStep("hover collection", () => InputManager.MoveMouseTo(getAddOrRemoveButton(1))); + AddAssert("collection has add button", () => getAddOrRemoveButton(1).IsPresent); } [Test] public void TestButtonDisabledAndEnabledWithBeatmapChanges() { - AddStep("expand header", () => - { - InputManager.MoveMouseTo(control.ChildrenOfType().Single()); - InputManager.Click(MouseButton.Left); - }); + addExpandHeaderStep(); AddStep("add collection", () => collectionManager.Collections.Add(new BeatmapCollection { Name = { Value = "1" } })); @@ -161,45 +151,37 @@ namespace osu.Game.Tests.Visual.SongSelect [Test] public void TestButtonChangesWhenAddedAndRemovedFromCollection() { - AddStep("expand header", () => - { - InputManager.MoveMouseTo(control.ChildrenOfType().Single()); - InputManager.Click(MouseButton.Left); - }); + addExpandHeaderStep(); AddStep("select available beatmap", () => Beatmap.Value = beatmapManager.GetWorkingBeatmap(beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps[0])); AddStep("add collection", () => collectionManager.Collections.Add(new BeatmapCollection { Name = { Value = "1" } })); - AddAssert("button is plus", () => getAddOrRemoveButton(1).Icon.Equals(FontAwesome.Solid.PlusCircle)); + AddAssert("button is plus", () => getAddOrRemoveButton(1).Icon.Equals(FontAwesome.Solid.PlusSquare)); AddStep("add beatmap to collection", () => collectionManager.Collections[0].Beatmaps.Add(Beatmap.Value.BeatmapInfo)); - AddAssert("button is minus", () => getAddOrRemoveButton(1).Icon.Equals(FontAwesome.Solid.MinusCircle)); + AddAssert("button is minus", () => getAddOrRemoveButton(1).Icon.Equals(FontAwesome.Solid.MinusSquare)); AddStep("remove beatmap from collection", () => collectionManager.Collections[0].Beatmaps.Clear()); - AddAssert("button is plus", () => getAddOrRemoveButton(1).Icon.Equals(FontAwesome.Solid.PlusCircle)); + AddAssert("button is plus", () => getAddOrRemoveButton(1).Icon.Equals(FontAwesome.Solid.PlusSquare)); } [Test] public void TestButtonAddsAndRemovesBeatmap() { - AddStep("expand header", () => - { - InputManager.MoveMouseTo(control.ChildrenOfType().Single()); - InputManager.Click(MouseButton.Left); - }); + addExpandHeaderStep(); AddStep("select available beatmap", () => Beatmap.Value = beatmapManager.GetWorkingBeatmap(beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps[0])); AddStep("add collection", () => collectionManager.Collections.Add(new BeatmapCollection { Name = { Value = "1" } })); - AddAssert("button is plus", () => getAddOrRemoveButton(1).Icon.Equals(FontAwesome.Solid.PlusCircle)); + AddAssert("button is plus", () => getAddOrRemoveButton(1).Icon.Equals(FontAwesome.Solid.PlusSquare)); addClickAddOrRemoveButtonStep(1); AddAssert("collection contains beatmap", () => collectionManager.Collections[0].Beatmaps.Contains(Beatmap.Value.BeatmapInfo)); - AddAssert("button is minus", () => getAddOrRemoveButton(1).Icon.Equals(FontAwesome.Solid.MinusCircle)); + AddAssert("button is minus", () => getAddOrRemoveButton(1).Icon.Equals(FontAwesome.Solid.MinusSquare)); addClickAddOrRemoveButtonStep(1); AddAssert("collection does not contain beatmap", () => !collectionManager.Collections[0].Beatmaps.Contains(Beatmap.Value.BeatmapInfo)); - AddAssert("button is plus", () => getAddOrRemoveButton(1).Icon.Equals(FontAwesome.Solid.PlusCircle)); + AddAssert("button is plus", () => getAddOrRemoveButton(1).Icon.Equals(FontAwesome.Solid.PlusSquare)); } private void assertCollectionHeaderDisplays(string collectionName, bool shouldDisplay = true) @@ -214,14 +196,17 @@ namespace osu.Game.Tests.Visual.SongSelect private IconButton getAddOrRemoveButton(int index) => getCollectionDropdownItems().ElementAt(index).ChildrenOfType().Single(); - private void addClickAddOrRemoveButtonStep(int index) + private void addExpandHeaderStep() => AddStep("expand header", () => { - AddStep("click add or remove button", () => - { - InputManager.MoveMouseTo(getAddOrRemoveButton(index)); - InputManager.Click(MouseButton.Left); - }); - } + InputManager.MoveMouseTo(control.ChildrenOfType().Single()); + InputManager.Click(MouseButton.Left); + }); + + private void addClickAddOrRemoveButtonStep(int index) => AddStep("click add or remove button", () => + { + InputManager.MoveMouseTo(getAddOrRemoveButton(index)); + InputManager.Click(MouseButton.Left); + }); private IEnumerable.DropdownMenu.DrawableDropdownMenuItem> getCollectionDropdownItems() => control.ChildrenOfType().Single().ChildrenOfType.DropdownMenu.DrawableDropdownMenuItem>(); diff --git a/osu.Game/Screens/Select/CollectionFilterDropdown.cs b/osu.Game/Screens/Select/CollectionFilterDropdown.cs index c4b98fa854..4e9e12fcaf 100644 --- a/osu.Game/Screens/Select/CollectionFilterDropdown.cs +++ b/osu.Game/Screens/Select/CollectionFilterDropdown.cs @@ -184,9 +184,7 @@ namespace osu.Game.Screens.Select Anchor = Anchor.CentreRight, Origin = Anchor.CentreRight, X = -OsuScrollContainer.SCROLL_BAR_HEIGHT, - Scale = new Vector2(0.7f), - AlwaysPresent = true, - Alpha = collectionBeatmaps == null ? 0 : 1, + Scale = new Vector2(0.65f), Action = addOrRemove, }); } @@ -204,6 +202,8 @@ namespace osu.Game.Screens.Select // Although the DrawableMenuItem binds to value changes of the item's text, the item is an internal implementation detail of Dropdown that has no knowledge // of the underlying CollectionFilter value and its accompanying name, so the real name has to be copied here. Without this, the collection name wouldn't update when changed. collectionName.BindValueChanged(name => content.Text = name.NewValue, true); + + updateButtonVisibility(); } protected override bool OnHover(HoverEvent e) @@ -237,7 +237,13 @@ namespace osu.Game.Screens.Select updateButtonVisibility(); } - private void updateButtonVisibility() => addOrRemoveButton.Alpha = IsHovered || IsPreSelected || beatmapInCollection ? 1 : 0; + private void updateButtonVisibility() + { + if (collectionBeatmaps == null) + addOrRemoveButton.Alpha = 0; + else + addOrRemoveButton.Alpha = IsHovered || IsPreSelected || beatmapInCollection ? 1 : 0; + } private void addOrRemove() { From 17e8171827c7b8150d376683491ded9c59b5264d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 8 Sep 2020 14:38:25 +0900 Subject: [PATCH 0959/1782] Don't prompt to remove empty collection --- osu.Game/Collections/DeleteCollectionDialog.cs | 9 +++------ osu.Game/Collections/DrawableCollectionListItem.cs | 12 +++++++++++- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/osu.Game/Collections/DeleteCollectionDialog.cs b/osu.Game/Collections/DeleteCollectionDialog.cs index f2b8de7c1e..8c8c897146 100644 --- a/osu.Game/Collections/DeleteCollectionDialog.cs +++ b/osu.Game/Collections/DeleteCollectionDialog.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 osu.Framework.Allocation; +using System; using osu.Framework.Graphics.Sprites; using osu.Game.Overlays.Dialog; @@ -9,10 +9,7 @@ namespace osu.Game.Collections { public class DeleteCollectionDialog : PopupDialog { - [Resolved] - private BeatmapCollectionManager collectionManager { get; set; } - - public DeleteCollectionDialog(BeatmapCollection collection) + public DeleteCollectionDialog(BeatmapCollection collection, Action deleteAction) { HeaderText = "Confirm deletion of"; BodyText = collection.Name.Value; @@ -24,7 +21,7 @@ namespace osu.Game.Collections new PopupDialogOkButton { Text = @"Yes. Go for it.", - Action = () => collectionManager.Collections.Remove(collection) + Action = deleteAction }, new PopupDialogCancelButton { diff --git a/osu.Game/Collections/DrawableCollectionListItem.cs b/osu.Game/Collections/DrawableCollectionListItem.cs index 9b7505f7c3..a1fc55556e 100644 --- a/osu.Game/Collections/DrawableCollectionListItem.cs +++ b/osu.Game/Collections/DrawableCollectionListItem.cs @@ -95,6 +95,9 @@ namespace osu.Game.Collections [Resolved(CanBeNull = true)] private DialogOverlay dialogOverlay { get; set; } + [Resolved] + private BeatmapCollectionManager collectionManager { get; set; } + private readonly BeatmapCollection collection; private Drawable background; @@ -146,9 +149,16 @@ namespace osu.Game.Collections protected override bool OnClick(ClickEvent e) { background.FlashColour(Color4.White, 150); - dialogOverlay?.Push(new DeleteCollectionDialog(collection)); + + if (collection.Beatmaps.Count == 0) + deleteCollection(); + else + dialogOverlay?.Push(new DeleteCollectionDialog(collection, deleteCollection)); + return true; } + + private void deleteCollection() => collectionManager.Collections.Remove(collection); } } } From 1260e30cde06611ca403a972a54403879c338223 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 8 Sep 2020 16:36:36 +0900 Subject: [PATCH 0960/1782] Make ShowDragHandle into a bindable --- .../OsuRearrangeableListContainer.cs | 4 ++-- .../Containers/OsuRearrangeableListItem.cs | 20 ++++++++++--------- .../Screens/Multi/DrawableRoomPlaylistItem.cs | 5 ++--- 3 files changed, 15 insertions(+), 14 deletions(-) diff --git a/osu.Game/Graphics/Containers/OsuRearrangeableListContainer.cs b/osu.Game/Graphics/Containers/OsuRearrangeableListContainer.cs index 47aed1c500..1048fd094c 100644 --- a/osu.Game/Graphics/Containers/OsuRearrangeableListContainer.cs +++ b/osu.Game/Graphics/Containers/OsuRearrangeableListContainer.cs @@ -12,13 +12,13 @@ namespace osu.Game.Graphics.Containers /// /// Whether any item is currently being dragged. Used to hide other items' drag handles. /// - private readonly BindableBool playlistDragActive = new BindableBool(); + protected readonly BindableBool DragActive = new BindableBool(); protected override ScrollContainer CreateScrollContainer() => new OsuScrollContainer(); protected sealed override RearrangeableListItem CreateDrawable(TModel item) => CreateOsuDrawable(item).With(d => { - d.PlaylistDragActive.BindTo(playlistDragActive); + d.DragActive.BindTo(DragActive); }); protected abstract OsuRearrangeableListItem CreateOsuDrawable(TModel item); diff --git a/osu.Game/Graphics/Containers/OsuRearrangeableListItem.cs b/osu.Game/Graphics/Containers/OsuRearrangeableListItem.cs index 29553954fe..9cdcb19a81 100644 --- a/osu.Game/Graphics/Containers/OsuRearrangeableListItem.cs +++ b/osu.Game/Graphics/Containers/OsuRearrangeableListItem.cs @@ -19,7 +19,7 @@ namespace osu.Game.Graphics.Containers /// /// Whether any item is currently being dragged. Used to hide other items' drag handles. /// - public readonly BindableBool PlaylistDragActive = new BindableBool(); + public readonly BindableBool DragActive = new BindableBool(); private Color4 handleColour = Color4.White; @@ -44,8 +44,9 @@ namespace osu.Game.Graphics.Containers /// /// Whether the drag handle should be shown. /// - protected virtual bool ShowDragHandle => true; + protected readonly Bindable ShowDragHandle = new Bindable(); + private Container handleContainer; private PlaylistItemHandle handle; protected OsuRearrangeableListItem(TModel item) @@ -58,8 +59,6 @@ namespace osu.Game.Graphics.Containers [BackgroundDependencyLoader] private void load() { - Container handleContainer; - InternalChild = new GridContainer { RelativeSizeAxes = Axes.X, @@ -88,9 +87,12 @@ namespace osu.Game.Graphics.Containers ColumnDimensions = new[] { new Dimension(GridSizeMode.AutoSize) }, RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize) } }; + } - if (!ShowDragHandle) - handleContainer.Alpha = 0; + protected override void LoadComplete() + { + base.LoadComplete(); + ShowDragHandle.BindValueChanged(show => handleContainer.Alpha = show.NewValue ? 1 : 0, true); } protected override bool OnDragStart(DragStartEvent e) @@ -98,13 +100,13 @@ namespace osu.Game.Graphics.Containers if (!base.OnDragStart(e)) return false; - PlaylistDragActive.Value = true; + DragActive.Value = true; return true; } protected override void OnDragEnd(DragEndEvent e) { - PlaylistDragActive.Value = false; + DragActive.Value = false; base.OnDragEnd(e); } @@ -112,7 +114,7 @@ namespace osu.Game.Graphics.Containers protected override bool OnHover(HoverEvent e) { - handle.UpdateHoverState(IsDragged || !PlaylistDragActive.Value); + handle.UpdateHoverState(IsDragged || !DragActive.Value); return base.OnHover(e); } diff --git a/osu.Game/Screens/Multi/DrawableRoomPlaylistItem.cs b/osu.Game/Screens/Multi/DrawableRoomPlaylistItem.cs index c0892235f2..b007e0349d 100644 --- a/osu.Game/Screens/Multi/DrawableRoomPlaylistItem.cs +++ b/osu.Game/Screens/Multi/DrawableRoomPlaylistItem.cs @@ -37,8 +37,6 @@ namespace osu.Game.Screens.Multi public readonly Bindable SelectedItem = new Bindable(); - protected override bool ShowDragHandle => allowEdit; - private Container maskingContainer; private Container difficultyIconContainer; private LinkFlowContainer beatmapText; @@ -63,12 +61,13 @@ namespace osu.Game.Screens.Multi // TODO: edit support should be moved out into a derived class this.allowEdit = allowEdit; - this.allowSelection = allowSelection; beatmap.BindTo(item.Beatmap); ruleset.BindTo(item.Ruleset); requiredMods.BindTo(item.RequiredMods); + + ShowDragHandle.Value = allowEdit; } [BackgroundDependencyLoader] From 0bf6bfe5ee4569bc7b2c0e83e92dd659d33299fc Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 8 Sep 2020 16:43:07 +0900 Subject: [PATCH 0961/1782] Create a new collection via a placeholder item --- .../Collections/DrawableCollectionList.cs | 97 +++++++++++++++++-- .../Collections/DrawableCollectionListItem.cs | 94 ++++++++++++++---- .../Collections/ManageCollectionsDialog.cs | 19 +--- 3 files changed, 163 insertions(+), 47 deletions(-) diff --git a/osu.Game/Collections/DrawableCollectionList.cs b/osu.Game/Collections/DrawableCollectionList.cs index e8bde9066f..f4b5a89b3e 100644 --- a/osu.Game/Collections/DrawableCollectionList.cs +++ b/osu.Game/Collections/DrawableCollectionList.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.Linq; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics.Containers; @@ -10,19 +12,94 @@ namespace osu.Game.Collections { public class DrawableCollectionList : OsuRearrangeableListContainer { - protected override ScrollContainer CreateScrollContainer() => base.CreateScrollContainer().With(d => - { - d.ScrollbarVisible = false; - d.Padding = new MarginPadding(10); - }); + private Scroll scroll; - protected override FillFlowContainer> CreateListFillFlowContainer() => new FillFlowContainer> + protected override ScrollContainer CreateScrollContainer() => scroll = new Scroll(); + + protected override FillFlowContainer> CreateListFillFlowContainer() => new Flow { - LayoutDuration = 200, - LayoutEasing = Easing.OutQuint, - Spacing = new Vector2(0, 5) + DragActive = { BindTarget = DragActive } }; - protected override OsuRearrangeableListItem CreateOsuDrawable(BeatmapCollection item) => new DrawableCollectionListItem(item); + protected override OsuRearrangeableListItem CreateOsuDrawable(BeatmapCollection item) + { + if (item == scroll.PlaceholderItem.Model) + return scroll.ReplacePlaceholder(); + + return new DrawableCollectionListItem(item, true); + } + + private class Scroll : OsuScrollContainer + { + public DrawableCollectionListItem PlaceholderItem { get; private set; } + + protected override Container Content => content; + private readonly Container content; + + private readonly Container placeholderContainer; + + public Scroll() + { + ScrollbarVisible = false; + Padding = new MarginPadding(10); + + base.Content.Add(new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + LayoutDuration = 200, + LayoutEasing = Easing.OutQuint, + Children = new Drawable[] + { + content = new Container { RelativeSizeAxes = Axes.X }, + placeholderContainer = new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y + } + } + }); + + ReplacePlaceholder(); + } + + protected override void Update() + { + base.Update(); + + // AutoSizeAxes cannot be used as the height should represent the post-layout-transform height at all times, so that the placeholder doesn't bounce around. + content.Height = ((Flow)Child).Children.Sum(c => c.DrawHeight + 5); + } + + /// + /// Replaces the current with a new one, and returns the previous. + /// + public DrawableCollectionListItem ReplacePlaceholder() + { + var previous = PlaceholderItem; + + placeholderContainer.Clear(false); + placeholderContainer.Add(PlaceholderItem = new DrawableCollectionListItem(new BeatmapCollection(), false)); + + return previous; + } + } + + private class Flow : FillFlowContainer> + { + public readonly IBindable DragActive = new Bindable(); + + public Flow() + { + Spacing = new Vector2(0, 5); + LayoutEasing = Easing.OutQuint; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + DragActive.BindValueChanged(active => LayoutDuration = active.NewValue ? 200 : 0); + } + } } } diff --git a/osu.Game/Collections/DrawableCollectionListItem.cs b/osu.Game/Collections/DrawableCollectionListItem.cs index a1fc55556e..90d5bae223 100644 --- a/osu.Game/Collections/DrawableCollectionListItem.cs +++ b/osu.Game/Collections/DrawableCollectionListItem.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; @@ -21,20 +22,33 @@ namespace osu.Game.Collections public class DrawableCollectionListItem : OsuRearrangeableListItem { private const float item_height = 35; - private const float button_width = item_height * 0.75f; - public DrawableCollectionListItem(BeatmapCollection item) + private readonly Bindable isCreated = new Bindable(); + + public DrawableCollectionListItem(BeatmapCollection item, bool isCreated) : base(item) { + this.isCreated.Value = isCreated; + + ShowDragHandle.BindTo(this.isCreated); } - protected override Drawable CreateContent() => new ItemContent(Model); + protected override Drawable CreateContent() => new ItemContent(Model) + { + IsCreated = { BindTarget = isCreated } + }; private class ItemContent : CircularContainer { + public readonly Bindable IsCreated = new Bindable(); + + private readonly IBindable collectionName; private readonly BeatmapCollection collection; + [Resolved] + private BeatmapCollectionManager collectionManager { get; set; } + private ItemTextBox textBox; public ItemContent(BeatmapCollection collection) @@ -44,6 +58,8 @@ namespace osu.Game.Collections RelativeSizeAxes = Axes.X; Height = item_height; Masking = true; + + collectionName = collection.Name.GetBoundCopy(); } [BackgroundDependencyLoader] @@ -55,6 +71,7 @@ namespace osu.Game.Collections { Anchor = Anchor.CentreRight, Origin = Anchor.CentreRight, + IsCreated = { BindTarget = IsCreated }, IsTextBoxHovered = v => textBox.ReceivePositionalInputAt(v) }, new Container @@ -68,12 +85,37 @@ namespace osu.Game.Collections RelativeSizeAxes = Axes.Both, Size = Vector2.One, CornerRadius = item_height / 2, - Current = collection.Name + Current = collection.Name, + PlaceholderText = IsCreated.Value ? string.Empty : "Create a new collection" }, } }, }; } + + protected override void LoadComplete() + { + base.LoadComplete(); + collectionName.BindValueChanged(_ => createNewCollection(), true); + } + + private void createNewCollection() + { + if (IsCreated.Value) + return; + + if (string.IsNullOrEmpty(collectionName.Value)) + return; + + // Add the new collection and disable our placeholder. If all text is removed, the placeholder should not show back again. + collectionManager.Collections.Add(collection); + textBox.PlaceholderText = string.Empty; + + // When this item changes from placeholder to non-placeholder (via changing containers), its textbox will lose focus, so it needs to be re-focused. + Schedule(() => GetContainingInputManager().ChangeFocus(textBox)); + + IsCreated.Value = true; + } } private class ItemTextBox : OsuTextBox @@ -90,6 +132,8 @@ namespace osu.Game.Collections public class DeleteButton : CompositeDrawable { + public readonly IBindable IsCreated = new Bindable(); + public Func IsTextBoxHovered; [Resolved(CanBeNull = true)] @@ -100,6 +144,7 @@ namespace osu.Game.Collections private readonly BeatmapCollection collection; + private Drawable fadeContainer; private Drawable background; public DeleteButton(BeatmapCollection collection) @@ -108,42 +153,51 @@ namespace osu.Game.Collections RelativeSizeAxes = Axes.Y; Width = button_width + item_height / 2; // add corner radius to cover with fill - - Alpha = 0.1f; } [BackgroundDependencyLoader] private void load(OsuColour colours) { - InternalChildren = new[] + InternalChild = fadeContainer = new Container { - background = new Box + RelativeSizeAxes = Axes.Both, + Alpha = 0.1f, + Children = new[] { - RelativeSizeAxes = Axes.Both, - Colour = colours.Red - }, - new SpriteIcon - { - Anchor = Anchor.CentreRight, - Origin = Anchor.Centre, - X = -button_width * 0.6f, - Size = new Vector2(10), - Icon = FontAwesome.Solid.Trash + background = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colours.Red + }, + new SpriteIcon + { + Anchor = Anchor.CentreRight, + Origin = Anchor.Centre, + X = -button_width * 0.6f, + Size = new Vector2(10), + Icon = FontAwesome.Solid.Trash + } } }; } + protected override void LoadComplete() + { + base.LoadComplete(); + IsCreated.BindValueChanged(created => Alpha = created.NewValue ? 1 : 0, true); + } + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => base.ReceivePositionalInputAt(screenSpacePos) && !IsTextBoxHovered(screenSpacePos); protected override bool OnHover(HoverEvent e) { - this.FadeTo(1f, 100, Easing.Out); + fadeContainer.FadeTo(1f, 100, Easing.Out); return false; } protected override void OnHoverLost(HoverLostEvent e) { - this.FadeTo(0.1f, 100); + fadeContainer.FadeTo(0.1f, 100); } protected override bool OnClick(ClickEvent e) diff --git a/osu.Game/Collections/ManageCollectionsDialog.cs b/osu.Game/Collections/ManageCollectionsDialog.cs index 036a745913..8f8ac9542c 100644 --- a/osu.Game/Collections/ManageCollectionsDialog.cs +++ b/osu.Game/Collections/ManageCollectionsDialog.cs @@ -8,7 +8,6 @@ using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; -using osu.Game.Graphics.UserInterface; using osuTK; namespace osu.Game.Collections @@ -51,9 +50,7 @@ namespace osu.Game.Collections RelativeSizeAxes = Axes.Both, RowDimensions = new[] { - new Dimension(GridSizeMode.Absolute, 50), - new Dimension(), - new Dimension(GridSizeMode.Absolute, 50), + new Dimension(GridSizeMode.AutoSize), }, Content = new[] { @@ -65,6 +62,7 @@ namespace osu.Game.Collections Origin = Anchor.Centre, Text = "Manage collections", Font = OsuFont.GetFont(size: 30), + Padding = new MarginPadding { Vertical = 10 }, } }, new Drawable[] @@ -87,19 +85,6 @@ namespace osu.Game.Collections } } }, - new Drawable[] - { - new OsuButton - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - Size = Vector2.One, - Padding = new MarginPadding(10), - Text = "Create new collection", - Action = () => collectionManager.Collections.Add(new BeatmapCollection { Name = { Value = "My new collection" } }) - }, - }, } } } From 38ade433a62b449be687ba3409cfd854d4d04876 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 8 Sep 2020 16:50:51 +0900 Subject: [PATCH 0962/1782] Add some xmldocs --- osu.Game/Collections/DrawableCollectionList.cs | 17 +++++++++++++++++ .../Collections/DrawableCollectionListItem.cs | 11 +++++++++++ 2 files changed, 28 insertions(+) diff --git a/osu.Game/Collections/DrawableCollectionList.cs b/osu.Game/Collections/DrawableCollectionList.cs index f4b5a89b3e..3c664a11d9 100644 --- a/osu.Game/Collections/DrawableCollectionList.cs +++ b/osu.Game/Collections/DrawableCollectionList.cs @@ -10,6 +10,9 @@ using osuTK; namespace osu.Game.Collections { + /// + /// Visualises a list of s. + /// public class DrawableCollectionList : OsuRearrangeableListContainer { private Scroll scroll; @@ -29,8 +32,18 @@ namespace osu.Game.Collections return new DrawableCollectionListItem(item, true); } + /// + /// The scroll container for this . + /// Contains the main flow of and attaches a placeholder item to the end of the list. + /// + /// + /// Use to transfer the placeholder into the main list. + /// private class Scroll : OsuScrollContainer { + /// + /// The currently-displayed placeholder item. + /// public DrawableCollectionListItem PlaceholderItem { get; private set; } protected override Container Content => content; @@ -74,6 +87,7 @@ namespace osu.Game.Collections /// /// Replaces the current with a new one, and returns the previous. /// + /// The current . public DrawableCollectionListItem ReplacePlaceholder() { var previous = PlaceholderItem; @@ -85,6 +99,9 @@ namespace osu.Game.Collections } } + /// + /// The flow of . Disables layout easing unless a drag is in progress. + /// private class Flow : FillFlowContainer> { public readonly IBindable DragActive = new Bindable(); diff --git a/osu.Game/Collections/DrawableCollectionListItem.cs b/osu.Game/Collections/DrawableCollectionListItem.cs index 90d5bae223..489382ec9e 100644 --- a/osu.Game/Collections/DrawableCollectionListItem.cs +++ b/osu.Game/Collections/DrawableCollectionListItem.cs @@ -19,6 +19,9 @@ using osuTK.Graphics; namespace osu.Game.Collections { + /// + /// Visualises a inside a . + /// public class DrawableCollectionListItem : OsuRearrangeableListItem { private const float item_height = 35; @@ -26,6 +29,11 @@ namespace osu.Game.Collections private readonly Bindable isCreated = new Bindable(); + /// + /// Creates a new . + /// + /// The . + /// Whether currently exists inside the . public DrawableCollectionListItem(BeatmapCollection item, bool isCreated) : base(item) { @@ -39,6 +47,9 @@ namespace osu.Game.Collections IsCreated = { BindTarget = isCreated } }; + /// + /// The main content of the . + /// private class ItemContent : CircularContainer { public readonly Bindable IsCreated = new Bindable(); From 2e40ff25f7228ed74aaf12fbd83ada10b4a0a917 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 8 Sep 2020 17:05:31 +0900 Subject: [PATCH 0963/1782] Only pad textbox after collection is created --- osu.Game/Collections/DrawableCollectionListItem.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Collections/DrawableCollectionListItem.cs b/osu.Game/Collections/DrawableCollectionListItem.cs index 489382ec9e..c67946977d 100644 --- a/osu.Game/Collections/DrawableCollectionListItem.cs +++ b/osu.Game/Collections/DrawableCollectionListItem.cs @@ -60,6 +60,7 @@ namespace osu.Game.Collections [Resolved] private BeatmapCollectionManager collectionManager { get; set; } + private Container textBoxPaddingContainer; private ItemTextBox textBox; public ItemContent(BeatmapCollection collection) @@ -85,7 +86,7 @@ namespace osu.Game.Collections IsCreated = { BindTarget = IsCreated }, IsTextBoxHovered = v => textBox.ReceivePositionalInputAt(v) }, - new Container + textBoxPaddingContainer = new Container { RelativeSizeAxes = Axes.Both, Padding = new MarginPadding { Right = button_width }, @@ -107,7 +108,9 @@ namespace osu.Game.Collections protected override void LoadComplete() { base.LoadComplete(); + collectionName.BindValueChanged(_ => createNewCollection(), true); + IsCreated.BindValueChanged(created => textBoxPaddingContainer.Padding = new MarginPadding { Right = created.NewValue ? button_width : 0 }, true); } private void createNewCollection() From bee450ae1ecba24a161e681e53e53f94462428fd Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 8 Sep 2020 17:05:43 +0900 Subject: [PATCH 0964/1782] Fix tests/add placeholder item tests --- .../TestSceneManageCollectionsDialog.cs | 80 ++++++++++++++----- .../SongSelect/TestSceneFilterControl.cs | 14 ++-- .../Collections/DrawableCollectionListItem.cs | 5 ++ 3 files changed, 72 insertions(+), 27 deletions(-) diff --git a/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs b/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs index fdaded6a5c..0c57c27911 100644 --- a/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs +++ b/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs @@ -7,11 +7,14 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.UserInterface; +using osu.Framework.Platform; using osu.Framework.Testing; +using osu.Game.Beatmaps; using osu.Game.Collections; -using osu.Game.Graphics.UserInterface; using osu.Game.Overlays; using osu.Game.Overlays.Dialog; +using osu.Game.Rulesets; +using osu.Game.Tests.Resources; using osuTK; using osuTK.Input; @@ -25,6 +28,9 @@ namespace osu.Game.Tests.Visual.Collections private readonly DialogOverlay dialogOverlay; private readonly BeatmapCollectionManager manager; + private RulesetStore rulesets; + private BeatmapManager beatmapManager; + private ManageCollectionsDialog dialog; public TestSceneManageCollectionsDialog() @@ -37,6 +43,15 @@ namespace osu.Game.Tests.Visual.Collections }); } + [BackgroundDependencyLoader] + private void load(GameHost host) + { + Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); + Dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, Audio, host, Beatmap.Default)); + + beatmapManager.Import(TestResources.GetTestBeatmapForImport()).Wait(); + } + protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) { var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); @@ -65,6 +80,12 @@ namespace osu.Game.Tests.Visual.Collections AddStep("hide dialog", () => dialog.Hide()); } + [Test] + public void TestLastItemIsPlaceholder() + { + AddAssert("last item is placeholder", () => !manager.Collections.Contains(dialog.ChildrenOfType().Last().Model)); + } + [Test] public void TestAddCollectionExternal() { @@ -78,23 +99,34 @@ namespace osu.Game.Tests.Visual.Collections } [Test] - public void TestAddCollectionViaButton() + public void TestFocusPlaceholderDoesNotCreateCollection() { - AddStep("press new collection button", () => + AddStep("focus placeholder", () => { - InputManager.MoveMouseTo(dialog.ChildrenOfType().Single()); + InputManager.MoveMouseTo(dialog.ChildrenOfType().Last()); InputManager.Click(MouseButton.Left); }); + assertCollectionCount(0); + } + + [Test] + public void TestAddCollectionViaPlaceholder() + { + DrawableCollectionListItem placeholderItem = null; + + AddStep("focus placeholder", () => + { + InputManager.MoveMouseTo(placeholderItem = dialog.ChildrenOfType().Last()); + InputManager.Click(MouseButton.Left); + }); + + // Done directly via the collection since InputManager methods cannot add text to textbox... + AddStep("change collection name", () => placeholderItem.Model.Name.Value = "a"); assertCollectionCount(1); + AddAssert("collection now exists", () => manager.Collections.Contains(placeholderItem.Model)); - AddStep("press again", () => - { - InputManager.MoveMouseTo(dialog.ChildrenOfType().Single()); - InputManager.Click(MouseButton.Left); - }); - - assertCollectionCount(2); + AddAssert("last item is placeholder", () => !manager.Collections.Contains(dialog.ChildrenOfType().Last().Model)); } [Test] @@ -117,7 +149,7 @@ namespace osu.Game.Tests.Visual.Collections AddStep("add two collections", () => manager.Collections.AddRange(new[] { new BeatmapCollection { Name = { Value = "1" } }, - new BeatmapCollection { Name = { Value = "2" } }, + new BeatmapCollection { Name = { Value = "2" }, Beatmaps = { beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps[0] } }, })); assertCollectionCount(2); @@ -128,6 +160,16 @@ namespace osu.Game.Tests.Visual.Collections InputManager.Click(MouseButton.Left); }); + AddAssert("dialog not displayed", () => dialogOverlay.CurrentDialog == null); + assertCollectionCount(1); + assertCollectionName(0, "2"); + + AddStep("click first delete button", () => + { + InputManager.MoveMouseTo(dialog.ChildrenOfType().First(), new Vector2(5, 0)); + InputManager.Click(MouseButton.Left); + }); + AddAssert("dialog displayed", () => dialogOverlay.CurrentDialog is DeleteCollectionDialog); AddStep("click confirmation", () => { @@ -135,8 +177,7 @@ namespace osu.Game.Tests.Visual.Collections InputManager.Click(MouseButton.Left); }); - assertCollectionCount(1); - assertCollectionName(0, "2"); + assertCollectionCount(0); } [Test] @@ -144,11 +185,10 @@ namespace osu.Game.Tests.Visual.Collections { AddStep("add two collections", () => manager.Collections.AddRange(new[] { - new BeatmapCollection { Name = { Value = "1" } }, - new BeatmapCollection { Name = { Value = "2" } }, + new BeatmapCollection { Name = { Value = "1" }, Beatmaps = { beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps[0] } }, })); - assertCollectionCount(2); + assertCollectionCount(1); AddStep("click first delete button", () => { @@ -157,13 +197,13 @@ namespace osu.Game.Tests.Visual.Collections }); AddAssert("dialog displayed", () => dialogOverlay.CurrentDialog is DeleteCollectionDialog); - AddStep("click confirmation", () => + AddStep("click cancellation", () => { InputManager.MoveMouseTo(dialogOverlay.CurrentDialog.ChildrenOfType().Last()); InputManager.Click(MouseButton.Left); }); - assertCollectionCount(2); + assertCollectionCount(1); } [Test] @@ -196,7 +236,7 @@ namespace osu.Game.Tests.Visual.Collections } private void assertCollectionCount(int count) - => AddUntilStep($"{count} collections shown", () => dialog.ChildrenOfType().Count() == count); + => AddUntilStep($"{count} collections shown", () => dialog.ChildrenOfType().Count(i => i.IsCreated.Value) == count); private void assertCollectionName(int index, string name) => AddUntilStep($"item {index + 1} has correct name", () => dialog.ChildrenOfType().ElementAt(index).ChildrenOfType().First().Text == name); diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs index 65b554b27b..4606c7b0c3 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs @@ -42,13 +42,6 @@ namespace osu.Game.Tests.Visual.SongSelect }); } - protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) - { - var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); - dependencies.Cache(collectionManager); - return dependencies; - } - [BackgroundDependencyLoader] private void load(GameHost host) { @@ -58,6 +51,13 @@ namespace osu.Game.Tests.Visual.SongSelect beatmapManager.Import(TestResources.GetTestBeatmapForImport()).Wait(); } + protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) + { + var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); + dependencies.Cache(collectionManager); + return dependencies; + } + [SetUp] public void SetUp() => Schedule(() => { diff --git a/osu.Game/Collections/DrawableCollectionListItem.cs b/osu.Game/Collections/DrawableCollectionListItem.cs index c67946977d..a7075c3179 100644 --- a/osu.Game/Collections/DrawableCollectionListItem.cs +++ b/osu.Game/Collections/DrawableCollectionListItem.cs @@ -27,6 +27,11 @@ namespace osu.Game.Collections private const float item_height = 35; private const float button_width = item_height * 0.75f; + /// + /// Whether the currently exists inside the . + /// + public IBindable IsCreated => isCreated; + private readonly Bindable isCreated = new Bindable(); /// From d2650fc1a05e012a58a2283a59ce4dfdc6e634e3 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 8 Sep 2020 17:12:58 +0900 Subject: [PATCH 0965/1782] Add count to deletion dialog --- osu.Game/Collections/DeleteCollectionDialog.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Collections/DeleteCollectionDialog.cs b/osu.Game/Collections/DeleteCollectionDialog.cs index 8c8c897146..e5a2f6fb81 100644 --- a/osu.Game/Collections/DeleteCollectionDialog.cs +++ b/osu.Game/Collections/DeleteCollectionDialog.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using Humanizer; using osu.Framework.Graphics.Sprites; using osu.Game.Overlays.Dialog; @@ -12,7 +13,7 @@ namespace osu.Game.Collections public DeleteCollectionDialog(BeatmapCollection collection, Action deleteAction) { HeaderText = "Confirm deletion of"; - BodyText = collection.Name.Value; + BodyText = $"{collection.Name.Value} ({"beatmap".ToQuantity(collection.Beatmaps.Count)})"; Icon = FontAwesome.Regular.TrashAlt; From 4737add00bc99290652f585bf85a6acbc4d70c55 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 8 Sep 2020 17:21:29 +0900 Subject: [PATCH 0966/1782] Add close button to dialog --- .../Collections/ManageCollectionsDialog.cs | 32 +++++++++++++++---- 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/osu.Game/Collections/ManageCollectionsDialog.cs b/osu.Game/Collections/ManageCollectionsDialog.cs index 8f8ac9542c..f6964191a1 100644 --- a/osu.Game/Collections/ManageCollectionsDialog.cs +++ b/osu.Game/Collections/ManageCollectionsDialog.cs @@ -5,9 +5,11 @@ 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.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; using osuTK; namespace osu.Game.Collections @@ -56,13 +58,31 @@ namespace osu.Game.Collections { new Drawable[] { - new OsuSpriteText + new Container { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Text = "Manage collections", - Font = OsuFont.GetFont(size: 30), - Padding = new MarginPadding { Vertical = 10 }, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Children = new Drawable[] + { + new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = "Manage collections", + Font = OsuFont.GetFont(size: 30), + Padding = new MarginPadding { Vertical = 10 }, + }, + new IconButton + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Icon = FontAwesome.Solid.Times, + Colour = colours.GreySeafoamDarker, + Scale = new Vector2(0.8f), + X = -10, + Action = () => State.Value = Visibility.Hidden + } + } } }, new Drawable[] From c3123bf11712937fed58039477442717e7e8076b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 8 Sep 2020 17:22:59 +0900 Subject: [PATCH 0967/1782] Rename drag blueprint selection method for discoverability --- .../Screens/Edit/Compose/Components/BlueprintContainer.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index fcff672045..865e225645 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -61,7 +61,7 @@ namespace osu.Game.Screens.Edit.Compose.Components AddRangeInternal(new[] { - DragBox = CreateDragBox(select), + DragBox = CreateDragBox(selectBlueprintsFromDragRectangle), selectionHandler, SelectionBlueprints = CreateSelectionBlueprintContainer(), selectionHandler.CreateProxy(), @@ -326,7 +326,7 @@ namespace osu.Game.Screens.Edit.Compose.Components /// Select all masks in a given rectangle selection area. /// /// The rectangle to perform a selection on in screen-space coordinates. - private void select(RectangleF rect) + private void selectBlueprintsFromDragRectangle(RectangleF rect) { foreach (var blueprint in SelectionBlueprints) { From 06328e00001315d83d70cfabef4a350d434d8399 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 8 Sep 2020 17:58:56 +0900 Subject: [PATCH 0968/1782] Add import/deletion progress notifications --- .../Collections/BeatmapCollectionManager.cs | 56 ++++++++++++++++++- osu.Game/OsuGame.cs | 1 + .../Sections/Maintenance/GeneralSettings.cs | 2 +- 3 files changed, 55 insertions(+), 4 deletions(-) diff --git a/osu.Game/Collections/BeatmapCollectionManager.cs b/osu.Game/Collections/BeatmapCollectionManager.cs index a553ac632e..6a5ed6bbbc 100644 --- a/osu.Game/Collections/BeatmapCollectionManager.cs +++ b/osu.Game/Collections/BeatmapCollectionManager.cs @@ -57,6 +57,10 @@ namespace osu.Game.Collections c.Changed += backgroundSave; Collections.CollectionChanged += (_, __) => backgroundSave(); } + /// + /// Set an endpoint for notifications to be posted to. + /// + public Action PostNotification { protected get; set; } /// /// Set a storage with access to an osu-stable install for import purposes. @@ -93,9 +97,25 @@ namespace osu.Game.Collections }); } - public async Task Import(Stream stream) => await Task.Run(async () => + public async Task Import(Stream stream) { - var collection = readCollections(stream); + var notification = new ProgressNotification + { + State = ProgressNotificationState.Active, + Text = "Collections import is initialising..." + }; + + PostNotification?.Invoke(notification); + + await import(stream, notification); + } + + private async Task import(Stream stream, ProgressNotification notification = null) => await Task.Run(async () => + { + if (notification != null) + notification.Progress = 0; + + var collection = readCollections(stream, notification); bool importCompleted = false; Schedule(() => @@ -106,6 +126,12 @@ namespace osu.Game.Collections while (!IsDisposed && !importCompleted) await Task.Delay(10); + + if (notification != null) + { + notification.CompletionText = $"Imported {collection.Count} collections"; + notification.State = ProgressNotificationState.Completed; + } }); private void importCollections(List newCollections) @@ -124,8 +150,14 @@ namespace osu.Game.Collections } } - private List readCollections(Stream stream) + private List readCollections(Stream stream, ProgressNotification notification = null) { + if (notification != null) + { + notification.Text = "Reading collections..."; + notification.Progress = 0; + } + var result = new List(); try @@ -139,11 +171,17 @@ namespace osu.Game.Collections for (int i = 0; i < collectionCount; i++) { + if (notification?.CancellationToken.IsCancellationRequested == true) + return result; + var collection = new BeatmapCollection { Name = { Value = sr.ReadString() } }; int mapCount = sr.ReadInt32(); for (int j = 0; j < mapCount; j++) { + if (notification?.CancellationToken.IsCancellationRequested == true) + return result; + string checksum = sr.ReadString(); var beatmap = beatmaps.QueryBeatmap(b => b.MD5Hash == checksum); @@ -151,6 +189,12 @@ namespace osu.Game.Collections collection.Beatmaps.Add(beatmap); } + if (notification != null) + { + notification.Text = $"Imported {i + 1} of {collectionCount} collections"; + notification.Progress = (float)(i + 1) / collectionCount; + } + result.Add(collection); } } @@ -163,6 +207,12 @@ namespace osu.Game.Collections return result; } + public void DeleteAll() + { + Collections.Clear(); + PostNotification?.Invoke(new SimpleNotification { Text = "Deleted all collections!" }); + } + private readonly object saveLock = new object(); private int lastSave; private int saveFailures; diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 8434ee11fa..33a353742d 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -537,6 +537,7 @@ namespace osu.Game ScoreManager.GetStableStorage = GetStorageForStableInstall; ScoreManager.PresentImport = items => PresentScore(items.First()); + CollectionManager.PostNotification = n => notifications.Post(n); CollectionManager.GetStableStorage = GetStorageForStableInstall; Container logoContainer; diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs index 74f9920ae0..30fd5921eb 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs @@ -126,7 +126,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance Text = "Delete ALL collections", Action = () => { - dialogOverlay?.Push(new DeleteAllBeatmapsDialog(() => collectionManager.Collections.Clear())); + dialogOverlay?.Push(new DeleteAllBeatmapsDialog(collectionManager.DeleteAll)); } }); From 070704cba719a124bd48c6b5a9133ddaae157e92 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 8 Sep 2020 17:59:38 +0900 Subject: [PATCH 0969/1782] Asyncify initial load --- .../Collections/BeatmapCollectionManager.cs | 41 ++++++++++++++++--- 1 file changed, 36 insertions(+), 5 deletions(-) diff --git a/osu.Game/Collections/BeatmapCollectionManager.cs b/osu.Game/Collections/BeatmapCollectionManager.cs index 6a5ed6bbbc..e4fc4c377b 100644 --- a/osu.Game/Collections/BeatmapCollectionManager.cs +++ b/osu.Game/Collections/BeatmapCollectionManager.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Collections.Specialized; using System.IO; using System.Linq; using System.Threading; @@ -15,6 +16,7 @@ using osu.Framework.Logging; using osu.Framework.Platform; using osu.Game.Beatmaps; using osu.Game.IO.Legacy; +using osu.Game.Overlays.Notifications; namespace osu.Game.Collections { @@ -46,17 +48,46 @@ namespace osu.Game.Collections [BackgroundDependencyLoader] private void load() + { + Collections.CollectionChanged += collectionsChanged; + loadDatabase(); + } + + private void collectionsChanged(object sender, NotifyCollectionChangedEventArgs e) + { + switch (e.Action) + { + case NotifyCollectionChangedAction.Add: + foreach (var c in e.NewItems.Cast()) + c.Changed += backgroundSave; + break; + + case NotifyCollectionChangedAction.Remove: + foreach (var c in e.OldItems.Cast()) + c.Changed -= backgroundSave; + break; + + case NotifyCollectionChangedAction.Replace: + foreach (var c in e.OldItems.Cast()) + c.Changed -= backgroundSave; + + foreach (var c in e.NewItems.Cast()) + c.Changed += backgroundSave; + break; + } + + backgroundSave(); + } + + private void loadDatabase() => Task.Run(async () => { if (storage.Exists(database_name)) { using (var stream = storage.GetStream(database_name)) - importCollections(readCollections(stream)); + await import(stream); } + }); - foreach (var c in Collections) - c.Changed += backgroundSave; - Collections.CollectionChanged += (_, __) => backgroundSave(); - } /// /// Set an endpoint for notifications to be posted to. /// From b1110e5e3a1778a4cf5075728194e524c468f0c1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 8 Sep 2020 18:10:14 +0900 Subject: [PATCH 0970/1782] Rename class to match derived class --- osu.Game/OsuGame.cs | 2 +- .../Music/{MusicActionHandler.cs => MusicKeyBindingHandler.cs} | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename osu.Game/Overlays/Music/{MusicActionHandler.cs => MusicKeyBindingHandler.cs} (96%) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index a73469d836..b4e671d0b0 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -648,7 +648,7 @@ namespace osu.Game chatOverlay.State.ValueChanged += state => channelManager.HighPollRate.Value = state.NewValue == Visibility.Visible; Add(externalLinkOpener = new ExternalLinkOpener()); - Add(new MusicActionHandler()); + Add(new MusicKeyBindingHandler()); // side overlays which cancel each other. var singleDisplaySideOverlays = new OverlayContainer[] { Settings, notifications }; diff --git a/osu.Game/Overlays/Music/MusicActionHandler.cs b/osu.Game/Overlays/Music/MusicKeyBindingHandler.cs similarity index 96% rename from osu.Game/Overlays/Music/MusicActionHandler.cs rename to osu.Game/Overlays/Music/MusicKeyBindingHandler.cs index cd8548c1c0..78e6ba1381 100644 --- a/osu.Game/Overlays/Music/MusicActionHandler.cs +++ b/osu.Game/Overlays/Music/MusicKeyBindingHandler.cs @@ -14,7 +14,7 @@ namespace osu.Game.Overlays.Music /// /// Handles relating to music playback, and displays a via the cached accordingly. /// - public class MusicActionHandler : Component, IKeyBindingHandler + public class MusicKeyBindingHandler : Component, IKeyBindingHandler { [Resolved] private IBindable beatmap { get; set; } From a46be45a71b9b70ca1b70b68d54d33a625070a4c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 8 Sep 2020 18:12:03 +0900 Subject: [PATCH 0971/1782] Fix OSD occasionally display incorrect play/pause state --- osu.Game/Overlays/Music/MusicKeyBindingHandler.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Music/MusicKeyBindingHandler.cs b/osu.Game/Overlays/Music/MusicKeyBindingHandler.cs index 78e6ba1381..277fb1a35d 100644 --- a/osu.Game/Overlays/Music/MusicKeyBindingHandler.cs +++ b/osu.Game/Overlays/Music/MusicKeyBindingHandler.cs @@ -33,9 +33,11 @@ namespace osu.Game.Overlays.Music switch (action) { case GlobalAction.MusicPlay: - if (musicController.TogglePause()) - onScreenDisplay?.Display(new MusicActionToast(musicController.IsPlaying ? "Play track" : "Pause track")); + // use previous state as TogglePause may not update the track's state immediately (state update is run on the audio thread see https://github.com/ppy/osu/issues/9880#issuecomment-674668842) + bool wasPlaying = musicController.IsPlaying; + if (musicController.TogglePause()) + onScreenDisplay?.Display(new MusicActionToast(wasPlaying ? "Pause track" : "Play track")); return true; case GlobalAction.MusicNext: From 14bf2ab936b38c16cccbac254293d28791002b33 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 8 Sep 2020 18:21:26 +0900 Subject: [PATCH 0972/1782] Fix grammar in xmldoc --- osu.Game/Overlays/Music/MusicKeyBindingHandler.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Music/MusicKeyBindingHandler.cs b/osu.Game/Overlays/Music/MusicKeyBindingHandler.cs index 78e6ba1381..02196348ae 100644 --- a/osu.Game/Overlays/Music/MusicKeyBindingHandler.cs +++ b/osu.Game/Overlays/Music/MusicKeyBindingHandler.cs @@ -12,7 +12,7 @@ using osu.Game.Overlays.OSD; namespace osu.Game.Overlays.Music { /// - /// Handles relating to music playback, and displays a via the cached accordingly. + /// Handles s related to music playback, and displays s via the global accordingly. /// public class MusicKeyBindingHandler : Component, IKeyBindingHandler { From f581df47c8edcf03771ae1e019323f0a23301995 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 8 Sep 2020 18:25:09 +0900 Subject: [PATCH 0973/1782] Add "New collection..." item to dropdown --- .../SongSelect/TestSceneFilterControl.cs | 23 +++++++++++++++++++ osu.Game/Screens/Select/CollectionFilter.cs | 17 ++++++++++++++ .../Select/CollectionFilterDropdown.cs | 18 ++++++++++++--- 3 files changed, 55 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs index 4606c7b0c3..955fe04c8c 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs @@ -184,6 +184,29 @@ namespace osu.Game.Tests.Visual.SongSelect AddAssert("button is plus", () => getAddOrRemoveButton(1).Icon.Equals(FontAwesome.Solid.PlusSquare)); } + [Test] + public void TestNewCollectionFilterIsNotSelected() + { + addExpandHeaderStep(); + + AddStep("add collection", () => collectionManager.Collections.Add(new BeatmapCollection { Name = { Value = "1" } })); + AddStep("select collection", () => + { + InputManager.MoveMouseTo(getCollectionDropdownItems().ElementAt(1)); + InputManager.Click(MouseButton.Left); + }); + + addExpandHeaderStep(); + + AddStep("click manage collections filter", () => + { + InputManager.MoveMouseTo(getCollectionDropdownItems().Last()); + InputManager.Click(MouseButton.Left); + }); + + AddAssert("collection filter still selected", () => control.CreateCriteria().Collection?.CollectionName.Value == "1"); + } + private void assertCollectionHeaderDisplays(string collectionName, bool shouldDisplay = true) => AddAssert($"collection dropdown header displays '{collectionName}'", () => shouldDisplay == (control.ChildrenOfType().Single().ChildrenOfType().First().Text == collectionName)); diff --git a/osu.Game/Screens/Select/CollectionFilter.cs b/osu.Game/Screens/Select/CollectionFilter.cs index 7628ed391e..9e36e3e089 100644 --- a/osu.Game/Screens/Select/CollectionFilter.cs +++ b/osu.Game/Screens/Select/CollectionFilter.cs @@ -45,4 +45,21 @@ namespace osu.Game.Screens.Select public virtual bool ContainsBeatmap(BeatmapInfo beatmap) => Collection?.Beatmaps.Any(b => b.Equals(beatmap)) ?? true; } + + public class AllBeatmapCollectionFilter : CollectionFilter + { + public AllBeatmapCollectionFilter() + : base(null) + { + } + } + + public class NewCollectionFilter : CollectionFilter + { + public NewCollectionFilter() + : base(null) + { + CollectionName.Value = "New collection..."; + } + } } diff --git a/osu.Game/Screens/Select/CollectionFilterDropdown.cs b/osu.Game/Screens/Select/CollectionFilterDropdown.cs index 4e9e12fcaf..5e5c684fe2 100644 --- a/osu.Game/Screens/Select/CollectionFilterDropdown.cs +++ b/osu.Game/Screens/Select/CollectionFilterDropdown.cs @@ -29,6 +29,9 @@ namespace osu.Game.Screens.Select private readonly IBindableList beatmaps = new BindableList(); private readonly BindableList filters = new BindableList(); + [Resolved(CanBeNull = true)] + private ManageCollectionsDialog manageCollectionsDialog { get; set; } + public CollectionFilterDropdown() { ItemSource = filters; @@ -57,10 +60,11 @@ namespace osu.Game.Screens.Select var selectedItem = SelectedItem?.Value?.Collection; filters.Clear(); - filters.Add(new CollectionFilter(null)); + filters.Add(new AllBeatmapCollectionFilter()); filters.AddRange(collections.Select(c => new CollectionFilter(c))); + filters.Add(new NewCollectionFilter()); - Current.Value = filters.SingleOrDefault(f => f.Collection == selectedItem) ?? filters[0]; + Current.Value = filters.SingleOrDefault(f => f.Collection != null && f.Collection == selectedItem) ?? filters[0]; } /// @@ -78,6 +82,14 @@ namespace osu.Game.Screens.Select beatmaps.BindTo(filter.NewValue.Collection.Beatmaps); beatmaps.CollectionChanged += filterBeatmapsChanged; + + // Never select the manage collection filter - rollback to the previous filter. + // This is done after the above since it is important that bindable is unbound from OldValue, which is lost after forcing it back to the old value. + if (filter.NewValue is NewCollectionFilter) + { + Current.Value = filter.OldValue; + manageCollectionsDialog?.Show(); + } } /// @@ -90,7 +102,7 @@ namespace osu.Game.Screens.Select Current.TriggerChange(); } - protected override string GenerateItemText(CollectionFilter item) => item.Collection?.Name.Value ?? "All beatmaps"; + protected override string GenerateItemText(CollectionFilter item) => item.CollectionName.Value; protected override DropdownHeader CreateHeader() => new CollectionDropdownHeader { From ad5d6117c76856237d2215a89f8c2f7c2ab7524d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 8 Sep 2020 18:26:13 +0900 Subject: [PATCH 0974/1782] Remove unnecessary RunTask calls --- .../Overlays/Music/MusicKeyBindingHandler.cs | 7 ++----- osu.Game/Overlays/MusicController.cs | 17 ++++------------- 2 files changed, 6 insertions(+), 18 deletions(-) diff --git a/osu.Game/Overlays/Music/MusicKeyBindingHandler.cs b/osu.Game/Overlays/Music/MusicKeyBindingHandler.cs index 02196348ae..f5968614cd 100644 --- a/osu.Game/Overlays/Music/MusicKeyBindingHandler.cs +++ b/osu.Game/Overlays/Music/MusicKeyBindingHandler.cs @@ -39,10 +39,7 @@ namespace osu.Game.Overlays.Music return true; case GlobalAction.MusicNext: - musicController.NextTrack(() => - { - onScreenDisplay?.Display(new MusicActionToast("Next track")); - }).RunTask(); + musicController.NextTrack(() => onScreenDisplay?.Display(new MusicActionToast("Next track"))); return true; @@ -59,7 +56,7 @@ namespace osu.Game.Overlays.Music onScreenDisplay?.Display(new MusicActionToast("Previous track")); break; } - }).RunTask(); + }); return true; } diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index a405be1b74..b568e4d02b 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -201,13 +201,8 @@ namespace osu.Game.Overlays /// /// Play the previous track or restart the current track if it's current time below . /// - /// - /// Invoked when the operation has been performed successfully. - /// The result isn't returned directly to the caller because - /// the operation is scheduled and isn't performed immediately. - /// - /// A of the operation. - public ScheduledDelegate PreviousTrack(Action onSuccess = null) => Schedule(() => + /// Invoked when the operation has been performed successfully. + public void PreviousTrack(Action onSuccess = null) => Schedule(() => { PreviousTrackResult res = prev(); if (res != PreviousTrackResult.None) @@ -248,13 +243,9 @@ namespace osu.Game.Overlays /// /// Play the next random or playlist track. /// - /// - /// Invoked when the operation has been performed successfully. - /// The result isn't returned directly to the caller because - /// the operation is scheduled and isn't performed immediately. - /// + /// Invoked when the operation has been performed successfully. /// A of the operation. - public ScheduledDelegate NextTrack(Action onSuccess = null) => Schedule(() => + public void NextTrack(Action onSuccess = null) => Schedule(() => { bool res = next(); if (res) From e1053c4b6f9376884786084e0d48a26962f6edcf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 8 Sep 2020 18:36:11 +0900 Subject: [PATCH 0975/1782] Revert exposure changes to GlobalActionContainer --- .../Visual/Menus/TestSceneMusicActionHandling.cs | 13 ++++++++----- .../Visual/Navigation/OsuGameTestScene.cs | 3 --- osu.Game/OsuGameBase.cs | 11 ++++++----- 3 files changed, 14 insertions(+), 13 deletions(-) diff --git a/osu.Game.Tests/Visual/Menus/TestSceneMusicActionHandling.cs b/osu.Game.Tests/Visual/Menus/TestSceneMusicActionHandling.cs index 9b8ba47992..4cad2b19d5 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneMusicActionHandling.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneMusicActionHandling.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Linq; using NUnit.Framework; +using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Input.Bindings; using osu.Game.Overlays; @@ -14,13 +15,15 @@ namespace osu.Game.Tests.Visual.Menus { public class TestSceneMusicActionHandling : OsuGameTestScene { + private GlobalActionContainer globalActionContainer => Game.ChildrenOfType().First(); + [Test] public void TestMusicPlayAction() { AddStep("ensure playing something", () => Game.MusicController.EnsurePlayingSomething()); - AddStep("toggle playback", () => Game.GlobalBinding.TriggerPressed(GlobalAction.MusicPlay)); + AddStep("toggle playback", () => globalActionContainer.TriggerPressed(GlobalAction.MusicPlay)); AddAssert("music paused", () => !Game.MusicController.IsPlaying && Game.MusicController.IsUserPaused); - AddStep("toggle playback", () => Game.GlobalBinding.TriggerPressed(GlobalAction.MusicPlay)); + AddStep("toggle playback", () => globalActionContainer.TriggerPressed(GlobalAction.MusicPlay)); AddAssert("music resumed", () => Game.MusicController.IsPlaying && !Game.MusicController.IsUserPaused); } @@ -62,16 +65,16 @@ namespace osu.Game.Tests.Visual.Menus AddStep("seek track to 6 second", () => Game.MusicController.SeekTo(6000)); AddUntilStep("wait for current time to update", () => Game.MusicController.CurrentTrack.CurrentTime > 5000); - AddStep("press previous", () => Game.GlobalBinding.TriggerPressed(GlobalAction.MusicPrev)); + AddStep("press previous", () => globalActionContainer.TriggerPressed(GlobalAction.MusicPrev)); AddAssert("no track change", () => trackChangeQueue.Count == 0); AddUntilStep("track restarted", () => Game.MusicController.CurrentTrack.CurrentTime < 5000); - AddStep("press previous", () => Game.GlobalBinding.TriggerPressed(GlobalAction.MusicPrev)); + AddStep("press previous", () => globalActionContainer.TriggerPressed(GlobalAction.MusicPrev)); AddAssert("track changed to previous", () => trackChangeQueue.Count == 1 && trackChangeQueue.Dequeue().changeDirection == TrackChangeDirection.Prev); - AddStep("press next", () => Game.GlobalBinding.TriggerPressed(GlobalAction.MusicNext)); + AddStep("press next", () => globalActionContainer.TriggerPressed(GlobalAction.MusicNext)); AddAssert("track changed to next", () => trackChangeQueue.Count == 1 && trackChangeQueue.Dequeue().changeDirection == TrackChangeDirection.Next); diff --git a/osu.Game.Tests/Visual/Navigation/OsuGameTestScene.cs b/osu.Game.Tests/Visual/Navigation/OsuGameTestScene.cs index e29d23ba75..c4acf4f7da 100644 --- a/osu.Game.Tests/Visual/Navigation/OsuGameTestScene.cs +++ b/osu.Game.Tests/Visual/Navigation/OsuGameTestScene.cs @@ -14,7 +14,6 @@ using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Graphics.UserInterface; -using osu.Game.Input.Bindings; using osu.Game.Online.API; using osu.Game.Overlays; using osu.Game.Rulesets; @@ -110,8 +109,6 @@ namespace osu.Game.Tests.Visual.Navigation public new OsuConfigManager LocalConfig => base.LocalConfig; - public new GlobalActionContainer GlobalBinding => base.GlobalBinding; - public new Bindable Beatmap => base.Beatmap; public new Bindable Ruleset => base.Ruleset; diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 8e01bda6ec..4bc8f4c527 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -64,8 +64,6 @@ namespace osu.Game protected FileStore FileStore; - protected GlobalActionContainer GlobalBinding; - protected KeyBindingStore KeyBindingStore; protected SettingsStore SettingsStore; @@ -253,7 +251,10 @@ namespace osu.Game AddInternal(RulesetConfigCache); MenuCursorContainer = new MenuCursorContainer { RelativeSizeAxes = Axes.Both }; - MenuCursorContainer.Child = GlobalBinding = new GlobalActionContainer(this) + + GlobalActionContainer globalBindings; + + MenuCursorContainer.Child = globalBindings = new GlobalActionContainer(this) { RelativeSizeAxes = Axes.Both, Child = content = new OsuTooltipContainer(MenuCursorContainer.Cursor) { RelativeSizeAxes = Axes.Both } @@ -261,8 +262,8 @@ namespace osu.Game base.Content.Add(CreateScalingContainer().WithChild(MenuCursorContainer)); - KeyBindingStore.Register(GlobalBinding); - dependencies.Cache(GlobalBinding); + KeyBindingStore.Register(globalBindings); + dependencies.Cache(globalBindings); PreviewTrackManager previewTrackManager; dependencies.Cache(previewTrackManager = new PreviewTrackManager()); From 4962213cc499eecf9df2f876fc8b14672b11b104 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 8 Sep 2020 18:42:55 +0900 Subject: [PATCH 0976/1782] Rename manage collections filter/text --- osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs | 2 +- osu.Game/Screens/Select/CollectionFilter.cs | 6 +++--- osu.Game/Screens/Select/CollectionFilterDropdown.cs | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs index 955fe04c8c..5b0e244bbe 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs @@ -185,7 +185,7 @@ namespace osu.Game.Tests.Visual.SongSelect } [Test] - public void TestNewCollectionFilterIsNotSelected() + public void TestManageCollectionsFilterIsNotSelected() { addExpandHeaderStep(); diff --git a/osu.Game/Screens/Select/CollectionFilter.cs b/osu.Game/Screens/Select/CollectionFilter.cs index 9e36e3e089..883019ab06 100644 --- a/osu.Game/Screens/Select/CollectionFilter.cs +++ b/osu.Game/Screens/Select/CollectionFilter.cs @@ -54,12 +54,12 @@ namespace osu.Game.Screens.Select } } - public class NewCollectionFilter : CollectionFilter + public class ManageCollectionsFilter : CollectionFilter { - public NewCollectionFilter() + public ManageCollectionsFilter() : base(null) { - CollectionName.Value = "New collection..."; + CollectionName.Value = "Manage collections..."; } } } diff --git a/osu.Game/Screens/Select/CollectionFilterDropdown.cs b/osu.Game/Screens/Select/CollectionFilterDropdown.cs index 5e5c684fe2..6b9ae1b5c8 100644 --- a/osu.Game/Screens/Select/CollectionFilterDropdown.cs +++ b/osu.Game/Screens/Select/CollectionFilterDropdown.cs @@ -62,7 +62,7 @@ namespace osu.Game.Screens.Select filters.Clear(); filters.Add(new AllBeatmapCollectionFilter()); filters.AddRange(collections.Select(c => new CollectionFilter(c))); - filters.Add(new NewCollectionFilter()); + filters.Add(new ManageCollectionsFilter()); Current.Value = filters.SingleOrDefault(f => f.Collection != null && f.Collection == selectedItem) ?? filters[0]; } @@ -85,7 +85,7 @@ namespace osu.Game.Screens.Select // Never select the manage collection filter - rollback to the previous filter. // This is done after the above since it is important that bindable is unbound from OldValue, which is lost after forcing it back to the old value. - if (filter.NewValue is NewCollectionFilter) + if (filter.NewValue is ManageCollectionsFilter) { Current.Value = filter.OldValue; manageCollectionsDialog?.Show(); From ae022d755964536843265ab1e1b7169edeef40a3 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 8 Sep 2020 18:55:53 +0900 Subject: [PATCH 0977/1782] Show all items in dropdown, set global max height --- osu.Game/Graphics/UserInterface/OsuContextMenu.cs | 2 ++ osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs | 2 +- osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/OsuContextMenu.cs b/osu.Game/Graphics/UserInterface/OsuContextMenu.cs index 4b629080e1..8c7b44f952 100644 --- a/osu.Game/Graphics/UserInterface/OsuContextMenu.cs +++ b/osu.Game/Graphics/UserInterface/OsuContextMenu.cs @@ -26,6 +26,8 @@ namespace osu.Game.Graphics.UserInterface }; ItemsContainer.Padding = new MarginPadding { Vertical = DrawableOsuMenuItem.MARGIN_VERTICAL }; + + MaxHeight = 250; } [BackgroundDependencyLoader] diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs index 9f21ec1ad1..cf1c51acd1 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs @@ -224,7 +224,7 @@ namespace osu.Game.Screens.Select.Carousel if (beatmap.OnlineBeatmapID.HasValue && beatmapOverlay != null) items.Add(new OsuMenuItem("Details...", MenuItemType.Standard, () => beatmapOverlay.FetchAndShowBeatmap(beatmap.OnlineBeatmapID.Value))); - var collectionItems = collectionManager.Collections.OrderByDescending(c => c.LastModifyDate).Take(3).Select(createCollectionMenuItem).ToList(); + var collectionItems = collectionManager.Collections.Select(createCollectionMenuItem).ToList(); if (manageCollectionsDialog != null) collectionItems.Add(new OsuMenuItem("More...", MenuItemType.Standard, manageCollectionsDialog.Show)); diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index 19ecc277c4..2c098291fa 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -142,7 +142,7 @@ namespace osu.Game.Screens.Select.Carousel if (beatmapSet.OnlineBeatmapSetID != null && viewDetails != null) items.Add(new OsuMenuItem("Details...", MenuItemType.Standard, () => viewDetails(beatmapSet.OnlineBeatmapSetID.Value))); - var collectionItems = collectionManager.Collections.OrderByDescending(c => c.LastModifyDate).Take(3).Select(createCollectionMenuItem).ToList(); + var collectionItems = collectionManager.Collections.Select(createCollectionMenuItem).ToList(); if (manageCollectionsDialog != null) collectionItems.Add(new OsuMenuItem("More...", MenuItemType.Standard, manageCollectionsDialog.Show)); From a5e1e8d043c98438e748d4c956febb6e29900a56 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 8 Sep 2020 18:57:18 +0900 Subject: [PATCH 0978/1782] Rename More... to Manage... --- osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs | 2 +- osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs index cf1c51acd1..e9990ab078 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs @@ -226,7 +226,7 @@ namespace osu.Game.Screens.Select.Carousel var collectionItems = collectionManager.Collections.Select(createCollectionMenuItem).ToList(); if (manageCollectionsDialog != null) - collectionItems.Add(new OsuMenuItem("More...", MenuItemType.Standard, manageCollectionsDialog.Show)); + collectionItems.Add(new OsuMenuItem("Manage...", MenuItemType.Standard, manageCollectionsDialog.Show)); items.Add(new OsuMenuItem("Collections") { Items = collectionItems }); diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index 2c098291fa..fe700f12df 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -144,7 +144,7 @@ namespace osu.Game.Screens.Select.Carousel var collectionItems = collectionManager.Collections.Select(createCollectionMenuItem).ToList(); if (manageCollectionsDialog != null) - collectionItems.Add(new OsuMenuItem("More...", MenuItemType.Standard, manageCollectionsDialog.Show)); + collectionItems.Add(new OsuMenuItem("Manage...", MenuItemType.Standard, manageCollectionsDialog.Show)); items.Add(new OsuMenuItem("Collections") { Items = collectionItems }); From 2b4e2d8ed63c4500aba1fb3236fb481469caab6e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 8 Sep 2020 19:04:46 +0900 Subject: [PATCH 0979/1782] Standardise corner radius of dropdowns --- osu.Game/Graphics/UserInterface/OsuDropdown.cs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/OsuDropdown.cs b/osu.Game/Graphics/UserInterface/OsuDropdown.cs index fc3a7229fa..cc76c12975 100644 --- a/osu.Game/Graphics/UserInterface/OsuDropdown.cs +++ b/osu.Game/Graphics/UserInterface/OsuDropdown.cs @@ -17,6 +17,8 @@ namespace osu.Game.Graphics.UserInterface { public class OsuDropdown : Dropdown, IHasAccentColour { + private const float corner_radius = 4; + private Color4 accentColour; public Color4 AccentColour @@ -57,9 +59,11 @@ namespace osu.Game.Graphics.UserInterface // todo: this uses the same styling as OsuMenu. hopefully we can just use OsuMenu in the future with some refactoring public OsuDropdownMenu() { - CornerRadius = 4; + CornerRadius = corner_radius; BackgroundColour = Color4.Black.Opacity(0.5f); + MaskingContainer.CornerRadius = corner_radius; + // todo: this uses the same styling as OsuMenu. hopefully we can just use OsuMenu in the future with some refactoring ItemsContainer.Padding = new MarginPadding(5); } @@ -138,7 +142,7 @@ namespace osu.Game.Graphics.UserInterface Foreground.Padding = new MarginPadding(2); Masking = true; - CornerRadius = 6; + CornerRadius = corner_radius; } [BackgroundDependencyLoader] @@ -237,7 +241,7 @@ namespace osu.Game.Graphics.UserInterface AutoSizeAxes = Axes.None; Margin = new MarginPadding { Bottom = 4 }; - CornerRadius = 4; + CornerRadius = corner_radius; Height = 40; Foreground.Children = new Drawable[] From b7ca0039282769ea2388da23af8e9884a7c265c4 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 8 Sep 2020 19:14:48 +0900 Subject: [PATCH 0980/1782] Remove unnecessary check --- osu.Game/Collections/BeatmapCollectionManager.cs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/osu.Game/Collections/BeatmapCollectionManager.cs b/osu.Game/Collections/BeatmapCollectionManager.cs index e4fc4c377b..c14b67a7e8 100644 --- a/osu.Game/Collections/BeatmapCollectionManager.cs +++ b/osu.Game/Collections/BeatmapCollectionManager.cs @@ -120,11 +120,8 @@ namespace osu.Game.Collections return Task.Run(async () => { - if (stable.Exists(database_name)) - { - using (var stream = stable.GetStream(database_name)) - await Import(stream); - } + using (var stream = stable.GetStream(database_name)) + await Import(stream); }); } From 8e2f5d4ea85be3d3f62678d2a84094e52ed15d37 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 8 Sep 2020 19:41:05 +0900 Subject: [PATCH 0981/1782] Fix test failures --- .../Collections/IO/ImportCollectionsTest.cs | 55 ++++++++++++------- .../Collections/BeatmapCollectionManager.cs | 7 +++ 2 files changed, 42 insertions(+), 20 deletions(-) diff --git a/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs b/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs index 7d772d3989..95013859f0 100644 --- a/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs +++ b/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs @@ -8,8 +8,8 @@ using System.Threading; using System.Threading.Tasks; using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Graphics; using osu.Framework.Platform; -using osu.Game.Beatmaps; using osu.Game.Collections; using osu.Game.Tests.Resources; @@ -21,11 +21,11 @@ namespace osu.Game.Tests.Collections.IO [Test] public async Task TestImportEmptyDatabase() { - using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportEmptyDatabase")) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) { try { - var osu = await loadOsu(host); + var osu = loadOsu(host); var collectionManager = osu.Dependencies.Get(); await collectionManager.Import(new MemoryStream()); @@ -42,11 +42,11 @@ namespace osu.Game.Tests.Collections.IO [Test] public async Task TestImportWithNoBeatmaps() { - using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportWithNoBeatmaps")) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) { try { - var osu = await loadOsu(host); + var osu = loadOsu(host); var collectionManager = osu.Dependencies.Get(); await collectionManager.Import(TestResources.OpenResource("Collections/collections.db")); @@ -69,11 +69,11 @@ namespace osu.Game.Tests.Collections.IO [Test] public async Task TestImportWithBeatmaps() { - using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportWithBeatmaps")) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) { try { - var osu = await loadOsu(host, true); + var osu = loadOsu(host, true); var collectionManager = osu.Dependencies.Get(); await collectionManager.Import(TestResources.OpenResource("Collections/collections.db")); @@ -99,13 +99,13 @@ namespace osu.Game.Tests.Collections.IO bool exceptionThrown = false; UnhandledExceptionEventHandler setException = (_, __) => exceptionThrown = true; - using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportMalformedDatabase")) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) { try { AppDomain.CurrentDomain.UnhandledException += setException; - var osu = await loadOsu(host, true); + var osu = loadOsu(host, true); var collectionManager = osu.Dependencies.Get(); @@ -137,11 +137,11 @@ namespace osu.Game.Tests.Collections.IO [Test] public async Task TestSaveAndReload() { - using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestSaveAndReload")) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) { try { - var osu = await loadOsu(host, true); + var osu = loadOsu(host, true); var collectionManager = osu.Dependencies.Get(); await collectionManager.Import(TestResources.OpenResource("Collections/collections.db")); @@ -163,7 +163,7 @@ namespace osu.Game.Tests.Collections.IO { try { - var osu = await loadOsu(host, true); + var osu = loadOsu(host, true); var collectionManager = osu.Dependencies.Get(); @@ -182,9 +182,9 @@ namespace osu.Game.Tests.Collections.IO } } - private async Task loadOsu(GameHost host, bool withBeatmap = false) + private OsuGameBase loadOsu(GameHost host, bool withBeatmap = false) { - var osu = new OsuGameBase(); + var osu = new TestOsuGameBase(withBeatmap); #pragma warning disable 4014 Task.Run(() => host.Run(osu)); @@ -192,12 +192,8 @@ namespace osu.Game.Tests.Collections.IO waitForOrAssert(() => osu.IsLoaded, @"osu! failed to start in a reasonable amount of time"); - if (withBeatmap) - { - var beatmapFile = TestResources.GetTestBeatmapForImport(); - var beatmapManager = osu.Dependencies.Get(); - await beatmapManager.Import(beatmapFile); - } + var collectionManager = osu.Dependencies.Get(); + waitForOrAssert(() => collectionManager.DatabaseLoaded, "Collection database did not load in a reasonable amount of time"); return osu; } @@ -211,5 +207,24 @@ namespace osu.Game.Tests.Collections.IO Assert.IsTrue(task.Wait(timeout), failureMessage); } + + private class TestOsuGameBase : OsuGameBase + { + private readonly bool withBeatmap; + + public TestOsuGameBase(bool withBeatmap) + { + this.withBeatmap = withBeatmap; + } + + protected override void AddInternal(Drawable drawable) + { + // The beatmap must be imported just before the collection manager is loaded. + if (drawable is BeatmapCollectionManager && withBeatmap) + BeatmapManager.Import(TestResources.GetTestBeatmapForImport()).Wait(); + + base.AddInternal(drawable); + } + } } } diff --git a/osu.Game/Collections/BeatmapCollectionManager.cs b/osu.Game/Collections/BeatmapCollectionManager.cs index c14b67a7e8..ed41627d63 100644 --- a/osu.Game/Collections/BeatmapCollectionManager.cs +++ b/osu.Game/Collections/BeatmapCollectionManager.cs @@ -33,6 +33,11 @@ namespace osu.Game.Collections public bool SupportsImportFromStable => RuntimeInfo.IsDesktop; + /// + /// Whether the user's database has finished loading. + /// + public bool DatabaseLoaded { get; private set; } + [Resolved] private GameHost host { get; set; } @@ -86,6 +91,8 @@ namespace osu.Game.Collections using (var stream = storage.GetStream(database_name)) await import(stream); } + + DatabaseLoaded = true; }); /// From a501df954b3337ef6782a07aeb565a0172c7c7b9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 8 Sep 2020 19:50:29 +0900 Subject: [PATCH 0982/1782] Avoid multiple editor screens potentially loading on top of each other --- osu.Game/Screens/Edit/Editor.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 3ba3eb108a..ac1f61c4fd 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -398,7 +398,11 @@ namespace osu.Game.Screens.Edit break; } - LoadComponentAsync(currentScreen, screenContainer.Add); + LoadComponentAsync(currentScreen, newScreen => + { + if (newScreen == currentScreen) + screenContainer.Add(newScreen); + }); } private void seek(UIEvent e, int direction) From 379fdadbe54e34ea99e57a86106b94bdd9b8bcd9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 7 Sep 2020 18:47:14 +0900 Subject: [PATCH 0983/1782] Add test scene for setup screen --- .../Visual/Editing/TestSceneSetupScreen.cs | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 osu.Game.Tests/Visual/Editing/TestSceneSetupScreen.cs diff --git a/osu.Game.Tests/Visual/Editing/TestSceneSetupScreen.cs b/osu.Game.Tests/Visual/Editing/TestSceneSetupScreen.cs new file mode 100644 index 0000000000..62e12158ab --- /dev/null +++ b/osu.Game.Tests/Visual/Editing/TestSceneSetupScreen.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 NUnit.Framework; +using osu.Framework.Allocation; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Osu.Beatmaps; +using osu.Game.Screens.Edit; +using osu.Game.Screens.Edit.Setup; + +namespace osu.Game.Tests.Visual.Editing +{ + [TestFixture] + public class TestSceneSetupScreen : EditorClockTestScene + { + [Cached(typeof(EditorBeatmap))] + [Cached(typeof(IBeatSnapProvider))] + private readonly EditorBeatmap editorBeatmap; + + public TestSceneSetupScreen() + { + editorBeatmap = new EditorBeatmap(new OsuBeatmap()); + } + + [BackgroundDependencyLoader] + private void load() + { + Beatmap.Value = CreateWorkingBeatmap(editorBeatmap.PlayableBeatmap); + Child = new SetupScreen(); + } + } +} From f43f8cf6b95bebf5c18683acdb0e96a6ff731fb3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 7 Sep 2020 19:21:35 +0900 Subject: [PATCH 0984/1782] Add basic setup for song select screen --- osu.Game/Screens/Edit/Setup/SetupScreen.cs | 74 +++++++++++++++++++++- 1 file changed, 72 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Setup/SetupScreen.cs b/osu.Game/Screens/Edit/Setup/SetupScreen.cs index 758dbc6e16..84e96a14e2 100644 --- a/osu.Game/Screens/Edit/Setup/SetupScreen.cs +++ b/osu.Game/Screens/Edit/Setup/SetupScreen.cs @@ -1,13 +1,83 @@ // 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.Beatmaps.Drawables; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterfaceV2; +using osuTK; + namespace osu.Game.Screens.Edit.Setup { public class SetupScreen : EditorScreen { - public SetupScreen() + [BackgroundDependencyLoader] + private void load(OsuColour colours) { - Child = new ScreenWhiteBox.UnderConstructionMessage("Setup mode"); + Children = new Drawable[] + { + new Box + { + Colour = colours.Gray0, + RelativeSizeAxes = Axes.Both, + }, + new OsuScrollContainer + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding(50), + Child = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Spacing = new Vector2(20), + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + new Container + { + RelativeSizeAxes = Axes.X, + Height = 250, + Masking = true, + CornerRadius = 50, + Child = new BeatmapBackgroundSprite(Beatmap.Value) + { + RelativeSizeAxes = Axes.Both, + FillMode = FillMode.Fill, + }, + }, + new OsuSpriteText + { + Text = "Beatmap metadata" + }, + new LabelledTextBox + { + Label = "Artist", + Current = { Value = Beatmap.Value.Metadata.Artist } + }, + new LabelledTextBox + { + Label = "Title", + Current = { Value = Beatmap.Value.Metadata.Title } + }, + new LabelledTextBox + { + Label = "Creator", + Current = { Value = Beatmap.Value.Metadata.AuthorString } + }, + new LabelledTextBox + { + Label = "Difficulty Name", + Current = { Value = Beatmap.Value.BeatmapInfo.Version } + }, + } + }, + }, + }; } } } From fe31edfa26a126c1a3d55a1cad2c51e60ce3aaa7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 8 Sep 2020 19:28:20 +0900 Subject: [PATCH 0985/1782] Add rudimentary saving logic --- osu.Game/Screens/Edit/Setup/SetupScreen.cs | 34 ++++++++++++++++++---- 1 file changed, 29 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Edit/Setup/SetupScreen.cs b/osu.Game/Screens/Edit/Setup/SetupScreen.cs index 84e96a14e2..7ea810c514 100644 --- a/osu.Game/Screens/Edit/Setup/SetupScreen.cs +++ b/osu.Game/Screens/Edit/Setup/SetupScreen.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.Linq; 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.Beatmaps.Drawables; using osu.Game.Graphics; using osu.Game.Graphics.Containers; @@ -16,6 +18,12 @@ namespace osu.Game.Screens.Edit.Setup { public class SetupScreen : EditorScreen { + private FillFlowContainer flow; + private LabelledTextBox artistTextBox; + private LabelledTextBox titleTextBox; + private LabelledTextBox creatorTextBox; + private LabelledTextBox difficultyTextBox; + [BackgroundDependencyLoader] private void load(OsuColour colours) { @@ -24,13 +32,14 @@ namespace osu.Game.Screens.Edit.Setup new Box { Colour = colours.Gray0, + Alpha = 0.4f, RelativeSizeAxes = Axes.Both, }, new OsuScrollContainer { RelativeSizeAxes = Axes.Both, Padding = new MarginPadding(50), - Child = new FillFlowContainer + Child = flow = new FillFlowContainer { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, @@ -54,22 +63,22 @@ namespace osu.Game.Screens.Edit.Setup { Text = "Beatmap metadata" }, - new LabelledTextBox + artistTextBox = new LabelledTextBox { Label = "Artist", Current = { Value = Beatmap.Value.Metadata.Artist } }, - new LabelledTextBox + titleTextBox = new LabelledTextBox { Label = "Title", Current = { Value = Beatmap.Value.Metadata.Title } }, - new LabelledTextBox + creatorTextBox = new LabelledTextBox { Label = "Creator", Current = { Value = Beatmap.Value.Metadata.AuthorString } }, - new LabelledTextBox + difficultyTextBox = new LabelledTextBox { Label = "Difficulty Name", Current = { Value = Beatmap.Value.BeatmapInfo.Version } @@ -78,6 +87,21 @@ namespace osu.Game.Screens.Edit.Setup }, }, }; + + foreach (var item in flow.OfType()) + item.OnCommit += onCommit; + } + + private void onCommit(TextBox sender, bool newText) + { + if (!newText) return; + + // for now, update these on commit rather than making BeatmapMetadata bindables. + // after switching database engines we can reconsider if switching to bindables is a good direction. + Beatmap.Value.Metadata.Artist = artistTextBox.Current.Value; + Beatmap.Value.Metadata.Title = titleTextBox.Current.Value; + Beatmap.Value.Metadata.AuthorString = creatorTextBox.Current.Value; + Beatmap.Value.BeatmapInfo.Version = difficultyTextBox.Current.Value; } } } From c8281b17bdee45478edfbb71ecfec3541d1e1e7b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 8 Sep 2020 19:49:26 +0900 Subject: [PATCH 0986/1782] Remove editor screen fade (looks bad) --- osu.Game/Screens/Edit/EditorScreen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/EditorScreen.cs b/osu.Game/Screens/Edit/EditorScreen.cs index d42447ac4b..8b5f0aaa71 100644 --- a/osu.Game/Screens/Edit/EditorScreen.cs +++ b/osu.Game/Screens/Edit/EditorScreen.cs @@ -43,7 +43,7 @@ namespace osu.Game.Screens.Edit public void Exit() { - this.FadeOut(250).Expire(); + Expire(); } } } From b55b6e374699e06eed4c0178ad88eec195b4d972 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 8 Sep 2020 19:50:44 +0900 Subject: [PATCH 0987/1782] Bring design somewhat in line with collections dialog --- osu.Game/Screens/Edit/Setup/SetupScreen.cs | 112 ++++++++++++--------- 1 file changed, 62 insertions(+), 50 deletions(-) diff --git a/osu.Game/Screens/Edit/Setup/SetupScreen.cs b/osu.Game/Screens/Edit/Setup/SetupScreen.cs index 7ea810c514..da8eb3a3b3 100644 --- a/osu.Game/Screens/Edit/Setup/SetupScreen.cs +++ b/osu.Game/Screens/Edit/Setup/SetupScreen.cs @@ -27,65 +27,77 @@ namespace osu.Game.Screens.Edit.Setup [BackgroundDependencyLoader] private void load(OsuColour colours) { - Children = new Drawable[] + Child = new Container { - new Box - { - Colour = colours.Gray0, - Alpha = 0.4f, - RelativeSizeAxes = Axes.Both, - }, - new OsuScrollContainer + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding(50), + Child = new Container { RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding(50), - Child = flow = new FillFlowContainer + Masking = true, + CornerRadius = 10, + Children = new Drawable[] { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Spacing = new Vector2(20), - Direction = FillDirection.Vertical, - Children = new Drawable[] + new Box { - new Container + Colour = colours.GreySeafoamDark, + RelativeSizeAxes = Axes.Both, + }, + new OsuScrollContainer + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding(10), + Child = flow = new FillFlowContainer { RelativeSizeAxes = Axes.X, - Height = 250, - Masking = true, - CornerRadius = 50, - Child = new BeatmapBackgroundSprite(Beatmap.Value) + AutoSizeAxes = Axes.Y, + Spacing = new Vector2(20), + Direction = FillDirection.Vertical, + Children = new Drawable[] { - RelativeSizeAxes = Axes.Both, - FillMode = FillMode.Fill, - }, + new Container + { + RelativeSizeAxes = Axes.X, + Height = 250, + Masking = true, + CornerRadius = 10, + Child = new BeatmapBackgroundSprite(Beatmap.Value) + { + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + FillMode = FillMode.Fill, + }, + }, + new OsuSpriteText + { + Text = "Beatmap metadata" + }, + artistTextBox = new LabelledTextBox + { + Label = "Artist", + Current = { Value = Beatmap.Value.Metadata.Artist } + }, + titleTextBox = new LabelledTextBox + { + Label = "Title", + Current = { Value = Beatmap.Value.Metadata.Title } + }, + creatorTextBox = new LabelledTextBox + { + Label = "Creator", + Current = { Value = Beatmap.Value.Metadata.AuthorString } + }, + difficultyTextBox = new LabelledTextBox + { + Label = "Difficulty Name", + Current = { Value = Beatmap.Value.BeatmapInfo.Version } + }, + } }, - new OsuSpriteText - { - Text = "Beatmap metadata" - }, - artistTextBox = new LabelledTextBox - { - Label = "Artist", - Current = { Value = Beatmap.Value.Metadata.Artist } - }, - titleTextBox = new LabelledTextBox - { - Label = "Title", - Current = { Value = Beatmap.Value.Metadata.Title } - }, - creatorTextBox = new LabelledTextBox - { - Label = "Creator", - Current = { Value = Beatmap.Value.Metadata.AuthorString } - }, - difficultyTextBox = new LabelledTextBox - { - Label = "Difficulty Name", - Current = { Value = Beatmap.Value.BeatmapInfo.Version } - }, - } - }, - }, + }, + } + } }; foreach (var item in flow.OfType()) From c38e7d796a577c477fbb844376dc6902667aa015 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 8 Sep 2020 19:51:31 +0900 Subject: [PATCH 0988/1782] Fix tab key not working --- osu.Game/Graphics/UserInterfaceV2/LabelledTextBox.cs | 6 ++++++ osu.Game/Screens/Edit/Setup/SetupScreen.cs | 12 ++++++++---- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/osu.Game/Graphics/UserInterfaceV2/LabelledTextBox.cs b/osu.Game/Graphics/UserInterfaceV2/LabelledTextBox.cs index 2cbe095d0b..290aba3468 100644 --- a/osu.Game/Graphics/UserInterfaceV2/LabelledTextBox.cs +++ b/osu.Game/Graphics/UserInterfaceV2/LabelledTextBox.cs @@ -3,6 +3,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.UserInterface; using osu.Game.Graphics.UserInterface; @@ -32,6 +33,11 @@ namespace osu.Game.Graphics.UserInterfaceV2 set => Component.Text = value; } + public Container TabbableContentContainer + { + set => Component.TabbableContentContainer = value; + } + [BackgroundDependencyLoader] private void load(OsuColour colours) { diff --git a/osu.Game/Screens/Edit/Setup/SetupScreen.cs b/osu.Game/Screens/Edit/Setup/SetupScreen.cs index da8eb3a3b3..a2c8f19016 100644 --- a/osu.Game/Screens/Edit/Setup/SetupScreen.cs +++ b/osu.Game/Screens/Edit/Setup/SetupScreen.cs @@ -76,22 +76,26 @@ namespace osu.Game.Screens.Edit.Setup artistTextBox = new LabelledTextBox { Label = "Artist", - Current = { Value = Beatmap.Value.Metadata.Artist } + Current = { Value = Beatmap.Value.Metadata.Artist }, + TabbableContentContainer = this }, titleTextBox = new LabelledTextBox { Label = "Title", - Current = { Value = Beatmap.Value.Metadata.Title } + Current = { Value = Beatmap.Value.Metadata.Title }, + TabbableContentContainer = this }, creatorTextBox = new LabelledTextBox { Label = "Creator", - Current = { Value = Beatmap.Value.Metadata.AuthorString } + Current = { Value = Beatmap.Value.Metadata.AuthorString }, + TabbableContentContainer = this }, difficultyTextBox = new LabelledTextBox { Label = "Difficulty Name", - Current = { Value = Beatmap.Value.BeatmapInfo.Version } + Current = { Value = Beatmap.Value.BeatmapInfo.Version }, + TabbableContentContainer = this }, } }, From 95eeebd93fd8aba3d3d2b837273944ad21328fdb Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 8 Sep 2020 15:31:00 +0300 Subject: [PATCH 0989/1782] Fix setting count for recent scores is overcomplicated --- .../Profile/Sections/Ranks/PaginatedScoreContainer.cs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs b/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs index 0b2bddabbc..7dbdf47cad 100644 --- a/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs @@ -49,12 +49,7 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks base.OnItemsReceived(items); if (type == ScoreType.Recent) - { - var count = items.Count; - - Header.Current.Value = count == 0 ? 0 : -1; - Header.Current.TriggerChange(); - } + Header.Current.Value = items.Count; } protected override APIRequest> CreateRequest() => From 2cd07b2d3c8d6e54e82c352b17870e48bcd0c060 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 9 Sep 2020 12:48:11 +0900 Subject: [PATCH 0990/1782] Fix editor crash on saving more than once I'm fixing this in the simplest way possible as this kind of issue is specific to EF core, which may cease to exist quite soon. Turns out the re-retrieval of the beatmap set causes concurrency confusion and wasn't actually needed in my final iteration of the new beatmap logic. --- osu.Game/Beatmaps/BeatmapManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 34bb578b2a..4496f3b330 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -231,7 +231,7 @@ namespace osu.Game.Beatmaps /// The beatmap content to write, null if to be omitted. public void Save(BeatmapInfo info, IBeatmap beatmapContent, ISkin beatmapSkin = null) { - var setInfo = QueryBeatmapSet(s => s.Beatmaps.Any(b => b.ID == info.ID)); + var setInfo = info.BeatmapSet; using (var stream = new MemoryStream()) { From 8cd0bbe469436ce28999541fa9545c0193193cdb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 9 Sep 2020 14:31:23 +0900 Subject: [PATCH 0991/1782] Make BeatmapCollectionManager a component --- osu.Game/Collections/BeatmapCollectionManager.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Collections/BeatmapCollectionManager.cs b/osu.Game/Collections/BeatmapCollectionManager.cs index ed41627d63..00ca660381 100644 --- a/osu.Game/Collections/BeatmapCollectionManager.cs +++ b/osu.Game/Collections/BeatmapCollectionManager.cs @@ -11,7 +11,7 @@ using System.Threading.Tasks; using osu.Framework; using osu.Framework.Allocation; using osu.Framework.Bindables; -using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics; using osu.Framework.Logging; using osu.Framework.Platform; using osu.Game.Beatmaps; @@ -20,7 +20,7 @@ using osu.Game.Overlays.Notifications; namespace osu.Game.Collections { - public class BeatmapCollectionManager : CompositeDrawable + public class BeatmapCollectionManager : Component { /// /// Database version in stable-compatible YYYYMMDD format. From 5d9ce0df980bf7ea645c9362889f5ffa363c4750 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 9 Sep 2020 14:44:04 +0900 Subject: [PATCH 0992/1782] Add remark about temporary nature of database format --- osu.Game/Collections/BeatmapCollectionManager.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/osu.Game/Collections/BeatmapCollectionManager.cs b/osu.Game/Collections/BeatmapCollectionManager.cs index 00ca660381..0e78c44024 100644 --- a/osu.Game/Collections/BeatmapCollectionManager.cs +++ b/osu.Game/Collections/BeatmapCollectionManager.cs @@ -20,6 +20,13 @@ using osu.Game.Overlays.Notifications; namespace osu.Game.Collections { + /// + /// Handles user-defined collections of beatmaps. + /// + /// + /// This is currently reading and writing from the osu-stable file format. This is a temporary arrangement until we refactor the + /// database backing the game. Going forward writing should be done in a similar way to other model stores. + /// public class BeatmapCollectionManager : Component { /// From 4ddf5f054ba3422d6e7c092ff8873e2a3815dea7 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 9 Sep 2020 15:31:08 +0900 Subject: [PATCH 0993/1782] Rename BeatmapCollectionManager -> CollectionManager --- .../Collections/IO/ImportCollectionsTest.cs | 16 ++++++++-------- .../TestSceneManageCollectionsDialog.cs | 4 ++-- .../Visual/SongSelect/TestSceneFilterControl.cs | 4 ++-- ...CollectionManager.cs => CollectionManager.cs} | 4 ++-- .../Collections/DrawableCollectionListItem.cs | 8 ++++---- osu.Game/Collections/ManageCollectionsDialog.cs | 2 +- osu.Game/OsuGameBase.cs | 4 ++-- .../Sections/Maintenance/GeneralSettings.cs | 2 +- .../Select/Carousel/DrawableCarouselBeatmap.cs | 2 +- .../Carousel/DrawableCarouselBeatmapSet.cs | 2 +- .../Screens/Select/CollectionFilterDropdown.cs | 2 +- 11 files changed, 25 insertions(+), 25 deletions(-) rename osu.Game/Collections/{BeatmapCollectionManager.cs => CollectionManager.cs} (99%) diff --git a/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs b/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs index 95013859f0..e2335b4d3c 100644 --- a/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs +++ b/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs @@ -27,7 +27,7 @@ namespace osu.Game.Tests.Collections.IO { var osu = loadOsu(host); - var collectionManager = osu.Dependencies.Get(); + var collectionManager = osu.Dependencies.Get(); await collectionManager.Import(new MemoryStream()); Assert.That(collectionManager.Collections.Count, Is.Zero); @@ -48,7 +48,7 @@ namespace osu.Game.Tests.Collections.IO { var osu = loadOsu(host); - var collectionManager = osu.Dependencies.Get(); + var collectionManager = osu.Dependencies.Get(); await collectionManager.Import(TestResources.OpenResource("Collections/collections.db")); Assert.That(collectionManager.Collections.Count, Is.EqualTo(2)); @@ -75,7 +75,7 @@ namespace osu.Game.Tests.Collections.IO { var osu = loadOsu(host, true); - var collectionManager = osu.Dependencies.Get(); + var collectionManager = osu.Dependencies.Get(); await collectionManager.Import(TestResources.OpenResource("Collections/collections.db")); Assert.That(collectionManager.Collections.Count, Is.EqualTo(2)); @@ -107,7 +107,7 @@ namespace osu.Game.Tests.Collections.IO var osu = loadOsu(host, true); - var collectionManager = osu.Dependencies.Get(); + var collectionManager = osu.Dependencies.Get(); using (var ms = new MemoryStream()) { @@ -143,7 +143,7 @@ namespace osu.Game.Tests.Collections.IO { var osu = loadOsu(host, true); - var collectionManager = osu.Dependencies.Get(); + var collectionManager = osu.Dependencies.Get(); await collectionManager.Import(TestResources.OpenResource("Collections/collections.db")); // Move first beatmap from second collection into the first. @@ -165,7 +165,7 @@ namespace osu.Game.Tests.Collections.IO { var osu = loadOsu(host, true); - var collectionManager = osu.Dependencies.Get(); + var collectionManager = osu.Dependencies.Get(); Assert.That(collectionManager.Collections.Count, Is.EqualTo(2)); @@ -192,7 +192,7 @@ namespace osu.Game.Tests.Collections.IO waitForOrAssert(() => osu.IsLoaded, @"osu! failed to start in a reasonable amount of time"); - var collectionManager = osu.Dependencies.Get(); + var collectionManager = osu.Dependencies.Get(); waitForOrAssert(() => collectionManager.DatabaseLoaded, "Collection database did not load in a reasonable amount of time"); return osu; @@ -220,7 +220,7 @@ namespace osu.Game.Tests.Collections.IO protected override void AddInternal(Drawable drawable) { // The beatmap must be imported just before the collection manager is loaded. - if (drawable is BeatmapCollectionManager && withBeatmap) + if (drawable is CollectionManager && withBeatmap) BeatmapManager.Import(TestResources.GetTestBeatmapForImport()).Wait(); base.AddInternal(drawable); diff --git a/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs b/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs index 0c57c27911..54ab20af7f 100644 --- a/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs +++ b/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs @@ -26,7 +26,7 @@ namespace osu.Game.Tests.Visual.Collections private readonly Container content; private readonly DialogOverlay dialogOverlay; - private readonly BeatmapCollectionManager manager; + private readonly CollectionManager manager; private RulesetStore rulesets; private BeatmapManager beatmapManager; @@ -37,7 +37,7 @@ namespace osu.Game.Tests.Visual.Collections { base.Content.AddRange(new Drawable[] { - manager = new BeatmapCollectionManager(LocalStorage), + manager = new CollectionManager(LocalStorage), content = new Container { RelativeSizeAxes = Axes.Both }, dialogOverlay = new DialogOverlay() }); diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs index 5b0e244bbe..6012150513 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs @@ -26,7 +26,7 @@ namespace osu.Game.Tests.Visual.SongSelect protected override Container Content => content; private readonly Container content; - private readonly BeatmapCollectionManager collectionManager; + private readonly CollectionManager collectionManager; private RulesetStore rulesets; private BeatmapManager beatmapManager; @@ -37,7 +37,7 @@ namespace osu.Game.Tests.Visual.SongSelect { base.Content.AddRange(new Drawable[] { - collectionManager = new BeatmapCollectionManager(LocalStorage), + collectionManager = new CollectionManager(LocalStorage), content = new Container { RelativeSizeAxes = Axes.Both } }); } diff --git a/osu.Game/Collections/BeatmapCollectionManager.cs b/osu.Game/Collections/CollectionManager.cs similarity index 99% rename from osu.Game/Collections/BeatmapCollectionManager.cs rename to osu.Game/Collections/CollectionManager.cs index 0e78c44024..8b91ab219f 100644 --- a/osu.Game/Collections/BeatmapCollectionManager.cs +++ b/osu.Game/Collections/CollectionManager.cs @@ -27,7 +27,7 @@ namespace osu.Game.Collections /// This is currently reading and writing from the osu-stable file format. This is a temporary arrangement until we refactor the /// database backing the game. Going forward writing should be done in a similar way to other model stores. /// - public class BeatmapCollectionManager : Component + public class CollectionManager : Component { /// /// Database version in stable-compatible YYYYMMDD format. @@ -53,7 +53,7 @@ namespace osu.Game.Collections private readonly Storage storage; - public BeatmapCollectionManager(Storage storage) + public CollectionManager(Storage storage) { this.storage = storage; } diff --git a/osu.Game/Collections/DrawableCollectionListItem.cs b/osu.Game/Collections/DrawableCollectionListItem.cs index a7075c3179..7d158f182f 100644 --- a/osu.Game/Collections/DrawableCollectionListItem.cs +++ b/osu.Game/Collections/DrawableCollectionListItem.cs @@ -28,7 +28,7 @@ namespace osu.Game.Collections private const float button_width = item_height * 0.75f; /// - /// Whether the currently exists inside the . + /// Whether the currently exists inside the . /// public IBindable IsCreated => isCreated; @@ -38,7 +38,7 @@ namespace osu.Game.Collections /// Creates a new . /// /// The . - /// Whether currently exists inside the . + /// Whether currently exists inside the . public DrawableCollectionListItem(BeatmapCollection item, bool isCreated) : base(item) { @@ -63,7 +63,7 @@ namespace osu.Game.Collections private readonly BeatmapCollection collection; [Resolved] - private BeatmapCollectionManager collectionManager { get; set; } + private CollectionManager collectionManager { get; set; } private Container textBoxPaddingContainer; private ItemTextBox textBox; @@ -159,7 +159,7 @@ namespace osu.Game.Collections private DialogOverlay dialogOverlay { get; set; } [Resolved] - private BeatmapCollectionManager collectionManager { get; set; } + private CollectionManager collectionManager { get; set; } private readonly BeatmapCollection collection; diff --git a/osu.Game/Collections/ManageCollectionsDialog.cs b/osu.Game/Collections/ManageCollectionsDialog.cs index f6964191a1..cfde9d5550 100644 --- a/osu.Game/Collections/ManageCollectionsDialog.cs +++ b/osu.Game/Collections/ManageCollectionsDialog.cs @@ -20,7 +20,7 @@ namespace osu.Game.Collections private const double exit_duration = 200; [Resolved] - private BeatmapCollectionManager collectionManager { get; set; } + private CollectionManager collectionManager { get; set; } public ManageCollectionsDialog() { diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index d98d4e2123..d4741f9e69 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -57,7 +57,7 @@ namespace osu.Game protected BeatmapManager BeatmapManager; - protected BeatmapCollectionManager CollectionManager; + protected CollectionManager CollectionManager; protected ScoreManager ScoreManager; @@ -228,7 +228,7 @@ namespace osu.Game dependencies.Cache(difficultyManager); AddInternal(difficultyManager); - dependencies.Cache(CollectionManager = new BeatmapCollectionManager(Storage)); + dependencies.Cache(CollectionManager = new CollectionManager(Storage)); AddInternal(CollectionManager); dependencies.Cache(KeyBindingStore = new KeyBindingStore(contextFactory, RulesetStore)); diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs index 30fd5921eb..83ee5e497a 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs @@ -28,7 +28,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance private TriangleButton undeleteButton; [BackgroundDependencyLoader] - private void load(BeatmapManager beatmaps, ScoreManager scores, SkinManager skins, BeatmapCollectionManager collectionManager, DialogOverlay dialogOverlay) + private void load(BeatmapManager beatmaps, ScoreManager scores, SkinManager skins, CollectionManager collectionManager, DialogOverlay dialogOverlay) { if (beatmaps.SupportsImportFromStable) { diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs index e9990ab078..1db73702bb 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs @@ -49,7 +49,7 @@ namespace osu.Game.Screens.Select.Carousel private BeatmapDifficultyManager difficultyManager { get; set; } [Resolved] - private BeatmapCollectionManager collectionManager { get; set; } + private CollectionManager collectionManager { get; set; } [Resolved(CanBeNull = true)] private ManageCollectionsDialog manageCollectionsDialog { get; set; } diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index fe700f12df..fd66315f67 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -36,7 +36,7 @@ namespace osu.Game.Screens.Select.Carousel private DialogOverlay dialogOverlay { get; set; } [Resolved] - private BeatmapCollectionManager collectionManager { get; set; } + private CollectionManager collectionManager { get; set; } [Resolved(CanBeNull = true)] private ManageCollectionsDialog manageCollectionsDialog { get; set; } diff --git a/osu.Game/Screens/Select/CollectionFilterDropdown.cs b/osu.Game/Screens/Select/CollectionFilterDropdown.cs index 6b9ae1b5c8..7270354e87 100644 --- a/osu.Game/Screens/Select/CollectionFilterDropdown.cs +++ b/osu.Game/Screens/Select/CollectionFilterDropdown.cs @@ -38,7 +38,7 @@ namespace osu.Game.Screens.Select } [BackgroundDependencyLoader] - private void load(BeatmapCollectionManager collectionManager) + private void load(CollectionManager collectionManager) { collections.BindTo(collectionManager.Collections); collections.CollectionChanged += (_, __) => collectionsChanged(); From 0360f7d8456b0dd43f27713a55e81d9319d96ae0 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 9 Sep 2020 15:39:15 +0900 Subject: [PATCH 0994/1782] Move CollectionManager to OsuGame --- osu.Game/Collections/DrawableCollectionListItem.cs | 8 ++++---- osu.Game/Collections/ManageCollectionsDialog.cs | 5 +++-- osu.Game/OsuGame.cs | 9 ++++++--- osu.Game/OsuGameBase.cs | 6 ------ .../Select/Carousel/DrawableCarouselBeatmap.cs | 13 ++++++++----- .../Select/Carousel/DrawableCarouselBeatmapSet.cs | 13 ++++++++----- 6 files changed, 29 insertions(+), 25 deletions(-) diff --git a/osu.Game/Collections/DrawableCollectionListItem.cs b/osu.Game/Collections/DrawableCollectionListItem.cs index 7d158f182f..988a3443c3 100644 --- a/osu.Game/Collections/DrawableCollectionListItem.cs +++ b/osu.Game/Collections/DrawableCollectionListItem.cs @@ -62,7 +62,7 @@ namespace osu.Game.Collections private readonly IBindable collectionName; private readonly BeatmapCollection collection; - [Resolved] + [Resolved(CanBeNull = true)] private CollectionManager collectionManager { get; set; } private Container textBoxPaddingContainer; @@ -127,7 +127,7 @@ namespace osu.Game.Collections return; // Add the new collection and disable our placeholder. If all text is removed, the placeholder should not show back again. - collectionManager.Collections.Add(collection); + collectionManager?.Collections.Add(collection); textBox.PlaceholderText = string.Empty; // When this item changes from placeholder to non-placeholder (via changing containers), its textbox will lose focus, so it needs to be re-focused. @@ -158,7 +158,7 @@ namespace osu.Game.Collections [Resolved(CanBeNull = true)] private DialogOverlay dialogOverlay { get; set; } - [Resolved] + [Resolved(CanBeNull = true)] private CollectionManager collectionManager { get; set; } private readonly BeatmapCollection collection; @@ -231,7 +231,7 @@ namespace osu.Game.Collections return true; } - private void deleteCollection() => collectionManager.Collections.Remove(collection); + private void deleteCollection() => collectionManager?.Collections.Remove(collection); } } } diff --git a/osu.Game/Collections/ManageCollectionsDialog.cs b/osu.Game/Collections/ManageCollectionsDialog.cs index cfde9d5550..680fec904f 100644 --- a/osu.Game/Collections/ManageCollectionsDialog.cs +++ b/osu.Game/Collections/ManageCollectionsDialog.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; @@ -19,7 +20,7 @@ namespace osu.Game.Collections private const double enter_duration = 500; private const double exit_duration = 200; - [Resolved] + [Resolved(CanBeNull = true)] private CollectionManager collectionManager { get; set; } public ManageCollectionsDialog() @@ -100,7 +101,7 @@ namespace osu.Game.Collections new DrawableCollectionList { RelativeSizeAxes = Axes.Both, - Items = { BindTarget = collectionManager.Collections } + Items = { BindTarget = collectionManager?.Collections ?? new BindableList() } } } } diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 0977f6c242..4a699dc82e 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -538,9 +538,6 @@ namespace osu.Game ScoreManager.GetStableStorage = GetStorageForStableInstall; ScoreManager.PresentImport = items => PresentScore(items.First()); - CollectionManager.PostNotification = n => notifications.Post(n); - CollectionManager.GetStableStorage = GetStorageForStableInstall; - Container logoContainer; BackButton.Receptor receptor; @@ -614,6 +611,12 @@ namespace osu.Game d.Origin = Anchor.TopRight; }), rightFloatingOverlayContent.Add, true); + loadComponentSingleFile(new CollectionManager(Storage) + { + PostNotification = n => notifications.Post(n), + GetStableStorage = GetStorageForStableInstall + }, Add, true); + loadComponentSingleFile(screenshotManager, Add); // dependency on notification overlay, dependent by settings overlay diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index d4741f9e69..4bc8f4c527 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -26,7 +26,6 @@ using osu.Framework.Graphics.Textures; using osu.Framework.Input; using osu.Framework.Logging; using osu.Game.Audio; -using osu.Game.Collections; using osu.Game.Database; using osu.Game.Input; using osu.Game.Input.Bindings; @@ -57,8 +56,6 @@ namespace osu.Game protected BeatmapManager BeatmapManager; - protected CollectionManager CollectionManager; - protected ScoreManager ScoreManager; protected SkinManager SkinManager; @@ -228,9 +225,6 @@ namespace osu.Game dependencies.Cache(difficultyManager); AddInternal(difficultyManager); - dependencies.Cache(CollectionManager = new CollectionManager(Storage)); - AddInternal(CollectionManager); - dependencies.Cache(KeyBindingStore = new KeyBindingStore(contextFactory, RulesetStore)); dependencies.Cache(SettingsStore = new SettingsStore(contextFactory)); dependencies.Cache(RulesetConfigCache = new RulesetConfigCache(SettingsStore)); diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs index 1db73702bb..10745fe3c1 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs @@ -48,7 +48,7 @@ namespace osu.Game.Screens.Select.Carousel [Resolved] private BeatmapDifficultyManager difficultyManager { get; set; } - [Resolved] + [Resolved(CanBeNull = true)] private CollectionManager collectionManager { get; set; } [Resolved(CanBeNull = true)] @@ -224,11 +224,14 @@ namespace osu.Game.Screens.Select.Carousel if (beatmap.OnlineBeatmapID.HasValue && beatmapOverlay != null) items.Add(new OsuMenuItem("Details...", MenuItemType.Standard, () => beatmapOverlay.FetchAndShowBeatmap(beatmap.OnlineBeatmapID.Value))); - var collectionItems = collectionManager.Collections.Select(createCollectionMenuItem).ToList(); - if (manageCollectionsDialog != null) - collectionItems.Add(new OsuMenuItem("Manage...", MenuItemType.Standard, manageCollectionsDialog.Show)); + if (collectionManager != null) + { + var collectionItems = collectionManager.Collections.Select(createCollectionMenuItem).ToList(); + if (manageCollectionsDialog != null) + collectionItems.Add(new OsuMenuItem("Manage...", MenuItemType.Standard, manageCollectionsDialog.Show)); - items.Add(new OsuMenuItem("Collections") { Items = collectionItems }); + items.Add(new OsuMenuItem("Collections") { Items = collectionItems }); + } if (hideRequested != null) items.Add(new OsuMenuItem("Hide", MenuItemType.Destructive, () => hideRequested(beatmap))); diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index fd66315f67..3c8ac69dd2 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -35,7 +35,7 @@ namespace osu.Game.Screens.Select.Carousel [Resolved(CanBeNull = true)] private DialogOverlay dialogOverlay { get; set; } - [Resolved] + [Resolved(CanBeNull = true)] private CollectionManager collectionManager { get; set; } [Resolved(CanBeNull = true)] @@ -142,11 +142,14 @@ namespace osu.Game.Screens.Select.Carousel if (beatmapSet.OnlineBeatmapSetID != null && viewDetails != null) items.Add(new OsuMenuItem("Details...", MenuItemType.Standard, () => viewDetails(beatmapSet.OnlineBeatmapSetID.Value))); - var collectionItems = collectionManager.Collections.Select(createCollectionMenuItem).ToList(); - if (manageCollectionsDialog != null) - collectionItems.Add(new OsuMenuItem("Manage...", MenuItemType.Standard, manageCollectionsDialog.Show)); + if (collectionManager != null) + { + var collectionItems = collectionManager.Collections.Select(createCollectionMenuItem).ToList(); + if (manageCollectionsDialog != null) + collectionItems.Add(new OsuMenuItem("Manage...", MenuItemType.Standard, manageCollectionsDialog.Show)); - items.Add(new OsuMenuItem("Collections") { Items = collectionItems }); + items.Add(new OsuMenuItem("Collections") { Items = collectionItems }); + } if (beatmapSet.Beatmaps.Any(b => b.Hidden)) items.Add(new OsuMenuItem("Restore all hidden", MenuItemType.Standard, () => restoreHiddenRequested(beatmapSet))); From 2d7e85f62203d5c017acd893d60d90fe22bc1325 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 9 Sep 2020 15:40:45 +0900 Subject: [PATCH 0995/1782] Remove async load (now using loadComponentSingleFile) --- osu.Game/Collections/CollectionManager.cs | 40 +++++------------------ 1 file changed, 9 insertions(+), 31 deletions(-) diff --git a/osu.Game/Collections/CollectionManager.cs b/osu.Game/Collections/CollectionManager.cs index 8b91ab219f..a50ab5b07a 100644 --- a/osu.Game/Collections/CollectionManager.cs +++ b/osu.Game/Collections/CollectionManager.cs @@ -40,11 +40,6 @@ namespace osu.Game.Collections public bool SupportsImportFromStable => RuntimeInfo.IsDesktop; - /// - /// Whether the user's database has finished loading. - /// - public bool DatabaseLoaded { get; private set; } - [Resolved] private GameHost host { get; set; } @@ -62,7 +57,12 @@ namespace osu.Game.Collections private void load() { Collections.CollectionChanged += collectionsChanged; - loadDatabase(); + + if (storage.Exists(database_name)) + { + using (var stream = storage.GetStream(database_name)) + importCollections(readCollections(stream)); + } } private void collectionsChanged(object sender, NotifyCollectionChangedEventArgs e) @@ -91,17 +91,6 @@ namespace osu.Game.Collections backgroundSave(); } - private void loadDatabase() => Task.Run(async () => - { - if (storage.Exists(database_name)) - { - using (var stream = storage.GetStream(database_name)) - await import(stream); - } - - DatabaseLoaded = true; - }); - /// /// Set an endpoint for notifications to be posted to. /// @@ -149,14 +138,6 @@ namespace osu.Game.Collections PostNotification?.Invoke(notification); - await import(stream, notification); - } - - private async Task import(Stream stream, ProgressNotification notification = null) => await Task.Run(async () => - { - if (notification != null) - notification.Progress = 0; - var collection = readCollections(stream, notification); bool importCompleted = false; @@ -169,12 +150,9 @@ namespace osu.Game.Collections while (!IsDisposed && !importCompleted) await Task.Delay(10); - if (notification != null) - { - notification.CompletionText = $"Imported {collection.Count} collections"; - notification.State = ProgressNotificationState.Completed; - } - }); + notification.CompletionText = $"Imported {collection.Count} collections"; + notification.State = ProgressNotificationState.Completed; + } private void importCollections(List newCollections) { From b1b99e4d6f3d54bea94cd65b8b38f649a3037c25 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 9 Sep 2020 15:55:56 +0900 Subject: [PATCH 0996/1782] Fix tests --- .../Collections/IO/ImportCollectionsTest.cs | 75 ++++++++----------- 1 file changed, 33 insertions(+), 42 deletions(-) diff --git a/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs b/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs index e2335b4d3c..a79e0d0338 100644 --- a/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs +++ b/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs @@ -8,7 +8,6 @@ using System.Threading; using System.Threading.Tasks; using NUnit.Framework; using osu.Framework.Allocation; -using osu.Framework.Graphics; using osu.Framework.Platform; using osu.Game.Collections; using osu.Game.Tests.Resources; @@ -27,10 +26,9 @@ namespace osu.Game.Tests.Collections.IO { var osu = loadOsu(host); - var collectionManager = osu.Dependencies.Get(); - await collectionManager.Import(new MemoryStream()); + await osu.CollectionManager.Import(new MemoryStream()); - Assert.That(collectionManager.Collections.Count, Is.Zero); + Assert.That(osu.CollectionManager.Collections.Count, Is.Zero); } finally { @@ -48,16 +46,15 @@ namespace osu.Game.Tests.Collections.IO { var osu = loadOsu(host); - var collectionManager = osu.Dependencies.Get(); - await collectionManager.Import(TestResources.OpenResource("Collections/collections.db")); + await osu.CollectionManager.Import(TestResources.OpenResource("Collections/collections.db")); - Assert.That(collectionManager.Collections.Count, Is.EqualTo(2)); + Assert.That(osu.CollectionManager.Collections.Count, Is.EqualTo(2)); - Assert.That(collectionManager.Collections[0].Name.Value, Is.EqualTo("First")); - Assert.That(collectionManager.Collections[0].Beatmaps.Count, Is.Zero); + Assert.That(osu.CollectionManager.Collections[0].Name.Value, Is.EqualTo("First")); + Assert.That(osu.CollectionManager.Collections[0].Beatmaps.Count, Is.Zero); - Assert.That(collectionManager.Collections[1].Name.Value, Is.EqualTo("Second")); - Assert.That(collectionManager.Collections[1].Beatmaps.Count, Is.Zero); + Assert.That(osu.CollectionManager.Collections[1].Name.Value, Is.EqualTo("Second")); + Assert.That(osu.CollectionManager.Collections[1].Beatmaps.Count, Is.Zero); } finally { @@ -75,16 +72,15 @@ namespace osu.Game.Tests.Collections.IO { var osu = loadOsu(host, true); - var collectionManager = osu.Dependencies.Get(); - await collectionManager.Import(TestResources.OpenResource("Collections/collections.db")); + await osu.CollectionManager.Import(TestResources.OpenResource("Collections/collections.db")); - Assert.That(collectionManager.Collections.Count, Is.EqualTo(2)); + Assert.That(osu.CollectionManager.Collections.Count, Is.EqualTo(2)); - Assert.That(collectionManager.Collections[0].Name.Value, Is.EqualTo("First")); - Assert.That(collectionManager.Collections[0].Beatmaps.Count, Is.EqualTo(1)); + Assert.That(osu.CollectionManager.Collections[0].Name.Value, Is.EqualTo("First")); + Assert.That(osu.CollectionManager.Collections[0].Beatmaps.Count, Is.EqualTo(1)); - Assert.That(collectionManager.Collections[1].Name.Value, Is.EqualTo("Second")); - Assert.That(collectionManager.Collections[1].Beatmaps.Count, Is.EqualTo(12)); + Assert.That(osu.CollectionManager.Collections[1].Name.Value, Is.EqualTo("Second")); + Assert.That(osu.CollectionManager.Collections[1].Beatmaps.Count, Is.EqualTo(12)); } finally { @@ -107,8 +103,6 @@ namespace osu.Game.Tests.Collections.IO var osu = loadOsu(host, true); - var collectionManager = osu.Dependencies.Get(); - using (var ms = new MemoryStream()) { using (var bw = new BinaryWriter(ms, Encoding.UTF8, true)) @@ -119,12 +113,12 @@ namespace osu.Game.Tests.Collections.IO ms.Seek(0, SeekOrigin.Begin); - await collectionManager.Import(ms); + await osu.CollectionManager.Import(ms); } Assert.That(host.UpdateThread.Running, Is.True); Assert.That(exceptionThrown, Is.False); - Assert.That(collectionManager.Collections.Count, Is.EqualTo(0)); + Assert.That(osu.CollectionManager.Collections.Count, Is.EqualTo(0)); } finally { @@ -143,15 +137,14 @@ namespace osu.Game.Tests.Collections.IO { var osu = loadOsu(host, true); - var collectionManager = osu.Dependencies.Get(); - await collectionManager.Import(TestResources.OpenResource("Collections/collections.db")); + await osu.CollectionManager.Import(TestResources.OpenResource("Collections/collections.db")); // Move first beatmap from second collection into the first. - collectionManager.Collections[0].Beatmaps.Add(collectionManager.Collections[1].Beatmaps[0]); - collectionManager.Collections[1].Beatmaps.RemoveAt(0); + osu.CollectionManager.Collections[0].Beatmaps.Add(osu.CollectionManager.Collections[1].Beatmaps[0]); + osu.CollectionManager.Collections[1].Beatmaps.RemoveAt(0); // Rename the second collecction. - collectionManager.Collections[1].Name.Value = "Another"; + osu.CollectionManager.Collections[1].Name.Value = "Another"; } finally { @@ -165,15 +158,13 @@ namespace osu.Game.Tests.Collections.IO { var osu = loadOsu(host, true); - var collectionManager = osu.Dependencies.Get(); + Assert.That(osu.CollectionManager.Collections.Count, Is.EqualTo(2)); - Assert.That(collectionManager.Collections.Count, Is.EqualTo(2)); + Assert.That(osu.CollectionManager.Collections[0].Name.Value, Is.EqualTo("First")); + Assert.That(osu.CollectionManager.Collections[0].Beatmaps.Count, Is.EqualTo(2)); - Assert.That(collectionManager.Collections[0].Name.Value, Is.EqualTo("First")); - Assert.That(collectionManager.Collections[0].Beatmaps.Count, Is.EqualTo(2)); - - Assert.That(collectionManager.Collections[1].Name.Value, Is.EqualTo("Another")); - Assert.That(collectionManager.Collections[1].Beatmaps.Count, Is.EqualTo(11)); + Assert.That(osu.CollectionManager.Collections[1].Name.Value, Is.EqualTo("Another")); + Assert.That(osu.CollectionManager.Collections[1].Beatmaps.Count, Is.EqualTo(11)); } finally { @@ -182,7 +173,7 @@ namespace osu.Game.Tests.Collections.IO } } - private OsuGameBase loadOsu(GameHost host, bool withBeatmap = false) + private TestOsuGameBase loadOsu(GameHost host, bool withBeatmap = false) { var osu = new TestOsuGameBase(withBeatmap); @@ -192,9 +183,6 @@ namespace osu.Game.Tests.Collections.IO waitForOrAssert(() => osu.IsLoaded, @"osu! failed to start in a reasonable amount of time"); - var collectionManager = osu.Dependencies.Get(); - waitForOrAssert(() => collectionManager.DatabaseLoaded, "Collection database did not load in a reasonable amount of time"); - return osu; } @@ -210,6 +198,8 @@ namespace osu.Game.Tests.Collections.IO private class TestOsuGameBase : OsuGameBase { + public CollectionManager CollectionManager { get; private set; } + private readonly bool withBeatmap; public TestOsuGameBase(bool withBeatmap) @@ -217,13 +207,14 @@ namespace osu.Game.Tests.Collections.IO this.withBeatmap = withBeatmap; } - protected override void AddInternal(Drawable drawable) + [BackgroundDependencyLoader] + private void load() { - // The beatmap must be imported just before the collection manager is loaded. - if (drawable is CollectionManager && withBeatmap) + // Beatmap must be imported before the collection manager is loaded. + if (withBeatmap) BeatmapManager.Import(TestResources.GetTestBeatmapForImport()).Wait(); - base.AddInternal(drawable); + AddInternal(CollectionManager = new CollectionManager(Storage)); } } } From 1a023d2c887ab72795c34e435cb22b4a130e2a6f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 9 Sep 2020 16:33:48 +0900 Subject: [PATCH 0997/1782] Fix a few more tests --- .../Sections/Maintenance/GeneralSettings.cs | 36 ++++++++++--------- .../Select/CollectionFilterDropdown.cs | 8 +++-- 2 files changed, 25 insertions(+), 19 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs index 83ee5e497a..848ce381a9 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs @@ -3,6 +3,7 @@ using System.Linq; using System.Threading.Tasks; +using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Game.Beatmaps; @@ -27,8 +28,8 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance private TriangleButton restoreButton; private TriangleButton undeleteButton; - [BackgroundDependencyLoader] - private void load(BeatmapManager beatmaps, ScoreManager scores, SkinManager skins, CollectionManager collectionManager, DialogOverlay dialogOverlay) + [BackgroundDependencyLoader(permitNulls: true)] + private void load(BeatmapManager beatmaps, ScoreManager scores, SkinManager skins, [CanBeNull] CollectionManager collectionManager, DialogOverlay dialogOverlay) { if (beatmaps.SupportsImportFromStable) { @@ -108,28 +109,31 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance } }); - if (collectionManager.SupportsImportFromStable) + if (collectionManager != null) { - Add(importCollectionsButton = new SettingsButton + if (collectionManager.SupportsImportFromStable) { - Text = "Import collections from stable", + Add(importCollectionsButton = new SettingsButton + { + Text = "Import collections from stable", + Action = () => + { + importCollectionsButton.Enabled.Value = false; + collectionManager.ImportFromStableAsync().ContinueWith(t => Schedule(() => importCollectionsButton.Enabled.Value = true)); + } + }); + } + + Add(new DangerousSettingsButton + { + Text = "Delete ALL collections", Action = () => { - importCollectionsButton.Enabled.Value = false; - collectionManager.ImportFromStableAsync().ContinueWith(t => Schedule(() => importCollectionsButton.Enabled.Value = true)); + dialogOverlay?.Push(new DeleteAllBeatmapsDialog(collectionManager.DeleteAll)); } }); } - Add(new DangerousSettingsButton - { - Text = "Delete ALL collections", - Action = () => - { - dialogOverlay?.Push(new DeleteAllBeatmapsDialog(collectionManager.DeleteAll)); - } - }); - AddRange(new Drawable[] { restoreButton = new SettingsButton diff --git a/osu.Game/Screens/Select/CollectionFilterDropdown.cs b/osu.Game/Screens/Select/CollectionFilterDropdown.cs index 7270354e87..1e2a3d0aa7 100644 --- a/osu.Game/Screens/Select/CollectionFilterDropdown.cs +++ b/osu.Game/Screens/Select/CollectionFilterDropdown.cs @@ -37,10 +37,12 @@ namespace osu.Game.Screens.Select ItemSource = filters; } - [BackgroundDependencyLoader] - private void load(CollectionManager collectionManager) + [BackgroundDependencyLoader(permitNulls: true)] + private void load([CanBeNull] CollectionManager collectionManager) { - collections.BindTo(collectionManager.Collections); + if (collectionManager != null) + collections.BindTo(collectionManager.Collections); + collections.CollectionChanged += (_, __) => collectionsChanged(); collectionsChanged(); } From e271408fca532fd6e1dcd93fc68c380e1b19eab4 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 9 Sep 2020 16:51:53 +0900 Subject: [PATCH 0998/1782] Move max score calculation inside ScoreProcessor --- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 8 ++++---- osu.Game/Scoring/ScoreManager.cs | 5 +---- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 7d138bd878..46994d4f18 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -67,7 +67,7 @@ namespace osu.Game.Rulesets.Scoring private readonly double accuracyPortion; private readonly double comboPortion; - private double maxHighestCombo; + private int maxHighestCombo; private double maxBaseScore; private double rollingMaxBaseScore; private double baseScore; @@ -204,10 +204,10 @@ namespace osu.Game.Rulesets.Scoring private double getScore(ScoringMode mode) { - return GetScore(mode, maxBaseScore, maxHighestCombo, baseScore / maxBaseScore, HighestCombo.Value / maxHighestCombo, bonusScore); + return GetScore(mode, maxHighestCombo, baseScore / maxBaseScore, (double)HighestCombo.Value / maxHighestCombo, bonusScore); } - public double GetScore(ScoringMode mode, double maxBaseScore, double maxHighestCombo, double accuracyRatio, double comboRatio, double bonusScore) + public double GetScore(ScoringMode mode, int maxCombo, double accuracyRatio, double comboRatio, double bonusScore) { switch (mode) { @@ -220,7 +220,7 @@ namespace osu.Game.Rulesets.Scoring case ScoringMode.Classic: // should emulate osu-stable's scoring as closely as we can (https://osu.ppy.sh/help/wiki/Score/ScoreV1) - return bonusScore + (accuracyRatio * maxBaseScore) * (1 + Math.Max(0, (comboRatio * maxHighestCombo) - 1) * scoreMultiplier / 25); + return bonusScore + (accuracyRatio * maxCombo * 300) * (1 + Math.Max(0, (comboRatio * maxCombo) - 1) * scoreMultiplier / 25); } } diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index 634cca159a..5518c86910 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -154,10 +154,7 @@ namespace osu.Game.Scoring scoreProcessor.Mods.Value = score.Mods; - double maxBaseScore = 300 * beatmapMaxCombo; - double maxHighestCombo = beatmapMaxCombo; - - Value = (long)Math.Round(scoreProcessor.GetScore(ScoringMode.Value, maxBaseScore, maxHighestCombo, score.Accuracy, score.MaxCombo / maxHighestCombo, 0)); + Value = (long)Math.Round(scoreProcessor.GetScore(ScoringMode.Value, beatmapMaxCombo, score.Accuracy, (double)score.MaxCombo / beatmapMaxCombo, 0)); } } From 37a659b2af22ca0246cd9833ed1d21fe37726fa6 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 9 Sep 2020 17:04:02 +0900 Subject: [PATCH 0999/1782] Refactor/add xmldocs --- .../Online/Leaderboards/LeaderboardScore.cs | 2 +- .../Overlays/BeatmapSet/Scores/ScoreTable.cs | 2 +- .../Scores/TopScoreStatisticsSection.cs | 4 +-- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 14 +++++--- osu.Game/Scoring/ScoreManager.cs | 33 ++++++++++++++++--- .../ContractedPanelMiddleContent.cs | 2 +- .../Expanded/ExpandedPanelMiddleContent.cs | 2 +- 7 files changed, 45 insertions(+), 14 deletions(-) diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs index 846bebe347..dcd0cb435a 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs @@ -194,7 +194,7 @@ namespace osu.Game.Online.Leaderboards { TextColour = Color4.White, GlowColour = Color4Extensions.FromHex(@"83ccfa"), - Current = scoreManager.GetTotalScoreString(score), + Current = scoreManager.GetBindableTotalScoreString(score), Font = OsuFont.Numeric.With(size: 23), }, RankContainer = new Container diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs index 6bebd98eef..56866765b6 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs @@ -124,7 +124,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores new OsuSpriteText { Margin = new MarginPadding { Right = horizontal_inset }, - Current = scoreManager.GetTotalScoreString(score), + Current = scoreManager.GetBindableTotalScoreString(score), Font = OsuFont.GetFont(size: text_size, weight: index == 0 ? FontWeight.Bold : FontWeight.Medium) }, new OsuSpriteText diff --git a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs index 507c692eb1..2fd522dc9d 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs @@ -95,7 +95,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores private void load() { if (score != null) - totalScoreColumn.Current = scoreManager.GetTotalScoreString(score); + totalScoreColumn.Current = scoreManager.GetBindableTotalScoreString(score); } private ScoreInfo score; @@ -121,7 +121,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores modsColumn.Mods = value.Mods; if (IsLoaded) - totalScoreColumn.Current = scoreManager.GetTotalScoreString(value); + totalScoreColumn.Current = scoreManager.GetBindableTotalScoreString(value); } } diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 46994d4f18..983f9a3abf 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -202,11 +202,17 @@ namespace osu.Game.Rulesets.Scoring TotalScore.Value = getScore(Mode.Value); } - private double getScore(ScoringMode mode) - { - return GetScore(mode, maxHighestCombo, baseScore / maxBaseScore, (double)HighestCombo.Value / maxHighestCombo, bonusScore); - } + private double getScore(ScoringMode mode) => GetScore(mode, maxHighestCombo, baseScore / maxBaseScore, (double)HighestCombo.Value / maxHighestCombo, bonusScore); + /// + /// Computes the total score. + /// + /// The to compute the total score in. + /// The maximum combo achievable in the beatmap. + /// The accuracy percentage achieved by the player. + /// The proportion of achieved by the player. + /// Any bonus score to be added. + /// The total score. public double GetScore(ScoringMode mode, int maxCombo, double accuracyRatio, double comboRatio, double bonusScore) { switch (mode) diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index 5518c86910..5a6ef6945c 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -86,15 +86,34 @@ namespace osu.Game.Scoring => base.CheckLocalAvailability(model, items) || (model.OnlineScoreID != null && items.Any(i => i.OnlineScoreID == model.OnlineScoreID)); - public Bindable GetTotalScore(ScoreInfo score) + /// + /// Retrieves a bindable that represents the total score of a . + /// + /// + /// Responds to changes in the currently-selected . + /// + /// The to retrieve the bindable for. + /// The bindable containing the total score. + public Bindable GetBindableTotalScore(ScoreInfo score) { var bindable = new TotalScoreBindable(score, difficulties); configManager?.BindWith(OsuSetting.ScoreDisplayMode, bindable.ScoringMode); return bindable; } - public Bindable GetTotalScoreString(ScoreInfo score) => new TotalScoreStringBindable(GetTotalScore(score)); + /// + /// Retrieves a bindable that represents the formatted total score string of a . + /// + /// + /// Responds to changes in the currently-selected . + /// + /// The to retrieve the bindable for. + /// The bindable containing the formatted total score string. + public Bindable GetBindableTotalScoreString(ScoreInfo score) => new TotalScoreStringBindable(GetBindableTotalScore(score)); + /// + /// Provides the total score of a . Responds to changes in the currently-selected . + /// private class TotalScoreBindable : Bindable { public readonly Bindable ScoringMode = new Bindable(); @@ -102,13 +121,16 @@ namespace osu.Game.Scoring private readonly ScoreInfo score; private readonly Func difficulties; + /// + /// Creates a new . + /// + /// The to provide the total score of. + /// A function to retrieve the . public TotalScoreBindable(ScoreInfo score, Func difficulties) { this.score = score; this.difficulties = difficulties; - Value = 0; - ScoringMode.BindValueChanged(onScoringModeChanged, true); } @@ -158,6 +180,9 @@ namespace osu.Game.Scoring } } + /// + /// Provides the total score of a as a formatted string. Responds to changes in the currently-selected . + /// private class TotalScoreStringBindable : Bindable { // ReSharper disable once PrivateFieldCanBeConvertedToLocalVariable (need to hold a reference) diff --git a/osu.Game/Screens/Ranking/Contracted/ContractedPanelMiddleContent.cs b/osu.Game/Screens/Ranking/Contracted/ContractedPanelMiddleContent.cs index b37b89e6c0..0b85eeafa8 100644 --- a/osu.Game/Screens/Ranking/Contracted/ContractedPanelMiddleContent.cs +++ b/osu.Game/Screens/Ranking/Contracted/ContractedPanelMiddleContent.cs @@ -163,7 +163,7 @@ namespace osu.Game.Screens.Ranking.Contracted { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Current = scoreManager.GetTotalScoreString(score), + Current = scoreManager.GetBindableTotalScoreString(score), Font = OsuFont.GetFont(size: 20, weight: FontWeight.Medium, fixedWidth: true), Spacing = new Vector2(-1, 0) }, diff --git a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs index 3433410d3c..0033cd1f43 100644 --- a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs +++ b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs @@ -239,7 +239,7 @@ namespace osu.Game.Screens.Ranking.Expanded using (BeginDelayedSequence(AccuracyCircle.ACCURACY_TRANSFORM_DELAY, true)) { scoreCounter.FadeIn(); - scoreCounter.Current = scoreManager.GetTotalScore(score); + scoreCounter.Current = scoreManager.GetBindableTotalScore(score); double delay = 0; From 5cdc8d2e7b46c092031a7a0a7daf8f645cae4c13 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 9 Sep 2020 17:37:11 +0900 Subject: [PATCH 1000/1782] Add cancellation support --- osu.Game/Scoring/ScoreManager.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index 5a6ef6945c..619ca76598 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Linq.Expressions; +using System.Threading; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore; using osu.Framework.Bindables; @@ -135,9 +136,13 @@ namespace osu.Game.Scoring } private IBindable difficultyBindable; + private CancellationTokenSource difficultyCancellationSource; private void onScoringModeChanged(ValueChangedEvent mode) { + difficultyCancellationSource?.Cancel(); + difficultyCancellationSource = null; + if (score.Beatmap == null) { Value = score.TotalScore; @@ -156,7 +161,7 @@ namespace osu.Game.Scoring } // We can compute the max combo locally after the async beatmap difficulty computation. - difficultyBindable = difficulties().GetBindableDifficulty(score.Beatmap, score.Ruleset, score.Mods); + difficultyBindable = difficulties().GetBindableDifficulty(score.Beatmap, score.Ruleset, score.Mods, (difficultyCancellationSource = new CancellationTokenSource()).Token); difficultyBindable.BindValueChanged(d => updateScore(d.NewValue.MaxCombo), true); } else From b1daca6cd33e19c71b91f90ad86f0247ebd4f628 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 9 Sep 2020 18:05:44 +0900 Subject: [PATCH 1001/1782] Fix overlay sound effects playing when open requested while disabled --- .../Graphics/Containers/OsuFocusedOverlayContainer.cs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs b/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs index 751ccc8f15..1d96e602d0 100644 --- a/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs +++ b/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs @@ -103,6 +103,8 @@ namespace osu.Game.Graphics.Containers { } + private bool playedPopInSound; + protected override void UpdateState(ValueChangedEvent state) { switch (state.NewValue) @@ -115,11 +117,18 @@ namespace osu.Game.Graphics.Containers } samplePopIn?.Play(); + playedPopInSound = true; + if (BlockScreenWideMouse && DimMainContent) game?.AddBlockingOverlay(this); break; case Visibility.Hidden: - samplePopOut?.Play(); + if (playedPopInSound) + { + samplePopOut?.Play(); + playedPopInSound = false; + } + if (BlockScreenWideMouse) game?.RemoveBlockingOverlay(this); break; } From cdf3e206857186de7052061b5210642a421f6524 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 9 Sep 2020 18:07:58 +0900 Subject: [PATCH 1002/1782] Add comment regarding feedback --- osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs b/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs index 1d96e602d0..41fd37a0d7 100644 --- a/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs +++ b/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs @@ -112,6 +112,7 @@ namespace osu.Game.Graphics.Containers case Visibility.Visible: if (OverlayActivationMode.Value == OverlayActivation.Disabled) { + // todo: visual/audible feedback that this operation could not complete. State.Value = Visibility.Hidden; return; } From c9f5005efd19270f54ab6834c5c3be301d38c11e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 9 Sep 2020 18:35:25 +0900 Subject: [PATCH 1003/1782] Add icons for editor toolbox tools --- .../Edit/HitCircleCompositionTool.cs | 4 +++ .../Edit/SliderCompositionTool.cs | 4 +++ .../Edit/SpinnerCompositionTool.cs | 4 +++ .../TestSceneEditorComposeRadioButtons.cs | 3 ++- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 2 +- .../Edit/Tools/HitObjectCompositionTool.cs | 4 +++ osu.Game/Rulesets/Edit/Tools/SelectTool.cs | 5 ++++ .../RadioButtons/DrawableRadioButton.cs | 27 +++++++------------ .../Components/RadioButtons/RadioButton.cs | 9 ++++++- 9 files changed, 42 insertions(+), 20 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/HitCircleCompositionTool.cs b/osu.Game.Rulesets.Osu/Edit/HitCircleCompositionTool.cs index 9c94fe0e3d..5f7c8b77b0 100644 --- a/osu.Game.Rulesets.Osu/Edit/HitCircleCompositionTool.cs +++ b/osu.Game.Rulesets.Osu/Edit/HitCircleCompositionTool.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; +using osu.Game.Beatmaps; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Tools; using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles; @@ -15,6 +17,8 @@ namespace osu.Game.Rulesets.Osu.Edit { } + public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Circles); + public override PlacementBlueprint CreatePlacementBlueprint() => new HitCirclePlacementBlueprint(); } } diff --git a/osu.Game.Rulesets.Osu/Edit/SliderCompositionTool.cs b/osu.Game.Rulesets.Osu/Edit/SliderCompositionTool.cs index a377deb35f..596224e5c6 100644 --- a/osu.Game.Rulesets.Osu/Edit/SliderCompositionTool.cs +++ b/osu.Game.Rulesets.Osu/Edit/SliderCompositionTool.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; +using osu.Game.Beatmaps; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Tools; using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders; @@ -15,6 +17,8 @@ namespace osu.Game.Rulesets.Osu.Edit { } + public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Sliders); + public override PlacementBlueprint CreatePlacementBlueprint() => new SliderPlacementBlueprint(); } } diff --git a/osu.Game.Rulesets.Osu/Edit/SpinnerCompositionTool.cs b/osu.Game.Rulesets.Osu/Edit/SpinnerCompositionTool.cs index 0de0af8f8c..c5e90da3bd 100644 --- a/osu.Game.Rulesets.Osu/Edit/SpinnerCompositionTool.cs +++ b/osu.Game.Rulesets.Osu/Edit/SpinnerCompositionTool.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; +using osu.Game.Beatmaps; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Tools; using osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners; @@ -15,6 +17,8 @@ namespace osu.Game.Rulesets.Osu.Edit { } + public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Spinners); + public override PlacementBlueprint CreatePlacementBlueprint() => new SpinnerPlacementBlueprint(); } } diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorComposeRadioButtons.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorComposeRadioButtons.cs index e4d7e025a8..0b52ae2b95 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorComposeRadioButtons.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorComposeRadioButtons.cs @@ -3,6 +3,7 @@ using NUnit.Framework; using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; using osu.Game.Screens.Edit.Components.RadioButtons; namespace osu.Game.Tests.Visual.Editing @@ -22,7 +23,7 @@ namespace osu.Game.Tests.Visual.Editing { new RadioButton("Item 1", () => { }), new RadioButton("Item 2", () => { }), - new RadioButton("Item 3", () => { }), + new RadioButton("Item 3", () => { }, () => new SpriteIcon { Icon = FontAwesome.Regular.Angry }), new RadioButton("Item 4", () => { }), new RadioButton("Item 5", () => { }) } diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index f134db1ffe..955548fee9 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -126,7 +126,7 @@ namespace osu.Game.Rulesets.Edit toolboxCollection.Items = CompositionTools .Prepend(new SelectTool()) - .Select(t => new RadioButton(t.Name, () => toolSelected(t))) + .Select(t => new RadioButton(t.Name, () => toolSelected(t), t.CreateIcon)) .ToList(); setSelectTool(); diff --git a/osu.Game/Rulesets/Edit/Tools/HitObjectCompositionTool.cs b/osu.Game/Rulesets/Edit/Tools/HitObjectCompositionTool.cs index 0631031302..0a01ac4320 100644 --- a/osu.Game/Rulesets/Edit/Tools/HitObjectCompositionTool.cs +++ b/osu.Game/Rulesets/Edit/Tools/HitObjectCompositionTool.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; + namespace osu.Game.Rulesets.Edit.Tools { public abstract class HitObjectCompositionTool @@ -14,6 +16,8 @@ namespace osu.Game.Rulesets.Edit.Tools public abstract PlacementBlueprint CreatePlacementBlueprint(); + public virtual Drawable CreateIcon() => null; + public override string ToString() => Name; } } diff --git a/osu.Game/Rulesets/Edit/Tools/SelectTool.cs b/osu.Game/Rulesets/Edit/Tools/SelectTool.cs index b96eeb0790..c050766b23 100644 --- a/osu.Game/Rulesets/Edit/Tools/SelectTool.cs +++ b/osu.Game/Rulesets/Edit/Tools/SelectTool.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 osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; + namespace osu.Game.Rulesets.Edit.Tools { public class SelectTool : HitObjectCompositionTool @@ -10,6 +13,8 @@ namespace osu.Game.Rulesets.Edit.Tools { } + public override Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.Solid.MousePointer }; + public override PlacementBlueprint CreatePlacementBlueprint() => null; } } diff --git a/osu.Game/Screens/Edit/Components/RadioButtons/DrawableRadioButton.cs b/osu.Game/Screens/Edit/Components/RadioButtons/DrawableRadioButton.cs index 7be91f4e8e..0cf7b83f3b 100644 --- a/osu.Game/Screens/Edit/Components/RadioButtons/DrawableRadioButton.cs +++ b/osu.Game/Screens/Edit/Components/RadioButtons/DrawableRadioButton.cs @@ -5,7 +5,6 @@ using System; using osu.Framework.Allocation; 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; @@ -29,7 +28,7 @@ namespace osu.Game.Screens.Edit.Components.RadioButtons private Color4 selectedBackgroundColour; private Color4 selectedBubbleColour; - private readonly Drawable bubble; + private Drawable icon; private readonly RadioButton button; public DrawableRadioButton(RadioButton button) @@ -40,19 +39,6 @@ namespace osu.Game.Screens.Edit.Components.RadioButtons Action = button.Select; RelativeSizeAxes = Axes.X; - - bubble = new CircularContainer - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - RelativeSizeAxes = Axes.Both, - FillMode = FillMode.Fit, - Scale = new Vector2(0.5f), - X = 10, - Masking = true, - Blending = BlendingParameters.Additive, - Child = new Box { RelativeSizeAxes = Axes.Both } - }; } [BackgroundDependencyLoader] @@ -73,7 +59,14 @@ namespace osu.Game.Screens.Edit.Components.RadioButtons Colour = Color4.Black.Opacity(0.5f) }; - Add(bubble); + Add(icon = (button.CreateIcon?.Invoke() ?? new Circle()).With(b => + { + b.Blending = BlendingParameters.Additive; + b.Anchor = Anchor.CentreLeft; + b.Origin = Anchor.CentreLeft; + b.Size = new Vector2(20); + b.X = 10; + })); } protected override void LoadComplete() @@ -96,7 +89,7 @@ namespace osu.Game.Screens.Edit.Components.RadioButtons return; BackgroundColour = button.Selected.Value ? selectedBackgroundColour : defaultBackgroundColour; - bubble.Colour = button.Selected.Value ? selectedBubbleColour : defaultBubbleColour; + icon.Colour = button.Selected.Value ? selectedBubbleColour : defaultBubbleColour; } protected override SpriteText CreateText() => new OsuSpriteText diff --git a/osu.Game/Screens/Edit/Components/RadioButtons/RadioButton.cs b/osu.Game/Screens/Edit/Components/RadioButtons/RadioButton.cs index b515d7c8bd..a7b0fb05e3 100644 --- a/osu.Game/Screens/Edit/Components/RadioButtons/RadioButton.cs +++ b/osu.Game/Screens/Edit/Components/RadioButtons/RadioButton.cs @@ -3,6 +3,7 @@ using System; using osu.Framework.Bindables; +using osu.Framework.Graphics; namespace osu.Game.Screens.Edit.Components.RadioButtons { @@ -19,11 +20,17 @@ namespace osu.Game.Screens.Edit.Components.RadioButtons /// public object Item; + /// + /// A function which creates a drawable icon to represent this item. If null, a sane default should be used. + /// + public readonly Func CreateIcon; + private readonly Action action; - public RadioButton(object item, Action action) + public RadioButton(object item, Action action, Func createIcon = null) { Item = item; + CreateIcon = createIcon; this.action = action; Selected = new BindableBool(); } From a65f564e45b9cedee263ae7016d24b7c1f2928f7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 9 Sep 2020 18:39:55 +0900 Subject: [PATCH 1004/1782] Add icons for other ruleset editors --- osu.Game.Rulesets.Mania/Edit/HoldNoteCompositionTool.cs | 4 ++++ osu.Game.Rulesets.Mania/Edit/NoteCompositionTool.cs | 4 ++++ osu.Game.Rulesets.Taiko/Edit/DrumRollCompositionTool.cs | 4 ++++ osu.Game.Rulesets.Taiko/Edit/HitCompositionTool.cs | 4 ++++ osu.Game.Rulesets.Taiko/Edit/SwellCompositionTool.cs | 4 ++++ 5 files changed, 20 insertions(+) diff --git a/osu.Game.Rulesets.Mania/Edit/HoldNoteCompositionTool.cs b/osu.Game.Rulesets.Mania/Edit/HoldNoteCompositionTool.cs index 295bf417c4..a5f10ed436 100644 --- a/osu.Game.Rulesets.Mania/Edit/HoldNoteCompositionTool.cs +++ b/osu.Game.Rulesets.Mania/Edit/HoldNoteCompositionTool.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; +using osu.Game.Beatmaps; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Tools; using osu.Game.Rulesets.Mania.Edit.Blueprints; @@ -14,6 +16,8 @@ namespace osu.Game.Rulesets.Mania.Edit { } + public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Sliders); + public override PlacementBlueprint CreatePlacementBlueprint() => new HoldNotePlacementBlueprint(); } } diff --git a/osu.Game.Rulesets.Mania/Edit/NoteCompositionTool.cs b/osu.Game.Rulesets.Mania/Edit/NoteCompositionTool.cs index 50b5f9a8fe..9f54152596 100644 --- a/osu.Game.Rulesets.Mania/Edit/NoteCompositionTool.cs +++ b/osu.Game.Rulesets.Mania/Edit/NoteCompositionTool.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; +using osu.Game.Beatmaps; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Tools; using osu.Game.Rulesets.Mania.Edit.Blueprints; @@ -15,6 +17,8 @@ namespace osu.Game.Rulesets.Mania.Edit { } + public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Circles); + public override PlacementBlueprint CreatePlacementBlueprint() => new NotePlacementBlueprint(); } } diff --git a/osu.Game.Rulesets.Taiko/Edit/DrumRollCompositionTool.cs b/osu.Game.Rulesets.Taiko/Edit/DrumRollCompositionTool.cs index bf77c76670..587a4efecb 100644 --- a/osu.Game.Rulesets.Taiko/Edit/DrumRollCompositionTool.cs +++ b/osu.Game.Rulesets.Taiko/Edit/DrumRollCompositionTool.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; +using osu.Game.Beatmaps; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Tools; using osu.Game.Rulesets.Taiko.Edit.Blueprints; @@ -15,6 +17,8 @@ namespace osu.Game.Rulesets.Taiko.Edit { } + public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Sliders); + public override PlacementBlueprint CreatePlacementBlueprint() => new DrumRollPlacementBlueprint(); } } diff --git a/osu.Game.Rulesets.Taiko/Edit/HitCompositionTool.cs b/osu.Game.Rulesets.Taiko/Edit/HitCompositionTool.cs index e877cf6240..3e97b4e322 100644 --- a/osu.Game.Rulesets.Taiko/Edit/HitCompositionTool.cs +++ b/osu.Game.Rulesets.Taiko/Edit/HitCompositionTool.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; +using osu.Game.Beatmaps; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Tools; using osu.Game.Rulesets.Taiko.Edit.Blueprints; @@ -15,6 +17,8 @@ namespace osu.Game.Rulesets.Taiko.Edit { } + public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Circles); + public override PlacementBlueprint CreatePlacementBlueprint() => new HitPlacementBlueprint(); } } diff --git a/osu.Game.Rulesets.Taiko/Edit/SwellCompositionTool.cs b/osu.Game.Rulesets.Taiko/Edit/SwellCompositionTool.cs index a6191fcedc..918afde1dd 100644 --- a/osu.Game.Rulesets.Taiko/Edit/SwellCompositionTool.cs +++ b/osu.Game.Rulesets.Taiko/Edit/SwellCompositionTool.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; +using osu.Game.Beatmaps; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Tools; using osu.Game.Rulesets.Taiko.Edit.Blueprints; @@ -15,6 +17,8 @@ namespace osu.Game.Rulesets.Taiko.Edit { } + public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Spinners); + public override PlacementBlueprint CreatePlacementBlueprint() => new SwellPlacementBlueprint(); } } From d3957e6155de4871e74d41fc7efe91b6eda53d6e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 9 Sep 2020 18:48:02 +0900 Subject: [PATCH 1005/1782] Move title specification for settings groups to constructor Using an abstract property was awkward for this as it is being consumed in the underlying constructor but could not be dynamically set in time from a derived class. --- .../Gameplay/TestSceneReplaySettingsOverlay.cs | 5 ++++- .../Ladder/Components/LadderEditorSettings.cs | 7 +++++-- osu.Game/Rulesets/Edit/ToolboxGroup.cs | 3 +-- .../Play/PlayerSettings/CollectionSettings.cs | 5 ++++- .../Play/PlayerSettings/DiscussionSettings.cs | 5 ++++- .../Screens/Play/PlayerSettings/InputSettings.cs | 3 +-- .../Screens/Play/PlayerSettings/PlaybackSettings.cs | 3 +-- .../Play/PlayerSettings/PlayerSettingsGroup.cs | 13 ++++++------- .../Screens/Play/PlayerSettings/VisualSettings.cs | 3 +-- 9 files changed, 27 insertions(+), 20 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplaySettingsOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplaySettingsOverlay.cs index cdfb3beb19..f8fab784cc 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneReplaySettingsOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplaySettingsOverlay.cs @@ -48,7 +48,10 @@ namespace osu.Game.Tests.Visual.Gameplay private class ExampleContainer : PlayerSettingsGroup { - protected override string Title => @"example"; + public ExampleContainer() + : base("example") + { + } } } } diff --git a/osu.Game.Tournament/Screens/Ladder/Components/LadderEditorSettings.cs b/osu.Game.Tournament/Screens/Ladder/Components/LadderEditorSettings.cs index fa530ea2c4..b60eb814e5 100644 --- a/osu.Game.Tournament/Screens/Ladder/Components/LadderEditorSettings.cs +++ b/osu.Game.Tournament/Screens/Ladder/Components/LadderEditorSettings.cs @@ -20,8 +20,6 @@ namespace osu.Game.Tournament.Screens.Ladder.Components { private const int padding = 10; - protected override string Title => @"ladder"; - private SettingsDropdown roundDropdown; private PlayerCheckbox losersCheckbox; private DateTextBox dateTimeBox; @@ -34,6 +32,11 @@ namespace osu.Game.Tournament.Screens.Ladder.Components [Resolved] private LadderInfo ladderInfo { get; set; } + public LadderEditorSettings() + : base("ladder") + { + } + [BackgroundDependencyLoader] private void load() { diff --git a/osu.Game/Rulesets/Edit/ToolboxGroup.cs b/osu.Game/Rulesets/Edit/ToolboxGroup.cs index eabb834616..7e17d88e17 100644 --- a/osu.Game/Rulesets/Edit/ToolboxGroup.cs +++ b/osu.Game/Rulesets/Edit/ToolboxGroup.cs @@ -8,9 +8,8 @@ namespace osu.Game.Rulesets.Edit { public class ToolboxGroup : PlayerSettingsGroup { - protected override string Title => "toolbox"; - public ToolboxGroup() + : base("toolbox") { RelativeSizeAxes = Axes.X; Width = 1; diff --git a/osu.Game/Screens/Play/PlayerSettings/CollectionSettings.cs b/osu.Game/Screens/Play/PlayerSettings/CollectionSettings.cs index d3570a8d2d..9e7f8e7394 100644 --- a/osu.Game/Screens/Play/PlayerSettings/CollectionSettings.cs +++ b/osu.Game/Screens/Play/PlayerSettings/CollectionSettings.cs @@ -10,7 +10,10 @@ namespace osu.Game.Screens.Play.PlayerSettings { public class CollectionSettings : PlayerSettingsGroup { - protected override string Title => @"collections"; + public CollectionSettings() + : base("collections") + { + } [BackgroundDependencyLoader] private void load() diff --git a/osu.Game/Screens/Play/PlayerSettings/DiscussionSettings.cs b/osu.Game/Screens/Play/PlayerSettings/DiscussionSettings.cs index bb4eea47ca..ac040774ee 100644 --- a/osu.Game/Screens/Play/PlayerSettings/DiscussionSettings.cs +++ b/osu.Game/Screens/Play/PlayerSettings/DiscussionSettings.cs @@ -10,7 +10,10 @@ namespace osu.Game.Screens.Play.PlayerSettings { public class DiscussionSettings : PlayerSettingsGroup { - protected override string Title => @"discussions"; + public DiscussionSettings() + : base("discussions") + { + } [BackgroundDependencyLoader] private void load(OsuConfigManager config) diff --git a/osu.Game/Screens/Play/PlayerSettings/InputSettings.cs b/osu.Game/Screens/Play/PlayerSettings/InputSettings.cs index 7a8696e27c..725a6e86bf 100644 --- a/osu.Game/Screens/Play/PlayerSettings/InputSettings.cs +++ b/osu.Game/Screens/Play/PlayerSettings/InputSettings.cs @@ -9,11 +9,10 @@ namespace osu.Game.Screens.Play.PlayerSettings { public class InputSettings : PlayerSettingsGroup { - protected override string Title => "Input settings"; - private readonly PlayerCheckbox mouseButtonsCheckbox; public InputSettings() + : base("Input Settings") { Children = new Drawable[] { diff --git a/osu.Game/Screens/Play/PlayerSettings/PlaybackSettings.cs b/osu.Game/Screens/Play/PlayerSettings/PlaybackSettings.cs index c691d161ed..24ddc277cd 100644 --- a/osu.Game/Screens/Play/PlayerSettings/PlaybackSettings.cs +++ b/osu.Game/Screens/Play/PlayerSettings/PlaybackSettings.cs @@ -13,8 +13,6 @@ namespace osu.Game.Screens.Play.PlayerSettings { private const int padding = 10; - protected override string Title => @"playback"; - public readonly Bindable UserPlaybackRate = new BindableDouble(1) { Default = 1, @@ -28,6 +26,7 @@ namespace osu.Game.Screens.Play.PlayerSettings private readonly OsuSpriteText multiplierText; public PlaybackSettings() + : base("playback") { Children = new Drawable[] { diff --git a/osu.Game/Screens/Play/PlayerSettings/PlayerSettingsGroup.cs b/osu.Game/Screens/Play/PlayerSettings/PlayerSettingsGroup.cs index 90424ec007..7928d41e3b 100644 --- a/osu.Game/Screens/Play/PlayerSettings/PlayerSettingsGroup.cs +++ b/osu.Game/Screens/Play/PlayerSettings/PlayerSettingsGroup.cs @@ -17,11 +17,6 @@ namespace osu.Game.Screens.Play.PlayerSettings { public abstract class PlayerSettingsGroup : Container { - /// - /// The title to be displayed in the header of this group. - /// - protected abstract string Title { get; } - private const float transition_duration = 250; private const int container_width = 270; private const int border_thickness = 2; @@ -58,7 +53,11 @@ namespace osu.Game.Screens.Play.PlayerSettings private Color4 expandedColour; - protected PlayerSettingsGroup() + /// + /// Create a new instance. + /// + /// The title to be displayed in the header of this group. + protected PlayerSettingsGroup(string title) { AutoSizeAxes = Axes.Y; Width = container_width; @@ -95,7 +94,7 @@ namespace osu.Game.Screens.Play.PlayerSettings { Origin = Anchor.CentreLeft, Anchor = Anchor.CentreLeft, - Text = Title.ToUpperInvariant(), + Text = title.ToUpperInvariant(), Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 17), Margin = new MarginPadding { Left = 10 }, }, diff --git a/osu.Game/Screens/Play/PlayerSettings/VisualSettings.cs b/osu.Game/Screens/Play/PlayerSettings/VisualSettings.cs index d6c66d0751..e06cf5c6d5 100644 --- a/osu.Game/Screens/Play/PlayerSettings/VisualSettings.cs +++ b/osu.Game/Screens/Play/PlayerSettings/VisualSettings.cs @@ -10,8 +10,6 @@ namespace osu.Game.Screens.Play.PlayerSettings { public class VisualSettings : PlayerSettingsGroup { - protected override string Title => "Visual settings"; - private readonly PlayerSliderBar dimSliderBar; private readonly PlayerSliderBar blurSliderBar; private readonly PlayerCheckbox showStoryboardToggle; @@ -19,6 +17,7 @@ namespace osu.Game.Screens.Play.PlayerSettings private readonly PlayerCheckbox beatmapHitsoundsToggle; public VisualSettings() + : base("Visual Settings") { Children = new Drawable[] { From fb2aced3ac32d4e312913e410557885700c85933 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 9 Sep 2020 19:14:28 +0900 Subject: [PATCH 1006/1782] Add toggle for distance snap --- .../Edit/OsuHitObjectComposer.cs | 13 +++++++++++++ osu.Game/Rulesets/Edit/HitObjectComposer.cs | 18 +++++++++++++++++- osu.Game/Rulesets/Edit/ToolboxGroup.cs | 4 ++-- 3 files changed, 32 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index 37019a7a05..f87bd53ec3 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Caching; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -38,6 +39,13 @@ namespace osu.Game.Rulesets.Osu.Edit new SpinnerCompositionTool() }; + private readonly BindableBool distanceSnapToggle = new BindableBool(true) { Description = "Distance Snap" }; + + protected override IEnumerable Toggles => new[] + { + distanceSnapToggle + }; + [BackgroundDependencyLoader] private void load() { @@ -45,6 +53,7 @@ namespace osu.Game.Rulesets.Osu.Edit EditorBeatmap.SelectedHitObjects.CollectionChanged += (_, __) => updateDistanceSnapGrid(); EditorBeatmap.PlacementObject.ValueChanged += _ => updateDistanceSnapGrid(); + distanceSnapToggle.ValueChanged += _ => updateDistanceSnapGrid(); } protected override ComposeBlueprintContainer CreateBlueprintContainer(IEnumerable hitObjects) @@ -87,6 +96,10 @@ namespace osu.Game.Rulesets.Osu.Edit { distanceSnapGridContainer.Clear(); distanceSnapGridCache.Invalidate(); + distanceSnapGrid = null; + + if (!distanceSnapToggle.Value) + return; switch (BlueprintContainer.CurrentTool) { diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index f134db1ffe..ee42cd9bae 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Collections.Specialized; using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input; @@ -13,6 +14,7 @@ using osu.Framework.Input.Events; using osu.Framework.Logging; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Overlays.Settings; using osu.Game.Rulesets.Configuration; using osu.Game.Rulesets.Edit.Tools; using osu.Game.Rulesets.Mods; @@ -94,7 +96,15 @@ namespace osu.Game.Rulesets.Edit Padding = new MarginPadding { Right = 10 }, Children = new Drawable[] { - new ToolboxGroup { Child = toolboxCollection = new RadioButtonCollection { RelativeSizeAxes = Axes.X } } + new ToolboxGroup("toolbox") { Child = toolboxCollection = new RadioButtonCollection { RelativeSizeAxes = Axes.X } }, + new ToolboxGroup("toggles") + { + ChildrenEnumerable = Toggles.Select(b => new SettingsCheckbox + { + Bindable = b, + LabelText = b?.Description ?? "unknown" + }) + } } }, new Container @@ -156,6 +166,12 @@ namespace osu.Game.Rulesets.Edit /// protected abstract IReadOnlyList CompositionTools { get; } + /// + /// A collection of toggles which will be displayed to the user. + /// The display name will be decided by . + /// + protected virtual IEnumerable Toggles => Enumerable.Empty(); + /// /// Construct a relevant blueprint container. This will manage hitobject selection/placement input handling and display logic. /// diff --git a/osu.Game/Rulesets/Edit/ToolboxGroup.cs b/osu.Game/Rulesets/Edit/ToolboxGroup.cs index 7e17d88e17..22b2b05657 100644 --- a/osu.Game/Rulesets/Edit/ToolboxGroup.cs +++ b/osu.Game/Rulesets/Edit/ToolboxGroup.cs @@ -8,8 +8,8 @@ namespace osu.Game.Rulesets.Edit { public class ToolboxGroup : PlayerSettingsGroup { - public ToolboxGroup() - : base("toolbox") + public ToolboxGroup(string title) + : base(title) { RelativeSizeAxes = Axes.X; Width = 1; From d210e056294b8bd92e9828e6a7c30c3ae960d239 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 9 Sep 2020 19:20:11 +0900 Subject: [PATCH 1007/1782] Add a touch of spacing between toolbox groups --- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index ee42cd9bae..928cdd2ea0 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -94,6 +94,7 @@ namespace osu.Game.Rulesets.Edit Name = "Sidebar", RelativeSizeAxes = Axes.Both, Padding = new MarginPadding { Right = 10 }, + Spacing = new Vector2(10), Children = new Drawable[] { new ToolboxGroup("toolbox") { Child = toolboxCollection = new RadioButtonCollection { RelativeSizeAxes = Axes.X } }, From ac0c4fcb8c2bfb162b820c9b03a128304fe31d0b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 9 Sep 2020 19:31:18 +0900 Subject: [PATCH 1008/1782] Add prompt to save beatmap on exiting editor --- osu.Game/Screens/Edit/Editor.cs | 57 ++++++++++++++------ osu.Game/Screens/Edit/PromptForSaveDialog.cs | 33 ++++++++++++ 2 files changed, 74 insertions(+), 16 deletions(-) create mode 100644 osu.Game/Screens/Edit/PromptForSaveDialog.cs diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index ac1f61c4fd..7e17225846 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -2,39 +2,40 @@ // See the LICENCE file in the repository root for full licence text. using System; -using osuTK.Graphics; -using osu.Framework.Screens; +using System.Collections.Generic; +using osu.Framework; +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.Screens.Edit.Components.Timelines.Summary; -using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics.UserInterface; -using osu.Framework.Input.Events; -using osu.Framework.Platform; -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.Design; -using osuTK.Input; -using System.Collections.Generic; -using osu.Framework; using osu.Framework.Input; using osu.Framework.Input.Bindings; +using osu.Framework.Input.Events; using osu.Framework.Logging; +using osu.Framework.Platform; +using osu.Framework.Screens; +using osu.Framework.Timing; using osu.Game.Beatmaps; +using osu.Game.Graphics; using osu.Game.Graphics.Cursor; +using osu.Game.Graphics.UserInterface; using osu.Game.Input.Bindings; using osu.Game.Online.API; +using osu.Game.Overlays; using osu.Game.Rulesets.Edit; +using osu.Game.Screens.Edit.Components; +using osu.Game.Screens.Edit.Components.Menus; +using osu.Game.Screens.Edit.Components.Timelines.Summary; using osu.Game.Screens.Edit.Compose; +using osu.Game.Screens.Edit.Design; using osu.Game.Screens.Edit.Setup; using osu.Game.Screens.Edit.Timing; using osu.Game.Screens.Play; using osu.Game.Users; +using osuTK.Graphics; +using osuTK.Input; namespace osu.Game.Screens.Edit { @@ -54,6 +55,11 @@ namespace osu.Game.Screens.Edit [Resolved] private BeatmapManager beatmapManager { get; set; } + [Resolved(canBeNull: true)] + private DialogOverlay dialogOverlay { get; set; } + + private bool exitConfirmed; + private Box bottomBackground; private Container screenContainer; @@ -346,12 +352,31 @@ namespace osu.Game.Screens.Edit public override bool OnExiting(IScreen next) { + if (!exitConfirmed && dialogOverlay != null) + { + dialogOverlay?.Push(new PromptForSaveDialog(confirmExit, confirmExitWithSave)); + return true; + } + Background.FadeColour(Color4.White, 500); resetTrack(); return base.OnExiting(next); } + private void confirmExitWithSave() + { + exitConfirmed = true; + saveBeatmap(); + this.Exit(); + } + + private void confirmExit() + { + exitConfirmed = true; + this.Exit(); + } + protected void Undo() => changeHandler.RestoreState(-1); protected void Redo() => changeHandler.RestoreState(1); diff --git a/osu.Game/Screens/Edit/PromptForSaveDialog.cs b/osu.Game/Screens/Edit/PromptForSaveDialog.cs new file mode 100644 index 0000000000..38d956557d --- /dev/null +++ b/osu.Game/Screens/Edit/PromptForSaveDialog.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 osu.Framework.Graphics.Sprites; +using osu.Game.Overlays.Dialog; + +namespace osu.Game.Screens.Edit +{ + public class PromptForSaveDialog : PopupDialog + { + public PromptForSaveDialog(Action exit, Action saveAndExit) + { + HeaderText = "Did you want to save your changes?"; + + Icon = FontAwesome.Regular.Save; + + Buttons = new PopupDialogButton[] + { + new PopupDialogCancelButton + { + Text = @"Save my masterpiece!", + Action = saveAndExit + }, + new PopupDialogOkButton + { + Text = @"Forget all changes", + Action = exit + }, + }; + } + } +} From 6f067ff300910cf82a0cfac72d3578fd73c520d2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 9 Sep 2020 19:40:41 +0900 Subject: [PATCH 1009/1782] Only show confirmation if changes have been made since last save --- osu.Game/Screens/Edit/Editor.cs | 13 ++++++++++++- osu.Game/Screens/Edit/EditorChangeHandler.cs | 13 +++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 7e17225846..58395e4848 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -60,6 +60,8 @@ namespace osu.Game.Screens.Edit private bool exitConfirmed; + private string lastSavedHash; + private Box bottomBackground; private Container screenContainer; @@ -124,6 +126,8 @@ namespace osu.Game.Screens.Edit changeHandler = new EditorChangeHandler(editorBeatmap); dependencies.CacheAs(changeHandler); + updateLastSavedHash(); + EditorMenuBar menuBar; OsuMenuItem undoMenuItem; OsuMenuItem redoMenuItem; @@ -352,7 +356,7 @@ namespace osu.Game.Screens.Edit public override bool OnExiting(IScreen next) { - if (!exitConfirmed && dialogOverlay != null) + if (!exitConfirmed && dialogOverlay != null && changeHandler.CurrentStateHash != lastSavedHash) { dialogOverlay?.Push(new PromptForSaveDialog(confirmExit, confirmExitWithSave)); return true; @@ -447,6 +451,8 @@ namespace osu.Game.Screens.Edit // save the loaded beatmap's data stream. beatmapManager.Save(playableBeatmap.BeatmapInfo, editorBeatmap, editorBeatmap.BeatmapSkin); + + updateLastSavedHash(); } private void exportBeatmap() @@ -455,6 +461,11 @@ namespace osu.Game.Screens.Edit beatmapManager.Export(Beatmap.Value.BeatmapSetInfo); } + private void updateLastSavedHash() + { + lastSavedHash = changeHandler.CurrentStateHash; + } + public double SnapTime(double time, double? referenceTime) => editorBeatmap.SnapTime(time, referenceTime); public double GetBeatLengthAtTime(double referenceTime) => editorBeatmap.GetBeatLengthAtTime(referenceTime); diff --git a/osu.Game/Screens/Edit/EditorChangeHandler.cs b/osu.Game/Screens/Edit/EditorChangeHandler.cs index 927c823c64..aa0f89912a 100644 --- a/osu.Game/Screens/Edit/EditorChangeHandler.cs +++ b/osu.Game/Screens/Edit/EditorChangeHandler.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.IO; using System.Text; using osu.Framework.Bindables; +using osu.Framework.Extensions; using osu.Game.Beatmaps.Formats; using osu.Game.Rulesets.Objects; @@ -24,6 +25,18 @@ namespace osu.Game.Screens.Edit private int currentState = -1; + /// + /// A SHA-2 hash representing the current visible editor state. + /// + public string CurrentStateHash + { + get + { + using (var stream = new MemoryStream(savedStates[currentState])) + return stream.ComputeSHA2Hash(); + } + } + private readonly EditorBeatmap editorBeatmap; private int bulkChangesStarted; private bool isRestoring; From 327179a81efbc9524be1a3a7d0ba1d54a3e46dff Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 9 Sep 2020 19:42:03 +0900 Subject: [PATCH 1010/1782] Expose unsaved changes state --- osu.Game/Screens/Edit/Editor.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 58395e4848..34c69d09e0 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -52,6 +52,8 @@ namespace osu.Game.Screens.Edit public override bool AllowRateAdjustments => false; + public bool HasUnsavedChanges => lastSavedHash != changeHandler.CurrentStateHash; + [Resolved] private BeatmapManager beatmapManager { get; set; } @@ -356,7 +358,7 @@ namespace osu.Game.Screens.Edit public override bool OnExiting(IScreen next) { - if (!exitConfirmed && dialogOverlay != null && changeHandler.CurrentStateHash != lastSavedHash) + if (!exitConfirmed && dialogOverlay != null && HasUnsavedChanges) { dialogOverlay?.Push(new PromptForSaveDialog(confirmExit, confirmExitWithSave)); return true; From c6e72dabd372c35a589e2b5c23220e5478b43536 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 9 Sep 2020 19:57:28 +0900 Subject: [PATCH 1011/1782] Add test coverage --- .../Editing/TestSceneEditorChangeStates.cs | 29 +++++++++++++++-- osu.Game/Screens/Edit/Editor.cs | 32 +++++++++---------- osu.Game/Tests/Beatmaps/TestBeatmap.cs | 1 + 3 files changed, 43 insertions(+), 19 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorChangeStates.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorChangeStates.cs index 293a6e6869..c8a32d966f 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorChangeStates.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorChangeStates.cs @@ -18,6 +18,8 @@ namespace osu.Game.Tests.Visual.Editing protected override Ruleset CreateEditorRuleset() => new OsuRuleset(); + protected new TestEditor Editor => (TestEditor)base.Editor; + public override void SetUpSteps() { base.SetUpSteps(); @@ -35,6 +37,7 @@ namespace osu.Game.Tests.Visual.Editing addUndoSteps(); AddAssert("no change occurred", () => hitObjectCount == editorBeatmap.HitObjects.Count); + AddAssert("no unsaved changes", () => !Editor.HasUnsavedChanges); } [Test] @@ -47,6 +50,7 @@ namespace osu.Game.Tests.Visual.Editing addRedoSteps(); AddAssert("no change occurred", () => hitObjectCount == editorBeatmap.HitObjects.Count); + AddAssert("no unsaved changes", () => !Editor.HasUnsavedChanges); } [Test] @@ -64,9 +68,11 @@ namespace osu.Game.Tests.Visual.Editing AddStep("add hitobject", () => editorBeatmap.Add(expectedObject = new HitCircle { StartTime = 1000 })); AddAssert("hitobject added", () => addedObject == expectedObject); + AddAssert("unsaved changes", () => Editor.HasUnsavedChanges); addUndoSteps(); AddAssert("hitobject removed", () => removedObject == expectedObject); + AddAssert("no unsaved changes", () => !Editor.HasUnsavedChanges); } [Test] @@ -94,6 +100,17 @@ namespace osu.Game.Tests.Visual.Editing addRedoSteps(); AddAssert("hitobject added", () => addedObject.StartTime == expectedObject.StartTime); // Can't compare via equality (new hitobject instance) AddAssert("no hitobject removed", () => removedObject == null); + AddAssert("unsaved changes", () => Editor.HasUnsavedChanges); + } + + [Test] + public void TestAddObjectThenSaveHasNoUnsavedChanges() + { + AddStep("add hitobject", () => editorBeatmap.Add(new HitCircle { StartTime = 1000 })); + + AddAssert("unsaved changes", () => Editor.HasUnsavedChanges); + AddStep("save changes", () => Editor.Save()); + AddAssert("no unsaved changes", () => !Editor.HasUnsavedChanges); } [Test] @@ -120,6 +137,7 @@ namespace osu.Game.Tests.Visual.Editing addUndoSteps(); AddAssert("hitobject added", () => addedObject.StartTime == expectedObject.StartTime); // Can't compare via equality (new hitobject instance) AddAssert("no hitobject removed", () => removedObject == null); + AddAssert("unsaved changes", () => Editor.HasUnsavedChanges); // 2 steps performed, 1 undone } [Test] @@ -148,19 +166,24 @@ namespace osu.Game.Tests.Visual.Editing addRedoSteps(); AddAssert("hitobject removed", () => removedObject.StartTime == expectedObject.StartTime); // Can't compare via equality (new hitobject instance after undo) AddAssert("no hitobject added", () => addedObject == null); + AddAssert("no changes", () => !Editor.HasUnsavedChanges); // end result is empty beatmap, matching original state } - private void addUndoSteps() => AddStep("undo", () => ((TestEditor)Editor).Undo()); + private void addUndoSteps() => AddStep("undo", () => Editor.Undo()); - private void addRedoSteps() => AddStep("redo", () => ((TestEditor)Editor).Redo()); + private void addRedoSteps() => AddStep("redo", () => Editor.Redo()); protected override Editor CreateEditor() => new TestEditor(); - private class TestEditor : Editor + protected class TestEditor : Editor { public new void Undo() => base.Undo(); public new void Redo() => base.Redo(); + + public new void Save() => base.Save(); + + public new bool HasUnsavedChanges => base.HasUnsavedChanges; } } } diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 34c69d09e0..23eb704920 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -52,7 +52,7 @@ namespace osu.Game.Screens.Edit public override bool AllowRateAdjustments => false; - public bool HasUnsavedChanges => lastSavedHash != changeHandler.CurrentStateHash; + protected bool HasUnsavedChanges => lastSavedHash != changeHandler.CurrentStateHash; [Resolved] private BeatmapManager beatmapManager { get; set; } @@ -136,7 +136,7 @@ namespace osu.Game.Screens.Edit var fileMenuItems = new List { - new EditorMenuItem("Save", MenuItemType.Standard, saveBeatmap) + new EditorMenuItem("Save", MenuItemType.Standard, Save) }; if (RuntimeInfo.IsDesktop) @@ -249,6 +249,17 @@ namespace osu.Game.Screens.Edit bottomBackground.Colour = colours.Gray2; } + protected void Save() + { + // apply any set-level metadata changes. + beatmapManager.Update(playableBeatmap.BeatmapInfo.BeatmapSet); + + // save the loaded beatmap's data stream. + beatmapManager.Save(playableBeatmap.BeatmapInfo, editorBeatmap, editorBeatmap.BeatmapSkin); + + updateLastSavedHash(); + } + protected override void Update() { base.Update(); @@ -268,7 +279,7 @@ namespace osu.Game.Screens.Edit return true; case PlatformActionType.Save: - saveBeatmap(); + Save(); return true; } @@ -373,7 +384,7 @@ namespace osu.Game.Screens.Edit private void confirmExitWithSave() { exitConfirmed = true; - saveBeatmap(); + Save(); this.Exit(); } @@ -446,20 +457,9 @@ namespace osu.Game.Screens.Edit clock.SeekForward(!clock.IsRunning, amount); } - private void saveBeatmap() - { - // apply any set-level metadata changes. - beatmapManager.Update(playableBeatmap.BeatmapInfo.BeatmapSet); - - // save the loaded beatmap's data stream. - beatmapManager.Save(playableBeatmap.BeatmapInfo, editorBeatmap, editorBeatmap.BeatmapSkin); - - updateLastSavedHash(); - } - private void exportBeatmap() { - saveBeatmap(); + Save(); beatmapManager.Export(Beatmap.Value.BeatmapSetInfo); } diff --git a/osu.Game/Tests/Beatmaps/TestBeatmap.cs b/osu.Game/Tests/Beatmaps/TestBeatmap.cs index 9fc20fd0f2..a375a17bcf 100644 --- a/osu.Game/Tests/Beatmaps/TestBeatmap.cs +++ b/osu.Game/Tests/Beatmaps/TestBeatmap.cs @@ -27,6 +27,7 @@ namespace osu.Game.Tests.Beatmaps BeatmapInfo.Ruleset = ruleset; BeatmapInfo.RulesetID = ruleset.ID ?? 0; BeatmapInfo.BeatmapSet.Metadata = BeatmapInfo.Metadata; + BeatmapInfo.BeatmapSet.Files = new List(); BeatmapInfo.BeatmapSet.Beatmaps = new List { BeatmapInfo }; BeatmapInfo.BeatmapSet.OnlineInfo = new BeatmapSetOnlineInfo { From 1803ecad8001db57c39b62fbab2ecacc07d7a9fe Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 9 Sep 2020 20:00:38 +0900 Subject: [PATCH 1012/1782] Add cancel exit button --- osu.Game/Screens/Edit/PromptForSaveDialog.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Screens/Edit/PromptForSaveDialog.cs b/osu.Game/Screens/Edit/PromptForSaveDialog.cs index 38d956557d..16504b47bd 100644 --- a/osu.Game/Screens/Edit/PromptForSaveDialog.cs +++ b/osu.Game/Screens/Edit/PromptForSaveDialog.cs @@ -27,6 +27,10 @@ namespace osu.Game.Screens.Edit Text = @"Forget all changes", Action = exit }, + new PopupDialogCancelButton + { + Text = @"Oops, continue editing", + }, }; } } From aeae009512aad6f33a7ba5b4a54b161460cc3ead Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 9 Sep 2020 20:11:29 +0900 Subject: [PATCH 1013/1782] Disable online beatmap lookups in tests --- osu.Game/Beatmaps/BeatmapManager.cs | 12 ++++++++---- osu.Game/OsuGameBase.cs | 2 +- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 34bb578b2a..d53f85c68d 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -66,12 +66,14 @@ namespace osu.Game.Beatmaps private readonly RulesetStore rulesets; private readonly BeatmapStore beatmaps; private readonly AudioManager audioManager; - private readonly BeatmapOnlineLookupQueue onlineLookupQueue; private readonly TextureStore textureStore; private readonly ITrackStore trackStore; + [CanBeNull] + private readonly BeatmapOnlineLookupQueue onlineLookupQueue; + public BeatmapManager(Storage storage, IDatabaseContextFactory contextFactory, RulesetStore rulesets, IAPIProvider api, [NotNull] AudioManager audioManager, GameHost host = null, - WorkingBeatmap defaultBeatmap = null) + WorkingBeatmap defaultBeatmap = null, bool performOnlineLookups = false) : base(storage, contextFactory, api, new BeatmapStore(contextFactory), host) { this.rulesets = rulesets; @@ -85,7 +87,8 @@ namespace osu.Game.Beatmaps beatmaps.ItemRemoved += removeWorkingCache; beatmaps.ItemUpdated += removeWorkingCache; - onlineLookupQueue = new BeatmapOnlineLookupQueue(api, storage); + if (performOnlineLookups) + onlineLookupQueue = new BeatmapOnlineLookupQueue(api, storage); textureStore = new LargeTextureStore(host?.CreateTextureLoaderStore(Files.Store)); trackStore = audioManager.GetTrackStore(Files.Store); @@ -142,7 +145,8 @@ namespace osu.Game.Beatmaps bool hadOnlineBeatmapIDs = beatmapSet.Beatmaps.Any(b => b.OnlineBeatmapID > 0); - await onlineLookupQueue.UpdateAsync(beatmapSet, cancellationToken); + if (onlineLookupQueue != null) + await onlineLookupQueue.UpdateAsync(beatmapSet, cancellationToken); // ensure at least one beatmap was able to retrieve or keep an online ID, else drop the set ID. if (hadOnlineBeatmapIDs && !beatmapSet.Beatmaps.Any(b => b.OnlineBeatmapID > 0)) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 4bc8f4c527..b61017f038 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -198,7 +198,7 @@ namespace osu.Game // ordering is important here to ensure foreign keys rules are not broken in ModelStore.Cleanup() dependencies.Cache(ScoreManager = new ScoreManager(RulesetStore, () => BeatmapManager, Storage, API, contextFactory, Host)); - dependencies.Cache(BeatmapManager = new BeatmapManager(Storage, contextFactory, RulesetStore, API, Audio, Host, defaultBeatmap)); + dependencies.Cache(BeatmapManager = new BeatmapManager(Storage, contextFactory, RulesetStore, API, Audio, Host, defaultBeatmap, true)); // 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 From bbef7ff720c91dce79839d9d3182fbc712ec388b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 9 Sep 2020 20:19:07 +0900 Subject: [PATCH 1014/1782] Fix leaderboard loading spinner disappearing too early --- osu.Game/Online/Leaderboards/Leaderboard.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs index db0f835c67..084ba89f6e 100644 --- a/osu.Game/Online/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -56,13 +56,14 @@ namespace osu.Game.Online.Leaderboards scrollFlow?.FadeOut(fade_duration, Easing.OutQuint).Expire(); scrollFlow = null; - loading.Hide(); - showScoresDelegate?.Cancel(); showScoresCancellationSource?.Cancel(); if (scores == null || !scores.Any()) + { + loading.Hide(); return; + } // ensure placeholder is hidden when displaying scores PlaceholderState = PlaceholderState.Successful; @@ -84,6 +85,7 @@ namespace osu.Game.Online.Leaderboards } scrollContainer.ScrollTo(0f, false); + loading.Hide(); }, (showScoresCancellationSource = new CancellationTokenSource()).Token)); } } From 12188ec3c931bc30e5bdb7e1c99f988027dbc33b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 9 Sep 2020 20:49:59 +0900 Subject: [PATCH 1015/1782] Fix broken RollingCounter current value --- osu.Game/Graphics/UserInterface/RollingCounter.cs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/RollingCounter.cs b/osu.Game/Graphics/UserInterface/RollingCounter.cs index ece1b8e22c..91a557094d 100644 --- a/osu.Game/Graphics/UserInterface/RollingCounter.cs +++ b/osu.Game/Graphics/UserInterface/RollingCounter.cs @@ -9,16 +9,20 @@ using System; using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Graphics.UserInterface; namespace osu.Game.Graphics.UserInterface { - public abstract class RollingCounter : Container + public abstract class RollingCounter : Container, IHasCurrentValue where T : struct, IEquatable { - /// - /// The current value. - /// - public Bindable Current = new Bindable(); + private readonly BindableWithCurrent current = new BindableWithCurrent(); + + public Bindable Current + { + get => current.Current; + set => current.Current = value; + } private SpriteText displayedCountSpriteText; From d7ca2cf1cca15da8fe476dfd4292d5bbd77cd167 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 9 Sep 2020 22:01:09 +0900 Subject: [PATCH 1016/1782] Replace loaded check with better variation --- .../Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs index 2fd522dc9d..3a842d0a43 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs @@ -120,7 +120,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores statisticsColumns.ChildrenEnumerable = value.SortedStatistics.Select(kvp => createStatisticsColumn(kvp.Key, kvp.Value)); modsColumn.Mods = value.Mods; - if (IsLoaded) + if (scoreManager != null) totalScoreColumn.Current = scoreManager.GetBindableTotalScoreString(value); } } From 43525614adefa151a20805225646b4421c9cbd3c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 9 Sep 2020 23:10:21 +0900 Subject: [PATCH 1017/1782] Store raw BeatmapCollection in filter control --- osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs | 2 +- osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs | 2 +- osu.Game/Screens/Select/FilterControl.cs | 2 +- osu.Game/Screens/Select/FilterCriteria.cs | 3 ++- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs index 6012150513..f89f22bf23 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs @@ -204,7 +204,7 @@ namespace osu.Game.Tests.Visual.SongSelect InputManager.Click(MouseButton.Left); }); - AddAssert("collection filter still selected", () => control.CreateCriteria().Collection?.CollectionName.Value == "1"); + AddAssert("collection filter still selected", () => control.CreateCriteria().Collection?.Name.Value == "1"); } private void assertCollectionHeaderDisplays(string collectionName, bool shouldDisplay = true) diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs index 8e5655e514..3892e02a8f 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs @@ -61,7 +61,7 @@ namespace osu.Game.Screens.Select.Carousel } if (match) - match &= criteria.Collection?.ContainsBeatmap(Beatmap) ?? true; + match &= criteria.Collection?.Beatmaps.Contains(Beatmap) ?? true; Filtered.Value = !match; } diff --git a/osu.Game/Screens/Select/FilterControl.cs b/osu.Game/Screens/Select/FilterControl.cs index 41ce0d65cd..9128160608 100644 --- a/osu.Game/Screens/Select/FilterControl.cs +++ b/osu.Game/Screens/Select/FilterControl.cs @@ -42,7 +42,7 @@ namespace osu.Game.Screens.Select Sort = sortMode.Value, AllowConvertedBeatmaps = showConverted.Value, Ruleset = ruleset.Value, - Collection = collectionDropdown?.Current.Value + Collection = collectionDropdown?.Current.Value.Collection }; if (!minimumStars.IsDefault) diff --git a/osu.Game/Screens/Select/FilterCriteria.cs b/osu.Game/Screens/Select/FilterCriteria.cs index acab982945..66f164bca8 100644 --- a/osu.Game/Screens/Select/FilterCriteria.cs +++ b/osu.Game/Screens/Select/FilterCriteria.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Linq; using JetBrains.Annotations; using osu.Game.Beatmaps; +using osu.Game.Collections; using osu.Game.Rulesets; using osu.Game.Screens.Select.Filter; @@ -56,7 +57,7 @@ namespace osu.Game.Screens.Select /// The collection to filter beatmaps from. /// [CanBeNull] - public CollectionFilter Collection; + public BeatmapCollection Collection; public struct OptionalRange : IEquatable> where T : struct From 6b56c6e83ff29b2c375af41c69ed89c344d4c72a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 9 Sep 2020 23:11:19 +0900 Subject: [PATCH 1018/1782] Rename to CollectionMenuItem --- .../SongSelect/TestSceneFilterControl.cs | 4 ++-- .../Select/CollectionFilterDropdown.cs | 24 +++++++++---------- ...lectionFilter.cs => CollectionMenuItem.cs} | 24 ++++++------------- 3 files changed, 21 insertions(+), 31 deletions(-) rename osu.Game/Screens/Select/{CollectionFilter.cs => CollectionMenuItem.cs} (60%) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs index f89f22bf23..23feb1466e 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs @@ -231,7 +231,7 @@ namespace osu.Game.Tests.Visual.SongSelect InputManager.Click(MouseButton.Left); }); - private IEnumerable.DropdownMenu.DrawableDropdownMenuItem> getCollectionDropdownItems() - => control.ChildrenOfType().Single().ChildrenOfType.DropdownMenu.DrawableDropdownMenuItem>(); + private IEnumerable.DropdownMenu.DrawableDropdownMenuItem> getCollectionDropdownItems() + => control.ChildrenOfType().Single().ChildrenOfType.DropdownMenu.DrawableDropdownMenuItem>(); } } diff --git a/osu.Game/Screens/Select/CollectionFilterDropdown.cs b/osu.Game/Screens/Select/CollectionFilterDropdown.cs index 1e2a3d0aa7..b08c3b167d 100644 --- a/osu.Game/Screens/Select/CollectionFilterDropdown.cs +++ b/osu.Game/Screens/Select/CollectionFilterDropdown.cs @@ -21,13 +21,13 @@ using osuTK; namespace osu.Game.Screens.Select { /// - /// A dropdown to select the to filter beatmaps using. + /// A dropdown to select the to filter beatmaps using. /// - public class CollectionFilterDropdown : OsuDropdown + public class CollectionFilterDropdown : OsuDropdown { private readonly IBindableList collections = new BindableList(); private readonly IBindableList beatmaps = new BindableList(); - private readonly BindableList filters = new BindableList(); + private readonly BindableList filters = new BindableList(); [Resolved(CanBeNull = true)] private ManageCollectionsDialog manageCollectionsDialog { get; set; } @@ -62,17 +62,17 @@ namespace osu.Game.Screens.Select var selectedItem = SelectedItem?.Value?.Collection; filters.Clear(); - filters.Add(new AllBeatmapCollectionFilter()); - filters.AddRange(collections.Select(c => new CollectionFilter(c))); - filters.Add(new ManageCollectionsFilter()); + filters.Add(new AllBeatmapsCollectionMenuItem()); + filters.AddRange(collections.Select(c => new CollectionMenuItem(c))); + filters.Add(new ManageCollectionsMenuItem()); Current.Value = filters.SingleOrDefault(f => f.Collection != null && f.Collection == selectedItem) ?? filters[0]; } /// - /// Occurs when the selection has changed. + /// Occurs when the selection has changed. /// - private void filterChanged(ValueChangedEvent filter) + private void filterChanged(ValueChangedEvent filter) { // Binding the beatmaps will trigger a collection change event, which results in an infinite-loop. This is rebound later, when it's safe to do so. beatmaps.CollectionChanged -= filterBeatmapsChanged; @@ -87,7 +87,7 @@ namespace osu.Game.Screens.Select // Never select the manage collection filter - rollback to the previous filter. // This is done after the above since it is important that bindable is unbound from OldValue, which is lost after forcing it back to the old value. - if (filter.NewValue is ManageCollectionsFilter) + if (filter.NewValue is ManageCollectionsMenuItem) { Current.Value = filter.OldValue; manageCollectionsDialog?.Show(); @@ -104,7 +104,7 @@ namespace osu.Game.Screens.Select Current.TriggerChange(); } - protected override string GenerateItemText(CollectionFilter item) => item.CollectionName.Value; + protected override string GenerateItemText(CollectionMenuItem item) => item.CollectionName.Value; protected override DropdownHeader CreateHeader() => new CollectionDropdownHeader { @@ -115,7 +115,7 @@ namespace osu.Game.Screens.Select public class CollectionDropdownHeader : OsuDropdownHeader { - public readonly Bindable SelectedItem = new Bindable(); + public readonly Bindable SelectedItem = new Bindable(); private readonly Bindable collectionName = new Bindable(); protected override string Label @@ -165,7 +165,7 @@ namespace osu.Game.Screens.Select private class CollectionDropdownMenuItem : OsuDropdownMenu.DrawableOsuDropdownMenuItem { [NotNull] - protected new CollectionFilter Item => ((DropdownMenuItem)base.Item).Value; + protected new CollectionMenuItem Item => ((DropdownMenuItem)base.Item).Value; [Resolved] private OsuColour colours { get; set; } diff --git a/osu.Game/Screens/Select/CollectionFilter.cs b/osu.Game/Screens/Select/CollectionMenuItem.cs similarity index 60% rename from osu.Game/Screens/Select/CollectionFilter.cs rename to osu.Game/Screens/Select/CollectionMenuItem.cs index 883019ab06..995651de19 100644 --- a/osu.Game/Screens/Select/CollectionFilter.cs +++ b/osu.Game/Screens/Select/CollectionMenuItem.cs @@ -1,10 +1,8 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System.Linq; using JetBrains.Annotations; using osu.Framework.Bindables; -using osu.Game.Beatmaps; using osu.Game.Collections; namespace osu.Game.Screens.Select @@ -12,7 +10,7 @@ namespace osu.Game.Screens.Select /// /// A filter. /// - public class CollectionFilter + public class CollectionMenuItem { /// /// The collection to filter beatmaps from. @@ -28,35 +26,27 @@ namespace osu.Game.Screens.Select public readonly Bindable CollectionName; /// - /// Creates a new . + /// Creates a new . /// /// The collection to filter beatmaps from. - public CollectionFilter([CanBeNull] BeatmapCollection collection) + public CollectionMenuItem([CanBeNull] BeatmapCollection collection) { Collection = collection; CollectionName = Collection?.Name.GetBoundCopy() ?? new Bindable("All beatmaps"); } - - /// - /// Whether the collection contains a given beatmap. - /// - /// The beatmap to check. - /// Whether contains . - public virtual bool ContainsBeatmap(BeatmapInfo beatmap) - => Collection?.Beatmaps.Any(b => b.Equals(beatmap)) ?? true; } - public class AllBeatmapCollectionFilter : CollectionFilter + public class AllBeatmapsCollectionMenuItem : CollectionMenuItem { - public AllBeatmapCollectionFilter() + public AllBeatmapsCollectionMenuItem() : base(null) { } } - public class ManageCollectionsFilter : CollectionFilter + public class ManageCollectionsMenuItem : CollectionMenuItem { - public ManageCollectionsFilter() + public ManageCollectionsMenuItem() : base(null) { CollectionName.Value = "Manage collections..."; From df1537f2a03e55aad03e83223ed4656b146cb126 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 10 Sep 2020 18:09:03 +0900 Subject: [PATCH 1019/1782] Update framework --- osu.Android.props | 2 +- osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.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 62397ca028..a2686c380e 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,6 +52,6 @@ - + diff --git a/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs b/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs index 20adbc1c02..88c855d768 100644 --- a/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs +++ b/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs @@ -192,7 +192,7 @@ namespace osu.Game.Rulesets.Osu.Statistics protected void AddPoint(Vector2 start, Vector2 end, Vector2 hitPoint, float radius) { - if (pointGrid.Content.Length == 0) + if (pointGrid.Content.Count == 0) return; double angle1 = Math.Atan2(end.Y - hitPoint.Y, hitPoint.X - end.X); // Angle between the end point and the hit point. diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 1de0633d1f..48582ae29d 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -24,7 +24,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 7187b48907..0eed2fa911 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -80,7 +80,7 @@ - + From 18d96738a11133e3b82f7d8f3049e8d7d27d1aa4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 10 Sep 2020 19:37:40 +0900 Subject: [PATCH 1020/1782] Fix hard crash on deleting a collection with no collection selected --- osu.Game/Screens/Select/FilterControl.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/FilterControl.cs b/osu.Game/Screens/Select/FilterControl.cs index 9128160608..3f1c88a1e3 100644 --- a/osu.Game/Screens/Select/FilterControl.cs +++ b/osu.Game/Screens/Select/FilterControl.cs @@ -189,7 +189,15 @@ namespace osu.Game.Screens.Select } }; - collectionDropdown.Current.ValueChanged += _ => updateCriteria(); + collectionDropdown.Current.ValueChanged += val => + { + if (val.NewValue == null) + // may be null briefly while menu is repopulated. + return; + + updateCriteria(); + }; + searchTextBox.Current.ValueChanged += _ => updateCriteria(); updateCriteria(); From 74eea8900bc2015bdb5f33a7e9f8e4f504bc8ce4 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 10 Sep 2020 20:00:57 +0900 Subject: [PATCH 1021/1782] Remove unnecessary check for negative durations --- .../Difficulty/TaikoDifficultyCalculator.cs | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs index ef43fc6d1e..e5485db4df 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs @@ -51,18 +51,11 @@ namespace osu.Game.Rulesets.Taiko.Difficulty for (int i = 2; i < beatmap.HitObjects.Count; i++) { - // Check for negative durations - var currentAfterLast = beatmap.HitObjects[i].StartTime > beatmap.HitObjects[i - 1].StartTime; - var lastAfterSecondLast = beatmap.HitObjects[i - 1].StartTime > beatmap.HitObjects[i - 2].StartTime; - - if (currentAfterLast && lastAfterSecondLast) - { - taikoDifficultyHitObjects.Add( - new TaikoDifficultyHitObject( - beatmap.HitObjects[i], beatmap.HitObjects[i - 1], beatmap.HitObjects[i - 2], clockRate, i - ) - ); - } + taikoDifficultyHitObjects.Add( + new TaikoDifficultyHitObject( + beatmap.HitObjects[i], beatmap.HitObjects[i - 1], beatmap.HitObjects[i - 2], clockRate, i + ) + ); } new StaminaCheeseDetector(taikoDifficultyHitObjects).FindCheese(); From 314cd13b7446c5497ff2e2b0a331a3d744ffbb52 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 10 Sep 2020 23:36:22 +0900 Subject: [PATCH 1022/1782] Fix song select filter ordering --- osu.Game/Screens/Select/FilterControl.cs | 132 +++++++++++------------ 1 file changed, 62 insertions(+), 70 deletions(-) diff --git a/osu.Game/Screens/Select/FilterControl.cs b/osu.Game/Screens/Select/FilterControl.cs index 3f1c88a1e3..079667a457 100644 --- a/osu.Game/Screens/Select/FilterControl.cs +++ b/osu.Game/Screens/Select/FilterControl.cs @@ -10,6 +10,7 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; using osu.Game.Configuration; using osu.Game.Graphics; +using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets; @@ -98,89 +99,80 @@ namespace osu.Game.Screens.Select Width = 0.5f, Anchor = Anchor.TopRight, Origin = Anchor.TopRight, - Child = new GridContainer + // Reverse ChildID so that dropdowns in the top section appear on top of the bottom section. + Child = new ReverseChildIDFillFlowContainer { RelativeSizeAxes = Axes.Both, - RowDimensions = new[] + Spacing = new Vector2(0, 5), + Children = new[] { - new Dimension(GridSizeMode.Absolute, 60), - new Dimension(GridSizeMode.Absolute, 5), - new Dimension(GridSizeMode.Absolute, 20), - }, - Content = new[] - { - new Drawable[] + new Container { - new Container + RelativeSizeAxes = Axes.X, + Height = 60, + Children = new Drawable[] { - RelativeSizeAxes = Axes.Both, - Children = new Drawable[] + searchTextBox = new SeekLimitedSearchTextBox { RelativeSizeAxes = Axes.X }, + new Box { - searchTextBox = new SeekLimitedSearchTextBox { RelativeSizeAxes = Axes.X }, - new Box + RelativeSizeAxes = Axes.X, + Height = 1, + Colour = OsuColour.Gray(80), + Origin = Anchor.BottomLeft, + Anchor = Anchor.BottomLeft, + }, + new FillFlowContainer + { + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomRight, + Direction = FillDirection.Horizontal, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Spacing = new Vector2(OsuTabControl.HORIZONTAL_SPACING, 0), + Children = new Drawable[] { - RelativeSizeAxes = Axes.X, - Height = 1, - Colour = OsuColour.Gray(80), - Origin = Anchor.BottomLeft, - Anchor = Anchor.BottomLeft, - }, - new FillFlowContainer - { - Anchor = Anchor.BottomRight, - Origin = Anchor.BottomRight, - Direction = FillDirection.Horizontal, - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Spacing = new Vector2(OsuTabControl.HORIZONTAL_SPACING, 0), - Children = new Drawable[] + new OsuTabControlCheckbox { - new OsuTabControlCheckbox - { - Text = "Show converted", - Current = config.GetBindable(OsuSetting.ShowConvertedBeatmaps), - Anchor = Anchor.BottomRight, - Origin = Anchor.BottomRight, - }, - sortTabs = new OsuTabControl - { - RelativeSizeAxes = Axes.X, - Width = 0.5f, - Height = 24, - AutoSort = true, - Anchor = Anchor.BottomRight, - Origin = Anchor.BottomRight, - AccentColour = colours.GreenLight, - Current = { BindTarget = sortMode } - }, - new OsuSpriteText - { - Text = "Sort by", - Font = OsuFont.GetFont(size: 14), - Margin = new MarginPadding(5), - Anchor = Anchor.BottomRight, - Origin = Anchor.BottomRight, - }, - } - }, - } + Text = "Show converted", + Current = config.GetBindable(OsuSetting.ShowConvertedBeatmaps), + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomRight, + }, + sortTabs = new OsuTabControl + { + RelativeSizeAxes = Axes.X, + Width = 0.5f, + Height = 24, + AutoSort = true, + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomRight, + AccentColour = colours.GreenLight, + Current = { BindTarget = sortMode } + }, + new OsuSpriteText + { + Text = "Sort by", + Font = OsuFont.GetFont(size: 14), + Margin = new MarginPadding(5), + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomRight, + }, + } + }, } }, - null, - new Drawable[] + new Container { - new Container + RelativeSizeAxes = Axes.X, + Height = 20, + Children = new Drawable[] { - RelativeSizeAxes = Axes.Both, - Children = new Drawable[] + collectionDropdown = new CollectionFilterDropdown { - collectionDropdown = new CollectionFilterDropdown - { - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - RelativeSizeAxes = Axes.X, - Width = 0.4f, - } + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + RelativeSizeAxes = Axes.X, + Width = 0.4f, } } }, From 447fd07b4ed4b7bf7f71862946ad975d8c07526f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 11 Sep 2020 01:13:55 +0900 Subject: [PATCH 1023/1782] Fix maps with only bonus score having NaN scores --- .../Gameplay/TestSceneScoreProcessor.cs | 26 +++++++++++++++++++ osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 8 +++++- 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs b/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs index b0baf0385e..c9ab4fa489 100644 --- a/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs +++ b/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs @@ -28,6 +28,20 @@ namespace osu.Game.Tests.Gameplay Assert.That(scoreProcessor.TotalScore.Value, Is.EqualTo(0.0)); } + [Test] + public void TestOnlyBonusScore() + { + var beatmap = new Beatmap { HitObjects = { new TestBonusHitObject() } }; + + var scoreProcessor = new ScoreProcessor(); + scoreProcessor.ApplyBeatmap(beatmap); + + // Apply a judgement + scoreProcessor.ApplyResult(new JudgementResult(new TestBonusHitObject(), new TestBonusJudgement()) { Type = HitResult.Perfect }); + + Assert.That(scoreProcessor.TotalScore.Value, Is.EqualTo(100)); + } + private class TestHitObject : HitObject { public override Judgement CreateJudgement() => new TestJudgement(); @@ -37,5 +51,17 @@ namespace osu.Game.Tests.Gameplay { protected override int NumericResultFor(HitResult result) => 100; } + + private class TestBonusHitObject : HitObject + { + public override Judgement CreateJudgement() => new TestBonusJudgement(); + } + + private class TestBonusJudgement : Judgement + { + public override bool AffectsCombo => false; + + protected override int NumericResultFor(HitResult result) => 100; + } } } diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 983f9a3abf..6fa5a87c8e 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -202,7 +202,13 @@ namespace osu.Game.Rulesets.Scoring TotalScore.Value = getScore(Mode.Value); } - private double getScore(ScoringMode mode) => GetScore(mode, maxHighestCombo, baseScore / maxBaseScore, (double)HighestCombo.Value / maxHighestCombo, bonusScore); + private double getScore(ScoringMode mode) + { + return GetScore(mode, maxHighestCombo, + maxBaseScore > 0 ? baseScore / maxBaseScore : 0, + maxHighestCombo > 0 ? (double)HighestCombo.Value / maxHighestCombo : 0, + bonusScore); + } /// /// Computes the total score. From 6e5c5ab9015e9b98ab52e344d38e5f97ffe57d5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 10 Sep 2020 18:22:49 +0200 Subject: [PATCH 1024/1782] Fix invalid initial value of currentMonoLength --- osu.Game.Rulesets.Taiko/Difficulty/Skills/Colour.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Colour.cs b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Colour.cs index 9fad83c6a1..ecd74f54ed 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Colour.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Colour.cs @@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills /// /// Length of the current mono pattern. /// - private int currentMonoLength = 1; + private int currentMonoLength; protected override double StrainValueOf(DifficultyHitObject current) { From 9b504272e41e9e6fadcb315eb732c05c9a458c0b Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Thu, 10 Sep 2020 20:24:43 +0300 Subject: [PATCH 1025/1782] Make Header a property --- .../Overlays/Profile/Sections/PaginatedContainerWithHeader.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Profile/Sections/PaginatedContainerWithHeader.cs b/osu.Game/Overlays/Profile/Sections/PaginatedContainerWithHeader.cs index 32c589e342..5e175a2203 100644 --- a/osu.Game/Overlays/Profile/Sections/PaginatedContainerWithHeader.cs +++ b/osu.Game/Overlays/Profile/Sections/PaginatedContainerWithHeader.cs @@ -12,7 +12,7 @@ namespace osu.Game.Overlays.Profile.Sections private readonly string headerText; private readonly CounterVisibilityState counterVisibilityState; - protected PaginatedContainerHeader Header; + protected PaginatedContainerHeader Header { get; private set; } protected PaginatedContainerWithHeader(Bindable user, string headerText, CounterVisibilityState counterVisibilityState, string missing = "") : base(user, missing) From 931e567c7e809401526f3dda607ace733c07212c Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Thu, 10 Sep 2020 20:25:35 +0300 Subject: [PATCH 1026/1782] Replace counter font size with an actual value --- osu.Game/Overlays/Profile/Sections/CounterPill.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Profile/Sections/CounterPill.cs b/osu.Game/Overlays/Profile/Sections/CounterPill.cs index 131df105ad..ca8abcfe5a 100644 --- a/osu.Game/Overlays/Profile/Sections/CounterPill.cs +++ b/osu.Game/Overlays/Profile/Sections/CounterPill.cs @@ -34,7 +34,7 @@ namespace osu.Game.Overlays.Profile.Sections Anchor = Anchor.Centre, Origin = Anchor.Centre, Margin = new MarginPadding { Horizontal = 10, Bottom = 1 }, - Font = OsuFont.GetFont(size: 14 * 0.8f, weight: FontWeight.Bold), + Font = OsuFont.GetFont(size: 11.2f, weight: FontWeight.Bold), Colour = colourProvider.Foreground1 } }; From e5f70d8eae57ae2892130730cb86ec2fdb990087 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Thu, 10 Sep 2020 20:31:00 +0300 Subject: [PATCH 1027/1782] Simplify counter visibility changes in PaginatedContainerHeader --- .../Sections/PaginatedContainerHeader.cs | 57 ++++++++----------- 1 file changed, 24 insertions(+), 33 deletions(-) diff --git a/osu.Game/Overlays/Profile/Sections/PaginatedContainerHeader.cs b/osu.Game/Overlays/Profile/Sections/PaginatedContainerHeader.cs index 4779b44eb0..8c617e5fbd 100644 --- a/osu.Game/Overlays/Profile/Sections/PaginatedContainerHeader.cs +++ b/osu.Game/Overlays/Profile/Sections/PaginatedContainerHeader.cs @@ -16,21 +16,14 @@ namespace osu.Game.Overlays.Profile.Sections { public class PaginatedContainerHeader : CompositeDrawable, IHasCurrentValue { + private readonly BindableWithCurrent current = new BindableWithCurrent(); + public Bindable Current { - get => current; - set - { - if (value == null) - throw new ArgumentNullException(nameof(value)); - - current.UnbindBindings(); - current.BindTo(value); - } + get => current.Current; + set => current.Current = value; } - private readonly Bindable current = new Bindable(); - private readonly string text; private readonly CounterVisibilityState counterState; @@ -82,7 +75,6 @@ namespace osu.Game.Overlays.Profile.Sections { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, - Alpha = getInitialCounterAlpha(), Current = { BindTarget = current } } } @@ -93,33 +85,32 @@ namespace osu.Game.Overlays.Profile.Sections protected override void LoadComplete() { base.LoadComplete(); - current.BindValueChanged(onCurrentChanged); - } - - private float getInitialCounterAlpha() - { - switch (counterState) - { - case CounterVisibilityState.AlwaysHidden: - return 0; - - case CounterVisibilityState.AlwaysVisible: - return 1; - - case CounterVisibilityState.VisibleWhenZero: - return current.Value == 0 ? 1 : 0; - - default: - throw new NotImplementedException($"{counterState} has an incorrect value."); - } + current.BindValueChanged(onCurrentChanged, true); } private void onCurrentChanged(ValueChangedEvent countValue) { - if (counterState == CounterVisibilityState.VisibleWhenZero) + float alpha; + + switch (counterState) { - counterPill.Alpha = countValue.NewValue == 0 ? 1 : 0; + case CounterVisibilityState.AlwaysHidden: + alpha = 0; + break; + + case CounterVisibilityState.AlwaysVisible: + alpha = 1; + break; + + case CounterVisibilityState.VisibleWhenZero: + alpha = current.Value == 0 ? 1 : 0; + break; + + default: + throw new NotImplementedException($"{counterState} has an incorrect value."); } + + counterPill.Alpha = alpha; } } From 6c9fcae69f3f55c6b23d8fbcc47c41df3aa5d88c Mon Sep 17 00:00:00 2001 From: Joehu Date: Thu, 10 Sep 2020 10:48:00 -0700 Subject: [PATCH 1028/1782] Fix drag handles not showing on now playing playlist items --- osu.Game/Overlays/Music/PlaylistItem.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Overlays/Music/PlaylistItem.cs b/osu.Game/Overlays/Music/PlaylistItem.cs index 840fa51b4f..12f7c7e09d 100644 --- a/osu.Game/Overlays/Music/PlaylistItem.cs +++ b/osu.Game/Overlays/Music/PlaylistItem.cs @@ -39,6 +39,8 @@ namespace osu.Game.Overlays.Music Padding = new MarginPadding { Left = 5 }; FilterTerms = item.Metadata.SearchableTerms; + + ShowDragHandle.Value = true; } [BackgroundDependencyLoader] From 913e3faf606130e9c1f4eb6308a403357e6b5ff1 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Thu, 10 Sep 2020 20:48:06 +0300 Subject: [PATCH 1029/1782] Move PaginatedContainerWithHeader logic to a base class --- .../Beatmaps/PaginatedBeatmapContainer.cs | 4 +-- .../PaginatedMostPlayedBeatmapContainer.cs | 4 +-- .../Profile/Sections/PaginatedContainer.cs | 27 +++++++++------ .../Sections/PaginatedContainerWithHeader.cs | 34 ------------------- .../Sections/Ranks/PaginatedScoreContainer.cs | 6 ++-- 5 files changed, 24 insertions(+), 51 deletions(-) delete mode 100644 osu.Game/Overlays/Profile/Sections/PaginatedContainerWithHeader.cs diff --git a/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs b/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs index d7c72131ea..265972bb86 100644 --- a/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs @@ -14,13 +14,13 @@ using osuTK; namespace osu.Game.Overlays.Profile.Sections.Beatmaps { - public class PaginatedBeatmapContainer : PaginatedContainerWithHeader + public class PaginatedBeatmapContainer : PaginatedContainer { private const float panel_padding = 10f; private readonly BeatmapSetType type; public PaginatedBeatmapContainer(BeatmapSetType type, Bindable user, string headerText) - : base(user, headerText, CounterVisibilityState.AlwaysVisible) + : base(user, "", headerText, CounterVisibilityState.AlwaysVisible) { this.type = type; ItemsPerPage = 6; diff --git a/osu.Game/Overlays/Profile/Sections/Historical/PaginatedMostPlayedBeatmapContainer.cs b/osu.Game/Overlays/Profile/Sections/Historical/PaginatedMostPlayedBeatmapContainer.cs index ad35ea1460..8f1d894379 100644 --- a/osu.Game/Overlays/Profile/Sections/Historical/PaginatedMostPlayedBeatmapContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Historical/PaginatedMostPlayedBeatmapContainer.cs @@ -13,10 +13,10 @@ using osu.Game.Users; namespace osu.Game.Overlays.Profile.Sections.Historical { - public class PaginatedMostPlayedBeatmapContainer : PaginatedContainerWithHeader + public class PaginatedMostPlayedBeatmapContainer : PaginatedContainer { public PaginatedMostPlayedBeatmapContainer(Bindable user) - : base(user, "Most Played Beatmaps", CounterVisibilityState.AlwaysHidden, "No records. :(") + : base(user, "No records. :(") { ItemsPerPage = 5; } diff --git a/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs b/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs index c22e5660e6..f0b11dc147 100644 --- a/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs @@ -36,10 +36,16 @@ namespace osu.Game.Overlays.Profile.Sections private readonly string missing; private ShowMoreButton moreButton; private OsuSpriteText missingText; + private PaginatedContainerHeader header; - protected PaginatedContainer(Bindable user, string missing = "") + private readonly string headerText; + private readonly CounterVisibilityState counterVisibilityState; + + protected PaginatedContainer(Bindable user, string missing = "", string headerText = "", CounterVisibilityState counterVisibilityState = CounterVisibilityState.AlwaysHidden) { + this.headerText = headerText; this.missing = missing; + this.counterVisibilityState = counterVisibilityState; User.BindTo(user); } @@ -50,9 +56,12 @@ namespace osu.Game.Overlays.Profile.Sections AutoSizeAxes = Axes.Y; Direction = FillDirection.Vertical; - Children = new[] + Children = new Drawable[] { - CreateHeaderContent, + header = new PaginatedContainerHeader(headerText, counterVisibilityState) + { + Alpha = string.IsNullOrEmpty(headerText) ? 0 : 1 + }, ItemsContainer = new FillFlowContainer { AutoSizeAxes = Axes.Y, @@ -91,7 +100,8 @@ namespace osu.Game.Overlays.Profile.Sections if (e.NewValue != null) { - OnUserChanged(e.NewValue); + showMore(); + SetCount(GetCount(e.NewValue)); } } @@ -130,17 +140,14 @@ namespace osu.Game.Overlays.Profile.Sections }, loadCancellation.Token); }); - protected virtual void OnUserChanged(User user) - { - showMore(); - } + protected virtual int GetCount(User user) => 0; + + protected void SetCount(int value) => header.Current.Value = value; protected virtual void OnItemsReceived(List items) { } - protected virtual Drawable CreateHeaderContent => Empty(); - protected abstract APIRequest> CreateRequest(); protected abstract Drawable CreateDrawableItem(TModel model); diff --git a/osu.Game/Overlays/Profile/Sections/PaginatedContainerWithHeader.cs b/osu.Game/Overlays/Profile/Sections/PaginatedContainerWithHeader.cs deleted file mode 100644 index 5e175a2203..0000000000 --- a/osu.Game/Overlays/Profile/Sections/PaginatedContainerWithHeader.cs +++ /dev/null @@ -1,34 +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; -using osu.Framework.Graphics; -using osu.Game.Users; - -namespace osu.Game.Overlays.Profile.Sections -{ - public abstract class PaginatedContainerWithHeader : PaginatedContainer - { - private readonly string headerText; - private readonly CounterVisibilityState counterVisibilityState; - - protected PaginatedContainerHeader Header { get; private set; } - - protected PaginatedContainerWithHeader(Bindable user, string headerText, CounterVisibilityState counterVisibilityState, string missing = "") - : base(user, missing) - { - this.headerText = headerText; - this.counterVisibilityState = counterVisibilityState; - } - - protected override Drawable CreateHeaderContent => Header = new PaginatedContainerHeader(headerText, counterVisibilityState); - - protected override void OnUserChanged(User user) - { - base.OnUserChanged(user); - Header.Current.Value = GetCount(user); - } - - protected virtual int GetCount(User user) => 0; - } -} diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs b/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs index 7dbdf47cad..71ee89d526 100644 --- a/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs @@ -14,12 +14,12 @@ using osu.Framework.Allocation; namespace osu.Game.Overlays.Profile.Sections.Ranks { - public class PaginatedScoreContainer : PaginatedContainerWithHeader + public class PaginatedScoreContainer : PaginatedContainer { private readonly ScoreType type; public PaginatedScoreContainer(ScoreType type, Bindable user, string headerText, CounterVisibilityState counterVisibilityState, string missingText = "") - : base(user, headerText, counterVisibilityState, missingText) + : base(user, missingText, headerText, counterVisibilityState) { this.type = type; @@ -49,7 +49,7 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks base.OnItemsReceived(items); if (type == ScoreType.Recent) - Header.Current.Value = items.Count; + SetCount(items.Count); } protected override APIRequest> CreateRequest() => From cfc6e2175d2fc9ae36b60402ed4a210d55ff33df Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Thu, 10 Sep 2020 20:58:37 +0300 Subject: [PATCH 1030/1782] Add missing header to MostPlayedBeatmapsContainer --- .../Sections/Historical/PaginatedMostPlayedBeatmapContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Profile/Sections/Historical/PaginatedMostPlayedBeatmapContainer.cs b/osu.Game/Overlays/Profile/Sections/Historical/PaginatedMostPlayedBeatmapContainer.cs index 8f1d894379..30284818a6 100644 --- a/osu.Game/Overlays/Profile/Sections/Historical/PaginatedMostPlayedBeatmapContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Historical/PaginatedMostPlayedBeatmapContainer.cs @@ -16,7 +16,7 @@ namespace osu.Game.Overlays.Profile.Sections.Historical public class PaginatedMostPlayedBeatmapContainer : PaginatedContainer { public PaginatedMostPlayedBeatmapContainer(Bindable user) - : base(user, "No records. :(") + : base(user, "No records. :(", "Most Played Beatmaps") { ItemsPerPage = 5; } From 370f22f975268dcea64886e36f6fe0d3079f4502 Mon Sep 17 00:00:00 2001 From: Joehu Date: Thu, 10 Sep 2020 11:11:45 -0700 Subject: [PATCH 1031/1782] Show drag handle by default on main class --- osu.Game/Graphics/Containers/OsuRearrangeableListItem.cs | 2 +- osu.Game/Overlays/Music/PlaylistItem.cs | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game/Graphics/Containers/OsuRearrangeableListItem.cs b/osu.Game/Graphics/Containers/OsuRearrangeableListItem.cs index 9cdcb19a81..911d47704a 100644 --- a/osu.Game/Graphics/Containers/OsuRearrangeableListItem.cs +++ b/osu.Game/Graphics/Containers/OsuRearrangeableListItem.cs @@ -44,7 +44,7 @@ namespace osu.Game.Graphics.Containers /// /// Whether the drag handle should be shown. /// - protected readonly Bindable ShowDragHandle = new Bindable(); + protected readonly Bindable ShowDragHandle = new Bindable(true); private Container handleContainer; private PlaylistItemHandle handle; diff --git a/osu.Game/Overlays/Music/PlaylistItem.cs b/osu.Game/Overlays/Music/PlaylistItem.cs index 12f7c7e09d..840fa51b4f 100644 --- a/osu.Game/Overlays/Music/PlaylistItem.cs +++ b/osu.Game/Overlays/Music/PlaylistItem.cs @@ -39,8 +39,6 @@ namespace osu.Game.Overlays.Music Padding = new MarginPadding { Left = 5 }; FilterTerms = item.Metadata.SearchableTerms; - - ShowDragHandle.Value = true; } [BackgroundDependencyLoader] From a350802158d7413c9bdb37319b47d92efb17a1be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 10 Sep 2020 19:21:16 +0200 Subject: [PATCH 1032/1782] Fix wrong mono streak length handling in corner case --- .../Difficulty/Skills/Colour.cs | 7 ++++++- .../NonVisual/LimitedCapacityQueueTest.cs | 21 +++++++++++++++++++ .../Difficulty/Utils/LimitedCapacityQueue.cs | 9 ++++++++ 3 files changed, 36 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Colour.cs b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Colour.cs index ecd74f54ed..32421ee00a 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Colour.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Colour.cs @@ -45,7 +45,12 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills // hits spaced more than a second apart are also exempt from colour strain. if (!(current.LastObject is Hit && current.BaseObject is Hit && current.DeltaTime < 1000)) { - previousHitType = null; + monoHistory.Clear(); + + var currentHit = current.BaseObject as Hit; + currentMonoLength = currentHit != null ? 1 : 0; + previousHitType = currentHit?.Type; + return 0.0; } diff --git a/osu.Game.Tests/NonVisual/LimitedCapacityQueueTest.cs b/osu.Game.Tests/NonVisual/LimitedCapacityQueueTest.cs index 52463dd7eb..a04415bc7f 100644 --- a/osu.Game.Tests/NonVisual/LimitedCapacityQueueTest.cs +++ b/osu.Game.Tests/NonVisual/LimitedCapacityQueueTest.cs @@ -94,5 +94,26 @@ namespace osu.Game.Tests.NonVisual Assert.Throws(() => queue.Dequeue()); } + + [Test] + public void TestClearQueue() + { + queue.Enqueue(3); + queue.Enqueue(5); + Assert.AreEqual(2, queue.Count); + + queue.Clear(); + Assert.AreEqual(0, queue.Count); + Assert.Throws(() => _ = queue[0]); + + queue.Enqueue(7); + Assert.AreEqual(1, queue.Count); + Assert.AreEqual(7, queue[0]); + Assert.Throws(() => _ = queue[1]); + + queue.Enqueue(9); + Assert.AreEqual(2, queue.Count); + Assert.AreEqual(9, queue[1]); + } } } diff --git a/osu.Game/Rulesets/Difficulty/Utils/LimitedCapacityQueue.cs b/osu.Game/Rulesets/Difficulty/Utils/LimitedCapacityQueue.cs index 0f014e8a8c..bc0eb8af88 100644 --- a/osu.Game/Rulesets/Difficulty/Utils/LimitedCapacityQueue.cs +++ b/osu.Game/Rulesets/Difficulty/Utils/LimitedCapacityQueue.cs @@ -40,8 +40,17 @@ namespace osu.Game.Rulesets.Difficulty.Utils this.capacity = capacity; array = new T[capacity]; + Clear(); + } + + /// + /// Removes all elements from the . + /// + public void Clear() + { start = 0; end = -1; + Count = 0; } /// From 64b1a009efb5f80f5c78faae44682b5895c2fd2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 10 Sep 2020 20:56:55 +0200 Subject: [PATCH 1033/1782] Adjust diffcalc test case to pass --- .../TaikoDifficultyCalculatorTest.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Taiko.Tests/TaikoDifficultyCalculatorTest.cs b/osu.Game.Rulesets.Taiko.Tests/TaikoDifficultyCalculatorTest.cs index 2d51e82bc4..71b3c23b50 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TaikoDifficultyCalculatorTest.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TaikoDifficultyCalculatorTest.cs @@ -13,8 +13,8 @@ namespace osu.Game.Rulesets.Taiko.Tests { protected override string ResourceAssembly => "osu.Game.Rulesets.Taiko"; - [TestCase(2.2905937546434592d, "diffcalc-test")] - [TestCase(2.2905937546434592d, "diffcalc-test-strong")] + [TestCase(2.2867022617692685d, "diffcalc-test")] + [TestCase(2.2867022617692685d, "diffcalc-test-strong")] public void Test(double expected, string name) => base.Test(expected, name); From 97690c818c444190dab9f3f589ee4aeefd3d41f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 11 Sep 2020 00:12:05 +0200 Subject: [PATCH 1034/1782] Add regression test coverage --- .../UserInterface/TestScenePlaylistOverlay.cs | 51 +++++++++++++++++-- 1 file changed, 46 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestScenePlaylistOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestScenePlaylistOverlay.cs index a470244f53..52141dea1a 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestScenePlaylistOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestScenePlaylistOverlay.cs @@ -2,32 +2,35 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Linq; using NUnit.Framework; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Testing; using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Overlays.Music; using osuTK; +using osuTK.Input; namespace osu.Game.Tests.Visual.UserInterface { - public class TestScenePlaylistOverlay : OsuTestScene + public class TestScenePlaylistOverlay : OsuManualInputManagerTestScene { private readonly BindableList beatmapSets = new BindableList(); + private PlaylistOverlay playlistOverlay; + [SetUp] public void Setup() => Schedule(() => { - PlaylistOverlay overlay; - Child = new Container { Anchor = Anchor.Centre, Origin = Anchor.Centre, Size = new Vector2(300, 500), - Child = overlay = new PlaylistOverlay + Child = playlistOverlay = new PlaylistOverlay { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -53,7 +56,45 @@ namespace osu.Game.Tests.Visual.UserInterface }); } - overlay.BeatmapSets.BindTo(beatmapSets); + playlistOverlay.BeatmapSets.BindTo(beatmapSets); }); + + [Test] + public void TestRearrangeItems() + { + AddUntilStep("wait for animations to complete", () => !playlistOverlay.Transforms.Any()); + + AddStep("hold 1st item handle", () => + { + var handle = this.ChildrenOfType().First(); + InputManager.MoveMouseTo(handle.ScreenSpaceDrawQuad.Centre); + InputManager.PressButton(MouseButton.Left); + }); + + AddStep("drag to 5th", () => + { + var item = this.ChildrenOfType().ElementAt(4); + InputManager.MoveMouseTo(item.ScreenSpaceDrawQuad.Centre); + }); + + AddAssert("song 1 is 5th", () => beatmapSets[4].Metadata.Title == "Some Song 1"); + + AddStep("release handle", () => InputManager.ReleaseButton(MouseButton.Left)); + } + + [Test] + public void TestFiltering() + { + AddStep("set filter to \"10\"", () => + { + var filterControl = playlistOverlay.ChildrenOfType().Single(); + filterControl.Search.Current.Value = "10"; + }); + + AddAssert("results filtered correctly", + () => playlistOverlay.ChildrenOfType() + .Where(item => item.MatchingFilter) + .All(item => item.FilterTerms.Any(term => term.Contains("10")))); + } } } From b594a2a507d82ab1cc45b8161e1874383cb10608 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 11 Sep 2020 11:15:50 +0900 Subject: [PATCH 1035/1782] Import collections on initial import-from-stable step --- osu.Game/Screens/Select/ImportFromStablePopup.cs | 2 +- osu.Game/Screens/Select/SongSelect.cs | 10 ++++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Select/ImportFromStablePopup.cs b/osu.Game/Screens/Select/ImportFromStablePopup.cs index 272f9566d5..8dab83b24c 100644 --- a/osu.Game/Screens/Select/ImportFromStablePopup.cs +++ b/osu.Game/Screens/Select/ImportFromStablePopup.cs @@ -12,7 +12,7 @@ namespace osu.Game.Screens.Select public ImportFromStablePopup(Action importFromStable) { HeaderText = @"You have no beatmaps!"; - BodyText = "An existing copy of osu! was found, though.\nWould you like to import your beatmaps, skins and scores?\nThis will create a second copy of all files on disk."; + BodyText = "An existing copy of osu! was found, though.\nWould you like to import your beatmaps, skins, collections and scores?\nThis will create a second copy of all files on disk."; Icon = FontAwesome.Solid.Plane; diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index ddbb021054..d313f67446 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -34,6 +34,7 @@ using System.Threading.Tasks; using osu.Framework.Audio.Track; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Bindings; +using osu.Game.Collections; using osu.Game.Graphics.UserInterface; using osu.Game.Scoring; @@ -103,7 +104,7 @@ namespace osu.Game.Screens.Select private MusicController music { get; set; } [BackgroundDependencyLoader(true)] - private void load(AudioManager audio, DialogOverlay dialog, OsuColour colours, SkinManager skins, ScoreManager scores) + private void load(AudioManager audio, DialogOverlay dialog, OsuColour colours, SkinManager skins, ScoreManager scores, CollectionManager collections) { // initial value transfer is required for FilterControl (it uses our re-cached bindables in its async load for the initial filter). transferRulesetValue(); @@ -294,7 +295,12 @@ namespace osu.Game.Screens.Select { dialogOverlay.Push(new ImportFromStablePopup(() => { - Task.Run(beatmaps.ImportFromStableAsync).ContinueWith(_ => scores.ImportFromStableAsync(), TaskContinuationOptions.OnlyOnRanToCompletion); + Task.Run(beatmaps.ImportFromStableAsync) + .ContinueWith(_ => + { + Task.Run(scores.ImportFromStableAsync); + Task.Run(collections.ImportFromStableAsync); + }, TaskContinuationOptions.OnlyOnRanToCompletion); Task.Run(skins.ImportFromStableAsync); })); } From be5d143b5a6b28030a2f39c7e5c03c3ec803318e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 11 Sep 2020 12:17:12 +0900 Subject: [PATCH 1036/1782] Reorder params --- .../Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs | 2 +- .../Sections/Historical/PaginatedMostPlayedBeatmapContainer.cs | 2 +- .../Profile/Sections/Kudosu/PaginatedKudosuHistoryContainer.cs | 2 +- osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs | 2 +- .../Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs | 2 +- .../Profile/Sections/Recent/PaginatedRecentActivityContainer.cs | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs b/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs index 265972bb86..4b7de8de90 100644 --- a/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs @@ -20,7 +20,7 @@ namespace osu.Game.Overlays.Profile.Sections.Beatmaps private readonly BeatmapSetType type; public PaginatedBeatmapContainer(BeatmapSetType type, Bindable user, string headerText) - : base(user, "", headerText, CounterVisibilityState.AlwaysVisible) + : base(user, headerText, "", CounterVisibilityState.AlwaysVisible) { this.type = type; ItemsPerPage = 6; diff --git a/osu.Game/Overlays/Profile/Sections/Historical/PaginatedMostPlayedBeatmapContainer.cs b/osu.Game/Overlays/Profile/Sections/Historical/PaginatedMostPlayedBeatmapContainer.cs index 30284818a6..8f19cd900c 100644 --- a/osu.Game/Overlays/Profile/Sections/Historical/PaginatedMostPlayedBeatmapContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Historical/PaginatedMostPlayedBeatmapContainer.cs @@ -16,7 +16,7 @@ namespace osu.Game.Overlays.Profile.Sections.Historical public class PaginatedMostPlayedBeatmapContainer : PaginatedContainer { public PaginatedMostPlayedBeatmapContainer(Bindable user) - : base(user, "No records. :(", "Most Played Beatmaps") + : base(user, "Most Played Beatmaps", "No records. :(") { ItemsPerPage = 5; } diff --git a/osu.Game/Overlays/Profile/Sections/Kudosu/PaginatedKudosuHistoryContainer.cs b/osu.Game/Overlays/Profile/Sections/Kudosu/PaginatedKudosuHistoryContainer.cs index c823053c4b..b968edcb5a 100644 --- a/osu.Game/Overlays/Profile/Sections/Kudosu/PaginatedKudosuHistoryContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Kudosu/PaginatedKudosuHistoryContainer.cs @@ -14,7 +14,7 @@ namespace osu.Game.Overlays.Profile.Sections.Kudosu public class PaginatedKudosuHistoryContainer : PaginatedContainer { public PaginatedKudosuHistoryContainer(Bindable user) - : base(user, "This user hasn't received any kudosu!") + : base(user, missing: "This user hasn't received any kudosu!") { ItemsPerPage = 5; } diff --git a/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs b/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs index f0b11dc147..6e681a779f 100644 --- a/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs @@ -41,7 +41,7 @@ namespace osu.Game.Overlays.Profile.Sections private readonly string headerText; private readonly CounterVisibilityState counterVisibilityState; - protected PaginatedContainer(Bindable user, string missing = "", string headerText = "", CounterVisibilityState counterVisibilityState = CounterVisibilityState.AlwaysHidden) + protected PaginatedContainer(Bindable user, string headerText = "", string missing = "", CounterVisibilityState counterVisibilityState = CounterVisibilityState.AlwaysHidden) { this.headerText = headerText; this.missing = missing; diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs b/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs index 71ee89d526..3c540d6fbb 100644 --- a/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs @@ -19,7 +19,7 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks private readonly ScoreType type; public PaginatedScoreContainer(ScoreType type, Bindable user, string headerText, CounterVisibilityState counterVisibilityState, string missingText = "") - : base(user, missingText, headerText, counterVisibilityState) + : base(user, headerText, missingText, counterVisibilityState) { this.type = type; diff --git a/osu.Game/Overlays/Profile/Sections/Recent/PaginatedRecentActivityContainer.cs b/osu.Game/Overlays/Profile/Sections/Recent/PaginatedRecentActivityContainer.cs index adfe31109b..4901789963 100644 --- a/osu.Game/Overlays/Profile/Sections/Recent/PaginatedRecentActivityContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Recent/PaginatedRecentActivityContainer.cs @@ -16,7 +16,7 @@ namespace osu.Game.Overlays.Profile.Sections.Recent public class PaginatedRecentActivityContainer : PaginatedContainer { public PaginatedRecentActivityContainer(Bindable user) - : base(user, "This user hasn't done anything notable recently!") + : base(user, missing: "This user hasn't done anything notable recently!") { ItemsPerPage = 10; } From 22c5e9f64f03e5cdca648028442e46e3c33439a8 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 11 Sep 2020 12:19:26 +0900 Subject: [PATCH 1037/1782] Rename missing parameter --- .../Kudosu/PaginatedKudosuHistoryContainer.cs | 2 +- .../Profile/Sections/PaginatedContainer.cs | 18 +++++++++--------- .../Recent/PaginatedRecentActivityContainer.cs | 2 +- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/osu.Game/Overlays/Profile/Sections/Kudosu/PaginatedKudosuHistoryContainer.cs b/osu.Game/Overlays/Profile/Sections/Kudosu/PaginatedKudosuHistoryContainer.cs index b968edcb5a..1b8bd23eb4 100644 --- a/osu.Game/Overlays/Profile/Sections/Kudosu/PaginatedKudosuHistoryContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Kudosu/PaginatedKudosuHistoryContainer.cs @@ -14,7 +14,7 @@ namespace osu.Game.Overlays.Profile.Sections.Kudosu public class PaginatedKudosuHistoryContainer : PaginatedContainer { public PaginatedKudosuHistoryContainer(Bindable user) - : base(user, missing: "This user hasn't received any kudosu!") + : base(user, missingText: "This user hasn't received any kudosu!") { ItemsPerPage = 5; } diff --git a/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs b/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs index 6e681a779f..c1107ce907 100644 --- a/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs @@ -33,18 +33,18 @@ namespace osu.Game.Overlays.Profile.Sections private APIRequest> retrievalRequest; private CancellationTokenSource loadCancellation; - private readonly string missing; + private readonly string missingText; private ShowMoreButton moreButton; - private OsuSpriteText missingText; + private OsuSpriteText missing; private PaginatedContainerHeader header; private readonly string headerText; private readonly CounterVisibilityState counterVisibilityState; - protected PaginatedContainer(Bindable user, string headerText = "", string missing = "", CounterVisibilityState counterVisibilityState = CounterVisibilityState.AlwaysHidden) + protected PaginatedContainer(Bindable user, string headerText = "", string missingText = "", CounterVisibilityState counterVisibilityState = CounterVisibilityState.AlwaysHidden) { this.headerText = headerText; - this.missing = missing; + this.missingText = missingText; this.counterVisibilityState = counterVisibilityState; User.BindTo(user); } @@ -76,10 +76,10 @@ namespace osu.Game.Overlays.Profile.Sections Margin = new MarginPadding { Top = 10 }, Action = showMore, }, - missingText = new OsuSpriteText + missing = new OsuSpriteText { Font = OsuFont.GetFont(size: 15), - Text = missing, + Text = missingText, Alpha = 0, }, }; @@ -124,15 +124,15 @@ namespace osu.Game.Overlays.Profile.Sections moreButton.Hide(); moreButton.IsLoading = false; - if (!string.IsNullOrEmpty(missingText.Text)) - missingText.Show(); + if (!string.IsNullOrEmpty(missing.Text)) + missing.Show(); return; } LoadComponentsAsync(items.Select(CreateDrawableItem).Where(d => d != null), drawables => { - missingText.Hide(); + missing.Hide(); moreButton.FadeTo(items.Count == ItemsPerPage ? 1 : 0); moreButton.IsLoading = false; diff --git a/osu.Game/Overlays/Profile/Sections/Recent/PaginatedRecentActivityContainer.cs b/osu.Game/Overlays/Profile/Sections/Recent/PaginatedRecentActivityContainer.cs index 4901789963..08f39c6272 100644 --- a/osu.Game/Overlays/Profile/Sections/Recent/PaginatedRecentActivityContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Recent/PaginatedRecentActivityContainer.cs @@ -16,7 +16,7 @@ namespace osu.Game.Overlays.Profile.Sections.Recent public class PaginatedRecentActivityContainer : PaginatedContainer { public PaginatedRecentActivityContainer(Bindable user) - : base(user, missing: "This user hasn't done anything notable recently!") + : base(user, missingText: "This user hasn't done anything notable recently!") { ItemsPerPage = 10; } From 5b80a7db5fcd6d2dd09079a286b8f838bc531aa3 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 11 Sep 2020 16:01:01 +0900 Subject: [PATCH 1038/1782] Re-namespace collections dropdown --- .../Select => Collections}/CollectionFilterDropdown.cs | 7 +++---- .../{Screens/Select => Collections}/CollectionMenuItem.cs | 3 +-- osu.Game/Screens/Select/FilterControl.cs | 1 + 3 files changed, 5 insertions(+), 6 deletions(-) rename osu.Game/{Screens/Select => Collections}/CollectionFilterDropdown.cs (97%) rename osu.Game/{Screens/Select => Collections}/CollectionMenuItem.cs (96%) diff --git a/osu.Game/Screens/Select/CollectionFilterDropdown.cs b/osu.Game/Collections/CollectionFilterDropdown.cs similarity index 97% rename from osu.Game/Screens/Select/CollectionFilterDropdown.cs rename to osu.Game/Collections/CollectionFilterDropdown.cs index b08c3b167d..c85efb73f5 100644 --- a/osu.Game/Screens/Select/CollectionFilterDropdown.cs +++ b/osu.Game/Collections/CollectionFilterDropdown.cs @@ -12,13 +12,12 @@ using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; using osu.Game.Beatmaps; -using osu.Game.Collections; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osuTK; -namespace osu.Game.Screens.Select +namespace osu.Game.Collections { /// /// A dropdown to select the to filter beatmaps using. @@ -152,7 +151,7 @@ namespace osu.Game.Screens.Select private void updateText() => base.Label = collectionName.Value; } - private class CollectionDropdownMenu : OsuDropdownMenu + protected class CollectionDropdownMenu : OsuDropdownMenu { public CollectionDropdownMenu() { @@ -162,7 +161,7 @@ namespace osu.Game.Screens.Select protected override DrawableDropdownMenuItem CreateDrawableDropdownMenuItem(MenuItem item) => new CollectionDropdownMenuItem(item); } - private class CollectionDropdownMenuItem : OsuDropdownMenu.DrawableOsuDropdownMenuItem + protected class CollectionDropdownMenuItem : OsuDropdownMenu.DrawableOsuDropdownMenuItem { [NotNull] protected new CollectionMenuItem Item => ((DropdownMenuItem)base.Item).Value; diff --git a/osu.Game/Screens/Select/CollectionMenuItem.cs b/osu.Game/Collections/CollectionMenuItem.cs similarity index 96% rename from osu.Game/Screens/Select/CollectionMenuItem.cs rename to osu.Game/Collections/CollectionMenuItem.cs index 995651de19..0560e03956 100644 --- a/osu.Game/Screens/Select/CollectionMenuItem.cs +++ b/osu.Game/Collections/CollectionMenuItem.cs @@ -3,9 +3,8 @@ using JetBrains.Annotations; using osu.Framework.Bindables; -using osu.Game.Collections; -namespace osu.Game.Screens.Select +namespace osu.Game.Collections { /// /// A filter. diff --git a/osu.Game/Screens/Select/FilterControl.cs b/osu.Game/Screens/Select/FilterControl.cs index 079667a457..c82a3742cc 100644 --- a/osu.Game/Screens/Select/FilterControl.cs +++ b/osu.Game/Screens/Select/FilterControl.cs @@ -8,6 +8,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; +using osu.Game.Collections; using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.Containers; From 4061480419f3fcc3c2d4758c2da380e7ac33d070 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 11 Sep 2020 16:02:46 +0900 Subject: [PATCH 1039/1782] Rename menu item --- .../SongSelect/TestSceneFilterControl.cs | 4 ++-- .../Collections/CollectionFilterDropdown.cs | 24 +++++++++---------- ...enuItem.cs => CollectionFilterMenuItem.cs} | 14 +++++------ 3 files changed, 21 insertions(+), 21 deletions(-) rename osu.Game/Collections/{CollectionMenuItem.cs => CollectionFilterMenuItem.cs} (73%) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs index 23feb1466e..7cd4791acb 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs @@ -231,7 +231,7 @@ namespace osu.Game.Tests.Visual.SongSelect InputManager.Click(MouseButton.Left); }); - private IEnumerable.DropdownMenu.DrawableDropdownMenuItem> getCollectionDropdownItems() - => control.ChildrenOfType().Single().ChildrenOfType.DropdownMenu.DrawableDropdownMenuItem>(); + private IEnumerable.DropdownMenu.DrawableDropdownMenuItem> getCollectionDropdownItems() + => control.ChildrenOfType().Single().ChildrenOfType.DropdownMenu.DrawableDropdownMenuItem>(); } } diff --git a/osu.Game/Collections/CollectionFilterDropdown.cs b/osu.Game/Collections/CollectionFilterDropdown.cs index c85efb73f5..bdd6e73f3f 100644 --- a/osu.Game/Collections/CollectionFilterDropdown.cs +++ b/osu.Game/Collections/CollectionFilterDropdown.cs @@ -20,13 +20,13 @@ using osuTK; namespace osu.Game.Collections { /// - /// A dropdown to select the to filter beatmaps using. + /// A dropdown to select the to filter beatmaps using. /// - public class CollectionFilterDropdown : OsuDropdown + public class CollectionFilterDropdown : OsuDropdown { private readonly IBindableList collections = new BindableList(); private readonly IBindableList beatmaps = new BindableList(); - private readonly BindableList filters = new BindableList(); + private readonly BindableList filters = new BindableList(); [Resolved(CanBeNull = true)] private ManageCollectionsDialog manageCollectionsDialog { get; set; } @@ -61,17 +61,17 @@ namespace osu.Game.Collections var selectedItem = SelectedItem?.Value?.Collection; filters.Clear(); - filters.Add(new AllBeatmapsCollectionMenuItem()); - filters.AddRange(collections.Select(c => new CollectionMenuItem(c))); - filters.Add(new ManageCollectionsMenuItem()); + filters.Add(new AllBeatmapsCollectionFilterMenuItem()); + filters.AddRange(collections.Select(c => new CollectionFilterMenuItem(c))); + filters.Add(new ManageCollectionsFilterMenuItem()); Current.Value = filters.SingleOrDefault(f => f.Collection != null && f.Collection == selectedItem) ?? filters[0]; } /// - /// Occurs when the selection has changed. + /// Occurs when the selection has changed. /// - private void filterChanged(ValueChangedEvent filter) + private void filterChanged(ValueChangedEvent filter) { // Binding the beatmaps will trigger a collection change event, which results in an infinite-loop. This is rebound later, when it's safe to do so. beatmaps.CollectionChanged -= filterBeatmapsChanged; @@ -86,7 +86,7 @@ namespace osu.Game.Collections // Never select the manage collection filter - rollback to the previous filter. // This is done after the above since it is important that bindable is unbound from OldValue, which is lost after forcing it back to the old value. - if (filter.NewValue is ManageCollectionsMenuItem) + if (filter.NewValue is ManageCollectionsFilterMenuItem) { Current.Value = filter.OldValue; manageCollectionsDialog?.Show(); @@ -103,7 +103,7 @@ namespace osu.Game.Collections Current.TriggerChange(); } - protected override string GenerateItemText(CollectionMenuItem item) => item.CollectionName.Value; + protected override string GenerateItemText(CollectionFilterMenuItem item) => item.CollectionName.Value; protected override DropdownHeader CreateHeader() => new CollectionDropdownHeader { @@ -114,7 +114,7 @@ namespace osu.Game.Collections public class CollectionDropdownHeader : OsuDropdownHeader { - public readonly Bindable SelectedItem = new Bindable(); + public readonly Bindable SelectedItem = new Bindable(); private readonly Bindable collectionName = new Bindable(); protected override string Label @@ -164,7 +164,7 @@ namespace osu.Game.Collections protected class CollectionDropdownMenuItem : OsuDropdownMenu.DrawableOsuDropdownMenuItem { [NotNull] - protected new CollectionMenuItem Item => ((DropdownMenuItem)base.Item).Value; + protected new CollectionFilterMenuItem Item => ((DropdownMenuItem)base.Item).Value; [Resolved] private OsuColour colours { get; set; } diff --git a/osu.Game/Collections/CollectionMenuItem.cs b/osu.Game/Collections/CollectionFilterMenuItem.cs similarity index 73% rename from osu.Game/Collections/CollectionMenuItem.cs rename to osu.Game/Collections/CollectionFilterMenuItem.cs index 0560e03956..4a489d2945 100644 --- a/osu.Game/Collections/CollectionMenuItem.cs +++ b/osu.Game/Collections/CollectionFilterMenuItem.cs @@ -9,7 +9,7 @@ namespace osu.Game.Collections /// /// A filter. /// - public class CollectionMenuItem + public class CollectionFilterMenuItem { /// /// The collection to filter beatmaps from. @@ -25,27 +25,27 @@ namespace osu.Game.Collections public readonly Bindable CollectionName; /// - /// Creates a new . + /// Creates a new . /// /// The collection to filter beatmaps from. - public CollectionMenuItem([CanBeNull] BeatmapCollection collection) + public CollectionFilterMenuItem([CanBeNull] BeatmapCollection collection) { Collection = collection; CollectionName = Collection?.Name.GetBoundCopy() ?? new Bindable("All beatmaps"); } } - public class AllBeatmapsCollectionMenuItem : CollectionMenuItem + public class AllBeatmapsCollectionFilterMenuItem : CollectionFilterMenuItem { - public AllBeatmapsCollectionMenuItem() + public AllBeatmapsCollectionFilterMenuItem() : base(null) { } } - public class ManageCollectionsMenuItem : CollectionMenuItem + public class ManageCollectionsFilterMenuItem : CollectionFilterMenuItem { - public ManageCollectionsMenuItem() + public ManageCollectionsFilterMenuItem() : base(null) { CollectionName.Value = "Manage collections..."; From 06c49070b130c7f9a7a394475b5db51009a045fa Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 11 Sep 2020 16:03:59 +0900 Subject: [PATCH 1040/1782] Remove player collection settings --- .../Play/PlayerSettings/CollectionSettings.cs | 35 ------------------- 1 file changed, 35 deletions(-) delete mode 100644 osu.Game/Screens/Play/PlayerSettings/CollectionSettings.cs diff --git a/osu.Game/Screens/Play/PlayerSettings/CollectionSettings.cs b/osu.Game/Screens/Play/PlayerSettings/CollectionSettings.cs deleted file mode 100644 index 9e7f8e7394..0000000000 --- a/osu.Game/Screens/Play/PlayerSettings/CollectionSettings.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.Game.Graphics.Sprites; -using osu.Game.Overlays.Music; - -namespace osu.Game.Screens.Play.PlayerSettings -{ - public class CollectionSettings : PlayerSettingsGroup - { - public CollectionSettings() - : base("collections") - { - } - - [BackgroundDependencyLoader] - private void load() - { - Children = new Drawable[] - { - new OsuSpriteText - { - Text = @"Add current song to", - }, - new CollectionsDropdown - { - RelativeSizeAxes = Axes.X, - Items = new[] { PlaylistCollection.All }, - }, - }; - } - } -} From a6a76de7a9254f9bc90bc4d8752035fa7bdf0d74 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 11 Sep 2020 16:08:49 +0900 Subject: [PATCH 1041/1782] Re-expose sealed methods --- osu.Game/Collections/CollectionFilterDropdown.cs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/osu.Game/Collections/CollectionFilterDropdown.cs b/osu.Game/Collections/CollectionFilterDropdown.cs index bdd6e73f3f..486f0a4fff 100644 --- a/osu.Game/Collections/CollectionFilterDropdown.cs +++ b/osu.Game/Collections/CollectionFilterDropdown.cs @@ -105,12 +105,16 @@ namespace osu.Game.Collections protected override string GenerateItemText(CollectionFilterMenuItem item) => item.CollectionName.Value; - protected override DropdownHeader CreateHeader() => new CollectionDropdownHeader + protected sealed override DropdownHeader CreateHeader() => CreateCollectionHeader().With(d => { - SelectedItem = { BindTarget = Current } - }; + d.SelectedItem.BindTarget = Current; + }); - protected override DropdownMenu CreateMenu() => new CollectionDropdownMenu(); + protected sealed override DropdownMenu CreateMenu() => CreateCollectionMenu(); + + protected virtual CollectionDropdownHeader CreateCollectionHeader() => new CollectionDropdownHeader(); + + protected virtual CollectionDropdownMenu CreateCollectionMenu() => new CollectionDropdownMenu(); public class CollectionDropdownHeader : OsuDropdownHeader { From 15b533f2a4dcf427b9ddfe5838e237427a7f0bbb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 11 Sep 2020 15:06:10 +0900 Subject: [PATCH 1042/1782] Hash skins based on name, not skin.ini contents It is feasible that a user may be changing the contents of skin.ini without changing the skin name / author. Such changes should not create a new skin if already imported. --- osu.Game/Database/ArchiveModelManager.cs | 10 +++++++--- osu.Game/Skinning/SkinManager.cs | 8 ++++++++ 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index 49d7edd56c..e87ab8167a 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -253,6 +253,9 @@ namespace osu.Game.Database /// Generally should include all file types which determine the file's uniqueness. /// Large files should be avoided if possible. /// + /// + /// This is only used by the default hash implementation. If is overridden, it will not be used. + /// protected abstract string[] HashableFileTypes { get; } internal static void LogForModel(TModel model, string message, Exception e = null) @@ -271,7 +274,7 @@ namespace osu.Game.Database /// /// In the case of no matching files, a hash will be generated from the passed archive's . /// - private string computeHash(TModel item, ArchiveReader reader = null) + protected virtual string ComputeHash(TModel item, ArchiveReader reader = null) { // for now, concatenate all .osu files in the set to create a unique hash. MemoryStream hashable = new MemoryStream(); @@ -318,10 +321,11 @@ namespace osu.Game.Database LogForModel(item, "Beginning import..."); item.Files = archive != null ? createFileInfos(archive, Files) : new List(); - item.Hash = computeHash(item, archive); await Populate(item, archive, cancellationToken); + item.Hash = ComputeHash(item, archive); + using (var write = ContextFactory.GetForWrite()) // used to share a context for full import. keep in mind this will block all writes. { try @@ -437,7 +441,7 @@ namespace osu.Game.Database { using (ContextFactory.GetForWrite()) { - item.Hash = computeHash(item); + item.Hash = ComputeHash(item); ModelStore.Update(item); } } diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index e1f713882a..46130cbdd4 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -12,6 +12,7 @@ using Microsoft.EntityFrameworkCore; using osu.Framework.Audio; using osu.Framework.Audio.Sample; using osu.Framework.Bindables; +using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.OpenGL.Textures; using osu.Framework.Graphics.Textures; @@ -86,6 +87,13 @@ namespace osu.Game.Skinning protected override SkinInfo CreateModel(ArchiveReader archive) => new SkinInfo { Name = archive.Name }; + protected override string ComputeHash(SkinInfo item, ArchiveReader reader = null) + { + // this is the optimal way to hash legacy skins, but will need to be reconsidered when we move forward with skin implementation. + // likely, the skin should expose a real version (ie. the version of the skin, not the skin.ini version it's targeting). + return item.ToString().ComputeSHA2Hash(); + } + protected override async Task Populate(SkinInfo model, ArchiveReader archive, CancellationToken cancellationToken = default) { await base.Populate(model, archive, cancellationToken); From 62e5c9d2636cd1b21b064633471e0e6e9c4844d6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 11 Sep 2020 16:20:30 +0900 Subject: [PATCH 1043/1782] Add test coverage --- osu.Game.Tests/Skins/IO/ImportSkinTest.cs | 170 ++++++++++++++++++++++ 1 file changed, 170 insertions(+) create mode 100644 osu.Game.Tests/Skins/IO/ImportSkinTest.cs diff --git a/osu.Game.Tests/Skins/IO/ImportSkinTest.cs b/osu.Game.Tests/Skins/IO/ImportSkinTest.cs new file mode 100644 index 0000000000..af38d0f3c4 --- /dev/null +++ b/osu.Game.Tests/Skins/IO/ImportSkinTest.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 System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Platform; +using osu.Game.Beatmaps; +using osu.Game.IO.Archives; +using osu.Game.Skinning; +using osu.Game.Tests.Resources; +using SharpCompress.Archives.Zip; + +namespace osu.Game.Tests.Skins.IO +{ + public class ImportSkinTest + { + [Test] + public async Task TestBasicImport() + { + using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) + { + try + { + var osu = await loadOsu(host); + + var imported = await loadIntoOsu(osu, new ZipArchiveReader(createOsk("test skin", "skinner"), "skin.osk")); + + Assert.That(imported.Name, Is.EqualTo("test skin")); + Assert.That(imported.Creator, Is.EqualTo("skinner")); + } + finally + { + host.Exit(); + } + } + } + + [Test] + public async Task TestImportTwiceWithSameMetadata() + { + using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) + { + try + { + var osu = await loadOsu(host); + + var imported = await loadIntoOsu(osu, new ZipArchiveReader(createOsk("test skin", "skinner"), "skin.osk")); + var imported2 = await loadIntoOsu(osu, new ZipArchiveReader(createOsk("test skin", "skinner"), "skin2.osk")); + + Assert.That(imported2.ID, Is.Not.EqualTo(imported.ID)); + Assert.That(osu.Dependencies.Get().GetAllUserSkins().Count, Is.EqualTo(1)); + + // the first should be overwritten by the second import. + Assert.That(osu.Dependencies.Get().GetAllUserSkins().First().Files.First().FileInfoID, Is.EqualTo(imported2.Files.First().FileInfoID)); + } + finally + { + host.Exit(); + } + } + } + + [Test] + public async Task TestImportTwiceWithDifferentMetadata() + { + using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) + { + try + { + var osu = await loadOsu(host); + + var imported = await loadIntoOsu(osu, new ZipArchiveReader(createOsk("test skin v2", "skinner"), "skin.osk")); + var imported2 = await loadIntoOsu(osu, new ZipArchiveReader(createOsk("test skin v2.1", "skinner"), "skin2.osk")); + + Assert.That(imported2.ID, Is.Not.EqualTo(imported.ID)); + Assert.That(osu.Dependencies.Get().GetAllUserSkins().Count, Is.EqualTo(2)); + + Assert.That(osu.Dependencies.Get().GetAllUserSkins().First().Files.First().FileInfoID, Is.EqualTo(imported.Files.First().FileInfoID)); + Assert.That(osu.Dependencies.Get().GetAllUserSkins().Last().Files.First().FileInfoID, Is.EqualTo(imported2.Files.First().FileInfoID)); + } + finally + { + host.Exit(); + } + } + } + + private MemoryStream createOsk(string name, string author) + { + var zipStream = new MemoryStream(); + using var zip = ZipArchive.Create(); + zip.AddEntry("skin.ini", generateSkinIni(name, author)); + zip.SaveTo(zipStream); + return zipStream; + } + + private MemoryStream generateSkinIni(string name, string author) + { + var stream = new MemoryStream(); + var writer = new StreamWriter(stream); + + writer.WriteLine("[General]"); + writer.WriteLine($"Name: {name}"); + writer.WriteLine($"Author: {author}"); + writer.WriteLine(); + writer.WriteLine($"# unique {Guid.NewGuid()}"); + + writer.Flush(); + + return stream; + } + + private async Task loadIntoOsu(OsuGameBase osu, ArchiveReader archive = null) + { + var beatmapManager = osu.Dependencies.Get(); + + var skinManager = osu.Dependencies.Get(); + return await skinManager.Import(archive); + } + + private async Task loadOsu(GameHost host) + { + var osu = new OsuGameBase(); + +#pragma warning disable 4014 + Task.Run(() => host.Run(osu)); +#pragma warning restore 4014 + + waitForOrAssert(() => osu.IsLoaded, @"osu! failed to start in a reasonable amount of time"); + + var beatmapFile = TestResources.GetTestBeatmapForImport(); + var beatmapManager = osu.Dependencies.Get(); + await beatmapManager.Import(beatmapFile); + + return osu; + } + + private void waitForOrAssert(Func result, string failureMessage, int timeout = 60000) + { + Task task = Task.Run(() => + { + while (!result()) Thread.Sleep(200); + }); + + 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" }; + } + } +} From ef77658311f2d7471e1bcbc56f252862131e1615 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 11 Sep 2020 16:29:14 +0900 Subject: [PATCH 1044/1782] Add coverage of case where skin.ini doesn't specify name/author --- osu.Game.Tests/Skins/IO/ImportSkinTest.cs | 26 +++++++++++++++++++++++ osu.Game/Skinning/SkinManager.cs | 16 ++++++++++---- 2 files changed, 38 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Skins/IO/ImportSkinTest.cs b/osu.Game.Tests/Skins/IO/ImportSkinTest.cs index af38d0f3c4..ad1a41a2b3 100644 --- a/osu.Game.Tests/Skins/IO/ImportSkinTest.cs +++ b/osu.Game.Tests/Skins/IO/ImportSkinTest.cs @@ -66,6 +66,32 @@ namespace osu.Game.Tests.Skins.IO } } + [Test] + public async Task TestImportTwiceWithNoMetadata() + { + using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) + { + try + { + var osu = await loadOsu(host); + + // if a user downloads two skins that do have skin.ini files but don't have any creator metadata in the skin.ini, they should both import separately just for safety. + var imported = await loadIntoOsu(osu, new ZipArchiveReader(createOsk(string.Empty, string.Empty), "download.osk")); + var imported2 = await loadIntoOsu(osu, new ZipArchiveReader(createOsk(string.Empty, string.Empty), "download.osk")); + + Assert.That(imported2.ID, Is.Not.EqualTo(imported.ID)); + Assert.That(osu.Dependencies.Get().GetAllUserSkins().Count, Is.EqualTo(2)); + + Assert.That(osu.Dependencies.Get().GetAllUserSkins().First().Files.First().FileInfoID, Is.EqualTo(imported.Files.First().FileInfoID)); + Assert.That(osu.Dependencies.Get().GetAllUserSkins().Last().Files.First().FileInfoID, Is.EqualTo(imported2.Files.First().FileInfoID)); + } + finally + { + host.Exit(); + } + } + } + [Test] public async Task TestImportTwiceWithDifferentMetadata() { diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index 46130cbdd4..eacfdaec4a 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -87,11 +87,19 @@ namespace osu.Game.Skinning protected override SkinInfo CreateModel(ArchiveReader archive) => new SkinInfo { Name = archive.Name }; + private const string unknown_creator_string = "Unknown"; + protected override string ComputeHash(SkinInfo item, ArchiveReader reader = null) { - // this is the optimal way to hash legacy skins, but will need to be reconsidered when we move forward with skin implementation. - // likely, the skin should expose a real version (ie. the version of the skin, not the skin.ini version it's targeting). - return item.ToString().ComputeSHA2Hash(); + if (item.Creator != null && item.Creator != unknown_creator_string) + { + // this is the optimal way to hash legacy skins, but will need to be reconsidered when we move forward with skin implementation. + // likely, the skin should expose a real version (ie. the version of the skin, not the skin.ini version it's targeting). + return item.ToString().ComputeSHA2Hash(); + } + + // if there was no creator, the ToString above would give the filename, which along isn't really enough to base any decisions on. + return base.ComputeHash(item, reader); } protected override async Task Populate(SkinInfo model, ArchiveReader archive, CancellationToken cancellationToken = default) @@ -108,7 +116,7 @@ namespace osu.Game.Skinning else { model.Name = model.Name.Replace(".osk", ""); - model.Creator ??= "Unknown"; + model.Creator ??= unknown_creator_string; } } From 948437865b32bafbe7af9df7c51c675f041e30d1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 11 Sep 2020 16:42:11 +0900 Subject: [PATCH 1045/1782] Remove unused code --- osu.Game.Tests/Skins/IO/ImportSkinTest.cs | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/osu.Game.Tests/Skins/IO/ImportSkinTest.cs b/osu.Game.Tests/Skins/IO/ImportSkinTest.cs index ad1a41a2b3..14eff4c5e3 100644 --- a/osu.Game.Tests/Skins/IO/ImportSkinTest.cs +++ b/osu.Game.Tests/Skins/IO/ImportSkinTest.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.IO; using System.Linq; using System.Threading; @@ -144,8 +143,6 @@ namespace osu.Game.Tests.Skins.IO private async Task loadIntoOsu(OsuGameBase osu, ArchiveReader archive = null) { - var beatmapManager = osu.Dependencies.Get(); - var skinManager = osu.Dependencies.Get(); return await skinManager.Import(archive); } @@ -176,21 +173,5 @@ namespace osu.Game.Tests.Skins.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" }; - } } } From fcc868362971a6ac8b9dd013157aab91fd6a2bf7 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 11 Sep 2020 16:46:11 +0900 Subject: [PATCH 1046/1782] Hook up now playing overlay to collections --- ...tionsDropdown.cs => CollectionDropdown.cs} | 16 +++++----- osu.Game/Overlays/Music/FilterControl.cs | 28 +++++++++++------- osu.Game/Overlays/Music/FilterCriteria.cs | 22 ++++++++++++++ osu.Game/Overlays/Music/Playlist.cs | 10 ++++++- osu.Game/Overlays/Music/PlaylistItem.cs | 29 +++++++++++++++---- osu.Game/Overlays/Music/PlaylistOverlay.cs | 8 +---- 6 files changed, 82 insertions(+), 31 deletions(-) rename osu.Game/Overlays/Music/{CollectionsDropdown.cs => CollectionDropdown.cs} (75%) create mode 100644 osu.Game/Overlays/Music/FilterCriteria.cs diff --git a/osu.Game/Overlays/Music/CollectionsDropdown.cs b/osu.Game/Overlays/Music/CollectionDropdown.cs similarity index 75% rename from osu.Game/Overlays/Music/CollectionsDropdown.cs rename to osu.Game/Overlays/Music/CollectionDropdown.cs index 5bd321f31e..4ab0ad643c 100644 --- a/osu.Game/Overlays/Music/CollectionsDropdown.cs +++ b/osu.Game/Overlays/Music/CollectionDropdown.cs @@ -7,13 +7,15 @@ using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Effects; -using osu.Framework.Graphics.UserInterface; +using osu.Game.Collections; using osu.Game.Graphics; -using osu.Game.Graphics.UserInterface; namespace osu.Game.Overlays.Music { - public class CollectionsDropdown : OsuDropdown + /// + /// A for use in the . + /// + public class CollectionDropdown : CollectionFilterDropdown { [BackgroundDependencyLoader] private void load(OsuColour colours) @@ -21,11 +23,11 @@ namespace osu.Game.Overlays.Music AccentColour = colours.Gray6; } - protected override DropdownHeader CreateHeader() => new CollectionsHeader(); + protected override CollectionDropdownHeader CreateCollectionHeader() => new CollectionsHeader(); - protected override DropdownMenu CreateMenu() => new CollectionsMenu(); + protected override CollectionDropdownMenu CreateCollectionMenu() => new CollectionsMenu(); - private class CollectionsMenu : OsuDropdownMenu + private class CollectionsMenu : CollectionDropdownMenu { public CollectionsMenu() { @@ -40,7 +42,7 @@ namespace osu.Game.Overlays.Music } } - private class CollectionsHeader : OsuDropdownHeader + private class CollectionsHeader : CollectionDropdownHeader { [BackgroundDependencyLoader] private void load(OsuColour colours) diff --git a/osu.Game/Overlays/Music/FilterControl.cs b/osu.Game/Overlays/Music/FilterControl.cs index 278bb55170..3e43387035 100644 --- a/osu.Game/Overlays/Music/FilterControl.cs +++ b/osu.Game/Overlays/Music/FilterControl.cs @@ -8,13 +8,15 @@ using osu.Game.Graphics.UserInterface; using osuTK; using System; using osu.Framework.Allocation; -using osu.Framework.Bindables; namespace osu.Game.Overlays.Music { public class FilterControl : Container { + public Action FilterChanged; + public readonly FilterTextBox Search; + private readonly CollectionDropdown collectionDropdown; public FilterControl() { @@ -32,21 +34,27 @@ namespace osu.Game.Overlays.Music RelativeSizeAxes = Axes.X, Height = 40, }, - new CollectionsDropdown - { - RelativeSizeAxes = Axes.X, - Items = new[] { PlaylistCollection.All }, - } + collectionDropdown = new CollectionDropdown { RelativeSizeAxes = Axes.X } }, }, }; - - Search.Current.ValueChanged += current_ValueChanged; } - private void current_ValueChanged(ValueChangedEvent e) => FilterChanged?.Invoke(e.NewValue); + protected override void LoadComplete() + { + base.LoadComplete(); - public Action FilterChanged; + Search.Current.BindValueChanged(_ => updateCriteria()); + collectionDropdown.Current.BindValueChanged(_ => updateCriteria(), true); + } + + private void updateCriteria() => FilterChanged?.Invoke(createCriteria()); + + private FilterCriteria createCriteria() => new FilterCriteria + { + SearchText = Search.Text, + Collection = collectionDropdown.Current.Value?.Collection + }; public class FilterTextBox : SearchTextBox { diff --git a/osu.Game/Overlays/Music/FilterCriteria.cs b/osu.Game/Overlays/Music/FilterCriteria.cs new file mode 100644 index 0000000000..f15edff4d0 --- /dev/null +++ b/osu.Game/Overlays/Music/FilterCriteria.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 JetBrains.Annotations; +using osu.Game.Collections; + +namespace osu.Game.Overlays.Music +{ + public class FilterCriteria + { + /// + /// The search text. + /// + public string SearchText; + + /// + /// The collection to filter beatmaps from. + /// + [CanBeNull] + public BeatmapCollection Collection; + } +} diff --git a/osu.Game/Overlays/Music/Playlist.cs b/osu.Game/Overlays/Music/Playlist.cs index 621a533dd6..4fe338926f 100644 --- a/osu.Game/Overlays/Music/Playlist.cs +++ b/osu.Game/Overlays/Music/Playlist.cs @@ -24,7 +24,15 @@ namespace osu.Game.Overlays.Music set => base.Padding = value; } - public void Filter(string searchTerm) => ((SearchContainer>)ListContainer).SearchTerm = searchTerm; + public void Filter(FilterCriteria criteria) + { + var items = (SearchContainer>)ListContainer; + + foreach (var item in items.OfType()) + item.InSelectedCollection = criteria.Collection?.Beatmaps.Any(b => b.BeatmapSet.Equals(item.Model)) ?? true; + + items.SearchTerm = criteria.SearchText; + } public BeatmapSetInfo FirstVisibleSet => Items.FirstOrDefault(i => ((PlaylistItem)ItemMap[i]).MatchingFilter); diff --git a/osu.Game/Overlays/Music/PlaylistItem.cs b/osu.Game/Overlays/Music/PlaylistItem.cs index 840fa51b4f..96dff39fae 100644 --- a/osu.Game/Overlays/Music/PlaylistItem.cs +++ b/osu.Game/Overlays/Music/PlaylistItem.cs @@ -95,23 +95,40 @@ namespace osu.Game.Overlays.Music return true; } + private bool inSelectedCollection = true; + + public bool InSelectedCollection + { + get => inSelectedCollection; + set + { + if (inSelectedCollection == value) + return; + + inSelectedCollection = value; + updateFilter(); + } + } + public IEnumerable FilterTerms { get; } - private bool matching = true; + private bool matchingFilter = true; public bool MatchingFilter { - get => matching; + get => matchingFilter && inSelectedCollection; set { - if (matching == value) return; + if (matchingFilter == value) + return; - matching = value; - - this.FadeTo(matching ? 1 : 0, 200); + matchingFilter = value; + updateFilter(); } } + private void updateFilter() => this.FadeTo(MatchingFilter ? 1 : 0, 200); + public bool FilteringActive { get; set; } } } diff --git a/osu.Game/Overlays/Music/PlaylistOverlay.cs b/osu.Game/Overlays/Music/PlaylistOverlay.cs index b45d84049f..050e687dfb 100644 --- a/osu.Game/Overlays/Music/PlaylistOverlay.cs +++ b/osu.Game/Overlays/Music/PlaylistOverlay.cs @@ -68,7 +68,7 @@ namespace osu.Game.Overlays.Music { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - FilterChanged = search => list.Filter(search), + FilterChanged = criteria => list.Filter(criteria), Padding = new MarginPadding(10), }, }, @@ -124,10 +124,4 @@ namespace osu.Game.Overlays.Music beatmap.Value.Track.Restart(); } } - - //todo: placeholder - public enum PlaylistCollection - { - All - } } From 6327f12fe4a0fa587257ab2559d36f09c7a8a0e8 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 11 Sep 2020 16:58:18 +0900 Subject: [PATCH 1047/1782] Disable manage collections item in now playing overlay --- osu.Game/Collections/CollectionFilterDropdown.cs | 9 ++++++++- osu.Game/Overlays/Music/CollectionDropdown.cs | 2 ++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/osu.Game/Collections/CollectionFilterDropdown.cs b/osu.Game/Collections/CollectionFilterDropdown.cs index 486f0a4fff..ec0e9d5a89 100644 --- a/osu.Game/Collections/CollectionFilterDropdown.cs +++ b/osu.Game/Collections/CollectionFilterDropdown.cs @@ -24,6 +24,11 @@ namespace osu.Game.Collections /// public class CollectionFilterDropdown : OsuDropdown { + /// + /// Whether to show the "manage collections..." menu item in the dropdown. + /// + protected virtual bool ShowManageCollectionsItem => true; + private readonly IBindableList collections = new BindableList(); private readonly IBindableList beatmaps = new BindableList(); private readonly BindableList filters = new BindableList(); @@ -63,7 +68,9 @@ namespace osu.Game.Collections filters.Clear(); filters.Add(new AllBeatmapsCollectionFilterMenuItem()); filters.AddRange(collections.Select(c => new CollectionFilterMenuItem(c))); - filters.Add(new ManageCollectionsFilterMenuItem()); + + if (ShowManageCollectionsItem) + filters.Add(new ManageCollectionsFilterMenuItem()); Current.Value = filters.SingleOrDefault(f => f.Collection != null && f.Collection == selectedItem) ?? filters[0]; } diff --git a/osu.Game/Overlays/Music/CollectionDropdown.cs b/osu.Game/Overlays/Music/CollectionDropdown.cs index 4ab0ad643c..ed0ebf696b 100644 --- a/osu.Game/Overlays/Music/CollectionDropdown.cs +++ b/osu.Game/Overlays/Music/CollectionDropdown.cs @@ -17,6 +17,8 @@ namespace osu.Game.Overlays.Music /// public class CollectionDropdown : CollectionFilterDropdown { + protected override bool ShowManageCollectionsItem => false; + [BackgroundDependencyLoader] private void load(OsuColour colours) { From b047fbb8ee98de23bc4e23aa0856b38a1b66d44d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 11 Sep 2020 17:45:57 +0900 Subject: [PATCH 1048/1782] Use bindable value for search text --- osu.Game/Overlays/Music/FilterControl.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Music/FilterControl.cs b/osu.Game/Overlays/Music/FilterControl.cs index 3e43387035..66adbeebe8 100644 --- a/osu.Game/Overlays/Music/FilterControl.cs +++ b/osu.Game/Overlays/Music/FilterControl.cs @@ -52,7 +52,7 @@ namespace osu.Game.Overlays.Music private FilterCriteria createCriteria() => new FilterCriteria { - SearchText = Search.Text, + SearchText = Search.Current.Value, Collection = collectionDropdown.Current.Value?.Collection }; From 8bae00454edbeffdfef9e47a3f5c7ccf87a5f508 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 11 Sep 2020 19:53:55 +0900 Subject: [PATCH 1049/1782] Fix slider serialization --- osu.Game.Rulesets.Osu/Objects/Slider.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index 705e88040f..5aeb23a425 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -112,8 +112,9 @@ namespace osu.Game.Rulesets.Osu.Objects /// public double TickDistanceMultiplier = 1; - public HitCircle HeadCircle; - public SliderTailCircle TailCircle; + public HitCircle HeadCircle { get; protected set; } + + public SliderTailCircle TailCircle { get; protected set; } public Slider() { From 8e028dd88fa5c8ac1feda1128fb403fb22519c88 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 11 Sep 2020 19:54:11 +0900 Subject: [PATCH 1050/1782] Fix incorrect ordering of ApplyDefaults for newly added objects --- osu.Game/Screens/Edit/EditorBeatmap.cs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Edit/EditorBeatmap.cs b/osu.Game/Screens/Edit/EditorBeatmap.cs index 061009e519..fd5270653d 100644 --- a/osu.Game/Screens/Edit/EditorBeatmap.cs +++ b/osu.Game/Screens/Edit/EditorBeatmap.cs @@ -79,11 +79,11 @@ namespace osu.Game.Screens.Edit private void updateHitObject([CanBeNull] HitObject hitObject, bool silent) { - scheduledUpdate?.Cancel(); - if (hitObject != null) pendingUpdates.Add(hitObject); + if (scheduledUpdate?.Completed == false) return; + scheduledUpdate = Schedule(() => { beatmapProcessor?.PreProcess(); @@ -158,10 +158,14 @@ namespace osu.Game.Screens.Edit { trackStartTime(hitObject); - mutableHitObjects.Insert(index, hitObject); - - HitObjectAdded?.Invoke(hitObject); updateHitObject(hitObject, true); + + // must occur after the batch-scheduled ApplyDefaults. + Schedule(() => + { + mutableHitObjects.Insert(index, hitObject); + HitObjectAdded?.Invoke(hitObject); + }); } /// From 7d7401123c05a2179bab664c027c5bdba252fd90 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 11 Sep 2020 19:54:20 +0900 Subject: [PATCH 1051/1782] Add initial implementation of editor clipboard --- osu.Game/Screens/Edit/ClipboardContent.cs | 27 ++++++++++++ osu.Game/Screens/Edit/Editor.cs | 52 ++++++++++++++++++++++- 2 files changed, 78 insertions(+), 1 deletion(-) create mode 100644 osu.Game/Screens/Edit/ClipboardContent.cs diff --git a/osu.Game/Screens/Edit/ClipboardContent.cs b/osu.Game/Screens/Edit/ClipboardContent.cs new file mode 100644 index 0000000000..b2edbedccc --- /dev/null +++ b/osu.Game/Screens/Edit/ClipboardContent.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 System.Collections.Generic; +using System.Linq; +using Newtonsoft.Json; +using osu.Game.IO.Serialization; +using osu.Game.IO.Serialization.Converters; +using osu.Game.Rulesets.Objects; + +namespace osu.Game.Screens.Edit +{ + public class ClipboardContent : IJsonSerializable + { + [JsonConverter(typeof(TypedListConverter))] + public IList HitObjects; + + public ClipboardContent() + { + } + + public ClipboardContent(EditorBeatmap editorBeatmap) + { + HitObjects = editorBeatmap.SelectedHitObjects.ToList(); + } + } +} diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 23eb704920..a063b0a303 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Linq; using osu.Framework; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -22,6 +23,7 @@ using osu.Game.Graphics; using osu.Game.Graphics.Cursor; using osu.Game.Graphics.UserInterface; using osu.Game.Input.Bindings; +using osu.Game.IO.Serialization; using osu.Game.Online.API; using osu.Game.Overlays; using osu.Game.Rulesets.Edit; @@ -131,9 +133,14 @@ namespace osu.Game.Screens.Edit updateLastSavedHash(); EditorMenuBar menuBar; + OsuMenuItem undoMenuItem; OsuMenuItem redoMenuItem; + EditorMenuItem cutMenuItem; + EditorMenuItem copyMenuItem; + EditorMenuItem pasteMenuItem; + var fileMenuItems = new List { new EditorMenuItem("Save", MenuItemType.Standard, Save) @@ -183,7 +190,11 @@ namespace osu.Game.Screens.Edit Items = new[] { undoMenuItem = new EditorMenuItem("Undo", MenuItemType.Standard, Undo), - redoMenuItem = new EditorMenuItem("Redo", MenuItemType.Standard, Redo) + redoMenuItem = new EditorMenuItem("Redo", MenuItemType.Standard, Redo), + new EditorMenuItemSpacer(), + cutMenuItem = new EditorMenuItem("Cut", MenuItemType.Standard, Cut), + copyMenuItem = new EditorMenuItem("Copy", MenuItemType.Standard, Copy), + pasteMenuItem = new EditorMenuItem("Paste", MenuItemType.Standard, Paste), } } } @@ -244,6 +255,17 @@ namespace osu.Game.Screens.Edit changeHandler.CanUndo.BindValueChanged(v => undoMenuItem.Action.Disabled = !v.NewValue, true); changeHandler.CanRedo.BindValueChanged(v => redoMenuItem.Action.Disabled = !v.NewValue, true); + // todo: BindCollectionChanged + editorBeatmap.SelectedHitObjects.CollectionChanged += (_, __) => + { + var hasObjects = editorBeatmap.SelectedHitObjects.Count > 0; + + cutMenuItem.Action.Disabled = !hasObjects; + copyMenuItem.Action.Disabled = !hasObjects; + }; + + clipboard.BindValueChanged(content => pasteMenuItem.Action.Disabled = string.IsNullOrEmpty(content.NewValue)); + menuBar.Mode.ValueChanged += onModeChanged; bottomBackground.Colour = colours.Gray2; @@ -394,6 +416,34 @@ namespace osu.Game.Screens.Edit this.Exit(); } + private readonly Bindable clipboard = new Bindable(); + + protected void Cut() + { + Copy(); + foreach (var h in editorBeatmap.SelectedHitObjects.ToArray()) + editorBeatmap.Remove(h); + } + + protected void Copy() + { + clipboard.Value = new ClipboardContent(editorBeatmap).Serialize(); + } + + protected void Paste() + { + if (string.IsNullOrEmpty(clipboard.Value)) + return; + + var objects = clipboard.Value.Deserialize().HitObjects; + double timeOffset = clock.CurrentTime - objects.First().StartTime; + + foreach (var h in objects) + h.StartTime += timeOffset; + + editorBeatmap.AddRange(objects); + } + protected void Undo() => changeHandler.RestoreState(-1); protected void Redo() => changeHandler.RestoreState(1); From de3d8e83e178986573108086bc59ecf7601aa74f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 11 Sep 2020 19:55:41 +0900 Subject: [PATCH 1052/1782] Add keyboard shortcuts --- osu.Game/Screens/Edit/Editor.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index a063b0a303..2af319870d 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -292,6 +292,18 @@ namespace osu.Game.Screens.Edit { switch (action.ActionType) { + case PlatformActionType.Cut: + Cut(); + return true; + + case PlatformActionType.Copy: + Copy(); + return true; + + case PlatformActionType.Paste: + Paste(); + return true; + case PlatformActionType.Undo: Undo(); return true; From 2858296c255a5c2427e756366cd4364a0bd30c01 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 11 Sep 2020 19:58:34 +0900 Subject: [PATCH 1053/1782] Avoid editor confirm-save dialog looping infinitely when using keyboard shortcut to exit Will now exit without saving if the keyboard shortcut is activated twice in a row, as expected. Closes #10136. --- 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 23eb704920..ce34c1dac0 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -369,7 +369,7 @@ namespace osu.Game.Screens.Edit public override bool OnExiting(IScreen next) { - if (!exitConfirmed && dialogOverlay != null && HasUnsavedChanges) + if (!exitConfirmed && dialogOverlay != null && HasUnsavedChanges && !(dialogOverlay.CurrentDialog is PromptForSaveDialog)) { dialogOverlay?.Push(new PromptForSaveDialog(confirmExit, confirmExitWithSave)); return true; From 139a5acd1b98ec8ae9e2775f3480ab3bf5052519 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 11 Sep 2020 20:14:12 +0900 Subject: [PATCH 1054/1782] Fix editor hitobjects getting masked weirdly Closes #10124 --- 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 b9cc054ed3..abb32bb6a8 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -112,7 +112,6 @@ namespace osu.Game.Rulesets.Edit { Name = "Content", RelativeSizeAxes = Axes.Both, - Masking = true, Children = new Drawable[] { // layers below playfield From 432c3e17ebd3fc7d05bf53c861e7946ee302febc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 11 Sep 2020 20:23:34 +0900 Subject: [PATCH 1055/1782] Fix toolbox becoming inoperable due to incorrect ordering --- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 81 ++++++++++----------- 1 file changed, 37 insertions(+), 44 deletions(-) diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index abb32bb6a8..e42a359d2e 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -82,56 +82,49 @@ namespace osu.Game.Rulesets.Edit return; } - InternalChild = new GridContainer + const float toolbar_width = 200; + + InternalChildren = new Drawable[] { - RelativeSizeAxes = Axes.Both, - Content = new[] + new Container { - new Drawable[] + Name = "Content", + Padding = new MarginPadding { Left = toolbar_width }, + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] { - new FillFlowContainer + // layers below playfield + drawableRulesetWrapper.CreatePlayfieldAdjustmentContainer().WithChildren(new Drawable[] { - Name = "Sidebar", - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Right = 10 }, - Spacing = new Vector2(10), - Children = new Drawable[] - { - new ToolboxGroup("toolbox") { Child = toolboxCollection = new RadioButtonCollection { RelativeSizeAxes = Axes.X } }, - new ToolboxGroup("toggles") - { - ChildrenEnumerable = Toggles.Select(b => new SettingsCheckbox - { - Bindable = b, - LabelText = b?.Description ?? "unknown" - }) - } - } - }, - new Container - { - Name = "Content", - RelativeSizeAxes = Axes.Both, - Children = new Drawable[] - { - // layers below playfield - drawableRulesetWrapper.CreatePlayfieldAdjustmentContainer().WithChildren(new Drawable[] - { - LayerBelowRuleset, - new EditorPlayfieldBorder { RelativeSizeAxes = Axes.Both } - }), - drawableRulesetWrapper, - // layers above playfield - drawableRulesetWrapper.CreatePlayfieldAdjustmentContainer() - .WithChild(BlueprintContainer = CreateBlueprintContainer(HitObjects)) - } - } - }, + LayerBelowRuleset, + new EditorPlayfieldBorder { RelativeSizeAxes = Axes.Both } + }), + drawableRulesetWrapper, + // layers above playfield + drawableRulesetWrapper.CreatePlayfieldAdjustmentContainer() + .WithChild(BlueprintContainer = CreateBlueprintContainer(HitObjects)) + } }, - ColumnDimensions = new[] + new FillFlowContainer { - new Dimension(GridSizeMode.Absolute, 200), - } + Name = "Sidebar", + RelativeSizeAxes = Axes.Y, + Width = toolbar_width, + Padding = new MarginPadding { Right = 10 }, + Spacing = new Vector2(10), + Children = new Drawable[] + { + new ToolboxGroup("toolbox") { Child = toolboxCollection = new RadioButtonCollection { RelativeSizeAxes = Axes.X } }, + new ToolboxGroup("toggles") + { + ChildrenEnumerable = Toggles.Select(b => new SettingsCheckbox + { + Bindable = b, + LabelText = b?.Description ?? "unknown" + }) + } + } + }, }; toolboxCollection.Items = CompositionTools From 73dd21c8fcdaa49157287747b8be14abb1388296 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 11 Sep 2020 20:27:04 +0900 Subject: [PATCH 1056/1782] Add failing test --- .../Visual/Editing/TestSceneEditorChangeStates.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorChangeStates.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorChangeStates.cs index c8a32d966f..dfe1e434dc 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorChangeStates.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorChangeStates.cs @@ -27,6 +27,17 @@ namespace osu.Game.Tests.Visual.Editing AddStep("get beatmap", () => editorBeatmap = Editor.ChildrenOfType().Single()); } + [Test] + public void TestSelectedObjects() + { + HitCircle obj = null; + AddStep("add hitobject", () => editorBeatmap.Add(obj = new HitCircle { StartTime = 1000 })); + AddStep("select hitobject", () => editorBeatmap.SelectedHitObjects.Add(obj)); + AddAssert("confirm 1 selected", () => editorBeatmap.SelectedHitObjects.Count == 1); + AddStep("deselect hitobject", () => editorBeatmap.SelectedHitObjects.Remove(obj)); + AddAssert("confirm 0 selected", () => editorBeatmap.SelectedHitObjects.Count == 0); + } + [Test] public void TestUndoFromInitialState() { From 22e6df02b650d72d6d22a0787c446984e9df982b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 11 Sep 2020 20:13:54 +0900 Subject: [PATCH 1057/1782] Fix editor selected hitobjects containing the selection up to five times --- .../Compose/Components/BlueprintContainer.cs | 2 -- .../Compose/Components/SelectionHandler.cs | 20 +++++++++++++------ 2 files 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 865e225645..b7b222d87b 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -367,14 +367,12 @@ namespace osu.Game.Screens.Edit.Compose.Components { selectionHandler.HandleSelected(blueprint); SelectionBlueprints.ChangeChildDepth(blueprint, 1); - beatmap.SelectedHitObjects.Add(blueprint.HitObject); } private void onBlueprintDeselected(SelectionBlueprint blueprint) { selectionHandler.HandleDeselected(blueprint); SelectionBlueprints.ChangeChildDepth(blueprint, 0); - beatmap.SelectedHitObjects.Remove(blueprint.HitObject); } #endregion diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index 9700cb8c8e..f397ee1596 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -145,10 +145,16 @@ namespace osu.Game.Screens.Edit.Compose.Components /// The blueprint. internal void HandleSelected(SelectionBlueprint blueprint) { - selectedBlueprints.Add(blueprint); - EditorBeatmap.SelectedHitObjects.Add(blueprint.HitObject); + if (!selectedBlueprints.Contains(blueprint)) + { + selectedBlueprints.Add(blueprint); - UpdateVisibility(); + // need to check this as well, as there are potentially multiple SelectionHandlers and the above check is not enough. + if (!EditorBeatmap.SelectedHitObjects.Contains(blueprint.HitObject)) + EditorBeatmap.SelectedHitObjects.Add(blueprint.HitObject); + + UpdateVisibility(); + } } /// @@ -157,10 +163,12 @@ namespace osu.Game.Screens.Edit.Compose.Components /// The blueprint. internal void HandleDeselected(SelectionBlueprint blueprint) { - selectedBlueprints.Remove(blueprint); - EditorBeatmap.SelectedHitObjects.Remove(blueprint.HitObject); + if (selectedBlueprints.Remove(blueprint)) + { + EditorBeatmap.SelectedHitObjects.Remove(blueprint.HitObject); - UpdateVisibility(); + UpdateVisibility(); + } } /// From 94d929d8cdf64a648afca7408ecd192287fc2479 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 11 Sep 2020 22:03:19 +0900 Subject: [PATCH 1058/1782] Remove unnecessary contains checks --- .../Compose/Components/SelectionHandler.cs | 20 +++++++------------ 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index f397ee1596..0a3c9072cf 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -145,16 +145,13 @@ namespace osu.Game.Screens.Edit.Compose.Components /// The blueprint. internal void HandleSelected(SelectionBlueprint blueprint) { - if (!selectedBlueprints.Contains(blueprint)) - { - selectedBlueprints.Add(blueprint); + selectedBlueprints.Add(blueprint); - // need to check this as well, as there are potentially multiple SelectionHandlers and the above check is not enough. - if (!EditorBeatmap.SelectedHitObjects.Contains(blueprint.HitObject)) - EditorBeatmap.SelectedHitObjects.Add(blueprint.HitObject); + // need to check this as well, as there are potentially multiple SelectionHandlers and the above check is not enough. + if (!EditorBeatmap.SelectedHitObjects.Contains(blueprint.HitObject)) + EditorBeatmap.SelectedHitObjects.Add(blueprint.HitObject); - UpdateVisibility(); - } + UpdateVisibility(); } /// @@ -163,12 +160,9 @@ namespace osu.Game.Screens.Edit.Compose.Components /// The blueprint. internal void HandleDeselected(SelectionBlueprint blueprint) { - if (selectedBlueprints.Remove(blueprint)) - { - EditorBeatmap.SelectedHitObjects.Remove(blueprint.HitObject); + EditorBeatmap.SelectedHitObjects.Remove(blueprint.HitObject); - UpdateVisibility(); - } + UpdateVisibility(); } /// From cb14d847deec463a2d236e33f516d74c61f80340 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 11 Sep 2020 22:40:12 +0900 Subject: [PATCH 1059/1782] 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 a2686c380e..6cbb4b2e68 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,6 +52,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 48582ae29d..8d23a32c3c 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -24,7 +24,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 0eed2fa911..d00b174195 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -80,7 +80,7 @@ - + From 001cd1194c934d02c326ce55a8da991396e568a5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 11 Sep 2020 22:53:03 +0900 Subject: [PATCH 1060/1782] Consume BindCollectionChanged --- osu.Game/Screens/Edit/Editor.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 896c4f9f61..d6b2b4ba3a 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -256,13 +256,13 @@ namespace osu.Game.Screens.Edit changeHandler.CanRedo.BindValueChanged(v => redoMenuItem.Action.Disabled = !v.NewValue, true); // todo: BindCollectionChanged - editorBeatmap.SelectedHitObjects.CollectionChanged += (_, __) => + editorBeatmap.SelectedHitObjects.BindCollectionChanged((_, __) => { var hasObjects = editorBeatmap.SelectedHitObjects.Count > 0; cutMenuItem.Action.Disabled = !hasObjects; copyMenuItem.Action.Disabled = !hasObjects; - }; + }, true); clipboard.BindValueChanged(content => pasteMenuItem.Action.Disabled = string.IsNullOrEmpty(content.NewValue)); From 2d9b0acabe7fd4826e7d791d88c89a348afc0601 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 12 Sep 2020 15:33:13 +0900 Subject: [PATCH 1061/1782] Fix empty selection via keyboard shortcuts crashing --- osu.Game/Screens/Edit/Editor.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index d6b2b4ba3a..19bac2a778 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using osu.Framework; using osu.Framework.Allocation; @@ -439,6 +440,9 @@ namespace osu.Game.Screens.Edit protected void Copy() { + if (editorBeatmap.SelectedHitObjects.Count == 0) + return; + clipboard.Value = new ClipboardContent(editorBeatmap).Serialize(); } @@ -448,6 +452,9 @@ namespace osu.Game.Screens.Edit return; var objects = clipboard.Value.Deserialize().HitObjects; + + Debug.Assert(objects.Any()); + double timeOffset = clock.CurrentTime - objects.First().StartTime; foreach (var h in objects) From 81f30cd2649a6edd2abc28868e13f492fc95bf1e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 12 Sep 2020 20:31:50 +0900 Subject: [PATCH 1062/1782] Select blueprint if object is already selected at the point of adding --- osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index b7b222d87b..bf1e18771f 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -271,6 +271,9 @@ namespace osu.Game.Screens.Edit.Compose.Components blueprint.Selected += onBlueprintSelected; blueprint.Deselected += onBlueprintDeselected; + if (beatmap.SelectedHitObjects.Contains(hitObject)) + blueprint.Select(); + SelectionBlueprints.Add(blueprint); } From 3854caae9b0a6df9ba6599960a0a02fade064ae5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 12 Sep 2020 21:20:37 +0900 Subject: [PATCH 1063/1782] Remove secondary schedule logic --- osu.Game/Screens/Edit/EditorBeatmap.cs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/osu.Game/Screens/Edit/EditorBeatmap.cs b/osu.Game/Screens/Edit/EditorBeatmap.cs index fd5270653d..5272530228 100644 --- a/osu.Game/Screens/Edit/EditorBeatmap.cs +++ b/osu.Game/Screens/Edit/EditorBeatmap.cs @@ -77,7 +77,7 @@ namespace osu.Game.Screens.Edit /// The to update. public void UpdateHitObject([NotNull] HitObject hitObject) => updateHitObject(hitObject, false); - private void updateHitObject([CanBeNull] HitObject hitObject, bool silent) + private void updateHitObject([CanBeNull] HitObject hitObject, bool silent, bool performAdd = false) { if (hitObject != null) pendingUpdates.Add(hitObject); @@ -93,6 +93,12 @@ namespace osu.Game.Screens.Edit beatmapProcessor?.PostProcess(); + if (performAdd) + { + foreach (var obj in pendingUpdates) + HitObjectAdded?.Invoke(obj); + } + if (!silent) { foreach (var obj in pendingUpdates) @@ -158,14 +164,8 @@ namespace osu.Game.Screens.Edit { trackStartTime(hitObject); - updateHitObject(hitObject, true); - - // must occur after the batch-scheduled ApplyDefaults. - Schedule(() => - { - mutableHitObjects.Insert(index, hitObject); - HitObjectAdded?.Invoke(hitObject); - }); + mutableHitObjects.Insert(index, hitObject); + updateHitObject(hitObject, true, true); } /// From 1a9f0ac16afbc396728521cc2664509b07695808 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 11 Sep 2020 23:02:23 +0900 Subject: [PATCH 1064/1782] Select new objects --- osu.Game/Screens/Edit/Editor.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 19bac2a778..ee3befe8bd 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -460,7 +460,10 @@ namespace osu.Game.Screens.Edit foreach (var h in objects) h.StartTime += timeOffset; + editorBeatmap.SelectedHitObjects.Clear(); + editorBeatmap.AddRange(objects); + editorBeatmap.SelectedHitObjects.AddRange(objects); } protected void Undo() => changeHandler.RestoreState(-1); From f17b2f1359eba9e9296f9e0a4ba1caac57f52f6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 12 Sep 2020 20:43:17 +0200 Subject: [PATCH 1065/1782] Ensure track is looping in song select immediately --- osu.Game/Screens/Select/SongSelect.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index d313f67446..683e3abcc2 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -517,6 +517,8 @@ namespace osu.Game.Screens.Select FilterControl.Activate(); ModSelect.SelectedMods.BindTo(selectedMods); + + music.TrackChanged += ensureTrackLooping; } private const double logo_transition = 250; @@ -568,6 +570,7 @@ namespace osu.Game.Screens.Select BeatmapDetails.Refresh(); music.CurrentTrack.Looping = true; + music.TrackChanged += ensureTrackLooping; music.ResetTrackAdjustments(); if (Beatmap != null && !Beatmap.Value.BeatmapSetInfo.DeletePending) @@ -593,6 +596,7 @@ namespace osu.Game.Screens.Select BeatmapOptions.Hide(); music.CurrentTrack.Looping = false; + music.TrackChanged -= ensureTrackLooping; this.ScaleTo(1.1f, 250, Easing.InSine); @@ -614,10 +618,14 @@ namespace osu.Game.Screens.Select FilterControl.Deactivate(); music.CurrentTrack.Looping = false; + music.TrackChanged -= ensureTrackLooping; return false; } + private void ensureTrackLooping(WorkingBeatmap beatmap, TrackChangeDirection changeDirection) + => music.CurrentTrack.Looping = true; + public override bool OnBackButton() { if (ModSelect.State.Value == Visibility.Visible) @@ -653,8 +661,6 @@ namespace osu.Game.Screens.Select beatmapInfoWedge.Beatmap = beatmap; BeatmapDetails.Beatmap = beatmap; - - music.CurrentTrack.Looping = true; } private readonly WeakReference lastTrack = new WeakReference(null); From 3db0e7cd75d55126d45b94661a865b17a1b8babd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 12 Sep 2020 22:34:57 +0200 Subject: [PATCH 1066/1782] Generalise LegacyRollingCounter --- .../Skinning/LegacyComboCounter.cs | 33 ------------ osu.Game/Skinning/LegacyRollingCounter.cs | 51 +++++++++++++++++++ 2 files changed, 51 insertions(+), 33 deletions(-) create mode 100644 osu.Game/Skinning/LegacyRollingCounter.cs diff --git a/osu.Game.Rulesets.Catch/Skinning/LegacyComboCounter.cs b/osu.Game.Rulesets.Catch/Skinning/LegacyComboCounter.cs index e03b30f58f..ccfabdc5fd 100644 --- a/osu.Game.Rulesets.Catch/Skinning/LegacyComboCounter.cs +++ b/osu.Game.Rulesets.Catch/Skinning/LegacyComboCounter.cs @@ -1,13 +1,10 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Game.Graphics.Sprites; -using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Catch.UI; using osu.Game.Screens.Play; using osu.Game.Skinning; @@ -128,35 +125,5 @@ namespace osu.Game.Rulesets.Catch.Skinning lastExplosion = explosion; } - - private class LegacyRollingCounter : RollingCounter - { - private readonly ISkin skin; - - private readonly string fontName; - private readonly float fontOverlap; - - protected override bool IsRollingProportional => true; - - public LegacyRollingCounter(ISkin skin, string fontName, float fontOverlap) - { - this.skin = skin; - this.fontName = fontName; - this.fontOverlap = fontOverlap; - } - - protected override double GetProportionalDuration(int currentValue, int newValue) - { - return Math.Abs(newValue - currentValue) * 75.0; - } - - protected override OsuSpriteText CreateSpriteText() - { - return new LegacySpriteText(skin, fontName) - { - Spacing = new Vector2(-fontOverlap, 0f) - }; - } - } } } diff --git a/osu.Game/Skinning/LegacyRollingCounter.cs b/osu.Game/Skinning/LegacyRollingCounter.cs new file mode 100644 index 0000000000..8aa9d4e9af --- /dev/null +++ b/osu.Game/Skinning/LegacyRollingCounter.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 System; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osuTK; + +namespace osu.Game.Skinning +{ + /// + /// An integer that uses number sprites from a legacy skin. + /// + public class LegacyRollingCounter : RollingCounter + { + private readonly ISkin skin; + + private readonly string fontName; + private readonly float fontOverlap; + + protected override bool IsRollingProportional => true; + + /// + /// Creates a new . + /// + /// The from which to get counter number sprites. + /// The name of the legacy font to use. + /// + /// The numeric overlap of number sprites to use. + /// A positive number will bring the number sprites closer together, while a negative number + /// will split them apart more. + /// + public LegacyRollingCounter(ISkin skin, string fontName, float fontOverlap) + { + this.skin = skin; + this.fontName = fontName; + this.fontOverlap = fontOverlap; + } + + protected override double GetProportionalDuration(int currentValue, int newValue) + { + return Math.Abs(newValue - currentValue) * 75.0; + } + + protected sealed override OsuSpriteText CreateSpriteText() => + new LegacySpriteText(skin, fontName) + { + Spacing = new Vector2(-fontOverlap, 0f) + }; + } +} From fcf3a1d13c9dcad3652062c613c741e049e9ea43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 12 Sep 2020 22:39:06 +0200 Subject: [PATCH 1067/1782] Encapsulate combo display better --- .../TestSceneCatcherArea.cs | 2 +- osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs | 13 ++----------- osu.Game.Rulesets.Catch/UI/CatcherArea.cs | 13 +++++++++---- 3 files changed, 12 insertions(+), 16 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs index b4f123598b..e055f08dc2 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs @@ -87,7 +87,7 @@ namespace osu.Game.Rulesets.Catch.Tests Schedule(() => { area.AttemptCatch(fruit); - area.OnResult(drawable, new JudgementResult(fruit, new CatchJudgement()) { Type = miss ? HitResult.Miss : HitResult.Great }); + area.OnNewResult(drawable, new JudgementResult(fruit, new CatchJudgement()) { Type = miss ? HitResult.Miss : HitResult.Great }); drawable.Expire(); }); diff --git a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs index 409ea6dbc6..735d7fc300 100644 --- a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs @@ -29,8 +29,6 @@ namespace osu.Game.Rulesets.Catch.UI internal readonly CatcherArea CatcherArea; - private CatchComboDisplay comboDisplay => CatcherArea.ComboDisplay; - public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => // only check the X position; handle all vertical space. base.ReceivePositionalInputAt(new Vector2(screenSpacePos.X, ScreenSpaceDrawQuad.Centre.Y)); @@ -73,16 +71,9 @@ namespace osu.Game.Rulesets.Catch.UI } private void onNewResult(DrawableHitObject judgedObject, JudgementResult result) - { - var catchObject = (DrawableCatchHitObject)judgedObject; - CatcherArea.OnResult(catchObject, result); - - comboDisplay.OnNewResult(catchObject, result); - } + => CatcherArea.OnNewResult((DrawableCatchHitObject)judgedObject, result); private void onRevertResult(DrawableHitObject judgedObject, JudgementResult result) - { - comboDisplay.OnRevertResult((DrawableCatchHitObject)judgedObject, result); - } + => CatcherArea.OnRevertResult((DrawableCatchHitObject)judgedObject, result); } } diff --git a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs index 9cfb9f41d7..d3e63b0333 100644 --- a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs +++ b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs @@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Catch.UI public Func> CreateDrawableRepresentation; public readonly Catcher MovableCatcher; - internal readonly CatchComboDisplay ComboDisplay; + private readonly CatchComboDisplay comboDisplay; public Container ExplodingFruitTarget { @@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Catch.UI Size = new Vector2(CatchPlayfield.WIDTH, CATCHER_SIZE); Children = new Drawable[] { - ComboDisplay = new CatchComboDisplay + comboDisplay = new CatchComboDisplay { RelativeSizeAxes = Axes.None, AutoSizeAxes = Axes.Both, @@ -50,7 +50,7 @@ namespace osu.Game.Rulesets.Catch.UI }; } - public void OnResult(DrawableCatchHitObject fruit, JudgementResult result) + public void OnNewResult(DrawableCatchHitObject fruit, JudgementResult result) { if (result.Judgement is IgnoreJudgement) return; @@ -99,8 +99,13 @@ namespace osu.Game.Rulesets.Catch.UI else MovableCatcher.Drop(); } + + comboDisplay.OnNewResult(fruit, result); } + public void OnRevertResult(DrawableCatchHitObject fruit, JudgementResult result) + => comboDisplay.OnRevertResult(fruit, result); + public void OnReleased(CatchAction action) { } @@ -119,7 +124,7 @@ namespace osu.Game.Rulesets.Catch.UI if (state?.CatcherX != null) MovableCatcher.X = state.CatcherX.Value; - ComboDisplay.X = MovableCatcher.X; + comboDisplay.X = MovableCatcher.X; } } } From c573392bb2db182233f2b45c17aa1c0c565c9c54 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 13 Sep 2020 22:31:59 +0900 Subject: [PATCH 1068/1782] Remove completed todo --- osu.Game/Screens/Edit/Editor.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index ee3befe8bd..d80f899f90 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -256,7 +256,6 @@ namespace osu.Game.Screens.Edit changeHandler.CanUndo.BindValueChanged(v => undoMenuItem.Action.Disabled = !v.NewValue, true); changeHandler.CanRedo.BindValueChanged(v => redoMenuItem.Action.Disabled = !v.NewValue, true); - // todo: BindCollectionChanged editorBeatmap.SelectedHitObjects.BindCollectionChanged((_, __) => { var hasObjects = editorBeatmap.SelectedHitObjects.Count > 0; From 320e3143565023804e2e5279a6fc8267c49ec87e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 13 Sep 2020 22:53:30 +0900 Subject: [PATCH 1069/1782] Use minimum start time to handle SelectedHitObjects not being sorted --- 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 d80f899f90..64365bd512 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -454,7 +454,7 @@ namespace osu.Game.Screens.Edit Debug.Assert(objects.Any()); - double timeOffset = clock.CurrentTime - objects.First().StartTime; + double timeOffset = clock.CurrentTime - objects.Min(o => o.StartTime); foreach (var h in objects) h.StartTime += timeOffset; From 3e37f27a66f65d7a7b938fdb8c8bc1c5edc03d76 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 13 Sep 2020 23:22:19 +0900 Subject: [PATCH 1070/1782] Fix regressed tests due to schedule changes --- .../Beatmaps/TestSceneEditorBeatmap.cs | 50 ++++++++----------- 1 file changed, 20 insertions(+), 30 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/TestSceneEditorBeatmap.cs b/osu.Game.Tests/Beatmaps/TestSceneEditorBeatmap.cs index b7b48ec06a..902f0d7c23 100644 --- a/osu.Game.Tests/Beatmaps/TestSceneEditorBeatmap.cs +++ b/osu.Game.Tests/Beatmaps/TestSceneEditorBeatmap.cs @@ -23,15 +23,19 @@ namespace osu.Game.Tests.Beatmaps [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)); + HitObject addedObject = null; + EditorBeatmap editorBeatmap = null; + + AddStep("add beatmap", () => + { + Child = editorBeatmap = new EditorBeatmap(new OsuBeatmap()); + editorBeatmap.HitObjectAdded += h => addedObject = h; + }); + + AddStep("add hitobject", () => editorBeatmap.Add(hitCircle)); + AddAssert("received add event", () => addedObject == hitCircle); } /// @@ -41,13 +45,15 @@ namespace osu.Game.Tests.Beatmaps 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)); + EditorBeatmap editorBeatmap = null; + AddStep("add beatmap", () => + { + Child = editorBeatmap = new EditorBeatmap(new OsuBeatmap { HitObjects = { hitCircle } }); + editorBeatmap.HitObjectRemoved += h => removedObject = h; + }); + AddStep("remove hitobject", () => editorBeatmap.Remove(editorBeatmap.HitObjects.First())); + AddAssert("received remove event", () => removedObject == hitCircle); } /// @@ -58,9 +64,7 @@ namespace osu.Game.Tests.Beatmaps public void TestInitialHitObjectStartTimeChangeEvent() { var hitCircle = new HitCircle(); - HitObject changedObject = null; - AddStep("add beatmap", () => { EditorBeatmap editorBeatmap; @@ -68,7 +72,6 @@ namespace osu.Game.Tests.Beatmaps Child = editorBeatmap = new EditorBeatmap(new OsuBeatmap { HitObjects = { hitCircle } }); editorBeatmap.HitObjectUpdated += h => changedObject = h; }); - AddStep("change start time", () => hitCircle.StartTime = 1000); AddAssert("received change event", () => changedObject == hitCircle); } @@ -82,18 +85,14 @@ namespace osu.Game.Tests.Beatmaps { EditorBeatmap editorBeatmap = null; HitObject changedObject = null; - AddStep("add beatmap", () => { Child = editorBeatmap = new EditorBeatmap(new OsuBeatmap()); editorBeatmap.HitObjectUpdated += h => changedObject = h; }); - var hitCircle = new HitCircle(); - AddStep("add object", () => editorBeatmap.Add(hitCircle)); AddAssert("event not received", () => changedObject == null); - AddStep("change start time", () => hitCircle.StartTime = 1000); AddAssert("event received", () => changedObject == hitCircle); } @@ -106,13 +105,10 @@ namespace osu.Game.Tests.Beatmaps { var hitCircle = new HitCircle(); var editorBeatmap = new EditorBeatmap(new OsuBeatmap { HitObjects = { hitCircle } }); - HitObject changedObject = null; editorBeatmap.HitObjectUpdated += h => changedObject = h; - editorBeatmap.Remove(hitCircle); Assert.That(changedObject, Is.Null); - hitCircle.StartTime = 1000; Assert.That(changedObject, Is.Null); } @@ -147,6 +143,7 @@ namespace osu.Game.Tests.Beatmaps public void TestResortWhenStartTimeChanged() { var hitCircle = new HitCircle { StartTime = 1000 }; + var editorBeatmap = new EditorBeatmap(new OsuBeatmap { HitObjects = @@ -173,7 +170,6 @@ namespace osu.Game.Tests.Beatmaps var updatedObjects = new List(); var allHitObjects = new List(); EditorBeatmap editorBeatmap = null; - AddStep("add beatmap", () => { updatedObjects.Clear(); @@ -187,11 +183,9 @@ namespace osu.Game.Tests.Beatmaps allHitObjects.Add(h); } }); - AddStep("change all start times", () => { editorBeatmap.HitObjectUpdated += h => updatedObjects.Add(h); - for (int i = 0; i < 10; i++) allHitObjects[i].StartTime += 10; }); @@ -208,7 +202,6 @@ namespace osu.Game.Tests.Beatmaps { var updatedObjects = new List(); EditorBeatmap editorBeatmap = null; - AddStep("add beatmap", () => { updatedObjects.Clear(); @@ -216,15 +209,12 @@ namespace osu.Game.Tests.Beatmaps Child = editorBeatmap = new EditorBeatmap(new OsuBeatmap()); editorBeatmap.Add(new HitCircle()); }); - AddStep("change start time twice", () => { editorBeatmap.HitObjectUpdated += h => updatedObjects.Add(h); - editorBeatmap.HitObjects[0].StartTime = 10; editorBeatmap.HitObjects[0].StartTime = 20; }); - AddAssert("only updated once", () => updatedObjects.Count == 1); } } From e328b791dfbaff551cccda920cef2cae410eeee8 Mon Sep 17 00:00:00 2001 From: Joehu Date: Sun, 13 Sep 2020 11:49:16 -0700 Subject: [PATCH 1071/1782] Add failing mod select input test --- .../Navigation/TestSceneScreenNavigation.cs | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs index 73a833c15d..0901976af2 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs @@ -6,9 +6,11 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics.Containers; using osu.Framework.Screens; +using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Overlays; using osu.Game.Overlays.Mods; +using osu.Game.Overlays.Toolbar; using osu.Game.Screens.Play; using osu.Game.Screens.Select; using osu.Game.Tests.Beatmaps.IO; @@ -143,6 +145,29 @@ namespace osu.Game.Tests.Visual.Navigation AddUntilStep("Track was restarted", () => Game.MusicController.IsPlaying); } + [Test] + public void TestModSelectInput() + { + TestSongSelect songSelect = null; + + PushAndConfirm(() => songSelect = new TestSongSelect()); + + AddStep("Show mods overlay", () => songSelect.ModSelectOverlay.Show()); + + AddStep("Change ruleset to osu!taiko", () => + { + InputManager.PressKey(Key.ControlLeft); + InputManager.PressKey(Key.Number2); + + InputManager.ReleaseKey(Key.ControlLeft); + InputManager.ReleaseKey(Key.Number2); + }); + + AddAssert("Ruleset changed to osu!taiko", () => Game.Toolbar.ChildrenOfType().Single().Current.Value.ID == 1); + + AddAssert("Mods overlay still visible", () => songSelect.ModSelectOverlay.State.Value == Visibility.Visible); + } + private void pushEscape() => AddStep("Press escape", () => pressAndRelease(Key.Escape)); From 4dacdb9994eecdc9043b19706b6683be1d892025 Mon Sep 17 00:00:00 2001 From: Joehu Date: Sun, 13 Sep 2020 11:50:21 -0700 Subject: [PATCH 1072/1782] Fix mod select overlay absorbing input from toolbar ruleset selector --- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 8a5e4d2683..4eb4fc6501 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -390,6 +390,9 @@ namespace osu.Game.Overlays.Mods protected override bool OnKeyDown(KeyDownEvent e) { + // don't absorb control as ToolbarRulesetSelector uses control + number to navigate + if (e.ControlPressed) return false; + switch (e.Key) { case Key.Number1: From 9f1a231f929677d806f0a1ecb4afb9499dda66a0 Mon Sep 17 00:00:00 2001 From: Shivam Date: Sun, 13 Sep 2020 21:03:46 +0200 Subject: [PATCH 1073/1782] Add anchor to the fillflowcontainer in TeamDisplay --- osu.Game.Tournament/Screens/Gameplay/Components/TeamDisplay.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Tournament/Screens/Gameplay/Components/TeamDisplay.cs b/osu.Game.Tournament/Screens/Gameplay/Components/TeamDisplay.cs index b01c93ae03..1b4a769b84 100644 --- a/osu.Game.Tournament/Screens/Gameplay/Components/TeamDisplay.cs +++ b/osu.Game.Tournament/Screens/Gameplay/Components/TeamDisplay.cs @@ -62,6 +62,8 @@ namespace osu.Game.Tournament.Screens.Gameplay.Components AutoSizeAxes = Axes.Both, Direction = FillDirection.Horizontal, Spacing = new Vector2(5), + Origin = anchor, + Anchor = anchor, Children = new Drawable[] { new DrawableTeamHeader(colour) From 692f2c8489751852c0ed717d8f9373f3a34c5ffd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 14 Sep 2020 14:45:49 +0900 Subject: [PATCH 1074/1782] Simplify debounced update pathway --- osu.Game/Screens/Edit/Editor.cs | 4 + osu.Game/Screens/Edit/EditorBeatmap.cs | 123 ++++++++++++------------- 2 files changed, 63 insertions(+), 64 deletions(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 64365bd512..71340041f0 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -459,10 +459,14 @@ namespace osu.Game.Screens.Edit foreach (var h in objects) h.StartTime += timeOffset; + changeHandler.BeginChange(); + editorBeatmap.SelectedHitObjects.Clear(); editorBeatmap.AddRange(objects); editorBeatmap.SelectedHitObjects.AddRange(objects); + + changeHandler.EndChange(); } protected void Undo() => changeHandler.RestoreState(-1); diff --git a/osu.Game/Screens/Edit/EditorBeatmap.cs b/osu.Game/Screens/Edit/EditorBeatmap.cs index 5272530228..3876fb0903 100644 --- a/osu.Game/Screens/Edit/EditorBeatmap.cs +++ b/osu.Game/Screens/Edit/EditorBeatmap.cs @@ -9,7 +9,6 @@ using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Framework.Threading; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.Timing; @@ -68,47 +67,6 @@ namespace osu.Game.Screens.Edit trackStartTime(obj); } - private readonly HashSet pendingUpdates = new HashSet(); - private ScheduledDelegate scheduledUpdate; - - /// - /// Updates a , invoking and re-processing the beatmap. - /// - /// The to update. - public void UpdateHitObject([NotNull] HitObject hitObject) => updateHitObject(hitObject, false); - - private void updateHitObject([CanBeNull] HitObject hitObject, bool silent, bool performAdd = false) - { - if (hitObject != null) - pendingUpdates.Add(hitObject); - - if (scheduledUpdate?.Completed == false) return; - - scheduledUpdate = Schedule(() => - { - beatmapProcessor?.PreProcess(); - - foreach (var obj in pendingUpdates) - obj.ApplyDefaults(ControlPointInfo, BeatmapInfo.BaseDifficulty); - - beatmapProcessor?.PostProcess(); - - if (performAdd) - { - foreach (var obj in pendingUpdates) - HitObjectAdded?.Invoke(obj); - } - - if (!silent) - { - foreach (var obj in pendingUpdates) - HitObjectUpdated?.Invoke(obj); - } - - pendingUpdates.Clear(); - }); - } - public BeatmapInfo BeatmapInfo { get => PlayableBeatmap.BeatmapInfo; @@ -131,6 +89,8 @@ namespace osu.Game.Screens.Edit private IList mutableHitObjects => (IList)PlayableBeatmap.HitObjects; + private readonly HashSet pendingUpdates = new HashSet(); + /// /// Adds a collection of s to this . /// @@ -165,23 +125,40 @@ namespace osu.Game.Screens.Edit trackStartTime(hitObject); mutableHitObjects.Insert(index, hitObject); - updateHitObject(hitObject, true, true); + + // must be run after any change to hitobject ordering + beatmapProcessor?.PreProcess(); + processHitObject(hitObject); + beatmapProcessor?.PostProcess(); + + HitObjectAdded?.Invoke(hitObject); + } + + /// + /// Updates a , invoking and re-processing the beatmap. + /// + /// The to update. + public void UpdateHitObject([NotNull] HitObject hitObject) + { + pendingUpdates.Add(hitObject); } /// /// Removes a from this . /// - /// The to add. + /// All to remove. /// True if the has been removed, false otherwise. - public bool Remove(HitObject hitObject) + public void Remove(params HitObject[] hitObjects) { - int index = FindIndex(hitObject); + foreach (var h in hitObjects) + { + int index = FindIndex(h); - if (index == -1) - return false; + if (index == -1) + continue; - RemoveAt(index); - return true; + RemoveAt(index); + } } /// @@ -203,11 +180,14 @@ namespace osu.Game.Screens.Edit var bindable = startTimeBindables[hitObject]; bindable.UnbindAll(); - startTimeBindables.Remove(hitObject); - HitObjectRemoved?.Invoke(hitObject); - updateHitObject(null, true); + // must be run after any change to hitobject ordering + beatmapProcessor?.PreProcess(); + processHitObject(hitObject); + beatmapProcessor?.PostProcess(); + + HitObjectRemoved?.Invoke(hitObject); } /// @@ -215,20 +195,35 @@ namespace osu.Game.Screens.Edit /// public void Clear() { - var removed = HitObjects.ToList(); + var removable = HitObjects.ToList(); - mutableHitObjects.Clear(); - - foreach (var b in startTimeBindables) - b.Value.UnbindAll(); - startTimeBindables.Clear(); - - foreach (var h in removed) - HitObjectRemoved?.Invoke(h); - - updateHitObject(null, true); + foreach (var h in removable) + Remove(h); } + protected override void Update() + { + base.Update(); + + // debounce updates as they are common and may come from input events, which can run needlessly many times per update frame. + if (pendingUpdates.Count > 0) + { + beatmapProcessor?.PreProcess(); + + foreach (var hitObject in pendingUpdates) + { + processHitObject(hitObject); + HitObjectUpdated?.Invoke(hitObject); + } + + pendingUpdates.Clear(); + + beatmapProcessor?.PostProcess(); + } + } + + private void processHitObject(HitObject hitObject) => hitObject.ApplyDefaults(ControlPointInfo, BeatmapInfo.BaseDifficulty); + private void trackStartTime(HitObject hitObject) { startTimeBindables[hitObject] = hitObject.StartTimeBindable.GetBoundCopy(); From da02ee88283a1c9012d4a26dbb6aff5385280671 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 14 Sep 2020 15:26:57 +0900 Subject: [PATCH 1075/1782] Add ability to create a TestBeatmap with no HitObjects --- osu.Game/Tests/Beatmaps/TestBeatmap.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Tests/Beatmaps/TestBeatmap.cs b/osu.Game/Tests/Beatmaps/TestBeatmap.cs index a375a17bcf..87b77f4616 100644 --- a/osu.Game/Tests/Beatmaps/TestBeatmap.cs +++ b/osu.Game/Tests/Beatmaps/TestBeatmap.cs @@ -15,14 +15,16 @@ namespace osu.Game.Tests.Beatmaps { public class TestBeatmap : Beatmap { - public TestBeatmap(RulesetInfo ruleset) + public TestBeatmap(RulesetInfo ruleset, bool withHitObjects = true) { var baseBeatmap = CreateBeatmap(); BeatmapInfo = baseBeatmap.BeatmapInfo; ControlPointInfo = baseBeatmap.ControlPointInfo; Breaks = baseBeatmap.Breaks; - HitObjects = baseBeatmap.HitObjects; + + if (withHitObjects) + HitObjects = baseBeatmap.HitObjects; BeatmapInfo.Ruleset = ruleset; BeatmapInfo.RulesetID = ruleset.ID ?? 0; From 0ef4dfc192a6725c9e62c7871bcb749bfdd3bb5d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 14 Sep 2020 15:27:08 +0900 Subject: [PATCH 1076/1782] Move more logic to base EditorTestScene --- .../Editing/TestSceneEditorChangeStates.cs | 75 ++++++------------- osu.Game/Tests/Visual/EditorTestScene.cs | 27 ++++++- 2 files changed, 49 insertions(+), 53 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorChangeStates.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorChangeStates.cs index dfe1e434dc..ab53f4fd93 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorChangeStates.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorChangeStates.cs @@ -1,41 +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 System.Linq; using NUnit.Framework; -using osu.Framework.Testing; using osu.Game.Rulesets; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Objects; -using osu.Game.Screens.Edit; namespace osu.Game.Tests.Visual.Editing { public class TestSceneEditorChangeStates : EditorTestScene { - private EditorBeatmap editorBeatmap; - protected override Ruleset CreateEditorRuleset() => new OsuRuleset(); - protected new TestEditor Editor => (TestEditor)base.Editor; - - public override void SetUpSteps() - { - base.SetUpSteps(); - - AddStep("get beatmap", () => editorBeatmap = Editor.ChildrenOfType().Single()); - } - [Test] public void TestSelectedObjects() { HitCircle obj = null; - AddStep("add hitobject", () => editorBeatmap.Add(obj = new HitCircle { StartTime = 1000 })); - AddStep("select hitobject", () => editorBeatmap.SelectedHitObjects.Add(obj)); - AddAssert("confirm 1 selected", () => editorBeatmap.SelectedHitObjects.Count == 1); - AddStep("deselect hitobject", () => editorBeatmap.SelectedHitObjects.Remove(obj)); - AddAssert("confirm 0 selected", () => editorBeatmap.SelectedHitObjects.Count == 0); + AddStep("add hitobject", () => EditorBeatmap.Add(obj = new HitCircle { StartTime = 1000 })); + AddStep("select hitobject", () => EditorBeatmap.SelectedHitObjects.Add(obj)); + AddAssert("confirm 1 selected", () => EditorBeatmap.SelectedHitObjects.Count == 1); + AddStep("deselect hitobject", () => EditorBeatmap.SelectedHitObjects.Remove(obj)); + AddAssert("confirm 0 selected", () => EditorBeatmap.SelectedHitObjects.Count == 0); } [Test] @@ -43,11 +29,11 @@ namespace osu.Game.Tests.Visual.Editing { int hitObjectCount = 0; - AddStep("get initial state", () => hitObjectCount = editorBeatmap.HitObjects.Count); + AddStep("get initial state", () => hitObjectCount = EditorBeatmap.HitObjects.Count); addUndoSteps(); - AddAssert("no change occurred", () => hitObjectCount == editorBeatmap.HitObjects.Count); + AddAssert("no change occurred", () => hitObjectCount == EditorBeatmap.HitObjects.Count); AddAssert("no unsaved changes", () => !Editor.HasUnsavedChanges); } @@ -56,11 +42,11 @@ namespace osu.Game.Tests.Visual.Editing { int hitObjectCount = 0; - AddStep("get initial state", () => hitObjectCount = editorBeatmap.HitObjects.Count); + AddStep("get initial state", () => hitObjectCount = EditorBeatmap.HitObjects.Count); addRedoSteps(); - AddAssert("no change occurred", () => hitObjectCount == editorBeatmap.HitObjects.Count); + AddAssert("no change occurred", () => hitObjectCount == EditorBeatmap.HitObjects.Count); AddAssert("no unsaved changes", () => !Editor.HasUnsavedChanges); } @@ -73,11 +59,11 @@ namespace osu.Game.Tests.Visual.Editing AddStep("bind removal", () => { - editorBeatmap.HitObjectAdded += h => addedObject = h; - editorBeatmap.HitObjectRemoved += h => removedObject = h; + EditorBeatmap.HitObjectAdded += h => addedObject = h; + EditorBeatmap.HitObjectRemoved += h => removedObject = h; }); - AddStep("add hitobject", () => editorBeatmap.Add(expectedObject = new HitCircle { StartTime = 1000 })); + AddStep("add hitobject", () => EditorBeatmap.Add(expectedObject = new HitCircle { StartTime = 1000 })); AddAssert("hitobject added", () => addedObject == expectedObject); AddAssert("unsaved changes", () => Editor.HasUnsavedChanges); @@ -95,11 +81,11 @@ namespace osu.Game.Tests.Visual.Editing AddStep("bind removal", () => { - editorBeatmap.HitObjectAdded += h => addedObject = h; - editorBeatmap.HitObjectRemoved += h => removedObject = h; + EditorBeatmap.HitObjectAdded += h => addedObject = h; + EditorBeatmap.HitObjectRemoved += h => removedObject = h; }); - AddStep("add hitobject", () => editorBeatmap.Add(expectedObject = new HitCircle { StartTime = 1000 })); + AddStep("add hitobject", () => EditorBeatmap.Add(expectedObject = new HitCircle { StartTime = 1000 })); addUndoSteps(); AddStep("reset variables", () => @@ -117,7 +103,7 @@ namespace osu.Game.Tests.Visual.Editing [Test] public void TestAddObjectThenSaveHasNoUnsavedChanges() { - AddStep("add hitobject", () => editorBeatmap.Add(new HitCircle { StartTime = 1000 })); + AddStep("add hitobject", () => EditorBeatmap.Add(new HitCircle { StartTime = 1000 })); AddAssert("unsaved changes", () => Editor.HasUnsavedChanges); AddStep("save changes", () => Editor.Save()); @@ -133,12 +119,12 @@ namespace osu.Game.Tests.Visual.Editing AddStep("bind removal", () => { - editorBeatmap.HitObjectAdded += h => addedObject = h; - editorBeatmap.HitObjectRemoved += h => removedObject = h; + EditorBeatmap.HitObjectAdded += h => addedObject = h; + EditorBeatmap.HitObjectRemoved += h => removedObject = h; }); - AddStep("add hitobject", () => editorBeatmap.Add(expectedObject = new HitCircle { StartTime = 1000 })); - AddStep("remove object", () => editorBeatmap.Remove(expectedObject)); + AddStep("add hitobject", () => EditorBeatmap.Add(expectedObject = new HitCircle { StartTime = 1000 })); + AddStep("remove object", () => EditorBeatmap.Remove(expectedObject)); AddStep("reset variables", () => { addedObject = null; @@ -160,12 +146,12 @@ namespace osu.Game.Tests.Visual.Editing AddStep("bind removal", () => { - editorBeatmap.HitObjectAdded += h => addedObject = h; - editorBeatmap.HitObjectRemoved += h => removedObject = h; + EditorBeatmap.HitObjectAdded += h => addedObject = h; + EditorBeatmap.HitObjectRemoved += h => removedObject = h; }); - AddStep("add hitobject", () => editorBeatmap.Add(expectedObject = new HitCircle { StartTime = 1000 })); - AddStep("remove object", () => editorBeatmap.Remove(expectedObject)); + AddStep("add hitobject", () => EditorBeatmap.Add(expectedObject = new HitCircle { StartTime = 1000 })); + AddStep("remove object", () => EditorBeatmap.Remove(expectedObject)); addUndoSteps(); AddStep("reset variables", () => @@ -183,18 +169,5 @@ namespace osu.Game.Tests.Visual.Editing private void addUndoSteps() => AddStep("undo", () => Editor.Undo()); private void addRedoSteps() => AddStep("redo", () => Editor.Redo()); - - protected override Editor CreateEditor() => new TestEditor(); - - protected class TestEditor : Editor - { - public new void Undo() => base.Undo(); - - public new void Redo() => base.Redo(); - - public new void Save() => base.Save(); - - public new bool HasUnsavedChanges => base.HasUnsavedChanges; - } } } diff --git a/osu.Game/Tests/Visual/EditorTestScene.cs b/osu.Game/Tests/Visual/EditorTestScene.cs index cd08f4712a..8f76f247cf 100644 --- a/osu.Game/Tests/Visual/EditorTestScene.cs +++ b/osu.Game/Tests/Visual/EditorTestScene.cs @@ -14,7 +14,11 @@ namespace osu.Game.Tests.Visual { public abstract class EditorTestScene : ScreenTestScene { - protected Editor Editor { get; private set; } + protected EditorBeatmap EditorBeatmap; + + protected TestEditor Editor { get; private set; } + + protected EditorClock EditorClock { get; private set; } [BackgroundDependencyLoader] private void load() @@ -29,6 +33,8 @@ namespace osu.Game.Tests.Visual AddStep("load editor", () => LoadScreen(Editor = CreateEditor())); AddUntilStep("wait for editor to load", () => Editor.ChildrenOfType().FirstOrDefault()?.IsLoaded == true && Editor.ChildrenOfType().FirstOrDefault()?.IsLoaded == true); + AddStep("get beatmap", () => EditorBeatmap = Editor.ChildrenOfType().Single()); + AddStep("get clock", () => EditorClock = Editor.ChildrenOfType().Single()); } /// @@ -39,6 +45,23 @@ namespace osu.Game.Tests.Visual protected sealed override Ruleset CreateRuleset() => CreateEditorRuleset(); - protected virtual Editor CreateEditor() => new Editor(); + protected virtual TestEditor CreateEditor() => new TestEditor(); + + protected class TestEditor : Editor + { + public new void Undo() => base.Undo(); + + public new void Redo() => base.Redo(); + + public new void Save() => base.Save(); + + public new void Cut() => base.Cut(); + + public new void Copy() => base.Copy(); + + public new void Paste() => base.Paste(); + + public new bool HasUnsavedChanges => base.HasUnsavedChanges; + } } } From 66faae2a6b9634fc8c7ed3502ae88e22032e480e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 14 Sep 2020 15:27:16 +0900 Subject: [PATCH 1077/1782] Add basic clipboards tests --- .../Editing/TestSceneEditorClipboard.cs | 96 +++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 osu.Game.Tests/Visual/Editing/TestSceneEditorClipboard.cs diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorClipboard.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorClipboard.cs new file mode 100644 index 0000000000..284127d66b --- /dev/null +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorClipboard.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 System.Linq; +using NUnit.Framework; +using osu.Game.Beatmaps; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Tests.Beatmaps; + +namespace osu.Game.Tests.Visual.Editing +{ + public class TestSceneEditorClipboard : EditorTestScene + { + protected override Ruleset CreateEditorRuleset() => new OsuRuleset(); + + protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new TestBeatmap(ruleset, false); + + [Test] + public void TestCutRemovesObjects() + { + var addedObject = new HitCircle { StartTime = 1000 }; + + AddStep("add hitobject", () => EditorBeatmap.Add(addedObject)); + + AddStep("select added object", () => EditorBeatmap.SelectedHitObjects.Add(addedObject)); + + AddStep("cut hitobject", () => Editor.Cut()); + + AddAssert("no hitobjects in beatmap", () => EditorBeatmap.HitObjects.Count == 0); + } + + [TestCase(1000)] + [TestCase(2000)] + public void TestCutPaste(double newTime) + { + var addedObject = new HitCircle { StartTime = 1000 }; + + AddStep("add hitobject", () => EditorBeatmap.Add(addedObject)); + + AddStep("select added object", () => EditorBeatmap.SelectedHitObjects.Add(addedObject)); + + AddStep("cut hitobject", () => Editor.Cut()); + + AddStep("move forward in time", () => EditorClock.Seek(newTime)); + + AddStep("paste hitobject", () => Editor.Paste()); + + AddAssert("is one object", () => EditorBeatmap.HitObjects.Count == 1); + + AddAssert("new object selected", () => EditorBeatmap.SelectedHitObjects.Single().StartTime == newTime); + } + + [Test] + public void TestCopyPaste() + { + var addedObject = new HitCircle { StartTime = 1000 }; + + AddStep("add hitobject", () => EditorBeatmap.Add(addedObject)); + + AddStep("select added object", () => EditorBeatmap.SelectedHitObjects.Add(addedObject)); + + AddStep("copy hitobject", () => Editor.Copy()); + + AddStep("move forward in time", () => EditorClock.Seek(2000)); + + AddStep("paste hitobject", () => Editor.Paste()); + + AddAssert("are two objects", () => EditorBeatmap.HitObjects.Count == 2); + + AddAssert("new object selected", () => EditorBeatmap.SelectedHitObjects.Single().StartTime == 2000); + } + + [Test] + public void TestCutNothing() + { + AddStep("cut hitobject", () => Editor.Cut()); + AddAssert("are no objects", () => EditorBeatmap.HitObjects.Count == 0); + } + + [Test] + public void TestCopyNothing() + { + AddStep("copy hitobject", () => Editor.Copy()); + AddAssert("are no objects", () => EditorBeatmap.HitObjects.Count == 0); + } + + [Test] + public void TestPasteNothing() + { + AddStep("paste hitobject", () => Editor.Paste()); + AddAssert("are no objects", () => EditorBeatmap.HitObjects.Count == 0); + } + } +} From 75e4f224e5a1d0a2f21db48d711dce86f1ee4239 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 14 Sep 2020 15:47:04 +0900 Subject: [PATCH 1078/1782] Add back accidentally removed remove --- 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 0a3c9072cf..60e25b01df 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -160,6 +160,8 @@ namespace osu.Game.Screens.Edit.Compose.Components /// The blueprint. internal void HandleDeselected(SelectionBlueprint blueprint) { + selectedBlueprints.Remove(blueprint); + EditorBeatmap.SelectedHitObjects.Remove(blueprint.HitObject); UpdateVisibility(); From b7a06524fb94c77f0c1719047b0f58466ab2a52a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 14 Sep 2020 15:47:10 +0900 Subject: [PATCH 1079/1782] Update comment to make more sense --- osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index 60e25b01df..f95bf350b6 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -147,7 +147,7 @@ namespace osu.Game.Screens.Edit.Compose.Components { selectedBlueprints.Add(blueprint); - // need to check this as well, as there are potentially multiple SelectionHandlers and the above check is not enough. + // there are potentially multiple SelectionHandlers active, but we only want to add hitobjects to the selected list once. if (!EditorBeatmap.SelectedHitObjects.Contains(blueprint.HitObject)) EditorBeatmap.SelectedHitObjects.Add(blueprint.HitObject); From 3e7f70e225625429fd3cdc1b1d68b9746b2abd09 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 14 Sep 2020 17:27:25 +0900 Subject: [PATCH 1080/1782] Add failing test covering post-converted json serializing --- .../Beatmaps/Formats/OsuJsonDecoderTest.cs | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs index b4c78ce273..e97c83e2c2 100644 --- a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs @@ -11,6 +11,8 @@ using osu.Game.Beatmaps.Formats; using osu.Game.IO; using osu.Game.IO.Serialization; using osu.Game.Rulesets.Objects.Types; +using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Osu.Beatmaps; using osu.Game.Rulesets.Scoring; using osu.Game.Tests.Resources; using osuTK; @@ -90,6 +92,38 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.AreEqual(2, difficulty.SliderTickRate); } + [Test] + public void TestDecodePostConverted() + { + var converted = new OsuBeatmapConverter(decodeAsJson(normal), new OsuRuleset()).Convert(); + + var processor = new OsuBeatmapProcessor(converted); + + processor.PreProcess(); + foreach (var o in converted.HitObjects) + o.ApplyDefaults(converted.ControlPointInfo, converted.BeatmapInfo.BaseDifficulty); + processor.PostProcess(); + + var beatmap = converted.Serialize().Deserialize(); + + var curveData = beatmap.HitObjects[0] as IHasPathWithRepeats; + var positionData = beatmap.HitObjects[0] as IHasPosition; + + Assert.IsNotNull(positionData); + Assert.IsNotNull(curveData); + Assert.AreEqual(90, curveData.Path.Distance); + Assert.AreEqual(new Vector2(192, 168), positionData.Position); + Assert.AreEqual(956, beatmap.HitObjects[0].StartTime); + Assert.IsTrue(beatmap.HitObjects[0].Samples.Any(s => s.Name == HitSampleInfo.HIT_NORMAL)); + + positionData = beatmap.HitObjects[1] as IHasPosition; + + Assert.IsNotNull(positionData); + Assert.AreEqual(new Vector2(304, 56), positionData.Position); + Assert.AreEqual(1285, beatmap.HitObjects[1].StartTime); + Assert.IsTrue(beatmap.HitObjects[1].Samples.Any(s => s.Name == HitSampleInfo.HIT_CLAP)); + } + [Test] public void TestDecodeHitObjects() { @@ -100,6 +134,7 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.IsNotNull(positionData); Assert.IsNotNull(curveData); + Assert.AreEqual(90, curveData.Path.Distance); Assert.AreEqual(new Vector2(192, 168), positionData.Position); Assert.AreEqual(956, beatmap.HitObjects[0].StartTime); Assert.IsTrue(beatmap.HitObjects[0].Samples.Any(s => s.Name == HitSampleInfo.HIT_NORMAL)); From a8b405791a76c320dfd397ac7be261dac07f2c7a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 14 Sep 2020 17:08:22 +0900 Subject: [PATCH 1081/1782] Fix non-convert slider and spinner serialization --- osu.Game.Rulesets.Osu/Objects/Slider.cs | 9 +++++++-- osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs | 2 ++ osu.Game/Rulesets/Objects/PathControlPoint.cs | 3 +++ osu.Game/Rulesets/Objects/Types/IHasDuration.cs | 3 --- 4 files changed, 12 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index 705e88040f..51f6a44a87 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -7,6 +7,7 @@ using System.Collections.Generic; using osu.Game.Rulesets.Objects; using System.Linq; using System.Threading; +using Newtonsoft.Json; using osu.Framework.Caching; using osu.Game.Audio; using osu.Game.Beatmaps; @@ -21,6 +22,7 @@ namespace osu.Game.Rulesets.Osu.Objects { public double EndTime => StartTime + this.SpanCount() * Path.Distance / Velocity; + [JsonIgnore] public double Duration { get => EndTime - StartTime; @@ -112,8 +114,11 @@ namespace osu.Game.Rulesets.Osu.Objects /// public double TickDistanceMultiplier = 1; - public HitCircle HeadCircle; - public SliderTailCircle TailCircle; + [JsonIgnore] + public HitCircle HeadCircle { get; protected set; } + + [JsonIgnore] + public SliderTailCircle TailCircle { get; protected set; } public Slider() { diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs index c522dc623c..36b421586e 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs @@ -3,6 +3,7 @@ using osu.Game.Rulesets.Objects.Types; using System.Collections.Generic; +using Newtonsoft.Json; using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; @@ -29,6 +30,7 @@ namespace osu.Game.Rulesets.Objects.Legacy public List> NodeSamples { get; set; } public int RepeatCount { get; set; } + [JsonIgnore] public double Duration { get => this.SpanCount() * Distance / Velocity; diff --git a/osu.Game/Rulesets/Objects/PathControlPoint.cs b/osu.Game/Rulesets/Objects/PathControlPoint.cs index 0336f94313..f11917f4f4 100644 --- a/osu.Game/Rulesets/Objects/PathControlPoint.cs +++ b/osu.Game/Rulesets/Objects/PathControlPoint.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.Bindables; using osu.Game.Rulesets.Objects.Types; using osuTK; @@ -13,12 +14,14 @@ namespace osu.Game.Rulesets.Objects /// /// The position of this . /// + [JsonProperty] 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. /// + [JsonProperty] public readonly Bindable Type = new Bindable(); /// diff --git a/osu.Game/Rulesets/Objects/Types/IHasDuration.cs b/osu.Game/Rulesets/Objects/Types/IHasDuration.cs index 185fd5977b..b558273650 100644 --- a/osu.Game/Rulesets/Objects/Types/IHasDuration.cs +++ b/osu.Game/Rulesets/Objects/Types/IHasDuration.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 Newtonsoft.Json; - namespace osu.Game.Rulesets.Objects.Types { /// @@ -28,7 +26,6 @@ namespace osu.Game.Rulesets.Objects.Types /// /// The duration of the HitObject. /// - [JsonIgnore] new double Duration { get; set; } } } From 36a234e5d95c283ef1cbeed754eefe4e2bcd9874 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 14 Sep 2020 17:43:27 +0900 Subject: [PATCH 1082/1782] Add slider specific clipboard test --- .../Editing/TestSceneEditorClipboard.cs | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorClipboard.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorClipboard.cs index 284127d66b..808d471e36 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorClipboard.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorClipboard.cs @@ -5,9 +5,12 @@ using System.Linq; using NUnit.Framework; using osu.Game.Beatmaps; using osu.Game.Rulesets; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Tests.Beatmaps; +using osuTK; namespace osu.Game.Tests.Visual.Editing { @@ -52,6 +55,39 @@ namespace osu.Game.Tests.Visual.Editing AddAssert("new object selected", () => EditorBeatmap.SelectedHitObjects.Single().StartTime == newTime); } + [Test] + public void TestCutPasteSlider() + { + var addedObject = new Slider + { + StartTime = 1000, + Path = new SliderPath + { + ControlPoints = + { + new PathControlPoint(), + new PathControlPoint(new Vector2(100, 0), PathType.Bezier) + } + } + }; + + AddStep("add hitobject", () => EditorBeatmap.Add(addedObject)); + + AddStep("select added object", () => EditorBeatmap.SelectedHitObjects.Add(addedObject)); + + AddStep("cut hitobject", () => Editor.Cut()); + + AddStep("paste hitobject", () => Editor.Paste()); + + AddAssert("is one object", () => EditorBeatmap.HitObjects.Count == 1); + + AddAssert("path matches", () => + { + var path = ((Slider)EditorBeatmap.HitObjects.Single()).Path; + return path.ControlPoints.Count == 2 && path.ControlPoints.SequenceEqual(addedObject.Path.ControlPoints); + }); + } + [Test] public void TestCopyPaste() { From dafbeda68136ad134f5df3e09e7c93d902717264 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 14 Sep 2020 17:48:29 +0900 Subject: [PATCH 1083/1782] Add test coverage for spinners too --- .../Editing/TestSceneEditorClipboard.cs | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorClipboard.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorClipboard.cs index 808d471e36..29046c82a6 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorClipboard.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorClipboard.cs @@ -88,6 +88,28 @@ namespace osu.Game.Tests.Visual.Editing }); } + [Test] + public void TestCutPasteSpinner() + { + var addedObject = new Spinner + { + StartTime = 1000, + Duration = 5000 + }; + + AddStep("add hitobject", () => EditorBeatmap.Add(addedObject)); + + AddStep("select added object", () => EditorBeatmap.SelectedHitObjects.Add(addedObject)); + + AddStep("cut hitobject", () => Editor.Cut()); + + AddStep("paste hitobject", () => Editor.Paste()); + + AddAssert("is one object", () => EditorBeatmap.HitObjects.Count == 1); + + AddAssert("duration matches", () => ((Spinner)EditorBeatmap.HitObjects.Single()).Duration == 5000); + } + [Test] public void TestCopyPaste() { From 70bc0b2bd025e234880bca5408b3ea6116ef8d7d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 14 Sep 2020 17:52:59 +0900 Subject: [PATCH 1084/1782] Add back inadvertently removed spacing --- .../Beatmaps/TestSceneEditorBeatmap.cs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/osu.Game.Tests/Beatmaps/TestSceneEditorBeatmap.cs b/osu.Game.Tests/Beatmaps/TestSceneEditorBeatmap.cs index 902f0d7c23..bf5b517603 100644 --- a/osu.Game.Tests/Beatmaps/TestSceneEditorBeatmap.cs +++ b/osu.Game.Tests/Beatmaps/TestSceneEditorBeatmap.cs @@ -64,7 +64,9 @@ namespace osu.Game.Tests.Beatmaps public void TestInitialHitObjectStartTimeChangeEvent() { var hitCircle = new HitCircle(); + HitObject changedObject = null; + AddStep("add beatmap", () => { EditorBeatmap editorBeatmap; @@ -72,6 +74,7 @@ namespace osu.Game.Tests.Beatmaps Child = editorBeatmap = new EditorBeatmap(new OsuBeatmap { HitObjects = { hitCircle } }); editorBeatmap.HitObjectUpdated += h => changedObject = h; }); + AddStep("change start time", () => hitCircle.StartTime = 1000); AddAssert("received change event", () => changedObject == hitCircle); } @@ -85,14 +88,18 @@ namespace osu.Game.Tests.Beatmaps { EditorBeatmap editorBeatmap = null; HitObject changedObject = null; + AddStep("add beatmap", () => { Child = editorBeatmap = new EditorBeatmap(new OsuBeatmap()); editorBeatmap.HitObjectUpdated += h => changedObject = h; }); + var hitCircle = new HitCircle(); + AddStep("add object", () => editorBeatmap.Add(hitCircle)); AddAssert("event not received", () => changedObject == null); + AddStep("change start time", () => hitCircle.StartTime = 1000); AddAssert("event received", () => changedObject == hitCircle); } @@ -105,10 +112,13 @@ namespace osu.Game.Tests.Beatmaps { var hitCircle = new HitCircle(); var editorBeatmap = new EditorBeatmap(new OsuBeatmap { HitObjects = { hitCircle } }); + HitObject changedObject = null; editorBeatmap.HitObjectUpdated += h => changedObject = h; + editorBeatmap.Remove(hitCircle); Assert.That(changedObject, Is.Null); + hitCircle.StartTime = 1000; Assert.That(changedObject, Is.Null); } @@ -170,6 +180,7 @@ namespace osu.Game.Tests.Beatmaps var updatedObjects = new List(); var allHitObjects = new List(); EditorBeatmap editorBeatmap = null; + AddStep("add beatmap", () => { updatedObjects.Clear(); @@ -183,9 +194,11 @@ namespace osu.Game.Tests.Beatmaps allHitObjects.Add(h); } }); + AddStep("change all start times", () => { editorBeatmap.HitObjectUpdated += h => updatedObjects.Add(h); + for (int i = 0; i < 10; i++) allHitObjects[i].StartTime += 10; }); @@ -202,6 +215,7 @@ namespace osu.Game.Tests.Beatmaps { var updatedObjects = new List(); EditorBeatmap editorBeatmap = null; + AddStep("add beatmap", () => { updatedObjects.Clear(); @@ -209,12 +223,15 @@ namespace osu.Game.Tests.Beatmaps Child = editorBeatmap = new EditorBeatmap(new OsuBeatmap()); editorBeatmap.Add(new HitCircle()); }); + AddStep("change start time twice", () => { editorBeatmap.HitObjectUpdated += h => updatedObjects.Add(h); + editorBeatmap.HitObjects[0].StartTime = 10; editorBeatmap.HitObjects[0].StartTime = 20; }); + AddAssert("only updated once", () => updatedObjects.Count == 1); } } From daf54c7eb962c6abe35c04202da6c7c9d71e12b7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 14 Sep 2020 17:55:41 +0900 Subject: [PATCH 1085/1782] Revert EditorBeatmap.Remove API --- osu.Game/Screens/Edit/EditorBeatmap.cs | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/osu.Game/Screens/Edit/EditorBeatmap.cs b/osu.Game/Screens/Edit/EditorBeatmap.cs index 3876fb0903..3a9bd85b0f 100644 --- a/osu.Game/Screens/Edit/EditorBeatmap.cs +++ b/osu.Game/Screens/Edit/EditorBeatmap.cs @@ -146,19 +146,17 @@ namespace osu.Game.Screens.Edit /// /// Removes a from this . /// - /// All to remove. + /// The to remove. /// True if the has been removed, false otherwise. - public void Remove(params HitObject[] hitObjects) + public bool Remove(HitObject hitObject) { - foreach (var h in hitObjects) - { - int index = FindIndex(h); + int index = FindIndex(hitObject); - if (index == -1) - continue; + if (index == -1) + return false; - RemoveAt(index); - } + RemoveAt(index); + return true; } /// @@ -195,9 +193,7 @@ namespace osu.Game.Screens.Edit /// public void Clear() { - var removable = HitObjects.ToList(); - - foreach (var h in removable) + foreach (var h in HitObjects.ToArray()) Remove(h); } From 91d37e0459e37f1caacfb7c623b05cd83d454848 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 14 Sep 2020 20:17:00 +0900 Subject: [PATCH 1086/1782] Fix typo in comment --- osu.Game/Skinning/SkinManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index eacfdaec4a..303c59b05e 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -98,7 +98,7 @@ namespace osu.Game.Skinning return item.ToString().ComputeSHA2Hash(); } - // if there was no creator, the ToString above would give the filename, which along isn't really enough to base any decisions on. + // if there was no creator, the ToString above would give the filename, which alone isn't really enough to base any decisions on. return base.ComputeHash(item, reader); } From 1884e0167bdb5bfa1d2769e00874520b39af8c71 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 14 Sep 2020 23:31:03 +0900 Subject: [PATCH 1087/1782] Eagerly populate skin metadata to allow usage in hashing computation --- osu.Game/Database/ArchiveModelManager.cs | 3 +-- osu.Game/Skinning/SkinManager.cs | 20 +++++++++++++++----- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index e87ab8167a..76bc4f7755 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -321,11 +321,10 @@ namespace osu.Game.Database LogForModel(item, "Beginning import..."); item.Files = archive != null ? createFileInfos(archive, Files) : new List(); + item.Hash = ComputeHash(item, archive); await Populate(item, archive, cancellationToken); - item.Hash = ComputeHash(item, archive); - using (var write = ContextFactory.GetForWrite()) // used to share a context for full import. keep in mind this will block all writes. { try diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index 303c59b05e..ee4b7bc8e7 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -91,6 +91,10 @@ namespace osu.Game.Skinning protected override string ComputeHash(SkinInfo item, ArchiveReader reader = null) { + // we need to populate early to create a hash based off skin.ini contents + if (item.Name?.Contains(".osk") == true) + populateMetadata(item); + if (item.Creator != null && item.Creator != unknown_creator_string) { // this is the optimal way to hash legacy skins, but will need to be reconsidered when we move forward with skin implementation. @@ -106,17 +110,23 @@ namespace osu.Game.Skinning { await base.Populate(model, archive, cancellationToken); - Skin reference = GetSkin(model); + if (model.Name?.Contains(".osk") == true) + populateMetadata(model); + } + + private void populateMetadata(SkinInfo item) + { + Skin reference = GetSkin(item); if (!string.IsNullOrEmpty(reference.Configuration.SkinInfo.Name)) { - model.Name = reference.Configuration.SkinInfo.Name; - model.Creator = reference.Configuration.SkinInfo.Creator; + item.Name = reference.Configuration.SkinInfo.Name; + item.Creator = reference.Configuration.SkinInfo.Creator; } else { - model.Name = model.Name.Replace(".osk", ""); - model.Creator ??= unknown_creator_string; + item.Name = item.Name.Replace(".osk", ""); + item.Creator ??= unknown_creator_string; } } From a377cccb4db62c9ed91c843c3511bdcd7f6f1786 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 14 Sep 2020 17:03:09 +0200 Subject: [PATCH 1088/1782] Unsubscribe from track changed event on disposal --- 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 683e3abcc2..2312985e1b 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -642,6 +642,7 @@ namespace osu.Game.Screens.Select base.Dispose(isDisposing); decoupledRuleset.UnbindAll(); + music.TrackChanged -= ensureTrackLooping; } /// From 368aca015a43d918c6e06fe7d5f25e83e8f01678 Mon Sep 17 00:00:00 2001 From: Joehu Date: Mon, 14 Sep 2020 11:18:00 -0700 Subject: [PATCH 1089/1782] Move override methods to bottom --- .../Select/Options/BeatmapOptionsOverlay.cs | 54 +++++++++---------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/osu.Game/Screens/Select/Options/BeatmapOptionsOverlay.cs b/osu.Game/Screens/Select/Options/BeatmapOptionsOverlay.cs index c01970f536..87e4505cd2 100644 --- a/osu.Game/Screens/Select/Options/BeatmapOptionsOverlay.cs +++ b/osu.Game/Screens/Select/Options/BeatmapOptionsOverlay.cs @@ -27,33 +27,6 @@ namespace osu.Game.Screens.Select.Options public override bool BlockScreenWideMouse => false; - protected override void PopIn() - { - base.PopIn(); - - this.FadeIn(transition_duration, Easing.OutQuint); - - if (buttonsContainer.Position.X == 1 || Alpha == 0) - buttonsContainer.MoveToX(x_position - x_movement); - - holder.ScaleTo(new Vector2(1, 1), transition_duration / 2, Easing.OutQuint); - - buttonsContainer.MoveToX(x_position, transition_duration, Easing.OutQuint); - buttonsContainer.TransformSpacingTo(Vector2.Zero, transition_duration, Easing.OutQuint); - } - - protected override void PopOut() - { - base.PopOut(); - - holder.ScaleTo(new Vector2(1, 0), transition_duration / 2, Easing.InSine); - - buttonsContainer.MoveToX(x_position + x_movement, transition_duration, Easing.InSine); - buttonsContainer.TransformSpacingTo(new Vector2(200f, 0f), transition_duration, Easing.InSine); - - this.FadeOut(transition_duration, Easing.InQuint); - } - public BeatmapOptionsOverlay() { AutoSizeAxes = Axes.Y; @@ -107,5 +80,32 @@ namespace osu.Game.Screens.Select.Options buttonsContainer.Add(button); } + + protected override void PopIn() + { + base.PopIn(); + + this.FadeIn(transition_duration, Easing.OutQuint); + + if (buttonsContainer.Position.X == 1 || Alpha == 0) + buttonsContainer.MoveToX(x_position - x_movement); + + holder.ScaleTo(new Vector2(1, 1), transition_duration / 2, Easing.OutQuint); + + buttonsContainer.MoveToX(x_position, transition_duration, Easing.OutQuint); + buttonsContainer.TransformSpacingTo(Vector2.Zero, transition_duration, Easing.OutQuint); + } + + protected override void PopOut() + { + base.PopOut(); + + holder.ScaleTo(new Vector2(1, 0), transition_duration / 2, Easing.InSine); + + buttonsContainer.MoveToX(x_position + x_movement, transition_duration, Easing.InSine); + buttonsContainer.TransformSpacingTo(new Vector2(200f, 0f), transition_duration, Easing.InSine); + + this.FadeOut(transition_duration, Easing.InQuint); + } } } From 1a8a7ae7f8fd0271c779ba15dec8aace09ac5cb4 Mon Sep 17 00:00:00 2001 From: Joehu Date: Mon, 14 Sep 2020 11:19:18 -0700 Subject: [PATCH 1090/1782] Remove hardcoded key param from AddButton --- .../TestSceneBeatmapOptionsOverlay.cs | 9 +++---- .../Select/Options/BeatmapOptionsButton.cs | 13 ---------- .../Select/Options/BeatmapOptionsOverlay.cs | 25 ++++++++++++++++--- osu.Game/Screens/Select/PlaySongSelect.cs | 2 +- osu.Game/Screens/Select/SongSelect.cs | 6 ++--- 5 files changed, 30 insertions(+), 25 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapOptionsOverlay.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapOptionsOverlay.cs index f55c099d83..82d0c63917 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapOptionsOverlay.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapOptionsOverlay.cs @@ -5,7 +5,6 @@ using System.ComponentModel; using osu.Framework.Graphics.Sprites; using osu.Game.Screens.Select.Options; using osuTK.Graphics; -using osuTK.Input; namespace osu.Game.Tests.Visual.SongSelect { @@ -16,10 +15,10 @@ namespace osu.Game.Tests.Visual.SongSelect { var overlay = new BeatmapOptionsOverlay(); - 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(@"Delete", @"all difficulties", FontAwesome.Solid.Trash, Color4.Pink, null, Key.Number3); - overlay.AddButton(@"Edit", @"beatmap", FontAwesome.Solid.PencilAlt, Color4.Yellow, null, Key.Number4); + overlay.AddButton(@"Remove", @"from unplayed", FontAwesome.Regular.TimesCircle, Color4.Purple, null); + overlay.AddButton(@"Clear", @"local scores", FontAwesome.Solid.Eraser, Color4.Purple, null); + overlay.AddButton(@"Delete", @"all difficulties", FontAwesome.Solid.Trash, Color4.Pink, null); + overlay.AddButton(@"Edit", @"beatmap", FontAwesome.Solid.PencilAlt, Color4.Yellow, null); Add(overlay); diff --git a/osu.Game/Screens/Select/Options/BeatmapOptionsButton.cs b/osu.Game/Screens/Select/Options/BeatmapOptionsButton.cs index 4e4653cb57..bd610608b9 100644 --- a/osu.Game/Screens/Select/Options/BeatmapOptionsButton.cs +++ b/osu.Game/Screens/Select/Options/BeatmapOptionsButton.cs @@ -52,8 +52,6 @@ namespace osu.Game.Screens.Select.Options set => secondLine.Text = value; } - public Key? HotKey; - protected override bool OnMouseDown(MouseDownEvent e) { flash.FadeTo(0.1f, 1000, Easing.OutQuint); @@ -75,17 +73,6 @@ namespace osu.Game.Screens.Select.Options return base.OnClick(e); } - protected override bool OnKeyDown(KeyDownEvent e) - { - if (!e.Repeat && e.Key == HotKey) - { - Click(); - return true; - } - - return false; - } - public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => box.ReceivePositionalInputAt(screenSpacePos); public BeatmapOptionsButton() diff --git a/osu.Game/Screens/Select/Options/BeatmapOptionsOverlay.cs b/osu.Game/Screens/Select/Options/BeatmapOptionsOverlay.cs index 87e4505cd2..70cbc7d588 100644 --- a/osu.Game/Screens/Select/Options/BeatmapOptionsOverlay.cs +++ b/osu.Game/Screens/Select/Options/BeatmapOptionsOverlay.cs @@ -11,6 +11,8 @@ using osuTK; using osuTK.Graphics; using osuTK.Input; using osu.Game.Graphics.Containers; +using osu.Framework.Input.Events; +using System.Linq; namespace osu.Game.Screens.Select.Options { @@ -60,9 +62,8 @@ namespace osu.Game.Screens.Select.Options /// Text in the second line. /// Colour of the button. /// Icon of the button. - /// Hotkey of the button. /// Binding the button does. - public void AddButton(string firstLine, string secondLine, IconUsage icon, Color4 colour, Action action, Key? hotkey = null) + public void AddButton(string firstLine, string secondLine, IconUsage icon, Color4 colour, Action action) { var button = new BeatmapOptionsButton { @@ -75,7 +76,6 @@ namespace osu.Game.Screens.Select.Options Hide(); action?.Invoke(); }, - HotKey = hotkey }; buttonsContainer.Add(button); @@ -107,5 +107,24 @@ namespace osu.Game.Screens.Select.Options this.FadeOut(transition_duration, Easing.InQuint); } + + protected override bool OnKeyDown(KeyDownEvent e) + { + if (!e.Repeat && e.Key >= Key.Number1 && e.Key <= Key.Number9) + { + int requested = e.Key - Key.Number1; + + // go reverse as buttonsContainer is a ReverseChildIDFillFlowContainer + BeatmapOptionsButton found = buttonsContainer.Children.ElementAtOrDefault((buttonsContainer.Children.Count - 1) - requested); + + if (found != null) + { + found.Click(); + return true; + } + } + + return base.OnKeyDown(e); + } } } diff --git a/osu.Game/Screens/Select/PlaySongSelect.cs b/osu.Game/Screens/Select/PlaySongSelect.cs index 2236aa4d72..19769f487d 100644 --- a/osu.Game/Screens/Select/PlaySongSelect.cs +++ b/osu.Game/Screens/Select/PlaySongSelect.cs @@ -36,7 +36,7 @@ namespace osu.Game.Screens.Select { ValidForResume = false; Edit(); - }, Key.Number4); + }); ((PlayBeatmapDetailArea)BeatmapDetails).Leaderboard.ScoreSelected += PresentScore; } diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index d313f67446..f5a7c54519 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -275,9 +275,9 @@ namespace osu.Game.Screens.Select Footer.AddButton(new FooterButtonRandom { Action = triggerRandom }); Footer.AddButton(new FooterButtonOptions(), BeatmapOptions); - 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); + BeatmapOptions.AddButton(@"Remove", @"from unplayed", FontAwesome.Regular.TimesCircle, colours.Purple, null); + BeatmapOptions.AddButton(@"Clear", @"local scores", FontAwesome.Solid.Eraser, colours.Purple, () => clearScores(Beatmap.Value.BeatmapInfo)); + BeatmapOptions.AddButton(@"Delete", @"all difficulties", FontAwesome.Solid.Trash, colours.Pink, () => delete(Beatmap.Value.BeatmapSetInfo)); } dialogOverlay = dialog; From ce9c63970cce934caa8b06303780160251d7ff72 Mon Sep 17 00:00:00 2001 From: Joehu Date: Mon, 14 Sep 2020 11:20:43 -0700 Subject: [PATCH 1091/1782] Fix button colors in beatmap options test --- .../SongSelect/TestSceneBeatmapOptionsOverlay.cs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapOptionsOverlay.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapOptionsOverlay.cs index 82d0c63917..61e61af028 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapOptionsOverlay.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapOptionsOverlay.cs @@ -3,8 +3,8 @@ using System.ComponentModel; using osu.Framework.Graphics.Sprites; +using osu.Game.Graphics; using osu.Game.Screens.Select.Options; -using osuTK.Graphics; namespace osu.Game.Tests.Visual.SongSelect { @@ -15,10 +15,12 @@ namespace osu.Game.Tests.Visual.SongSelect { var overlay = new BeatmapOptionsOverlay(); - overlay.AddButton(@"Remove", @"from unplayed", FontAwesome.Regular.TimesCircle, Color4.Purple, null); - overlay.AddButton(@"Clear", @"local scores", FontAwesome.Solid.Eraser, Color4.Purple, null); - overlay.AddButton(@"Delete", @"all difficulties", FontAwesome.Solid.Trash, Color4.Pink, null); - overlay.AddButton(@"Edit", @"beatmap", FontAwesome.Solid.PencilAlt, Color4.Yellow, null); + var colours = new OsuColour(); + + overlay.AddButton(@"Remove", @"from unplayed", FontAwesome.Regular.TimesCircle, colours.Purple, null); + overlay.AddButton(@"Clear", @"local scores", FontAwesome.Solid.Eraser, colours.Purple, null); + overlay.AddButton(@"Delete", @"all difficulties", FontAwesome.Solid.Trash, colours.Pink, null); + overlay.AddButton(@"Edit", @"beatmap", FontAwesome.Solid.PencilAlt, colours.Yellow, null); Add(overlay); From c30174cea36744b881bc236d2f8085c887854972 Mon Sep 17 00:00:00 2001 From: Joehu Date: Mon, 14 Sep 2020 11:21:23 -0700 Subject: [PATCH 1092/1782] Add manage collections button to beatmap options --- .../Visual/SongSelect/TestSceneBeatmapOptionsOverlay.cs | 1 + osu.Game/Screens/Select/SongSelect.cs | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapOptionsOverlay.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapOptionsOverlay.cs index 61e61af028..cab47dca65 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapOptionsOverlay.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapOptionsOverlay.cs @@ -17,6 +17,7 @@ namespace osu.Game.Tests.Visual.SongSelect var colours = new OsuColour(); + overlay.AddButton(@"Manage", @"collections", FontAwesome.Solid.Book, colours.Green, null); overlay.AddButton(@"Remove", @"from unplayed", FontAwesome.Regular.TimesCircle, colours.Purple, null); overlay.AddButton(@"Clear", @"local scores", FontAwesome.Solid.Eraser, colours.Purple, null); overlay.AddButton(@"Delete", @"all difficulties", FontAwesome.Solid.Trash, colours.Pink, null); diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index f5a7c54519..482d469cc3 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -103,6 +103,9 @@ namespace osu.Game.Screens.Select [Resolved] private MusicController music { get; set; } + [Resolved(CanBeNull = true)] + private ManageCollectionsDialog manageCollectionsDialog { get; set; } + [BackgroundDependencyLoader(true)] private void load(AudioManager audio, DialogOverlay dialog, OsuColour colours, SkinManager skins, ScoreManager scores, CollectionManager collections) { @@ -275,6 +278,7 @@ namespace osu.Game.Screens.Select Footer.AddButton(new FooterButtonRandom { Action = triggerRandom }); Footer.AddButton(new FooterButtonOptions(), BeatmapOptions); + BeatmapOptions.AddButton(@"Manage", @"collections", FontAwesome.Solid.Book, colours.Green, () => manageCollectionsDialog?.Show()); BeatmapOptions.AddButton(@"Remove", @"from unplayed", FontAwesome.Regular.TimesCircle, colours.Purple, null); BeatmapOptions.AddButton(@"Clear", @"local scores", FontAwesome.Solid.Eraser, colours.Purple, () => clearScores(Beatmap.Value.BeatmapInfo)); BeatmapOptions.AddButton(@"Delete", @"all difficulties", FontAwesome.Solid.Trash, colours.Pink, () => delete(Beatmap.Value.BeatmapSetInfo)); From a09bd787f0b5b20b6a678e2a8cdeb1ba2c5c614a Mon Sep 17 00:00:00 2001 From: Joehu Date: Mon, 14 Sep 2020 11:21:39 -0700 Subject: [PATCH 1093/1782] Add failing beatmap options input test --- .../Navigation/TestSceneScreenNavigation.cs | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs index 0901976af2..c96952431a 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs @@ -13,6 +13,7 @@ using osu.Game.Overlays.Mods; using osu.Game.Overlays.Toolbar; using osu.Game.Screens.Play; using osu.Game.Screens.Select; +using osu.Game.Screens.Select.Options; using osu.Game.Tests.Beatmaps.IO; using osuTK; using osuTK.Input; @@ -168,6 +169,29 @@ namespace osu.Game.Tests.Visual.Navigation AddAssert("Mods overlay still visible", () => songSelect.ModSelectOverlay.State.Value == Visibility.Visible); } + [Test] + public void TestBeatmapOptionsInput() + { + TestSongSelect songSelect = null; + + PushAndConfirm(() => songSelect = new TestSongSelect()); + + AddStep("Show options overlay", () => songSelect.BeatmapOptionsOverlay.Show()); + + AddStep("Change ruleset to osu!taiko", () => + { + InputManager.PressKey(Key.ControlLeft); + InputManager.PressKey(Key.Number2); + + InputManager.ReleaseKey(Key.ControlLeft); + InputManager.ReleaseKey(Key.Number2); + }); + + AddAssert("Ruleset changed to osu!taiko", () => Game.Toolbar.ChildrenOfType().Single().Current.Value.ID == 1); + + AddAssert("Options overlay still visible", () => songSelect.BeatmapOptionsOverlay.State.Value == Visibility.Visible); + } + private void pushEscape() => AddStep("Press escape", () => pressAndRelease(Key.Escape)); @@ -193,6 +217,8 @@ namespace osu.Game.Tests.Visual.Navigation private class TestSongSelect : PlaySongSelect { public ModSelectOverlay ModSelectOverlay => ModSelect; + + public BeatmapOptionsOverlay BeatmapOptionsOverlay => BeatmapOptions; } } } From 57610ddad51c1a07bc346f2e6beab161b97058ee Mon Sep 17 00:00:00 2001 From: Joehu Date: Mon, 14 Sep 2020 11:22:16 -0700 Subject: [PATCH 1094/1782] Fix beatmap options absorbing input from toolbar ruleset selector --- osu.Game/Screens/Select/Options/BeatmapOptionsOverlay.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Screens/Select/Options/BeatmapOptionsOverlay.cs b/osu.Game/Screens/Select/Options/BeatmapOptionsOverlay.cs index 70cbc7d588..2676635764 100644 --- a/osu.Game/Screens/Select/Options/BeatmapOptionsOverlay.cs +++ b/osu.Game/Screens/Select/Options/BeatmapOptionsOverlay.cs @@ -110,6 +110,9 @@ namespace osu.Game.Screens.Select.Options protected override bool OnKeyDown(KeyDownEvent e) { + // don't absorb control as ToolbarRulesetSelector uses control + number to navigate + if (e.ControlPressed) return false; + if (!e.Repeat && e.Key >= Key.Number1 && e.Key <= Key.Number9) { int requested = e.Key - Key.Number1; From c833f5fcc4b046da16676b0a4a5aae58d799927d Mon Sep 17 00:00:00 2001 From: Joehu Date: Mon, 14 Sep 2020 11:23:41 -0700 Subject: [PATCH 1095/1782] Reorder buttons to match stable --- .../Visual/SongSelect/TestSceneBeatmapOptionsOverlay.cs | 2 +- osu.Game/Screens/Select/SongSelect.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapOptionsOverlay.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapOptionsOverlay.cs index cab47dca65..e9742acdde 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapOptionsOverlay.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapOptionsOverlay.cs @@ -18,9 +18,9 @@ namespace osu.Game.Tests.Visual.SongSelect var colours = new OsuColour(); overlay.AddButton(@"Manage", @"collections", FontAwesome.Solid.Book, colours.Green, null); + overlay.AddButton(@"Delete", @"all difficulties", FontAwesome.Solid.Trash, colours.Pink, null); overlay.AddButton(@"Remove", @"from unplayed", FontAwesome.Regular.TimesCircle, colours.Purple, null); overlay.AddButton(@"Clear", @"local scores", FontAwesome.Solid.Eraser, colours.Purple, null); - overlay.AddButton(@"Delete", @"all difficulties", FontAwesome.Solid.Trash, colours.Pink, null); overlay.AddButton(@"Edit", @"beatmap", FontAwesome.Solid.PencilAlt, colours.Yellow, null); Add(overlay); diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 482d469cc3..ed2e24c5bf 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -279,9 +279,9 @@ namespace osu.Game.Screens.Select Footer.AddButton(new FooterButtonOptions(), BeatmapOptions); BeatmapOptions.AddButton(@"Manage", @"collections", FontAwesome.Solid.Book, colours.Green, () => manageCollectionsDialog?.Show()); + BeatmapOptions.AddButton(@"Delete", @"all difficulties", FontAwesome.Solid.Trash, colours.Pink, () => delete(Beatmap.Value.BeatmapSetInfo)); BeatmapOptions.AddButton(@"Remove", @"from unplayed", FontAwesome.Regular.TimesCircle, colours.Purple, null); BeatmapOptions.AddButton(@"Clear", @"local scores", FontAwesome.Solid.Eraser, colours.Purple, () => clearScores(Beatmap.Value.BeatmapInfo)); - BeatmapOptions.AddButton(@"Delete", @"all difficulties", FontAwesome.Solid.Trash, colours.Pink, () => delete(Beatmap.Value.BeatmapSetInfo)); } dialogOverlay = dialog; From 43daabc98242bec098d78285f542d0179b817495 Mon Sep 17 00:00:00 2001 From: Joehu Date: Mon, 14 Sep 2020 12:10:00 -0700 Subject: [PATCH 1096/1782] Remove unused using and move dialog to BDL --- osu.Game/Screens/Select/Options/BeatmapOptionsButton.cs | 1 - osu.Game/Screens/Select/SongSelect.cs | 5 +---- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/osu.Game/Screens/Select/Options/BeatmapOptionsButton.cs b/osu.Game/Screens/Select/Options/BeatmapOptionsButton.cs index bd610608b9..6e2f3cc9df 100644 --- a/osu.Game/Screens/Select/Options/BeatmapOptionsButton.cs +++ b/osu.Game/Screens/Select/Options/BeatmapOptionsButton.cs @@ -12,7 +12,6 @@ using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osuTK; using osuTK.Graphics; -using osuTK.Input; using osu.Game.Graphics.Containers; namespace osu.Game.Screens.Select.Options diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index ed2e24c5bf..180752a579 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -103,11 +103,8 @@ namespace osu.Game.Screens.Select [Resolved] private MusicController music { get; set; } - [Resolved(CanBeNull = true)] - private ManageCollectionsDialog manageCollectionsDialog { get; set; } - [BackgroundDependencyLoader(true)] - private void load(AudioManager audio, DialogOverlay dialog, OsuColour colours, SkinManager skins, ScoreManager scores, CollectionManager collections) + private void load(AudioManager audio, DialogOverlay dialog, OsuColour colours, SkinManager skins, ScoreManager scores, CollectionManager collections, ManageCollectionsDialog manageCollectionsDialog) { // initial value transfer is required for FilterControl (it uses our re-cached bindables in its async load for the initial filter). transferRulesetValue(); From 15e423157b4c49db9764ad99162341f11a37110c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 15 Sep 2020 14:01:29 +0900 Subject: [PATCH 1097/1782] Fix tests that access LocalStorage before BDL --- .../TestSceneManageCollectionsDialog.cs | 27 +++++++------------ .../SongSelect/TestSceneFilterControl.cs | 22 +++++---------- 2 files changed, 15 insertions(+), 34 deletions(-) diff --git a/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs b/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs index 54ab20af7f..55b61bc54a 100644 --- a/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs +++ b/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs @@ -22,44 +22,35 @@ namespace osu.Game.Tests.Visual.Collections { public class TestSceneManageCollectionsDialog : OsuManualInputManagerTestScene { - protected override Container Content => content; + protected override Container Content { get; } = new Container { RelativeSizeAxes = Axes.Both }; - private readonly Container content; - private readonly DialogOverlay dialogOverlay; - private readonly CollectionManager manager; + private DialogOverlay dialogOverlay; + private CollectionManager manager; private RulesetStore rulesets; private BeatmapManager beatmapManager; private ManageCollectionsDialog dialog; - public TestSceneManageCollectionsDialog() + [BackgroundDependencyLoader] + private void load(GameHost host) { base.Content.AddRange(new Drawable[] { manager = new CollectionManager(LocalStorage), - content = new Container { RelativeSizeAxes = Axes.Both }, + Content, dialogOverlay = new DialogOverlay() }); - } - [BackgroundDependencyLoader] - private void load(GameHost host) - { + Dependencies.Cache(manager); + Dependencies.Cache(dialogOverlay); + Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); Dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, Audio, host, Beatmap.Default)); beatmapManager.Import(TestResources.GetTestBeatmapForImport()).Wait(); } - protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) - { - var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); - dependencies.Cache(manager); - dependencies.Cache(dialogOverlay); - return dependencies; - } - [SetUp] public void SetUp() => Schedule(() => { diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs index 7cd4791acb..0f03368296 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs @@ -23,41 +23,31 @@ namespace osu.Game.Tests.Visual.SongSelect { public class TestSceneFilterControl : OsuManualInputManagerTestScene { - protected override Container Content => content; - private readonly Container content; + protected override Container Content { get; } = new Container { RelativeSizeAxes = Axes.Both }; - private readonly CollectionManager collectionManager; + private CollectionManager collectionManager; private RulesetStore rulesets; private BeatmapManager beatmapManager; private FilterControl control; - public TestSceneFilterControl() + [BackgroundDependencyLoader] + private void load(GameHost host) { base.Content.AddRange(new Drawable[] { collectionManager = new CollectionManager(LocalStorage), - content = new Container { RelativeSizeAxes = Axes.Both } + Content }); - } - [BackgroundDependencyLoader] - private void load(GameHost host) - { + Dependencies.Cache(collectionManager); Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); Dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, Audio, host, Beatmap.Default)); beatmapManager.Import(TestResources.GetTestBeatmapForImport()).Wait(); } - protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) - { - var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); - dependencies.Cache(collectionManager); - return dependencies; - } - [SetUp] public void SetUp() => Schedule(() => { From 234152b2fe6a886051acf5aea1d49edaf343bac9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 15 Sep 2020 14:03:36 +0900 Subject: [PATCH 1098/1782] Use host storage as LocalStorage for headless test runs --- osu.Game/Tests/Visual/OsuTestScene.cs | 32 ++++++++++++++++----------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/osu.Game/Tests/Visual/OsuTestScene.cs b/osu.Game/Tests/Visual/OsuTestScene.cs index 4db5139813..6286809595 100644 --- a/osu.Game/Tests/Visual/OsuTestScene.cs +++ b/osu.Game/Tests/Visual/OsuTestScene.cs @@ -46,7 +46,7 @@ namespace osu.Game.Tests.Visual private Lazy localStorage; protected Storage LocalStorage => localStorage.Value; - private readonly Lazy contextFactory; + private Lazy contextFactory; protected IAPIProvider API { @@ -71,6 +71,17 @@ namespace osu.Game.Tests.Visual protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) { + contextFactory = new Lazy(() => + { + var factory = new DatabaseContextFactory(LocalStorage); + factory.ResetDatabase(); + using (var usage = factory.Get()) + usage.Migrate(); + return factory; + }); + + RecycleLocalStorage(); + var baseDependencies = base.CreateChildDependencies(parent); var providedRuleset = CreateRuleset(); @@ -104,16 +115,6 @@ namespace osu.Game.Tests.Visual protected OsuTestScene() { - RecycleLocalStorage(); - contextFactory = new Lazy(() => - { - var factory = new DatabaseContextFactory(LocalStorage); - factory.ResetDatabase(); - using (var usage = factory.Get()) - usage.Migrate(); - return factory; - }); - base.Content.Add(content = new DrawSizePreservingFillContainer()); } @@ -131,9 +132,14 @@ namespace osu.Game.Tests.Visual } } - localStorage = new Lazy(() => new NativeStorage(Path.Combine(RuntimeInfo.StartupDirectory, $"{GetType().Name}-{Guid.NewGuid()}"))); + localStorage = host is HeadlessGameHost + ? new Lazy(() => host.Storage) + : new Lazy(() => new NativeStorage(Path.Combine(RuntimeInfo.StartupDirectory, $"{GetType().Name}-{Guid.NewGuid()}"))); } + [Resolved] + private GameHost host { get; set; } + [Resolved] protected AudioManager Audio { get; private set; } @@ -172,7 +178,7 @@ namespace osu.Game.Tests.Visual if (MusicController?.TrackLoaded == true) MusicController.CurrentTrack.Stop(); - if (contextFactory.IsValueCreated) + if (contextFactory?.IsValueCreated == true) contextFactory.Value.ResetDatabase(); RecycleLocalStorage(); From 879979ef57f4b8139c1f37d117f2229d84f3d45b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 15 Sep 2020 14:25:31 +0900 Subject: [PATCH 1099/1782] Move host lookup to inside lazy retrieval to handle edge cases --- osu.Game/Tests/Visual/OsuTestScene.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/osu.Game/Tests/Visual/OsuTestScene.cs b/osu.Game/Tests/Visual/OsuTestScene.cs index 6286809595..611bc8f30f 100644 --- a/osu.Game/Tests/Visual/OsuTestScene.cs +++ b/osu.Game/Tests/Visual/OsuTestScene.cs @@ -132,9 +132,8 @@ namespace osu.Game.Tests.Visual } } - localStorage = host is HeadlessGameHost - ? new Lazy(() => host.Storage) - : new Lazy(() => new NativeStorage(Path.Combine(RuntimeInfo.StartupDirectory, $"{GetType().Name}-{Guid.NewGuid()}"))); + localStorage = + new Lazy(() => host is HeadlessGameHost ? host.Storage : new NativeStorage(Path.Combine(RuntimeInfo.StartupDirectory, $"{GetType().Name}-{Guid.NewGuid()}"))); } [Resolved] From 2c7492d717fad46a677b09749b37aabeb1781f5d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 15 Sep 2020 14:34:58 +0900 Subject: [PATCH 1100/1782] Add null check in SongSelect disposal for safety --- osu.Game/Screens/Select/SongSelect.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 2312985e1b..260ab0e89f 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -642,7 +642,9 @@ namespace osu.Game.Screens.Select base.Dispose(isDisposing); decoupledRuleset.UnbindAll(); - music.TrackChanged -= ensureTrackLooping; + + if (music != null) + music.TrackChanged -= ensureTrackLooping; } /// From 0446bc861043a80862bb39a2743c5087579774a5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 15 Sep 2020 14:43:24 +0900 Subject: [PATCH 1101/1782] Fix game.ini getting left over by PlayerTestScene subclasses --- osu.Game/Tests/Visual/PlayerTestScene.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game/Tests/Visual/PlayerTestScene.cs b/osu.Game/Tests/Visual/PlayerTestScene.cs index 2c46e7f6d3..aa3bd2e4b7 100644 --- a/osu.Game/Tests/Visual/PlayerTestScene.cs +++ b/osu.Game/Tests/Visual/PlayerTestScene.cs @@ -81,6 +81,12 @@ namespace osu.Game.Tests.Visual LoadScreen(Player); } + protected override void Dispose(bool isDisposing) + { + LocalConfig?.Dispose(); + base.Dispose(isDisposing); + } + /// /// Creates the ruleset for setting up the component. /// From 3242b10187f77e55f171e71610e17026187e30e2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 15 Sep 2020 15:00:04 +0900 Subject: [PATCH 1102/1782] Change order of dependency caching to promote use of locals --- .../Collections/TestSceneManageCollectionsDialog.cs | 10 +++++----- .../Visual/SongSelect/TestSceneFilterControl.cs | 9 +++++---- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs b/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs index 55b61bc54a..fef1605f0c 100644 --- a/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs +++ b/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs @@ -35,6 +35,11 @@ namespace osu.Game.Tests.Visual.Collections [BackgroundDependencyLoader] private void load(GameHost host) { + Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); + Dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, Audio, host, Beatmap.Default)); + + beatmapManager.Import(TestResources.GetTestBeatmapForImport()).Wait(); + base.Content.AddRange(new Drawable[] { manager = new CollectionManager(LocalStorage), @@ -44,11 +49,6 @@ namespace osu.Game.Tests.Visual.Collections Dependencies.Cache(manager); Dependencies.Cache(dialogOverlay); - - Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); - Dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, Audio, host, Beatmap.Default)); - - beatmapManager.Import(TestResources.GetTestBeatmapForImport()).Wait(); } [SetUp] diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs index 0f03368296..5d0fb248df 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs @@ -35,6 +35,11 @@ namespace osu.Game.Tests.Visual.SongSelect [BackgroundDependencyLoader] private void load(GameHost host) { + Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); + Dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, Audio, host, Beatmap.Default)); + + beatmapManager.Import(TestResources.GetTestBeatmapForImport()).Wait(); + base.Content.AddRange(new Drawable[] { collectionManager = new CollectionManager(LocalStorage), @@ -42,10 +47,6 @@ namespace osu.Game.Tests.Visual.SongSelect }); Dependencies.Cache(collectionManager); - Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); - Dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, Audio, host, Beatmap.Default)); - - beatmapManager.Import(TestResources.GetTestBeatmapForImport()).Wait(); } [SetUp] From 9e73237a900e8b9cb3bdf9689a140f4ee11ce2ba Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 15 Sep 2020 15:21:03 +0900 Subject: [PATCH 1103/1782] Fix score present tests potentially succeeding a step when they shouldn't --- .../Visual/Navigation/TestScenePresentScore.cs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs b/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs index a899d072ac..74037dd3ec 100644 --- a/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs +++ b/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs @@ -133,6 +133,12 @@ namespace osu.Game.Tests.Visual.Navigation return () => imported; } + /// + /// Some tests test waiting for a particular screen twice in a row, but expect a new instance each time. + /// There's a case where they may succeed incorrectly if we don't compare against the previous instance. + /// + private IScreen lastWaitedScreen; + private void presentAndConfirm(Func getImport, ScorePresentType type) { AddStep("present score", () => Game.PresentScore(getImport(), type)); @@ -140,13 +146,15 @@ namespace osu.Game.Tests.Visual.Navigation switch (type) { case ScorePresentType.Results: - AddUntilStep("wait for results", () => Game.ScreenStack.CurrentScreen is ResultsScreen); + AddUntilStep("wait for results", () => lastWaitedScreen != Game.ScreenStack.CurrentScreen && Game.ScreenStack.CurrentScreen is ResultsScreen); + AddStep("store last waited screen", () => lastWaitedScreen = Game.ScreenStack.CurrentScreen); AddUntilStep("correct score displayed", () => ((ResultsScreen)Game.ScreenStack.CurrentScreen).Score.ID == getImport().ID); AddAssert("correct ruleset selected", () => Game.Ruleset.Value.ID == getImport().Ruleset.ID); break; case ScorePresentType.Gameplay: - AddUntilStep("wait for player loader", () => Game.ScreenStack.CurrentScreen is ReplayPlayerLoader); + AddUntilStep("wait for player loader", () => lastWaitedScreen != Game.ScreenStack.CurrentScreen && Game.ScreenStack.CurrentScreen is ReplayPlayerLoader); + AddStep("store last waited screen", () => lastWaitedScreen = Game.ScreenStack.CurrentScreen); AddUntilStep("correct score displayed", () => ((ReplayPlayerLoader)Game.ScreenStack.CurrentScreen).Score.ID == getImport().ID); AddAssert("correct ruleset selected", () => Game.Ruleset.Value.ID == getImport().Ruleset.ID); break; From 9c041dbac2fba1bafe1f9290b091048e8c6438f3 Mon Sep 17 00:00:00 2001 From: Morilli <35152647+Morilli@users.noreply.github.com> Date: Tue, 15 Sep 2020 08:24:01 +0200 Subject: [PATCH 1104/1782] Fix mania scrollspeed slider precision --- .../Configuration/ManiaRulesetConfigManager.cs | 2 +- osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Configuration/ManiaRulesetConfigManager.cs b/osu.Game.Rulesets.Mania/Configuration/ManiaRulesetConfigManager.cs index 7e84f17809..756f2b7b2f 100644 --- a/osu.Game.Rulesets.Mania/Configuration/ManiaRulesetConfigManager.cs +++ b/osu.Game.Rulesets.Mania/Configuration/ManiaRulesetConfigManager.cs @@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Mania.Configuration { base.InitialiseDefaults(); - Set(ManiaRulesetSetting.ScrollTime, 1500.0, DrawableManiaRuleset.MIN_TIME_RANGE, DrawableManiaRuleset.MAX_TIME_RANGE, 1); + Set(ManiaRulesetSetting.ScrollTime, 1500.0, DrawableManiaRuleset.MIN_TIME_RANGE, DrawableManiaRuleset.MAX_TIME_RANGE, 5); Set(ManiaRulesetSetting.ScrollDirection, ManiaScrollingDirection.Down); } diff --git a/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs b/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs index 2ebfd0cfc1..b470405df2 100644 --- a/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs +++ b/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs @@ -34,7 +34,8 @@ namespace osu.Game.Rulesets.Mania new SettingsSlider { LabelText = "Scroll speed", - Bindable = config.GetBindable(ManiaRulesetSetting.ScrollTime) + Bindable = config.GetBindable(ManiaRulesetSetting.ScrollTime), + KeyboardStep = 5 }, }; } From f7c9c805665152a526f9b67b48281fc51ca7e4ac Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 15 Sep 2020 19:01:32 +0900 Subject: [PATCH 1105/1782] Force OsuGameTests to use a unique storage each run --- osu.Game.Tests/Visual/Navigation/OsuGameTestScene.cs | 2 ++ osu.Game/Tests/Visual/OsuTestScene.cs | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Navigation/OsuGameTestScene.cs b/osu.Game.Tests/Visual/Navigation/OsuGameTestScene.cs index c4acf4f7da..4c18cfa61c 100644 --- a/osu.Game.Tests/Visual/Navigation/OsuGameTestScene.cs +++ b/osu.Game.Tests/Visual/Navigation/OsuGameTestScene.cs @@ -34,6 +34,8 @@ namespace osu.Game.Tests.Visual.Navigation protected TestOsuGame Game; + protected override bool UseFreshStoragePerRun => true; + [BackgroundDependencyLoader] private void load(GameHost host) { diff --git a/osu.Game/Tests/Visual/OsuTestScene.cs b/osu.Game/Tests/Visual/OsuTestScene.cs index 611bc8f30f..f00cefaefd 100644 --- a/osu.Game/Tests/Visual/OsuTestScene.cs +++ b/osu.Game/Tests/Visual/OsuTestScene.cs @@ -118,6 +118,8 @@ namespace osu.Game.Tests.Visual base.Content.Add(content = new DrawSizePreservingFillContainer()); } + protected virtual bool UseFreshStoragePerRun => false; + public virtual void RecycleLocalStorage() { if (localStorage?.IsValueCreated == true) @@ -133,7 +135,7 @@ namespace osu.Game.Tests.Visual } localStorage = - new Lazy(() => host is HeadlessGameHost ? host.Storage : new NativeStorage(Path.Combine(RuntimeInfo.StartupDirectory, $"{GetType().Name}-{Guid.NewGuid()}"))); + new Lazy(() => !UseFreshStoragePerRun && host is HeadlessGameHost ? host.Storage : new NativeStorage(Path.Combine(RuntimeInfo.StartupDirectory, $"{GetType().Name}-{Guid.NewGuid()}"))); } [Resolved] From e43e12cb2dd0504917f27fb2b83efd637885366b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 15 Sep 2020 20:17:59 +0900 Subject: [PATCH 1106/1782] Pause playback in present tests to avoid track inadvertently changing at menu --- .../Visual/Navigation/TestScenePresentBeatmap.cs | 7 +++++++ osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/osu.Game.Tests/Visual/Navigation/TestScenePresentBeatmap.cs b/osu.Game.Tests/Visual/Navigation/TestScenePresentBeatmap.cs index 27f5b29738..a003b9ae4d 100644 --- a/osu.Game.Tests/Visual/Navigation/TestScenePresentBeatmap.cs +++ b/osu.Game.Tests/Visual/Navigation/TestScenePresentBeatmap.cs @@ -74,6 +74,13 @@ namespace osu.Game.Tests.Visual.Navigation private void returnToMenu() { + // if we don't pause, there's a chance the track may change at the main menu out of our control (due to reaching the end of the track). + AddStep("pause audio", () => + { + if (Game.MusicController.IsPlaying) + Game.MusicController.TogglePause(); + }); + AddStep("return to menu", () => Game.ScreenStack.CurrentScreen.Exit()); AddUntilStep("wait for menu", () => Game.ScreenStack.CurrentScreen is MainMenu); } diff --git a/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs b/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs index 74037dd3ec..52b577b402 100644 --- a/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs +++ b/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs @@ -110,6 +110,13 @@ namespace osu.Game.Tests.Visual.Navigation private void returnToMenu() { + // if we don't pause, there's a chance the track may change at the main menu out of our control (due to reaching the end of the track). + AddStep("pause audio", () => + { + if (Game.MusicController.IsPlaying) + Game.MusicController.TogglePause(); + }); + AddStep("return to menu", () => Game.ScreenStack.CurrentScreen.Exit()); AddUntilStep("wait for menu", () => Game.ScreenStack.CurrentScreen is MainMenu); } From dbfaa4a0df5ae5924d1c640fa200ecea39d318f1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 15 Sep 2020 22:50:44 +0900 Subject: [PATCH 1107/1782] Remove beatmap paths from tests where they would result in exceptions --- osu.Game.Tests/Visual/Multiplayer/TestSceneMatchSongSelect.cs | 1 - osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs | 3 --- osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs | 1 - 3 files changed, 5 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchSongSelect.cs index faea32f90f..55b8902d7b 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchSongSelect.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchSongSelect.cs @@ -54,7 +54,6 @@ namespace osu.Game.Tests.Visual.Multiplayer { Ruleset = new OsuRuleset().RulesetInfo, OnlineBeatmapID = beatmapId, - Path = "normal.osu", Version = $"{beatmapId} (length {TimeSpan.FromMilliseconds(length):m\\:ss}, bpm {bpm:0.#})", Length = length, BPM = bpm, diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs index a3ea4619cc..3aff390a47 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs @@ -839,7 +839,6 @@ namespace osu.Game.Tests.Visual.SongSelect new BeatmapInfo { OnlineBeatmapID = id * 10, - Path = "normal.osu", Version = "Normal", StarDifficulty = 2, BaseDifficulty = new BeatmapDifficulty @@ -850,7 +849,6 @@ namespace osu.Game.Tests.Visual.SongSelect new BeatmapInfo { OnlineBeatmapID = id * 10 + 1, - Path = "hard.osu", Version = "Hard", StarDifficulty = 5, BaseDifficulty = new BeatmapDifficulty @@ -861,7 +859,6 @@ namespace osu.Game.Tests.Visual.SongSelect new BeatmapInfo { OnlineBeatmapID = id * 10 + 2, - Path = "insane.osu", Version = "Insane", StarDifficulty = 6, BaseDifficulty = new BeatmapDifficulty diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs index f7d66ca5cf..0299b7a084 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs @@ -879,7 +879,6 @@ namespace osu.Game.Tests.Visual.SongSelect { Ruleset = getRuleset(), OnlineBeatmapID = beatmapId, - Path = "normal.osu", Version = $"{beatmapId} (length {TimeSpan.FromMilliseconds(length):m\\:ss}, bpm {bpm:0.#})", Length = length, BPM = bpm, From 3c70b3127c31a9098a294f09749e49d7e9ca6da9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 15 Sep 2020 23:19:31 +0900 Subject: [PATCH 1108/1782] Fix potential nullref in FilterControl during asynchronous load --- osu.Game/Screens/Select/FilterControl.cs | 30 ++++++++++++------------ 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/osu.Game/Screens/Select/FilterControl.cs b/osu.Game/Screens/Select/FilterControl.cs index c82a3742cc..952a5d1eaa 100644 --- a/osu.Game/Screens/Select/FilterControl.cs +++ b/osu.Game/Screens/Select/FilterControl.cs @@ -66,24 +66,9 @@ namespace osu.Game.Screens.Select [BackgroundDependencyLoader(permitNulls: true)] private void load(OsuColour colours, IBindable parentRuleset, OsuConfigManager config) { - config.BindWith(OsuSetting.ShowConvertedBeatmaps, showConverted); - showConverted.ValueChanged += _ => updateCriteria(); - - config.BindWith(OsuSetting.DisplayStarsMinimum, minimumStars); - minimumStars.ValueChanged += _ => updateCriteria(); - - config.BindWith(OsuSetting.DisplayStarsMaximum, maximumStars); - maximumStars.ValueChanged += _ => updateCriteria(); - - ruleset.BindTo(parentRuleset); - ruleset.BindValueChanged(_ => updateCriteria()); - sortMode = config.GetBindable(OsuSetting.SongSelectSortingMode); groupMode = config.GetBindable(OsuSetting.SongSelectGroupingMode); - groupMode.BindValueChanged(_ => updateCriteria()); - sortMode.BindValueChanged(_ => updateCriteria()); - Children = new Drawable[] { new Box @@ -182,6 +167,21 @@ namespace osu.Game.Screens.Select } }; + config.BindWith(OsuSetting.ShowConvertedBeatmaps, showConverted); + showConverted.ValueChanged += _ => updateCriteria(); + + config.BindWith(OsuSetting.DisplayStarsMinimum, minimumStars); + minimumStars.ValueChanged += _ => updateCriteria(); + + config.BindWith(OsuSetting.DisplayStarsMaximum, maximumStars); + maximumStars.ValueChanged += _ => updateCriteria(); + + ruleset.BindTo(parentRuleset); + ruleset.BindValueChanged(_ => updateCriteria()); + + groupMode.BindValueChanged(_ => updateCriteria()); + sortMode.BindValueChanged(_ => updateCriteria()); + collectionDropdown.Current.ValueChanged += val => { if (val.NewValue == null) From 35c7677d0a0996d4df518aa6da5c7b464f0059a3 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 16 Sep 2020 01:59:07 +0300 Subject: [PATCH 1109/1782] Fix gameplay samples potentially start playing while player is paused --- osu.Game/Skinning/SkinnableSound.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/osu.Game/Skinning/SkinnableSound.cs b/osu.Game/Skinning/SkinnableSound.cs index cf629f231f..9e49134806 100644 --- a/osu.Game/Skinning/SkinnableSound.cs +++ b/osu.Game/Skinning/SkinnableSound.cs @@ -114,6 +114,8 @@ namespace osu.Game.Skinning protected override void SkinChanged(ISkinSource skin, bool allowFallback) { + bool wasPlaying = IsPlaying; + var channels = hitSamples.Select(s => { var ch = skin.GetSample(s); @@ -138,8 +140,10 @@ namespace osu.Game.Skinning samplesContainer.ChildrenEnumerable = channels.Select(c => new DrawableSample(c)); - if (requestedPlaying) - Play(); + // Sample channels have been reloaded to new ones because skin has changed. + // Start playback internally for them if they were playing previously. + if (wasPlaying) + play(); } #region Re-expose AudioContainer From 105634c09936295a4083fef5b3f7c7d56f10a56a Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 16 Sep 2020 01:59:41 +0300 Subject: [PATCH 1110/1782] Add test case ensuring correct behaviour --- .../Gameplay/TestSceneSkinnableSound.cs | 61 ++++++++++++++++++- 1 file changed, 60 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs index e0a1f947ec..5f39a57d8a 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs @@ -1,12 +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.Sample; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Audio; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.OpenGL.Textures; +using osu.Framework.Graphics.Textures; using osu.Framework.Testing; using osu.Framework.Timing; using osu.Game.Audio; @@ -20,6 +25,7 @@ namespace osu.Game.Tests.Visual.Gameplay [Cached] private GameplayClock gameplayClock = new GameplayClock(new FramedClock()); + private TestSkinSourceContainer skinSource; private SkinnableSound skinnableSound; [SetUp] @@ -29,7 +35,7 @@ namespace osu.Game.Tests.Visual.Gameplay Children = new Drawable[] { - new Container + skinSource = new TestSkinSourceContainer { Clock = gameplayClock, RelativeSizeAxes = Axes.Both, @@ -101,5 +107,58 @@ namespace osu.Game.Tests.Visual.Gameplay AddAssert("sample not playing", () => !sample.Playing); AddAssert("sample not playing", () => !sample.Playing); } + + [Test] + public void TestSkinChangeDoesntPlayOnPause() + { + DrawableSample sample = null; + AddStep("start sample", () => + { + skinnableSound.Play(); + sample = skinnableSound.ChildrenOfType().Single(); + }); + + AddAssert("sample playing", () => sample.Playing); + + AddStep("pause gameplay clock", () => gameplayClock.IsPaused.Value = true); + AddUntilStep("wait for sample to stop playing", () => !sample.Playing); + + AddStep("trigger skin change", () => + { + skinSource.TriggerSourceChanged(); + }); + + AddStep("retrieve new sample", () => + { + DrawableSample newSample = skinnableSound.ChildrenOfType().Single(); + Assert.IsTrue(newSample != sample, "Sample still hasn't been updated after a skin change event"); + sample = newSample; + }); + + AddAssert("new sample paused", () => !sample.Playing); + AddStep("resume gameplay clock", () => gameplayClock.IsPaused.Value = false); + + AddWaitStep("wait a bit", 5); + AddAssert("new sample not played", () => !sample.Playing); + } + + [Cached(typeof(ISkinSource))] + private class TestSkinSourceContainer : Container, ISkinSource + { + [Resolved] + private ISkinSource source { get; set; } + + public event Action SourceChanged; + + public Drawable GetDrawableComponent(ISkinComponent component) => source?.GetDrawableComponent(component); + public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => source?.GetTexture(componentName, wrapModeS, wrapModeT); + public SampleChannel GetSample(ISampleInfo sampleInfo) => source?.GetSample(sampleInfo); + public IBindable GetConfig(TLookup lookup) => source?.GetConfig(lookup); + + public void TriggerSourceChanged() + { + SourceChanged?.Invoke(); + } + } } } From c6386ea60505e10b56e2189423fa36d4d0f6a87a Mon Sep 17 00:00:00 2001 From: Joehu Date: Tue, 15 Sep 2020 21:33:52 -0700 Subject: [PATCH 1111/1782] Remember leaderboard mods filter selection in song select --- osu.Game/Configuration/OsuConfigManager.cs | 2 ++ osu.Game/Screens/Select/BeatmapDetailArea.cs | 2 ++ osu.Game/Screens/Select/BeatmapDetailAreaTabControl.cs | 6 ++++++ osu.Game/Screens/Select/PlayBeatmapDetailArea.cs | 7 +++++++ 4 files changed, 17 insertions(+) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 71820dea55..207a3f01d3 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -24,6 +24,7 @@ namespace osu.Game.Configuration Set(OsuSetting.Skin, 0, -1, int.MaxValue); Set(OsuSetting.BeatmapDetailTab, PlayBeatmapDetailArea.TabType.Details); + Set(OsuSetting.BeatmapDetailModsFilter, false); Set(OsuSetting.ShowConvertedBeatmaps, true); Set(OsuSetting.DisplayStarsMinimum, 0.0, 0, 10, 0.1); @@ -200,6 +201,7 @@ namespace osu.Game.Configuration CursorRotation, MenuParallax, BeatmapDetailTab, + BeatmapDetailModsFilter, Username, ReleaseStream, SavePassword, diff --git a/osu.Game/Screens/Select/BeatmapDetailArea.cs b/osu.Game/Screens/Select/BeatmapDetailArea.cs index 2e78b1aed2..89ae92ec91 100644 --- a/osu.Game/Screens/Select/BeatmapDetailArea.cs +++ b/osu.Game/Screens/Select/BeatmapDetailArea.cs @@ -30,6 +30,8 @@ namespace osu.Game.Screens.Select protected Bindable CurrentTab => tabControl.Current; + protected Bindable CurrentModsFilter => tabControl.CurrentModsFilter; + private readonly Container content; protected override Container Content => content; diff --git a/osu.Game/Screens/Select/BeatmapDetailAreaTabControl.cs b/osu.Game/Screens/Select/BeatmapDetailAreaTabControl.cs index 63711e3e50..df8c68a0dd 100644 --- a/osu.Game/Screens/Select/BeatmapDetailAreaTabControl.cs +++ b/osu.Game/Screens/Select/BeatmapDetailAreaTabControl.cs @@ -26,6 +26,12 @@ namespace osu.Game.Screens.Select set => tabs.Current = value; } + public Bindable CurrentModsFilter + { + get => modsCheckbox.Current; + set => modsCheckbox.Current = value; + } + public Action OnFilter; // passed the selected tab and if mods is checked public IReadOnlyList TabItems diff --git a/osu.Game/Screens/Select/PlayBeatmapDetailArea.cs b/osu.Game/Screens/Select/PlayBeatmapDetailArea.cs index d719502a4f..c87a4bbc54 100644 --- a/osu.Game/Screens/Select/PlayBeatmapDetailArea.cs +++ b/osu.Game/Screens/Select/PlayBeatmapDetailArea.cs @@ -29,6 +29,8 @@ namespace osu.Game.Screens.Select private Bindable selectedTab; + private Bindable selectedModsFilter; + public PlayBeatmapDetailArea() { Add(Leaderboard = new BeatmapLeaderboard { RelativeSizeAxes = Axes.Both }); @@ -38,8 +40,13 @@ namespace osu.Game.Screens.Select private void load(OsuConfigManager config) { selectedTab = config.GetBindable(OsuSetting.BeatmapDetailTab); + selectedModsFilter = config.GetBindable(OsuSetting.BeatmapDetailModsFilter); + selectedTab.BindValueChanged(tab => CurrentTab.Value = getTabItemFromTabType(tab.NewValue), true); CurrentTab.BindValueChanged(tab => selectedTab.Value = getTabTypeFromTabItem(tab.NewValue)); + + selectedModsFilter.BindValueChanged(checkbox => CurrentModsFilter.Value = checkbox.NewValue, true); + CurrentModsFilter.BindValueChanged(checkbox => selectedModsFilter.Value = checkbox.NewValue); } public override void Refresh() From ff5b29230261ac3ef26b1e1e375652b29357ddfe Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 16 Sep 2020 19:36:36 +0900 Subject: [PATCH 1112/1782] Fix global bindings being lost when running tests under headless contexts --- osu.Game/Tests/Visual/OsuTestScene.cs | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/osu.Game/Tests/Visual/OsuTestScene.cs b/osu.Game/Tests/Visual/OsuTestScene.cs index f00cefaefd..b59a1db403 100644 --- a/osu.Game/Tests/Visual/OsuTestScene.cs +++ b/osu.Game/Tests/Visual/OsuTestScene.cs @@ -69,12 +69,26 @@ namespace osu.Game.Tests.Visual /// protected virtual bool UseOnlineAPI => false; + /// + /// When running headless, there is an opportunity to use the host storage rather than creating a second isolated one. + /// This is because the host is recycled per TestScene execution in headless at an nunit level. + /// + private Storage isolatedHostStorage; + protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) { + if (!UseFreshStoragePerRun) + isolatedHostStorage = (parent.Get() as HeadlessGameHost)?.Storage; + contextFactory = new Lazy(() => { var factory = new DatabaseContextFactory(LocalStorage); - factory.ResetDatabase(); + + // only reset the database if not using the host storage. + // if we reset the host storage, it will delete global key bindings. + if (isolatedHostStorage == null) + factory.ResetDatabase(); + using (var usage = factory.Get()) usage.Migrate(); return factory; @@ -135,12 +149,9 @@ namespace osu.Game.Tests.Visual } localStorage = - new Lazy(() => !UseFreshStoragePerRun && host is HeadlessGameHost ? host.Storage : new NativeStorage(Path.Combine(RuntimeInfo.StartupDirectory, $"{GetType().Name}-{Guid.NewGuid()}"))); + new Lazy(() => isolatedHostStorage ?? new NativeStorage(Path.Combine(RuntimeInfo.StartupDirectory, $"{GetType().Name}-{Guid.NewGuid()}"))); } - [Resolved] - private GameHost host { get; set; } - [Resolved] protected AudioManager Audio { get; private set; } From 9063c60b9cb79ee5fb6db07f736a8510d8371861 Mon Sep 17 00:00:00 2001 From: Joehu Date: Wed, 16 Sep 2020 12:00:27 -0700 Subject: [PATCH 1113/1782] Fix profile section tab control not absorbing input from behind --- osu.Game/Overlays/UserProfileOverlay.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game/Overlays/UserProfileOverlay.cs b/osu.Game/Overlays/UserProfileOverlay.cs index 2b316c0e34..d52ad84592 100644 --- a/osu.Game/Overlays/UserProfileOverlay.cs +++ b/osu.Game/Overlays/UserProfileOverlay.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.Framework.Input.Events; using osu.Game.Graphics.Containers; using osu.Game.Online.API.Requests; using osu.Game.Overlays.Profile; @@ -176,6 +177,10 @@ namespace osu.Game.Overlays AccentColour = colourProvider.Highlight1; } + protected override bool OnClick(ClickEvent e) => true; + + protected override bool OnHover(HoverEvent e) => true; + private class ProfileSectionTabItem : OverlayTabItem { public ProfileSectionTabItem(ProfileSection value) From 3529a1bfeacf3c765731871d54db2b7f01911fb4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 16 Sep 2020 19:36:36 +0900 Subject: [PATCH 1114/1782] Fix global bindings being lost when running tests under headless contexts --- osu.Game/Tests/Visual/OsuTestScene.cs | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/osu.Game/Tests/Visual/OsuTestScene.cs b/osu.Game/Tests/Visual/OsuTestScene.cs index f00cefaefd..b59a1db403 100644 --- a/osu.Game/Tests/Visual/OsuTestScene.cs +++ b/osu.Game/Tests/Visual/OsuTestScene.cs @@ -69,12 +69,26 @@ namespace osu.Game.Tests.Visual /// protected virtual bool UseOnlineAPI => false; + /// + /// When running headless, there is an opportunity to use the host storage rather than creating a second isolated one. + /// This is because the host is recycled per TestScene execution in headless at an nunit level. + /// + private Storage isolatedHostStorage; + protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) { + if (!UseFreshStoragePerRun) + isolatedHostStorage = (parent.Get() as HeadlessGameHost)?.Storage; + contextFactory = new Lazy(() => { var factory = new DatabaseContextFactory(LocalStorage); - factory.ResetDatabase(); + + // only reset the database if not using the host storage. + // if we reset the host storage, it will delete global key bindings. + if (isolatedHostStorage == null) + factory.ResetDatabase(); + using (var usage = factory.Get()) usage.Migrate(); return factory; @@ -135,12 +149,9 @@ namespace osu.Game.Tests.Visual } localStorage = - new Lazy(() => !UseFreshStoragePerRun && host is HeadlessGameHost ? host.Storage : new NativeStorage(Path.Combine(RuntimeInfo.StartupDirectory, $"{GetType().Name}-{Guid.NewGuid()}"))); + new Lazy(() => isolatedHostStorage ?? new NativeStorage(Path.Combine(RuntimeInfo.StartupDirectory, $"{GetType().Name}-{Guid.NewGuid()}"))); } - [Resolved] - private GameHost host { get; set; } - [Resolved] protected AudioManager Audio { get; private set; } From d2580ebc7023b9730dbf4fe4e047dcd597946803 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 17 Sep 2020 13:01:34 +0900 Subject: [PATCH 1115/1782] Attempt to fix tests by avoiding clash between import tests names --- osu.Game.Tests/Skins/IO/ImportSkinTest.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Skins/IO/ImportSkinTest.cs b/osu.Game.Tests/Skins/IO/ImportSkinTest.cs index 14eff4c5e3..ef5ff0e75d 100644 --- a/osu.Game.Tests/Skins/IO/ImportSkinTest.cs +++ b/osu.Game.Tests/Skins/IO/ImportSkinTest.cs @@ -22,7 +22,7 @@ namespace osu.Game.Tests.Skins.IO [Test] public async Task TestBasicImport() { - using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportSkinTest))) { try { @@ -43,7 +43,7 @@ namespace osu.Game.Tests.Skins.IO [Test] public async Task TestImportTwiceWithSameMetadata() { - using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportSkinTest))) { try { @@ -68,7 +68,7 @@ namespace osu.Game.Tests.Skins.IO [Test] public async Task TestImportTwiceWithNoMetadata() { - using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportSkinTest))) { try { @@ -94,7 +94,7 @@ namespace osu.Game.Tests.Skins.IO [Test] public async Task TestImportTwiceWithDifferentMetadata() { - using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportSkinTest))) { try { From 0b289d2e779140930d8fd90c3de13bd3b0dcef8d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 17 Sep 2020 13:07:05 +0900 Subject: [PATCH 1116/1782] Add hostname differentiation to beatmap tests too --- .../Beatmaps/IO/ImportBeatmapTest.cs | 42 +++++++++---------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs index dd3dba1274..bc6fbed07a 100644 --- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs @@ -34,7 +34,7 @@ namespace osu.Game.Tests.Beatmaps.IO public async Task TestImportWhenClosed() { // unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here. - using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportBeatmapTest))) { try { @@ -51,7 +51,7 @@ namespace osu.Game.Tests.Beatmaps.IO public async Task TestImportThenDelete() { // unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here. - using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportBeatmapTest))) { try { @@ -72,7 +72,7 @@ namespace osu.Game.Tests.Beatmaps.IO public async Task TestImportThenImport() { // unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here. - using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportBeatmapTest))) { try { @@ -98,7 +98,7 @@ namespace osu.Game.Tests.Beatmaps.IO [Test] public async Task TestImportThenImportWithReZip() { - using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportBeatmapTest))) { try { @@ -156,7 +156,7 @@ namespace osu.Game.Tests.Beatmaps.IO [Test] public async Task TestImportThenImportWithChangedFile() { - using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportBeatmapTest))) { try { @@ -207,7 +207,7 @@ namespace osu.Game.Tests.Beatmaps.IO [Test] public async Task TestImportThenImportWithDifferentFilename() { - using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportBeatmapTest))) { try { @@ -259,7 +259,7 @@ namespace osu.Game.Tests.Beatmaps.IO 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()) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportBeatmapTest))) { try { @@ -301,7 +301,7 @@ namespace osu.Game.Tests.Beatmaps.IO public async Task TestRollbackOnFailure() { // unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here. - using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportBeatmapTest))) { try { @@ -378,7 +378,7 @@ namespace osu.Game.Tests.Beatmaps.IO public async Task TestImportThenDeleteThenImport() { // unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here. - using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportBeatmapTest))) { try { @@ -406,7 +406,7 @@ namespace osu.Game.Tests.Beatmaps.IO public async Task TestImportThenDeleteThenImportWithOnlineIDMismatch(bool set) { // unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here. - using (HeadlessGameHost host = new CleanRunHeadlessGameHost(set.ToString())) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost($"{nameof(ImportBeatmapTest)}-{set}")) { try { @@ -440,7 +440,7 @@ namespace osu.Game.Tests.Beatmaps.IO public async Task TestImportWithDuplicateBeatmapIDs() { // unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here. - using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportBeatmapTest))) { try { @@ -496,8 +496,8 @@ namespace osu.Game.Tests.Beatmaps.IO [Ignore("Binding IPC on Appveyor isn't working (port in use). Need to figure out why")] public void TestImportOverIPC() { - using (HeadlessGameHost host = new CleanRunHeadlessGameHost("host", true)) - using (HeadlessGameHost client = new CleanRunHeadlessGameHost("client", true)) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost($"{nameof(ImportBeatmapTest)}-host", true)) + using (HeadlessGameHost client = new CleanRunHeadlessGameHost($"{nameof(ImportBeatmapTest)}-client", true)) { try { @@ -526,7 +526,7 @@ namespace osu.Game.Tests.Beatmaps.IO [Test] public async Task TestImportWhenFileOpen() { - using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportBeatmapTest))) { try { @@ -548,7 +548,7 @@ namespace osu.Game.Tests.Beatmaps.IO [Test] public async Task TestImportWithDuplicateHashes() { - using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportBeatmapTest))) { try { @@ -590,7 +590,7 @@ namespace osu.Game.Tests.Beatmaps.IO [Test] public async Task TestImportNestedStructure() { - using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportBeatmapTest))) { try { @@ -635,7 +635,7 @@ namespace osu.Game.Tests.Beatmaps.IO [Test] public async Task TestImportWithIgnoredDirectoryInArchive() { - using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportBeatmapTest))) { try { @@ -689,7 +689,7 @@ namespace osu.Game.Tests.Beatmaps.IO [Test] public async Task TestUpdateBeatmapInfo() { - using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportBeatmapTest))) { try { @@ -719,7 +719,7 @@ namespace osu.Game.Tests.Beatmaps.IO [Test] public async Task TestUpdateBeatmapFile() { - using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportBeatmapTest))) { try { @@ -761,7 +761,7 @@ namespace osu.Game.Tests.Beatmaps.IO [Test] public void TestCreateNewEmptyBeatmap() { - using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportBeatmapTest))) { try { @@ -788,7 +788,7 @@ namespace osu.Game.Tests.Beatmaps.IO [Test] public void TestCreateNewBeatmapWithObject() { - using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportBeatmapTest))) { try { From 835c8d74b77298faef44e1b37a45596e9ff93cd9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 17 Sep 2020 16:12:18 +0900 Subject: [PATCH 1117/1782] Wait for two update frames before attempting to migrate storage --- osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs b/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs index 199e69a19d..17e6b712f3 100644 --- a/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs +++ b/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs @@ -272,8 +272,16 @@ namespace osu.Game.Tests.NonVisual { var osu = new OsuGameBase(); Task.Run(() => host.Run(osu)); + waitForOrAssert(() => osu.IsLoaded, @"osu! failed to start in a reasonable amount of time"); + bool ready = false; + // wait for two update frames to be executed. this ensures that all components have had a change to run LoadComplete and hopefully avoid + // database access (GlobalActionContainer is one to do this). + host.UpdateThread.Scheduler.Add(() => host.UpdateThread.Scheduler.Add(() => ready = true)); + + waitForOrAssert(() => ready, @"osu! failed to start in a reasonable amount of time"); + return osu; } From 89a2f20922fea81dc585b9681d43a2f995157c17 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 17 Sep 2020 16:12:30 +0900 Subject: [PATCH 1118/1782] Use new CleanRun host class in import tests --- .../NonVisual/CustomDataDirectoryTest.cs | 25 ++++++++++--------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs b/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs index 17e6b712f3..211fa4ca42 100644 --- a/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs +++ b/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs @@ -4,6 +4,7 @@ using System; using System.IO; using System.Linq; +using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; using NUnit.Framework; @@ -22,7 +23,7 @@ namespace osu.Game.Tests.NonVisual [Test] public void TestDefaultDirectory() { - using (HeadlessGameHost host = new CustomTestHeadlessGameHost(nameof(TestDefaultDirectory))) + using (var host = new CustomTestHeadlessGameHost()) { try { @@ -45,7 +46,7 @@ namespace osu.Game.Tests.NonVisual { string customPath = prepareCustomPath(); - using (var host = new CustomTestHeadlessGameHost(nameof(TestCustomDirectory))) + using (var host = new CustomTestHeadlessGameHost()) { using (var storageConfig = new StorageConfigManager(host.InitialStorage)) storageConfig.Set(StorageConfig.FullPath, customPath); @@ -71,7 +72,7 @@ namespace osu.Game.Tests.NonVisual { string customPath = prepareCustomPath(); - using (var host = new CustomTestHeadlessGameHost(nameof(TestSubDirectoryLookup))) + using (var host = new CustomTestHeadlessGameHost()) { using (var storageConfig = new StorageConfigManager(host.InitialStorage)) storageConfig.Set(StorageConfig.FullPath, customPath); @@ -104,7 +105,7 @@ namespace osu.Game.Tests.NonVisual { string customPath = prepareCustomPath(); - using (HeadlessGameHost host = new CustomTestHeadlessGameHost(nameof(TestMigration))) + using (var host = new CustomTestHeadlessGameHost()) { try { @@ -165,7 +166,7 @@ namespace osu.Game.Tests.NonVisual string customPath = prepareCustomPath(); string customPath2 = prepareCustomPath("-2"); - using (HeadlessGameHost host = new CustomTestHeadlessGameHost(nameof(TestMigrationBetweenTwoTargets))) + using (var host = new CustomTestHeadlessGameHost()) { try { @@ -194,7 +195,7 @@ namespace osu.Game.Tests.NonVisual { string customPath = prepareCustomPath(); - using (HeadlessGameHost host = new CustomTestHeadlessGameHost(nameof(TestMigrationToSameTargetFails))) + using (var host = new CustomTestHeadlessGameHost()) { try { @@ -215,7 +216,7 @@ namespace osu.Game.Tests.NonVisual { string customPath = prepareCustomPath(); - using (HeadlessGameHost host = new CustomTestHeadlessGameHost(nameof(TestMigrationToNestedTargetFails))) + using (var host = new CustomTestHeadlessGameHost()) { try { @@ -244,7 +245,7 @@ namespace osu.Game.Tests.NonVisual { string customPath = prepareCustomPath(); - using (HeadlessGameHost host = new CustomTestHeadlessGameHost(nameof(TestMigrationToSeeminglyNestedTarget))) + using (var host = new CustomTestHeadlessGameHost()) { try { @@ -315,14 +316,14 @@ namespace osu.Game.Tests.NonVisual return path; } - public class CustomTestHeadlessGameHost : HeadlessGameHost + public class CustomTestHeadlessGameHost : CleanRunHeadlessGameHost { public Storage InitialStorage { get; } - public CustomTestHeadlessGameHost(string name) - : base(name) + public CustomTestHeadlessGameHost([CallerMemberName] string callingMethodName = @"") + : base(callingMethodName: callingMethodName) { - string defaultStorageLocation = getDefaultLocationFor(name); + string defaultStorageLocation = getDefaultLocationFor(callingMethodName); InitialStorage = new DesktopStorage(defaultStorageLocation, this); InitialStorage.DeleteDirectory(string.Empty); From 81f0a06fc41a5d3332303133ac885c4595717d5a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 17 Sep 2020 16:30:34 +0900 Subject: [PATCH 1119/1782] Fix potential endless taiko beatmap conversion --- osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs index 2a1aa5d1df..e6f6b9faac 100644 --- a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs +++ b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs @@ -8,6 +8,7 @@ using osu.Game.Rulesets.Taiko.Objects; using System; using System.Collections.Generic; using System.Linq; +using osu.Framework.Utils; using osu.Game.Audio; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.Formats; @@ -87,6 +88,9 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps { List> allSamples = obj is IHasPathWithRepeats curveData ? curveData.NodeSamples : new List>(new[] { samples }); + if (Precision.AlmostEquals(0, tickSpacing)) + yield break; + int i = 0; for (double j = obj.StartTime; j <= obj.StartTime + taikoDuration + tickSpacing / 8; j += tickSpacing) From 73a7b759cb048146ff1afb27e63367c5eb95eb27 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 17 Sep 2020 17:04:44 +0900 Subject: [PATCH 1120/1782] Add missing obsoletion notice --- osu.Game/Rulesets/Objects/HitObject.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Rulesets/Objects/HitObject.cs b/osu.Game/Rulesets/Objects/HitObject.cs index 1d60b266e3..0dfde834ee 100644 --- a/osu.Game/Rulesets/Objects/HitObject.cs +++ b/osu.Game/Rulesets/Objects/HitObject.cs @@ -145,6 +145,7 @@ namespace osu.Game.Rulesets.Objects #pragma warning restore 618 } + [Obsolete("Use the cancellation-supporting override")] // Can be removed 20210318 protected virtual void CreateNestedHitObjects() { } From 009e1b44450309991b4e660fef58b41a4df4ad58 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 17 Sep 2020 17:05:24 +0900 Subject: [PATCH 1121/1782] Make Spinner use cancellation token --- osu.Game.Rulesets.Osu/Objects/Spinner.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Spinner.cs b/osu.Game.Rulesets.Osu/Objects/Spinner.cs index 1658a4e7c2..194aa640f9 100644 --- a/osu.Game.Rulesets.Osu/Objects/Spinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Spinner.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; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Judgements; @@ -48,14 +49,16 @@ namespace osu.Game.Rulesets.Osu.Objects MaximumBonusSpins = (int)((maximum_rotations_per_second - minimumRotationsPerSecond) * secondsDuration); } - protected override void CreateNestedHitObjects() + protected override void CreateNestedHitObjects(CancellationToken cancellationToken) { - base.CreateNestedHitObjects(); + base.CreateNestedHitObjects(cancellationToken); int totalSpins = MaximumBonusSpins + SpinsRequired; for (int i = 0; i < totalSpins; i++) { + cancellationToken.ThrowIfCancellationRequested(); + double startTime = StartTime + (float)(i + 1) / totalSpins * Duration; AddNested(i < SpinsRequired From c7d24203ceb00022fe10d74be6e81d9434e89d6c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 17 Sep 2020 17:40:05 +0900 Subject: [PATCH 1122/1782] Make beatmap conversion support cancellation tokens --- .../Beatmaps/CatchBeatmapConverter.cs | 3 ++- .../Beatmaps/ManiaBeatmapConverter.cs | 7 ++++--- .../Beatmaps/OsuBeatmapConverter.cs | 3 ++- .../Beatmaps/TaikoBeatmapConverter.cs | 7 ++++--- .../TestSceneDrawableScrollingRuleset.cs | 3 ++- osu.Game/Beatmaps/BeatmapConverter.cs | 21 +++++++++---------- osu.Game/Beatmaps/DummyWorkingBeatmap.cs | 3 ++- osu.Game/Beatmaps/IBeatmapConverter.cs | 5 ++++- osu.Game/Beatmaps/WorkingBeatmap.cs | 2 +- 9 files changed, 31 insertions(+), 23 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs index 145a40f5f5..34964fc4ae 100644 --- a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs +++ b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs @@ -5,6 +5,7 @@ using osu.Game.Beatmaps; using osu.Game.Rulesets.Catch.Objects; using System.Collections.Generic; using System.Linq; +using System.Threading; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Objects; using osu.Framework.Extensions.IEnumerableExtensions; @@ -20,7 +21,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps public override bool CanConvert() => Beatmap.HitObjects.All(h => h is IHasXPosition); - protected override IEnumerable ConvertHitObject(HitObject obj, IBeatmap beatmap) + protected override IEnumerable ConvertHitObject(HitObject obj, IBeatmap beatmap, CancellationToken cancellationToken) { var positionData = obj as IHasXPosition; var comboData = obj as IHasCombo; diff --git a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs index 211905835c..524ea27efa 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs @@ -5,6 +5,7 @@ using osu.Game.Rulesets.Mania.Objects; using System; using System.Linq; using System.Collections.Generic; +using System.Threading; using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Rulesets.Objects; @@ -68,14 +69,14 @@ namespace osu.Game.Rulesets.Mania.Beatmaps public override bool CanConvert() => Beatmap.HitObjects.All(h => h is IHasXPosition); - protected override Beatmap ConvertBeatmap(IBeatmap original) + protected override Beatmap ConvertBeatmap(IBeatmap original, CancellationToken cancellationToken) { BeatmapDifficulty difficulty = original.BeatmapInfo.BaseDifficulty; 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); + return base.ConvertBeatmap(original, cancellationToken); } protected override Beatmap CreateBeatmap() @@ -88,7 +89,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps return beatmap; } - protected override IEnumerable ConvertHitObject(HitObject original, IBeatmap beatmap) + protected override IEnumerable ConvertHitObject(HitObject original, IBeatmap beatmap, CancellationToken cancellationToken) { if (original is ManiaHitObject maniaOriginal) { diff --git a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs index fcad356a1c..a2fc4848af 100644 --- a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs +++ b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs @@ -8,6 +8,7 @@ using osu.Game.Rulesets.Osu.Objects; using System.Collections.Generic; using osu.Game.Rulesets.Objects.Types; using System.Linq; +using System.Threading; using osu.Game.Rulesets.Osu.UI; using osu.Framework.Extensions.IEnumerableExtensions; @@ -22,7 +23,7 @@ namespace osu.Game.Rulesets.Osu.Beatmaps public override bool CanConvert() => Beatmap.HitObjects.All(h => h is IHasPosition); - protected override IEnumerable ConvertHitObject(HitObject original, IBeatmap beatmap) + protected override IEnumerable ConvertHitObject(HitObject original, IBeatmap beatmap, CancellationToken cancellationToken) { var positionData = original as IHasPosition; var comboData = original as IHasCombo; diff --git a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs index 2a1aa5d1df..91e31aeced 100644 --- a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs +++ b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs @@ -8,6 +8,7 @@ using osu.Game.Rulesets.Taiko.Objects; using System; using System.Collections.Generic; using System.Linq; +using System.Threading; using osu.Game.Audio; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.Formats; @@ -48,14 +49,14 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps public override bool CanConvert() => true; - protected override Beatmap ConvertBeatmap(IBeatmap original) + protected override Beatmap ConvertBeatmap(IBeatmap original, CancellationToken cancellationToken) { // Rewrite the beatmap info to add the slider velocity multiplier original.BeatmapInfo = original.BeatmapInfo.Clone(); original.BeatmapInfo.BaseDifficulty = original.BeatmapInfo.BaseDifficulty.Clone(); original.BeatmapInfo.BaseDifficulty.SliderMultiplier *= LEGACY_VELOCITY_MULTIPLIER; - Beatmap converted = base.ConvertBeatmap(original); + Beatmap converted = base.ConvertBeatmap(original, cancellationToken); if (original.BeatmapInfo.RulesetID == 3) { @@ -72,7 +73,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps return converted; } - protected override IEnumerable ConvertHitObject(HitObject obj, IBeatmap beatmap) + protected override IEnumerable ConvertHitObject(HitObject obj, IBeatmap beatmap, CancellationToken cancellationToken) { // Old osu! used hit sounding to determine various hit type information IList samples = obj.Samples; diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs index bd7e894cf8..1a1babb4a8 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading; using NUnit.Framework; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -276,7 +277,7 @@ namespace osu.Game.Tests.Visual.Gameplay public override bool CanConvert() => true; - protected override IEnumerable ConvertHitObject(HitObject original, IBeatmap beatmap) + protected override IEnumerable ConvertHitObject(HitObject original, IBeatmap beatmap, CancellationToken cancellationToken) { yield return new TestHitObject { diff --git a/osu.Game/Beatmaps/BeatmapConverter.cs b/osu.Game/Beatmaps/BeatmapConverter.cs index 11fee030f8..3083cee07e 100644 --- a/osu.Game/Beatmaps/BeatmapConverter.cs +++ b/osu.Game/Beatmaps/BeatmapConverter.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading; using osu.Game.Rulesets; using osu.Game.Rulesets.Objects; @@ -36,34 +37,31 @@ namespace osu.Game.Beatmaps /// public abstract bool CanConvert(); - /// - /// Converts . - /// - /// The converted Beatmap. - public IBeatmap Convert() + public IBeatmap Convert(CancellationToken cancellationToken = default) { // We always operate on a clone of the original beatmap, to not modify it game-wide - return ConvertBeatmap(Beatmap.Clone()); + return ConvertBeatmap(Beatmap.Clone(), cancellationToken); } /// /// Performs the conversion of a Beatmap using this Beatmap Converter. /// /// The un-converted Beatmap. + /// The cancellation token. /// The converted Beatmap. - protected virtual Beatmap ConvertBeatmap(IBeatmap original) + protected virtual Beatmap ConvertBeatmap(IBeatmap original, CancellationToken cancellationToken) { var beatmap = CreateBeatmap(); beatmap.BeatmapInfo = original.BeatmapInfo; beatmap.ControlPointInfo = original.ControlPointInfo; - beatmap.HitObjects = convertHitObjects(original.HitObjects, original).OrderBy(s => s.StartTime).ToList(); + beatmap.HitObjects = convertHitObjects(original.HitObjects, original, cancellationToken).OrderBy(s => s.StartTime).ToList(); beatmap.Breaks = original.Breaks; return beatmap; } - private List convertHitObjects(IReadOnlyList hitObjects, IBeatmap beatmap) + private List convertHitObjects(IReadOnlyList hitObjects, IBeatmap beatmap, CancellationToken cancellationToken) { var result = new List(hitObjects.Count); @@ -75,7 +73,7 @@ namespace osu.Game.Beatmaps continue; } - var converted = ConvertHitObject(obj, beatmap); + var converted = ConvertHitObject(obj, beatmap, cancellationToken); if (ObjectConverted != null) { @@ -104,7 +102,8 @@ namespace osu.Game.Beatmaps /// /// The hit object to convert. /// The un-converted Beatmap. + /// The cancellation token. /// The converted hit object. - protected abstract IEnumerable ConvertHitObject(HitObject original, IBeatmap beatmap); + protected abstract IEnumerable ConvertHitObject(HitObject original, IBeatmap beatmap, CancellationToken cancellationToken); } } diff --git a/osu.Game/Beatmaps/DummyWorkingBeatmap.cs b/osu.Game/Beatmaps/DummyWorkingBeatmap.cs index af2a2ac250..fdc839ccff 100644 --- a/osu.Game/Beatmaps/DummyWorkingBeatmap.cs +++ b/osu.Game/Beatmaps/DummyWorkingBeatmap.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Threading; using JetBrains.Annotations; using osu.Framework.Audio; using osu.Framework.Audio.Track; @@ -76,7 +77,7 @@ namespace osu.Game.Beatmaps public bool CanConvert() => true; - public IBeatmap Convert() + public IBeatmap Convert(CancellationToken cancellationToken) { foreach (var obj in Beatmap.HitObjects) ObjectConverted?.Invoke(obj, obj.Yield()); diff --git a/osu.Game/Beatmaps/IBeatmapConverter.cs b/osu.Game/Beatmaps/IBeatmapConverter.cs index 173d5494ba..83d0ada1b9 100644 --- a/osu.Game/Beatmaps/IBeatmapConverter.cs +++ b/osu.Game/Beatmaps/IBeatmapConverter.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Threading; using osu.Game.Rulesets; using osu.Game.Rulesets.Objects; @@ -30,6 +31,8 @@ namespace osu.Game.Beatmaps /// /// Converts . /// - IBeatmap Convert(); + /// The cancellation token. + /// The converted Beatmap. + IBeatmap Convert(CancellationToken cancellationToken); } } diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs index d9780233d1..30382c444f 100644 --- a/osu.Game/Beatmaps/WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/WorkingBeatmap.cs @@ -109,7 +109,7 @@ namespace osu.Game.Beatmaps } // Convert - IBeatmap converted = converter.Convert(); + IBeatmap converted = converter.Convert(cancellationSource.Token); // Apply conversion mods to the result foreach (var mod in mods.OfType()) From e71991a53c068cb86bdc77396d7a6ec40640a9fe Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 17 Sep 2020 18:37:48 +0900 Subject: [PATCH 1123/1782] Add default token --- osu.Game/Beatmaps/DummyWorkingBeatmap.cs | 2 +- osu.Game/Beatmaps/IBeatmapConverter.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Beatmaps/DummyWorkingBeatmap.cs b/osu.Game/Beatmaps/DummyWorkingBeatmap.cs index fdc839ccff..c114358771 100644 --- a/osu.Game/Beatmaps/DummyWorkingBeatmap.cs +++ b/osu.Game/Beatmaps/DummyWorkingBeatmap.cs @@ -77,7 +77,7 @@ namespace osu.Game.Beatmaps public bool CanConvert() => true; - public IBeatmap Convert(CancellationToken cancellationToken) + public IBeatmap Convert(CancellationToken cancellationToken = default) { foreach (var obj in Beatmap.HitObjects) ObjectConverted?.Invoke(obj, obj.Yield()); diff --git a/osu.Game/Beatmaps/IBeatmapConverter.cs b/osu.Game/Beatmaps/IBeatmapConverter.cs index 83d0ada1b9..2833af8ca2 100644 --- a/osu.Game/Beatmaps/IBeatmapConverter.cs +++ b/osu.Game/Beatmaps/IBeatmapConverter.cs @@ -33,6 +33,6 @@ namespace osu.Game.Beatmaps /// /// The cancellation token. /// The converted Beatmap. - IBeatmap Convert(CancellationToken cancellationToken); + IBeatmap Convert(CancellationToken cancellationToken = default); } } From de5ef8a4715dc1c138c6cd3aba93f02765bdae95 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 17 Sep 2020 21:36:55 +0900 Subject: [PATCH 1124/1782] Rework to support obsoletion --- osu.Game/Beatmaps/BeatmapConverter.cs | 34 ++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/BeatmapConverter.cs b/osu.Game/Beatmaps/BeatmapConverter.cs index 3083cee07e..cb0b3a8d09 100644 --- a/osu.Game/Beatmaps/BeatmapConverter.cs +++ b/osu.Game/Beatmaps/BeatmapConverter.cs @@ -27,6 +27,8 @@ namespace osu.Game.Beatmaps public IBeatmap Beatmap { get; } + private CancellationToken cancellationToken; + protected BeatmapConverter(IBeatmap beatmap, Ruleset ruleset) { Beatmap = beatmap; @@ -39,6 +41,8 @@ namespace osu.Game.Beatmaps public IBeatmap Convert(CancellationToken cancellationToken = default) { + this.cancellationToken = cancellationToken; + // We always operate on a clone of the original beatmap, to not modify it game-wide return ConvertBeatmap(Beatmap.Clone(), cancellationToken); } @@ -51,6 +55,19 @@ namespace osu.Game.Beatmaps /// The converted Beatmap. protected virtual Beatmap ConvertBeatmap(IBeatmap original, CancellationToken cancellationToken) { +#pragma warning disable 618 + return ConvertBeatmap(original); +#pragma warning restore 618 + } + + /// + /// Performs the conversion of a Beatmap using this Beatmap Converter. + /// + /// The un-converted Beatmap. + /// The converted Beatmap. + [Obsolete("Use the cancellation-supporting override")] // Can be removed 20210318 + protected virtual Beatmap ConvertBeatmap(IBeatmap original) + { var beatmap = CreateBeatmap(); beatmap.BeatmapInfo = original.BeatmapInfo; @@ -104,6 +121,21 @@ namespace osu.Game.Beatmaps /// The un-converted Beatmap. /// The cancellation token. /// The converted hit object. - protected abstract IEnumerable ConvertHitObject(HitObject original, IBeatmap beatmap, CancellationToken cancellationToken); + protected virtual IEnumerable ConvertHitObject(HitObject original, IBeatmap beatmap, CancellationToken cancellationToken) + { +#pragma warning disable 618 + return ConvertHitObject(original, beatmap); +#pragma warning restore 618 + } + + /// + /// Performs the conversion of a hit object. + /// This method is generally executed sequentially for all objects in a beatmap. + /// + /// The hit object to convert. + /// The un-converted Beatmap. + /// The converted hit object. + [Obsolete("Use the cancellation-supporting override")] // Can be removed 20210318 + protected virtual IEnumerable ConvertHitObject(HitObject original, IBeatmap beatmap) => Enumerable.Empty(); } } From 83d23c954712e401758a2591ceff241c2384ae14 Mon Sep 17 00:00:00 2001 From: Joehu Date: Thu, 17 Sep 2020 14:56:08 -0700 Subject: [PATCH 1125/1782] Use new icon in chat overlay --- osu.Game/Overlays/ChatOverlay.cs | 11 ++++++----- osu.Game/Overlays/OverlayTitle.cs | 4 +++- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/osu.Game/Overlays/ChatOverlay.cs b/osu.Game/Overlays/ChatOverlay.cs index 25a59e9b25..c53eccf78b 100644 --- a/osu.Game/Overlays/ChatOverlay.cs +++ b/osu.Game/Overlays/ChatOverlay.cs @@ -23,6 +23,7 @@ using osu.Game.Overlays.Chat.Selection; using osu.Game.Overlays.Chat.Tabs; using osuTK.Input; using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Textures; namespace osu.Game.Overlays { @@ -78,7 +79,7 @@ namespace osu.Game.Overlays } [BackgroundDependencyLoader] - private void load(OsuConfigManager config, OsuColour colours) + private void load(OsuConfigManager config, OsuColour colours, TextureStore textures) { const float padding = 5; @@ -163,13 +164,13 @@ namespace osu.Game.Overlays RelativeSizeAxes = Axes.Both, Colour = Color4.Black, }, - new SpriteIcon + new Sprite { - Icon = FontAwesome.Solid.Comments, + Texture = textures.Get(IconTexture), Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, - Size = new Vector2(20), - Margin = new MarginPadding(10), + Size = new Vector2(OverlayTitle.ICON_SIZE), + Margin = new MarginPadding { Left = 10 }, }, ChannelTabControl = CreateChannelTabControl().With(d => { diff --git a/osu.Game/Overlays/OverlayTitle.cs b/osu.Game/Overlays/OverlayTitle.cs index 17eeece1f8..c3ea35adfc 100644 --- a/osu.Game/Overlays/OverlayTitle.cs +++ b/osu.Game/Overlays/OverlayTitle.cs @@ -14,6 +14,8 @@ namespace osu.Game.Overlays { public abstract class OverlayTitle : CompositeDrawable, INamedOverlayComponent { + public const float ICON_SIZE = 30; + private readonly OsuSpriteText titleText; private readonly Container icon; @@ -51,7 +53,7 @@ namespace osu.Game.Overlays Anchor = Anchor.Centre, Origin = Anchor.Centre, Margin = new MarginPadding { Horizontal = 5 }, // compensates for osu-web sprites having around 5px of whitespace on each side - Size = new Vector2(30) + Size = new Vector2(ICON_SIZE) }, titleText = new OsuSpriteText { From 2ad7e6ca880ec24bc87d0ee3e8bfa6cba8a478b4 Mon Sep 17 00:00:00 2001 From: Joehu Date: Thu, 17 Sep 2020 19:10:58 -0700 Subject: [PATCH 1126/1782] Fix hovered channel tabs color when unselected --- osu.Game/Overlays/Chat/Tabs/ChannelTabItem.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Chat/Tabs/ChannelTabItem.cs b/osu.Game/Overlays/Chat/Tabs/ChannelTabItem.cs index 09dc06b95f..cca4dc33e5 100644 --- a/osu.Game/Overlays/Chat/Tabs/ChannelTabItem.cs +++ b/osu.Game/Overlays/Chat/Tabs/ChannelTabItem.cs @@ -211,7 +211,7 @@ namespace osu.Game.Overlays.Chat.Tabs TweenEdgeEffectTo(deactivateEdgeEffect, TRANSITION_LENGTH); - box.FadeColour(BackgroundInactive, TRANSITION_LENGTH, Easing.OutQuint); + box.FadeColour(IsHovered ? backgroundHover : BackgroundInactive, TRANSITION_LENGTH, Easing.OutQuint); highlightBox.FadeOut(TRANSITION_LENGTH, Easing.OutQuint); Text.Font = Text.Font.With(weight: FontWeight.Medium); From c62e4ef5e5b81954db33625748179186947bf53a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 18 Sep 2020 13:06:41 +0900 Subject: [PATCH 1127/1782] Allow one hitobject in taiko beatmap converter edge case --- osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs index 9e82161a61..ed7b8589ba 100644 --- a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs +++ b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs @@ -89,9 +89,6 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps { List> allSamples = obj is IHasPathWithRepeats curveData ? curveData.NodeSamples : new List>(new[] { samples }); - if (Precision.AlmostEquals(0, tickSpacing)) - yield break; - int i = 0; for (double j = obj.StartTime; j <= obj.StartTime + taikoDuration + tickSpacing / 8; j += tickSpacing) @@ -109,6 +106,9 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps }; i = (i + 1) % allSamples.Count; + + if (Precision.AlmostEquals(0, tickSpacing)) + break; } } else From 393ee1c9f5e4719efc9eb35ee97752cd7ae6d4ba Mon Sep 17 00:00:00 2001 From: Joehu Date: Thu, 17 Sep 2020 23:09:09 -0700 Subject: [PATCH 1128/1782] Fix hovered osu tab items not showing hover state when deselected --- osu.Game/Graphics/UserInterface/OsuTabControl.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/OsuTabControl.cs b/osu.Game/Graphics/UserInterface/OsuTabControl.cs index 61501b0cd8..dbcce9a84a 100644 --- a/osu.Game/Graphics/UserInterface/OsuTabControl.cs +++ b/osu.Game/Graphics/UserInterface/OsuTabControl.cs @@ -123,8 +123,8 @@ namespace osu.Game.Graphics.UserInterface protected void FadeUnhovered() { - Bar.FadeOut(transition_length, Easing.OutQuint); - Text.FadeColour(AccentColour, transition_length, Easing.OutQuint); + Bar.FadeTo(IsHovered ? 1 : 0, transition_length, Easing.OutQuint); + Text.FadeColour(IsHovered ? Color4.White : AccentColour, transition_length, Easing.OutQuint); } protected override bool OnHover(HoverEvent e) From 3cef93ee27dac06ea85c761bc4c24f19e17637ca Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 18 Sep 2020 18:05:33 +0900 Subject: [PATCH 1129/1782] Centralise import test helper methods --- .../Beatmaps/IO/ImportBeatmapTest.cs | 50 ++++++-------- .../Collections/IO/ImportCollectionsTest.cs | 62 ++--------------- osu.Game.Tests/ImportTest.cs | 66 +++++++++++++++++++ .../NonVisual/CustomDataDirectoryTest.cs | 47 +++---------- osu.Game.Tests/Scores/IO/ImportScoreTest.cs | 55 ++++------------ osu.Game.Tests/Skins/IO/ImportSkinTest.cs | 56 ++++------------ 6 files changed, 129 insertions(+), 207 deletions(-) create mode 100644 osu.Game.Tests/ImportTest.cs diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs index bc6fbed07a..80fbda8e1d 100644 --- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs @@ -28,7 +28,7 @@ using FileInfo = System.IO.FileInfo; namespace osu.Game.Tests.Beatmaps.IO { [TestFixture] - public class ImportBeatmapTest + public class ImportBeatmapTest : ImportTest { [Test] public async Task TestImportWhenClosed() @@ -38,7 +38,7 @@ namespace osu.Game.Tests.Beatmaps.IO { try { - await LoadOszIntoOsu(loadOsu(host)); + await LoadOszIntoOsu(LoadOsuIntoHost(host)); } finally { @@ -55,7 +55,7 @@ namespace osu.Game.Tests.Beatmaps.IO { try { - var osu = loadOsu(host); + var osu = LoadOsuIntoHost(host); var imported = await LoadOszIntoOsu(osu); @@ -76,7 +76,7 @@ namespace osu.Game.Tests.Beatmaps.IO { try { - var osu = loadOsu(host); + var osu = LoadOsuIntoHost(host); var imported = await LoadOszIntoOsu(osu); var importedSecondTime = await LoadOszIntoOsu(osu); @@ -102,7 +102,7 @@ namespace osu.Game.Tests.Beatmaps.IO { try { - var osu = loadOsu(host); + var osu = LoadOsuIntoHost(host); var temp = TestResources.GetTestBeatmapForImport(); @@ -160,7 +160,7 @@ namespace osu.Game.Tests.Beatmaps.IO { try { - var osu = loadOsu(host); + var osu = LoadOsuIntoHost(host); var temp = TestResources.GetTestBeatmapForImport(); @@ -211,7 +211,7 @@ namespace osu.Game.Tests.Beatmaps.IO { try { - var osu = loadOsu(host); + var osu = LoadOsuIntoHost(host); var temp = TestResources.GetTestBeatmapForImport(); @@ -263,7 +263,7 @@ namespace osu.Game.Tests.Beatmaps.IO { try { - var osu = loadOsu(host); + var osu = LoadOsuIntoHost(host); var imported = await LoadOszIntoOsu(osu); @@ -314,7 +314,7 @@ namespace osu.Game.Tests.Beatmaps.IO Interlocked.Increment(ref loggedExceptionCount); }; - var osu = loadOsu(host); + var osu = LoadOsuIntoHost(host); var manager = osu.Dependencies.Get(); // ReSharper disable once AccessToModifiedClosure @@ -382,7 +382,7 @@ namespace osu.Game.Tests.Beatmaps.IO { try { - var osu = loadOsu(host); + var osu = LoadOsuIntoHost(host); var imported = await LoadOszIntoOsu(osu); @@ -410,7 +410,7 @@ namespace osu.Game.Tests.Beatmaps.IO { try { - var osu = loadOsu(host); + var osu = LoadOsuIntoHost(host); var imported = await LoadOszIntoOsu(osu); @@ -444,7 +444,7 @@ namespace osu.Game.Tests.Beatmaps.IO { try { - var osu = loadOsu(host); + var osu = LoadOsuIntoHost(host); var metadata = new BeatmapMetadata { @@ -504,7 +504,7 @@ namespace osu.Game.Tests.Beatmaps.IO Assert.IsTrue(host.IsPrimaryInstance); Assert.IsFalse(client.IsPrimaryInstance); - var osu = loadOsu(host); + var osu = LoadOsuIntoHost(host); var temp = TestResources.GetTestBeatmapForImport(); @@ -530,7 +530,7 @@ namespace osu.Game.Tests.Beatmaps.IO { try { - var osu = loadOsu(host); + var osu = LoadOsuIntoHost(host); var temp = TestResources.GetTestBeatmapForImport(); using (File.OpenRead(temp)) await osu.Dependencies.Get().Import(temp); @@ -552,7 +552,7 @@ namespace osu.Game.Tests.Beatmaps.IO { try { - var osu = loadOsu(host); + var osu = LoadOsuIntoHost(host); var temp = TestResources.GetTestBeatmapForImport(); @@ -594,7 +594,7 @@ namespace osu.Game.Tests.Beatmaps.IO { try { - var osu = loadOsu(host); + var osu = LoadOsuIntoHost(host); var temp = TestResources.GetTestBeatmapForImport(); @@ -639,7 +639,7 @@ namespace osu.Game.Tests.Beatmaps.IO { try { - var osu = loadOsu(host); + var osu = LoadOsuIntoHost(host); var temp = TestResources.GetTestBeatmapForImport(); @@ -693,7 +693,7 @@ namespace osu.Game.Tests.Beatmaps.IO { try { - var osu = loadOsu(host); + var osu = LoadOsuIntoHost(host); var manager = osu.Dependencies.Get(); var temp = TestResources.GetTestBeatmapForImport(); @@ -723,7 +723,7 @@ namespace osu.Game.Tests.Beatmaps.IO { try { - var osu = loadOsu(host); + var osu = LoadOsuIntoHost(host); var manager = osu.Dependencies.Get(); var temp = TestResources.GetTestBeatmapForImport(); @@ -765,7 +765,7 @@ namespace osu.Game.Tests.Beatmaps.IO { try { - var osu = loadOsu(host); + var osu = LoadOsuIntoHost(host); var manager = osu.Dependencies.Get(); var working = manager.CreateNew(new OsuRuleset().RulesetInfo, User.SYSTEM_USER); @@ -792,7 +792,7 @@ namespace osu.Game.Tests.Beatmaps.IO { try { - var osu = loadOsu(host); + var osu = LoadOsuIntoHost(host); var manager = osu.Dependencies.Get(); var working = manager.CreateNew(new OsuRuleset().RulesetInfo, User.SYSTEM_USER); @@ -863,14 +863,6 @@ namespace osu.Game.Tests.Beatmaps.IO Assert.AreEqual(expected, osu.Dependencies.Get().QueryFiles(f => f.ReferenceCount == 1).Count()); } - private OsuGameBase loadOsu(GameHost host) - { - var osu = new OsuGameBase(); - Task.Run(() => host.Run(osu)); - waitForOrAssert(() => osu.IsLoaded, @"osu! failed to start in a reasonable amount of time"); - return osu; - } - private static void ensureLoaded(OsuGameBase osu, int timeout = 60000) { IEnumerable resultSets = null; diff --git a/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs b/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs index a79e0d0338..a8ee1bcc2e 100644 --- a/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs +++ b/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs @@ -4,18 +4,15 @@ using System; using System.IO; using System.Text; -using System.Threading; using System.Threading.Tasks; using NUnit.Framework; -using osu.Framework.Allocation; using osu.Framework.Platform; -using osu.Game.Collections; using osu.Game.Tests.Resources; namespace osu.Game.Tests.Collections.IO { [TestFixture] - public class ImportCollectionsTest + public class ImportCollectionsTest : ImportTest { [Test] public async Task TestImportEmptyDatabase() @@ -24,7 +21,7 @@ namespace osu.Game.Tests.Collections.IO { try { - var osu = loadOsu(host); + var osu = LoadOsuIntoHost(host); await osu.CollectionManager.Import(new MemoryStream()); @@ -44,7 +41,7 @@ namespace osu.Game.Tests.Collections.IO { try { - var osu = loadOsu(host); + var osu = LoadOsuIntoHost(host); await osu.CollectionManager.Import(TestResources.OpenResource("Collections/collections.db")); @@ -70,7 +67,7 @@ namespace osu.Game.Tests.Collections.IO { try { - var osu = loadOsu(host, true); + var osu = LoadOsuIntoHost(host, true); await osu.CollectionManager.Import(TestResources.OpenResource("Collections/collections.db")); @@ -101,7 +98,7 @@ namespace osu.Game.Tests.Collections.IO { AppDomain.CurrentDomain.UnhandledException += setException; - var osu = loadOsu(host, true); + var osu = LoadOsuIntoHost(host, true); using (var ms = new MemoryStream()) { @@ -135,7 +132,7 @@ namespace osu.Game.Tests.Collections.IO { try { - var osu = loadOsu(host, true); + var osu = LoadOsuIntoHost(host, true); await osu.CollectionManager.Import(TestResources.OpenResource("Collections/collections.db")); @@ -156,7 +153,7 @@ namespace osu.Game.Tests.Collections.IO { try { - var osu = loadOsu(host, true); + var osu = LoadOsuIntoHost(host, true); Assert.That(osu.CollectionManager.Collections.Count, Is.EqualTo(2)); @@ -172,50 +169,5 @@ namespace osu.Game.Tests.Collections.IO } } } - - private TestOsuGameBase loadOsu(GameHost host, bool withBeatmap = false) - { - var osu = new TestOsuGameBase(withBeatmap); - -#pragma warning disable 4014 - Task.Run(() => host.Run(osu)); -#pragma warning restore 4014 - - waitForOrAssert(() => osu.IsLoaded, @"osu! failed to start in a reasonable amount of time"); - - return osu; - } - - private void waitForOrAssert(Func result, string failureMessage, int timeout = 60000) - { - Task task = Task.Run(() => - { - while (!result()) Thread.Sleep(200); - }); - - Assert.IsTrue(task.Wait(timeout), failureMessage); - } - - private class TestOsuGameBase : OsuGameBase - { - public CollectionManager CollectionManager { get; private set; } - - private readonly bool withBeatmap; - - public TestOsuGameBase(bool withBeatmap) - { - this.withBeatmap = withBeatmap; - } - - [BackgroundDependencyLoader] - private void load() - { - // Beatmap must be imported before the collection manager is loaded. - if (withBeatmap) - BeatmapManager.Import(TestResources.GetTestBeatmapForImport()).Wait(); - - AddInternal(CollectionManager = new CollectionManager(Storage)); - } - } } } diff --git a/osu.Game.Tests/ImportTest.cs b/osu.Game.Tests/ImportTest.cs new file mode 100644 index 0000000000..ea351e0d45 --- /dev/null +++ b/osu.Game.Tests/ImportTest.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.Threading; +using System.Threading.Tasks; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Platform; +using osu.Game.Collections; +using osu.Game.Tests.Resources; + +namespace osu.Game.Tests +{ + public abstract class ImportTest + { + protected virtual TestOsuGameBase LoadOsuIntoHost(GameHost host, bool withBeatmap = false) + { + var osu = new TestOsuGameBase(withBeatmap); + Task.Run(() => host.Run(osu)); + + waitForOrAssert(() => osu.IsLoaded, @"osu! failed to start in a reasonable amount of time"); + + bool ready = false; + // wait for two update frames to be executed. this ensures that all components have had a change to run LoadComplete and hopefully avoid + // database access (GlobalActionContainer is one to do this). + host.UpdateThread.Scheduler.Add(() => host.UpdateThread.Scheduler.Add(() => ready = true)); + + waitForOrAssert(() => ready, @"osu! failed to start in a reasonable amount of time"); + + return osu; + } + + private void waitForOrAssert(Func result, string failureMessage, int timeout = 60000) + { + Task task = Task.Run(() => + { + while (!result()) Thread.Sleep(200); + }); + + Assert.IsTrue(task.Wait(timeout), failureMessage); + } + + public class TestOsuGameBase : OsuGameBase + { + public CollectionManager CollectionManager { get; private set; } + + private readonly bool withBeatmap; + + public TestOsuGameBase(bool withBeatmap) + { + this.withBeatmap = withBeatmap; + } + + [BackgroundDependencyLoader] + private void load() + { + // Beatmap must be imported before the collection manager is loaded. + if (withBeatmap) + BeatmapManager.Import(TestResources.GetTestBeatmapForImport()).Wait(); + + AddInternal(CollectionManager = new CollectionManager(Storage)); + } + } + } +} diff --git a/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs b/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs index 211fa4ca42..b6ab73eceb 100644 --- a/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs +++ b/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs @@ -5,8 +5,6 @@ using System; using System.IO; using System.Linq; using System.Runtime.CompilerServices; -using System.Threading; -using System.Threading.Tasks; using NUnit.Framework; using osu.Framework; using osu.Framework.Allocation; @@ -18,7 +16,7 @@ using osu.Game.IO; namespace osu.Game.Tests.NonVisual { [TestFixture] - public class CustomDataDirectoryTest + public class CustomDataDirectoryTest : ImportTest { [Test] public void TestDefaultDirectory() @@ -29,7 +27,7 @@ namespace osu.Game.Tests.NonVisual { string defaultStorageLocation = getDefaultLocationFor(nameof(TestDefaultDirectory)); - var osu = loadOsu(host); + var osu = LoadOsuIntoHost(host); var storage = osu.Dependencies.Get(); Assert.That(storage.GetFullPath("."), Is.EqualTo(defaultStorageLocation)); @@ -53,7 +51,7 @@ namespace osu.Game.Tests.NonVisual try { - var osu = loadOsu(host); + var osu = LoadOsuIntoHost(host); // switch to DI'd storage var storage = osu.Dependencies.Get(); @@ -79,7 +77,7 @@ namespace osu.Game.Tests.NonVisual try { - var osu = loadOsu(host); + var osu = LoadOsuIntoHost(host); // switch to DI'd storage var storage = osu.Dependencies.Get(); @@ -111,7 +109,7 @@ namespace osu.Game.Tests.NonVisual { string defaultStorageLocation = getDefaultLocationFor(nameof(TestMigration)); - var osu = loadOsu(host); + var osu = LoadOsuIntoHost(host); var storage = osu.Dependencies.Get(); // Store the current storage's path. We'll need to refer to this for assertions in the original directory after the migration completes. @@ -170,7 +168,7 @@ namespace osu.Game.Tests.NonVisual { try { - var osu = loadOsu(host); + var osu = LoadOsuIntoHost(host); const string database_filename = "client.db"; @@ -199,7 +197,7 @@ namespace osu.Game.Tests.NonVisual { try { - var osu = loadOsu(host); + var osu = LoadOsuIntoHost(host); Assert.DoesNotThrow(() => osu.Migrate(customPath)); Assert.Throws(() => osu.Migrate(customPath)); @@ -220,7 +218,7 @@ namespace osu.Game.Tests.NonVisual { try { - var osu = loadOsu(host); + var osu = LoadOsuIntoHost(host); Assert.DoesNotThrow(() => osu.Migrate(customPath)); @@ -249,7 +247,7 @@ namespace osu.Game.Tests.NonVisual { try { - var osu = loadOsu(host); + var osu = LoadOsuIntoHost(host); Assert.DoesNotThrow(() => osu.Migrate(customPath)); @@ -269,33 +267,6 @@ namespace osu.Game.Tests.NonVisual } } - private OsuGameBase loadOsu(GameHost host) - { - var osu = new OsuGameBase(); - Task.Run(() => host.Run(osu)); - - waitForOrAssert(() => osu.IsLoaded, @"osu! failed to start in a reasonable amount of time"); - - bool ready = false; - // wait for two update frames to be executed. this ensures that all components have had a change to run LoadComplete and hopefully avoid - // database access (GlobalActionContainer is one to do this). - host.UpdateThread.Scheduler.Add(() => host.UpdateThread.Scheduler.Add(() => ready = true)); - - waitForOrAssert(() => ready, @"osu! failed to start in a reasonable amount of time"); - - return osu; - } - - private static void waitForOrAssert(Func result, string failureMessage, int timeout = 60000) - { - Task task = Task.Run(() => - { - while (!result()) Thread.Sleep(200); - }); - - Assert.IsTrue(task.Wait(timeout), failureMessage); - } - private static string getDefaultLocationFor(string testTypeName) { string path = Path.Combine(RuntimeInfo.StartupDirectory, "headless", testTypeName); diff --git a/osu.Game.Tests/Scores/IO/ImportScoreTest.cs b/osu.Game.Tests/Scores/IO/ImportScoreTest.cs index a4d20714fa..7522aca5dc 100644 --- a/osu.Game.Tests/Scores/IO/ImportScoreTest.cs +++ b/osu.Game.Tests/Scores/IO/ImportScoreTest.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; -using System.Threading; using System.Threading.Tasks; using NUnit.Framework; using osu.Framework.Allocation; @@ -17,12 +16,11 @@ using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; -using osu.Game.Tests.Resources; using osu.Game.Users; namespace osu.Game.Tests.Scores.IO { - public class ImportScoreTest + public class ImportScoreTest : ImportTest { [Test] public async Task TestBasicImport() @@ -31,7 +29,7 @@ namespace osu.Game.Tests.Scores.IO { try { - var osu = await loadOsu(host); + var osu = LoadOsuIntoHost(host, true); var toImport = new ScoreInfo { @@ -45,7 +43,7 @@ namespace osu.Game.Tests.Scores.IO OnlineScoreID = 12345, }; - var imported = await loadIntoOsu(osu, toImport); + var imported = await loadScoreIntoOsu(osu, toImport); Assert.AreEqual(toImport.Rank, imported.Rank); Assert.AreEqual(toImport.TotalScore, imported.TotalScore); @@ -70,14 +68,14 @@ namespace osu.Game.Tests.Scores.IO { try { - var osu = await loadOsu(host); + var osu = LoadOsuIntoHost(host, true); var toImport = new ScoreInfo { Mods = new Mod[] { new OsuModHardRock(), new OsuModDoubleTime() }, }; - var imported = await loadIntoOsu(osu, toImport); + var imported = await loadScoreIntoOsu(osu, toImport); Assert.IsTrue(imported.Mods.Any(m => m is OsuModHardRock)); Assert.IsTrue(imported.Mods.Any(m => m is OsuModDoubleTime)); @@ -96,7 +94,7 @@ namespace osu.Game.Tests.Scores.IO { try { - var osu = await loadOsu(host); + var osu = LoadOsuIntoHost(host, true); var toImport = new ScoreInfo { @@ -107,7 +105,7 @@ namespace osu.Game.Tests.Scores.IO } }; - var imported = await loadIntoOsu(osu, toImport); + var imported = await loadScoreIntoOsu(osu, toImport); Assert.AreEqual(toImport.Statistics[HitResult.Perfect], imported.Statistics[HitResult.Perfect]); Assert.AreEqual(toImport.Statistics[HitResult.Miss], imported.Statistics[HitResult.Miss]); @@ -126,7 +124,7 @@ namespace osu.Game.Tests.Scores.IO { try { - var osu = await loadOsu(host); + var osu = LoadOsuIntoHost(host, true); var toImport = new ScoreInfo { @@ -138,7 +136,7 @@ namespace osu.Game.Tests.Scores.IO } }; - var imported = await loadIntoOsu(osu, toImport); + var imported = await loadScoreIntoOsu(osu, toImport); var beatmapManager = osu.Dependencies.Get(); var scoreManager = osu.Dependencies.Get(); @@ -146,7 +144,7 @@ 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)); - var secondImport = await loadIntoOsu(osu, imported); + var secondImport = await loadScoreIntoOsu(osu, imported); Assert.That(secondImport, Is.Null); } finally @@ -163,9 +161,9 @@ namespace osu.Game.Tests.Scores.IO { try { - var osu = await loadOsu(host); + var osu = LoadOsuIntoHost(host, true); - await loadIntoOsu(osu, new ScoreInfo { OnlineScoreID = 2 }, new TestArchiveReader()); + await loadScoreIntoOsu(osu, new ScoreInfo { OnlineScoreID = 2 }, new TestArchiveReader()); var scoreManager = osu.Dependencies.Get(); @@ -179,7 +177,7 @@ namespace osu.Game.Tests.Scores.IO } } - private async Task loadIntoOsu(OsuGameBase osu, ScoreInfo score, ArchiveReader archive = null) + private async Task loadScoreIntoOsu(OsuGameBase osu, ScoreInfo score, ArchiveReader archive = null) { var beatmapManager = osu.Dependencies.Get(); @@ -192,33 +190,6 @@ namespace osu.Game.Tests.Scores.IO return scoreManager.GetAllUsableScores().FirstOrDefault(); } - private async Task loadOsu(GameHost host) - { - var osu = new OsuGameBase(); - -#pragma warning disable 4014 - Task.Run(() => host.Run(osu)); -#pragma warning restore 4014 - - waitForOrAssert(() => osu.IsLoaded, @"osu! failed to start in a reasonable amount of time"); - - var beatmapFile = TestResources.GetTestBeatmapForImport(); - var beatmapManager = osu.Dependencies.Get(); - await beatmapManager.Import(beatmapFile); - - return osu; - } - - private void waitForOrAssert(Func result, string failureMessage, int timeout = 60000) - { - Task task = Task.Run(() => - { - while (!result()) Thread.Sleep(200); - }); - - Assert.IsTrue(task.Wait(timeout), failureMessage); - } - private class TestArchiveReader : ArchiveReader { public TestArchiveReader() diff --git a/osu.Game.Tests/Skins/IO/ImportSkinTest.cs b/osu.Game.Tests/Skins/IO/ImportSkinTest.cs index ef5ff0e75d..a5b4b04ef5 100644 --- a/osu.Game.Tests/Skins/IO/ImportSkinTest.cs +++ b/osu.Game.Tests/Skins/IO/ImportSkinTest.cs @@ -4,20 +4,17 @@ using System; using System.IO; using System.Linq; -using System.Threading; using System.Threading.Tasks; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Platform; -using osu.Game.Beatmaps; using osu.Game.IO.Archives; using osu.Game.Skinning; -using osu.Game.Tests.Resources; using SharpCompress.Archives.Zip; namespace osu.Game.Tests.Skins.IO { - public class ImportSkinTest + public class ImportSkinTest : ImportTest { [Test] public async Task TestBasicImport() @@ -26,9 +23,9 @@ namespace osu.Game.Tests.Skins.IO { try { - var osu = await loadOsu(host); + var osu = LoadOsuIntoHost(host); - var imported = await loadIntoOsu(osu, new ZipArchiveReader(createOsk("test skin", "skinner"), "skin.osk")); + var imported = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOsk("test skin", "skinner"), "skin.osk")); Assert.That(imported.Name, Is.EqualTo("test skin")); Assert.That(imported.Creator, Is.EqualTo("skinner")); @@ -47,10 +44,10 @@ namespace osu.Game.Tests.Skins.IO { try { - var osu = await loadOsu(host); + var osu = LoadOsuIntoHost(host); - var imported = await loadIntoOsu(osu, new ZipArchiveReader(createOsk("test skin", "skinner"), "skin.osk")); - var imported2 = await loadIntoOsu(osu, new ZipArchiveReader(createOsk("test skin", "skinner"), "skin2.osk")); + var imported = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOsk("test skin", "skinner"), "skin.osk")); + var imported2 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOsk("test skin", "skinner"), "skin2.osk")); Assert.That(imported2.ID, Is.Not.EqualTo(imported.ID)); Assert.That(osu.Dependencies.Get().GetAllUserSkins().Count, Is.EqualTo(1)); @@ -72,11 +69,11 @@ namespace osu.Game.Tests.Skins.IO { try { - var osu = await loadOsu(host); + var osu = LoadOsuIntoHost(host); // if a user downloads two skins that do have skin.ini files but don't have any creator metadata in the skin.ini, they should both import separately just for safety. - var imported = await loadIntoOsu(osu, new ZipArchiveReader(createOsk(string.Empty, string.Empty), "download.osk")); - var imported2 = await loadIntoOsu(osu, new ZipArchiveReader(createOsk(string.Empty, string.Empty), "download.osk")); + var imported = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOsk(string.Empty, string.Empty), "download.osk")); + var imported2 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOsk(string.Empty, string.Empty), "download.osk")); Assert.That(imported2.ID, Is.Not.EqualTo(imported.ID)); Assert.That(osu.Dependencies.Get().GetAllUserSkins().Count, Is.EqualTo(2)); @@ -98,10 +95,10 @@ namespace osu.Game.Tests.Skins.IO { try { - var osu = await loadOsu(host); + var osu = LoadOsuIntoHost(host); - var imported = await loadIntoOsu(osu, new ZipArchiveReader(createOsk("test skin v2", "skinner"), "skin.osk")); - var imported2 = await loadIntoOsu(osu, new ZipArchiveReader(createOsk("test skin v2.1", "skinner"), "skin2.osk")); + var imported = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOsk("test skin v2", "skinner"), "skin.osk")); + var imported2 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOsk("test skin v2.1", "skinner"), "skin2.osk")); Assert.That(imported2.ID, Is.Not.EqualTo(imported.ID)); Assert.That(osu.Dependencies.Get().GetAllUserSkins().Count, Is.EqualTo(2)); @@ -141,37 +138,10 @@ namespace osu.Game.Tests.Skins.IO return stream; } - private async Task loadIntoOsu(OsuGameBase osu, ArchiveReader archive = null) + private async Task loadSkinIntoOsu(OsuGameBase osu, ArchiveReader archive = null) { var skinManager = osu.Dependencies.Get(); return await skinManager.Import(archive); } - - private async Task loadOsu(GameHost host) - { - var osu = new OsuGameBase(); - -#pragma warning disable 4014 - Task.Run(() => host.Run(osu)); -#pragma warning restore 4014 - - waitForOrAssert(() => osu.IsLoaded, @"osu! failed to start in a reasonable amount of time"); - - var beatmapFile = TestResources.GetTestBeatmapForImport(); - var beatmapManager = osu.Dependencies.Get(); - await beatmapManager.Import(beatmapFile); - - return osu; - } - - private void waitForOrAssert(Func result, string failureMessage, int timeout = 60000) - { - Task task = Task.Run(() => - { - while (!result()) Thread.Sleep(200); - }); - - Assert.IsTrue(task.Wait(timeout), failureMessage); - } } } From 1fcf443314f2d75d34d791d4a13c8a2e3af8547f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 18 Sep 2020 19:33:03 +0900 Subject: [PATCH 1130/1782] Ensure BeatmapProcessor.PostProcess is run before firing HitObjectUpdated events --- osu.Game/Screens/Edit/EditorBeatmap.cs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Edit/EditorBeatmap.cs b/osu.Game/Screens/Edit/EditorBeatmap.cs index 3a9bd85b0f..3248c5b8be 100644 --- a/osu.Game/Screens/Edit/EditorBeatmap.cs +++ b/osu.Game/Screens/Edit/EditorBeatmap.cs @@ -207,14 +207,15 @@ namespace osu.Game.Screens.Edit beatmapProcessor?.PreProcess(); foreach (var hitObject in pendingUpdates) - { processHitObject(hitObject); - HitObjectUpdated?.Invoke(hitObject); - } - - pendingUpdates.Clear(); beatmapProcessor?.PostProcess(); + + // explicitly needs to be fired after PostProcess + foreach (var hitObject in pendingUpdates) + HitObjectUpdated?.Invoke(hitObject); + + pendingUpdates.Clear(); } } From 735b6b0d6ffd51dc96d9b74c6e5a07af5193aed4 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 19 Sep 2020 05:54:06 +0300 Subject: [PATCH 1131/1782] Remove a pointless portion of the inline comment --- osu.Game/Skinning/SkinnableSound.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Skinning/SkinnableSound.cs b/osu.Game/Skinning/SkinnableSound.cs index 9e49134806..ba14049b41 100644 --- a/osu.Game/Skinning/SkinnableSound.cs +++ b/osu.Game/Skinning/SkinnableSound.cs @@ -140,8 +140,7 @@ namespace osu.Game.Skinning samplesContainer.ChildrenEnumerable = channels.Select(c => new DrawableSample(c)); - // Sample channels have been reloaded to new ones because skin has changed. - // Start playback internally for them if they were playing previously. + // Start playback internally for the new samples if the previous ones were playing beforehand. if (wasPlaying) play(); } From b3ffd36b656ea39a6d5b6d3cb2a344716c67aa17 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 19 Sep 2020 05:55:28 +0300 Subject: [PATCH 1132/1782] Use lambda expression instead --- osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs index 5f39a57d8a..eb3636ab66 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs @@ -123,10 +123,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("pause gameplay clock", () => gameplayClock.IsPaused.Value = true); AddUntilStep("wait for sample to stop playing", () => !sample.Playing); - AddStep("trigger skin change", () => - { - skinSource.TriggerSourceChanged(); - }); + AddStep("trigger skin change", () => skinSource.TriggerSourceChanged()); AddStep("retrieve new sample", () => { From 1e1422c16a52219c1a2b9c79f546172d1a943e6a Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 19 Sep 2020 05:55:39 +0300 Subject: [PATCH 1133/1782] Samples don't get paused... --- osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs index eb3636ab66..ab66ee252c 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs @@ -132,7 +132,7 @@ namespace osu.Game.Tests.Visual.Gameplay sample = newSample; }); - AddAssert("new sample paused", () => !sample.Playing); + AddAssert("new sample stopped", () => !sample.Playing); AddStep("resume gameplay clock", () => gameplayClock.IsPaused.Value = false); AddWaitStep("wait a bit", 5); From 522e2cdbcdf41c3e7816caa51032ee30bf6ca6c6 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 19 Sep 2020 05:56:35 +0300 Subject: [PATCH 1134/1782] Avoid embedding NUnit Assert inside test steps if possible --- osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs index ab66ee252c..ed75d83151 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs @@ -125,11 +125,11 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("trigger skin change", () => skinSource.TriggerSourceChanged()); - AddStep("retrieve new sample", () => + AddAssert("retrieve and ensure current sample is different", () => { - DrawableSample newSample = skinnableSound.ChildrenOfType().Single(); - Assert.IsTrue(newSample != sample, "Sample still hasn't been updated after a skin change event"); - sample = newSample; + DrawableSample oldSample = sample; + sample = skinnableSound.ChildrenOfType().Single(); + return sample != oldSample; }); AddAssert("new sample stopped", () => !sample.Playing); From 847ec8c248e640227d781e06ab4f3188d7c3c3b5 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Sat, 19 Sep 2020 14:52:05 +0900 Subject: [PATCH 1135/1782] Fix n^2 characteristic in taiko diffcalc --- .../Preprocessing/StaminaCheeseDetector.cs | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/StaminaCheeseDetector.cs b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/StaminaCheeseDetector.cs index d07bff4369..3b1a9ad777 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/StaminaCheeseDetector.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/StaminaCheeseDetector.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.Game.Rulesets.Difficulty.Utils; using osu.Game.Rulesets.Taiko.Objects; @@ -67,6 +68,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing // as that index can be simply subtracted from the current index to get the number of elements in between // without off-by-one errors int indexBeforeLastRepeat = -1; + int lastMarkEnd = 0; for (int i = 0; i < hitObjects.Count; i++) { @@ -87,7 +89,8 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing if (repeatedLength < roll_min_repetitions) continue; - markObjectsAsCheese(i, repeatedLength); + markObjectsAsCheese(Math.Max(lastMarkEnd, i - repeatedLength + 1), i); + lastMarkEnd = i; } } @@ -113,6 +116,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing private void findTlTap(int parity, HitType type) { int tlLength = -2; + int lastMarkEnd = 0; for (int i = parity; i < hitObjects.Count; i += 2) { @@ -124,17 +128,18 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing if (tlLength < tl_min_repetitions) continue; - markObjectsAsCheese(i, tlLength); + markObjectsAsCheese(Math.Max(lastMarkEnd, i - tlLength + 1), i); + lastMarkEnd = i; } } /// - /// Marks elements counting backwards from as . + /// Marks all objects from to (inclusive) as . /// - private void markObjectsAsCheese(int end, int count) + private void markObjectsAsCheese(int start, int end) { - for (int i = 0; i < count; ++i) - hitObjects[end - i].StaminaCheese = true; + for (int i = start; i <= end; i++) + hitObjects[i].StaminaCheese = true; } } } From e0cef6686d5b80c53d460d540ab4843c12471d30 Mon Sep 17 00:00:00 2001 From: S Stewart Date: Sat, 19 Sep 2020 14:54:14 -0500 Subject: [PATCH 1136/1782] Change collection deletion notif to be consistent --- osu.Game/Collections/CollectionManager.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Collections/CollectionManager.cs b/osu.Game/Collections/CollectionManager.cs index a50ab5b07a..f96a689faf 100644 --- a/osu.Game/Collections/CollectionManager.cs +++ b/osu.Game/Collections/CollectionManager.cs @@ -12,6 +12,7 @@ using osu.Framework; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; using osu.Framework.Logging; using osu.Framework.Platform; using osu.Game.Beatmaps; @@ -230,7 +231,7 @@ namespace osu.Game.Collections public void DeleteAll() { Collections.Clear(); - PostNotification?.Invoke(new SimpleNotification { Text = "Deleted all collections!" }); + PostNotification?.Invoke(new ProgressCompletionNotification { Text = "Deleted all collections!"}); } private readonly object saveLock = new object(); From c49dcca1ff9b2946efe38548ed5ffe7f4e8356b8 Mon Sep 17 00:00:00 2001 From: S Stewart Date: Sat, 19 Sep 2020 14:55:52 -0500 Subject: [PATCH 1137/1782] spacing oops --- osu.Game/Collections/CollectionManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Collections/CollectionManager.cs b/osu.Game/Collections/CollectionManager.cs index f96a689faf..5f0f52125b 100644 --- a/osu.Game/Collections/CollectionManager.cs +++ b/osu.Game/Collections/CollectionManager.cs @@ -231,7 +231,7 @@ namespace osu.Game.Collections public void DeleteAll() { Collections.Clear(); - PostNotification?.Invoke(new ProgressCompletionNotification { Text = "Deleted all collections!"}); + PostNotification?.Invoke(new ProgressCompletionNotification { Text = "Deleted all collections!" }); } private readonly object saveLock = new object(); From d2f498a2689031099233203121e171c192fee810 Mon Sep 17 00:00:00 2001 From: S Stewart Date: Sat, 19 Sep 2020 15:13:52 -0500 Subject: [PATCH 1138/1782] remove unnec using --- osu.Game/Collections/CollectionManager.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Collections/CollectionManager.cs b/osu.Game/Collections/CollectionManager.cs index 5f0f52125b..569ac749a4 100644 --- a/osu.Game/Collections/CollectionManager.cs +++ b/osu.Game/Collections/CollectionManager.cs @@ -12,7 +12,6 @@ using osu.Framework; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Framework.Graphics.Sprites; using osu.Framework.Logging; using osu.Framework.Platform; using osu.Game.Beatmaps; From 026fc2023b9d47352ce302f5a1dff3747cec6245 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 20 Sep 2020 18:10:44 +0200 Subject: [PATCH 1139/1782] Add visual tests for strong hit explosions --- .../DrawableTestStrongHit.cs | 44 +++++++++++++++++++ .../Skinning/TestSceneHitExplosion.cs | 25 +++++++---- 2 files changed, 60 insertions(+), 9 deletions(-) create mode 100644 osu.Game.Rulesets.Taiko.Tests/DrawableTestStrongHit.cs diff --git a/osu.Game.Rulesets.Taiko.Tests/DrawableTestStrongHit.cs b/osu.Game.Rulesets.Taiko.Tests/DrawableTestStrongHit.cs new file mode 100644 index 0000000000..7cb984b254 --- /dev/null +++ b/osu.Game.Rulesets.Taiko.Tests/DrawableTestStrongHit.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 System.Linq; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Scoring; +using osu.Game.Rulesets.Taiko.Objects; +using osu.Game.Rulesets.Taiko.Objects.Drawables; + +namespace osu.Game.Rulesets.Taiko.Tests +{ + public class DrawableTestStrongHit : DrawableHit + { + private readonly HitResult type; + private readonly bool hitBoth; + + public DrawableTestStrongHit(double startTime, HitResult type = HitResult.Great, bool hitBoth = true) + : base(new Hit + { + IsStrong = true, + StartTime = startTime, + }) + { + // in order to create nested strong hit + HitObject.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); + + this.type = type; + this.hitBoth = hitBoth; + } + + protected override void LoadAsyncComplete() + { + base.LoadAsyncComplete(); + + Result.Type = type; + + var nestedStrongHit = (DrawableStrongNestedHit)NestedHitObjects.Single(); + nestedStrongHit.Result.Type = hitBoth ? type : HitResult.Miss; + } + + public override bool OnPressed(TaikoAction action) => false; + } +} diff --git a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneHitExplosion.cs b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneHitExplosion.cs index 2b5efec7f9..48969e0f5a 100644 --- a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneHitExplosion.cs +++ b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneHitExplosion.cs @@ -2,7 +2,6 @@ // 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.Graphics.Containers; using osu.Game.Rulesets.Scoring; @@ -15,24 +14,29 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning [TestFixture] public class TestSceneHitExplosion : TaikoSkinnableTestScene { - [BackgroundDependencyLoader] - private void load() + [Test] + public void TestNormalHit() { - AddStep("Great", () => SetContents(() => getContentFor(HitResult.Great))); - AddStep("Good", () => SetContents(() => getContentFor(HitResult.Good))); - AddStep("Miss", () => SetContents(() => getContentFor(HitResult.Miss))); + AddStep("Great", () => SetContents(() => getContentFor(createHit(HitResult.Great)))); + AddStep("Good", () => SetContents(() => getContentFor(createHit(HitResult.Good)))); + AddStep("Miss", () => SetContents(() => getContentFor(createHit(HitResult.Miss)))); } - private Drawable getContentFor(HitResult type) + [Test] + public void TestStrongHit([Values(false, true)] bool hitBoth) { - DrawableTaikoHitObject hit; + AddStep("Great", () => SetContents(() => getContentFor(createStrongHit(HitResult.Great, hitBoth)))); + AddStep("Good", () => SetContents(() => getContentFor(createStrongHit(HitResult.Good, hitBoth)))); + } + private Drawable getContentFor(DrawableTaikoHitObject hit) + { return new Container { RelativeSizeAxes = Axes.Both, Children = new Drawable[] { - hit = createHit(type), + hit, new HitExplosion(hit) { Anchor = Anchor.Centre, @@ -43,5 +47,8 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning } private DrawableTaikoHitObject createHit(HitResult type) => new DrawableTestHit(new Hit { StartTime = Time.Current }, type); + + private DrawableTaikoHitObject createStrongHit(HitResult type, bool hitBoth) + => new DrawableTestStrongHit(Time.Current, type, hitBoth); } } From 919b19612f06e622c799a001ac65aa508a1b0cdc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 20 Sep 2020 16:29:36 +0200 Subject: [PATCH 1140/1782] Add lookups for strong hit explosions --- .../Skinning/TaikoLegacySkinTransformer.cs | 8 ++++++++ osu.Game.Rulesets.Taiko/TaikoSkinComponents.cs | 2 ++ 2 files changed, 10 insertions(+) diff --git a/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacySkinTransformer.cs b/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacySkinTransformer.cs index f032c5f485..c222ccb51f 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacySkinTransformer.cs @@ -75,7 +75,9 @@ namespace osu.Game.Rulesets.Taiko.Skinning return null; case TaikoSkinComponents.TaikoExplosionGood: + case TaikoSkinComponents.TaikoExplosionGoodStrong: case TaikoSkinComponents.TaikoExplosionGreat: + case TaikoSkinComponents.TaikoExplosionGreatStrong: case TaikoSkinComponents.TaikoExplosionMiss: var sprite = this.GetAnimation(getHitName(taikoComponent.Component), true, false); @@ -107,8 +109,14 @@ namespace osu.Game.Rulesets.Taiko.Skinning case TaikoSkinComponents.TaikoExplosionGood: return "taiko-hit100"; + case TaikoSkinComponents.TaikoExplosionGoodStrong: + return "taiko-hit100k"; + case TaikoSkinComponents.TaikoExplosionGreat: return "taiko-hit300"; + + case TaikoSkinComponents.TaikoExplosionGreatStrong: + return "taiko-hit300k"; } throw new ArgumentOutOfRangeException(nameof(component), "Invalid result type"); diff --git a/osu.Game.Rulesets.Taiko/TaikoSkinComponents.cs b/osu.Game.Rulesets.Taiko/TaikoSkinComponents.cs index ac4fb51661..0d785adb4a 100644 --- a/osu.Game.Rulesets.Taiko/TaikoSkinComponents.cs +++ b/osu.Game.Rulesets.Taiko/TaikoSkinComponents.cs @@ -17,7 +17,9 @@ namespace osu.Game.Rulesets.Taiko BarLine, TaikoExplosionMiss, TaikoExplosionGood, + TaikoExplosionGoodStrong, TaikoExplosionGreat, + TaikoExplosionGreatStrong, Scroller, Mascot, } From 074387c6763ed34c157fc85ef8a4b950e4b8a6ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 20 Sep 2020 18:07:57 +0200 Subject: [PATCH 1141/1782] Show strong hit explosion where applicable --- osu.Game.Rulesets.Taiko/UI/HitExplosion.cs | 29 ++++++++++++++++++---- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/UI/HitExplosion.cs b/osu.Game.Rulesets.Taiko/UI/HitExplosion.cs index f0585b9c50..e3eabbf88f 100644 --- a/osu.Game.Rulesets.Taiko/UI/HitExplosion.cs +++ b/osu.Game.Rulesets.Taiko/UI/HitExplosion.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Linq; using osuTK; using osu.Framework.Allocation; using osu.Framework.Graphics; @@ -9,6 +10,7 @@ using osu.Framework.Graphics.Containers; 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.Skinning; namespace osu.Game.Rulesets.Taiko.UI @@ -45,24 +47,41 @@ namespace osu.Game.Rulesets.Taiko.UI [BackgroundDependencyLoader] private void load() { - Child = skinnable = new SkinnableDrawable(new TaikoSkinComponent(getComponentName(JudgedObject.Result?.Type ?? HitResult.Great)), _ => new DefaultHitExplosion()); + Child = skinnable = new SkinnableDrawable(new TaikoSkinComponent(getComponentName(JudgedObject)), _ => new DefaultHitExplosion()); } - private TaikoSkinComponents getComponentName(HitResult resultType) + private TaikoSkinComponents getComponentName(DrawableHitObject judgedObject) { + var resultType = judgedObject.Result?.Type ?? HitResult.Great; + switch (resultType) { case HitResult.Miss: return TaikoSkinComponents.TaikoExplosionMiss; case HitResult.Good: - return TaikoSkinComponents.TaikoExplosionGood; + return useStrongExplosion(judgedObject) + ? TaikoSkinComponents.TaikoExplosionGoodStrong + : TaikoSkinComponents.TaikoExplosionGood; case HitResult.Great: - return TaikoSkinComponents.TaikoExplosionGreat; + return useStrongExplosion(judgedObject) + ? TaikoSkinComponents.TaikoExplosionGreatStrong + : TaikoSkinComponents.TaikoExplosionGreat; } - throw new ArgumentOutOfRangeException(nameof(resultType), "Invalid result type"); + throw new ArgumentOutOfRangeException(nameof(judgedObject), "Invalid result type"); + } + + private bool useStrongExplosion(DrawableHitObject judgedObject) + { + if (!(judgedObject.HitObject is Hit)) + return false; + + if (!(judgedObject.NestedHitObjects.SingleOrDefault() is DrawableStrongNestedHit nestedHit)) + return false; + + return judgedObject.Result.Type == nestedHit.Result.Type; } /// From 1c7556ea5d67793c6f363a6e661b2d042896baf4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 20 Sep 2020 19:38:57 +0200 Subject: [PATCH 1142/1782] Schedule explosion addition to ensure both hits are processed --- osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs index dabdfe6f44..0e241be2bd 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs @@ -218,12 +218,16 @@ namespace osu.Game.Rulesets.Taiko.UI private void addDrumRollHit(DrawableDrumRollTick drawableTick) => drumRollHitContainer.Add(new DrawableFlyingHit(drawableTick)); - private void addExplosion(DrawableHitObject drawableObject, HitType type) + /// + /// As legacy skins have different explosions for singular and double strong hits, + /// explosion addition is scheduled to ensure that both hits are processed if they occur on the same frame. + /// + private void addExplosion(DrawableHitObject drawableObject, HitType type) => Schedule(() => { hitExplosionContainer.Add(new HitExplosion(drawableObject)); if (drawableObject.HitObject.Kiai) kiaiExplosionContainer.Add(new KiaiHitExplosion(drawableObject, type)); - } + }); private class ProxyContainer : LifetimeManagementContainer { From 4072abaed8ba980a5890656f6aa20b693cecf295 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 20 Sep 2020 16:26:33 +0200 Subject: [PATCH 1143/1782] Allow miss explosions to be displayed --- osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs index 0e241be2bd..7976d5bc6d 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs @@ -205,9 +205,6 @@ namespace osu.Game.Rulesets.Taiko.UI X = result.IsHit ? judgedObject.Position.X : 0, }); - if (!result.IsHit) - break; - var type = (judgedObject.HitObject as Hit)?.Type ?? HitType.Centre; addExplosion(judgedObject, type); From a0573af0e1edd56f58f8af59674de1ad95fa067a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 20 Sep 2020 20:44:31 +0200 Subject: [PATCH 1144/1782] Fix test failure due to uninitialised drawable hit object --- osu.Game.Rulesets.Taiko.Tests/TestSceneHits.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneHits.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneHits.cs index 44452d70c1..99d1b72ea4 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TestSceneHits.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneHits.cs @@ -174,7 +174,9 @@ namespace osu.Game.Rulesets.Taiko.Tests private void addMissJudgement() { - ((TaikoPlayfield)drawableRuleset.Playfield).OnNewResult(new DrawableTestHit(new Hit()), new JudgementResult(new HitObject(), new TaikoJudgement()) { Type = HitResult.Miss }); + DrawableTestHit h; + Add(h = new DrawableTestHit(new Hit(), HitResult.Miss)); + ((TaikoPlayfield)drawableRuleset.Playfield).OnNewResult(h, new JudgementResult(new HitObject(), new TaikoJudgement()) { Type = HitResult.Miss }); } private void addBarLine(bool major, double delay = scroll_time) From 5b697580afcf26ce430d6dc7abc991f7a1769946 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 21 Sep 2020 16:38:16 +0900 Subject: [PATCH 1145/1782] Add mention of ruleset templates to readme --- README.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index d3e9ca5121..7c749f3422 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,13 @@ If you are looking to install or test osu! without setting up a development envi If your platform is not listed above, there is still a chance you can manually build it by following the instructions below. -## Developing or debugging +## Developing a custom ruleset + +osu! is designed to have extensible modular gameplay modes, called "rulesets". Building one of these allows a developer to harness the power of osu! for their own game style. To get started working on a ruleset, we have some templates available [here](https://github.com/ppy/osu-templates). + +You can see some examples of custom rulesets by visiting the [custom ruleset directory](https://github.com/ppy/osu/issues/5852). + +## Developing osu! Please make sure you have the following prerequisites: From 842f8bea55bce02fffffe3980719a871c6d0422f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 21 Sep 2020 18:15:33 +0900 Subject: [PATCH 1146/1782] Fix bindings not correctly being cleaned up in OsuHitObjectComposer --- osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index f87bd53ec3..6513334977 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -46,13 +46,20 @@ namespace osu.Game.Rulesets.Osu.Edit distanceSnapToggle }; + private BindableList selectedHitObjects; + + private Bindable placementObject; + [BackgroundDependencyLoader] private void load() { LayerBelowRuleset.Add(distanceSnapGridContainer = new Container { RelativeSizeAxes = Axes.Both }); - EditorBeatmap.SelectedHitObjects.CollectionChanged += (_, __) => updateDistanceSnapGrid(); - EditorBeatmap.PlacementObject.ValueChanged += _ => updateDistanceSnapGrid(); + selectedHitObjects = EditorBeatmap.SelectedHitObjects.GetBoundCopy(); + selectedHitObjects.CollectionChanged += (_, __) => updateDistanceSnapGrid(); + + placementObject = EditorBeatmap.PlacementObject.GetBoundCopy(); + placementObject.ValueChanged += _ => updateDistanceSnapGrid(); distanceSnapToggle.ValueChanged += _ => updateDistanceSnapGrid(); } From dd5b15c64fb422ca50475b879ec9130f55f528e8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 21 Sep 2020 18:27:15 +0900 Subject: [PATCH 1147/1782] Fix HitObjectContainer not correctly unbinding from startTime fast enough --- osu.Game/Rulesets/UI/HitObjectContainer.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/osu.Game/Rulesets/UI/HitObjectContainer.cs b/osu.Game/Rulesets/UI/HitObjectContainer.cs index f4f66f1272..9a0217a1eb 100644 --- a/osu.Game/Rulesets/UI/HitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/HitObjectContainer.cs @@ -43,10 +43,20 @@ namespace osu.Game.Rulesets.UI return true; } + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + unbindStartTimeMap(); + } + public virtual void Clear(bool disposeChildren = true) { ClearInternal(disposeChildren); + unbindStartTimeMap(); + } + private void unbindStartTimeMap() + { foreach (var kvp in startTimeMap) kvp.Value.bindable.UnbindAll(); startTimeMap.Clear(); From 0cecb2bba348e9d178704d911339ba6df7a1b26b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 21 Sep 2020 19:33:19 +0900 Subject: [PATCH 1148/1782] Remove incorrect assumption from tests --- osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs index f7909071ea..9e78185272 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs @@ -7,7 +7,6 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; -using osu.Framework.Bindables; using osu.Framework.Graphics.Sprites; using osu.Framework.Testing; using osu.Framework.Timing; @@ -194,13 +193,7 @@ namespace osu.Game.Rulesets.Osu.Tests addSeekStep(0); - AddStep("adjust track rate", () => MusicController.CurrentTrack.AddAdjustment(AdjustableProperty.Tempo, new BindableDouble(rate))); - // autoplay replay frames use track time; - // if a spin takes 1000ms in track time and we're playing with a 2x rate adjustment, the spin will take 500ms of *real* time. - // therefore we need to apply the rate adjustment to the replay itself to change from track time to real time, - // as real time is what we care about for spinners - // (so we're making the spin take 1000ms in real time *always*, regardless of the track clock's rate). - transformReplay(replay => applyRateAdjustment(replay, rate)); + AddStep("adjust track rate", () => Player.GameplayClockContainer.UserPlaybackRate.Value = rate); addSeekStep(1000); AddAssert("progress almost same", () => Precision.AlmostEquals(expectedProgress, drawableSpinner.Progress, 0.05)); From 3f788da06d0aa4379f8133ce319dcde18cabe1fc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 21 Sep 2020 19:39:54 +0900 Subject: [PATCH 1149/1782] Fix SPM changing incorrectly with playback rate changes --- .../Pieces/SpinnerRotationTracker.cs | 7 +++- .../Rulesets/UI/FrameStabilityContainer.cs | 8 +++- osu.Game/Screens/Play/GameplayClock.cs | 24 +++++++++++ .../Screens/Play/GameplayClockContainer.cs | 42 +++++++++++++++++-- 4 files changed, 75 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerRotationTracker.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerRotationTracker.cs index f1a782cbb5..e949017ccf 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerRotationTracker.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerRotationTracker.cs @@ -2,11 +2,13 @@ // 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.Input.Events; using osu.Framework.Utils; +using osu.Game.Screens.Play; using osuTK; namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces @@ -77,6 +79,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces private bool rotationTransferred; + [Resolved] + private GameplayClock gameplayClock { get; set; } + protected override void Update() { base.Update(); @@ -126,7 +131,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces currentRotation += angle; // rate has to be applied each frame, because it's not guaranteed to be constant throughout playback // (see: ModTimeRamp) - RateAdjustedRotation += (float)(Math.Abs(angle) * Clock.Rate); + RateAdjustedRotation += (float)(Math.Abs(angle) * gameplayClock.TrueGameplayRate); } } } diff --git a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs index d574991fa0..b585a78f42 100644 --- a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs +++ b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs @@ -2,7 +2,9 @@ // 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.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Timing; @@ -59,7 +61,7 @@ namespace osu.Game.Rulesets.UI { if (clock != null) { - stabilityGameplayClock.ParentGameplayClock = parentGameplayClock = clock; + parentGameplayClock = stabilityGameplayClock.ParentGameplayClock = clock; GameplayClock.IsPaused.BindTo(clock.IsPaused); } } @@ -191,7 +193,9 @@ namespace osu.Game.Rulesets.UI private class StabilityGameplayClock : GameplayClock { - public IFrameBasedClock ParentGameplayClock; + public GameplayClock ParentGameplayClock; + + public override IEnumerable> NonGameplayAdjustments => ParentGameplayClock.NonGameplayAdjustments; public StabilityGameplayClock(FramedClock underlyingClock) : base(underlyingClock) diff --git a/osu.Game/Screens/Play/GameplayClock.cs b/osu.Game/Screens/Play/GameplayClock.cs index 4f2cf5005c..45da8816d6 100644 --- a/osu.Game/Screens/Play/GameplayClock.cs +++ b/osu.Game/Screens/Play/GameplayClock.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.Bindables; using osu.Framework.Timing; @@ -20,6 +22,11 @@ namespace osu.Game.Screens.Play public readonly BindableBool IsPaused = new BindableBool(); + /// + /// All adjustments applied to this clock which don't come from gameplay or mods. + /// + public virtual IEnumerable> NonGameplayAdjustments => Enumerable.Empty>(); + public GameplayClock(IFrameBasedClock underlyingClock) { this.underlyingClock = underlyingClock; @@ -29,6 +36,23 @@ namespace osu.Game.Screens.Play public double Rate => underlyingClock.Rate; + /// + /// The rate of gameplay when playback is at 100%. + /// This excludes any seeking / user adjustments. + /// + public double TrueGameplayRate + { + get + { + double baseRate = Rate; + + foreach (var adjustment in NonGameplayAdjustments) + baseRate /= adjustment.Value; + + return baseRate; + } + } + public bool IsRunning => underlyingClock.IsRunning; /// diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index 7a9cb3dddd..d5c3a7232f 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.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.Diagnostics.CodeAnalysis; using System.Linq; using System.Threading.Tasks; @@ -50,8 +51,10 @@ namespace osu.Game.Screens.Play /// /// The final clock which is exposed to underlying components. /// - [Cached] - public readonly GameplayClock GameplayClock; + public GameplayClock GameplayClock => localGameplayClock; + + [Cached(typeof(GameplayClock))] + private readonly LocalGameplayClock localGameplayClock; private Bindable userAudioOffset; @@ -79,7 +82,7 @@ namespace osu.Game.Screens.Play userOffsetClock = new HardwareCorrectionOffsetClock(platformOffsetClock); // the clock to be exposed via DI to children. - GameplayClock = new GameplayClock(userOffsetClock); + localGameplayClock = new LocalGameplayClock(userOffsetClock); GameplayClock.IsPaused.BindTo(IsPaused); } @@ -200,11 +203,26 @@ namespace osu.Game.Screens.Play protected override void Update() { if (!IsPaused.Value) + { userOffsetClock.ProcessFrame(); + } base.Update(); } + private double getTrueGameplayRate() + { + double baseRate = track.Rate; + + if (speedAdjustmentsApplied) + { + baseRate /= UserPlaybackRate.Value; + baseRate /= pauseFreqAdjust.Value; + } + + return baseRate; + } + private bool speedAdjustmentsApplied; private void updateRate() @@ -215,6 +233,9 @@ namespace osu.Game.Screens.Play track.AddAdjustment(AdjustableProperty.Frequency, pauseFreqAdjust); track.AddAdjustment(AdjustableProperty.Tempo, UserPlaybackRate); + localGameplayClock.MutableNonGameplayAdjustments.Add(pauseFreqAdjust); + localGameplayClock.MutableNonGameplayAdjustments.Add(UserPlaybackRate); + speedAdjustmentsApplied = true; } @@ -231,9 +252,24 @@ namespace osu.Game.Screens.Play track.RemoveAdjustment(AdjustableProperty.Frequency, pauseFreqAdjust); track.RemoveAdjustment(AdjustableProperty.Tempo, UserPlaybackRate); + localGameplayClock.MutableNonGameplayAdjustments.Remove(pauseFreqAdjust); + localGameplayClock.MutableNonGameplayAdjustments.Remove(UserPlaybackRate); + speedAdjustmentsApplied = false; } + public class LocalGameplayClock : GameplayClock + { + public readonly List> MutableNonGameplayAdjustments = new List>(); + + public override IEnumerable> NonGameplayAdjustments => MutableNonGameplayAdjustments; + + public LocalGameplayClock(FramedOffsetClock underlyingClock) + : base(underlyingClock) + { + } + } + private class HardwareCorrectionOffsetClock : FramedOffsetClock { // we always want to apply the same real-time offset, so it should be adjusted by the difference in playback rate (from realtime) to achieve this. From 508278505f71a7b72531787364f8ece28f07f9d7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 21 Sep 2020 19:40:57 +0900 Subject: [PATCH 1150/1782] Make local clock private --- 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 d5c3a7232f..4094de1c4f 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -258,7 +258,7 @@ namespace osu.Game.Screens.Play speedAdjustmentsApplied = false; } - public class LocalGameplayClock : GameplayClock + private class LocalGameplayClock : GameplayClock { public readonly List> MutableNonGameplayAdjustments = new List>(); From bfe332909c5312df0e1e338217463305a5a5b691 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 21 Sep 2020 14:25:36 +0300 Subject: [PATCH 1151/1782] Remove "hide combo counter on break time" feature for being too complex The combo counter will be hidden at most one second after the break has started anyways, so why not just remove this feature if the way of implementing it is complicated to be merged within the legacy counter implementation. --- .../TestSceneComboCounter.cs | 15 -------------- .../Skinning/LegacyComboCounter.cs | 20 ------------------- osu.Game/Screens/Play/GameplayBeatmap.cs | 5 ----- osu.Game/Screens/Play/Player.cs | 1 - 4 files changed, 41 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneComboCounter.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneComboCounter.cs index 89521d616d..e79792e04a 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneComboCounter.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneComboCounter.cs @@ -3,8 +3,6 @@ using System.Linq; using NUnit.Framework; -using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Utils; using osu.Game.Rulesets.Catch.Objects; @@ -12,7 +10,6 @@ using osu.Game.Rulesets.Catch.Objects.Drawables; using osu.Game.Rulesets.Catch.UI; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Scoring; -using osu.Game.Screens.Play; using osuTK; using osuTK.Graphics; @@ -21,20 +18,9 @@ namespace osu.Game.Rulesets.Catch.Tests public class TestSceneComboCounter : CatchSkinnableTestScene { private ScoreProcessor scoreProcessor; - private GameplayBeatmap gameplayBeatmap; - private readonly Bindable isBreakTime = new BindableBool(); private Color4 judgedObjectColour = Color4.White; - [BackgroundDependencyLoader] - private void load() - { - gameplayBeatmap = new GameplayBeatmap(CreateBeatmapForSkinProvider()); - gameplayBeatmap.IsBreakTime.BindTo(isBreakTime); - Dependencies.Cache(gameplayBeatmap); - Add(gameplayBeatmap); - } - [SetUp] public void SetUp() => Schedule(() => { @@ -54,7 +40,6 @@ namespace osu.Game.Rulesets.Catch.Tests AddRepeatStep("perform hit", () => performJudgement(HitResult.Perfect), 20); AddStep("perform miss", () => performJudgement(HitResult.Miss)); - AddToggleStep("toggle gameplay break", v => isBreakTime.Value = v); AddStep("randomize judged object colour", () => { judgedObjectColour = new Color4( diff --git a/osu.Game.Rulesets.Catch/Skinning/LegacyComboCounter.cs b/osu.Game.Rulesets.Catch/Skinning/LegacyComboCounter.cs index ccfabdc5fd..6a10ba5eb3 100644 --- a/osu.Game.Rulesets.Catch/Skinning/LegacyComboCounter.cs +++ b/osu.Game.Rulesets.Catch/Skinning/LegacyComboCounter.cs @@ -1,12 +1,9 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Catch.UI; -using osu.Game.Screens.Play; using osu.Game.Skinning; using osuTK; using osuTK.Graphics; @@ -47,23 +44,6 @@ namespace osu.Game.Rulesets.Catch.Skinning }; } - private IBindable isBreakTime; - - [Resolved(canBeNull: true)] - private GameplayBeatmap beatmap { get; set; } - - protected override void LoadComplete() - { - base.LoadComplete(); - - isBreakTime = beatmap?.IsBreakTime.GetBoundCopy(); - isBreakTime?.BindValueChanged(b => - { - if (b.NewValue) - this.FadeOut(400.0, Easing.OutQuint); - }); - } - public void DisplayInitialCombo(int combo) => updateCombo(combo, null, true); public void UpdateCombo(int combo, Color4? hitObjectColour) => updateCombo(combo, hitObjectColour, false); diff --git a/osu.Game/Screens/Play/GameplayBeatmap.cs b/osu.Game/Screens/Play/GameplayBeatmap.cs index d7eed73275..64894544f4 100644 --- a/osu.Game/Screens/Play/GameplayBeatmap.cs +++ b/osu.Game/Screens/Play/GameplayBeatmap.cs @@ -16,11 +16,6 @@ namespace osu.Game.Screens.Play { public readonly IBeatmap PlayableBeatmap; - /// - /// Whether the gameplay is currently in a break. - /// - public IBindable IsBreakTime { get; } = new Bindable(); - public GameplayBeatmap(IBeatmap playableBeatmap) { PlayableBeatmap = playableBeatmap; diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 478f88ab11..539f9227a3 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -632,7 +632,6 @@ namespace osu.Game.Screens.Play // bind component bindables. Background.IsBreakTime.BindTo(breakTracker.IsBreakTime); - gameplayBeatmap.IsBreakTime.BindTo(breakTracker.IsBreakTime); DimmableStoryboard.IsBreakTime.BindTo(breakTracker.IsBreakTime); Background.StoryboardReplacesBackground.BindTo(storyboardReplacesBackground); From 25bf160d942d8c1bdb6dac7951073a145fe57656 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 21 Sep 2020 22:30:14 +0900 Subject: [PATCH 1152/1782] Fix missing GameplayClock in some tests --- .../Objects/Drawables/Pieces/SpinnerRotationTracker.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerRotationTracker.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerRotationTracker.cs index e949017ccf..05ed38d241 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerRotationTracker.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerRotationTracker.cs @@ -79,7 +79,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces private bool rotationTransferred; - [Resolved] + [Resolved(canBeNull: true)] private GameplayClock gameplayClock { get; set; } protected override void Update() @@ -131,7 +131,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces currentRotation += angle; // rate has to be applied each frame, because it's not guaranteed to be constant throughout playback // (see: ModTimeRamp) - RateAdjustedRotation += (float)(Math.Abs(angle) * gameplayClock.TrueGameplayRate); + RateAdjustedRotation += (float)(Math.Abs(angle) * (gameplayClock?.TrueGameplayRate ?? Clock.Rate)); } } } From f629c33dc0544e920aeb18f0de40d0e4e1ea9887 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 22 Sep 2020 12:14:31 +0900 Subject: [PATCH 1153/1782] Make explosion additive to match stable --- osu.Game.Rulesets.Catch/Skinning/LegacyComboCounter.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Rulesets.Catch/Skinning/LegacyComboCounter.cs b/osu.Game.Rulesets.Catch/Skinning/LegacyComboCounter.cs index 6a10ba5eb3..cce8a81c00 100644 --- a/osu.Game.Rulesets.Catch/Skinning/LegacyComboCounter.cs +++ b/osu.Game.Rulesets.Catch/Skinning/LegacyComboCounter.cs @@ -89,6 +89,7 @@ namespace osu.Game.Rulesets.Catch.Skinning var explosion = new LegacyRollingCounter(skin, fontName, fontOverlap) { Alpha = 0.65f, + Blending = BlendingParameters.Additive, Anchor = Anchor.Centre, Origin = Anchor.Centre, Scale = new Vector2(1.5f), From a27a65bf03d1a2d5ac3a8ef98a41f867ef4528d8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 22 Sep 2020 12:24:26 +0900 Subject: [PATCH 1154/1782] Don't recreate explosion counter each increment --- .../Skinning/LegacyComboCounter.cs | 59 +++++++------------ 1 file changed, 22 insertions(+), 37 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Skinning/LegacyComboCounter.cs b/osu.Game.Rulesets.Catch/Skinning/LegacyComboCounter.cs index cce8a81c00..c3231e1e55 100644 --- a/osu.Game.Rulesets.Catch/Skinning/LegacyComboCounter.cs +++ b/osu.Game.Rulesets.Catch/Skinning/LegacyComboCounter.cs @@ -16,19 +16,14 @@ namespace osu.Game.Rulesets.Catch.Skinning /// public class LegacyComboCounter : CompositeDrawable, ICatchComboCounter { - private readonly ISkin skin; - - private readonly string fontName; - private readonly float fontOverlap; - private readonly LegacyRollingCounter counter; + private readonly LegacyRollingCounter explosion; + public LegacyComboCounter(ISkin skin) { - this.skin = skin; - - fontName = skin.GetConfig(LegacySetting.ComboPrefix)?.Value ?? "score"; - fontOverlap = skin.GetConfig(LegacySetting.ComboOverlap)?.Value ?? -2f; + var fontName = skin.GetConfig(LegacySetting.ComboPrefix)?.Value ?? "score"; + var fontOverlap = skin.GetConfig(LegacySetting.ComboOverlap)?.Value ?? -2f; AutoSizeAxes = Axes.Both; @@ -37,18 +32,27 @@ namespace osu.Game.Rulesets.Catch.Skinning Origin = Anchor.Centre; Scale = new Vector2(0.8f); - InternalChild = counter = new LegacyRollingCounter(skin, fontName, fontOverlap) + InternalChildren = new Drawable[] { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, + explosion = new LegacyRollingCounter(skin, fontName, fontOverlap) + { + Alpha = 0.65f, + Blending = BlendingParameters.Additive, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Scale = new Vector2(1.5f), + }, + counter = new LegacyRollingCounter(skin, fontName, fontOverlap) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, }; } public void DisplayInitialCombo(int combo) => updateCombo(combo, null, true); public void UpdateCombo(int combo, Color4? hitObjectColour) => updateCombo(combo, hitObjectColour, false); - private LegacyRollingCounter lastExplosion; - private void updateCombo(int combo, Color4? hitObjectColour, bool immediate) { // There may still be existing transforms to the counter (including value change after 250ms), @@ -59,17 +63,12 @@ namespace osu.Game.Rulesets.Catch.Skinning if (combo == 0) { counter.Current.Value = 0; - if (lastExplosion != null) - lastExplosion.Current.Value = 0; + explosion.Current.Value = 0; this.FadeOut(immediate ? 0.0 : 400.0, Easing.Out); return; } - // Remove last explosion to not conflict with the upcoming one. - if (lastExplosion != null) - RemoveInternal(lastExplosion); - this.FadeIn().Delay(1000.0).FadeOut(300.0); // For simplicity, in the case of rewinding we'll just set the counter to the current combo value. @@ -86,25 +85,11 @@ namespace osu.Game.Rulesets.Catch.Skinning counter.Delay(250.0).ScaleTo(1f).ScaleTo(1.1f, 60.0).Then().ScaleTo(1f, 30.0); - var explosion = new LegacyRollingCounter(skin, fontName, fontOverlap) - { - Alpha = 0.65f, - Blending = BlendingParameters.Additive, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Scale = new Vector2(1.5f), - Colour = hitObjectColour ?? Color4.White, - Depth = 1f, - }; - - AddInternal(explosion); + explosion.Colour = hitObjectColour ?? Color4.White; explosion.SetCountWithoutRolling(combo); - explosion.ScaleTo(1.9f, 400.0, Easing.Out) - .FadeOut(400.0) - .Expire(true); - - lastExplosion = explosion; + explosion.ScaleTo(1.5f).ScaleTo(1.9f, 400.0, Easing.Out) + .FadeOutFromOne(400.0); } } } From 92cda6bccb2e2ae4e5ca461b92d0fa42322726f7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 22 Sep 2020 12:27:47 +0900 Subject: [PATCH 1155/1782] Adjust xmldoc slightly --- osu.Game.Rulesets.Catch/Skinning/LegacyComboCounter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch/Skinning/LegacyComboCounter.cs b/osu.Game.Rulesets.Catch/Skinning/LegacyComboCounter.cs index c3231e1e55..320fc9c440 100644 --- a/osu.Game.Rulesets.Catch/Skinning/LegacyComboCounter.cs +++ b/osu.Game.Rulesets.Catch/Skinning/LegacyComboCounter.cs @@ -12,7 +12,7 @@ using static osu.Game.Skinning.LegacySkinConfiguration; namespace osu.Game.Rulesets.Catch.Skinning { /// - /// A combo counter implementation that visually behaves almost similar to osu!stable's combo counter. + /// A combo counter implementation that visually behaves almost similar to stable's osu!catch combo counter. /// public class LegacyComboCounter : CompositeDrawable, ICatchComboCounter { From 08d8975566b9a3474c3df1b3882e86e49fd3f649 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 22 Sep 2020 12:35:18 +0900 Subject: [PATCH 1156/1782] Remove DisplayInitialCombo method for simplicity --- .../Skinning/LegacyComboCounter.cs | 12 +++++++++--- osu.Game.Rulesets.Catch/UI/CatchComboDisplay.cs | 2 +- osu.Game.Rulesets.Catch/UI/ICatchComboCounter.cs | 12 +----------- 3 files changed, 11 insertions(+), 15 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Skinning/LegacyComboCounter.cs b/osu.Game.Rulesets.Catch/Skinning/LegacyComboCounter.cs index 320fc9c440..047d9b3602 100644 --- a/osu.Game.Rulesets.Catch/Skinning/LegacyComboCounter.cs +++ b/osu.Game.Rulesets.Catch/Skinning/LegacyComboCounter.cs @@ -50,11 +50,17 @@ namespace osu.Game.Rulesets.Catch.Skinning }; } - public void DisplayInitialCombo(int combo) => updateCombo(combo, null, true); - public void UpdateCombo(int combo, Color4? hitObjectColour) => updateCombo(combo, hitObjectColour, false); + private int lastDisplayedCombo; - private void updateCombo(int combo, Color4? hitObjectColour, bool immediate) + public void UpdateCombo(int combo, Color4? hitObjectColour = null) { + bool immediate = Time.Elapsed < 0; + + if (combo == lastDisplayedCombo) + return; + + lastDisplayedCombo = combo; + // There may still be existing transforms to the counter (including value change after 250ms), // finish them immediately before new transforms. counter.FinishTransforms(); diff --git a/osu.Game.Rulesets.Catch/UI/CatchComboDisplay.cs b/osu.Game.Rulesets.Catch/UI/CatchComboDisplay.cs index b53711e4ed..deb2cb99ed 100644 --- a/osu.Game.Rulesets.Catch/UI/CatchComboDisplay.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchComboDisplay.cs @@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Catch.UI protected override void SkinChanged(ISkinSource skin, bool allowFallback) { base.SkinChanged(skin, allowFallback); - ComboCounter?.DisplayInitialCombo(currentCombo); + ComboCounter?.UpdateCombo(currentCombo); } public void OnNewResult(DrawableCatchHitObject judgedObject, JudgementResult result) diff --git a/osu.Game.Rulesets.Catch/UI/ICatchComboCounter.cs b/osu.Game.Rulesets.Catch/UI/ICatchComboCounter.cs index 1363ed1352..cfb6879067 100644 --- a/osu.Game.Rulesets.Catch/UI/ICatchComboCounter.cs +++ b/osu.Game.Rulesets.Catch/UI/ICatchComboCounter.cs @@ -11,16 +11,6 @@ namespace osu.Game.Rulesets.Catch.UI /// public interface ICatchComboCounter : IDrawable { - /// - /// Updates the counter to display the provided as initial value. - /// The value should be immediately displayed without any animation. - /// - /// - /// This is required for when instantiating a combo counter in middle of accumulating combo (via skin change). - /// - /// The combo value to be displayed as initial. - void DisplayInitialCombo(int combo); - /// /// Updates the counter to animate a transition from the old combo value it had to the current provided one. /// @@ -29,6 +19,6 @@ namespace osu.Game.Rulesets.Catch.UI /// /// The new combo value. /// The colour of the object if hit, null on miss. - void UpdateCombo(int combo, Color4? hitObjectColour); + void UpdateCombo(int combo, Color4? hitObjectColour = null); } } From ffd4874ac0f6e98df62d7e09520e8ce46cead0b1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 22 Sep 2020 12:37:18 +0900 Subject: [PATCH 1157/1782] Remove unnecessary double suffixes --- osu.Game.Rulesets.Catch/Skinning/LegacyComboCounter.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Skinning/LegacyComboCounter.cs b/osu.Game.Rulesets.Catch/Skinning/LegacyComboCounter.cs index 047d9b3602..5dcc532a08 100644 --- a/osu.Game.Rulesets.Catch/Skinning/LegacyComboCounter.cs +++ b/osu.Game.Rulesets.Catch/Skinning/LegacyComboCounter.cs @@ -86,16 +86,16 @@ namespace osu.Game.Rulesets.Catch.Skinning return; } - counter.ScaleTo(1.5f).ScaleTo(0.8f, 250.0, Easing.Out) + counter.ScaleTo(1.5f).ScaleTo(0.8f, 250, Easing.Out) .OnComplete(c => c.SetCountWithoutRolling(combo)); - counter.Delay(250.0).ScaleTo(1f).ScaleTo(1.1f, 60.0).Then().ScaleTo(1f, 30.0); + counter.Delay(250).ScaleTo(1f).ScaleTo(1.1f, 60).Then().ScaleTo(1f, 30); explosion.Colour = hitObjectColour ?? Color4.White; explosion.SetCountWithoutRolling(combo); - explosion.ScaleTo(1.5f).ScaleTo(1.9f, 400.0, Easing.Out) - .FadeOutFromOne(400.0); + explosion.ScaleTo(1.5f).ScaleTo(1.9f, 400, Easing.Out) + .FadeOutFromOne(400); } } } From 1c58f568d61584ef5a5568b52c1eeefa9bcdf1d1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 22 Sep 2020 12:54:21 +0900 Subject: [PATCH 1158/1782] Simplify and reformat rewind/transform logic --- .../Skinning/LegacyComboCounter.cs | 48 ++++++++----------- .../UI/CatchComboDisplay.cs | 6 --- 2 files changed, 21 insertions(+), 33 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Skinning/LegacyComboCounter.cs b/osu.Game.Rulesets.Catch/Skinning/LegacyComboCounter.cs index 5dcc532a08..a87286da89 100644 --- a/osu.Game.Rulesets.Catch/Skinning/LegacyComboCounter.cs +++ b/osu.Game.Rulesets.Catch/Skinning/LegacyComboCounter.cs @@ -54,16 +54,14 @@ namespace osu.Game.Rulesets.Catch.Skinning public void UpdateCombo(int combo, Color4? hitObjectColour = null) { - bool immediate = Time.Elapsed < 0; - if (combo == lastDisplayedCombo) return; - lastDisplayedCombo = combo; - // There may still be existing transforms to the counter (including value change after 250ms), // finish them immediately before new transforms. - counter.FinishTransforms(); + counter.SetCountWithoutRolling(lastDisplayedCombo); + + lastDisplayedCombo = combo; // Combo fell to zero, roll down and fade out the counter. if (combo == 0) @@ -71,31 +69,27 @@ namespace osu.Game.Rulesets.Catch.Skinning counter.Current.Value = 0; explosion.Current.Value = 0; - this.FadeOut(immediate ? 0.0 : 400.0, Easing.Out); - return; + this.FadeOut(400, Easing.Out); } - - this.FadeIn().Delay(1000.0).FadeOut(300.0); - - // For simplicity, in the case of rewinding we'll just set the counter to the current combo value. - immediate |= Time.Elapsed < 0; - - if (immediate) + else { - counter.SetCountWithoutRolling(combo); - return; + this.FadeInFromZero().Delay(1000).FadeOut(300); + + counter.ScaleTo(1.5f) + .ScaleTo(0.8f, 250, Easing.Out) + .OnComplete(c => c.SetCountWithoutRolling(combo)); + + counter.Delay(250) + .ScaleTo(1f) + .ScaleTo(1.1f, 60).Then().ScaleTo(1f, 30); + + explosion.Colour = hitObjectColour ?? Color4.White; + + explosion.SetCountWithoutRolling(combo); + explosion.ScaleTo(1.5f) + .ScaleTo(1.9f, 400, Easing.Out) + .FadeOutFromOne(400); } - - counter.ScaleTo(1.5f).ScaleTo(0.8f, 250, Easing.Out) - .OnComplete(c => c.SetCountWithoutRolling(combo)); - - counter.Delay(250).ScaleTo(1f).ScaleTo(1.1f, 60).Then().ScaleTo(1f, 30); - - explosion.Colour = hitObjectColour ?? Color4.White; - - explosion.SetCountWithoutRolling(combo); - explosion.ScaleTo(1.5f).ScaleTo(1.9f, 400, Easing.Out) - .FadeOutFromOne(400); } } } diff --git a/osu.Game.Rulesets.Catch/UI/CatchComboDisplay.cs b/osu.Game.Rulesets.Catch/UI/CatchComboDisplay.cs index deb2cb99ed..58a3140bb5 100644 --- a/osu.Game.Rulesets.Catch/UI/CatchComboDisplay.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchComboDisplay.cs @@ -50,12 +50,6 @@ namespace osu.Game.Rulesets.Catch.UI if (!result.Judgement.AffectsCombo || !result.HasResult) return; - if (result.Type == HitResult.Miss) - { - updateCombo(result.ComboAtJudgement, null); - return; - } - updateCombo(result.ComboAtJudgement, judgedObject.AccentColour.Value); } From 1b261f177f8ea0f4b1ffc5bf554d5588daddeac6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 22 Sep 2020 13:17:53 +0900 Subject: [PATCH 1159/1782] Disable rewind handling --- osu.Game.Rulesets.Catch/Skinning/LegacyComboCounter.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch/Skinning/LegacyComboCounter.cs b/osu.Game.Rulesets.Catch/Skinning/LegacyComboCounter.cs index a87286da89..c8abc9e832 100644 --- a/osu.Game.Rulesets.Catch/Skinning/LegacyComboCounter.cs +++ b/osu.Game.Rulesets.Catch/Skinning/LegacyComboCounter.cs @@ -63,6 +63,14 @@ namespace osu.Game.Rulesets.Catch.Skinning lastDisplayedCombo = combo; + if (Time.Elapsed < 0) + { + // needs more work to make rewind somehow look good. + // basically we want the previous increment to play... or turning off RemoveCompletedTransforms (not feasible from a performance angle). + Hide(); + return; + } + // Combo fell to zero, roll down and fade out the counter. if (combo == 0) { @@ -73,7 +81,7 @@ namespace osu.Game.Rulesets.Catch.Skinning } else { - this.FadeInFromZero().Delay(1000).FadeOut(300); + this.FadeInFromZero().Then().Delay(1000).FadeOut(300); counter.ScaleTo(1.5f) .ScaleTo(0.8f, 250, Easing.Out) From 7c40071b21d047d3f14e92a4d876d8d11e8fc4c3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 22 Sep 2020 13:32:00 +0900 Subject: [PATCH 1160/1782] Revert changes to SkinnableTestScene but change load order --- osu.Game/Tests/Visual/SkinnableTestScene.cs | 33 ++++++++------------- 1 file changed, 12 insertions(+), 21 deletions(-) diff --git a/osu.Game/Tests/Visual/SkinnableTestScene.cs b/osu.Game/Tests/Visual/SkinnableTestScene.cs index 58e0b23fab..c0db05d5c5 100644 --- a/osu.Game/Tests/Visual/SkinnableTestScene.cs +++ b/osu.Game/Tests/Visual/SkinnableTestScene.cs @@ -153,38 +153,29 @@ namespace osu.Game.Tests.Visual { private readonly bool extrapolateAnimations; - private readonly HashSet legacyFontPrefixes = new HashSet(); - public TestLegacySkin(SkinInfo skin, IResourceStore storage, AudioManager audioManager, bool extrapolateAnimations) : base(skin, storage, audioManager, "skin.ini") { this.extrapolateAnimations = extrapolateAnimations; - - // use a direct string lookup instead of enum to avoid having to reference ruleset assemblies. - legacyFontPrefixes.Add(GetConfig("HitCirclePrefix")?.Value ?? "default"); - legacyFontPrefixes.Add(GetConfig("ScorePrefix")?.Value ?? "score"); - legacyFontPrefixes.Add(GetConfig("ComboPrefix")?.Value ?? "score"); } public override Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) { + var lookup = base.GetTexture(componentName, wrapModeS, wrapModeT); + + if (lookup != null) + return lookup; + // extrapolate frames to test longer animations - if (extrapolateAnimations && isAnimationComponent(componentName, out var number) && number < 60) - return base.GetTexture(componentName.Replace($"-{number}", $"-{number % 2}"), wrapModeS, wrapModeT); + if (extrapolateAnimations) + { + var match = Regex.Match(componentName, "-([0-9]*)"); - return base.GetTexture(componentName, wrapModeS, wrapModeT); - } + if (match.Length > 0 && int.TryParse(match.Groups[1].Value, out var number) && number < 60) + return base.GetTexture(componentName.Replace($"-{number}", $"-{number % 2}"), wrapModeS, wrapModeT); + } - private bool isAnimationComponent(string componentName, out int number) - { - number = 0; - - // legacy font glyph textures have the pattern "{fontPrefix}-{character}", which could be mistaken for an animation frame. - if (legacyFontPrefixes.Any(p => componentName.StartsWith($"{p}-"))) - return false; - - var match = Regex.Match(componentName, "-([0-9]*)"); - return match.Length > 0 && int.TryParse(match.Groups[1].Value, out number); + return null; } } } From 552968f65f7c5451faef6c96c144efe3bcefce31 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 22 Sep 2020 13:38:52 +0900 Subject: [PATCH 1161/1782] Remove unnecessary using --- osu.Game/Tests/Visual/SkinnableTestScene.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Tests/Visual/SkinnableTestScene.cs b/osu.Game/Tests/Visual/SkinnableTestScene.cs index c0db05d5c5..a856789d96 100644 --- a/osu.Game/Tests/Visual/SkinnableTestScene.cs +++ b/osu.Game/Tests/Visual/SkinnableTestScene.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Linq; using System.Text.RegularExpressions; using JetBrains.Annotations; using osu.Framework.Allocation; From 3276b9ae9cba2aaeb7b9d60001bfe05353931b0d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 22 Sep 2020 15:08:53 +0900 Subject: [PATCH 1162/1782] Fix fail animation breaking on post-fail judgements --- osu.Game/Screens/Play/FailAnimation.cs | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Play/FailAnimation.cs b/osu.Game/Screens/Play/FailAnimation.cs index 54c644c999..608f20affd 100644 --- a/osu.Game/Screens/Play/FailAnimation.cs +++ b/osu.Game/Screens/Play/FailAnimation.cs @@ -89,6 +89,8 @@ namespace osu.Game.Screens.Play private void applyToPlayfield(Playfield playfield) { + double failTime = playfield.Time.Current; + foreach (var nested in playfield.NestedPlayfields) applyToPlayfield(nested); @@ -97,13 +99,29 @@ namespace osu.Game.Screens.Play if (appliedObjects.Contains(obj)) continue; - obj.RotateTo(RNG.NextSingle(-90, 90), duration); - obj.ScaleTo(obj.Scale * 0.5f, duration); - obj.MoveToOffset(new Vector2(0, 400), duration); + float rotation = RNG.NextSingle(-90, 90); + Vector2 originalPosition = obj.Position; + Vector2 originalScale = obj.Scale; + + dropOffScreen(obj, failTime, rotation, originalScale, originalPosition); + + // need to reapply the fail drop after judgement state changes + obj.ApplyCustomUpdateState += (o, _) => dropOffScreen(obj, failTime, rotation, originalScale, originalPosition); + appliedObjects.Add(obj); } } + private void dropOffScreen(DrawableHitObject obj, double failTime, float randomRotation, Vector2 originalScale, Vector2 originalPosition) + { + using (obj.BeginAbsoluteSequence(failTime)) + { + obj.RotateTo(randomRotation, duration); + obj.ScaleTo(originalScale * 0.5f, duration); + obj.MoveTo(originalPosition + new Vector2(0, 400), duration); + } + } + protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); From 3062fe44113c0ea8ef8e829db291297eac7d0f65 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 22 Sep 2020 15:55:25 +0900 Subject: [PATCH 1163/1782] Add editor key bindings to switch between screens --- .../Input/Bindings/GlobalActionContainer.cs | 27 +++++++++++++-- .../KeyBinding/GlobalKeyBindingsSection.cs | 12 +++++++ osu.Game/Screens/Edit/Editor.cs | 34 ++++++++++++++----- 3 files changed, 62 insertions(+), 11 deletions(-) diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs index 45b07581ec..3cabfce7bb 100644 --- a/osu.Game/Input/Bindings/GlobalActionContainer.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs @@ -21,7 +21,7 @@ namespace osu.Game.Input.Bindings handler = game; } - public override IEnumerable DefaultKeyBindings => GlobalKeyBindings.Concat(InGameKeyBindings).Concat(AudioControlKeyBindings); + public override IEnumerable DefaultKeyBindings => GlobalKeyBindings.Concat(InGameKeyBindings).Concat(AudioControlKeyBindings).Concat(EditorKeyBindings); public IEnumerable GlobalKeyBindings => new[] { @@ -50,6 +50,14 @@ namespace osu.Game.Input.Bindings new KeyBinding(InputKey.KeypadEnter, GlobalAction.Select), }; + public IEnumerable EditorKeyBindings => new[] + { + new KeyBinding(new[] { InputKey.F1 }, GlobalAction.EditorComposeMode), + new KeyBinding(new[] { InputKey.F2 }, GlobalAction.EditorDesignMode), + new KeyBinding(new[] { InputKey.F3 }, GlobalAction.EditorTimingMode), + new KeyBinding(new[] { InputKey.F4 }, GlobalAction.EditorSetupMode), + }; + public IEnumerable InGameKeyBindings => new[] { new KeyBinding(InputKey.Space, GlobalAction.SkipCutscene), @@ -68,7 +76,7 @@ namespace osu.Game.Input.Bindings new KeyBinding(new[] { InputKey.Alt, InputKey.Down }, GlobalAction.DecreaseVolume), new KeyBinding(new[] { InputKey.Alt, InputKey.MouseWheelDown }, GlobalAction.DecreaseVolume), - new KeyBinding(InputKey.F4, GlobalAction.ToggleMute), + new KeyBinding(InputKey.Mute, GlobalAction.ToggleMute), new KeyBinding(InputKey.TrackPrevious, GlobalAction.MusicPrev), new KeyBinding(InputKey.F1, GlobalAction.MusicPrev), @@ -139,7 +147,7 @@ namespace osu.Game.Input.Bindings [Description("Quick exit (Hold)")] QuickExit, - // Game-wide beatmap msi ccotolle keybindings + // Game-wide beatmap music controller keybindings [Description("Next track")] MusicNext, @@ -166,5 +174,18 @@ namespace osu.Game.Input.Bindings [Description("Pause")] PauseGameplay, + + // Editor + [Description("Setup Mode")] + EditorSetupMode, + + [Description("Compose Mode")] + EditorComposeMode, + + [Description("Design Mode")] + EditorDesignMode, + + [Description("Timing Mode")] + EditorTimingMode, } } diff --git a/osu.Game/Overlays/KeyBinding/GlobalKeyBindingsSection.cs b/osu.Game/Overlays/KeyBinding/GlobalKeyBindingsSection.cs index 5b44c486a3..9a27c55c53 100644 --- a/osu.Game/Overlays/KeyBinding/GlobalKeyBindingsSection.cs +++ b/osu.Game/Overlays/KeyBinding/GlobalKeyBindingsSection.cs @@ -22,6 +22,7 @@ namespace osu.Game.Overlays.KeyBinding Add(new DefaultBindingsSubsection(manager)); Add(new AudioControlKeyBindingsSubsection(manager)); Add(new InGameKeyBindingsSubsection(manager)); + Add(new EditorKeyBindingsSubsection(manager)); } private class DefaultBindingsSubsection : KeyBindingsSubsection @@ -56,5 +57,16 @@ namespace osu.Game.Overlays.KeyBinding Defaults = manager.AudioControlKeyBindings; } } + + private class EditorKeyBindingsSubsection : KeyBindingsSubsection + { + protected override string Header => "Editor"; + + public EditorKeyBindingsSubsection(GlobalActionContainer manager) + : base(null) + { + Defaults = manager.EditorKeyBindings; + } + } } } diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 71340041f0..b7a59bc2e2 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -79,6 +79,8 @@ namespace osu.Game.Screens.Edit private EditorBeatmap editorBeatmap; private EditorChangeHandler changeHandler; + private EditorMenuBar menuBar; + private DependencyContainer dependencies; protected override UserActivity InitialActivity => new UserActivity.Editing(Beatmap.Value.BeatmapInfo); @@ -133,8 +135,6 @@ namespace osu.Game.Screens.Edit updateLastSavedHash(); - EditorMenuBar menuBar; - OsuMenuItem undoMenuItem; OsuMenuItem redoMenuItem; @@ -374,14 +374,32 @@ namespace osu.Game.Screens.Edit public bool OnPressed(GlobalAction action) { - if (action == GlobalAction.Back) + switch (action) { - // as we don't want to display the back button, manual handling of exit action is required. - this.Exit(); - return true; - } + case GlobalAction.Back: + // as we don't want to display the back button, manual handling of exit action is required. + this.Exit(); + return true; - return false; + case GlobalAction.EditorComposeMode: + menuBar.Mode.Value = EditorScreenMode.Compose; + return true; + + case GlobalAction.EditorDesignMode: + menuBar.Mode.Value = EditorScreenMode.Design; + return true; + + case GlobalAction.EditorTimingMode: + menuBar.Mode.Value = EditorScreenMode.Timing; + return true; + + case GlobalAction.EditorSetupMode: + menuBar.Mode.Value = EditorScreenMode.SongSetup; + return true; + + default: + return false; + } } public void OnReleased(GlobalAction action) From 8e432664600f4fb2c3ad2c19991ff61ae385fa5b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 22 Sep 2020 16:02:07 +0900 Subject: [PATCH 1164/1782] Fix compose mode not showing distance snap grid when entering with a selection --- osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index f87bd53ec3..f086b92b60 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -54,6 +54,9 @@ namespace osu.Game.Rulesets.Osu.Edit EditorBeatmap.SelectedHitObjects.CollectionChanged += (_, __) => updateDistanceSnapGrid(); EditorBeatmap.PlacementObject.ValueChanged += _ => updateDistanceSnapGrid(); distanceSnapToggle.ValueChanged += _ => updateDistanceSnapGrid(); + + // we may be entering the screen with a selection already active + updateDistanceSnapGrid(); } protected override ComposeBlueprintContainer CreateBlueprintContainer(IEnumerable hitObjects) From b1f7cfbd5b81b397dddf3487f23bf62a03fb1bc3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 22 Sep 2020 17:34:14 +0900 Subject: [PATCH 1165/1782] Reduce children levels in RingPiece --- .../Spinners/Components/SpinnerPiece.cs | 6 +---- .../Objects/Drawables/Pieces/RingPiece.cs | 24 +++++++------------ 2 files changed, 10 insertions(+), 20 deletions(-) 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 65c8720031..2347d8a34c 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/Components/SpinnerPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/Components/SpinnerPiece.cs @@ -34,11 +34,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners.Components Alpha = 0.5f, Child = new Box { RelativeSizeAxes = Axes.Both } }, - ring = new RingPiece - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre - } + ring = new RingPiece() }; } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/RingPiece.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/RingPiece.cs index 82e4383143..bcf64b81a6 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/RingPiece.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/RingPiece.cs @@ -9,7 +9,7 @@ using osu.Framework.Graphics.Shapes; namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces { - public class RingPiece : Container + public class RingPiece : CircularContainer { public RingPiece() { @@ -18,21 +18,15 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces Anchor = Anchor.Centre; Origin = Anchor.Centre; - InternalChild = new CircularContainer + Masking = true; + BorderThickness = 10; + BorderColour = Color4.White; + + Child = new Box { - Masking = true, - BorderThickness = 10, - BorderColour = Color4.White, - RelativeSizeAxes = Axes.Both, - Children = new Drawable[] - { - new Box - { - AlwaysPresent = true, - Alpha = 0, - RelativeSizeAxes = Axes.Both - } - } + AlwaysPresent = true, + Alpha = 0, + RelativeSizeAxes = Axes.Both }; } } From e0a2321822f5ddd9c9a2cf443703bd4f38f62801 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 22 Sep 2020 18:17:04 +0900 Subject: [PATCH 1166/1782] Reduce complexity of AllHitObjects enumerator when nested playfields are not present --- osu.Game/Rulesets/UI/Playfield.cs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/UI/Playfield.cs b/osu.Game/Rulesets/UI/Playfield.cs index c52183f3f2..d92ba210db 100644 --- a/osu.Game/Rulesets/UI/Playfield.cs +++ b/osu.Game/Rulesets/UI/Playfield.cs @@ -37,7 +37,21 @@ namespace osu.Game.Rulesets.UI /// /// All the s contained in this and all . /// - public IEnumerable AllHitObjects => HitObjectContainer?.Objects.Concat(NestedPlayfields.SelectMany(p => p.AllHitObjects)) ?? Enumerable.Empty(); + public IEnumerable AllHitObjects + { + get + { + if (HitObjectContainer == null) + return Enumerable.Empty(); + + var enumerable = HitObjectContainer.Objects; + + if (nestedPlayfields.IsValueCreated) + enumerable = enumerable.Concat(NestedPlayfields.SelectMany(p => p.AllHitObjects)); + + return enumerable; + } + } /// /// All s nested inside this . From 260ca31df038384f63d3addbbe83c013883dad18 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 23 Sep 2020 12:31:50 +0900 Subject: [PATCH 1167/1782] Change default mute key to Ctrl+F4 for now --- 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 3cabfce7bb..41be4cfcc3 100644 --- a/osu.Game/Input/Bindings/GlobalActionContainer.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs @@ -76,7 +76,7 @@ namespace osu.Game.Input.Bindings new KeyBinding(new[] { InputKey.Alt, InputKey.Down }, GlobalAction.DecreaseVolume), new KeyBinding(new[] { InputKey.Alt, InputKey.MouseWheelDown }, GlobalAction.DecreaseVolume), - new KeyBinding(InputKey.Mute, GlobalAction.ToggleMute), + new KeyBinding(new[] { InputKey.Control, InputKey.F4 }, GlobalAction.ToggleMute), new KeyBinding(InputKey.TrackPrevious, GlobalAction.MusicPrev), new KeyBinding(InputKey.F1, GlobalAction.MusicPrev), From c38cd50723a204bd2fecbe357f5fd81ef01e1ac0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 23 Sep 2020 13:16:46 +0900 Subject: [PATCH 1168/1782] Fix editor not using beatmap combo colours initially on load --- .../Screens/Edit/EditorScreenWithTimeline.cs | 1 + .../Skinning/BeatmapSkinProvidingContainer.cs | 58 +++++++++++++++---- 2 files changed, 49 insertions(+), 10 deletions(-) diff --git a/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs b/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs index 67442aa55e..66d90809db 100644 --- a/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs +++ b/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs @@ -94,6 +94,7 @@ namespace osu.Game.Screens.Edit } }, }; + LoadComponentAsync(CreateMainContent(), content => { spinner.State.Value = Visibility.Hidden; diff --git a/osu.Game/Skinning/BeatmapSkinProvidingContainer.cs b/osu.Game/Skinning/BeatmapSkinProvidingContainer.cs index 40335db697..fc01f0bd31 100644 --- a/osu.Game/Skinning/BeatmapSkinProvidingContainer.cs +++ b/osu.Game/Skinning/BeatmapSkinProvidingContainer.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.Bindables; using osu.Game.Audio; @@ -13,25 +14,62 @@ namespace osu.Game.Skinning /// public class BeatmapSkinProvidingContainer : SkinProvidingContainer { - private readonly Bindable beatmapSkins = new Bindable(); - private readonly Bindable beatmapHitsounds = new Bindable(); + private Bindable beatmapSkins; + private Bindable beatmapHitsounds; - protected override bool AllowConfigurationLookup => 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; + protected override bool AllowConfigurationLookup + { + get + { + if (beatmapSkins == null) + throw new InvalidOperationException($"{nameof(BeatmapSkinProvidingContainer)} needs to be loaded before being consumed."); + + return beatmapSkins.Value; + } + } + + protected override bool AllowDrawableLookup(ISkinComponent component) + { + if (beatmapSkins == null) + throw new InvalidOperationException($"{nameof(BeatmapSkinProvidingContainer)} needs to be loaded before being consumed."); + + return beatmapSkins.Value; + } + + protected override bool AllowTextureLookup(string componentName) + { + if (beatmapSkins == null) + throw new InvalidOperationException($"{nameof(BeatmapSkinProvidingContainer)} needs to be loaded before being consumed."); + + return beatmapSkins.Value; + } + + protected override bool AllowSampleLookup(ISampleInfo componentName) + { + if (beatmapSkins == null) + throw new InvalidOperationException($"{nameof(BeatmapSkinProvidingContainer)} needs to be loaded before being consumed."); + + return beatmapHitsounds.Value; + } public BeatmapSkinProvidingContainer(ISkin skin) : base(skin) { } - [BackgroundDependencyLoader] - private void load(OsuConfigManager config) + protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) { - config.BindWith(OsuSetting.BeatmapSkins, beatmapSkins); - config.BindWith(OsuSetting.BeatmapHitsounds, beatmapHitsounds); + var config = parent.Get(); + beatmapSkins = config.GetBindable(OsuSetting.BeatmapSkins); + beatmapHitsounds = config.GetBindable(OsuSetting.BeatmapHitsounds); + + return base.CreateChildDependencies(parent); + } + + [BackgroundDependencyLoader] + private void load() + { beatmapSkins.BindValueChanged(_ => TriggerSourceChanged()); beatmapHitsounds.BindValueChanged(_ => TriggerSourceChanged()); } From ba160aab76bbdc32484f08fe1751a2f5bd2501d9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 23 Sep 2020 15:41:43 +0900 Subject: [PATCH 1169/1782] Fix large construction/disposal overhead on beatmaps with hitobjects at same point in time --- .../Drawables/Connections/FollowPointRenderer.cs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs index 4d73e711bb..11571ea761 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs @@ -46,7 +46,20 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections private void addConnection(FollowPointConnection connection) { // 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))); + int index = connections.AddInPlace(connection, Comparer.Create((g1, g2) => + { + int comp = g1.StartTime.Value.CompareTo(g2.StartTime.Value); + + if (comp != 0) + return comp; + + // we always want to insert the new item after equal ones. + // this is important for beatmaps with multiple hitobjects at the same point in time. + // if we use standard comparison insert order, there will be a churn of connections getting re-updated to + // the next object at the point-in-time, adding a construction/disposal overhead (see FollowPointConnection.End implementation's ClearInternal). + // this is easily visible on https://osu.ppy.sh/beatmapsets/150945#osu/372245 + return -1; + })); if (index < connections.Count - 1) { From c5b684bd2e5b6862da0248ce8ce9d86f7e0b9a94 Mon Sep 17 00:00:00 2001 From: Joehu Date: Wed, 23 Sep 2020 00:30:20 -0700 Subject: [PATCH 1170/1782] Fix typo in log when beatmap fails to load --- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 2 +- osu.Game/Screens/Play/Player.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index e42a359d2e..28a77a8bdf 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -78,7 +78,7 @@ namespace osu.Game.Rulesets.Edit } catch (Exception e) { - Logger.Error(e, "Could not load beatmap sucessfully!"); + Logger.Error(e, "Could not load beatmap successfully!"); return; } diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 539f9227a3..8e2ed583f2 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -399,7 +399,7 @@ namespace osu.Game.Screens.Play } catch (Exception e) { - Logger.Error(e, "Could not load beatmap sucessfully!"); + Logger.Error(e, "Could not load beatmap successfully!"); //couldn't load, hard abort! return null; } From a1ec167982705b3802c75dad79a1d032f918a4a2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 23 Sep 2020 16:38:16 +0900 Subject: [PATCH 1171/1782] Add the ability to toggle new combo state from composer context menu --- .../TestSceneHitObjectAccentColour.cs | 2 +- .../Objects/Types/IHasComboInformation.cs | 5 ++ .../Compose/Components/SelectionHandler.cs | 57 +++++++++++++++++++ 3 files changed, 63 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Gameplay/TestSceneHitObjectAccentColour.cs b/osu.Game.Tests/Gameplay/TestSceneHitObjectAccentColour.cs index 2d5e4b911e..58cc324233 100644 --- a/osu.Game.Tests/Gameplay/TestSceneHitObjectAccentColour.cs +++ b/osu.Game.Tests/Gameplay/TestSceneHitObjectAccentColour.cs @@ -81,7 +81,7 @@ namespace osu.Game.Tests.Gameplay private class TestHitObjectWithCombo : ConvertHitObject, IHasComboInformation { - public bool NewCombo { get; } = false; + public bool NewCombo { get; set; } = false; public int ComboOffset { get; } = 0; public Bindable IndexInCurrentComboBindable { get; } = new Bindable(); diff --git a/osu.Game/Rulesets/Objects/Types/IHasComboInformation.cs b/osu.Game/Rulesets/Objects/Types/IHasComboInformation.cs index 4e3de04278..211c077d4f 100644 --- a/osu.Game/Rulesets/Objects/Types/IHasComboInformation.cs +++ b/osu.Game/Rulesets/Objects/Types/IHasComboInformation.cs @@ -24,6 +24,11 @@ namespace osu.Game.Rulesets.Objects.Types /// int ComboIndex { get; set; } + /// + /// Whether the HitObject starts a new combo. + /// + new bool NewCombo { get; set; } + Bindable LastInComboBindable { get; } /// diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index f95bf350b6..c33c755940 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -20,6 +20,7 @@ using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Objects.Types; using osuTK; namespace osu.Game.Screens.Edit.Compose.Components @@ -268,6 +269,24 @@ namespace osu.Game.Screens.Edit.Compose.Components changeHandler?.EndChange(); } + public void SetNewCombo(bool state) + { + changeHandler?.BeginChange(); + + foreach (var h in SelectedHitObjects) + { + var comboInfo = h as IHasComboInformation; + + if (comboInfo == null) + throw new InvalidOperationException($"Tried to change combo state of a {h.GetType()}, which doesn't implement {nameof(IHasComboInformation)}"); + + comboInfo.NewCombo = state; + EditorBeatmap?.UpdateHitObject(h); + } + + changeHandler?.EndChange(); + } + /// /// Removes a hit sample from all selected s. /// @@ -297,6 +316,9 @@ namespace osu.Game.Screens.Edit.Compose.Components items.AddRange(GetContextMenuItemsForSelection(selectedBlueprints)); + if (selectedBlueprints.All(b => b.HitObject is IHasComboInformation)) + items.Add(createNewComboMenuItem()); + if (selectedBlueprints.Count == 1) items.AddRange(selectedBlueprints[0].ContextMenuItems); @@ -326,6 +348,41 @@ namespace osu.Game.Screens.Edit.Compose.Components protected virtual IEnumerable GetContextMenuItemsForSelection(IEnumerable selection) => Enumerable.Empty(); + private MenuItem createNewComboMenuItem() + { + return new TernaryStateMenuItem("New combo", MenuItemType.Standard, setNewComboState) + { + State = { Value = getHitSampleState() } + }; + + void setNewComboState(TernaryState state) + { + switch (state) + { + case TernaryState.False: + SetNewCombo(false); + break; + + case TernaryState.True: + SetNewCombo(true); + break; + } + } + + TernaryState getHitSampleState() + { + int countExisting = selectedBlueprints.Select(b => b.HitObject as IHasComboInformation).Count(h => h.NewCombo); + + if (countExisting == 0) + return TernaryState.False; + + if (countExisting < SelectedHitObjects.Count()) + return TernaryState.Indeterminate; + + return TernaryState.True; + } + } + private MenuItem createHitSampleMenuItem(string name, string sampleName) { return new TernaryStateMenuItem(name, MenuItemType.Standard, setHitSampleState) From 2d67faeb7250687ccf2457502452fffce705c839 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 23 Sep 2020 16:40:56 +0900 Subject: [PATCH 1172/1782] Add xmldoc --- osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index c33c755940..71177fe3e4 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -269,6 +269,11 @@ namespace osu.Game.Screens.Edit.Compose.Components changeHandler?.EndChange(); } + /// + /// Set the new combo state of all selected s. + /// + /// Whether to set or unset. + /// Throws if any selected object doesn't implement public void SetNewCombo(bool state) { changeHandler?.BeginChange(); From 487fc2a2c6912de760946f22bd31a5c9c7384901 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 23 Sep 2020 16:58:22 +0900 Subject: [PATCH 1173/1782] Add missing change handler scopings to taiko context menu operations --- .../Edit/TaikoSelectionHandler.cs | 8 ++++++++ .../Compose/Components/SelectionHandler.cs | 18 +++++++++--------- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs b/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs index eebf6980fe..40565048c2 100644 --- a/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs +++ b/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs @@ -22,6 +22,8 @@ namespace osu.Game.Rulesets.Taiko.Edit yield return new TernaryStateMenuItem("Rim", action: state => { + ChangeHandler.BeginChange(); + foreach (var h in hits) { switch (state) @@ -35,6 +37,8 @@ namespace osu.Game.Rulesets.Taiko.Edit break; } } + + ChangeHandler.EndChange(); }) { State = { Value = getTernaryState(hits, h => h.Type == HitType.Rim) } @@ -47,6 +51,8 @@ namespace osu.Game.Rulesets.Taiko.Edit yield return new TernaryStateMenuItem("Strong", action: state => { + ChangeHandler.BeginChange(); + foreach (var h in hits) { switch (state) @@ -62,6 +68,8 @@ namespace osu.Game.Rulesets.Taiko.Edit EditorBeatmap?.UpdateHitObject(h); } + + ChangeHandler.EndChange(); }) { State = { Value = getTernaryState(hits, h => h.IsStrong) } diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index 71177fe3e4..ca824a7cef 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -45,7 +45,7 @@ namespace osu.Game.Screens.Edit.Compose.Components protected EditorBeatmap EditorBeatmap { get; private set; } [Resolved(CanBeNull = true)] - private IEditorChangeHandler changeHandler { get; set; } + protected IEditorChangeHandler ChangeHandler { get; private set; } public SelectionHandler() { @@ -194,12 +194,12 @@ namespace osu.Game.Screens.Edit.Compose.Components private void deleteSelected() { - changeHandler?.BeginChange(); + ChangeHandler?.BeginChange(); foreach (var h in selectedBlueprints.ToList()) EditorBeatmap?.Remove(h.HitObject); - changeHandler?.EndChange(); + ChangeHandler?.EndChange(); } #endregion @@ -255,7 +255,7 @@ namespace osu.Game.Screens.Edit.Compose.Components /// The name of the hit sample. public void AddHitSample(string sampleName) { - changeHandler?.BeginChange(); + ChangeHandler?.BeginChange(); foreach (var h in SelectedHitObjects) { @@ -266,7 +266,7 @@ namespace osu.Game.Screens.Edit.Compose.Components h.Samples.Add(new HitSampleInfo { Name = sampleName }); } - changeHandler?.EndChange(); + ChangeHandler?.EndChange(); } /// @@ -276,7 +276,7 @@ namespace osu.Game.Screens.Edit.Compose.Components /// Throws if any selected object doesn't implement public void SetNewCombo(bool state) { - changeHandler?.BeginChange(); + ChangeHandler?.BeginChange(); foreach (var h in SelectedHitObjects) { @@ -289,7 +289,7 @@ namespace osu.Game.Screens.Edit.Compose.Components EditorBeatmap?.UpdateHitObject(h); } - changeHandler?.EndChange(); + ChangeHandler?.EndChange(); } /// @@ -298,12 +298,12 @@ namespace osu.Game.Screens.Edit.Compose.Components /// The name of the hit sample. public void RemoveHitSample(string sampleName) { - changeHandler?.BeginChange(); + ChangeHandler?.BeginChange(); foreach (var h in SelectedHitObjects) h.SamplesBindable.RemoveAll(s => s.Name == sampleName); - changeHandler?.EndChange(); + ChangeHandler?.EndChange(); } #endregion From 02201d0ec66a23dbfa05001397bc216667e610e8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 23 Sep 2020 17:08:25 +0900 Subject: [PATCH 1174/1782] Fix incorrect cast logic --- osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index 71177fe3e4..e85dbee6d9 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -376,7 +376,7 @@ namespace osu.Game.Screens.Edit.Compose.Components TernaryState getHitSampleState() { - int countExisting = selectedBlueprints.Select(b => b.HitObject as IHasComboInformation).Count(h => h.NewCombo); + int countExisting = selectedBlueprints.Select(b => (IHasComboInformation)b.HitObject).Count(h => h.NewCombo); if (countExisting == 0) return TernaryState.False; From 8f3eb9a422c25472c35f08cc6c82f98881eadf84 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 23 Sep 2020 17:57:57 +0900 Subject: [PATCH 1175/1782] Fix taiko sample selection not updating when changing strong/rim type --- .../Objects/Drawables/DrawableHit.cs | 32 +++++++++++++++---- .../Drawables/DrawableTaikoHitObject.cs | 28 +++++++++++++++- 2 files changed, 53 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs index 92ae7e0fd3..f4234445d6 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs @@ -42,6 +42,8 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables : base(hit) { FillMode = FillMode.Fit; + + updateActionsFromType(); } [BackgroundDependencyLoader] @@ -50,21 +52,39 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables type = HitObject.TypeBindable.GetBoundCopy(); type.BindValueChanged(_ => { - updateType(); + updateActionsFromType(); + + // will overwrite samples, should only be called on change. + updateSamplesFromTypeChange(); + RecreatePieces(); }); - - updateType(); } - private void updateType() + private void updateSamplesFromTypeChange() + { + var rimSamples = HitObject.Samples.Where(s => s.Name == HitSampleInfo.HIT_CLAP || s.Name == HitSampleInfo.HIT_WHISTLE).ToArray(); + + bool isRimType = HitObject.Type == HitType.Rim; + + if (isRimType != rimSamples.Any()) + { + if (isRimType) + HitObject.Samples.Add(new HitSampleInfo { Name = HitSampleInfo.HIT_CLAP }); + else + { + foreach (var sample in rimSamples) + HitObject.Samples.Remove(sample); + } + } + } + + private void updateActionsFromType() { HitActions = HitObject.Type == HitType.Centre ? new[] { TaikoAction.LeftCentre, TaikoAction.RightCentre } : new[] { TaikoAction.LeftRim, TaikoAction.RightRim }; - - RecreatePieces(); } protected override SkinnableDrawable CreateMainPiece() => HitObject.Type == HitType.Centre diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs index 929cf8a937..0474de8453 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs @@ -141,7 +141,31 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables private void load() { isStrong = HitObject.IsStrongBindable.GetBoundCopy(); - isStrong.BindValueChanged(_ => RecreatePieces(), true); + isStrong.BindValueChanged(_ => + { + // will overwrite samples, should only be called on change. + updateSamplesFromStrong(); + + RecreatePieces(); + }); + + RecreatePieces(); + } + + private void updateSamplesFromStrong() + { + var strongSamples = HitObject.Samples.Where(s => s.Name == HitSampleInfo.HIT_FINISH).ToArray(); + + if (isStrong.Value != strongSamples.Any()) + { + if (isStrong.Value) + HitObject.Samples.Add(new HitSampleInfo { Name = HitSampleInfo.HIT_FINISH }); + else + { + foreach (var sample in strongSamples) + HitObject.Samples.Remove(sample); + } + } } protected virtual void RecreatePieces() @@ -150,6 +174,8 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables MainPiece?.Expire(); Content.Add(MainPiece = CreateMainPiece()); + + LoadSamples(); } protected override void AddNestedHitObject(DrawableHitObject hitObject) From 9a0e5ac154242ee6fd1764582899e6c1c48114b3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 23 Sep 2020 18:09:40 +0900 Subject: [PATCH 1176/1782] Handle type/strength changes from samples changes --- .../Objects/Drawables/DrawableSlider.cs | 4 +-- .../Objects/Drawables/DrawableSpinner.cs | 4 +-- .../Objects/Drawables/DrawableHit.cs | 16 ++++++++-- .../Drawables/DrawableTaikoHitObject.cs | 30 ++++++++++++------- .../Objects/Drawables/DrawableHitObject.cs | 10 +++++-- 5 files changed, 43 insertions(+), 21 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index 07f40f763b..fc3bb76ae1 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -89,9 +89,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables private SkinnableSound slidingSample; - protected override void LoadSamples() + protected override void LoadSamples(bool changed) { - base.LoadSamples(); + base.LoadSamples(changed); slidingSample?.Expire(); slidingSample = null; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index a57bb466c7..e78c886beb 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -88,9 +88,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables private const float spinning_sample_initial_frequency = 1.0f; private const float spinning_sample_modulated_base_frequency = 0.5f; - protected override void LoadSamples() + protected override void LoadSamples(bool changed) { - base.LoadSamples(); + base.LoadSamples(changed); spinningSample?.Expire(); spinningSample = null; diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs index f4234445d6..7608514817 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs @@ -61,19 +61,29 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables }); } + private HitSampleInfo[] rimSamples => HitObject.Samples.Where(s => s.Name == HitSampleInfo.HIT_CLAP || s.Name == HitSampleInfo.HIT_WHISTLE).ToArray(); + + protected override void LoadSamples(bool changed) + { + base.LoadSamples(changed); + + if (changed) + type.Value = rimSamples.Any() ? HitType.Rim : HitType.Centre; + } + private void updateSamplesFromTypeChange() { - var rimSamples = HitObject.Samples.Where(s => s.Name == HitSampleInfo.HIT_CLAP || s.Name == HitSampleInfo.HIT_WHISTLE).ToArray(); + var samples = rimSamples; bool isRimType = HitObject.Type == HitType.Rim; - if (isRimType != rimSamples.Any()) + if (isRimType != samples.Any()) { if (isRimType) HitObject.Samples.Add(new HitSampleInfo { Name = HitSampleInfo.HIT_CLAP }); else { - foreach (var sample in rimSamples) + foreach (var sample in samples) HitObject.Samples.Remove(sample); } } diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs index 0474de8453..4b1cd80967 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.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; -using osu.Framework.Input.Bindings; -using osu.Game.Rulesets.Objects.Drawables; -using osuTK; -using System.Linq; -using osu.Game.Audio; 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.Primitives; +using osu.Framework.Input.Bindings; +using osu.Game.Audio; using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Skinning; +using osuTK; namespace osu.Game.Rulesets.Taiko.Objects.Drawables { @@ -152,17 +152,27 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables RecreatePieces(); } + private HitSampleInfo[] strongSamples => HitObject.Samples.Where(s => s.Name == HitSampleInfo.HIT_FINISH).ToArray(); + + protected override void LoadSamples(bool changed) + { + base.LoadSamples(changed); + + if (changed) + isStrong.Value = strongSamples.Any(); + } + private void updateSamplesFromStrong() { - var strongSamples = HitObject.Samples.Where(s => s.Name == HitSampleInfo.HIT_FINISH).ToArray(); + var samples = strongSamples; - if (isStrong.Value != strongSamples.Any()) + if (isStrong.Value != samples.Any()) { if (isStrong.Value) HitObject.Samples.Add(new HitSampleInfo { Name = HitSampleInfo.HIT_FINISH }); else { - foreach (var sample in strongSamples) + foreach (var sample in samples) HitObject.Samples.Remove(sample); } } @@ -174,8 +184,6 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables MainPiece?.Expire(); Content.Add(MainPiece = CreateMainPiece()); - - LoadSamples(); } protected override void AddNestedHitObject(DrawableHitObject hitObject) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 581617b567..4521556182 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 if (Result == null) throw new InvalidOperationException($"{GetType().ReadableName()} must provide a {nameof(JudgementResult)} through {nameof(CreateResult)}."); - LoadSamples(); + LoadSamples(false); } protected override void LoadAsyncComplete() @@ -145,7 +145,7 @@ namespace osu.Game.Rulesets.Objects.Drawables } samplesBindable = HitObject.SamplesBindable.GetBoundCopy(); - samplesBindable.CollectionChanged += (_, __) => LoadSamples(); + samplesBindable.CollectionChanged += (_, __) => LoadSamples(true); apply(HitObject); } @@ -157,7 +157,11 @@ namespace osu.Game.Rulesets.Objects.Drawables updateState(ArmedState.Idle, true); } - protected virtual void LoadSamples() + /// + /// Called to perform sample-related logic. + /// + /// True if triggered from a post-load change to samples. + protected virtual void LoadSamples(bool changed) { if (Samples != null) { From fee379b4b9b997bd53e73e76fe626a2bd71fff93 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 23 Sep 2020 18:12:07 +0900 Subject: [PATCH 1177/1782] Reword xmldoc for legibility --- 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 4521556182..08235e4ff8 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -158,9 +158,9 @@ namespace osu.Game.Rulesets.Objects.Drawables } /// - /// Called to perform sample-related logic. + /// Invoked by the base to populate samples, once on initial load and potentially again on any change to the samples collection. /// - /// True if triggered from a post-load change to samples. + /// True if triggered from a change to the samples collection. protected virtual void LoadSamples(bool changed) { if (Samples != null) From e8d099c01d842822acffe33a8dea781edba509a3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 23 Sep 2020 19:28:20 +0900 Subject: [PATCH 1178/1782] 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 6cbb4b2e68..d701aaf199 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,6 +52,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 8d23a32c3c..71826e161c 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -24,7 +24,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index d00b174195..90aa903318 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -80,7 +80,7 @@ - + From 673a75c46c6f0e57e903bb4b8c9f81111ca76587 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 23 Sep 2020 21:06:11 +0900 Subject: [PATCH 1179/1782] Fix failing test --- osu.Game.Tests/Visual/Online/TestSceneCommentsContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneCommentsContainer.cs b/osu.Game.Tests/Visual/Online/TestSceneCommentsContainer.cs index 08130e60db..c2a18330c9 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneCommentsContainer.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneCommentsContainer.cs @@ -68,7 +68,7 @@ namespace osu.Game.Tests.Visual.Online public void TestMultipleLoads() { var comments = exampleComments; - int topLevelCommentCount = exampleComments.Comments.Count(comment => comment.IsTopLevel); + int topLevelCommentCount = exampleComments.Comments.Count; AddStep("hide container", () => commentsContainer.Hide()); setUpCommentsResponse(comments); From f4d2c2684dd6dda8e53f0338a586d18c9e180491 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 24 Sep 2020 12:21:08 +0900 Subject: [PATCH 1180/1782] Add more descriptive description and download button when statistics not available --- .../Ranking/Statistics/StatisticsPanel.cs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs index c2ace6a04e..b0cff244b2 100644 --- a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs +++ b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs @@ -75,7 +75,21 @@ namespace osu.Game.Screens.Ranking.Statistics return; if (newScore.HitEvents == null || newScore.HitEvents.Count == 0) - content.Add(new MessagePlaceholder("Score has no statistics :(")); + content.Add(new FillFlowContainer + { + RelativeSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + new MessagePlaceholder("Extended statistics are only available after watching a replay!"), + new ReplayDownloadButton(newScore) + { + Scale = new Vector2(1.5f), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + } + }); else { spinner.Show(); From cb903ec9e2a378575a5b686a5c543f6883761036 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 24 Sep 2020 12:21:46 +0900 Subject: [PATCH 1181/1782] Fix extended statistics not being vertically centered --- osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs index c2ace6a04e..bd1b038181 100644 --- a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs +++ b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs @@ -91,7 +91,10 @@ namespace osu.Game.Screens.Ranking.Statistics { var rows = new FillFlowContainer { - RelativeSizeAxes = Axes.Both, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, Direction = FillDirection.Vertical, Spacing = new Vector2(30, 15), Alpha = 0 From fda6e88dd364eb53e851e44abb5e390b09abd461 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 24 Sep 2020 12:39:08 +0900 Subject: [PATCH 1182/1782] Fix braces style --- osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs index b0cff244b2..997a5b47ac 100644 --- a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs +++ b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs @@ -75,6 +75,7 @@ namespace osu.Game.Screens.Ranking.Statistics return; if (newScore.HitEvents == null || newScore.HitEvents.Count == 0) + { content.Add(new FillFlowContainer { RelativeSizeAxes = Axes.Both, @@ -90,6 +91,7 @@ namespace osu.Game.Screens.Ranking.Statistics }, } }); + } else { spinner.Show(); From 56123575747a57bf2eca568f1d5b484a00bba7ca Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 24 Sep 2020 12:49:32 +0900 Subject: [PATCH 1183/1782] Fix score panel being incorrectly vertically aligned on screen resize --- osu.Game/Screens/Ranking/ResultsScreen.cs | 6 +++--- osu.Game/Screens/Ranking/ScorePanelList.cs | 2 ++ 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index c95cf1066e..c48cd238c0 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -273,10 +273,10 @@ namespace osu.Game.Screens.Ranking detachedPanelContainer.Add(expandedPanel); // Move into its original location in the local container first, then to the final location. - var origLocation = detachedPanelContainer.ToLocalSpace(screenSpacePos); - expandedPanel.MoveTo(origLocation) + var origLocation = detachedPanelContainer.ToLocalSpace(screenSpacePos).X; + expandedPanel.MoveToX(origLocation) .Then() - .MoveTo(new Vector2(StatisticsPanel.SIDE_PADDING, origLocation.Y), 150, Easing.OutQuint); + .MoveToX(StatisticsPanel.SIDE_PADDING, 150, Easing.OutQuint); // Hide contracted panels. foreach (var contracted in ScorePanelList.GetScorePanels().Where(p => p.State == PanelState.Contracted)) diff --git a/osu.Game/Screens/Ranking/ScorePanelList.cs b/osu.Game/Screens/Ranking/ScorePanelList.cs index 10bd99c8ce..0d7d339df0 100644 --- a/osu.Game/Screens/Ranking/ScorePanelList.cs +++ b/osu.Game/Screens/Ranking/ScorePanelList.cs @@ -99,6 +99,8 @@ namespace osu.Game.Screens.Ranking { var panel = new ScorePanel(score) { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, PostExpandAction = () => PostExpandAction?.Invoke() }.With(p => { From 9c074e0ffbf10fe23af9ddd867c7b6eadd5c25e7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 24 Sep 2020 13:10:54 +0900 Subject: [PATCH 1184/1782] Fix editor not showing sign when time goes negative --- .../Screens/Edit/Components/TimeInfoContainer.cs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Edit/Components/TimeInfoContainer.cs b/osu.Game/Screens/Edit/Components/TimeInfoContainer.cs index c1f54d7938..c68eeeb4f9 100644 --- a/osu.Game/Screens/Edit/Components/TimeInfoContainer.cs +++ b/osu.Game/Screens/Edit/Components/TimeInfoContainer.cs @@ -22,10 +22,12 @@ namespace osu.Game.Screens.Edit.Components { trackTimer = new OsuSpriteText { - Origin = Anchor.BottomLeft, - RelativePositionAxes = Axes.Y, - Font = OsuFont.GetFont(size: 22, fixedWidth: true), - Y = 0.5f, + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + // intentionally fudged centre to avoid movement of the number portion when + // going negative. + X = -35, + Font = OsuFont.GetFont(size: 25, fixedWidth: true), } }; } @@ -34,7 +36,8 @@ namespace osu.Game.Screens.Edit.Components { base.Update(); - trackTimer.Text = TimeSpan.FromMilliseconds(editorClock.CurrentTime).ToString(@"mm\:ss\:fff"); + var timespan = TimeSpan.FromMilliseconds(editorClock.CurrentTime); + trackTimer.Text = $"{(timespan < TimeSpan.Zero ? "-" : string.Empty)}{timespan:mm\\:ss\\:fff}"; } } } From eb39f6dbd8f68244f6e91dca0706aa737636d46c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 24 Sep 2020 13:17:03 +0900 Subject: [PATCH 1185/1782] Update failing test to find correct download button --- osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs index 03cb5fa3db..ff96a999ec 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs @@ -220,7 +220,7 @@ namespace osu.Game.Tests.Visual.Ranking AddStep("load results", () => Child = new TestResultsContainer(screen = createResultsScreen())); - AddAssert("download button is disabled", () => !screen.ChildrenOfType().Single().Enabled.Value); + AddAssert("download button is disabled", () => !screen.ChildrenOfType().Last().Enabled.Value); AddStep("click contracted panel", () => { @@ -229,7 +229,7 @@ namespace osu.Game.Tests.Visual.Ranking InputManager.Click(MouseButton.Left); }); - AddAssert("download button is enabled", () => screen.ChildrenOfType().Single().Enabled.Value); + AddAssert("download button is enabled", () => screen.ChildrenOfType().Last().Enabled.Value); } private class TestResultsContainer : Container From 156edf24c2736b28d134b9675e64aab08fd1f708 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 24 Sep 2020 13:22:14 +0900 Subject: [PATCH 1186/1782] Change properties to methods and improve naming --- .../Objects/Drawables/DrawableHit.cs | 10 +++++----- .../Objects/Drawables/DrawableTaikoHitObject.cs | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs index 7608514817..7cf77885cf 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs @@ -61,29 +61,29 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables }); } - private HitSampleInfo[] rimSamples => HitObject.Samples.Where(s => s.Name == HitSampleInfo.HIT_CLAP || s.Name == HitSampleInfo.HIT_WHISTLE).ToArray(); + private HitSampleInfo[] getRimSamples() => HitObject.Samples.Where(s => s.Name == HitSampleInfo.HIT_CLAP || s.Name == HitSampleInfo.HIT_WHISTLE).ToArray(); protected override void LoadSamples(bool changed) { base.LoadSamples(changed); if (changed) - type.Value = rimSamples.Any() ? HitType.Rim : HitType.Centre; + type.Value = getRimSamples().Any() ? HitType.Rim : HitType.Centre; } private void updateSamplesFromTypeChange() { - var samples = rimSamples; + var rimSamples = getRimSamples(); bool isRimType = HitObject.Type == HitType.Rim; - if (isRimType != samples.Any()) + if (isRimType != rimSamples.Any()) { if (isRimType) HitObject.Samples.Add(new HitSampleInfo { Name = HitSampleInfo.HIT_CLAP }); else { - foreach (var sample in samples) + foreach (var sample in rimSamples) HitObject.Samples.Remove(sample); } } diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs index 4b1cd80967..f3790e65af 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs @@ -152,27 +152,27 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables RecreatePieces(); } - private HitSampleInfo[] strongSamples => HitObject.Samples.Where(s => s.Name == HitSampleInfo.HIT_FINISH).ToArray(); + private HitSampleInfo[] getStrongSamples() => HitObject.Samples.Where(s => s.Name == HitSampleInfo.HIT_FINISH).ToArray(); protected override void LoadSamples(bool changed) { base.LoadSamples(changed); if (changed) - isStrong.Value = strongSamples.Any(); + isStrong.Value = getStrongSamples().Any(); } private void updateSamplesFromStrong() { - var samples = strongSamples; + var strongSamples = getStrongSamples(); - if (isStrong.Value != samples.Any()) + if (isStrong.Value != strongSamples.Any()) { if (isStrong.Value) HitObject.Samples.Add(new HitSampleInfo { Name = HitSampleInfo.HIT_FINISH }); else { - foreach (var sample in samples) + foreach (var sample in strongSamples) HitObject.Samples.Remove(sample); } } From 33fad27ec21705999cb6ad123d6c588fe60ff802 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 24 Sep 2020 13:28:29 +0900 Subject: [PATCH 1187/1782] Avoid API change to DrawableHitObject --- .../Objects/Drawables/DrawableSlider.cs | 4 ++-- .../Objects/Drawables/DrawableSpinner.cs | 4 ++-- .../Objects/Drawables/DrawableHit.cs | 11 +++++------ .../Objects/Drawables/DrawableTaikoHitObject.cs | 11 +++++------ .../Rulesets/Objects/Drawables/DrawableHitObject.cs | 7 +++---- 5 files changed, 17 insertions(+), 20 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index fc3bb76ae1..07f40f763b 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -89,9 +89,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables private SkinnableSound slidingSample; - protected override void LoadSamples(bool changed) + protected override void LoadSamples() { - base.LoadSamples(changed); + base.LoadSamples(); slidingSample?.Expire(); slidingSample = null; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index e78c886beb..a57bb466c7 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -88,9 +88,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables private const float spinning_sample_initial_frequency = 1.0f; private const float spinning_sample_modulated_base_frequency = 0.5f; - protected override void LoadSamples(bool changed) + protected override void LoadSamples() { - base.LoadSamples(changed); + base.LoadSamples(); spinningSample?.Expire(); spinningSample = null; diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs index 7cf77885cf..3a6eaa83db 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs @@ -36,11 +36,12 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables private bool pressHandledThisFrame; - private Bindable type; + private readonly Bindable type; public DrawableHit(Hit hit) : base(hit) { + type = HitObject.TypeBindable.GetBoundCopy(); FillMode = FillMode.Fit; updateActionsFromType(); @@ -49,7 +50,6 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables [BackgroundDependencyLoader] private void load() { - type = HitObject.TypeBindable.GetBoundCopy(); type.BindValueChanged(_ => { updateActionsFromType(); @@ -63,12 +63,11 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables private HitSampleInfo[] getRimSamples() => HitObject.Samples.Where(s => s.Name == HitSampleInfo.HIT_CLAP || s.Name == HitSampleInfo.HIT_WHISTLE).ToArray(); - protected override void LoadSamples(bool changed) + protected override void LoadSamples() { - base.LoadSamples(changed); + base.LoadSamples(); - if (changed) - type.Value = getRimSamples().Any() ? HitType.Rim : HitType.Centre; + type.Value = getRimSamples().Any() ? HitType.Rim : HitType.Centre; } private void updateSamplesFromTypeChange() diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs index f3790e65af..9cd23383c4 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs @@ -120,7 +120,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables protected Vector2 BaseSize; protected SkinnableDrawable MainPiece; - private Bindable isStrong; + private readonly Bindable isStrong; private readonly Container strongHitContainer; @@ -128,6 +128,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables : base(hitObject) { HitObject = hitObject; + isStrong = HitObject.IsStrongBindable.GetBoundCopy(); Anchor = Anchor.CentreLeft; Origin = Anchor.Custom; @@ -140,7 +141,6 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables [BackgroundDependencyLoader] private void load() { - isStrong = HitObject.IsStrongBindable.GetBoundCopy(); isStrong.BindValueChanged(_ => { // will overwrite samples, should only be called on change. @@ -154,12 +154,11 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables private HitSampleInfo[] getStrongSamples() => HitObject.Samples.Where(s => s.Name == HitSampleInfo.HIT_FINISH).ToArray(); - protected override void LoadSamples(bool changed) + protected override void LoadSamples() { - base.LoadSamples(changed); + base.LoadSamples(); - if (changed) - isStrong.Value = getStrongSamples().Any(); + isStrong.Value = getStrongSamples().Any(); } private void updateSamplesFromStrong() diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 08235e4ff8..28d3a39096 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 if (Result == null) throw new InvalidOperationException($"{GetType().ReadableName()} must provide a {nameof(JudgementResult)} through {nameof(CreateResult)}."); - LoadSamples(false); + LoadSamples(); } protected override void LoadAsyncComplete() @@ -145,7 +145,7 @@ namespace osu.Game.Rulesets.Objects.Drawables } samplesBindable = HitObject.SamplesBindable.GetBoundCopy(); - samplesBindable.CollectionChanged += (_, __) => LoadSamples(true); + samplesBindable.CollectionChanged += (_, __) => LoadSamples(); apply(HitObject); } @@ -160,8 +160,7 @@ namespace osu.Game.Rulesets.Objects.Drawables /// /// Invoked by the base to populate samples, once on initial load and potentially again on any change to the samples collection. /// - /// True if triggered from a change to the samples collection. - protected virtual void LoadSamples(bool changed) + protected virtual void LoadSamples() { if (Samples != null) { From 600b823a30aa562b350351c155db3f13fbfec5ee Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 24 Sep 2020 14:29:44 +0900 Subject: [PATCH 1188/1782] Fix game texture store being disposed by rulesets --- .../UI/DrawableRulesetDependencies.cs | 49 ++++++++++++++----- 1 file changed, 36 insertions(+), 13 deletions(-) diff --git a/osu.Game/Rulesets/UI/DrawableRulesetDependencies.cs b/osu.Game/Rulesets/UI/DrawableRulesetDependencies.cs index 83a1077d70..c1742970c7 100644 --- a/osu.Game/Rulesets/UI/DrawableRulesetDependencies.cs +++ b/osu.Game/Rulesets/UI/DrawableRulesetDependencies.cs @@ -10,6 +10,7 @@ using osu.Framework.Audio; using osu.Framework.Audio.Sample; using osu.Framework.Audio.Track; using osu.Framework.Bindables; +using osu.Framework.Graphics.OpenGL.Textures; using osu.Framework.Graphics.Textures; using osu.Framework.IO.Stores; using osu.Game.Rulesets.Configuration; @@ -46,12 +47,11 @@ namespace osu.Game.Rulesets.UI if (resources != null) { TextureStore = new TextureStore(new TextureLoaderStore(new NamespacedResourceStore(resources, @"Textures"))); - TextureStore.AddStore(parent.Get()); - Cache(TextureStore); + CacheAs(TextureStore = new FallbackTextureStore(TextureStore, parent.Get())); SampleStore = parent.Get().GetSampleStore(new NamespacedResourceStore(resources, @"Samples")); SampleStore.PlaybackConcurrency = OsuGameBase.SAMPLE_CONCURRENCY; - CacheAs(new FallbackSampleStore(SampleStore, parent.Get())); + CacheAs(SampleStore = new FallbackSampleStore(SampleStore, parent.Get())); } RulesetConfigManager = parent.Get().GetConfigFor(ruleset); @@ -82,6 +82,7 @@ namespace osu.Game.Rulesets.UI isDisposed = true; SampleStore?.Dispose(); + TextureStore?.Dispose(); RulesetConfigManager = null; } @@ -89,27 +90,24 @@ namespace osu.Game.Rulesets.UI } /// - /// A sample store which adds a fallback source. + /// A sample store which adds a fallback source and prevents disposal of the fallback source. /// - /// - /// This is a temporary implementation to workaround ISampleStore limitations. - /// public class FallbackSampleStore : ISampleStore { private readonly ISampleStore primary; - private readonly ISampleStore secondary; + private readonly ISampleStore fallback; - public FallbackSampleStore(ISampleStore primary, ISampleStore secondary) + public FallbackSampleStore(ISampleStore primary, ISampleStore fallback) { this.primary = primary; - this.secondary = secondary; + this.fallback = fallback; } - public SampleChannel Get(string name) => primary.Get(name) ?? secondary.Get(name); + public SampleChannel Get(string name) => primary.Get(name) ?? fallback.Get(name); - public Task GetAsync(string name) => primary.GetAsync(name) ?? secondary.GetAsync(name); + public Task GetAsync(string name) => primary.GetAsync(name) ?? fallback.GetAsync(name); - public Stream GetStream(string name) => primary.GetStream(name) ?? secondary.GetStream(name); + public Stream GetStream(string name) => primary.GetStream(name) ?? fallback.GetStream(name); public IEnumerable GetAvailableResources() => throw new NotSupportedException(); @@ -145,6 +143,31 @@ namespace osu.Game.Rulesets.UI public void Dispose() { + primary?.Dispose(); + } + } + + /// + /// A texture store which adds a fallback source and prevents disposal of the fallback source. + /// + public class FallbackTextureStore : TextureStore + { + private readonly TextureStore primary; + private readonly TextureStore fallback; + + public FallbackTextureStore(TextureStore primary, TextureStore fallback) + { + this.primary = primary; + this.fallback = fallback; + } + + public override Texture Get(string name, WrapMode wrapModeS, WrapMode wrapModeT) + => primary.Get(name, wrapModeS, wrapModeT) ?? fallback.Get(name, wrapModeS, wrapModeT); + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + primary?.Dispose(); } } } From 62c2dbc3104157b88b981634e8a3d42f322eb7cf Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 24 Sep 2020 14:33:43 +0900 Subject: [PATCH 1189/1782] Nest classes + make private --- .../UI/DrawableRulesetDependencies.cs | 146 +++++++++--------- 1 file changed, 73 insertions(+), 73 deletions(-) diff --git a/osu.Game/Rulesets/UI/DrawableRulesetDependencies.cs b/osu.Game/Rulesets/UI/DrawableRulesetDependencies.cs index c1742970c7..a9b2a15b35 100644 --- a/osu.Game/Rulesets/UI/DrawableRulesetDependencies.cs +++ b/osu.Game/Rulesets/UI/DrawableRulesetDependencies.cs @@ -87,87 +87,87 @@ namespace osu.Game.Rulesets.UI } #endregion - } - /// - /// A sample store which adds a fallback source and prevents disposal of the fallback source. - /// - public class FallbackSampleStore : ISampleStore - { - private readonly ISampleStore primary; - private readonly ISampleStore fallback; - - public FallbackSampleStore(ISampleStore primary, ISampleStore fallback) + /// + /// A sample store which adds a fallback source and prevents disposal of the fallback source. + /// + private class FallbackSampleStore : ISampleStore { - this.primary = primary; - this.fallback = fallback; + private readonly ISampleStore primary; + private readonly ISampleStore fallback; + + public FallbackSampleStore(ISampleStore primary, ISampleStore fallback) + { + this.primary = primary; + this.fallback = fallback; + } + + public SampleChannel Get(string name) => primary.Get(name) ?? fallback.Get(name); + + public Task GetAsync(string name) => primary.GetAsync(name) ?? fallback.GetAsync(name); + + public Stream GetStream(string name) => primary.GetStream(name) ?? fallback.GetStream(name); + + public IEnumerable GetAvailableResources() => throw new NotSupportedException(); + + public void AddAdjustment(AdjustableProperty type, BindableNumber adjustBindable) => throw new NotSupportedException(); + + public void RemoveAdjustment(AdjustableProperty type, BindableNumber adjustBindable) => throw new NotSupportedException(); + + public void RemoveAllAdjustments(AdjustableProperty type) => throw new NotSupportedException(); + + public BindableNumber Volume => throw new NotSupportedException(); + + public BindableNumber Balance => throw new NotSupportedException(); + + public BindableNumber Frequency => throw new NotSupportedException(); + + public BindableNumber Tempo => throw new NotSupportedException(); + + public IBindable GetAggregate(AdjustableProperty type) => throw new NotSupportedException(); + + public IBindable AggregateVolume => throw new NotSupportedException(); + + public IBindable AggregateBalance => throw new NotSupportedException(); + + public IBindable AggregateFrequency => throw new NotSupportedException(); + + public IBindable AggregateTempo => throw new NotSupportedException(); + + public int PlaybackConcurrency + { + get => throw new NotSupportedException(); + set => throw new NotSupportedException(); + } + + public void Dispose() + { + primary?.Dispose(); + } } - public SampleChannel Get(string name) => primary.Get(name) ?? fallback.Get(name); - - public Task GetAsync(string name) => primary.GetAsync(name) ?? fallback.GetAsync(name); - - public Stream GetStream(string name) => primary.GetStream(name) ?? fallback.GetStream(name); - - public IEnumerable GetAvailableResources() => throw new NotSupportedException(); - - public void AddAdjustment(AdjustableProperty type, BindableNumber adjustBindable) => throw new NotSupportedException(); - - public void RemoveAdjustment(AdjustableProperty type, BindableNumber adjustBindable) => throw new NotSupportedException(); - - public void RemoveAllAdjustments(AdjustableProperty type) => throw new NotSupportedException(); - - public BindableNumber Volume => throw new NotSupportedException(); - - public BindableNumber Balance => throw new NotSupportedException(); - - public BindableNumber Frequency => throw new NotSupportedException(); - - public BindableNumber Tempo => throw new NotSupportedException(); - - public IBindable GetAggregate(AdjustableProperty type) => throw new NotSupportedException(); - - public IBindable AggregateVolume => throw new NotSupportedException(); - - public IBindable AggregateBalance => throw new NotSupportedException(); - - public IBindable AggregateFrequency => throw new NotSupportedException(); - - public IBindable AggregateTempo => throw new NotSupportedException(); - - public int PlaybackConcurrency + /// + /// A texture store which adds a fallback source and prevents disposal of the fallback source. + /// + private class FallbackTextureStore : TextureStore { - get => throw new NotSupportedException(); - set => throw new NotSupportedException(); - } + private readonly TextureStore primary; + private readonly TextureStore fallback; - public void Dispose() - { - primary?.Dispose(); - } - } + public FallbackTextureStore(TextureStore primary, TextureStore fallback) + { + this.primary = primary; + this.fallback = fallback; + } - /// - /// A texture store which adds a fallback source and prevents disposal of the fallback source. - /// - public class FallbackTextureStore : TextureStore - { - private readonly TextureStore primary; - private readonly TextureStore fallback; + public override Texture Get(string name, WrapMode wrapModeS, WrapMode wrapModeT) + => primary.Get(name, wrapModeS, wrapModeT) ?? fallback.Get(name, wrapModeS, wrapModeT); - public FallbackTextureStore(TextureStore primary, TextureStore fallback) - { - this.primary = primary; - this.fallback = fallback; - } - - public override Texture Get(string name, WrapMode wrapModeS, WrapMode wrapModeT) - => primary.Get(name, wrapModeS, wrapModeT) ?? fallback.Get(name, wrapModeS, wrapModeT); - - protected override void Dispose(bool disposing) - { - base.Dispose(disposing); - primary?.Dispose(); + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + primary?.Dispose(); + } } } } From d666db362366bee77515635474325a7633138607 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 24 Sep 2020 14:46:28 +0900 Subject: [PATCH 1190/1782] Add test --- .../TestSceneDrawableRulesetDependencies.cs | 134 ++++++++++++++++++ 1 file changed, 134 insertions(+) create mode 100644 osu.Game.Tests/Rulesets/TestSceneDrawableRulesetDependencies.cs diff --git a/osu.Game.Tests/Rulesets/TestSceneDrawableRulesetDependencies.cs b/osu.Game.Tests/Rulesets/TestSceneDrawableRulesetDependencies.cs new file mode 100644 index 0000000000..89e3b48aa3 --- /dev/null +++ b/osu.Game.Tests/Rulesets/TestSceneDrawableRulesetDependencies.cs @@ -0,0 +1,134 @@ +// Copyright (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 NUnit.Framework; +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.Graphics.Containers; +using osu.Framework.Graphics.OpenGL.Textures; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Textures; +using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.UI; +using osu.Game.Tests.Visual; + +namespace osu.Game.Tests.Rulesets +{ + public class TestSceneDrawableRulesetDependencies : OsuTestScene + { + [Test] + public void TestDisposalDoesNotDisposeParentStores() + { + DrawableWithDependencies drawable = null; + TestTextureStore textureStore = null; + TestSampleStore sampleStore = null; + + AddStep("add dependencies", () => + { + Child = drawable = new DrawableWithDependencies(); + textureStore = drawable.ParentTextureStore; + sampleStore = drawable.ParentSampleStore; + }); + + AddStep("clear children", Clear); + AddUntilStep("wait for disposal", () => drawable.IsDisposed); + + AddStep("GC", () => + { + drawable = null; + + GC.Collect(); + GC.WaitForPendingFinalizers(); + }); + + AddAssert("parent texture store not disposed", () => !textureStore.IsDisposed); + AddAssert("parent sample store not disposed", () => !sampleStore.IsDisposed); + } + + private class DrawableWithDependencies : CompositeDrawable + { + public TestTextureStore ParentTextureStore { get; private set; } + public TestSampleStore ParentSampleStore { get; private set; } + + public DrawableWithDependencies() + { + InternalChild = new Box { RelativeSizeAxes = Axes.Both }; + } + + protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) + { + var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); + + dependencies.CacheAs(ParentTextureStore = new TestTextureStore()); + dependencies.CacheAs(ParentSampleStore = new TestSampleStore()); + + return new DrawableRulesetDependencies(new OsuRuleset(), dependencies); + } + + public new bool IsDisposed { get; private set; } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + IsDisposed = true; + } + } + + private class TestTextureStore : TextureStore + { + public override Texture Get(string name, WrapMode wrapModeS, WrapMode wrapModeT) => null; + + public bool IsDisposed { get; private set; } + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + IsDisposed = true; + } + } + + private class TestSampleStore : ISampleStore + { + public bool IsDisposed { get; private set; } + + public void Dispose() + { + IsDisposed = true; + } + + public SampleChannel Get(string name) => null; + + public Task GetAsync(string name) => null; + + public Stream GetStream(string name) => null; + + public IEnumerable GetAvailableResources() => throw new NotImplementedException(); + + public BindableNumber Volume => throw new NotImplementedException(); + public BindableNumber Balance => throw new NotImplementedException(); + public BindableNumber Frequency => throw new NotImplementedException(); + public BindableNumber Tempo => throw new NotImplementedException(); + + public void AddAdjustment(AdjustableProperty type, BindableNumber adjustBindable) => throw new NotImplementedException(); + + public void RemoveAdjustment(AdjustableProperty type, BindableNumber adjustBindable) => throw new NotImplementedException(); + + public void RemoveAllAdjustments(AdjustableProperty type) => throw new NotImplementedException(); + + public IBindable AggregateVolume => throw new NotImplementedException(); + public IBindable AggregateBalance => throw new NotImplementedException(); + public IBindable AggregateFrequency => throw new NotImplementedException(); + public IBindable AggregateTempo => throw new NotImplementedException(); + + public int PlaybackConcurrency { get; set; } + } + } +} From 6ebea3f6f2c9f706b49ed5e367da4904c15a574c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 24 Sep 2020 16:09:06 +0900 Subject: [PATCH 1191/1782] Add ability to toggle editor toggles using keyboard shortcuts (Q~P) --- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 83 ++++++++++++++++++++- 1 file changed, 80 insertions(+), 3 deletions(-) diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index 28a77a8bdf..b81e0ce159 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -58,6 +58,8 @@ namespace osu.Game.Rulesets.Edit private RadioButtonCollection toolboxCollection; + private ToolboxGroup togglesCollection; + protected HitObjectComposer(Ruleset ruleset) { Ruleset = ruleset; @@ -115,7 +117,7 @@ namespace osu.Game.Rulesets.Edit Children = new Drawable[] { new ToolboxGroup("toolbox") { Child = toolboxCollection = new RadioButtonCollection { RelativeSizeAxes = Axes.X } }, - new ToolboxGroup("toggles") + togglesCollection = new ToolboxGroup("toggles") { ChildrenEnumerable = Toggles.Select(b => new SettingsCheckbox { @@ -190,9 +192,9 @@ namespace osu.Game.Rulesets.Edit protected override bool OnKeyDown(KeyDownEvent e) { - if (e.Key >= Key.Number1 && e.Key <= Key.Number9) + if (checkLeftToggleFromKey(e.Key, out var leftIndex)) { - var item = toolboxCollection.Items.ElementAtOrDefault(e.Key - Key.Number1); + var item = toolboxCollection.Items.ElementAtOrDefault(leftIndex); if (item != null) { @@ -201,9 +203,84 @@ namespace osu.Game.Rulesets.Edit } } + if (checkRightToggleFromKey(e.Key, out var rightIndex)) + { + var item = togglesCollection.Children[rightIndex]; + + if (item is SettingsCheckbox checkbox) + { + checkbox.Bindable.Value = !checkbox.Bindable.Value; + return true; + } + } + return base.OnKeyDown(e); } + private bool checkLeftToggleFromKey(Key key, out int index) + { + if (key < Key.Number1 || key > Key.Number9) + { + index = -1; + return false; + } + + index = key - Key.Number1; + return true; + } + + private bool checkRightToggleFromKey(Key key, out int index) + { + switch (key) + { + case Key.Q: + index = 0; + break; + + case Key.W: + index = 1; + break; + + case Key.E: + index = 2; + break; + + case Key.R: + index = 3; + break; + + case Key.T: + index = 4; + break; + + case Key.Y: + index = 5; + break; + + case Key.U: + index = 6; + break; + + case Key.I: + index = 7; + break; + + case Key.O: + index = 8; + break; + + case Key.P: + index = 9; + break; + + default: + index = -1; + break; + } + + return index >= 0; + } + private void selectionChanged(object sender, NotifyCollectionChangedEventArgs changedArgs) { if (EditorBeatmap.SelectedHitObjects.Any()) From 44be0ab76220d8c0e1d720fb26181e2c54c00788 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 24 Sep 2020 14:22:18 +0900 Subject: [PATCH 1192/1782] Add basic osu! object to object snapping --- .../Edit/OsuHitObjectComposer.cs | 41 +++++++++++++++++++ .../Compose/Components/BlueprintContainer.cs | 2 +- 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index e1cbfa93f6..9fb9e49ad3 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -15,6 +15,7 @@ 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.Objects.Types; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.UI; using osu.Game.Screens.Edit.Compose.Components; @@ -94,6 +95,10 @@ namespace osu.Game.Rulesets.Osu.Edit public override SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition) { + if (snapToVisibleBlueprints(screenSpacePosition, out var snapResult)) + return snapResult; + + // will be null if distance snap is disabled or not feasible for the current time value. if (distanceSnapGrid == null) return base.SnapScreenSpacePositionToValidTime(screenSpacePosition); @@ -102,6 +107,42 @@ namespace osu.Game.Rulesets.Osu.Edit return new SnapResult(distanceSnapGrid.ToScreenSpace(pos), time, PlayfieldAtScreenSpacePosition(screenSpacePosition)); } + private bool snapToVisibleBlueprints(Vector2 screenSpacePosition, out SnapResult snapResult) + { + // check other on-screen objects for snapping/stacking + var blueprints = BlueprintContainer.SelectionBlueprints.AliveChildren; + + var playfield = PlayfieldAtScreenSpacePosition(screenSpacePosition); + + float snapRadius = + playfield.GamefieldToScreenSpace(new Vector2(OsuHitObject.OBJECT_RADIUS / 5)).X - + playfield.GamefieldToScreenSpace(Vector2.Zero).X; + + foreach (var b in blueprints) + { + if (b.IsSelected) + continue; + + var hitObject = b.HitObject; + + Vector2 startPos = ((IHasPosition)hitObject).Position; + + Vector2 objectScreenPos = playfield.GamefieldToScreenSpace(startPos); + + if (Vector2.Distance(objectScreenPos, screenSpacePosition) < snapRadius) + { + // bypasses time snapping + { + snapResult = new SnapResult(objectScreenPos, null, playfield); + return true; + } + } + } + + snapResult = null; + return false; + } + private void updateDistanceSnapGrid() { distanceSnapGridContainer.Clear(); diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index bf1e18771f..d5e4b4fee5 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -30,7 +30,7 @@ namespace osu.Game.Screens.Edit.Compose.Components { protected DragBox DragBox { get; private set; } - protected Container SelectionBlueprints { get; private set; } + public Container SelectionBlueprints { get; private set; } private SelectionHandler selectionHandler; From d9e8ac6842f1ea3ed145ee07c8885964276b3a18 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 24 Sep 2020 14:34:41 +0900 Subject: [PATCH 1193/1782] Add support for slider end snapping --- .../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 9fb9e49ad3..ad92016cd0 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -15,7 +15,6 @@ 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.Objects.Types; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.UI; using osu.Game.Screens.Edit.Compose.Components; @@ -123,19 +122,27 @@ namespace osu.Game.Rulesets.Osu.Edit if (b.IsSelected) continue; - var hitObject = b.HitObject; + var hitObject = (OsuHitObject)b.HitObject; - Vector2 startPos = ((IHasPosition)hitObject).Position; + Vector2? snap = checkSnap(hitObject.Position); + if (snap == null && hitObject.Position != hitObject.EndPosition) + snap = checkSnap(hitObject.EndPosition); - Vector2 objectScreenPos = playfield.GamefieldToScreenSpace(startPos); - - if (Vector2.Distance(objectScreenPos, screenSpacePosition) < snapRadius) + if (snap != null) { - // bypasses time snapping - { - snapResult = new SnapResult(objectScreenPos, null, playfield); - return true; - } + // only return distance portion, since time is not really valid + snapResult = new SnapResult(snap.Value, null, playfield); + return true; + } + + Vector2? checkSnap(Vector2 checkPos) + { + Vector2 checkScreenPos = playfield.GamefieldToScreenSpace(checkPos); + + if (Vector2.Distance(checkScreenPos, screenSpacePosition) < snapRadius) + return checkScreenPos; + + return null; } } From 1a98e8d7156c5dd624821e20ac59c168d6ebbdc2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 24 Sep 2020 14:46:03 +0900 Subject: [PATCH 1194/1782] Add test coverage of object-object snapping --- .../Editor/TestSceneObjectObjectSnap.cs | 67 +++++++++++++++++++ .../TestSceneOsuEditor.cs} | 4 +- 2 files changed, 69 insertions(+), 2 deletions(-) create mode 100644 osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectObjectSnap.cs rename osu.Game.Rulesets.Osu.Tests/{TestSceneEditor.cs => Editor/TestSceneOsuEditor.cs} (76%) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectObjectSnap.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectObjectSnap.cs new file mode 100644 index 0000000000..b20c790bcb --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectObjectSnap.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 System.Linq; +using NUnit.Framework; +using osu.Framework.Testing; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.UI; +using osu.Game.Tests.Beatmaps; +using osuTK; +using osuTK.Input; + +namespace osu.Game.Rulesets.Osu.Tests.Editor +{ + [TestFixture] + public class TestSceneObjectObjectSnap : TestSceneOsuEditor + { + private OsuPlayfield playfield; + + protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new TestBeatmap(Ruleset.Value, false); + + public override void SetUpSteps() + { + base.SetUpSteps(); + AddStep("get playfield", () => playfield = Editor.ChildrenOfType().First()); + } + + [Test] + public void TestHitCircleSnapsToOtherHitCircle() + { + AddStep("move mouse to centre", () => InputManager.MoveMouseTo(playfield.ScreenSpaceDrawQuad.Centre)); + + AddStep("disable distance snap", () => + { + InputManager.PressKey(Key.Q); + InputManager.ReleaseKey(Key.Q); + }); + + AddStep("enter placement mode", () => + { + InputManager.PressKey(Key.Number2); + InputManager.ReleaseKey(Key.Number2); + }); + + AddStep("place first object", () => InputManager.Click(MouseButton.Left)); + + AddStep("move mouse slightly", () => InputManager.MoveMouseTo(playfield.ScreenSpaceDrawQuad.Centre + new Vector2(5))); + + AddStep("place second object", () => InputManager.Click(MouseButton.Left)); + + AddAssert("both objects at same location", () => + { + var objects = EditorBeatmap.HitObjects; + + var first = (OsuHitObject)objects.First(); + var second = (OsuHitObject)objects.Last(); + + return first.Position == second.Position; + }); + + // TODO: remove + AddWaitStep("wait", 10); + } + } +} diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneEditor.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditor.cs similarity index 76% rename from osu.Game.Rulesets.Osu.Tests/TestSceneEditor.cs rename to osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditor.cs index 9239034a53..e1ca3ddd61 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneEditor.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditor.cs @@ -4,10 +4,10 @@ using NUnit.Framework; using osu.Game.Tests.Visual; -namespace osu.Game.Rulesets.Osu.Tests +namespace osu.Game.Rulesets.Osu.Tests.Editor { [TestFixture] - public class TestSceneEditor : EditorTestScene + public class TestSceneOsuEditor : EditorTestScene { protected override Ruleset CreateEditorRuleset() => new OsuRuleset(); } From 89ded2903c70a92d8a69951c3929b907d092507a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 24 Sep 2020 16:22:47 +0900 Subject: [PATCH 1195/1782] Add test coverage of circle-slider snapping --- .../Editor/TestSceneObjectObjectSnap.cs | 47 +++++++++++++++++-- 1 file changed, 44 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectObjectSnap.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectObjectSnap.cs index b20c790bcb..94f826d01f 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectObjectSnap.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectObjectSnap.cs @@ -4,8 +4,8 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Testing; +using osu.Framework.Utils; using osu.Game.Beatmaps; -using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.UI; using osu.Game.Tests.Beatmaps; @@ -59,9 +59,50 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor return first.Position == second.Position; }); + } - // TODO: remove - AddWaitStep("wait", 10); + [Test] + public void TestHitCircleSnapsToSliderEnd() + { + AddStep("move mouse to centre", () => InputManager.MoveMouseTo(playfield.ScreenSpaceDrawQuad.Centre)); + + AddStep("disable distance snap", () => + { + InputManager.PressKey(Key.Q); + InputManager.ReleaseKey(Key.Q); + }); + + AddStep("enter slider placement mode", () => + { + InputManager.PressKey(Key.Number3); + InputManager.ReleaseKey(Key.Number3); + }); + + AddStep("start slider placement", () => InputManager.Click(MouseButton.Left)); + + AddStep("move to place end", () => InputManager.MoveMouseTo(playfield.ScreenSpaceDrawQuad.Centre + new Vector2(120, 0))); + + AddStep("end slider placement", () => InputManager.Click(MouseButton.Right)); + + AddStep("enter circle placement mode", () => + { + InputManager.PressKey(Key.Number2); + InputManager.ReleaseKey(Key.Number2); + }); + + AddStep("move mouse slightly", () => InputManager.MoveMouseTo(playfield.ScreenSpaceDrawQuad.Centre + new Vector2(130, 0))); + + AddStep("place second object", () => InputManager.Click(MouseButton.Left)); + + AddAssert("circle is at slider's end", () => + { + var objects = EditorBeatmap.HitObjects; + + var first = (Slider)objects.First(); + var second = (OsuHitObject)objects.Last(); + + return Precision.AlmostEquals(first.EndPosition, second.Position); + }); } } } From ead6479442614d54987f51a9c0ae9efb21e4cb67 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 24 Sep 2020 16:31:30 +0900 Subject: [PATCH 1196/1782] Also test with distance snap enabled for sanity --- .../Editor/TestSceneObjectObjectSnap.cs | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectObjectSnap.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectObjectSnap.cs index 94f826d01f..1638ec8224 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectObjectSnap.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectObjectSnap.cs @@ -27,16 +27,20 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor AddStep("get playfield", () => playfield = Editor.ChildrenOfType().First()); } - [Test] - public void TestHitCircleSnapsToOtherHitCircle() + [TestCase(true)] + [TestCase(false)] + public void TestHitCircleSnapsToOtherHitCircle(bool distanceSnapEnabled) { AddStep("move mouse to centre", () => InputManager.MoveMouseTo(playfield.ScreenSpaceDrawQuad.Centre)); - AddStep("disable distance snap", () => + if (!distanceSnapEnabled) { - InputManager.PressKey(Key.Q); - InputManager.ReleaseKey(Key.Q); - }); + AddStep("disable distance snap", () => + { + InputManager.PressKey(Key.Q); + InputManager.ReleaseKey(Key.Q); + }); + } AddStep("enter placement mode", () => { From 15b1069099a8ff53e719839e570f08a8c1cd9fa1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 24 Sep 2020 16:37:08 +0900 Subject: [PATCH 1197/1782] Fix tests not being relative to screen space --- .../Editor/TestSceneObjectObjectSnap.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectObjectSnap.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectObjectSnap.cs index 1638ec8224..da987c7f47 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectObjectSnap.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectObjectSnap.cs @@ -50,7 +50,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor AddStep("place first object", () => InputManager.Click(MouseButton.Left)); - AddStep("move mouse slightly", () => InputManager.MoveMouseTo(playfield.ScreenSpaceDrawQuad.Centre + new Vector2(5))); + AddStep("move mouse slightly", () => InputManager.MoveMouseTo(playfield.ScreenSpaceDrawQuad.Centre + new Vector2(playfield.ScreenSpaceDrawQuad.Width * 0.02f, 0))); AddStep("place second object", () => InputManager.Click(MouseButton.Left)); @@ -84,7 +84,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor AddStep("start slider placement", () => InputManager.Click(MouseButton.Left)); - AddStep("move to place end", () => InputManager.MoveMouseTo(playfield.ScreenSpaceDrawQuad.Centre + new Vector2(120, 0))); + AddStep("move to place end", () => InputManager.MoveMouseTo(playfield.ScreenSpaceDrawQuad.Centre + new Vector2(playfield.ScreenSpaceDrawQuad.Width * 0.185f, 0))); AddStep("end slider placement", () => InputManager.Click(MouseButton.Right)); @@ -94,7 +94,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor InputManager.ReleaseKey(Key.Number2); }); - AddStep("move mouse slightly", () => InputManager.MoveMouseTo(playfield.ScreenSpaceDrawQuad.Centre + new Vector2(130, 0))); + AddStep("move mouse slightly", () => InputManager.MoveMouseTo(playfield.ScreenSpaceDrawQuad.Centre + new Vector2(playfield.ScreenSpaceDrawQuad.Width * 0.20f, 0))); AddStep("place second object", () => InputManager.Click(MouseButton.Left)); From 158d3071261592bc6c5e41b39ac0909e6e938661 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 24 Sep 2020 17:03:54 +0900 Subject: [PATCH 1198/1782] Avoid destroying editor screens when changing between modes --- .../Screens/Edit/Compose/ComposeScreen.cs | 5 +++++ osu.Game/Screens/Edit/Design/DesignScreen.cs | 1 + osu.Game/Screens/Edit/Editor.cs | 20 ++++++++++++++++--- osu.Game/Screens/Edit/EditorScreen.cs | 6 +++++- .../Screens/Edit/EditorScreenWithTimeline.cs | 5 +++++ osu.Game/Screens/Edit/Setup/SetupScreen.cs | 5 +++++ osu.Game/Screens/Edit/Timing/TimingScreen.cs | 5 +++++ 7 files changed, 43 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/ComposeScreen.cs b/osu.Game/Screens/Edit/Compose/ComposeScreen.cs index 04983ca597..d7a4661fa0 100644 --- a/osu.Game/Screens/Edit/Compose/ComposeScreen.cs +++ b/osu.Game/Screens/Edit/Compose/ComposeScreen.cs @@ -13,6 +13,11 @@ namespace osu.Game.Screens.Edit.Compose { private HitObjectComposer composer; + public ComposeScreen() + : base(EditorScreenMode.Compose) + { + } + protected override Drawable CreateMainContent() { var ruleset = Beatmap.Value.BeatmapInfo.Ruleset?.CreateInstance(); diff --git a/osu.Game/Screens/Edit/Design/DesignScreen.cs b/osu.Game/Screens/Edit/Design/DesignScreen.cs index 9f1fcf55b2..f15639733c 100644 --- a/osu.Game/Screens/Edit/Design/DesignScreen.cs +++ b/osu.Game/Screens/Edit/Design/DesignScreen.cs @@ -6,6 +6,7 @@ namespace osu.Game.Screens.Edit.Design public class DesignScreen : EditorScreen { public DesignScreen() + : base(EditorScreenMode.Design) { Child = new ScreenWhiteBox.UnderConstructionMessage("Design mode"); } diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index b7a59bc2e2..2ffffe1c66 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -68,7 +68,7 @@ namespace osu.Game.Screens.Edit private string lastSavedHash; private Box bottomBackground; - private Container screenContainer; + private Container screenContainer; private EditorScreen currentScreen; @@ -163,7 +163,7 @@ namespace osu.Game.Screens.Edit Name = "Screen container", RelativeSizeAxes = Axes.Both, Padding = new MarginPadding { Top = 40, Bottom = 60 }, - Child = screenContainer = new Container + Child = screenContainer = new Container { RelativeSizeAxes = Axes.Both, Masking = true @@ -512,7 +512,21 @@ namespace osu.Game.Screens.Edit private void onModeChanged(ValueChangedEvent e) { - currentScreen?.Exit(); + var lastScreen = currentScreen; + + lastScreen? + .ScaleTo(0.98f, 200, Easing.OutQuint) + .FadeOut(200, Easing.OutQuint); + + if ((currentScreen = screenContainer.FirstOrDefault(s => s.Type == e.NewValue)) != null) + { + screenContainer.ChangeChildDepth(currentScreen, lastScreen?.Depth + 1 ?? 0); + + currentScreen + .ScaleTo(1, 200, Easing.OutQuint) + .FadeIn(200, Easing.OutQuint); + return; + } switch (e.NewValue) { diff --git a/osu.Game/Screens/Edit/EditorScreen.cs b/osu.Game/Screens/Edit/EditorScreen.cs index 8b5f0aaa71..52bffc4342 100644 --- a/osu.Game/Screens/Edit/EditorScreen.cs +++ b/osu.Game/Screens/Edit/EditorScreen.cs @@ -23,8 +23,12 @@ namespace osu.Game.Screens.Edit protected override Container Content => content; private readonly Container content; - protected EditorScreen() + public readonly EditorScreenMode Type; + + protected EditorScreen(EditorScreenMode type) { + Type = type; + Anchor = Anchor.Centre; Origin = Anchor.Centre; RelativeSizeAxes = Axes.Both; diff --git a/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs b/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs index 66d90809db..34eddbefad 100644 --- a/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs +++ b/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs @@ -25,6 +25,11 @@ namespace osu.Game.Screens.Edit private Container timelineContainer; + protected EditorScreenWithTimeline(EditorScreenMode type) + : base(type) + { + } + [BackgroundDependencyLoader(true)] private void load([CanBeNull] BindableBeatDivisor beatDivisor) { diff --git a/osu.Game/Screens/Edit/Setup/SetupScreen.cs b/osu.Game/Screens/Edit/Setup/SetupScreen.cs index a2c8f19016..9dcdcb25f5 100644 --- a/osu.Game/Screens/Edit/Setup/SetupScreen.cs +++ b/osu.Game/Screens/Edit/Setup/SetupScreen.cs @@ -24,6 +24,11 @@ namespace osu.Game.Screens.Edit.Setup private LabelledTextBox creatorTextBox; private LabelledTextBox difficultyTextBox; + public SetupScreen() + : base(EditorScreenMode.SongSetup) + { + } + [BackgroundDependencyLoader] private void load(OsuColour colours) { diff --git a/osu.Game/Screens/Edit/Timing/TimingScreen.cs b/osu.Game/Screens/Edit/Timing/TimingScreen.cs index 8c40c8e721..d7da29218f 100644 --- a/osu.Game/Screens/Edit/Timing/TimingScreen.cs +++ b/osu.Game/Screens/Edit/Timing/TimingScreen.cs @@ -24,6 +24,11 @@ namespace osu.Game.Screens.Edit.Timing [Resolved] private EditorClock clock { get; set; } + public TimingScreen() + : base(EditorScreenMode.Timing) + { + } + protected override Drawable CreateMainContent() => new GridContainer { RelativeSizeAxes = Axes.Both, From 937d5870b3bde89750e79e3f0f5237641114a026 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 24 Sep 2020 18:18:34 +0900 Subject: [PATCH 1199/1782] Add a basic file selector with extension filtering support --- .../Settings/TestSceneDirectorySelector.cs | 3 +- .../Visual/Settings/TestSceneFileSelector.cs | 18 +++++ .../Screens/StablePathSelectScreen.cs | 2 +- .../UserInterfaceV2/DirectorySelector.cs | 79 ++++++++++++------- .../Graphics/UserInterfaceV2/FileSelector.cs | 68 ++++++++++++++++ .../Maintenance/MigrationSelectScreen.cs | 2 +- 6 files changed, 138 insertions(+), 34 deletions(-) create mode 100644 osu.Game.Tests/Visual/Settings/TestSceneFileSelector.cs create mode 100644 osu.Game/Graphics/UserInterfaceV2/FileSelector.cs diff --git a/osu.Game.Tests/Visual/Settings/TestSceneDirectorySelector.cs b/osu.Game.Tests/Visual/Settings/TestSceneDirectorySelector.cs index 0cd0f13b5f..082d85603e 100644 --- a/osu.Game.Tests/Visual/Settings/TestSceneDirectorySelector.cs +++ b/osu.Game.Tests/Visual/Settings/TestSceneDirectorySelector.cs @@ -3,7 +3,6 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; -using osu.Framework.Platform; using osu.Game.Graphics.UserInterfaceV2; namespace osu.Game.Tests.Visual.Settings @@ -11,7 +10,7 @@ namespace osu.Game.Tests.Visual.Settings public class TestSceneDirectorySelector : OsuTestScene { [BackgroundDependencyLoader] - private void load(GameHost host) + private void load() { Add(new DirectorySelector { RelativeSizeAxes = Axes.Both }); } diff --git a/osu.Game.Tests/Visual/Settings/TestSceneFileSelector.cs b/osu.Game.Tests/Visual/Settings/TestSceneFileSelector.cs new file mode 100644 index 0000000000..08173148b0 --- /dev/null +++ b/osu.Game.Tests/Visual/Settings/TestSceneFileSelector.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.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Game.Graphics.UserInterfaceV2; + +namespace osu.Game.Tests.Visual.Settings +{ + public class TestSceneFileSelector : OsuTestScene + { + [BackgroundDependencyLoader] + private void load() + { + Add(new FileSelector { RelativeSizeAxes = Axes.Both }); + } + } +} diff --git a/osu.Game.Tournament/Screens/StablePathSelectScreen.cs b/osu.Game.Tournament/Screens/StablePathSelectScreen.cs index b4d56f60c7..717b43f704 100644 --- a/osu.Game.Tournament/Screens/StablePathSelectScreen.cs +++ b/osu.Game.Tournament/Screens/StablePathSelectScreen.cs @@ -129,7 +129,7 @@ namespace osu.Game.Tournament.Screens protected virtual void ChangePath() { - var target = directorySelector.CurrentDirectory.Value.FullName; + var target = directorySelector.CurrentPath.Value.FullName; var fileBasedIpc = ipc as FileBasedIPC; Logger.Log($"Changing Stable CE location to {target}"); diff --git a/osu.Game/Graphics/UserInterfaceV2/DirectorySelector.cs b/osu.Game/Graphics/UserInterfaceV2/DirectorySelector.cs index ae34281bfb..a1cd074619 100644 --- a/osu.Game/Graphics/UserInterfaceV2/DirectorySelector.cs +++ b/osu.Game/Graphics/UserInterfaceV2/DirectorySelector.cs @@ -28,11 +28,11 @@ namespace osu.Game.Graphics.UserInterfaceV2 private GameHost host { get; set; } [Cached] - public readonly Bindable CurrentDirectory = new Bindable(); + public readonly Bindable CurrentPath = new Bindable(); public DirectorySelector(string initialPath = null) { - CurrentDirectory.Value = new DirectoryInfo(initialPath ?? Environment.GetFolderPath(Environment.SpecialFolder.UserProfile)); + CurrentPath.Value = new DirectoryInfo(initialPath ?? Environment.GetFolderPath(Environment.SpecialFolder.UserProfile)); } [BackgroundDependencyLoader] @@ -74,7 +74,7 @@ namespace osu.Game.Graphics.UserInterfaceV2 } }; - CurrentDirectory.BindValueChanged(updateDisplay, true); + CurrentPath.BindValueChanged(updateDisplay, true); } private void updateDisplay(ValueChangedEvent directory) @@ -92,22 +92,27 @@ namespace osu.Game.Graphics.UserInterfaceV2 } else { - directoryFlow.Add(new ParentDirectoryPiece(CurrentDirectory.Value.Parent)); + directoryFlow.Add(new ParentDirectoryPiece(CurrentPath.Value.Parent)); - foreach (var dir in CurrentDirectory.Value.GetDirectories().OrderBy(d => d.Name)) - { - if ((dir.Attributes & FileAttributes.Hidden) == 0) - directoryFlow.Add(new DirectoryPiece(dir)); - } + directoryFlow.AddRange(GetEntriesForPath(CurrentPath.Value)); } } catch (Exception) { - CurrentDirectory.Value = directory.OldValue; + CurrentPath.Value = directory.OldValue; this.FlashColour(Color4.Red, 300); } } + protected virtual IEnumerable GetEntriesForPath(DirectoryInfo path) + { + foreach (var dir in path.GetDirectories().OrderBy(d => d.Name)) + { + if ((dir.Attributes & FileAttributes.Hidden) == 0) + yield return new DirectoryPiece(dir); + } + } + private class CurrentDirectoryDisplay : CompositeDrawable { [Resolved] @@ -126,7 +131,7 @@ namespace osu.Game.Graphics.UserInterfaceV2 Origin = Anchor.Centre, RelativeSizeAxes = Axes.X, Spacing = new Vector2(5), - Height = DirectoryPiece.HEIGHT, + Height = DisplayPiece.HEIGHT, Direction = FillDirection.Horizontal, }, }; @@ -150,7 +155,7 @@ namespace osu.Game.Graphics.UserInterfaceV2 flow.ChildrenEnumerable = new Drawable[] { - new OsuSpriteText { Text = "Current Directory: ", Font = OsuFont.Default.With(size: DirectoryPiece.HEIGHT), }, + new OsuSpriteText { Text = "Current Directory: ", Font = OsuFont.Default.With(size: DisplayPiece.HEIGHT), }, new ComputerPiece(), }.Concat(pathPieces); } @@ -198,24 +203,44 @@ namespace osu.Game.Graphics.UserInterfaceV2 } } - private class DirectoryPiece : CompositeDrawable + protected class DirectoryPiece : DisplayPiece { - public const float HEIGHT = 20; - - protected const float FONT_SIZE = 16; - protected readonly DirectoryInfo Directory; - private readonly string displayName; - - protected FillFlowContainer Flow; - [Resolved] private Bindable currentDirectory { get; set; } public DirectoryPiece(DirectoryInfo directory, string displayName = null) + : base(displayName) { Directory = directory; + } + + protected override bool OnClick(ClickEvent e) + { + currentDirectory.Value = Directory; + return true; + } + + protected override string FallbackName => Directory.Name; + + protected override IconUsage? Icon => Directory.Name.Contains(Path.DirectorySeparatorChar) + ? FontAwesome.Solid.Database + : FontAwesome.Regular.Folder; + } + + protected abstract class DisplayPiece : CompositeDrawable + { + public const float HEIGHT = 20; + + protected const float FONT_SIZE = 16; + + private readonly string displayName; + + protected FillFlowContainer Flow; + + protected DisplayPiece(string displayName = null) + { this.displayName = displayName; } @@ -259,20 +284,14 @@ namespace osu.Game.Graphics.UserInterfaceV2 { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, - Text = displayName ?? Directory.Name, + Text = displayName ?? FallbackName, Font = OsuFont.Default.With(size: FONT_SIZE) }); } - protected override bool OnClick(ClickEvent e) - { - currentDirectory.Value = Directory; - return true; - } + protected abstract string FallbackName { get; } - protected virtual IconUsage? Icon => Directory.Name.Contains(Path.DirectorySeparatorChar) - ? FontAwesome.Solid.Database - : FontAwesome.Regular.Folder; + protected abstract IconUsage? Icon { get; } } } } diff --git a/osu.Game/Graphics/UserInterfaceV2/FileSelector.cs b/osu.Game/Graphics/UserInterfaceV2/FileSelector.cs new file mode 100644 index 0000000000..861d1887e1 --- /dev/null +++ b/osu.Game/Graphics/UserInterfaceV2/FileSelector.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 System.Collections.Generic; +using System.IO; +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Input.Events; + +namespace osu.Game.Graphics.UserInterfaceV2 +{ + public class FileSelector : DirectorySelector + { + private readonly string[] validFileExtensions; + + [Cached] + public readonly Bindable CurrentFile = new Bindable(); + + public FileSelector(string initialPath = null, string[] validFileExtensions = null) + : base(initialPath) + { + this.validFileExtensions = validFileExtensions ?? Array.Empty(); + } + + protected override IEnumerable GetEntriesForPath(DirectoryInfo path) + { + foreach (var dir in base.GetEntriesForPath(path)) + yield return dir; + + IEnumerable files = path.GetFiles(); + + if (validFileExtensions.Length > 0) + files = files.Where(f => validFileExtensions.Contains(f.Extension)); + + foreach (var file in files.OrderBy(d => d.Name)) + { + if ((file.Attributes & FileAttributes.Hidden) == 0) + yield return new FilePiece(file); + } + } + + protected class FilePiece : DisplayPiece + { + private readonly FileInfo file; + + [Resolved] + private Bindable currentFile { get; set; } + + public FilePiece(FileInfo file) + { + this.file = file; + } + + protected override bool OnClick(ClickEvent e) + { + currentFile.Value = file; + return true; + } + + protected override string FallbackName => file.Name; + + protected override IconUsage? Icon => FontAwesome.Regular.FileAudio; + } + } +} diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationSelectScreen.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationSelectScreen.cs index 79d842a617..ad540e3691 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationSelectScreen.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationSelectScreen.cs @@ -106,7 +106,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance private void start() { - var target = directorySelector.CurrentDirectory.Value; + var target = directorySelector.CurrentPath.Value; try { From ea77ea4a08757dc77cf2d1470666395efe79f349 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 24 Sep 2020 17:24:05 +0900 Subject: [PATCH 1200/1782] Add basic testing of new beatmaps persistence --- .../Editing/TestSceneEditorBeatmapCreation.cs | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs new file mode 100644 index 0000000000..3cc95dec9e --- /dev/null +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.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 NUnit.Framework; +using osu.Game.Beatmaps; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Osu; +using osu.Game.Storyboards; + +namespace osu.Game.Tests.Visual.Editing +{ + public class TestSceneEditorBeatmapCreation : EditorTestScene + { + protected override Ruleset CreateEditorRuleset() => new OsuRuleset(); + + protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null) => new DummyWorkingBeatmap(Audio, null); + + [Test] + public void TestCreateNewBeatmap() + { + AddStep("save beatmap", () => Editor.Save()); + AddAssert("new beatmap persisted", () => EditorBeatmap.BeatmapInfo.ID > 0); + } + } +} From 4b9581bca0cbab33263239a8204dece936de3faa Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 24 Sep 2020 18:18:50 +0900 Subject: [PATCH 1201/1782] Add audio selection to song setup screen --- .../UserInterfaceV2/LabelledTextBox.cs | 9 ++- osu.Game/Screens/Edit/Setup/SetupScreen.cs | 80 +++++++++++++++++++ 2 files changed, 87 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/UserInterfaceV2/LabelledTextBox.cs b/osu.Game/Graphics/UserInterfaceV2/LabelledTextBox.cs index 290aba3468..72d25a7836 100644 --- a/osu.Game/Graphics/UserInterfaceV2/LabelledTextBox.cs +++ b/osu.Game/Graphics/UserInterfaceV2/LabelledTextBox.cs @@ -44,12 +44,17 @@ namespace osu.Game.Graphics.UserInterfaceV2 Component.BorderColour = colours.Blue; } - protected override OsuTextBox CreateComponent() => new OsuTextBox + protected virtual OsuTextBox CreateTextBox() => new OsuTextBox { Anchor = Anchor.Centre, Origin = Anchor.Centre, RelativeSizeAxes = Axes.X, CornerRadius = CORNER_RADIUS, - }.With(t => t.OnCommit += (sender, newText) => OnCommit?.Invoke(sender, newText)); + }; + + protected override OsuTextBox CreateComponent() => CreateTextBox().With(t => + { + t.OnCommit += (sender, newText) => OnCommit?.Invoke(sender, newText); + }); } } diff --git a/osu.Game/Screens/Edit/Setup/SetupScreen.cs b/osu.Game/Screens/Edit/Setup/SetupScreen.cs index a2c8f19016..b0de9e6674 100644 --- a/osu.Game/Screens/Edit/Setup/SetupScreen.cs +++ b/osu.Game/Screens/Edit/Setup/SetupScreen.cs @@ -1,16 +1,21 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; +using System.IO; 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.Graphics.UserInterface; +using osu.Framework.Input.Events; using osu.Game.Beatmaps.Drawables; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterfaceV2; using osuTK; @@ -23,10 +28,17 @@ namespace osu.Game.Screens.Edit.Setup private LabelledTextBox titleTextBox; private LabelledTextBox creatorTextBox; private LabelledTextBox difficultyTextBox; + private LabelledTextBox audioTrackTextBox; [BackgroundDependencyLoader] private void load(OsuColour colours) { + Container audioTrackFileChooserContainer = new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + }; + Child = new Container { RelativeSizeAxes = Axes.Both, @@ -70,6 +82,18 @@ namespace osu.Game.Screens.Edit.Setup }, }, new OsuSpriteText + { + Text = "Resources" + }, + audioTrackTextBox = new FileChooserLabelledTextBox + { + Label = "Audio Track", + Current = { Value = Beatmap.Value.Metadata.AudioFile ?? "Click to select a track" }, + Target = audioTrackFileChooserContainer, + TabbableContentContainer = this + }, + audioTrackFileChooserContainer, + new OsuSpriteText { Text = "Beatmap metadata" }, @@ -120,4 +144,60 @@ namespace osu.Game.Screens.Edit.Setup Beatmap.Value.BeatmapInfo.Version = difficultyTextBox.Current.Value; } } + + internal class FileChooserLabelledTextBox : LabelledTextBox + { + public Container Target; + + private readonly IBindable currentFile = new Bindable(); + + public FileChooserLabelledTextBox() + { + currentFile.BindValueChanged(onFileSelected); + } + + private void onFileSelected(ValueChangedEvent file) + { + if (file.NewValue == null) + return; + + Target.Clear(); + Current.Value = file.NewValue.FullName; + } + + protected override OsuTextBox CreateTextBox() => + new FileChooserOsuTextBox + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.X, + CornerRadius = CORNER_RADIUS, + OnFocused = DisplayFileChooser + }; + + public void DisplayFileChooser() + { + Target.Child = new FileSelector("/Users/Dean/.osu/Songs", new[] { ".mp3", ".ogg" }) + { + RelativeSizeAxes = Axes.X, + Height = 400, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + CurrentFile = { BindTarget = currentFile } + }; + } + + internal class FileChooserOsuTextBox : OsuTextBox + { + public Action OnFocused; + + protected override void OnFocus(FocusEvent e) + { + OnFocused?.Invoke(); + base.OnFocus(e); + + GetContainingInputManager().TriggerFocusContention(this); + } + } + } } From 4d714866cdba4d3ef7c171c6d3f9316eb8cd5d43 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 24 Sep 2020 18:30:02 +0900 Subject: [PATCH 1202/1782] Add ability to actually import a new audio file to the beatmap / database --- osu.Game/Screens/Edit/Setup/SetupScreen.cs | 30 +++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Setup/SetupScreen.cs b/osu.Game/Screens/Edit/Setup/SetupScreen.cs index b0de9e6674..02b8afffe9 100644 --- a/osu.Game/Screens/Edit/Setup/SetupScreen.cs +++ b/osu.Game/Screens/Edit/Setup/SetupScreen.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.Allocation; using osu.Framework.Bindables; @@ -11,13 +10,16 @@ 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.Beatmaps.Drawables; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterfaceV2; +using osu.Game.IO; using osuTK; +using FileInfo = System.IO.FileInfo; namespace osu.Game.Screens.Edit.Setup { @@ -128,10 +130,36 @@ namespace osu.Game.Screens.Edit.Setup } }; + audioTrackTextBox.Current.BindValueChanged(audioTrackChanged); + foreach (var item in flow.OfType()) item.OnCommit += onCommit; } + [Resolved] + private FileStore files { get; set; } + + private void audioTrackChanged(ValueChangedEvent filePath) + { + var info = new FileInfo(filePath.NewValue); + + if (!info.Exists) + audioTrackTextBox.Current.Value = filePath.OldValue; + + IO.FileInfo osuFileInfo; + + using (var stream = info.OpenRead()) + osuFileInfo = files.Add(stream); + + Beatmap.Value.BeatmapSetInfo.Files.Add(new BeatmapSetFileInfo + { + FileInfo = osuFileInfo, + Filename = info.Name + }); + + Beatmap.Value.Metadata.AudioFile = info.Name; + } + private void onCommit(TextBox sender, bool newText) { if (!newText) return; From 65e6dd2ac3e01f205b9d4b838696bd49acf2a7d1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 24 Sep 2020 18:34:17 +0900 Subject: [PATCH 1203/1782] Remove the previous audio file before adding a new one --- osu.Game/Screens/Edit/Setup/SetupScreen.cs | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Setup/SetupScreen.cs b/osu.Game/Screens/Edit/Setup/SetupScreen.cs index 02b8afffe9..ce201e544a 100644 --- a/osu.Game/Screens/Edit/Setup/SetupScreen.cs +++ b/osu.Game/Screens/Edit/Setup/SetupScreen.cs @@ -144,14 +144,29 @@ namespace osu.Game.Screens.Edit.Setup var info = new FileInfo(filePath.NewValue); if (!info.Exists) + { audioTrackTextBox.Current.Value = filePath.OldValue; + return; + } + var beatmapFiles = Beatmap.Value.BeatmapSetInfo.Files; + + // remove the old file + var oldFile = beatmapFiles.FirstOrDefault(f => f.Filename == filePath.OldValue); + + if (oldFile != null) + { + beatmapFiles.Remove(oldFile); + files.Dereference(oldFile.FileInfo); + } + + // add the new file IO.FileInfo osuFileInfo; using (var stream = info.OpenRead()) osuFileInfo = files.Add(stream); - Beatmap.Value.BeatmapSetInfo.Files.Add(new BeatmapSetFileInfo + beatmapFiles.Add(new BeatmapSetFileInfo { FileInfo = osuFileInfo, Filename = info.Name From 978f6edf38488d7299f6e6df27a2887c74a077a2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 24 Sep 2020 18:55:49 +0900 Subject: [PATCH 1204/1782] Add basic track reloading support while inside the editor --- osu.Game/Overlays/MusicController.cs | 5 +++++ osu.Game/Screens/Edit/Editor.cs | 17 +++++++++++++++-- osu.Game/Screens/Edit/EditorClock.cs | 9 +++++++-- osu.Game/Screens/Edit/Setup/SetupScreen.cs | 11 +++++++++++ 4 files changed, 38 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index b568e4d02b..66c9b15c0a 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -81,6 +81,11 @@ namespace osu.Game.Overlays mods.BindValueChanged(_ => ResetTrackAdjustments(), true); } + /// + /// Forcefully reload the current 's track from disk. + /// + public void ForceReloadCurrentBeatmap() => changeTrack(); + /// /// Change the position of a in the current playlist. /// diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index b7a59bc2e2..74b92f3168 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -43,6 +43,7 @@ using osuTK.Input; namespace osu.Game.Screens.Edit { [Cached(typeof(IBeatSnapProvider))] + [Cached] public class Editor : ScreenWithBeatmapBackground, IKeyBindingHandler, IKeyBindingHandler, IBeatSnapProvider { public override float BackgroundParallaxAmount => 0.1f; @@ -91,6 +92,9 @@ namespace osu.Game.Screens.Edit [Resolved] private IAPIProvider api { get; set; } + [Resolved] + private MusicController music { get; set; } + [BackgroundDependencyLoader] private void load(OsuColour colours, GameHost host) { @@ -98,9 +102,9 @@ namespace osu.Game.Screens.Edit 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); + + UpdateClockSource(); dependencies.CacheAs(clock); AddInternal(clock); @@ -271,6 +275,15 @@ namespace osu.Game.Screens.Edit bottomBackground.Colour = colours.Gray2; } + /// + /// If the beatmap's track has changed, this method must be called to keep the editor in a valid state. + /// + public void UpdateClockSource() + { + var sourceClock = (IAdjustableClock)Beatmap.Value.Track ?? new StopwatchClock(); + clock.ChangeSource(sourceClock); + } + protected void Save() { // apply any set-level metadata changes. diff --git a/osu.Game/Screens/Edit/EditorClock.cs b/osu.Game/Screens/Edit/EditorClock.cs index d4d0feb813..a829f23697 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 osu.Framework.Audio.Track; using osu.Framework.Graphics; using osu.Framework.Graphics.Transforms; using osu.Framework.Utils; @@ -17,7 +18,7 @@ namespace osu.Game.Screens.Edit /// public class EditorClock : Component, IFrameBasedClock, IAdjustableClock, ISourceChangeableClock { - public readonly double TrackLength; + public double TrackLength; public ControlPointInfo ControlPointInfo; @@ -190,7 +191,11 @@ namespace osu.Game.Screens.Edit public FrameTimeInfo TimeInfo => underlyingClock.TimeInfo; - public void ChangeSource(IClock source) => underlyingClock.ChangeSource(source); + public void ChangeSource(IClock source) + { + underlyingClock.ChangeSource(source); + TrackLength = (source as Track)?.Length ?? 60000; + } public IClock Source => underlyingClock.Source; diff --git a/osu.Game/Screens/Edit/Setup/SetupScreen.cs b/osu.Game/Screens/Edit/Setup/SetupScreen.cs index ce201e544a..075815203c 100644 --- a/osu.Game/Screens/Edit/Setup/SetupScreen.cs +++ b/osu.Game/Screens/Edit/Setup/SetupScreen.cs @@ -18,6 +18,7 @@ using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterfaceV2; using osu.Game.IO; +using osu.Game.Overlays; using osuTK; using FileInfo = System.IO.FileInfo; @@ -139,6 +140,12 @@ namespace osu.Game.Screens.Edit.Setup [Resolved] private FileStore files { get; set; } + [Resolved] + private MusicController music { get; set; } + + [Resolved] + private Editor editor { get; set; } + private void audioTrackChanged(ValueChangedEvent filePath) { var info = new FileInfo(filePath.NewValue); @@ -173,6 +180,10 @@ namespace osu.Game.Screens.Edit.Setup }); Beatmap.Value.Metadata.AudioFile = info.Name; + + music.ForceReloadCurrentBeatmap(); + + editor.UpdateClockSource(); } private void onCommit(TextBox sender, bool newText) From 7e7e2fd64a6c9e95919028e838fd5439971d83b5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 24 Sep 2020 19:01:28 +0900 Subject: [PATCH 1205/1782] Use bindable for track to fix rate adjustments not applying correctly --- osu.Game/Screens/Edit/Components/BottomBarContainer.cs | 7 +++++-- osu.Game/Screens/Edit/Components/PlaybackControl.cs | 4 ++-- osu.Game/Screens/Edit/EditorClock.cs | 10 +++++++--- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/Edit/Components/BottomBarContainer.cs b/osu.Game/Screens/Edit/Components/BottomBarContainer.cs index cb5078a479..08091fc3f7 100644 --- a/osu.Game/Screens/Edit/Components/BottomBarContainer.cs +++ b/osu.Game/Screens/Edit/Components/BottomBarContainer.cs @@ -18,7 +18,8 @@ namespace osu.Game.Screens.Edit.Components private const float contents_padding = 15; protected readonly IBindable Beatmap = new Bindable(); - protected Track Track => Beatmap.Value.Track; + + protected readonly IBindable Track = new Bindable(); private readonly Drawable background; private readonly Container content; @@ -42,9 +43,11 @@ namespace osu.Game.Screens.Edit.Components } [BackgroundDependencyLoader] - private void load(IBindable beatmap, OsuColour colours) + private void load(IBindable beatmap, OsuColour colours, EditorClock clock) { Beatmap.BindTo(beatmap); + Track.BindTo(clock.Track); + background.Colour = colours.Gray1; } } diff --git a/osu.Game/Screens/Edit/Components/PlaybackControl.cs b/osu.Game/Screens/Edit/Components/PlaybackControl.cs index 59b3d1c565..9739f2876a 100644 --- a/osu.Game/Screens/Edit/Components/PlaybackControl.cs +++ b/osu.Game/Screens/Edit/Components/PlaybackControl.cs @@ -62,12 +62,12 @@ namespace osu.Game.Screens.Edit.Components } }; - Track?.AddAdjustment(AdjustableProperty.Tempo, tempo); + Track.BindValueChanged(tr => tr.NewValue?.AddAdjustment(AdjustableProperty.Tempo, tempo), true); } protected override void Dispose(bool isDisposing) { - Track?.RemoveAdjustment(AdjustableProperty.Tempo, tempo); + Track.Value?.RemoveAdjustment(AdjustableProperty.Tempo, tempo); base.Dispose(isDisposing); } diff --git a/osu.Game/Screens/Edit/EditorClock.cs b/osu.Game/Screens/Edit/EditorClock.cs index a829f23697..ec203df064 100644 --- a/osu.Game/Screens/Edit/EditorClock.cs +++ b/osu.Game/Screens/Edit/EditorClock.cs @@ -4,6 +4,7 @@ using System; using System.Linq; using osu.Framework.Audio.Track; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Transforms; using osu.Framework.Utils; @@ -18,7 +19,11 @@ namespace osu.Game.Screens.Edit /// public class EditorClock : Component, IFrameBasedClock, IAdjustableClock, ISourceChangeableClock { - public double TrackLength; + public IBindable Track => track; + + private readonly Bindable track = new Bindable(); + + public double TrackLength => track.Value?.Length ?? 60000; public ControlPointInfo ControlPointInfo; @@ -36,7 +41,6 @@ namespace osu.Game.Screens.Edit this.beatDivisor = beatDivisor; ControlPointInfo = controlPointInfo; - TrackLength = trackLength; underlyingClock = new DecoupleableInterpolatingFramedClock(); } @@ -193,8 +197,8 @@ namespace osu.Game.Screens.Edit public void ChangeSource(IClock source) { + track.Value = source as Track; underlyingClock.ChangeSource(source); - TrackLength = (source as Track)?.Length ?? 60000; } public IClock Source => underlyingClock.Source; From 833ff1c1d77df62adf003cfa30c1753699e59a77 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 24 Sep 2020 19:12:12 +0900 Subject: [PATCH 1206/1782] Fix test failures due to editor dependency --- osu.Game/Screens/Edit/Setup/SetupScreen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Setup/SetupScreen.cs b/osu.Game/Screens/Edit/Setup/SetupScreen.cs index 075815203c..7090987093 100644 --- a/osu.Game/Screens/Edit/Setup/SetupScreen.cs +++ b/osu.Game/Screens/Edit/Setup/SetupScreen.cs @@ -143,7 +143,7 @@ namespace osu.Game.Screens.Edit.Setup [Resolved] private MusicController music { get; set; } - [Resolved] + [Resolved(canBeNull: true)] private Editor editor { get; set; } private void audioTrackChanged(ValueChangedEvent filePath) From cc9ae328116900fee199aff64e87d44d32f5be7b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 24 Sep 2020 21:05:29 +0900 Subject: [PATCH 1207/1782] Fix summary timeline not updating to new track length correctly --- .../Components/Timelines/Summary/Parts/TimelinePart.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) 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 4a7c3f26bc..5b8f7c747b 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/TimelinePart.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/TimelinePart.cs @@ -3,6 +3,7 @@ using System; using osu.Framework.Allocation; +using osu.Framework.Audio.Track; using osu.Framework.Bindables; using osuTK; using osu.Framework.Graphics; @@ -22,6 +23,8 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts { protected readonly IBindable Beatmap = new Bindable(); + protected readonly IBindable Track = new Bindable(); + private readonly Container content; protected override Container Content => content; @@ -35,12 +38,15 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts updateRelativeChildSize(); LoadBeatmap(b.NewValue); }; + + Track.ValueChanged += _ => updateRelativeChildSize(); } [BackgroundDependencyLoader] - private void load(IBindable beatmap) + private void load(IBindable beatmap, EditorClock clock) { Beatmap.BindTo(beatmap); + Track.BindTo(clock.Track); } private void updateRelativeChildSize() From 011b17624429504950f3befcf6159a931a806f0d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 24 Sep 2020 22:00:13 +0900 Subject: [PATCH 1208/1782] Add test coverage of audio track changing --- .../Editing/TestSceneEditorBeatmapCreation.cs | 35 ++++++++++++++++++ osu.Game/Screens/Edit/Setup/SetupScreen.cs | 36 ++++++++++--------- osu.Game/Tests/Visual/EditorTestScene.cs | 6 ++-- 3 files changed, 59 insertions(+), 18 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs index 3cc95dec9e..8ba172fc70 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs @@ -1,11 +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.IO; +using System.Linq; using NUnit.Framework; +using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; +using osu.Game.Screens.Edit.Setup; using osu.Game.Storyboards; +using osu.Game.Tests.Resources; +using SharpCompress.Archives; +using SharpCompress.Archives.Zip; namespace osu.Game.Tests.Visual.Editing { @@ -13,6 +20,8 @@ namespace osu.Game.Tests.Visual.Editing { protected override Ruleset CreateEditorRuleset() => new OsuRuleset(); + protected override bool EditorComponentsReady => Editor.ChildrenOfType().FirstOrDefault()?.IsLoaded == true; + protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null) => new DummyWorkingBeatmap(Audio, null); [Test] @@ -21,5 +30,31 @@ namespace osu.Game.Tests.Visual.Editing AddStep("save beatmap", () => Editor.Save()); AddAssert("new beatmap persisted", () => EditorBeatmap.BeatmapInfo.ID > 0); } + + [Test] + public void TestAddAudioTrack() + { + AddAssert("switch track to real track", () => + { + var setup = Editor.ChildrenOfType().First(); + + var temp = TestResources.GetTestBeatmapForImport(); + + string extractedFolder = $"{temp}_extracted"; + Directory.CreateDirectory(extractedFolder); + + using (var zip = ZipArchive.Open(temp)) + zip.WriteToDirectory(extractedFolder); + + bool success = setup.ChangeAudioTrack(Path.Combine(extractedFolder, "03. Renatus - Soleily 192kbps.mp3")); + + File.Delete(temp); + Directory.Delete(extractedFolder, true); + + return success; + }); + + AddAssert("track length changed", () => Beatmap.Value.Track.Length > 60000); + } } } diff --git a/osu.Game/Screens/Edit/Setup/SetupScreen.cs b/osu.Game/Screens/Edit/Setup/SetupScreen.cs index 7090987093..e238e1fcf0 100644 --- a/osu.Game/Screens/Edit/Setup/SetupScreen.cs +++ b/osu.Game/Screens/Edit/Setup/SetupScreen.cs @@ -33,6 +33,15 @@ namespace osu.Game.Screens.Edit.Setup private LabelledTextBox difficultyTextBox; private LabelledTextBox audioTrackTextBox; + [Resolved] + private FileStore files { get; set; } + + [Resolved] + private MusicController music { get; set; } + + [Resolved(canBeNull: true)] + private Editor editor { get; set; } + [BackgroundDependencyLoader] private void load(OsuColour colours) { @@ -137,29 +146,17 @@ namespace osu.Game.Screens.Edit.Setup item.OnCommit += onCommit; } - [Resolved] - private FileStore files { get; set; } - - [Resolved] - private MusicController music { get; set; } - - [Resolved(canBeNull: true)] - private Editor editor { get; set; } - - private void audioTrackChanged(ValueChangedEvent filePath) + public bool ChangeAudioTrack(string path) { - var info = new FileInfo(filePath.NewValue); + var info = new FileInfo(path); if (!info.Exists) - { - audioTrackTextBox.Current.Value = filePath.OldValue; - return; - } + return false; var beatmapFiles = Beatmap.Value.BeatmapSetInfo.Files; // remove the old file - var oldFile = beatmapFiles.FirstOrDefault(f => f.Filename == filePath.OldValue); + var oldFile = beatmapFiles.FirstOrDefault(f => f.Filename == Beatmap.Value.Metadata.AudioFile); if (oldFile != null) { @@ -184,6 +181,13 @@ namespace osu.Game.Screens.Edit.Setup music.ForceReloadCurrentBeatmap(); editor.UpdateClockSource(); + return true; + } + + private void audioTrackChanged(ValueChangedEvent filePath) + { + if (!ChangeAudioTrack(filePath.NewValue)) + audioTrackTextBox.Current.Value = filePath.OldValue; } private void onCommit(TextBox sender, bool newText) diff --git a/osu.Game/Tests/Visual/EditorTestScene.cs b/osu.Game/Tests/Visual/EditorTestScene.cs index 8f76f247cf..a9ee8e2668 100644 --- a/osu.Game/Tests/Visual/EditorTestScene.cs +++ b/osu.Game/Tests/Visual/EditorTestScene.cs @@ -26,13 +26,15 @@ namespace osu.Game.Tests.Visual Beatmap.Value = CreateWorkingBeatmap(Ruleset.Value); } + protected virtual bool EditorComponentsReady => Editor.ChildrenOfType().FirstOrDefault()?.IsLoaded == true + && Editor.ChildrenOfType().FirstOrDefault()?.IsLoaded == true; + public override void SetUpSteps() { base.SetUpSteps(); AddStep("load editor", () => LoadScreen(Editor = CreateEditor())); - AddUntilStep("wait for editor to load", () => Editor.ChildrenOfType().FirstOrDefault()?.IsLoaded == true - && Editor.ChildrenOfType().FirstOrDefault()?.IsLoaded == true); + AddUntilStep("wait for editor to load", () => EditorComponentsReady); AddStep("get beatmap", () => EditorBeatmap = Editor.ChildrenOfType().Single()); AddStep("get clock", () => EditorClock = Editor.ChildrenOfType().Single()); } From 94c1cc8ffa4b8eefb0c7bd498084e0d2dbee9c6f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 24 Sep 2020 22:25:04 +0900 Subject: [PATCH 1209/1782] Fix test runs under headless --- .../Visual/Editing/TestSceneEditorBeatmapCreation.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs index 8ba172fc70..7215b80a97 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.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.IO; using System.Linq; using NUnit.Framework; @@ -22,11 +23,17 @@ namespace osu.Game.Tests.Visual.Editing protected override bool EditorComponentsReady => Editor.ChildrenOfType().FirstOrDefault()?.IsLoaded == true; - protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null) => new DummyWorkingBeatmap(Audio, null); + public override void SetUpSteps() + { + AddStep("set dummy", () => Beatmap.Value = new DummyWorkingBeatmap(Audio, null)); + + base.SetUpSteps(); + } [Test] public void TestCreateNewBeatmap() { + AddStep("add random hitobject", () => EditorBeatmap.Metadata.Title = Guid.NewGuid().ToString()); AddStep("save beatmap", () => Editor.Save()); AddAssert("new beatmap persisted", () => EditorBeatmap.BeatmapInfo.ID > 0); } From dbc522aedea4e0cf3e5b36b07e4e5f04d36f4b0c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 24 Sep 2020 22:41:52 +0900 Subject: [PATCH 1210/1782] Remove weird using --- osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs index 7215b80a97..df2fdfa79d 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs @@ -10,7 +10,6 @@ using osu.Game.Beatmaps; using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; using osu.Game.Screens.Edit.Setup; -using osu.Game.Storyboards; using osu.Game.Tests.Resources; using SharpCompress.Archives; using SharpCompress.Archives.Zip; From c3df7e1fa8c897971e9662f909ada3444e203930 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 25 Sep 2020 01:05:12 +0900 Subject: [PATCH 1211/1782] Fix scroll container's scrollbar not respecting minimum size on first resize --- osu.Game/Graphics/Containers/OsuScrollContainer.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Graphics/Containers/OsuScrollContainer.cs b/osu.Game/Graphics/Containers/OsuScrollContainer.cs index d504a11b22..ed5c73bee6 100644 --- a/osu.Game/Graphics/Containers/OsuScrollContainer.cs +++ b/osu.Game/Graphics/Containers/OsuScrollContainer.cs @@ -112,6 +112,9 @@ namespace osu.Game.Graphics.Containers CornerRadius = 5; + // needs to be set initially for the ResizeTo to respect minimum size + Size = new Vector2(SCROLL_BAR_HEIGHT); + const float margin = 3; Margin = new MarginPadding From 6ff26f6b8c943e1178a5e4ebf78c086501cc75cb Mon Sep 17 00:00:00 2001 From: Joehu Date: Thu, 24 Sep 2020 12:52:42 -0700 Subject: [PATCH 1212/1782] Fix anchor of tournament ruleset selector dropdown --- osu.Game/Graphics/UserInterfaceV2/LabelledDrawable.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/UserInterfaceV2/LabelledDrawable.cs b/osu.Game/Graphics/UserInterfaceV2/LabelledDrawable.cs index 0e995ca73d..af0043436a 100644 --- a/osu.Game/Graphics/UserInterfaceV2/LabelledDrawable.cs +++ b/osu.Game/Graphics/UserInterfaceV2/LabelledDrawable.cs @@ -73,8 +73,8 @@ namespace osu.Game.Graphics.UserInterfaceV2 }, new Container { - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Child = Component = CreateComponent().With(d => From 8a0c79466d8fdf159b07d360b150b27b9e73b23d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 25 Sep 2020 12:16:50 +0900 Subject: [PATCH 1213/1782] Use simplified methods for press/release key --- .../Editor/TestSceneObjectObjectSnap.cs | 32 +++---------------- 1 file changed, 5 insertions(+), 27 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectObjectSnap.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectObjectSnap.cs index da987c7f47..1ca94df26b 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectObjectSnap.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectObjectSnap.cs @@ -34,19 +34,9 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor AddStep("move mouse to centre", () => InputManager.MoveMouseTo(playfield.ScreenSpaceDrawQuad.Centre)); if (!distanceSnapEnabled) - { - AddStep("disable distance snap", () => - { - InputManager.PressKey(Key.Q); - InputManager.ReleaseKey(Key.Q); - }); - } + AddStep("disable distance snap", () => InputManager.Key(Key.Q)); - AddStep("enter placement mode", () => - { - InputManager.PressKey(Key.Number2); - InputManager.ReleaseKey(Key.Number2); - }); + AddStep("enter placement mode", () => InputManager.Key(Key.Number2)); AddStep("place first object", () => InputManager.Click(MouseButton.Left)); @@ -70,17 +60,9 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor { AddStep("move mouse to centre", () => InputManager.MoveMouseTo(playfield.ScreenSpaceDrawQuad.Centre)); - AddStep("disable distance snap", () => - { - InputManager.PressKey(Key.Q); - InputManager.ReleaseKey(Key.Q); - }); + AddStep("disable distance snap", () => InputManager.Key(Key.Q)); - AddStep("enter slider placement mode", () => - { - InputManager.PressKey(Key.Number3); - InputManager.ReleaseKey(Key.Number3); - }); + AddStep("enter slider placement mode", () => InputManager.Key(Key.Number3)); AddStep("start slider placement", () => InputManager.Click(MouseButton.Left)); @@ -88,11 +70,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor AddStep("end slider placement", () => InputManager.Click(MouseButton.Right)); - AddStep("enter circle placement mode", () => - { - InputManager.PressKey(Key.Number2); - InputManager.ReleaseKey(Key.Number2); - }); + AddStep("enter circle placement mode", () => InputManager.Key(Key.Number2)); AddStep("move mouse slightly", () => InputManager.MoveMouseTo(playfield.ScreenSpaceDrawQuad.Centre + new Vector2(playfield.ScreenSpaceDrawQuad.Width * 0.20f, 0))); From 44a6637c36a065f13b22d7786a8f22ed16c40840 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 25 Sep 2020 12:20:37 +0900 Subject: [PATCH 1214/1782] Use SingleOrDefault --- 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 2ffffe1c66..c9d57785f9 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -518,7 +518,7 @@ namespace osu.Game.Screens.Edit .ScaleTo(0.98f, 200, Easing.OutQuint) .FadeOut(200, Easing.OutQuint); - if ((currentScreen = screenContainer.FirstOrDefault(s => s.Type == e.NewValue)) != null) + if ((currentScreen = screenContainer.SingleOrDefault(s => s.Type == e.NewValue)) != null) { screenContainer.ChangeChildDepth(currentScreen, lastScreen?.Depth + 1 ?? 0); From d602072ee3ddb834899295a7c664b6f170d73175 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 25 Sep 2020 12:24:41 +0900 Subject: [PATCH 1215/1782] Use SingleOrDefault where feasible --- osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs index df2fdfa79d..d4976c3d48 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs @@ -20,7 +20,7 @@ namespace osu.Game.Tests.Visual.Editing { protected override Ruleset CreateEditorRuleset() => new OsuRuleset(); - protected override bool EditorComponentsReady => Editor.ChildrenOfType().FirstOrDefault()?.IsLoaded == true; + protected override bool EditorComponentsReady => Editor.ChildrenOfType().SingleOrDefault()?.IsLoaded == true; public override void SetUpSteps() { From 9846d87eb0787ec65f0df9ec6558448dbb18f0b1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 25 Sep 2020 12:25:50 +0900 Subject: [PATCH 1216/1782] Fix misleading step name (and add comment as to its purpose) --- .../Visual/Editing/TestSceneEditorBeatmapCreation.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs index d4976c3d48..ceacbd51a2 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs @@ -32,7 +32,10 @@ namespace osu.Game.Tests.Visual.Editing [Test] public void TestCreateNewBeatmap() { - AddStep("add random hitobject", () => EditorBeatmap.Metadata.Title = Guid.NewGuid().ToString()); + // if we save a beatmap with a hash collision, things fall over. + // probably needs a more solid resolution in the future but this will do for now. + AddStep("make new beatmap unique", () => EditorBeatmap.Metadata.Title = Guid.NewGuid().ToString()); + AddStep("save beatmap", () => Editor.Save()); AddAssert("new beatmap persisted", () => EditorBeatmap.BeatmapInfo.ID > 0); } From a17eac3692441e5664bc98c31c6b1c71a5c8ece2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 25 Sep 2020 12:27:08 +0900 Subject: [PATCH 1217/1782] Rename reload method to not mention beatmap unnecessarily --- osu.Game/Overlays/MusicController.cs | 2 +- osu.Game/Screens/Edit/Setup/SetupScreen.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index 66c9b15c0a..0764f34697 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -84,7 +84,7 @@ namespace osu.Game.Overlays /// /// Forcefully reload the current 's track from disk. /// - public void ForceReloadCurrentBeatmap() => changeTrack(); + public void ReloadCurrentTrack() => changeTrack(); /// /// Change the position of a in the current playlist. diff --git a/osu.Game/Screens/Edit/Setup/SetupScreen.cs b/osu.Game/Screens/Edit/Setup/SetupScreen.cs index e238e1fcf0..9c72268eea 100644 --- a/osu.Game/Screens/Edit/Setup/SetupScreen.cs +++ b/osu.Game/Screens/Edit/Setup/SetupScreen.cs @@ -178,7 +178,7 @@ namespace osu.Game.Screens.Edit.Setup Beatmap.Value.Metadata.AudioFile = info.Name; - music.ForceReloadCurrentBeatmap(); + music.ReloadCurrentTrack(); editor.UpdateClockSource(); return true; From b1e72c311edc68d3c98136349b4597418ba5e6c5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 25 Sep 2020 12:28:41 +0900 Subject: [PATCH 1218/1782] Add null check because we can --- osu.Game/Screens/Edit/Setup/SetupScreen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Setup/SetupScreen.cs b/osu.Game/Screens/Edit/Setup/SetupScreen.cs index 9c72268eea..4d553756b8 100644 --- a/osu.Game/Screens/Edit/Setup/SetupScreen.cs +++ b/osu.Game/Screens/Edit/Setup/SetupScreen.cs @@ -180,7 +180,7 @@ namespace osu.Game.Screens.Edit.Setup music.ReloadCurrentTrack(); - editor.UpdateClockSource(); + editor?.UpdateClockSource(); return true; } From f047ff10bfcb616b236b864cd5ae663d74875100 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 25 Sep 2020 12:30:05 +0900 Subject: [PATCH 1219/1782] Remove local specification for file selector search path --- osu.Game/Screens/Edit/Setup/SetupScreen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Setup/SetupScreen.cs b/osu.Game/Screens/Edit/Setup/SetupScreen.cs index 4d553756b8..edbe75448b 100644 --- a/osu.Game/Screens/Edit/Setup/SetupScreen.cs +++ b/osu.Game/Screens/Edit/Setup/SetupScreen.cs @@ -235,7 +235,7 @@ namespace osu.Game.Screens.Edit.Setup public void DisplayFileChooser() { - Target.Child = new FileSelector("/Users/Dean/.osu/Songs", new[] { ".mp3", ".ogg" }) + Target.Child = new FileSelector(validFileExtensions: new[] { ".mp3", ".ogg" }) { RelativeSizeAxes = Axes.X, Height = 400, From a890e5830d8e2af9a200fd170d91202bc87fd514 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 25 Sep 2020 12:42:28 +0900 Subject: [PATCH 1220/1782] Add more file icons --- .../Graphics/UserInterfaceV2/FileSelector.cs | 28 ++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/osu.Game/Graphics/UserInterfaceV2/FileSelector.cs b/osu.Game/Graphics/UserInterfaceV2/FileSelector.cs index 861d1887e1..e10b8f7033 100644 --- a/osu.Game/Graphics/UserInterfaceV2/FileSelector.cs +++ b/osu.Game/Graphics/UserInterfaceV2/FileSelector.cs @@ -62,7 +62,33 @@ namespace osu.Game.Graphics.UserInterfaceV2 protected override string FallbackName => file.Name; - protected override IconUsage? Icon => FontAwesome.Regular.FileAudio; + protected override IconUsage? Icon + { + get + { + switch (file.Extension) + { + case ".ogg": + case ".mp3": + case ".wav": + return FontAwesome.Regular.FileAudio; + + case ".jpg": + case ".jpeg": + case ".png": + return FontAwesome.Regular.FileImage; + + case ".mp4": + case ".avi": + case ".mov": + case ".flv": + return FontAwesome.Regular.FileVideo; + + default: + return FontAwesome.Regular.File; + } + } + } } } } From a8c85ed882f2e7c7424e86d4a79ac7a6e05387ba Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 25 Sep 2020 12:42:37 +0900 Subject: [PATCH 1221/1782] Add test for filtered mode --- .../Visual/Settings/TestSceneFileSelector.cs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Settings/TestSceneFileSelector.cs b/osu.Game.Tests/Visual/Settings/TestSceneFileSelector.cs index 08173148b0..311e4c3362 100644 --- a/osu.Game.Tests/Visual/Settings/TestSceneFileSelector.cs +++ b/osu.Game.Tests/Visual/Settings/TestSceneFileSelector.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 osu.Framework.Allocation; +using NUnit.Framework; using osu.Framework.Graphics; using osu.Game.Graphics.UserInterfaceV2; @@ -9,10 +9,16 @@ namespace osu.Game.Tests.Visual.Settings { public class TestSceneFileSelector : OsuTestScene { - [BackgroundDependencyLoader] - private void load() + [Test] + public void TestAllFiles() { - Add(new FileSelector { RelativeSizeAxes = Axes.Both }); + AddStep("create", () => Child = new FileSelector { RelativeSizeAxes = Axes.Both }); + } + + [Test] + public void TestJpgFilesOnly() + { + AddStep("create", () => Child = new FileSelector(validFileExtensions: new[] { ".jpg" }) { RelativeSizeAxes = Axes.Both }); } } } From c21745eb075e2f0e44c3d8ea5b314f1224234622 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 25 Sep 2020 12:43:41 +0900 Subject: [PATCH 1222/1782] Fix missing HeadlessTest specification in new test --- osu.Game.Tests/Rulesets/TestSceneDrawableRulesetDependencies.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Tests/Rulesets/TestSceneDrawableRulesetDependencies.cs b/osu.Game.Tests/Rulesets/TestSceneDrawableRulesetDependencies.cs index 89e3b48aa3..33e3c7cb8c 100644 --- a/osu.Game.Tests/Rulesets/TestSceneDrawableRulesetDependencies.cs +++ b/osu.Game.Tests/Rulesets/TestSceneDrawableRulesetDependencies.cs @@ -16,12 +16,14 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.OpenGL.Textures; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Textures; +using osu.Framework.Testing; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.UI; using osu.Game.Tests.Visual; namespace osu.Game.Tests.Rulesets { + [HeadlessTest] public class TestSceneDrawableRulesetDependencies : OsuTestScene { [Test] From eff6af3111eba46adb782205eca4f2b7a40347c2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 25 Sep 2020 12:45:13 +0900 Subject: [PATCH 1223/1782] Add "bindables" to dictionary --- osu.sln.DotSettings | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.sln.DotSettings b/osu.sln.DotSettings index 29ca385275..64f3d41acb 100644 --- a/osu.sln.DotSettings +++ b/osu.sln.DotSettings @@ -909,6 +909,7 @@ private void load() True True True + True True True True From 50ba320a5118e3bcd6c5d40022ca2df22ac4ab89 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 25 Sep 2020 13:10:04 +0900 Subject: [PATCH 1224/1782] Expand available file operations in ArchiveModelManager --- osu.Game/Beatmaps/BeatmapManager.cs | 2 +- osu.Game/Database/ArchiveModelManager.cs | 44 +++++++++++++++++++----- 2 files changed, 36 insertions(+), 10 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index e9f41f6bff..b48ab6112e 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -260,7 +260,7 @@ namespace osu.Game.Beatmaps fileInfo.Filename = beatmapInfo.Path; stream.Seek(0, SeekOrigin.Begin); - UpdateFile(setInfo, fileInfo, stream); + ReplaceFile(setInfo, fileInfo, stream); } } diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index 76bc4f7755..c39b71b058 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -401,29 +401,55 @@ namespace osu.Game.Database } /// - /// Update an existing file, or create a new entry if not already part of the 's files. + /// Replace an existing file with a new version. /// /// The item to operate on. - /// The file model to be updated or added. + /// The existing file to be replaced. /// The new file contents. - public void UpdateFile(TModel model, TFileModel file, Stream contents) + /// An optional filename for the new file. Will use the previous filename if not specified. + public void ReplaceFile(TModel model, TFileModel file, Stream contents, string filename = null) + { + using (ContextFactory.GetForWrite()) + { + DeleteFile(model, file); + AddFile(model, contents, filename ?? file.Filename); + } + } + + /// + /// Delete new file. + /// + /// The item to operate on. + /// The existing file to be deleted. + public void DeleteFile(TModel model, TFileModel file) { using (var usage = ContextFactory.GetForWrite()) { // Dereference the existing file info, since the file model will be removed. if (file.FileInfo != null) - { Files.Dereference(file.FileInfo); - // Remove the file model. - usage.Context.Set().Remove(file); - } + // This shouldn't be required, but here for safety in case the provided TModel is not being change tracked + // Definitely can be removed once we rework the database backend. + usage.Context.Set().Remove(file); - // Add the new file info and containing file model. model.Files.Remove(file); + } + } + + /// + /// Add a new file. + /// + /// The item to operate on. + /// The new file contents. + /// The filename for the new file. + public void AddFile(TModel model, Stream contents, string filename) + { + using (ContextFactory.GetForWrite()) + { model.Files.Add(new TFileModel { - Filename = file.Filename, + Filename = filename, FileInfo = Files.Add(contents) }); From ea971ecb9008f2b09cee1fe546cf8a6ce4c65c81 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 25 Sep 2020 13:11:34 +0900 Subject: [PATCH 1225/1782] Remove local file handling from SetupScreen --- osu.Game/Screens/Edit/Setup/SetupScreen.cs | 34 ++++++++-------------- 1 file changed, 12 insertions(+), 22 deletions(-) diff --git a/osu.Game/Screens/Edit/Setup/SetupScreen.cs b/osu.Game/Screens/Edit/Setup/SetupScreen.cs index edbe75448b..802f304835 100644 --- a/osu.Game/Screens/Edit/Setup/SetupScreen.cs +++ b/osu.Game/Screens/Edit/Setup/SetupScreen.cs @@ -2,6 +2,7 @@ // 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.Bindables; @@ -17,10 +18,8 @@ using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterfaceV2; -using osu.Game.IO; using osu.Game.Overlays; using osuTK; -using FileInfo = System.IO.FileInfo; namespace osu.Game.Screens.Edit.Setup { @@ -34,10 +33,10 @@ namespace osu.Game.Screens.Edit.Setup private LabelledTextBox audioTrackTextBox; [Resolved] - private FileStore files { get; set; } + private MusicController music { get; set; } [Resolved] - private MusicController music { get; set; } + private BeatmapManager beatmaps { get; set; } [Resolved(canBeNull: true)] private Editor editor { get; set; } @@ -153,28 +152,19 @@ namespace osu.Game.Screens.Edit.Setup if (!info.Exists) return false; - var beatmapFiles = Beatmap.Value.BeatmapSetInfo.Files; + var set = Beatmap.Value.BeatmapSetInfo; - // remove the old file - var oldFile = beatmapFiles.FirstOrDefault(f => f.Filename == Beatmap.Value.Metadata.AudioFile); - - if (oldFile != null) - { - beatmapFiles.Remove(oldFile); - files.Dereference(oldFile.FileInfo); - } - - // add the new file - IO.FileInfo osuFileInfo; + // remove the previous audio track for now. + // in the future we probably want to check if this is being used elsewhere (other difficulties?) + var oldFile = set.Files.FirstOrDefault(f => f.Filename == Beatmap.Value.Metadata.AudioFile); using (var stream = info.OpenRead()) - osuFileInfo = files.Add(stream); - - beatmapFiles.Add(new BeatmapSetFileInfo { - FileInfo = osuFileInfo, - Filename = info.Name - }); + if (oldFile != null) + beatmaps.ReplaceFile(set, oldFile, stream, info.Name); + else + beatmaps.AddFile(set, stream, info.Name); + } Beatmap.Value.Metadata.AudioFile = info.Name; From 892d440ed0f337ac47ed192f6db3fdebfbfa5b19 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 25 Sep 2020 13:19:07 +0900 Subject: [PATCH 1226/1782] Add fallback path for potential null ParentGameplayClock --- osu.Game/Rulesets/UI/FrameStabilityContainer.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs index b585a78f42..6716f828ed 100644 --- a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs +++ b/osu.Game/Rulesets/UI/FrameStabilityContainer.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; @@ -195,7 +196,7 @@ namespace osu.Game.Rulesets.UI { public GameplayClock ParentGameplayClock; - public override IEnumerable> NonGameplayAdjustments => ParentGameplayClock.NonGameplayAdjustments; + public override IEnumerable> NonGameplayAdjustments => ParentGameplayClock?.NonGameplayAdjustments ?? Enumerable.Empty>(); public StabilityGameplayClock(FramedClock underlyingClock) : base(underlyingClock) From 26ba7d3100ff77a91cf01ae4737f3c014b7fe5ca Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 25 Sep 2020 13:20:19 +0900 Subject: [PATCH 1227/1782] Remove unused method (was moved to a more local location) --- osu.Game/Screens/Play/GameplayClockContainer.cs | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index 4094de1c4f..6679e56871 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -210,19 +210,6 @@ namespace osu.Game.Screens.Play base.Update(); } - private double getTrueGameplayRate() - { - double baseRate = track.Rate; - - if (speedAdjustmentsApplied) - { - baseRate /= UserPlaybackRate.Value; - baseRate /= pauseFreqAdjust.Value; - } - - return baseRate; - } - private bool speedAdjustmentsApplied; private void updateRate() From 325bfdbf7139a779341d7ecd48fb758b2de4556a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 25 Sep 2020 13:25:42 +0900 Subject: [PATCH 1228/1782] Fix hard crash on hitting an out of range key (Q~P) --- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index b81e0ce159..e2a49221c0 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -205,7 +205,7 @@ namespace osu.Game.Rulesets.Edit if (checkRightToggleFromKey(e.Key, out var rightIndex)) { - var item = togglesCollection.Children[rightIndex]; + var item = togglesCollection.ElementAtOrDefault(rightIndex); if (item is SettingsCheckbox checkbox) { From 3c191cfe257280d2ef89983435cb4b3f22fff3a9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 25 Sep 2020 14:08:47 +0900 Subject: [PATCH 1229/1782] Add basic xmldoc to HitObjectComposer --- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index b81e0ce159..0ea57ef4e1 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -31,6 +31,11 @@ using osuTK.Input; namespace osu.Game.Rulesets.Edit { + /// + /// Top level container for editor compose mode. + /// Responsible for providing snapping and generally gluing components together. + /// + /// The base type of supported objects. [Cached(Type = typeof(IPlacementHandler))] public abstract class HitObjectComposer : HitObjectComposer, IPlacementHandler where TObject : HitObject From bca774a0d4c16b8ca53ddd5dc7d19061dbf35971 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 25 Sep 2020 14:09:31 +0900 Subject: [PATCH 1230/1782] Allow BlueprintContainer to specify toggles --- osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs | 4 ++-- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index e1cbfa93f6..2127385ab5 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -41,10 +41,10 @@ namespace osu.Game.Rulesets.Osu.Edit private readonly BindableBool distanceSnapToggle = new BindableBool(true) { Description = "Distance Snap" }; - protected override IEnumerable Toggles => new[] + protected override IEnumerable> Toggles => base.Toggles.Concat(new[] { distanceSnapToggle - }; + }); private BindableList selectedHitObjects; diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index 0ea57ef4e1..b2d7c40a22 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -170,7 +170,7 @@ namespace osu.Game.Rulesets.Edit /// A collection of toggles which will be displayed to the user. /// The display name will be decided by . /// - protected virtual IEnumerable Toggles => Enumerable.Empty(); + protected virtual IEnumerable> Toggles => BlueprintContainer.Toggles; /// /// Construct a relevant blueprint container. This will manage hitobject selection/placement input handling and display logic. From e009264f1029fa336439f2a2d10d081b6b2d9c03 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 25 Sep 2020 14:10:30 +0900 Subject: [PATCH 1231/1782] Add new combo toggle to main composer interface --- osu.Game/Rulesets/Edit/PlacementBlueprint.cs | 2 +- .../Compose/Components/BlueprintContainer.cs | 18 ++++---- .../Components/ComposeBlueprintContainer.cs | 41 +++++++++++++++++++ .../Compose/Components/SelectionHandler.cs | 2 + 4 files changed, 53 insertions(+), 10 deletions(-) diff --git a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs index 02d5955ae6..d986b71380 100644 --- a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs +++ b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs @@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Edit /// /// The that is being placed. /// - protected readonly HitObject HitObject; + public readonly HitObject HitObject; [Resolved(canBeNull: true)] protected EditorClock EditorClock { get; private set; } diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index bf1e18771f..aa567dbdf4 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -41,7 +41,7 @@ namespace osu.Game.Screens.Edit.Compose.Components private EditorClock editorClock { get; set; } [Resolved] - private EditorBeatmap beatmap { get; set; } + protected EditorBeatmap Beatmap { get; private set; } private readonly BindableList selectedHitObjects = new BindableList(); @@ -68,10 +68,10 @@ namespace osu.Game.Screens.Edit.Compose.Components DragBox.CreateProxy().With(p => p.Depth = float.MinValue) }); - foreach (var obj in beatmap.HitObjects) + foreach (var obj in Beatmap.HitObjects) AddBlueprintFor(obj); - selectedHitObjects.BindTo(beatmap.SelectedHitObjects); + selectedHitObjects.BindTo(Beatmap.SelectedHitObjects); selectedHitObjects.CollectionChanged += (selectedObjects, args) => { switch (args.Action) @@ -94,8 +94,8 @@ namespace osu.Game.Screens.Edit.Compose.Components { base.LoadComplete(); - beatmap.HitObjectAdded += AddBlueprintFor; - beatmap.HitObjectRemoved += removeBlueprintFor; + Beatmap.HitObjectAdded += AddBlueprintFor; + Beatmap.HitObjectRemoved += removeBlueprintFor; } protected virtual Container CreateSelectionBlueprintContainer() => @@ -271,7 +271,7 @@ namespace osu.Game.Screens.Edit.Compose.Components blueprint.Selected += onBlueprintSelected; blueprint.Deselected += onBlueprintDeselected; - if (beatmap.SelectedHitObjects.Contains(hitObject)) + if (Beatmap.SelectedHitObjects.Contains(hitObject)) blueprint.Select(); SelectionBlueprints.Add(blueprint); @@ -460,10 +460,10 @@ namespace osu.Game.Screens.Edit.Compose.Components { base.Dispose(isDisposing); - if (beatmap != null) + if (Beatmap != null) { - beatmap.HitObjectAdded -= AddBlueprintFor; - beatmap.HitObjectRemoved -= removeBlueprintFor; + Beatmap.HitObjectAdded -= AddBlueprintFor; + Beatmap.HitObjectRemoved -= removeBlueprintFor; } } } diff --git a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs index e1f311f1b8..2a7c52579f 100644 --- a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs @@ -4,6 +4,7 @@ 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.Input; @@ -11,6 +12,7 @@ using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Tools; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Objects.Types; using osuTK; namespace osu.Game.Screens.Edit.Compose.Components @@ -54,8 +56,45 @@ namespace osu.Game.Screens.Edit.Compose.Components base.LoadComplete(); inputManager = GetContainingInputManager(); + + Beatmap.SelectedHitObjects.CollectionChanged += (_, __) => updateTogglesFromSelection(); + + // the updated object may be in the selection + Beatmap.HitObjectUpdated += _ => updateTogglesFromSelection(); + + NewCombo.ValueChanged += combo => + { + if (Beatmap.SelectedHitObjects.Count > 0) + { + foreach (var h in Beatmap.SelectedHitObjects) + { + if (h is IHasComboInformation c) + { + c.NewCombo = combo.NewValue; + Beatmap.UpdateHitObject(h); + } + } + } + else if (currentPlacement != null) + { + // update placement object from toggle + if (currentPlacement.HitObject is IHasComboInformation c) + c.NewCombo = combo.NewValue; + } + }; } + private void updateTogglesFromSelection() => + NewCombo.Value = Beatmap.SelectedHitObjects.OfType().All(c => c.NewCombo); + + public readonly Bindable NewCombo = new Bindable { Description = "New Combo" }; + + public virtual IEnumerable> Toggles => new[] + { + //TODO: this should only be enabled (visible?) for rulesets that provide combo-supporting HitObjects. + NewCombo + }; + #region Placement /// @@ -86,7 +125,9 @@ namespace osu.Game.Screens.Edit.Compose.Components removePlacement(); if (currentPlacement != null) + { updatePlacementPosition(); + } } protected sealed override SelectionBlueprint CreateBlueprintFor(HitObject hitObject) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index 6e2c8bd01c..ca22b443fb 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -35,6 +35,8 @@ namespace osu.Game.Screens.Edit.Compose.Components public IEnumerable SelectedBlueprints => selectedBlueprints; private readonly List selectedBlueprints; + public int SelectedCount => selectedBlueprints.Count; + public IEnumerable SelectedHitObjects => selectedBlueprints.Select(b => b.HitObject); private Drawable content; From a6adf8334eea25818c0c38ba7dc59ff7358ee910 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 25 Sep 2020 14:19:35 +0900 Subject: [PATCH 1232/1782] Use existing method to update combo state of selection --- .../Compose/Components/BlueprintContainer.cs | 44 +++++++++---------- .../Components/ComposeBlueprintContainer.cs | 9 +--- .../Compose/Components/SelectionHandler.cs | 2 +- 3 files changed, 24 insertions(+), 31 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index aa567dbdf4..fc37aa577c 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -24,7 +24,7 @@ namespace osu.Game.Screens.Edit.Compose.Components { /// /// A container which provides a "blueprint" display of hitobjects. - /// Includes selection and manipulation support via a . + /// Includes selection and manipulation support via a . /// public abstract class BlueprintContainer : CompositeDrawable, IKeyBindingHandler { @@ -32,7 +32,7 @@ namespace osu.Game.Screens.Edit.Compose.Components protected Container SelectionBlueprints { get; private set; } - private SelectionHandler selectionHandler; + protected SelectionHandler SelectionHandler; [Resolved(CanBeNull = true)] private IEditorChangeHandler changeHandler { get; set; } @@ -56,15 +56,15 @@ namespace osu.Game.Screens.Edit.Compose.Components [BackgroundDependencyLoader] private void load() { - selectionHandler = CreateSelectionHandler(); - selectionHandler.DeselectAll = deselectAll; + SelectionHandler = CreateSelectionHandler(); + SelectionHandler.DeselectAll = deselectAll; AddRangeInternal(new[] { DragBox = CreateDragBox(selectBlueprintsFromDragRectangle), - selectionHandler, + SelectionHandler, SelectionBlueprints = CreateSelectionBlueprintContainer(), - selectionHandler.CreateProxy(), + SelectionHandler.CreateProxy(), DragBox.CreateProxy().With(p => p.Depth = float.MinValue) }); @@ -102,7 +102,7 @@ namespace osu.Game.Screens.Edit.Compose.Components new Container { RelativeSizeAxes = Axes.Both }; /// - /// Creates a which outlines s and handles movement of selections. + /// Creates a which outlines s and handles movement of selections. /// protected virtual SelectionHandler CreateSelectionHandler() => new SelectionHandler(); @@ -130,7 +130,7 @@ namespace osu.Game.Screens.Edit.Compose.Components return false; // store for double-click handling - clickedBlueprint = selectionHandler.SelectedBlueprints.FirstOrDefault(b => b.IsHovered); + clickedBlueprint = SelectionHandler.SelectedBlueprints.FirstOrDefault(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 @@ -147,7 +147,7 @@ namespace osu.Game.Screens.Edit.Compose.Components return false; // ensure the blueprint which was hovered for the first click is still the hovered blueprint. - if (clickedBlueprint == null || selectionHandler.SelectedBlueprints.FirstOrDefault(b => b.IsHovered) != clickedBlueprint) + if (clickedBlueprint == null || SelectionHandler.SelectedBlueprints.FirstOrDefault(b => b.IsHovered) != clickedBlueprint) return false; editorClock?.SeekTo(clickedBlueprint.HitObject.StartTime); @@ -208,7 +208,7 @@ namespace osu.Game.Screens.Edit.Compose.Components if (DragBox.State == Visibility.Visible) { DragBox.Hide(); - selectionHandler.UpdateVisibility(); + SelectionHandler.UpdateVisibility(); } } @@ -217,7 +217,7 @@ namespace osu.Game.Screens.Edit.Compose.Components switch (e.Key) { case Key.Escape: - if (!selectionHandler.SelectedBlueprints.Any()) + if (!SelectionHandler.SelectedBlueprints.Any()) return false; deselectAll(); @@ -298,14 +298,14 @@ namespace osu.Game.Screens.Edit.Compose.Components 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)) + if (!allowDeselection && SelectionHandler.SelectedBlueprints.Any(s => s.IsHovered)) return; foreach (SelectionBlueprint blueprint in SelectionBlueprints.AliveChildren) { if (blueprint.IsHovered) { - selectionHandler.HandleSelectionRequested(blueprint, e.CurrentState); + SelectionHandler.HandleSelectionRequested(blueprint, e.CurrentState); clickSelectionBegan = true; break; } @@ -358,23 +358,23 @@ namespace osu.Game.Screens.Edit.Compose.Components private void selectAll() { SelectionBlueprints.ToList().ForEach(m => m.Select()); - selectionHandler.UpdateVisibility(); + SelectionHandler.UpdateVisibility(); } /// /// Deselects all selected s. /// - private void deselectAll() => selectionHandler.SelectedBlueprints.ToList().ForEach(m => m.Deselect()); + private void deselectAll() => SelectionHandler.SelectedBlueprints.ToList().ForEach(m => m.Deselect()); private void onBlueprintSelected(SelectionBlueprint blueprint) { - selectionHandler.HandleSelected(blueprint); + SelectionHandler.HandleSelected(blueprint); SelectionBlueprints.ChangeChildDepth(blueprint, 1); } private void onBlueprintDeselected(SelectionBlueprint blueprint) { - selectionHandler.HandleDeselected(blueprint); + SelectionHandler.HandleDeselected(blueprint); SelectionBlueprints.ChangeChildDepth(blueprint, 0); } @@ -391,16 +391,16 @@ namespace osu.Game.Screens.Edit.Compose.Components /// private void prepareSelectionMovement() { - if (!selectionHandler.SelectedBlueprints.Any()) + if (!SelectionHandler.SelectedBlueprints.Any()) return; // 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)) + if (!clickSelectionBegan && !SelectionHandler.SelectedBlueprints.Any(b => b.IsHovered)) return; // 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.HitObject.StartTime).First(); + movementBlueprint = SelectionHandler.SelectedBlueprints.OrderBy(b => b.HitObject.StartTime).First(); movementBlueprintOriginalPosition = movementBlueprint.ScreenSpaceSelectionPoint; // todo: unsure if correct } @@ -425,14 +425,14 @@ namespace osu.Game.Screens.Edit.Compose.Components var result = snapProvider.SnapScreenSpacePositionToValidTime(movePosition); // Move the hitobjects. - if (!selectionHandler.HandleMovement(new MoveSelectionEvent(movementBlueprint, result.ScreenSpacePosition))) + if (!SelectionHandler.HandleMovement(new MoveSelectionEvent(movementBlueprint, result.ScreenSpacePosition))) return true; if (result.Time.HasValue) { // Apply the start time at the newly snapped-to position double offset = result.Time.Value - draggedObject.StartTime; - foreach (HitObject obj in selectionHandler.SelectedHitObjects) + foreach (HitObject obj in SelectionHandler.SelectedHitObjects) obj.StartTime += offset; } diff --git a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs index 2a7c52579f..6f66c1bd6f 100644 --- a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs @@ -66,14 +66,7 @@ namespace osu.Game.Screens.Edit.Compose.Components { if (Beatmap.SelectedHitObjects.Count > 0) { - foreach (var h in Beatmap.SelectedHitObjects) - { - if (h is IHasComboInformation c) - { - c.NewCombo = combo.NewValue; - Beatmap.UpdateHitObject(h); - } - } + SelectionHandler.SetNewCombo(combo.NewValue); } else if (currentPlacement != null) { diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index ca22b443fb..1c5c3179ca 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -285,7 +285,7 @@ namespace osu.Game.Screens.Edit.Compose.Components var comboInfo = h as IHasComboInformation; if (comboInfo == null) - throw new InvalidOperationException($"Tried to change combo state of a {h.GetType()}, which doesn't implement {nameof(IHasComboInformation)}"); + continue; comboInfo.NewCombo = state; EditorBeatmap?.UpdateHitObject(h); From 7f9a5f5f0df1bceb5c8f1d3d1aedf2be9d6acadf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 25 Sep 2020 14:25:24 +0900 Subject: [PATCH 1233/1782] Ensure setup screen text boxes commit on losing focus --- osu.Game/Graphics/UserInterfaceV2/LabelledTextBox.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Graphics/UserInterfaceV2/LabelledTextBox.cs b/osu.Game/Graphics/UserInterfaceV2/LabelledTextBox.cs index 290aba3468..902c23c3c6 100644 --- a/osu.Game/Graphics/UserInterfaceV2/LabelledTextBox.cs +++ b/osu.Game/Graphics/UserInterfaceV2/LabelledTextBox.cs @@ -46,6 +46,7 @@ namespace osu.Game.Graphics.UserInterfaceV2 protected override OsuTextBox CreateComponent() => new OsuTextBox { + CommitOnFocusLost = true, Anchor = Anchor.Centre, Origin = Anchor.Centre, RelativeSizeAxes = Axes.X, From 50290f3cb4730ca93a1ed388ce432241520bd8e9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 25 Sep 2020 15:09:47 +0900 Subject: [PATCH 1234/1782] Rework ternary states to fix context menus not updating after already displayed --- .../Compose/Components/SelectionHandler.cs | 170 ++++++++++-------- 1 file changed, 93 insertions(+), 77 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index 6e2c8bd01c..1c564c6605 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -4,7 +4,9 @@ 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; using osu.Framework.Graphics.Cursor; @@ -59,6 +61,8 @@ namespace osu.Game.Screens.Edit.Compose.Components [BackgroundDependencyLoader] private void load(OsuColour colours) { + createStateBindables(); + InternalChild = content = new Container { Children = new Drawable[] @@ -308,6 +312,90 @@ namespace osu.Game.Screens.Edit.Compose.Components #endregion + #region Selection State + + private readonly Bindable selectionNewComboState = new Bindable(); + + private readonly Dictionary> selectionSampleStates = new Dictionary>(); + + /// + /// Set up ternary state bindables and bind them to selection/hitobject changes (in both directions) + /// + private void createStateBindables() + { + // hit samples + var sampleTypes = new[] { HitSampleInfo.HIT_WHISTLE, HitSampleInfo.HIT_CLAP, HitSampleInfo.HIT_FINISH }; + + foreach (var sampleName in sampleTypes) + { + var bindable = new Bindable + { + Description = sampleName.Replace("hit", string.Empty).Titleize() + }; + + bindable.ValueChanged += state => + { + switch (state.NewValue) + { + case TernaryState.False: + RemoveHitSample(sampleName); + break; + + case TernaryState.True: + AddHitSample(sampleName); + break; + } + }; + + selectionSampleStates[sampleName] = bindable; + } + + // new combo + selectionNewComboState.ValueChanged += state => + { + switch (state.NewValue) + { + case TernaryState.False: + SetNewCombo(false); + break; + + case TernaryState.True: + SetNewCombo(true); + break; + } + }; + + // bring in updates from selection changes + EditorBeatmap.HitObjectUpdated += _ => updateTernaryStates(); + EditorBeatmap.SelectedHitObjects.CollectionChanged += (sender, args) => updateTernaryStates(); + } + + private void updateTernaryStates() + { + selectionNewComboState.Value = getStateFromBlueprints(selectedBlueprints.Select(b => (IHasComboInformation)b.HitObject).Count(h => h.NewCombo)); + + foreach (var (sampleName, bindable) in selectionSampleStates) + { + bindable.Value = getStateFromBlueprints(SelectedHitObjects.Count(h => h.Samples.Any(s => s.Name == sampleName))); + } + } + + /// + /// Given a count of "true" blueprints, retrieve the correct ternary display state. + /// + private TernaryState getStateFromBlueprints(int count) + { + if (count == 0) + return TernaryState.False; + + if (count < SelectedHitObjects.Count()) + return TernaryState.Indeterminate; + + return TernaryState.True; + } + + #endregion + #region Context Menu public MenuItem[] ContextMenuItems @@ -322,7 +410,9 @@ namespace osu.Game.Screens.Edit.Compose.Components items.AddRange(GetContextMenuItemsForSelection(selectedBlueprints)); if (selectedBlueprints.All(b => b.HitObject is IHasComboInformation)) - items.Add(createNewComboMenuItem()); + { + items.Add(new TernaryStateMenuItem("New combo") { State = { BindTarget = selectionNewComboState } }); + } if (selectedBlueprints.Count == 1) items.AddRange(selectedBlueprints[0].ContextMenuItems); @@ -331,12 +421,8 @@ namespace osu.Game.Screens.Edit.Compose.Components { new OsuMenuItem("Sound") { - Items = new[] - { - createHitSampleMenuItem("Whistle", HitSampleInfo.HIT_WHISTLE), - createHitSampleMenuItem("Clap", HitSampleInfo.HIT_CLAP), - createHitSampleMenuItem("Finish", HitSampleInfo.HIT_FINISH) - } + Items = selectionSampleStates.Select(kvp => + new TernaryStateMenuItem(kvp.Value.Description) { State = { BindTarget = kvp.Value } }).ToArray() }, new OsuMenuItem("Delete", MenuItemType.Destructive, deleteSelected), }); @@ -353,76 +439,6 @@ namespace osu.Game.Screens.Edit.Compose.Components protected virtual IEnumerable GetContextMenuItemsForSelection(IEnumerable selection) => Enumerable.Empty(); - private MenuItem createNewComboMenuItem() - { - return new TernaryStateMenuItem("New combo", MenuItemType.Standard, setNewComboState) - { - State = { Value = getHitSampleState() } - }; - - void setNewComboState(TernaryState state) - { - switch (state) - { - case TernaryState.False: - SetNewCombo(false); - break; - - case TernaryState.True: - SetNewCombo(true); - break; - } - } - - TernaryState getHitSampleState() - { - int countExisting = selectedBlueprints.Select(b => (IHasComboInformation)b.HitObject).Count(h => h.NewCombo); - - if (countExisting == 0) - return TernaryState.False; - - if (countExisting < SelectedHitObjects.Count()) - return TernaryState.Indeterminate; - - return TernaryState.True; - } - } - - private MenuItem createHitSampleMenuItem(string name, string sampleName) - { - return new TernaryStateMenuItem(name, MenuItemType.Standard, setHitSampleState) - { - State = { Value = getHitSampleState() } - }; - - void setHitSampleState(TernaryState state) - { - switch (state) - { - case TernaryState.False: - RemoveHitSample(sampleName); - break; - - case TernaryState.True: - AddHitSample(sampleName); - break; - } - } - - TernaryState getHitSampleState() - { - int countExisting = SelectedHitObjects.Count(h => h.Samples.Any(s => s.Name == sampleName)); - - if (countExisting == 0) - return TernaryState.False; - - if (countExisting < SelectedHitObjects.Count()) - return TernaryState.Indeterminate; - - return TernaryState.True; - } - } - #endregion } } From a859fe78ee52841fd458f4949e2b1e72d3c59a1c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 25 Sep 2020 15:27:45 +0900 Subject: [PATCH 1235/1782] Expose update ternary state method and use better state determination function --- .../Compose/Components/SelectionHandler.cs | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index 1c564c6605..8a152a9c57 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -366,32 +366,32 @@ namespace osu.Game.Screens.Edit.Compose.Components }; // bring in updates from selection changes - EditorBeatmap.HitObjectUpdated += _ => updateTernaryStates(); - EditorBeatmap.SelectedHitObjects.CollectionChanged += (sender, args) => updateTernaryStates(); + EditorBeatmap.HitObjectUpdated += _ => UpdateTernaryStates(); + EditorBeatmap.SelectedHitObjects.CollectionChanged += (sender, args) => UpdateTernaryStates(); } - private void updateTernaryStates() + /// + /// Called when context menu ternary states may need to be recalculated (selection changed or hitobject updated). + /// + protected virtual void UpdateTernaryStates() { - selectionNewComboState.Value = getStateFromBlueprints(selectedBlueprints.Select(b => (IHasComboInformation)b.HitObject).Count(h => h.NewCombo)); + selectionNewComboState.Value = GetStateFromSelection(SelectedHitObjects.OfType(), h => h.NewCombo); foreach (var (sampleName, bindable) in selectionSampleStates) { - bindable.Value = getStateFromBlueprints(SelectedHitObjects.Count(h => h.Samples.Any(s => s.Name == sampleName))); + bindable.Value = GetStateFromSelection(SelectedHitObjects, h => h.Samples.Any(s => s.Name == sampleName)); } } /// - /// Given a count of "true" blueprints, retrieve the correct ternary display state. + /// Given a selection target and a function of truth, retrieve the correct ternary state for display. /// - private TernaryState getStateFromBlueprints(int count) + protected TernaryState GetStateFromSelection(IEnumerable selection, Func func) { - if (count == 0) - return TernaryState.False; + if (selection.Any(func)) + return selection.All(func) ? TernaryState.True : TernaryState.Indeterminate; - if (count < SelectedHitObjects.Count()) - return TernaryState.Indeterminate; - - return TernaryState.True; + return TernaryState.False; } #endregion From 727ab98d22d2b748f1148f8bcc1f7cb27b41f538 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 25 Sep 2020 15:32:45 +0900 Subject: [PATCH 1236/1782] Update taiko selection handler with new logic --- .../Edit/TaikoSelectionHandler.cs | 128 +++++++++--------- 1 file changed, 67 insertions(+), 61 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs b/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs index 40565048c2..a3ecf7ed95 100644 --- a/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs +++ b/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs @@ -1,9 +1,10 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using System.Collections.Generic; using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics.UserInterface; using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Edit; @@ -14,75 +15,80 @@ namespace osu.Game.Rulesets.Taiko.Edit { public class TaikoSelectionHandler : SelectionHandler { + private readonly Bindable selectionRimState = new Bindable(); + private readonly Bindable selectionStrongState = new Bindable(); + + [BackgroundDependencyLoader] + private void load() + { + selectionStrongState.ValueChanged += state => + { + switch (state.NewValue) + { + case TernaryState.False: + SetStrongState(false); + break; + + case TernaryState.True: + SetStrongState(true); + break; + } + }; + + selectionRimState.ValueChanged += state => + { + switch (state.NewValue) + { + case TernaryState.False: + SetRimState(false); + break; + + case TernaryState.True: + SetRimState(true); + break; + } + }; + } + + public void SetStrongState(bool state) + { + var hits = SelectedHitObjects.OfType(); + + ChangeHandler.BeginChange(); + + foreach (var h in hits) + h.IsStrong = state; + + ChangeHandler.EndChange(); + } + + public void SetRimState(bool state) + { + var hits = SelectedHitObjects.OfType(); + + ChangeHandler.BeginChange(); + + foreach (var h in hits) + h.Type = state ? HitType.Rim : HitType.Centre; + + ChangeHandler.EndChange(); + } + protected override IEnumerable GetContextMenuItemsForSelection(IEnumerable selection) { if (selection.All(s => s.HitObject is Hit)) - { - var hits = selection.Select(s => s.HitObject).OfType(); - - yield return new TernaryStateMenuItem("Rim", action: state => - { - ChangeHandler.BeginChange(); - - foreach (var h in hits) - { - switch (state) - { - case TernaryState.True: - h.Type = HitType.Rim; - break; - - case TernaryState.False: - h.Type = HitType.Centre; - break; - } - } - - ChangeHandler.EndChange(); - }) - { - State = { Value = getTernaryState(hits, h => h.Type == HitType.Rim) } - }; - } + yield return new TernaryStateMenuItem("Rim") { State = { BindTarget = selectionRimState } }; if (selection.All(s => s.HitObject is TaikoHitObject)) - { - var hits = selection.Select(s => s.HitObject).OfType(); - - yield return new TernaryStateMenuItem("Strong", action: state => - { - ChangeHandler.BeginChange(); - - foreach (var h in hits) - { - switch (state) - { - case TernaryState.True: - h.IsStrong = true; - break; - - case TernaryState.False: - h.IsStrong = false; - break; - } - - EditorBeatmap?.UpdateHitObject(h); - } - - ChangeHandler.EndChange(); - }) - { - State = { Value = getTernaryState(hits, h => h.IsStrong) } - }; - } + yield return new TernaryStateMenuItem("Strong") { State = { BindTarget = selectionStrongState } }; } - private TernaryState getTernaryState(IEnumerable selection, Func func) + protected override void UpdateTernaryStates() { - if (selection.Any(func)) - return selection.All(func) ? TernaryState.True : TernaryState.Indeterminate; + base.UpdateTernaryStates(); - return TernaryState.False; + selectionRimState.Value = GetStateFromSelection(SelectedHitObjects.OfType(), h => h.Type == HitType.Rim); + selectionStrongState.Value = GetStateFromSelection(SelectedHitObjects.OfType(), h => h.IsStrong); } } } From ae68dcd962090efe418228f4c11c09d166a12af1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 25 Sep 2020 16:33:22 +0900 Subject: [PATCH 1237/1782] Add ternary toggle buttons to editor toolbox selection --- .../Edit/OsuHitObjectComposer.cs | 11 +- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 14 +-- .../TernaryButtons/DrawableTernaryButton.cs | 112 ++++++++++++++++++ .../TernaryButtons/TernaryButton.cs | 44 +++++++ .../Components/ComposeBlueprintContainer.cs | 29 ++--- .../Compose/Components/SelectionHandler.cs | 22 ++-- 6 files changed, 194 insertions(+), 38 deletions(-) create mode 100644 osu.Game/Screens/Edit/Components/TernaryButtons/DrawableTernaryButton.cs create mode 100644 osu.Game/Screens/Edit/Components/TernaryButtons/TernaryButton.cs diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index 0d0a139a8a..49af80dd63 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -9,7 +9,9 @@ using osu.Framework.Bindables; using osu.Framework.Caching; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; using osu.Game.Beatmaps; +using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Tools; using osu.Game.Rulesets.Mods; @@ -17,6 +19,7 @@ using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.UI; +using osu.Game.Screens.Edit.Components.TernaryButtons; using osu.Game.Screens.Edit.Compose.Components; using osuTK; @@ -39,11 +42,11 @@ namespace osu.Game.Rulesets.Osu.Edit new SpinnerCompositionTool() }; - private readonly BindableBool distanceSnapToggle = new BindableBool(true) { Description = "Distance Snap" }; + private readonly Bindable distanceSnapToggle = new Bindable(); - protected override IEnumerable> Toggles => base.Toggles.Concat(new[] + protected override IEnumerable Toggles => base.Toggles.Concat(new[] { - distanceSnapToggle + new TernaryButton(distanceSnapToggle, "Distance Snap", () => new SpriteIcon { Icon = FontAwesome.Solid.Ruler }) }); private BindableList selectedHitObjects; @@ -156,7 +159,7 @@ namespace osu.Game.Rulesets.Osu.Edit distanceSnapGridCache.Invalidate(); distanceSnapGrid = null; - if (!distanceSnapToggle.Value) + if (distanceSnapToggle.Value != TernaryState.True) return; switch (BlueprintContainer.CurrentTool) diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index b2d7c40a22..afef542e36 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -14,7 +14,6 @@ using osu.Framework.Input.Events; using osu.Framework.Logging; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Overlays.Settings; using osu.Game.Rulesets.Configuration; using osu.Game.Rulesets.Edit.Tools; using osu.Game.Rulesets.Mods; @@ -24,6 +23,7 @@ using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Components.RadioButtons; +using osu.Game.Screens.Edit.Components.TernaryButtons; using osu.Game.Screens.Edit.Compose; using osu.Game.Screens.Edit.Compose.Components; using osuTK; @@ -124,11 +124,7 @@ namespace osu.Game.Rulesets.Edit new ToolboxGroup("toolbox") { Child = toolboxCollection = new RadioButtonCollection { RelativeSizeAxes = Axes.X } }, togglesCollection = new ToolboxGroup("toggles") { - ChildrenEnumerable = Toggles.Select(b => new SettingsCheckbox - { - Bindable = b, - LabelText = b?.Description ?? "unknown" - }) + ChildrenEnumerable = Toggles.Select(b => new DrawableTernaryButton(b)) } } }, @@ -170,7 +166,7 @@ namespace osu.Game.Rulesets.Edit /// A collection of toggles which will be displayed to the user. /// The display name will be decided by . /// - protected virtual IEnumerable> Toggles => BlueprintContainer.Toggles; + protected virtual IEnumerable Toggles => BlueprintContainer.Toggles; /// /// Construct a relevant blueprint container. This will manage hitobject selection/placement input handling and display logic. @@ -212,9 +208,9 @@ namespace osu.Game.Rulesets.Edit { var item = togglesCollection.Children[rightIndex]; - if (item is SettingsCheckbox checkbox) + if (item is DrawableTernaryButton button) { - checkbox.Bindable.Value = !checkbox.Bindable.Value; + button.Button.Toggle(); return true; } } diff --git a/osu.Game/Screens/Edit/Components/TernaryButtons/DrawableTernaryButton.cs b/osu.Game/Screens/Edit/Components/TernaryButtons/DrawableTernaryButton.cs new file mode 100644 index 0000000000..c72fff5c91 --- /dev/null +++ b/osu.Game/Screens/Edit/Components/TernaryButtons/DrawableTernaryButton.cs @@ -0,0 +1,112 @@ +// 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.Effects; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Screens.Edit.Components.TernaryButtons +{ + internal class DrawableTernaryButton : TriangleButton + { + private Color4 defaultBackgroundColour; + private Color4 defaultBubbleColour; + private Color4 selectedBackgroundColour; + private Color4 selectedBubbleColour; + + private Drawable icon; + + public readonly TernaryButton Button; + + public DrawableTernaryButton(TernaryButton button) + { + Button = button; + + Text = button.Description; + + RelativeSizeAxes = Axes.X; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + defaultBackgroundColour = colours.Gray3; + defaultBubbleColour = defaultBackgroundColour.Darken(0.5f); + selectedBackgroundColour = colours.BlueDark; + selectedBubbleColour = selectedBackgroundColour.Lighten(0.5f); + + Triangles.Alpha = 0; + + Content.EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Shadow, + Radius = 2, + Offset = new Vector2(0, 1), + Colour = Color4.Black.Opacity(0.5f) + }; + + Add(icon = (Button.CreateIcon?.Invoke() ?? new Circle()).With(b => + { + b.Blending = BlendingParameters.Additive; + b.Anchor = Anchor.CentreLeft; + b.Origin = Anchor.CentreLeft; + b.Size = new Vector2(20); + b.X = 10; + })); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + Button.Bindable.BindValueChanged(selected => updateSelectionState(), true); + + Action = onAction; + } + + private void onAction() + { + Button.Toggle(); + } + + private void updateSelectionState() + { + if (!IsLoaded) + return; + + switch (Button.Bindable.Value) + { + case TernaryState.Indeterminate: + icon.Colour = selectedBubbleColour.Darken(0.5f); + BackgroundColour = selectedBackgroundColour.Darken(0.5f); + break; + + case TernaryState.False: + icon.Colour = defaultBubbleColour; + BackgroundColour = defaultBackgroundColour; + break; + + case TernaryState.True: + icon.Colour = selectedBubbleColour; + BackgroundColour = selectedBackgroundColour; + break; + } + } + + protected override SpriteText CreateText() => new OsuSpriteText + { + Depth = -1, + Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft, + X = 40f + }; + } +} diff --git a/osu.Game/Screens/Edit/Components/TernaryButtons/TernaryButton.cs b/osu.Game/Screens/Edit/Components/TernaryButtons/TernaryButton.cs new file mode 100644 index 0000000000..7f64695bde --- /dev/null +++ b/osu.Game/Screens/Edit/Components/TernaryButtons/TernaryButton.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 System; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Game.Graphics.UserInterface; + +namespace osu.Game.Screens.Edit.Components.TernaryButtons +{ + public class TernaryButton + { + public readonly Bindable Bindable; + + public readonly string Description; + + /// + /// A function which creates a drawable icon to represent this item. If null, a sane default should be used. + /// + public readonly Func CreateIcon; + + public TernaryButton(Bindable bindable, string description, Func createIcon = null) + { + Bindable = bindable; + Description = description; + CreateIcon = createIcon; + } + + public void Toggle() + { + switch (Bindable.Value) + { + case TernaryState.False: + case TernaryState.Indeterminate: + Bindable.Value = TernaryState.True; + break; + + case TernaryState.True: + Bindable.Value = TernaryState.False; + break; + } + } + } +} diff --git a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs index 6f66c1bd6f..91eab18acb 100644 --- a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs @@ -7,12 +7,16 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; using osu.Framework.Input; +using osu.Game.Graphics; +using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Tools; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Types; +using osu.Game.Screens.Edit.Components.TernaryButtons; using osuTK; namespace osu.Game.Screens.Edit.Compose.Components @@ -57,35 +61,26 @@ namespace osu.Game.Screens.Edit.Compose.Components inputManager = GetContainingInputManager(); - Beatmap.SelectedHitObjects.CollectionChanged += (_, __) => updateTogglesFromSelection(); - - // the updated object may be in the selection - Beatmap.HitObjectUpdated += _ => updateTogglesFromSelection(); + // updates to selected are handled for us by SelectionHandler. + NewCombo.BindTo(SelectionHandler.SelectionNewComboState); + // we are responsible for current placement blueprint updated based on state changes. NewCombo.ValueChanged += combo => { - if (Beatmap.SelectedHitObjects.Count > 0) + if (currentPlacement != null) { - SelectionHandler.SetNewCombo(combo.NewValue); - } - else if (currentPlacement != null) - { - // update placement object from toggle if (currentPlacement.HitObject is IHasComboInformation c) - c.NewCombo = combo.NewValue; + c.NewCombo = combo.NewValue == TernaryState.True; } }; } - private void updateTogglesFromSelection() => - NewCombo.Value = Beatmap.SelectedHitObjects.OfType().All(c => c.NewCombo); + public readonly Bindable NewCombo = new Bindable { Description = "New Combo" }; - public readonly Bindable NewCombo = new Bindable { Description = "New Combo" }; - - public virtual IEnumerable> Toggles => new[] + public virtual IEnumerable Toggles => new[] { //TODO: this should only be enabled (visible?) for rulesets that provide combo-supporting HitObjects. - NewCombo + new TernaryButton(NewCombo, "New combo", () => new SpriteIcon { Icon = FontAwesome.Regular.DotCircle }) }; #region Placement diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index a316f34ad0..ed956357b6 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -316,9 +316,15 @@ namespace osu.Game.Screens.Edit.Compose.Components #region Selection State - private readonly Bindable selectionNewComboState = new Bindable(); + /// + /// The state of "new combo" for all selected hitobjects. + /// + public readonly Bindable SelectionNewComboState = new Bindable(); - private readonly Dictionary> selectionSampleStates = new Dictionary>(); + /// + /// The state of each sample type for all selected hitobjects. Keys match with constant specifications. + /// + public readonly Dictionary> SelectionSampleStates = new Dictionary>(); /// /// Set up ternary state bindables and bind them to selection/hitobject changes (in both directions) @@ -349,11 +355,11 @@ namespace osu.Game.Screens.Edit.Compose.Components } }; - selectionSampleStates[sampleName] = bindable; + SelectionSampleStates[sampleName] = bindable; } // new combo - selectionNewComboState.ValueChanged += state => + SelectionNewComboState.ValueChanged += state => { switch (state.NewValue) { @@ -377,9 +383,9 @@ namespace osu.Game.Screens.Edit.Compose.Components /// protected virtual void UpdateTernaryStates() { - selectionNewComboState.Value = GetStateFromSelection(SelectedHitObjects.OfType(), h => h.NewCombo); + SelectionNewComboState.Value = GetStateFromSelection(SelectedHitObjects.OfType(), h => h.NewCombo); - foreach (var (sampleName, bindable) in selectionSampleStates) + foreach (var (sampleName, bindable) in SelectionSampleStates) { bindable.Value = GetStateFromSelection(SelectedHitObjects, h => h.Samples.Any(s => s.Name == sampleName)); } @@ -413,7 +419,7 @@ namespace osu.Game.Screens.Edit.Compose.Components if (selectedBlueprints.All(b => b.HitObject is IHasComboInformation)) { - items.Add(new TernaryStateMenuItem("New combo") { State = { BindTarget = selectionNewComboState } }); + items.Add(new TernaryStateMenuItem("New combo") { State = { BindTarget = SelectionNewComboState } }); } if (selectedBlueprints.Count == 1) @@ -423,7 +429,7 @@ namespace osu.Game.Screens.Edit.Compose.Components { new OsuMenuItem("Sound") { - Items = selectionSampleStates.Select(kvp => + Items = SelectionSampleStates.Select(kvp => new TernaryStateMenuItem(kvp.Value.Description) { State = { BindTarget = kvp.Value } }).ToArray() }, new OsuMenuItem("Delete", MenuItemType.Destructive, deleteSelected), From a6298c60eb30d5c7f0bf5dc8422148f531c315ad Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 25 Sep 2020 16:44:37 +0900 Subject: [PATCH 1238/1782] Fix button spacing --- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index afef542e36..f823d37060 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -63,7 +63,7 @@ namespace osu.Game.Rulesets.Edit private RadioButtonCollection toolboxCollection; - private ToolboxGroup togglesCollection; + private FillFlowContainer togglesCollection; protected HitObjectComposer(Ruleset ruleset) { @@ -121,10 +121,20 @@ namespace osu.Game.Rulesets.Edit Spacing = new Vector2(10), Children = new Drawable[] { - new ToolboxGroup("toolbox") { Child = toolboxCollection = new RadioButtonCollection { RelativeSizeAxes = Axes.X } }, - togglesCollection = new ToolboxGroup("toggles") + new ToolboxGroup("toolbox") { - ChildrenEnumerable = Toggles.Select(b => new DrawableTernaryButton(b)) + Child = toolboxCollection = new RadioButtonCollection { RelativeSizeAxes = Axes.X } + }, + new ToolboxGroup("toggles") + { + Child = togglesCollection = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 5), + ChildrenEnumerable = Toggles.Select(b => new DrawableTernaryButton(b)) + }, } } }, From da820c815e5fc97a350669e9904d2700c25fbe44 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 25 Sep 2020 16:46:06 +0900 Subject: [PATCH 1239/1782] Add shortcut keys to toolbox gorup titles --- 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 f823d37060..a592500a87 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -121,11 +121,11 @@ namespace osu.Game.Rulesets.Edit Spacing = new Vector2(10), Children = new Drawable[] { - new ToolboxGroup("toolbox") + new ToolboxGroup("toolbox (1-9)") { Child = toolboxCollection = new RadioButtonCollection { RelativeSizeAxes = Axes.X } }, - new ToolboxGroup("toggles") + new ToolboxGroup("toggles (Q~P)") { Child = togglesCollection = new FillFlowContainer { From b70a20e7f198b236b4b404cb3eea54dca3ae105e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 25 Sep 2020 16:56:39 +0900 Subject: [PATCH 1240/1782] Avoid consuming keystrokes in editor when a modifier key is held down --- 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 b81e0ce159..7c9d996039 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -192,6 +192,9 @@ namespace osu.Game.Rulesets.Edit protected override bool OnKeyDown(KeyDownEvent e) { + if (e.ControlPressed || e.AltPressed || e.SuperPressed) + return false; + if (checkLeftToggleFromKey(e.Key, out var leftIndex)) { var item = toolboxCollection.Items.ElementAtOrDefault(leftIndex); From 98c6027352deb80fd0d80e9bc5abcb12ead00552 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 25 Sep 2020 17:07:58 +0900 Subject: [PATCH 1241/1782] Remove unused using --- .../Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs index 91eab18acb..cab277f10b 100644 --- a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs @@ -9,7 +9,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Input; -using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Tools; From b8e9f19b92195f8c6e270540c9d77f8a17b5e2c8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 25 Sep 2020 17:30:31 +0900 Subject: [PATCH 1242/1782] Move common HitSampleInfo lookup to static method --- osu.Game/Audio/HitSampleInfo.cs | 5 +++++ osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs | 5 +---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/osu.Game/Audio/HitSampleInfo.cs b/osu.Game/Audio/HitSampleInfo.cs index f6b0107bd2..8b1f5a366a 100644 --- a/osu.Game/Audio/HitSampleInfo.cs +++ b/osu.Game/Audio/HitSampleInfo.cs @@ -17,6 +17,11 @@ namespace osu.Game.Audio public const string HIT_NORMAL = @"hitnormal"; public const string HIT_CLAP = @"hitclap"; + /// + /// All valid sample addition constants. + /// + public static IEnumerable AllAdditions => new[] { HIT_WHISTLE, HIT_CLAP, HIT_FINISH }; + /// /// The bank to load the sample from. /// diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index ed956357b6..6ca85fe026 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -331,10 +331,7 @@ namespace osu.Game.Screens.Edit.Compose.Components /// private void createStateBindables() { - // hit samples - var sampleTypes = new[] { HitSampleInfo.HIT_WHISTLE, HitSampleInfo.HIT_CLAP, HitSampleInfo.HIT_FINISH }; - - foreach (var sampleName in sampleTypes) + foreach (var sampleName in HitSampleInfo.AllAdditions) { var bindable = new Bindable { From 51cc644b7b203110f88b7bee33cd6321ab1fb66b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 25 Sep 2020 17:42:49 +0900 Subject: [PATCH 1243/1782] Fix set access to SelectionHandler Co-authored-by: Dan Balasescu --- 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 8934a1b6d3..8908520cd7 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -32,7 +32,7 @@ namespace osu.Game.Screens.Edit.Compose.Components public Container SelectionBlueprints { get; private set; } - protected SelectionHandler SelectionHandler; + protected SelectionHandler SelectionHandler { get; private set; } [Resolved(CanBeNull = true)] private IEditorChangeHandler changeHandler { get; set; } From 22511c36c3b0d306418b55adbb61376796f85187 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 25 Sep 2020 17:40:43 +0900 Subject: [PATCH 1244/1782] Ensure toggles are not instantiated more than once for safety --- .../Edit/OsuHitObjectComposer.cs | 2 +- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 8 ++++++-- .../Components/ComposeBlueprintContainer.cs | 19 +++++++++++-------- 3 files changed, 18 insertions(+), 11 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index 49af80dd63..a4dd463ea5 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -44,7 +44,7 @@ namespace osu.Game.Rulesets.Osu.Edit private readonly Bindable distanceSnapToggle = new Bindable(); - protected override IEnumerable Toggles => base.Toggles.Concat(new[] + protected override IEnumerable CreateToggles() => base.CreateToggles().Concat(new[] { new TernaryButton(distanceSnapToggle, "Distance Snap", () => new SpriteIcon { Icon = FontAwesome.Solid.Ruler }) }); diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index a592500a87..e692f8d606 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -133,7 +133,6 @@ namespace osu.Game.Rulesets.Edit AutoSizeAxes = Axes.Y, Direction = FillDirection.Vertical, Spacing = new Vector2(0, 5), - ChildrenEnumerable = Toggles.Select(b => new DrawableTernaryButton(b)) }, } } @@ -145,6 +144,9 @@ namespace osu.Game.Rulesets.Edit .Select(t => new RadioButton(t.Name, () => toolSelected(t), t.CreateIcon)) .ToList(); + Toggles = CreateToggles().ToArray(); + togglesCollection.AddRange(Toggles.Select(b => new DrawableTernaryButton(b))); + setSelectTool(); EditorBeatmap.SelectedHitObjects.CollectionChanged += selectionChanged; @@ -176,7 +178,9 @@ namespace osu.Game.Rulesets.Edit /// A collection of toggles which will be displayed to the user. /// The display name will be decided by . /// - protected virtual IEnumerable Toggles => BlueprintContainer.Toggles; + public TernaryButton[] Toggles { get; private set; } + + protected virtual IEnumerable CreateToggles() => BlueprintContainer.Toggles; /// /// Construct a relevant blueprint container. This will manage hitobject selection/placement input handling and display logic. diff --git a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs index cab277f10b..0f4bab8b33 100644 --- a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs @@ -51,6 +51,8 @@ namespace osu.Game.Screens.Edit.Compose.Components [BackgroundDependencyLoader] private void load() { + Toggles = CreateToggles().ToArray(); + AddInternal(placementBlueprintContainer); } @@ -66,21 +68,22 @@ namespace osu.Game.Screens.Edit.Compose.Components // we are responsible for current placement blueprint updated based on state changes. NewCombo.ValueChanged += combo => { - if (currentPlacement != null) - { - if (currentPlacement.HitObject is IHasComboInformation c) - c.NewCombo = combo.NewValue == TernaryState.True; - } + if (currentPlacement == null) return; + + if (currentPlacement.HitObject is IHasComboInformation c) + c.NewCombo = combo.NewValue == TernaryState.True; }; } public readonly Bindable NewCombo = new Bindable { Description = "New Combo" }; - public virtual IEnumerable Toggles => new[] + public TernaryButton[] Toggles { get; private set; } + + protected virtual IEnumerable CreateToggles() { //TODO: this should only be enabled (visible?) for rulesets that provide combo-supporting HitObjects. - new TernaryButton(NewCombo, "New combo", () => new SpriteIcon { Icon = FontAwesome.Regular.DotCircle }) - }; + yield return new TernaryButton(NewCombo, "New combo", () => new SpriteIcon { Icon = FontAwesome.Regular.DotCircle }); + } #region Placement From 346d14d40bfc09d0da125c0850dafe587eccb376 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 25 Sep 2020 17:45:19 +0900 Subject: [PATCH 1245/1782] Rename variables to match --- .../Edit/OsuHitObjectComposer.cs | 2 +- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 15 ++++++++------- .../Components/ComposeBlueprintContainer.cs | 12 +++++++++--- 3 files changed, 18 insertions(+), 11 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index a4dd463ea5..912a705d16 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -44,7 +44,7 @@ namespace osu.Game.Rulesets.Osu.Edit private readonly Bindable distanceSnapToggle = new Bindable(); - protected override IEnumerable CreateToggles() => base.CreateToggles().Concat(new[] + protected override IEnumerable CreateTernaryButtons() => base.CreateTernaryButtons().Concat(new[] { new TernaryButton(distanceSnapToggle, "Distance Snap", () => new SpriteIcon { Icon = FontAwesome.Solid.Ruler }) }); diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index e692f8d606..3ad2394420 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -6,7 +6,6 @@ using System.Collections.Generic; using System.Collections.Specialized; using System.Linq; using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input; @@ -144,8 +143,8 @@ namespace osu.Game.Rulesets.Edit .Select(t => new RadioButton(t.Name, () => toolSelected(t), t.CreateIcon)) .ToList(); - Toggles = CreateToggles().ToArray(); - togglesCollection.AddRange(Toggles.Select(b => new DrawableTernaryButton(b))); + TernaryStates = CreateTernaryButtons().ToArray(); + togglesCollection.AddRange(TernaryStates.Select(b => new DrawableTernaryButton(b))); setSelectTool(); @@ -175,12 +174,14 @@ namespace osu.Game.Rulesets.Edit protected abstract IReadOnlyList CompositionTools { get; } /// - /// A collection of toggles which will be displayed to the user. - /// The display name will be decided by . + /// A collection of states which will be displayed to the user in the toolbox. /// - public TernaryButton[] Toggles { get; private set; } + public TernaryButton[] TernaryStates { get; private set; } - protected virtual IEnumerable CreateToggles() => BlueprintContainer.Toggles; + /// + /// Create all ternary states required to be displayed to the user. + /// + protected virtual IEnumerable CreateTernaryButtons() => BlueprintContainer.TernaryStates; /// /// Construct a relevant blueprint container. This will manage hitobject selection/placement input handling and display logic. diff --git a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs index 0f4bab8b33..88c3170c34 100644 --- a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs @@ -51,7 +51,7 @@ namespace osu.Game.Screens.Edit.Compose.Components [BackgroundDependencyLoader] private void load() { - Toggles = CreateToggles().ToArray(); + TernaryStates = CreateTernaryButtons().ToArray(); AddInternal(placementBlueprintContainer); } @@ -77,9 +77,15 @@ namespace osu.Game.Screens.Edit.Compose.Components public readonly Bindable NewCombo = new Bindable { Description = "New Combo" }; - public TernaryButton[] Toggles { get; private set; } + /// + /// A collection of states which will be displayed to the user in the toolbox. + /// + public TernaryButton[] TernaryStates { get; private set; } - protected virtual IEnumerable CreateToggles() + /// + /// Create all ternary states required to be displayed to the user. + /// + protected virtual IEnumerable CreateTernaryButtons() { //TODO: this should only be enabled (visible?) for rulesets that provide combo-supporting HitObjects. yield return new TernaryButton(NewCombo, "New combo", () => new SpriteIcon { Icon = FontAwesome.Regular.DotCircle }); From b561429f920d52c53e5ae33f1efbd1e1e9f0f1be Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 25 Sep 2020 17:53:41 +0900 Subject: [PATCH 1246/1782] Add toolbar toggle buttons for hit samples --- .../Components/ComposeBlueprintContainer.cs | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs index 88c3170c34..a83977c15f 100644 --- a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs @@ -3,12 +3,14 @@ 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.Sprites; using osu.Framework.Input; +using osu.Game.Audio; using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Tools; @@ -73,6 +75,34 @@ namespace osu.Game.Screens.Edit.Compose.Components if (currentPlacement.HitObject is IHasComboInformation c) c.NewCombo = combo.NewValue == TernaryState.True; }; + + // we own SelectionHandler so don't need to worry about making bindable copies (for simplicity) + foreach (var kvp in SelectionHandler.SelectionSampleStates) + { + kvp.Value.BindValueChanged(c => sampleChanged(kvp.Key, c.NewValue)); + } + } + + private void sampleChanged(string sampleName, TernaryState state) + { + if (currentPlacement == null) return; + + var samples = currentPlacement.HitObject.Samples; + + var existingSample = samples.FirstOrDefault(s => s.Name == sampleName); + + switch (state) + { + case TernaryState.False: + if (existingSample != null) + samples.Remove(existingSample); + break; + + case TernaryState.True: + if (existingSample == null) + samples.Add(new HitSampleInfo { Name = sampleName }); + break; + } } public readonly Bindable NewCombo = new Bindable { Description = "New Combo" }; @@ -89,6 +119,26 @@ namespace osu.Game.Screens.Edit.Compose.Components { //TODO: this should only be enabled (visible?) for rulesets that provide combo-supporting HitObjects. yield return new TernaryButton(NewCombo, "New combo", () => new SpriteIcon { Icon = FontAwesome.Regular.DotCircle }); + + foreach (var kvp in SelectionHandler.SelectionSampleStates) + yield return new TernaryButton(kvp.Value, kvp.Key.Replace("hit", string.Empty).Titleize(), () => getIconForSample(kvp.Key)); + } + + private Drawable getIconForSample(string sampleName) + { + switch (sampleName) + { + case HitSampleInfo.HIT_CLAP: + return new SpriteIcon { Icon = FontAwesome.Solid.Hands }; + + case HitSampleInfo.HIT_WHISTLE: + return new SpriteIcon { Icon = FontAwesome.Solid.Bullhorn }; + + case HitSampleInfo.HIT_FINISH: + return new SpriteIcon { Icon = FontAwesome.Solid.DrumSteelpan }; + } + + return null; } #region Placement From dbfa05d3b34339ecb139de74a4c3c2aa24f48342 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 25 Sep 2020 18:00:17 +0900 Subject: [PATCH 1247/1782] Fix placement object not getting updated with initial state --- .../Components/ComposeBlueprintContainer.cs | 30 ++++++++++++++----- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs index a83977c15f..81d7fa4b32 100644 --- a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs @@ -68,21 +68,31 @@ namespace osu.Game.Screens.Edit.Compose.Components NewCombo.BindTo(SelectionHandler.SelectionNewComboState); // we are responsible for current placement blueprint updated based on state changes. - NewCombo.ValueChanged += combo => - { - if (currentPlacement == null) return; - - if (currentPlacement.HitObject is IHasComboInformation c) - c.NewCombo = combo.NewValue == TernaryState.True; - }; + NewCombo.ValueChanged += _ => updatePlacementNewCombo(); // we own SelectionHandler so don't need to worry about making bindable copies (for simplicity) foreach (var kvp in SelectionHandler.SelectionSampleStates) { - kvp.Value.BindValueChanged(c => sampleChanged(kvp.Key, c.NewValue)); + kvp.Value.BindValueChanged(_ => updatePlacementSamples()); } } + private void updatePlacementNewCombo() + { + if (currentPlacement == null) return; + + if (currentPlacement.HitObject is IHasComboInformation c) + c.NewCombo = NewCombo.Value == TernaryState.True; + } + + private void updatePlacementSamples() + { + if (currentPlacement == null) return; + + foreach (var kvp in SelectionHandler.SelectionSampleStates) + sampleChanged(kvp.Key, kvp.Value.Value); + } + private void sampleChanged(string sampleName, TernaryState state) { if (currentPlacement == null) return; @@ -206,6 +216,10 @@ namespace osu.Game.Screens.Edit.Compose.Components // Fixes a 1-frame position discrepancy due to the first mouse move event happening in the next frame updatePlacementPosition(); + + updatePlacementSamples(); + + updatePlacementNewCombo(); } } From 4cc02abc76be15a30aeb82427aa04a80de2c696a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 25 Sep 2020 18:11:49 +0900 Subject: [PATCH 1248/1782] 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 d701aaf199..dc3e14c141 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,6 +52,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 71826e161c..6412f707d0 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -24,7 +24,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 90aa903318..f1e13169a5 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -80,7 +80,7 @@ - + From 59bfa086847f8b89874a88b2257c5bd105eb9bd2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 25 Sep 2020 18:26:54 +0900 Subject: [PATCH 1249/1782] Forcefully re-apply DrawableHitObject state transforms on post-load DefaultsApplied --- 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 28d3a39096..7c05bc9aa7 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -186,6 +186,7 @@ namespace osu.Game.Rulesets.Objects.Drawables private void onDefaultsApplied(HitObject hitObject) { apply(hitObject); + updateState(state.Value, true); DefaultsApplied?.Invoke(this); } From 8b255f457971745411f896764889edc92a650a98 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 25 Sep 2020 18:40:20 +0900 Subject: [PATCH 1250/1782] Fix test failures The issue was the ArchiveModelManager change; the test local change is just there because it makes more sense to run for every test in that scene. --- .../Visual/Editing/TestSceneEditorBeatmapCreation.cs | 8 ++++---- osu.Game/Database/ArchiveModelManager.cs | 8 +++++--- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs index ceacbd51a2..720cf51f2c 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs @@ -27,15 +27,15 @@ namespace osu.Game.Tests.Visual.Editing AddStep("set dummy", () => Beatmap.Value = new DummyWorkingBeatmap(Audio, null)); base.SetUpSteps(); + + // if we save a beatmap with a hash collision, things fall over. + // probably needs a more solid resolution in the future but this will do for now. + AddStep("make new beatmap unique", () => EditorBeatmap.Metadata.Title = Guid.NewGuid().ToString()); } [Test] public void TestCreateNewBeatmap() { - // if we save a beatmap with a hash collision, things fall over. - // probably needs a more solid resolution in the future but this will do for now. - AddStep("make new beatmap unique", () => EditorBeatmap.Metadata.Title = Guid.NewGuid().ToString()); - AddStep("save beatmap", () => Editor.Save()); AddAssert("new beatmap persisted", () => EditorBeatmap.BeatmapInfo.ID > 0); } diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index c39b71b058..bbe2604216 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -427,11 +427,13 @@ namespace osu.Game.Database { // Dereference the existing file info, since the file model will be removed. if (file.FileInfo != null) + { Files.Dereference(file.FileInfo); - // This shouldn't be required, but here for safety in case the provided TModel is not being change tracked - // Definitely can be removed once we rework the database backend. - usage.Context.Set().Remove(file); + // This shouldn't be required, but here for safety in case the provided TModel is not being change tracked + // Definitely can be removed once we rework the database backend. + usage.Context.Set().Remove(file); + } model.Files.Remove(file); } From c41fb67e730cba4dc9c8b706a511acf430b3938a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 25 Sep 2020 18:48:04 +0900 Subject: [PATCH 1251/1782] Move all ruleset editor tests to their own namespace --- .../{ => Editor}/ManiaPlacementBlueprintTestScene.cs | 2 +- .../{ => Editor}/ManiaSelectionBlueprintTestScene.cs | 2 +- osu.Game.Rulesets.Mania.Tests/{ => Editor}/TestSceneEditor.cs | 2 +- .../{ => Editor}/TestSceneHoldNotePlacementBlueprint.cs | 2 +- .../{ => Editor}/TestSceneHoldNoteSelectionBlueprint.cs | 2 +- .../{ => Editor}/TestSceneManiaBeatSnapGrid.cs | 2 +- .../{ => Editor}/TestSceneManiaHitObjectComposer.cs | 2 +- .../{ => Editor}/TestSceneNotePlacementBlueprint.cs | 2 +- .../{ => Editor}/TestSceneNoteSelectionBlueprint.cs | 2 +- .../{ => Editor}/TestSceneHitCirclePlacementBlueprint.cs | 2 +- .../{ => Editor}/TestSceneHitCircleSelectionBlueprint.cs | 2 +- .../{ => Editor}/TestSceneOsuDistanceSnapGrid.cs | 2 +- .../{ => Editor}/TestScenePathControlPointVisualiser.cs | 2 +- .../{ => Editor}/TestSceneSliderPlacementBlueprint.cs | 2 +- .../{ => Editor}/TestSceneSliderSelectionBlueprint.cs | 2 +- .../{ => Editor}/TestSceneSpinnerPlacementBlueprint.cs | 2 +- .../{ => Editor}/TestSceneSpinnerSelectionBlueprint.cs | 2 +- osu.Game.Rulesets.Taiko.Tests/{ => Editor}/TestSceneEditor.cs | 2 +- .../{ => Editor}/TestSceneTaikoHitObjectComposer.cs | 2 +- 19 files changed, 19 insertions(+), 19 deletions(-) rename osu.Game.Rulesets.Mania.Tests/{ => Editor}/ManiaPlacementBlueprintTestScene.cs (97%) rename osu.Game.Rulesets.Mania.Tests/{ => Editor}/ManiaSelectionBlueprintTestScene.cs (95%) rename osu.Game.Rulesets.Mania.Tests/{ => Editor}/TestSceneEditor.cs (96%) rename osu.Game.Rulesets.Mania.Tests/{ => Editor}/TestSceneHoldNotePlacementBlueprint.cs (93%) rename osu.Game.Rulesets.Mania.Tests/{ => Editor}/TestSceneHoldNoteSelectionBlueprint.cs (97%) rename osu.Game.Rulesets.Mania.Tests/{ => Editor}/TestSceneManiaBeatSnapGrid.cs (98%) rename osu.Game.Rulesets.Mania.Tests/{ => Editor}/TestSceneManiaHitObjectComposer.cs (99%) rename osu.Game.Rulesets.Mania.Tests/{ => Editor}/TestSceneNotePlacementBlueprint.cs (97%) rename osu.Game.Rulesets.Mania.Tests/{ => Editor}/TestSceneNoteSelectionBlueprint.cs (96%) rename osu.Game.Rulesets.Osu.Tests/{ => Editor}/TestSceneHitCirclePlacementBlueprint.cs (94%) rename osu.Game.Rulesets.Osu.Tests/{ => Editor}/TestSceneHitCircleSelectionBlueprint.cs (98%) rename osu.Game.Rulesets.Osu.Tests/{ => Editor}/TestSceneOsuDistanceSnapGrid.cs (99%) rename osu.Game.Rulesets.Osu.Tests/{ => Editor}/TestScenePathControlPointVisualiser.cs (97%) rename osu.Game.Rulesets.Osu.Tests/{ => Editor}/TestSceneSliderPlacementBlueprint.cs (99%) rename osu.Game.Rulesets.Osu.Tests/{ => Editor}/TestSceneSliderSelectionBlueprint.cs (99%) rename osu.Game.Rulesets.Osu.Tests/{ => Editor}/TestSceneSpinnerPlacementBlueprint.cs (94%) rename osu.Game.Rulesets.Osu.Tests/{ => Editor}/TestSceneSpinnerSelectionBlueprint.cs (96%) rename osu.Game.Rulesets.Taiko.Tests/{ => Editor}/TestSceneEditor.cs (88%) rename osu.Game.Rulesets.Taiko.Tests/{ => Editor}/TestSceneTaikoHitObjectComposer.cs (97%) diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaPlacementBlueprintTestScene.cs b/osu.Game.Rulesets.Mania.Tests/Editor/ManiaPlacementBlueprintTestScene.cs similarity index 97% rename from osu.Game.Rulesets.Mania.Tests/ManiaPlacementBlueprintTestScene.cs rename to osu.Game.Rulesets.Mania.Tests/Editor/ManiaPlacementBlueprintTestScene.cs index 0fe4a3c669..ece523e84c 100644 --- a/osu.Game.Rulesets.Mania.Tests/ManiaPlacementBlueprintTestScene.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/ManiaPlacementBlueprintTestScene.cs @@ -16,7 +16,7 @@ using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Tests.Visual; using osuTK.Graphics; -namespace osu.Game.Rulesets.Mania.Tests +namespace osu.Game.Rulesets.Mania.Tests.Editor { public abstract class ManiaPlacementBlueprintTestScene : PlacementBlueprintTestScene { diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaSelectionBlueprintTestScene.cs b/osu.Game.Rulesets.Mania.Tests/Editor/ManiaSelectionBlueprintTestScene.cs similarity index 95% rename from osu.Game.Rulesets.Mania.Tests/ManiaSelectionBlueprintTestScene.cs rename to osu.Game.Rulesets.Mania.Tests/Editor/ManiaSelectionBlueprintTestScene.cs index 149f6582ab..176fbba921 100644 --- a/osu.Game.Rulesets.Mania.Tests/ManiaSelectionBlueprintTestScene.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/ManiaSelectionBlueprintTestScene.cs @@ -8,7 +8,7 @@ using osu.Game.Rulesets.Mania.UI; using osu.Game.Tests.Visual; using osuTK.Graphics; -namespace osu.Game.Rulesets.Mania.Tests +namespace osu.Game.Rulesets.Mania.Tests.Editor { public abstract class ManiaSelectionBlueprintTestScene : SelectionBlueprintTestScene { diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneEditor.cs b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneEditor.cs similarity index 96% rename from osu.Game.Rulesets.Mania.Tests/TestSceneEditor.cs rename to osu.Game.Rulesets.Mania.Tests/Editor/TestSceneEditor.cs index 3b9c03b86a..d3afbc63eb 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneEditor.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneEditor.cs @@ -8,7 +8,7 @@ using osu.Game.Rulesets.Mania.Configuration; using osu.Game.Rulesets.Mania.UI; using osu.Game.Tests.Visual; -namespace osu.Game.Rulesets.Mania.Tests +namespace osu.Game.Rulesets.Mania.Tests.Editor { [TestFixture] public class TestSceneEditor : EditorTestScene diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNotePlacementBlueprint.cs b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneHoldNotePlacementBlueprint.cs similarity index 93% rename from osu.Game.Rulesets.Mania.Tests/TestSceneHoldNotePlacementBlueprint.cs rename to osu.Game.Rulesets.Mania.Tests/Editor/TestSceneHoldNotePlacementBlueprint.cs index b4332264b9..87c74a12cf 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNotePlacementBlueprint.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneHoldNotePlacementBlueprint.cs @@ -8,7 +8,7 @@ using osu.Game.Rulesets.Mania.Objects.Drawables; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; -namespace osu.Game.Rulesets.Mania.Tests +namespace osu.Game.Rulesets.Mania.Tests.Editor { public class TestSceneHoldNotePlacementBlueprint : ManiaPlacementBlueprintTestScene { diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteSelectionBlueprint.cs b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneHoldNoteSelectionBlueprint.cs similarity index 97% rename from osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteSelectionBlueprint.cs rename to osu.Game.Rulesets.Mania.Tests/Editor/TestSceneHoldNoteSelectionBlueprint.cs index 90394f3d1b..24f4c6858e 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneHoldNoteSelectionBlueprint.cs @@ -12,7 +12,7 @@ using osu.Game.Rulesets.Mania.Objects.Drawables; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Tests.Visual; -namespace osu.Game.Rulesets.Mania.Tests +namespace osu.Game.Rulesets.Mania.Tests.Editor { public class TestSceneHoldNoteSelectionBlueprint : ManiaSelectionBlueprintTestScene { diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneManiaBeatSnapGrid.cs b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaBeatSnapGrid.cs similarity index 98% rename from osu.Game.Rulesets.Mania.Tests/TestSceneManiaBeatSnapGrid.cs rename to osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaBeatSnapGrid.cs index 639be0bc11..654b752001 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneManiaBeatSnapGrid.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaBeatSnapGrid.cs @@ -20,7 +20,7 @@ using osu.Game.Screens.Edit; using osu.Game.Tests.Visual; using osuTK; -namespace osu.Game.Rulesets.Mania.Tests +namespace osu.Game.Rulesets.Mania.Tests.Editor { public class TestSceneManiaBeatSnapGrid : EditorClockTestScene { diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaHitObjectComposer.cs similarity index 99% rename from osu.Game.Rulesets.Mania.Tests/TestSceneManiaHitObjectComposer.cs rename to osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaHitObjectComposer.cs index 1a3fa29d4a..c9551ee79e 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneManiaHitObjectComposer.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaHitObjectComposer.cs @@ -23,7 +23,7 @@ using osu.Game.Tests.Visual; using osuTK; using osuTK.Input; -namespace osu.Game.Rulesets.Mania.Tests +namespace osu.Game.Rulesets.Mania.Tests.Editor { public class TestSceneManiaHitObjectComposer : EditorClockTestScene { diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneNotePlacementBlueprint.cs b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneNotePlacementBlueprint.cs similarity index 97% rename from osu.Game.Rulesets.Mania.Tests/TestSceneNotePlacementBlueprint.cs rename to osu.Game.Rulesets.Mania.Tests/Editor/TestSceneNotePlacementBlueprint.cs index 2d97e61aa5..36c34a8fb9 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneNotePlacementBlueprint.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneNotePlacementBlueprint.cs @@ -18,7 +18,7 @@ using osu.Game.Tests.Visual; using osuTK; using osuTK.Input; -namespace osu.Game.Rulesets.Mania.Tests +namespace osu.Game.Rulesets.Mania.Tests.Editor { public class TestSceneNotePlacementBlueprint : ManiaPlacementBlueprintTestScene { diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneNoteSelectionBlueprint.cs b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneNoteSelectionBlueprint.cs similarity index 96% rename from osu.Game.Rulesets.Mania.Tests/TestSceneNoteSelectionBlueprint.cs rename to osu.Game.Rulesets.Mania.Tests/Editor/TestSceneNoteSelectionBlueprint.cs index 1514bdf0bd..0e47a12a8e 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneNoteSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneNoteSelectionBlueprint.cs @@ -12,7 +12,7 @@ using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Tests.Visual; using osuTK; -namespace osu.Game.Rulesets.Mania.Tests +namespace osu.Game.Rulesets.Mania.Tests.Editor { public class TestSceneNoteSelectionBlueprint : ManiaSelectionBlueprintTestScene { diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCirclePlacementBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneHitCirclePlacementBlueprint.cs similarity index 94% rename from osu.Game.Rulesets.Osu.Tests/TestSceneHitCirclePlacementBlueprint.cs rename to osu.Game.Rulesets.Osu.Tests/Editor/TestSceneHitCirclePlacementBlueprint.cs index 4c6abc45f7..7bccec6c97 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCirclePlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneHitCirclePlacementBlueprint.cs @@ -9,7 +9,7 @@ using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Tests.Visual; -namespace osu.Game.Rulesets.Osu.Tests +namespace osu.Game.Rulesets.Osu.Tests.Editor { public class TestSceneHitCirclePlacementBlueprint : PlacementBlueprintTestScene { diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleSelectionBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneHitCircleSelectionBlueprint.cs similarity index 98% rename from osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleSelectionBlueprint.cs rename to osu.Game.Rulesets.Osu.Tests/Editor/TestSceneHitCircleSelectionBlueprint.cs index 0ecce42e88..66cd405195 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneHitCircleSelectionBlueprint.cs @@ -11,7 +11,7 @@ using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Tests.Visual; using osuTK; -namespace osu.Game.Rulesets.Osu.Tests +namespace osu.Game.Rulesets.Osu.Tests.Editor { public class TestSceneHitCircleSelectionBlueprint : SelectionBlueprintTestScene { diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuDistanceSnapGrid.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuDistanceSnapGrid.cs similarity index 99% rename from osu.Game.Rulesets.Osu.Tests/TestSceneOsuDistanceSnapGrid.cs rename to osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuDistanceSnapGrid.cs index 0d0be2953b..1232369a0b 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuDistanceSnapGrid.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuDistanceSnapGrid.cs @@ -19,7 +19,7 @@ using osu.Game.Tests.Visual; using osuTK; using osuTK.Graphics; -namespace osu.Game.Rulesets.Osu.Tests +namespace osu.Game.Rulesets.Osu.Tests.Editor { public class TestSceneOsuDistanceSnapGrid : OsuManualInputManagerTestScene { diff --git a/osu.Game.Rulesets.Osu.Tests/TestScenePathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestScenePathControlPointVisualiser.cs similarity index 97% rename from osu.Game.Rulesets.Osu.Tests/TestScenePathControlPointVisualiser.cs rename to osu.Game.Rulesets.Osu.Tests/Editor/TestScenePathControlPointVisualiser.cs index 21fa283b6d..738a21b17e 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestScenePathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestScenePathControlPointVisualiser.cs @@ -12,7 +12,7 @@ using osu.Game.Rulesets.Osu.Objects; using osu.Game.Tests.Visual; using osuTK; -namespace osu.Game.Rulesets.Osu.Tests +namespace osu.Game.Rulesets.Osu.Tests.Editor { public class TestScenePathControlPointVisualiser : OsuTestScene { diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs similarity index 99% rename from osu.Game.Rulesets.Osu.Tests/TestSceneSliderPlacementBlueprint.cs rename to osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs index fe9973f4d8..49d7d9249c 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs @@ -14,7 +14,7 @@ using osu.Game.Tests.Visual; using osuTK; using osuTK.Input; -namespace osu.Game.Rulesets.Osu.Tests +namespace osu.Game.Rulesets.Osu.Tests.Editor { public class TestSceneSliderPlacementBlueprint : PlacementBlueprintTestScene { diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSelectionBlueprint.cs similarity index 99% rename from osu.Game.Rulesets.Osu.Tests/TestSceneSliderSelectionBlueprint.cs rename to osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSelectionBlueprint.cs index d5be538d94..f6e1be693b 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSelectionBlueprint.cs @@ -16,7 +16,7 @@ using osu.Game.Tests.Visual; using osuTK; using osuTK.Input; -namespace osu.Game.Rulesets.Osu.Tests +namespace osu.Game.Rulesets.Osu.Tests.Editor { public class TestSceneSliderSelectionBlueprint : SelectionBlueprintTestScene { diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerPlacementBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSpinnerPlacementBlueprint.cs similarity index 94% rename from osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerPlacementBlueprint.cs rename to osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSpinnerPlacementBlueprint.cs index d74d072857..fa6c660b01 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSpinnerPlacementBlueprint.cs @@ -9,7 +9,7 @@ using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Tests.Visual; -namespace osu.Game.Rulesets.Osu.Tests +namespace osu.Game.Rulesets.Osu.Tests.Editor { public class TestSceneSpinnerPlacementBlueprint : PlacementBlueprintTestScene { diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerSelectionBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSpinnerSelectionBlueprint.cs similarity index 96% rename from osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerSelectionBlueprint.cs rename to osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSpinnerSelectionBlueprint.cs index 011463ab14..4248f68a60 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSpinnerSelectionBlueprint.cs @@ -11,7 +11,7 @@ using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Tests.Visual; using osuTK; -namespace osu.Game.Rulesets.Osu.Tests +namespace osu.Game.Rulesets.Osu.Tests.Editor { public class TestSceneSpinnerSelectionBlueprint : SelectionBlueprintTestScene { diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneEditor.cs b/osu.Game.Rulesets.Taiko.Tests/Editor/TestSceneEditor.cs similarity index 88% rename from osu.Game.Rulesets.Taiko.Tests/TestSceneEditor.cs rename to osu.Game.Rulesets.Taiko.Tests/Editor/TestSceneEditor.cs index 411fe08bcf..e3c1613bd9 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TestSceneEditor.cs +++ b/osu.Game.Rulesets.Taiko.Tests/Editor/TestSceneEditor.cs @@ -4,7 +4,7 @@ using NUnit.Framework; using osu.Game.Tests.Visual; -namespace osu.Game.Rulesets.Taiko.Tests +namespace osu.Game.Rulesets.Taiko.Tests.Editor { [TestFixture] public class TestSceneEditor : EditorTestScene diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoHitObjectComposer.cs b/osu.Game.Rulesets.Taiko.Tests/Editor/TestSceneTaikoHitObjectComposer.cs similarity index 97% rename from osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoHitObjectComposer.cs rename to osu.Game.Rulesets.Taiko.Tests/Editor/TestSceneTaikoHitObjectComposer.cs index 34d5fdf857..626537053a 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoHitObjectComposer.cs +++ b/osu.Game.Rulesets.Taiko.Tests/Editor/TestSceneTaikoHitObjectComposer.cs @@ -12,7 +12,7 @@ using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Screens.Edit; using osu.Game.Tests.Visual; -namespace osu.Game.Rulesets.Taiko.Tests +namespace osu.Game.Rulesets.Taiko.Tests.Editor { public class TestSceneTaikoHitObjectComposer : EditorClockTestScene { From acfa62bb50e669cdcfdc45f24f67339df7799eba Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 25 Sep 2020 19:25:58 +0900 Subject: [PATCH 1252/1782] Fix potential taiko crash on rewind --- osu.Game.Rulesets.Taiko.Tests/DrawableTestHit.cs | 13 +++++++++---- .../DrawableTestStrongHit.cs | 15 +++------------ .../Skinning/TestSceneHitExplosion.cs | 10 ++++------ osu.Game.Rulesets.Taiko/UI/DefaultHitExplosion.cs | 12 +++++++++--- osu.Game.Rulesets.Taiko/UI/HitExplosion.cs | 11 +++++------ osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs | 8 ++++---- 6 files changed, 34 insertions(+), 35 deletions(-) diff --git a/osu.Game.Rulesets.Taiko.Tests/DrawableTestHit.cs b/osu.Game.Rulesets.Taiko.Tests/DrawableTestHit.cs index 1db07b3244..3ffc6187b7 100644 --- a/osu.Game.Rulesets.Taiko.Tests/DrawableTestHit.cs +++ b/osu.Game.Rulesets.Taiko.Tests/DrawableTestHit.cs @@ -2,26 +2,31 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Rulesets.Taiko.Objects.Drawables; namespace osu.Game.Rulesets.Taiko.Tests { - internal class DrawableTestHit : DrawableTaikoHitObject + public class DrawableTestHit : DrawableHit { - private readonly HitResult type; + public readonly HitResult Type; public DrawableTestHit(Hit hit, HitResult type = HitResult.Great) : base(hit) { - this.type = type; + Type = type; + + // in order to create nested strong hit + HitObject.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); } [BackgroundDependencyLoader] private void load() { - Result.Type = type; + Result.Type = Type; } public override bool OnPressed(TaikoAction action) => false; diff --git a/osu.Game.Rulesets.Taiko.Tests/DrawableTestStrongHit.cs b/osu.Game.Rulesets.Taiko.Tests/DrawableTestStrongHit.cs index 7cb984b254..829bcf34a1 100644 --- a/osu.Game.Rulesets.Taiko.Tests/DrawableTestStrongHit.cs +++ b/osu.Game.Rulesets.Taiko.Tests/DrawableTestStrongHit.cs @@ -2,17 +2,14 @@ // See the LICENCE file in the repository root for full licence text. using System.Linq; -using osu.Game.Beatmaps; -using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Rulesets.Taiko.Objects.Drawables; namespace osu.Game.Rulesets.Taiko.Tests { - public class DrawableTestStrongHit : DrawableHit + public class DrawableTestStrongHit : DrawableTestHit { - private readonly HitResult type; private readonly bool hitBoth; public DrawableTestStrongHit(double startTime, HitResult type = HitResult.Great, bool hitBoth = true) @@ -20,12 +17,8 @@ namespace osu.Game.Rulesets.Taiko.Tests { IsStrong = true, StartTime = startTime, - }) + }, type) { - // in order to create nested strong hit - HitObject.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); - - this.type = type; this.hitBoth = hitBoth; } @@ -33,10 +26,8 @@ namespace osu.Game.Rulesets.Taiko.Tests { base.LoadAsyncComplete(); - Result.Type = type; - var nestedStrongHit = (DrawableStrongNestedHit)NestedHitObjects.Single(); - nestedStrongHit.Result.Type = hitBoth ? type : HitResult.Miss; + nestedStrongHit.Result.Type = hitBoth ? Type : HitResult.Miss; } public override bool OnPressed(TaikoAction action) => false; diff --git a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneHitExplosion.cs b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneHitExplosion.cs index 48969e0f5a..19cc56527e 100644 --- a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneHitExplosion.cs +++ b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneHitExplosion.cs @@ -6,7 +6,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Taiko.Objects; -using osu.Game.Rulesets.Taiko.Objects.Drawables; using osu.Game.Rulesets.Taiko.UI; namespace osu.Game.Rulesets.Taiko.Tests.Skinning @@ -29,7 +28,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning AddStep("Good", () => SetContents(() => getContentFor(createStrongHit(HitResult.Good, hitBoth)))); } - private Drawable getContentFor(DrawableTaikoHitObject hit) + private Drawable getContentFor(DrawableTestHit hit) { return new Container { @@ -37,7 +36,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning Children = new Drawable[] { hit, - new HitExplosion(hit) + new HitExplosion(hit, hit.Type) { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -46,9 +45,8 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning }; } - private DrawableTaikoHitObject createHit(HitResult type) => new DrawableTestHit(new Hit { StartTime = Time.Current }, type); + private DrawableTestHit createHit(HitResult type) => new DrawableTestHit(new Hit { StartTime = Time.Current }, type); - private DrawableTaikoHitObject createStrongHit(HitResult type, bool hitBoth) - => new DrawableTestStrongHit(Time.Current, type, hitBoth); + private DrawableTestHit createStrongHit(HitResult type, bool hitBoth) => new DrawableTestStrongHit(Time.Current, type, hitBoth); } } diff --git a/osu.Game.Rulesets.Taiko/UI/DefaultHitExplosion.cs b/osu.Game.Rulesets.Taiko/UI/DefaultHitExplosion.cs index 9943a58e3e..7b8ab89233 100644 --- a/osu.Game.Rulesets.Taiko/UI/DefaultHitExplosion.cs +++ b/osu.Game.Rulesets.Taiko/UI/DefaultHitExplosion.cs @@ -15,8 +15,14 @@ namespace osu.Game.Rulesets.Taiko.UI { internal class DefaultHitExplosion : CircularContainer { - [Resolved] - private DrawableHitObject judgedObject { get; set; } + private readonly DrawableHitObject judgedObject; + private readonly HitResult result; + + public DefaultHitExplosion(DrawableHitObject judgedObject, HitResult result) + { + this.judgedObject = judgedObject; + this.result = result; + } [BackgroundDependencyLoader] private void load(OsuColour colours) @@ -31,7 +37,7 @@ namespace osu.Game.Rulesets.Taiko.UI Alpha = 0.15f; Masking = true; - if (judgedObject.Result.Type == HitResult.Miss) + if (result == HitResult.Miss) return; bool isRim = (judgedObject.HitObject as Hit)?.Type == HitType.Rim; diff --git a/osu.Game.Rulesets.Taiko/UI/HitExplosion.cs b/osu.Game.Rulesets.Taiko/UI/HitExplosion.cs index e3eabbf88f..16300d5715 100644 --- a/osu.Game.Rulesets.Taiko/UI/HitExplosion.cs +++ b/osu.Game.Rulesets.Taiko/UI/HitExplosion.cs @@ -22,8 +22,8 @@ namespace osu.Game.Rulesets.Taiko.UI { public override bool RemoveWhenNotAlive => true; - [Cached(typeof(DrawableHitObject))] public readonly DrawableHitObject JudgedObject; + private readonly HitResult result; private SkinnableDrawable skinnable; @@ -31,9 +31,10 @@ namespace osu.Game.Rulesets.Taiko.UI public override double LifetimeEnd => skinnable.Drawable.LifetimeEnd; - public HitExplosion(DrawableHitObject judgedObject) + public HitExplosion(DrawableHitObject judgedObject, HitResult result) { JudgedObject = judgedObject; + this.result = result; Anchor = Anchor.Centre; Origin = Anchor.Centre; @@ -47,14 +48,12 @@ namespace osu.Game.Rulesets.Taiko.UI [BackgroundDependencyLoader] private void load() { - Child = skinnable = new SkinnableDrawable(new TaikoSkinComponent(getComponentName(JudgedObject)), _ => new DefaultHitExplosion()); + Child = skinnable = new SkinnableDrawable(new TaikoSkinComponent(getComponentName(JudgedObject)), _ => new DefaultHitExplosion(JudgedObject, result)); } private TaikoSkinComponents getComponentName(DrawableHitObject judgedObject) { - var resultType = judgedObject.Result?.Type ?? HitResult.Great; - - switch (resultType) + switch (result) { case HitResult.Miss: return TaikoSkinComponents.TaikoExplosionMiss; diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs index 7976d5bc6d..7b3fbb1faf 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs @@ -9,6 +9,7 @@ using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Rulesets.Taiko.Objects.Drawables; @@ -206,8 +207,7 @@ namespace osu.Game.Rulesets.Taiko.UI }); var type = (judgedObject.HitObject as Hit)?.Type ?? HitType.Centre; - - addExplosion(judgedObject, type); + addExplosion(judgedObject, result.Type, type); break; } } @@ -219,9 +219,9 @@ namespace osu.Game.Rulesets.Taiko.UI /// As legacy skins have different explosions for singular and double strong hits, /// explosion addition is scheduled to ensure that both hits are processed if they occur on the same frame. /// - private void addExplosion(DrawableHitObject drawableObject, HitType type) => Schedule(() => + private void addExplosion(DrawableHitObject drawableObject, HitResult result, HitType type) => Schedule(() => { - hitExplosionContainer.Add(new HitExplosion(drawableObject)); + hitExplosionContainer.Add(new HitExplosion(drawableObject, result)); if (drawableObject.HitObject.Kiai) kiaiExplosionContainer.Add(new KiaiHitExplosion(drawableObject, type)); }); From 480eeb5fbee83ce1ac7eb5c28fb55e71b78c4640 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 25 Sep 2020 19:37:34 +0900 Subject: [PATCH 1253/1782] Add back caching --- osu.Game.Rulesets.Taiko/UI/HitExplosion.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Rulesets.Taiko/UI/HitExplosion.cs b/osu.Game.Rulesets.Taiko/UI/HitExplosion.cs index 16300d5715..efd1b25046 100644 --- a/osu.Game.Rulesets.Taiko/UI/HitExplosion.cs +++ b/osu.Game.Rulesets.Taiko/UI/HitExplosion.cs @@ -22,7 +22,9 @@ namespace osu.Game.Rulesets.Taiko.UI { public override bool RemoveWhenNotAlive => true; + [Cached(typeof(DrawableHitObject))] public readonly DrawableHitObject JudgedObject; + private readonly HitResult result; private SkinnableDrawable skinnable; From 0853f0e128dc14a880bc8d7ae0c2553fd67b4b9f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 25 Sep 2020 19:38:23 +0900 Subject: [PATCH 1254/1782] Remove comment --- osu.Game.Rulesets.Taiko.Tests/DrawableTestHit.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko.Tests/DrawableTestHit.cs b/osu.Game.Rulesets.Taiko.Tests/DrawableTestHit.cs index 3ffc6187b7..e0af973b53 100644 --- a/osu.Game.Rulesets.Taiko.Tests/DrawableTestHit.cs +++ b/osu.Game.Rulesets.Taiko.Tests/DrawableTestHit.cs @@ -19,7 +19,6 @@ namespace osu.Game.Rulesets.Taiko.Tests { Type = type; - // in order to create nested strong hit HitObject.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); } From 1c4baa4e2adf718272cede63b8ae0393a3b77ef3 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 25 Sep 2020 20:11:27 +0900 Subject: [PATCH 1255/1782] Add bonus hit results and orderings --- osu.Game/Rulesets/Scoring/HitResult.cs | 103 ++++++++++++++++++++++++- 1 file changed, 102 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Scoring/HitResult.cs b/osu.Game/Rulesets/Scoring/HitResult.cs index b057af2a50..370ffb3a7f 100644 --- a/osu.Game/Rulesets/Scoring/HitResult.cs +++ b/osu.Game/Rulesets/Scoring/HitResult.cs @@ -2,17 +2,23 @@ // See the LICENCE file in the repository root for full licence text. using System.ComponentModel; +using osu.Game.Utils; namespace osu.Game.Rulesets.Scoring { + [HasOrderedElements] public enum HitResult { /// /// Indicates that the object has not been judged yet. /// [Description(@"")] + [Order(13)] None, + [Order(12)] + Ignore, + /// /// Indicates that the object has been judged as a miss. /// @@ -21,47 +27,142 @@ namespace osu.Game.Rulesets.Scoring /// "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")] + [Order(5)] Miss, [Description(@"Meh")] + [Order(4)] Meh, /// /// Optional judgement. /// [Description(@"OK")] + [Order(3)] Ok, [Description(@"Good")] + [Order(2)] Good, [Description(@"Great")] + [Order(1)] Great, /// /// Optional judgement. /// [Description(@"Perfect")] + [Order(0)] Perfect, /// /// Indicates small tick miss. /// + [Order(11)] SmallTickMiss, /// /// Indicates a small tick hit. /// + [Description(@"S Tick")] + [Order(7)] SmallTickHit, /// /// Indicates a large tick miss. /// + [Order(10)] LargeTickMiss, /// /// Indicates a large tick hit. /// - LargeTickHit + [Description(@"L Tick")] + [Order(6)] + LargeTickHit, + + /// + /// Indicates a small bonus. + /// + [Description("S Bonus")] + [Order(9)] + SmallBonus, + + /// + /// Indicate a large bonus. + /// + [Description("L Bonus")] + [Order(8)] + LargeBonus, + } + + public static class HitResultExtensions + { + /// + /// Whether a affects the combo. + /// + public static bool AffectsCombo(this HitResult result) + { + switch (result) + { + case HitResult.Miss: + case HitResult.Meh: + case HitResult.Ok: + case HitResult.Good: + case HitResult.Great: + case HitResult.Perfect: + case HitResult.LargeTickHit: + case HitResult.LargeTickMiss: + return true; + + default: + return false; + } + } + + /// + /// Whether a should be counted as combo score. + /// + /// + /// This is not the reciprocal of , as and do not affect combo + /// but are still considered as part of the accuracy (not bonus) portion of the score. + /// + public static bool IsBonus(this HitResult result) + { + switch (result) + { + case HitResult.SmallBonus: + case HitResult.LargeBonus: + return true; + + default: + return false; + } + } + + /// + /// Whether a represents a successful hit. + /// + public static bool IsHit(this HitResult result) + { + switch (result) + { + case HitResult.None: + case HitResult.Ignore: + case HitResult.Miss: + case HitResult.SmallTickMiss: + case HitResult.LargeTickMiss: + return false; + + default: + return true; + } + } + + /// + /// Whether a is scorable. + /// + public static bool IsScorable(this HitResult result) => result > HitResult.Ignore; } } From a07597c3691ef68583269ef5e2b41487e64458b8 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 25 Sep 2020 20:22:59 +0900 Subject: [PATCH 1256/1782] Adjust displays to use new results/orderings --- .../Overlays/BeatmapSet/Scores/ScoreTable.cs | 12 +++-- .../Scores/TopScoreStatisticsSection.cs | 6 +-- osu.Game/Scoring/ScoreInfo.cs | 45 ++++++++++++++++++- .../ContractedPanelMiddleContent.cs | 6 ++- .../Expanded/ExpandedPanelMiddleContent.cs | 5 ++- .../Expanded/Statistics/CounterStatistic.cs | 34 +++++++++++++- .../Expanded/Statistics/HitResultStatistic.cs | 4 +- osu.Game/Tests/TestScoreInfo.cs | 6 +++ 8 files changed, 99 insertions(+), 19 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs index 56866765b6..edf04dc55a 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs @@ -92,10 +92,8 @@ namespace osu.Game.Overlays.BeatmapSet.Scores new TableColumn("max combo", Anchor.CentreLeft, new Dimension(GridSizeMode.Distributed, minSize: 70, maxSize: 120)) }; - foreach (var statistic in score.SortedStatistics.Take(score.SortedStatistics.Count() - 1)) - columns.Add(new TableColumn(statistic.Key.GetDescription(), Anchor.CentreLeft, new Dimension(GridSizeMode.Distributed, minSize: 35, maxSize: 60))); - - columns.Add(new TableColumn(score.SortedStatistics.LastOrDefault().Key.GetDescription(), Anchor.CentreLeft, new Dimension(GridSizeMode.Distributed, minSize: 45, maxSize: 95))); + foreach (var (key, _, _) in score.GetStatisticsForDisplay()) + columns.Add(new TableColumn(key.GetDescription(), Anchor.CentreLeft, new Dimension(GridSizeMode.Distributed, minSize: 35, maxSize: 60))); if (showPerformancePoints) columns.Add(new TableColumn("pp", Anchor.CentreLeft, new Dimension(GridSizeMode.Absolute, 30))); @@ -148,13 +146,13 @@ namespace osu.Game.Overlays.BeatmapSet.Scores } }; - foreach (var kvp in score.SortedStatistics) + foreach (var (_, value, maxCount) in score.GetStatisticsForDisplay()) { content.Add(new OsuSpriteText { - Text = $"{kvp.Value}", + Text = maxCount == null ? $"{value}" : $"{value}/{maxCount}", Font = OsuFont.GetFont(size: text_size), - Colour = kvp.Value == 0 ? Color4.Gray : Color4.White + Colour = value == 0 ? Color4.Gray : Color4.White }); } diff --git a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs index 3a842d0a43..05789e1fc0 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs @@ -117,7 +117,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores ppColumn.Alpha = value.Beatmap?.Status == BeatmapSetOnlineStatus.Ranked ? 1 : 0; ppColumn.Text = $@"{value.PP:N0}"; - statisticsColumns.ChildrenEnumerable = value.SortedStatistics.Select(kvp => createStatisticsColumn(kvp.Key, kvp.Value)); + statisticsColumns.ChildrenEnumerable = value.GetStatisticsForDisplay().Select(s => createStatisticsColumn(s.result, s.count, s.maxCount)); modsColumn.Mods = value.Mods; if (scoreManager != null) @@ -125,9 +125,9 @@ namespace osu.Game.Overlays.BeatmapSet.Scores } } - private TextColumn createStatisticsColumn(HitResult hitResult, int count) => new TextColumn(hitResult.GetDescription(), smallFont, bottom_columns_min_width) + private TextColumn createStatisticsColumn(HitResult hitResult, int count, int? maxCount) => new TextColumn(hitResult.GetDescription(), smallFont, bottom_columns_min_width) { - Text = count.ToString() + Text = maxCount == null ? $"{count}" : $"{count}/{maxCount}" }; private class InfoColumn : CompositeDrawable diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index efcf1737c9..4ed3f92e25 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -7,6 +7,7 @@ using System.ComponentModel.DataAnnotations.Schema; using System.Linq; using Newtonsoft.Json; using Newtonsoft.Json.Converters; +using osu.Framework.Extensions; using osu.Game.Beatmaps; using osu.Game.Database; using osu.Game.Rulesets; @@ -147,8 +148,6 @@ namespace osu.Game.Scoring [JsonProperty("statistics")] public Dictionary Statistics = new Dictionary(); - public IOrderedEnumerable> SortedStatistics => Statistics.OrderByDescending(pair => pair.Key); - [JsonIgnore] [Column("Statistics")] public string StatisticsJson @@ -186,6 +185,48 @@ namespace osu.Game.Scoring [JsonProperty("position")] public int? Position { get; set; } + public IEnumerable<(HitResult result, int count, int? maxCount)> GetStatisticsForDisplay() + { + foreach (var key in OrderAttributeUtils.GetValuesInOrder()) + { + if (key.IsBonus()) + continue; + + int value = Statistics.GetOrDefault(key); + + switch (key) + { + case HitResult.SmallTickHit: + { + int total = value + Statistics.GetOrDefault(HitResult.SmallTickMiss); + if (total > 0) + yield return (key, value, total); + + break; + } + + case HitResult.LargeTickHit: + { + int total = value + Statistics.GetOrDefault(HitResult.LargeTickMiss); + if (total > 0) + yield return (key, value, total); + + break; + } + + case HitResult.SmallTickMiss: + case HitResult.LargeTickMiss: + break; + + default: + if (value > 0 || key == HitResult.Miss) + yield return (key, value, null); + + break; + } + } + } + [Serializable] protected class DeserializedMod : IMod { diff --git a/osu.Game/Screens/Ranking/Contracted/ContractedPanelMiddleContent.cs b/osu.Game/Screens/Ranking/Contracted/ContractedPanelMiddleContent.cs index 0b85eeafa8..95ece1a9fb 100644 --- a/osu.Game/Screens/Ranking/Contracted/ContractedPanelMiddleContent.cs +++ b/osu.Game/Screens/Ranking/Contracted/ContractedPanelMiddleContent.cs @@ -13,6 +13,7 @@ using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Online.Leaderboards; +using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; using osu.Game.Screens.Play.HUD; using osu.Game.Users; @@ -116,7 +117,7 @@ namespace osu.Game.Screens.Ranking.Contracted AutoSizeAxes = Axes.Y, Direction = FillDirection.Vertical, Spacing = new Vector2(0, 5), - ChildrenEnumerable = score.SortedStatistics.Select(s => createStatistic(s.Key.GetDescription(), s.Value.ToString())) + ChildrenEnumerable = score.GetStatisticsForDisplay().Select(s => createStatistic(s.result, s.count, s.maxCount)) }, new FillFlowContainer { @@ -198,6 +199,9 @@ namespace osu.Game.Screens.Ranking.Contracted }; } + private Drawable createStatistic(HitResult result, int count, int? maxCount) + => createStatistic(result.GetDescription(), maxCount == null ? $"{count}" : $"{count}/{maxCount}"); + private Drawable createStatistic(string key, string value) => new Container { RelativeSizeAxes = Axes.X, diff --git a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs index 0033cd1f43..ebab8c88f6 100644 --- a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs +++ b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs @@ -65,8 +65,9 @@ namespace osu.Game.Screens.Ranking.Expanded }; var bottomStatistics = new List(); - foreach (var stat in score.SortedStatistics) - bottomStatistics.Add(new HitResultStatistic(stat.Key, stat.Value)); + + foreach (var (key, value, maxCount) in score.GetStatisticsForDisplay()) + bottomStatistics.Add(new HitResultStatistic(key, value, maxCount)); statisticDisplays.AddRange(topStatistics); statisticDisplays.AddRange(bottomStatistics); diff --git a/osu.Game/Screens/Ranking/Expanded/Statistics/CounterStatistic.cs b/osu.Game/Screens/Ranking/Expanded/Statistics/CounterStatistic.cs index e820831809..fc01f5e9c4 100644 --- a/osu.Game/Screens/Ranking/Expanded/Statistics/CounterStatistic.cs +++ b/osu.Game/Screens/Ranking/Expanded/Statistics/CounterStatistic.cs @@ -2,6 +2,7 @@ // 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.Graphics.UserInterface; @@ -16,6 +17,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics public class CounterStatistic : StatisticDisplay { private readonly int count; + private readonly int? maxCount; private RollingCounter counter; @@ -24,10 +26,12 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics /// /// The name of the statistic. /// The value to display. - public CounterStatistic(string header, int count) + /// The maximum value of . Not displayed if null. + public CounterStatistic(string header, int count, int? maxCount = null) : base(header) { this.count = count; + this.maxCount = maxCount; } public override void Appear() @@ -36,7 +40,33 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics counter.Current.Value = count; } - protected override Drawable CreateContent() => counter = new Counter(); + protected override Drawable CreateContent() + { + var container = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Child = counter = new Counter + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre + } + }; + + if (maxCount != null) + { + container.Add(new OsuSpriteText + { + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + Font = OsuFont.Torus.With(size: 12, fixedWidth: true), + Spacing = new Vector2(-2, 0), + Text = $"/{maxCount}" + }); + } + + return container; + } private class Counter : RollingCounter { diff --git a/osu.Game/Screens/Ranking/Expanded/Statistics/HitResultStatistic.cs b/osu.Game/Screens/Ranking/Expanded/Statistics/HitResultStatistic.cs index faa4a6a96c..a86033713f 100644 --- a/osu.Game/Screens/Ranking/Expanded/Statistics/HitResultStatistic.cs +++ b/osu.Game/Screens/Ranking/Expanded/Statistics/HitResultStatistic.cs @@ -12,8 +12,8 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics { private readonly HitResult result; - public HitResultStatistic(HitResult result, int count) - : base(result.GetDescription(), count) + public HitResultStatistic(HitResult result, int count, int? maxCount = null) + : base(result.GetDescription(), count, maxCount) { this.result = result; } diff --git a/osu.Game/Tests/TestScoreInfo.cs b/osu.Game/Tests/TestScoreInfo.cs index 31cced6ce4..704d01e479 100644 --- a/osu.Game/Tests/TestScoreInfo.cs +++ b/osu.Game/Tests/TestScoreInfo.cs @@ -37,6 +37,12 @@ namespace osu.Game.Tests Statistics[HitResult.Meh] = 50; Statistics[HitResult.Good] = 100; Statistics[HitResult.Great] = 300; + Statistics[HitResult.SmallTickHit] = 50; + Statistics[HitResult.SmallTickMiss] = 25; + Statistics[HitResult.LargeTickHit] = 100; + Statistics[HitResult.LargeTickMiss] = 50; + Statistics[HitResult.SmallBonus] = 10; + Statistics[HitResult.SmallBonus] = 50; Position = 1; } From 2517fffb7e61b4bad9f0f9da41a834d1c42a14d8 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 25 Sep 2020 20:48:16 +0900 Subject: [PATCH 1257/1782] Fix incorrect display in beatmap overlay table --- .../Overlays/BeatmapSet/Scores/ScoreTable.cs | 36 +++++++++++++++---- 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs index edf04dc55a..968355c377 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs @@ -11,9 +11,11 @@ using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Online.Leaderboards; +using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; using osu.Game.Scoring; using osu.Game.Users.Drawables; +using osu.Game.Utils; using osuTK; using osuTK.Graphics; @@ -55,6 +57,11 @@ namespace osu.Game.Overlays.BeatmapSet.Scores highAccuracyColour = colours.GreenLight; } + /// + /// The statistics that appear in the table, in order of appearance. + /// + private readonly List statisticResultTypes = new List(); + private bool showPerformancePoints; public void DisplayScores(IReadOnlyList scores, bool showPerformanceColumn) @@ -65,11 +72,12 @@ namespace osu.Game.Overlays.BeatmapSet.Scores return; showPerformancePoints = showPerformanceColumn; + statisticResultTypes.Clear(); for (int i = 0; i < scores.Count; i++) backgroundFlow.Add(new ScoreTableRowBackground(i, scores[i], row_height)); - Columns = createHeaders(scores.FirstOrDefault()); + Columns = createHeaders(scores); Content = scores.Select((s, i) => createContent(i, s)).ToArray().ToRectangular(); } @@ -79,7 +87,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores backgroundFlow.Clear(); } - private TableColumn[] createHeaders(ScoreInfo score) + private TableColumn[] createHeaders(IReadOnlyList scores) { var columns = new List { @@ -92,8 +100,17 @@ namespace osu.Game.Overlays.BeatmapSet.Scores new TableColumn("max combo", Anchor.CentreLeft, new Dimension(GridSizeMode.Distributed, minSize: 70, maxSize: 120)) }; - foreach (var (key, _, _) in score.GetStatisticsForDisplay()) - columns.Add(new TableColumn(key.GetDescription(), Anchor.CentreLeft, new Dimension(GridSizeMode.Distributed, minSize: 35, maxSize: 60))); + // All statistics across all scores, unordered. + var allScoreStatistics = scores.SelectMany(s => s.GetStatisticsForDisplay().Select(stat => stat.result)).ToHashSet(); + + foreach (var result in OrderAttributeUtils.GetValuesInOrder()) + { + if (!allScoreStatistics.Contains(result)) + continue; + + columns.Add(new TableColumn(result.GetDescription(), Anchor.CentreLeft, new Dimension(GridSizeMode.Distributed, minSize: 35, maxSize: 60))); + statisticResultTypes.Add(result); + } if (showPerformancePoints) columns.Add(new TableColumn("pp", Anchor.CentreLeft, new Dimension(GridSizeMode.Absolute, 30))); @@ -146,13 +163,18 @@ namespace osu.Game.Overlays.BeatmapSet.Scores } }; - foreach (var (_, value, maxCount) in score.GetStatisticsForDisplay()) + var availableStatistics = score.GetStatisticsForDisplay().ToDictionary(tuple => tuple.result); + + foreach (var result in statisticResultTypes) { + if (!availableStatistics.TryGetValue(result, out var stat)) + stat = (result, 0, null); + content.Add(new OsuSpriteText { - Text = maxCount == null ? $"{value}" : $"{value}/{maxCount}", + Text = stat.maxCount == null ? $"{stat.count}" : $"{stat.count}/{stat.maxCount}", Font = OsuFont.GetFont(size: text_size), - Colour = value == 0 ? Color4.Gray : Color4.White + Colour = stat.count == 0 ? Color4.Gray : Color4.White }); } From 4bcc3ca82883588cd152a1d8efa58e5a60711b76 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 25 Sep 2020 22:16:14 +0900 Subject: [PATCH 1258/1782] Add AffectsAccuracy extension --- osu.Game/Rulesets/Scoring/HitResult.cs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/osu.Game/Rulesets/Scoring/HitResult.cs b/osu.Game/Rulesets/Scoring/HitResult.cs index 370ffb3a7f..1de62cf8e5 100644 --- a/osu.Game/Rulesets/Scoring/HitResult.cs +++ b/osu.Game/Rulesets/Scoring/HitResult.cs @@ -100,7 +100,7 @@ namespace osu.Game.Rulesets.Scoring public static class HitResultExtensions { /// - /// Whether a affects the combo. + /// Whether a increases/decreases the combo, and affects the combo portion of the score. /// public static bool AffectsCombo(this HitResult result) { @@ -122,12 +122,14 @@ namespace osu.Game.Rulesets.Scoring } /// - /// Whether a should be counted as combo score. + /// Whether a affects the accuracy portion of the score. + /// + public static bool AffectsAccuracy(this HitResult result) + => IsScorable(result) && !IsBonus(result); + + /// + /// Whether a should be counted as bonus score. /// - /// - /// This is not the reciprocal of , as and do not affect combo - /// but are still considered as part of the accuracy (not bonus) portion of the score. - /// public static bool IsBonus(this HitResult result) { switch (result) From 9a24346a008ed8c730bea94adb9271324c5a79a7 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 25 Sep 2020 23:29:40 +0900 Subject: [PATCH 1259/1782] Fix HP drain edgecase potentially causing insta-fails --- .../TestSceneDrainingHealthProcessor.cs | 39 +++++++++++++++++++ .../Scoring/DrainingHealthProcessor.cs | 2 +- 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Gameplay/TestSceneDrainingHealthProcessor.cs b/osu.Game.Tests/Gameplay/TestSceneDrainingHealthProcessor.cs index 460ad1b898..4876d051aa 100644 --- a/osu.Game.Tests/Gameplay/TestSceneDrainingHealthProcessor.cs +++ b/osu.Game.Tests/Gameplay/TestSceneDrainingHealthProcessor.cs @@ -1,15 +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.Threading; using NUnit.Framework; using osu.Framework.Graphics; using osu.Framework.Utils; using osu.Framework.Testing; using osu.Framework.Timing; using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.Timing; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Scoring; using osu.Game.Tests.Visual; @@ -175,6 +178,24 @@ namespace osu.Game.Tests.Gameplay assertHealthNotEqualTo(0); } + [Test] + public void TestSingleLongObjectDoesNotDrain() + { + var beatmap = new Beatmap + { + HitObjects = { new JudgeableLongHitObject() } + }; + + beatmap.HitObjects[0].ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); + + createProcessor(beatmap); + setTime(0); + assertHealthEqualTo(1); + + setTime(5000); + assertHealthEqualTo(1); + } + private Beatmap createBeatmap(double startTime, double endTime, params BreakPeriod[] breaks) { var beatmap = new Beatmap @@ -235,5 +256,23 @@ namespace osu.Game.Tests.Gameplay } } } + + private class JudgeableLongHitObject : JudgeableHitObject, IHasDuration + { + public double EndTime => StartTime + Duration; + public double Duration { get; set; } = 5000; + + public JudgeableLongHitObject() + : base(false) + { + } + + protected override void CreateNestedHitObjects(CancellationToken cancellationToken) + { + base.CreateNestedHitObjects(cancellationToken); + + AddNested(new JudgeableHitObject()); + } + } } } diff --git a/osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs b/osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs index 130907b242..ba9bbb055f 100644 --- a/osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs +++ b/osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs @@ -133,7 +133,7 @@ namespace osu.Game.Rulesets.Scoring private double computeDrainRate() { - if (healthIncreases.Count == 0) + if (healthIncreases.Count <= 1) return 0; int adjustment = 1; From e7d04564545cb4fe87d04add0792d6d14c1284c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Max=20H=C3=BCbner?= Date: Sat, 26 Sep 2020 16:25:17 +0200 Subject: [PATCH 1260/1782] Add SpinnerNoBlink to LegacySettings --- osu.Game/Skinning/LegacySkinConfiguration.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Skinning/LegacySkinConfiguration.cs b/osu.Game/Skinning/LegacySkinConfiguration.cs index 1d5412d93f..a0e8fb2f92 100644 --- a/osu.Game/Skinning/LegacySkinConfiguration.cs +++ b/osu.Game/Skinning/LegacySkinConfiguration.cs @@ -19,6 +19,7 @@ namespace osu.Game.Skinning ComboOverlap, AnimationFramerate, LayeredHitSounds, + SpinnerNoBlink } } } From 33d000e5325af0ca813c9c26de761761332f51b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Max=20H=C3=BCbner?= Date: Sat, 26 Sep 2020 16:25:57 +0200 Subject: [PATCH 1261/1782] Add support for SpinnerNoBlink in legacy spinner --- .../Skinning/LegacyOldStyleSpinner.cs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacyOldStyleSpinner.cs b/osu.Game.Rulesets.Osu/Skinning/LegacyOldStyleSpinner.cs index e157842fd1..ada3a825d0 100644 --- a/osu.Game.Rulesets.Osu/Skinning/LegacyOldStyleSpinner.cs +++ b/osu.Game.Rulesets.Osu/Skinning/LegacyOldStyleSpinner.cs @@ -12,6 +12,7 @@ using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Skinning; using osuTK; +using static osu.Game.Skinning.LegacySkinConfiguration; namespace osu.Game.Rulesets.Osu.Skinning { @@ -25,12 +26,16 @@ namespace osu.Game.Rulesets.Osu.Skinning private Sprite metreSprite; private Container metre; + private bool spinnerNoBlink; + private const float sprite_scale = 1 / 1.6f; private const float final_metre_height = 692 * sprite_scale; [BackgroundDependencyLoader] private void load(ISkinSource source, DrawableHitObject drawableObject) { + spinnerNoBlink = source.GetConfig(LegacySetting.SpinnerNoBlink)?.Value ?? false; + drawableSpinner = (DrawableSpinner)drawableObject; RelativeSizeAxes = Axes.Both; @@ -117,12 +122,15 @@ namespace osu.Game.Rulesets.Osu.Skinning private float getMetreHeight(float progress) { - progress = Math.Min(99, progress * 100); + progress *= 100; + + // the spinner should still blink at 100% progress. + if (!spinnerNoBlink) + progress = Math.Min(99, progress); int barCount = (int)progress / 10; - // todo: add SpinnerNoBlink support - if (RNG.NextBool(((int)progress % 10) / 10f)) + if (!spinnerNoBlink && RNG.NextBool(((int)progress % 10) / 10f)) barCount++; return (float)barCount / total_bars * final_metre_height; From b64e69fabd46f83e64853e8630ac06090032f904 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 26 Sep 2020 17:18:50 +0200 Subject: [PATCH 1262/1782] Add test hits to playfields directly where possible --- .../Skinning/TestSceneDrawableTaikoMascot.cs | 2 +- osu.Game.Rulesets.Taiko.Tests/TestSceneHits.cs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableTaikoMascot.cs b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableTaikoMascot.cs index 47d8a5c012..36289bda93 100644 --- a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableTaikoMascot.cs +++ b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableTaikoMascot.cs @@ -212,7 +212,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning foreach (var playfield in playfields) { var hit = new DrawableTestHit(new Hit(), judgementResult.Type); - Add(hit); + playfield.Add(hit); playfield.OnNewResult(hit, judgementResult); } diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneHits.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneHits.cs index 99d1b72ea4..9fc07340c6 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TestSceneHits.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneHits.cs @@ -149,7 +149,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) }; - Add(h); + drawableRuleset.Playfield.Add(h); ((TaikoPlayfield)drawableRuleset.Playfield).OnNewResult(h, new JudgementResult(new HitObject(), new TaikoJudgement()) { Type = hitResult }); } @@ -166,7 +166,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) }; - Add(h); + drawableRuleset.Playfield.Add(h); ((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 }); @@ -175,7 +175,7 @@ namespace osu.Game.Rulesets.Taiko.Tests private void addMissJudgement() { DrawableTestHit h; - Add(h = new DrawableTestHit(new Hit(), HitResult.Miss)); + drawableRuleset.Playfield.Add(h = new DrawableTestHit(new Hit(), HitResult.Miss)); ((TaikoPlayfield)drawableRuleset.Playfield).OnNewResult(h, new JudgementResult(new HitObject(), new TaikoJudgement()) { Type = HitResult.Miss }); } From 095686a320823d80be303f2d6028a0da1bf4bb8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 26 Sep 2020 17:26:26 +0200 Subject: [PATCH 1263/1782] Hide test hit directly in explosion scene --- osu.Game.Rulesets.Taiko.Tests/DrawableTestHit.cs | 6 ++++++ .../Skinning/TestSceneHitExplosion.cs | 4 +++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko.Tests/DrawableTestHit.cs b/osu.Game.Rulesets.Taiko.Tests/DrawableTestHit.cs index e0af973b53..4eeb4a1475 100644 --- a/osu.Game.Rulesets.Taiko.Tests/DrawableTestHit.cs +++ b/osu.Game.Rulesets.Taiko.Tests/DrawableTestHit.cs @@ -22,6 +22,12 @@ namespace osu.Game.Rulesets.Taiko.Tests HitObject.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); } + protected override void UpdateInitialTransforms() + { + // base implementation in DrawableHitObject forces alpha to 1. + // suppress locally to allow hiding the visuals wherever necessary. + } + [BackgroundDependencyLoader] private void load() { diff --git a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneHitExplosion.cs b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneHitExplosion.cs index 19cc56527e..45c94a8a86 100644 --- a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneHitExplosion.cs +++ b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneHitExplosion.cs @@ -35,7 +35,9 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning RelativeSizeAxes = Axes.Both, Children = new Drawable[] { - hit, + // the hit needs to be added to hierarchy in order for nested objects to be created correctly. + // setting zero alpha is supposed to prevent the test from looking broken. + hit.With(h => h.Alpha = 0), new HitExplosion(hit, hit.Type) { Anchor = Anchor.Centre, From b1e02db8742e0510d88c68cbe0e15823abee86e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 26 Sep 2020 20:36:38 +0200 Subject: [PATCH 1264/1782] Extract base taiko drawable ruleset scene --- .../DrawableTaikoRulesetTestScene.cs | 55 ++++++++++++++ .../TestSceneHits.cs | 74 +++++-------------- 2 files changed, 75 insertions(+), 54 deletions(-) create mode 100644 osu.Game.Rulesets.Taiko.Tests/DrawableTaikoRulesetTestScene.cs diff --git a/osu.Game.Rulesets.Taiko.Tests/DrawableTaikoRulesetTestScene.cs b/osu.Game.Rulesets.Taiko.Tests/DrawableTaikoRulesetTestScene.cs new file mode 100644 index 0000000000..d1c4a1c56d --- /dev/null +++ b/osu.Game.Rulesets.Taiko.Tests/DrawableTaikoRulesetTestScene.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.Collections.Generic; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Taiko.Objects; +using osu.Game.Rulesets.Taiko.UI; +using osu.Game.Tests.Visual; + +namespace osu.Game.Rulesets.Taiko.Tests +{ + public abstract class DrawableTaikoRulesetTestScene : OsuTestScene + { + protected DrawableTaikoRuleset DrawableRuleset { get; private set; } + protected Container PlayfieldContainer { get; private set; } + + [BackgroundDependencyLoader] + private void load() + { + var controlPointInfo = new ControlPointInfo(); + controlPointInfo.Add(0, new TimingControlPoint()); + + WorkingBeatmap beatmap = CreateWorkingBeatmap(new Beatmap + { + HitObjects = new List { new Hit { Type = HitType.Centre } }, + BeatmapInfo = new BeatmapInfo + { + BaseDifficulty = new BeatmapDifficulty(), + Metadata = new BeatmapMetadata + { + Artist = @"Unknown", + Title = @"Sample Beatmap", + AuthorString = @"peppy", + }, + Ruleset = new TaikoRuleset().RulesetInfo + }, + ControlPointInfo = controlPointInfo + }); + + Add(PlayfieldContainer = new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.X, + Height = 768, + Children = new[] { DrawableRuleset = new DrawableTaikoRuleset(new TaikoRuleset(), beatmap.GetPlayableBeatmap(new TaikoRuleset().RulesetInfo)) } + }); + } + } +} diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneHits.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneHits.cs index 9fc07340c6..b6cfe368f7 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TestSceneHits.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneHits.cs @@ -2,11 +2,9 @@ // 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.Utils; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; @@ -18,13 +16,12 @@ 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; namespace osu.Game.Rulesets.Taiko.Tests { [TestFixture] - public class TestSceneHits : OsuTestScene + public class TestSceneHits : DrawableTaikoRulesetTestScene { private const double default_duration = 3000; private const float scroll_time = 1000; @@ -32,8 +29,6 @@ namespace osu.Game.Rulesets.Taiko.Tests protected override double TimePerAction => default_duration * 2; private readonly Random rng = new Random(1337); - private DrawableTaikoRuleset drawableRuleset; - private Container playfieldContainer; [BackgroundDependencyLoader] private void load() @@ -64,35 +59,6 @@ namespace osu.Game.Rulesets.Taiko.Tests AddStep("Height test 4", () => changePlayfieldSize(4)); AddStep("Height test 5", () => changePlayfieldSize(5)); AddStep("Reset height", () => changePlayfieldSize(6)); - - var controlPointInfo = new ControlPointInfo(); - controlPointInfo.Add(0, new TimingControlPoint()); - - WorkingBeatmap beatmap = CreateWorkingBeatmap(new Beatmap - { - HitObjects = new List { new Hit { Type = HitType.Centre } }, - BeatmapInfo = new BeatmapInfo - { - BaseDifficulty = new BeatmapDifficulty(), - Metadata = new BeatmapMetadata - { - Artist = @"Unknown", - Title = @"Sample Beatmap", - AuthorString = @"peppy", - }, - Ruleset = new TaikoRuleset().RulesetInfo - }, - ControlPointInfo = controlPointInfo - }); - - Add(playfieldContainer = new Container - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.X, - Height = 768, - Children = new[] { drawableRuleset = new DrawableTaikoRuleset(new TaikoRuleset(), beatmap.GetPlayableBeatmap(new TaikoRuleset().RulesetInfo)) } - }); } private void changePlayfieldSize(int step) @@ -128,11 +94,11 @@ namespace osu.Game.Rulesets.Taiko.Tests switch (step) { default: - playfieldContainer.Delay(delay).ResizeTo(new Vector2(1, rng.Next(25, 400)), 500); + PlayfieldContainer.Delay(delay).ResizeTo(new Vector2(1, rng.Next(25, 400)), 500); break; case 6: - playfieldContainer.Delay(delay).ResizeTo(new Vector2(1, TaikoPlayfield.DEFAULT_HEIGHT), 500); + PlayfieldContainer.Delay(delay).ResizeTo(new Vector2(1, TaikoPlayfield.DEFAULT_HEIGHT), 500); break; } } @@ -149,9 +115,9 @@ 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) }; - drawableRuleset.Playfield.Add(h); + DrawableRuleset.Playfield.Add(h); - ((TaikoPlayfield)drawableRuleset.Playfield).OnNewResult(h, new JudgementResult(new HitObject(), new TaikoJudgement()) { Type = hitResult }); + ((TaikoPlayfield)DrawableRuleset.Playfield).OnNewResult(h, new JudgementResult(new HitObject(), new TaikoJudgement()) { Type = hitResult }); } private void addStrongHitJudgement(bool kiai) @@ -166,37 +132,37 @@ 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) }; - drawableRuleset.Playfield.Add(h); + DrawableRuleset.Playfield.Add(h); - ((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 }); + ((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() { DrawableTestHit h; - drawableRuleset.Playfield.Add(h = new DrawableTestHit(new Hit(), HitResult.Miss)); - ((TaikoPlayfield)drawableRuleset.Playfield).OnNewResult(h, new JudgementResult(new HitObject(), new TaikoJudgement()) { Type = HitResult.Miss }); + DrawableRuleset.Playfield.Add(h = new DrawableTestHit(new Hit(), HitResult.Miss)); + ((TaikoPlayfield)DrawableRuleset.Playfield).OnNewResult(h, new JudgementResult(new HitObject(), new TaikoJudgement()) { Type = HitResult.Miss }); } private void addBarLine(bool major, double delay = scroll_time) { - BarLine bl = new BarLine { StartTime = drawableRuleset.Playfield.Time.Current + delay }; + BarLine bl = new BarLine { StartTime = DrawableRuleset.Playfield.Time.Current + delay }; - drawableRuleset.Playfield.Add(major ? new DrawableBarLineMajor(bl) : new DrawableBarLine(bl)); + DrawableRuleset.Playfield.Add(major ? new DrawableBarLineMajor(bl) : new DrawableBarLine(bl)); } private void addSwell(double duration = default_duration) { var swell = new Swell { - StartTime = drawableRuleset.Playfield.Time.Current + scroll_time, + StartTime = DrawableRuleset.Playfield.Time.Current + scroll_time, Duration = duration, }; swell.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); - drawableRuleset.Playfield.Add(new DrawableSwell(swell)); + DrawableRuleset.Playfield.Add(new DrawableSwell(swell)); } private void addDrumRoll(bool strong, double duration = default_duration, bool kiai = false) @@ -206,7 +172,7 @@ namespace osu.Game.Rulesets.Taiko.Tests var d = new DrumRoll { - StartTime = drawableRuleset.Playfield.Time.Current + scroll_time, + StartTime = DrawableRuleset.Playfield.Time.Current + scroll_time, IsStrong = strong, Duration = duration, TickRate = 8, @@ -217,33 +183,33 @@ namespace osu.Game.Rulesets.Taiko.Tests d.ApplyDefaults(cpi, new BeatmapDifficulty()); - drawableRuleset.Playfield.Add(new DrawableDrumRoll(d)); + DrawableRuleset.Playfield.Add(new DrawableDrumRoll(d)); } private void addCentreHit(bool strong) { Hit h = new Hit { - StartTime = drawableRuleset.Playfield.Time.Current + scroll_time, + StartTime = DrawableRuleset.Playfield.Time.Current + scroll_time, IsStrong = strong }; h.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); - drawableRuleset.Playfield.Add(new DrawableHit(h)); + DrawableRuleset.Playfield.Add(new DrawableHit(h)); } private void addRimHit(bool strong) { Hit h = new Hit { - StartTime = drawableRuleset.Playfield.Time.Current + scroll_time, + StartTime = DrawableRuleset.Playfield.Time.Current + scroll_time, IsStrong = strong }; h.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); - drawableRuleset.Playfield.Add(new DrawableHit(h)); + DrawableRuleset.Playfield.Add(new DrawableHit(h)); } private class TestStrongNestedHit : DrawableStrongNestedHit From 0563a488f479083260753908b2ca8c140d3d98b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 26 Sep 2020 20:37:23 +0200 Subject: [PATCH 1265/1782] Add failing test case --- .../TestSceneFlyingHits.cs | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 osu.Game.Rulesets.Taiko.Tests/TestSceneFlyingHits.cs diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneFlyingHits.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneFlyingHits.cs new file mode 100644 index 0000000000..7492a76a67 --- /dev/null +++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneFlyingHits.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 System.Linq; +using NUnit.Framework; +using osu.Framework.Testing; +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.Rulesets.Taiko.Objects.Drawables; +using osu.Game.Rulesets.Taiko.UI; + +namespace osu.Game.Rulesets.Taiko.Tests +{ + [TestFixture] + public class TestSceneFlyingHits : DrawableTaikoRulesetTestScene + { + [TestCase(HitType.Centre)] + [TestCase(HitType.Rim)] + public void TestFlyingHits(HitType hitType) + { + DrawableFlyingHit flyingHit = null; + + AddStep("add flying hit", () => + { + addFlyingHit(hitType); + + // flying hits all land in one common scrolling container (and stay there for rewind purposes), + // so we need to manually get the latest one. + flyingHit = this.ChildrenOfType() + .OrderByDescending(h => h.HitObject.StartTime) + .FirstOrDefault(); + }); + + AddAssert("hit type is correct", () => flyingHit.HitObject.Type == hitType); + } + + private void addFlyingHit(HitType hitType) + { + var tick = new DrumRollTick { HitWindows = HitWindows.Empty, StartTime = DrawableRuleset.Playfield.Time.Current }; + + DrawableDrumRollTick h; + DrawableRuleset.Playfield.Add(h = new DrawableDrumRollTick(tick) { JudgementType = hitType }); + ((TaikoPlayfield)DrawableRuleset.Playfield).OnNewResult(h, new JudgementResult(tick, new TaikoDrumRollTickJudgement()) { Type = HitResult.Perfect }); + } + } +} From d61a8327da94243b65d7761ee23d9a5f4649a5fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 26 Sep 2020 20:59:55 +0200 Subject: [PATCH 1266/1782] Fix rim flying hits changing colour --- .../Objects/Drawables/DrawableFlyingHit.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableFlyingHit.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableFlyingHit.cs index 460e760629..3253c1ce5a 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableFlyingHit.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableFlyingHit.cs @@ -27,5 +27,11 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables base.LoadComplete(); ApplyResult(r => r.Type = r.Judgement.MaxResult); } + + protected override void LoadSamples() + { + // block base call - flying hits are not supposed to play samples + // the base call could overwrite the type of this hit + } } } From 00aea7748970cb3d29bde9f7a171d6ba5a545480 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 27 Sep 2020 11:18:13 +0200 Subject: [PATCH 1267/1782] Fix potential instability of overlay activation tests --- .../Visual/Gameplay/TestSceneOverlayActivation.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneOverlayActivation.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneOverlayActivation.cs index 3ee0f4e720..e36cc6861d 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneOverlayActivation.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneOverlayActivation.cs @@ -12,6 +12,14 @@ namespace osu.Game.Tests.Visual.Gameplay { protected new OverlayTestPlayer Player => base.Player as OverlayTestPlayer; + public override void SetUpSteps() + { + base.SetUpSteps(); + + AddUntilStep("gameplay has started", + () => Player.GameplayClockContainer.GameplayClock.CurrentTime > Player.DrawableRuleset.GameplayStartTime); + } + [Test] public void TestGameplayOverlayActivation() { From 8d9945dea895ab03f7fcb9562a1d533ebc502d81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 27 Sep 2020 11:28:20 +0200 Subject: [PATCH 1268/1782] Change until step to assert for consistency --- osu.Game.Tests/Visual/Gameplay/TestSceneOverlayActivation.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneOverlayActivation.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneOverlayActivation.cs index e36cc6861d..ce04b940e7 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneOverlayActivation.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneOverlayActivation.cs @@ -29,7 +29,7 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestGameplayOverlayActivationPaused() { - AddUntilStep("activation mode is disabled", () => Player.OverlayActivationMode == OverlayActivation.Disabled); + AddAssert("activation mode is disabled", () => Player.OverlayActivationMode == OverlayActivation.Disabled); AddStep("pause gameplay", () => Player.Pause()); AddUntilStep("activation mode is user triggered", () => Player.OverlayActivationMode == OverlayActivation.UserTriggered); } From deb207001a8fc34feea461fd6cc6d7f275b0df27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 27 Sep 2020 15:23:34 +0200 Subject: [PATCH 1269/1782] Remove schedule causing default skin explosion regression --- osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs index 7b3fbb1faf..120cf264c3 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs @@ -215,16 +215,12 @@ namespace osu.Game.Rulesets.Taiko.UI private void addDrumRollHit(DrawableDrumRollTick drawableTick) => drumRollHitContainer.Add(new DrawableFlyingHit(drawableTick)); - /// - /// As legacy skins have different explosions for singular and double strong hits, - /// explosion addition is scheduled to ensure that both hits are processed if they occur on the same frame. - /// - private void addExplosion(DrawableHitObject drawableObject, HitResult result, HitType type) => Schedule(() => + private void addExplosion(DrawableHitObject drawableObject, HitResult result, HitType type) { hitExplosionContainer.Add(new HitExplosion(drawableObject, result)); if (drawableObject.HitObject.Kiai) kiaiExplosionContainer.Add(new KiaiHitExplosion(drawableObject, type)); - }); + } private class ProxyContainer : LifetimeManagementContainer { From d5f1d94b517703f202bfcf6dfe695eb46ee02f33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 27 Sep 2020 15:29:04 +0200 Subject: [PATCH 1270/1782] Allow specifying two sprites for legacy hit explosions --- .../Skinning/LegacyHitExplosion.cs | 29 +++++++++++++++++-- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Skinning/LegacyHitExplosion.cs b/osu.Game.Rulesets.Taiko/Skinning/LegacyHitExplosion.cs index b5ec2e8def..ca0a8f601c 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/LegacyHitExplosion.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/LegacyHitExplosion.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; @@ -8,14 +9,36 @@ namespace osu.Game.Rulesets.Taiko.Skinning { public class LegacyHitExplosion : CompositeDrawable { - public LegacyHitExplosion(Drawable sprite) - { - InternalChild = sprite; + private readonly Drawable sprite; + private readonly Drawable strongSprite; + /// + /// Creates a new legacy hit explosion. + /// + /// + /// Contrary to stable's, this implementation doesn't require a frame-perfect hit + /// for the strong sprite to be displayed. + /// + /// The normal legacy explosion sprite. + /// The strong legacy explosion sprite. + public LegacyHitExplosion(Drawable sprite, Drawable strongSprite = null) + { + this.sprite = sprite; + this.strongSprite = strongSprite; + } + + [BackgroundDependencyLoader] + private void load() + { Anchor = Anchor.Centre; Origin = Anchor.Centre; AutoSizeAxes = Axes.Both; + + AddInternal(sprite); + + if (strongSprite != null) + AddInternal(strongSprite.With(s => s.Alpha = 0)); } protected override void LoadComplete() From eb62ad4e551e72c3642ba56db21c954a4c850c19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 27 Sep 2020 15:33:18 +0200 Subject: [PATCH 1271/1782] Look up both sprites for legacy explosions --- .../Skinning/TaikoLegacySkinTransformer.cs | 30 ++++++++++--------- .../TaikoSkinComponents.cs | 2 -- osu.Game.Rulesets.Taiko/UI/HitExplosion.cs | 27 ++++------------- 3 files changed, 21 insertions(+), 38 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacySkinTransformer.cs b/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacySkinTransformer.cs index c222ccb51f..d320b824e6 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacySkinTransformer.cs @@ -74,15 +74,23 @@ namespace osu.Game.Rulesets.Taiko.Skinning return null; - case TaikoSkinComponents.TaikoExplosionGood: - case TaikoSkinComponents.TaikoExplosionGoodStrong: - case TaikoSkinComponents.TaikoExplosionGreat: - case TaikoSkinComponents.TaikoExplosionGreatStrong: case TaikoSkinComponents.TaikoExplosionMiss: - var sprite = this.GetAnimation(getHitName(taikoComponent.Component), true, false); - if (sprite != null) - return new LegacyHitExplosion(sprite); + var missSprite = this.GetAnimation(getHitName(taikoComponent.Component), true, false); + if (missSprite != null) + return new LegacyHitExplosion(missSprite); + + return null; + + case TaikoSkinComponents.TaikoExplosionGood: + case TaikoSkinComponents.TaikoExplosionGreat: + + var hitName = getHitName(taikoComponent.Component); + var hitSprite = this.GetAnimation(hitName, true, false); + var strongHitSprite = this.GetAnimation($"{hitName}k", true, false); + + if (hitSprite != null) + return new LegacyHitExplosion(hitSprite, strongHitSprite); return null; @@ -109,17 +117,11 @@ namespace osu.Game.Rulesets.Taiko.Skinning case TaikoSkinComponents.TaikoExplosionGood: return "taiko-hit100"; - case TaikoSkinComponents.TaikoExplosionGoodStrong: - return "taiko-hit100k"; - case TaikoSkinComponents.TaikoExplosionGreat: return "taiko-hit300"; - - case TaikoSkinComponents.TaikoExplosionGreatStrong: - return "taiko-hit300k"; } - throw new ArgumentOutOfRangeException(nameof(component), "Invalid result type"); + throw new ArgumentOutOfRangeException(nameof(component), $"Invalid component type: {component}"); } public override SampleChannel GetSample(ISampleInfo sampleInfo) => Source.GetSample(new LegacyTaikoSampleInfo(sampleInfo)); diff --git a/osu.Game.Rulesets.Taiko/TaikoSkinComponents.cs b/osu.Game.Rulesets.Taiko/TaikoSkinComponents.cs index 0d785adb4a..ac4fb51661 100644 --- a/osu.Game.Rulesets.Taiko/TaikoSkinComponents.cs +++ b/osu.Game.Rulesets.Taiko/TaikoSkinComponents.cs @@ -17,9 +17,7 @@ namespace osu.Game.Rulesets.Taiko BarLine, TaikoExplosionMiss, TaikoExplosionGood, - TaikoExplosionGoodStrong, TaikoExplosionGreat, - TaikoExplosionGreatStrong, Scroller, Mascot, } diff --git a/osu.Game.Rulesets.Taiko/UI/HitExplosion.cs b/osu.Game.Rulesets.Taiko/UI/HitExplosion.cs index efd1b25046..53765f04dd 100644 --- a/osu.Game.Rulesets.Taiko/UI/HitExplosion.cs +++ b/osu.Game.Rulesets.Taiko/UI/HitExplosion.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Linq; using osuTK; using osu.Framework.Allocation; using osu.Framework.Graphics; @@ -10,7 +9,6 @@ using osu.Framework.Graphics.Containers; 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.Skinning; namespace osu.Game.Rulesets.Taiko.UI @@ -50,10 +48,10 @@ namespace osu.Game.Rulesets.Taiko.UI [BackgroundDependencyLoader] private void load() { - Child = skinnable = new SkinnableDrawable(new TaikoSkinComponent(getComponentName(JudgedObject)), _ => new DefaultHitExplosion(JudgedObject, result)); + Child = skinnable = new SkinnableDrawable(new TaikoSkinComponent(getComponentName(result)), _ => new DefaultHitExplosion(JudgedObject, result)); } - private TaikoSkinComponents getComponentName(DrawableHitObject judgedObject) + private static TaikoSkinComponents getComponentName(HitResult result) { switch (result) { @@ -61,28 +59,13 @@ namespace osu.Game.Rulesets.Taiko.UI return TaikoSkinComponents.TaikoExplosionMiss; case HitResult.Good: - return useStrongExplosion(judgedObject) - ? TaikoSkinComponents.TaikoExplosionGoodStrong - : TaikoSkinComponents.TaikoExplosionGood; + return TaikoSkinComponents.TaikoExplosionGood; case HitResult.Great: - return useStrongExplosion(judgedObject) - ? TaikoSkinComponents.TaikoExplosionGreatStrong - : TaikoSkinComponents.TaikoExplosionGreat; + return TaikoSkinComponents.TaikoExplosionGreat; } - throw new ArgumentOutOfRangeException(nameof(judgedObject), "Invalid result type"); - } - - private bool useStrongExplosion(DrawableHitObject judgedObject) - { - if (!(judgedObject.HitObject is Hit)) - return false; - - if (!(judgedObject.NestedHitObjects.SingleOrDefault() is DrawableStrongNestedHit nestedHit)) - return false; - - return judgedObject.Result.Type == nestedHit.Result.Type; + throw new ArgumentOutOfRangeException(nameof(result), $"Invalid result type: {result}"); } /// From 2f7c0b49344127ea1ccbe286f021edc71285352b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 27 Sep 2020 16:07:19 +0200 Subject: [PATCH 1272/1782] Allow switching between legacy sprites --- .../Skinning/LegacyHitExplosion.cs | 33 ++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/Skinning/LegacyHitExplosion.cs b/osu.Game.Rulesets.Taiko/Skinning/LegacyHitExplosion.cs index ca0a8f601c..0f91239819 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/LegacyHitExplosion.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/LegacyHitExplosion.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.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Taiko.Objects.Drawables; namespace osu.Game.Rulesets.Taiko.Skinning { @@ -12,6 +15,9 @@ namespace osu.Game.Rulesets.Taiko.Skinning private readonly Drawable sprite; private readonly Drawable strongSprite; + private DrawableHit hit; + private DrawableStrongNestedHit nestedStrongHit; + /// /// Creates a new legacy hit explosion. /// @@ -28,7 +34,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning } [BackgroundDependencyLoader] - private void load() + private void load(DrawableHitObject judgedObject) { Anchor = Anchor.Centre; Origin = Anchor.Centre; @@ -39,6 +45,12 @@ namespace osu.Game.Rulesets.Taiko.Skinning if (strongSprite != null) AddInternal(strongSprite.With(s => s.Alpha = 0)); + + if (judgedObject is DrawableHit h) + { + hit = h; + nestedStrongHit = hit.NestedHitObjects.SingleOrDefault() as DrawableStrongNestedHit; + } } protected override void LoadComplete() @@ -56,5 +68,24 @@ namespace osu.Game.Rulesets.Taiko.Skinning Expire(true); } + + protected override void Update() + { + base.Update(); + + if (shouldSwitchToStrongSprite() && strongSprite != null) + { + sprite.FadeOut(50, Easing.OutQuint); + strongSprite.FadeIn(50, Easing.OutQuint); + } + } + + private bool shouldSwitchToStrongSprite() + { + if (hit == null || nestedStrongHit == null) + return false; + + return hit.Result.Type == nestedStrongHit.Result.Type; + } } } From 49441286311c0c052a1100c13324b9c5b5f90f09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 27 Sep 2020 18:11:12 +0200 Subject: [PATCH 1273/1782] Ensure both sprites are centered --- .../Skinning/LegacyHitExplosion.cs | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Skinning/LegacyHitExplosion.cs b/osu.Game.Rulesets.Taiko/Skinning/LegacyHitExplosion.cs index 0f91239819..f24f75f097 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/LegacyHitExplosion.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/LegacyHitExplosion.cs @@ -41,10 +41,21 @@ namespace osu.Game.Rulesets.Taiko.Skinning AutoSizeAxes = Axes.Both; - AddInternal(sprite); + AddInternal(sprite.With(s => + { + s.Anchor = Anchor.Centre; + s.Origin = Anchor.Centre; + })); if (strongSprite != null) - AddInternal(strongSprite.With(s => s.Alpha = 0)); + { + AddInternal(strongSprite.With(s => + { + s.Alpha = 0; + s.Anchor = Anchor.Centre; + s.Origin = Anchor.Centre; + })); + } if (judgedObject is DrawableHit h) { From 3cf430f49434ed3883ca1c5dc06844ca3d287327 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 28 Sep 2020 15:30:18 +0900 Subject: [PATCH 1274/1782] Avoid saving state changes if nothing has changed (via binary comparison) --- .../Editing/EditorChangeHandlerTest.cs | 57 ++++++++++++++++++- osu.Game/Screens/Edit/EditorChangeHandler.cs | 28 +++++---- 2 files changed, 71 insertions(+), 14 deletions(-) diff --git a/osu.Game.Tests/Editing/EditorChangeHandlerTest.cs b/osu.Game.Tests/Editing/EditorChangeHandlerTest.cs index feda1ae0e9..ff2c9fb1a9 100644 --- a/osu.Game.Tests/Editing/EditorChangeHandlerTest.cs +++ b/osu.Game.Tests/Editing/EditorChangeHandlerTest.cs @@ -2,7 +2,9 @@ // See the LICENCE file in the repository root for full licence text. using NUnit.Framework; +using osu.Framework.Utils; using osu.Game.Beatmaps; +using osu.Game.Rulesets.Osu.Objects; using osu.Game.Screens.Edit; namespace osu.Game.Tests.Editing @@ -13,11 +15,12 @@ namespace osu.Game.Tests.Editing [Test] public void TestSaveRestoreState() { - var handler = new EditorChangeHandler(new EditorBeatmap(new Beatmap())); + var (handler, beatmap) = createChangeHandler(); Assert.That(handler.CanUndo.Value, Is.False); Assert.That(handler.CanRedo.Value, Is.False); + addArbitraryChange(beatmap); handler.SaveState(); Assert.That(handler.CanUndo.Value, Is.True); @@ -29,15 +32,48 @@ namespace osu.Game.Tests.Editing Assert.That(handler.CanRedo.Value, Is.True); } + [Test] + public void TestSaveSameStateDoesNotSave() + { + var (handler, beatmap) = createChangeHandler(); + + Assert.That(handler.CanUndo.Value, Is.False); + Assert.That(handler.CanRedo.Value, Is.False); + + addArbitraryChange(beatmap); + handler.SaveState(); + + Assert.That(handler.CanUndo.Value, Is.True); + Assert.That(handler.CanRedo.Value, Is.False); + + string hash = handler.CurrentStateHash; + + // save a save without making any changes + handler.SaveState(); + + Assert.That(hash, Is.EqualTo(handler.CurrentStateHash)); + + handler.RestoreState(-1); + + Assert.That(hash, Is.Not.EqualTo(handler.CurrentStateHash)); + + // we should only be able to restore once even though we saved twice. + Assert.That(handler.CanUndo.Value, Is.False); + Assert.That(handler.CanRedo.Value, Is.True); + } + [Test] public void TestMaxStatesSaved() { - var handler = new EditorChangeHandler(new EditorBeatmap(new Beatmap())); + var (handler, beatmap) = createChangeHandler(); Assert.That(handler.CanUndo.Value, Is.False); for (int i = 0; i < EditorChangeHandler.MAX_SAVED_STATES; i++) + { + addArbitraryChange(beatmap); handler.SaveState(); + } Assert.That(handler.CanUndo.Value, Is.True); @@ -53,12 +89,15 @@ namespace osu.Game.Tests.Editing [Test] public void TestMaxStatesExceeded() { - var handler = new EditorChangeHandler(new EditorBeatmap(new Beatmap())); + var (handler, beatmap) = createChangeHandler(); Assert.That(handler.CanUndo.Value, Is.False); for (int i = 0; i < EditorChangeHandler.MAX_SAVED_STATES * 2; i++) + { + addArbitraryChange(beatmap); handler.SaveState(); + } Assert.That(handler.CanUndo.Value, Is.True); @@ -70,5 +109,17 @@ namespace osu.Game.Tests.Editing Assert.That(handler.CanUndo.Value, Is.False); } + + private (EditorChangeHandler, EditorBeatmap) createChangeHandler() + { + var beatmap = new EditorBeatmap(new Beatmap()); + + return (new EditorChangeHandler(beatmap), beatmap); + } + + private void addArbitraryChange(EditorBeatmap beatmap) + { + beatmap.Add(new HitCircle { StartTime = RNG.Next(0, 100000) }); + } } } diff --git a/osu.Game/Screens/Edit/EditorChangeHandler.cs b/osu.Game/Screens/Edit/EditorChangeHandler.cs index aa0f89912a..286fdbb020 100644 --- a/osu.Game/Screens/Edit/EditorChangeHandler.cs +++ b/osu.Game/Screens/Edit/EditorChangeHandler.cs @@ -4,9 +4,11 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; using System.Text; using osu.Framework.Bindables; using osu.Framework.Extensions; +using osu.Framework.Logging; using osu.Game.Beatmaps.Formats; using osu.Game.Rulesets.Objects; @@ -89,23 +91,27 @@ namespace osu.Game.Screens.Edit if (isRestoring) return; - if (currentState < savedStates.Count - 1) - savedStates.RemoveRange(currentState + 1, savedStates.Count - currentState - 1); - - if (savedStates.Count > MAX_SAVED_STATES) - savedStates.RemoveAt(0); - using (var stream = new MemoryStream()) { using (var sw = new StreamWriter(stream, Encoding.UTF8, 1024, true)) new LegacyBeatmapEncoder(editorBeatmap, editorBeatmap.BeatmapSkin).Encode(sw); - savedStates.Add(stream.ToArray()); + var newState = stream.ToArray(); + + // if the previous state is binary equal we don't need to push a new one, unless this is the initial state. + if (savedStates.Count > 0 && newState.SequenceEqual(savedStates.Last())) return; + + if (currentState < savedStates.Count - 1) + savedStates.RemoveRange(currentState + 1, savedStates.Count - currentState - 1); + + if (savedStates.Count > MAX_SAVED_STATES) + savedStates.RemoveAt(0); + + savedStates.Add(newState); + + currentState = savedStates.Count - 1; + updateBindables(); } - - currentState = savedStates.Count - 1; - - updateBindables(); } /// From 1aa8b400d432b17031d4d5a37898dc0a3ac66d2c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 28 Sep 2020 14:45:36 +0900 Subject: [PATCH 1275/1782] Avoid unnecessary object updates from SelectionHandlers --- osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs | 8 +++++++- .../Screens/Edit/Compose/Components/SelectionHandler.cs | 3 +-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs b/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs index a3ecf7ed95..d5dd758e10 100644 --- a/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs +++ b/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs @@ -57,7 +57,13 @@ namespace osu.Game.Rulesets.Taiko.Edit ChangeHandler.BeginChange(); foreach (var h in hits) - h.IsStrong = state; + { + if (h.IsStrong != state) + { + h.IsStrong = state; + EditorBeatmap.UpdateHitObject(h); + } + } ChangeHandler.EndChange(); } diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index 6ca85fe026..a0220cf987 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -288,8 +288,7 @@ namespace osu.Game.Screens.Edit.Compose.Components { var comboInfo = h as IHasComboInformation; - if (comboInfo == null) - continue; + if (comboInfo == null || comboInfo.NewCombo == state) continue; comboInfo.NewCombo = state; EditorBeatmap?.UpdateHitObject(h); From 0ae2266b8229ba5c8192385230c4506b2bf3e5a7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 28 Sep 2020 14:51:28 +0900 Subject: [PATCH 1276/1782] Fix new placement hitobjects in the editor not getting the default sample added --- .../Edit/Compose/Components/ComposeBlueprintContainer.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs index 81d7fa4b32..9b3314e2ad 100644 --- a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs @@ -212,6 +212,9 @@ namespace osu.Game.Screens.Edit.Compose.Components if (blueprint != null) { + // doing this post-creations as adding the default hit sample should be the case regardless of the ruleset. + blueprint.HitObject.Samples.Add(new HitSampleInfo { Name = HitSampleInfo.HIT_NORMAL }); + placementBlueprintContainer.Child = currentPlacement = blueprint; // Fixes a 1-frame position discrepancy due to the first mouse move event happening in the next frame From a4e9c85333b06d293cc88a921ac38612490b1c04 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 28 Sep 2020 14:37:55 +0900 Subject: [PATCH 1277/1782] Trigger a hitobject update after blueprint drag ends --- .../Screens/Edit/Compose/Components/BlueprintContainer.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index 8908520cd7..970e16d1c3 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -201,6 +201,10 @@ namespace osu.Game.Screens.Edit.Compose.Components if (isDraggingBlueprint) { + // handle positional change etc. + foreach (var obj in selectedHitObjects) + Beatmap.UpdateHitObject(obj); + changeHandler?.EndChange(); isDraggingBlueprint = false; } From 6095446f10df0a34b1676aa1bf46deb9cceb7a26 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 28 Sep 2020 14:15:26 +0900 Subject: [PATCH 1278/1782] Fix autoplay generators failing on empty hitobjects lists --- osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs | 3 +++ osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs | 3 +++ osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs | 3 +++ osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs | 3 +++ 4 files changed, 12 insertions(+) diff --git a/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs b/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs index 5d11c574b1..a4f54bfe82 100644 --- a/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs +++ b/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs @@ -31,6 +31,9 @@ namespace osu.Game.Rulesets.Catch.Replays public override Replay Generate() { + if (Beatmap.HitObjects.Count == 0) + return Replay; + // todo: add support for HT DT const double dash_speed = Catcher.BASE_SPEED; const double movement_speed = dash_speed / 2; diff --git a/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs b/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs index 483327d5b3..3ebbe5af8e 100644 --- a/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs +++ b/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs @@ -46,6 +46,9 @@ namespace osu.Game.Rulesets.Mania.Replays public override Replay Generate() { + if (Beatmap.HitObjects.Count == 0) + return Replay; + var pointGroups = generateActionPoints().GroupBy(a => a.Time).OrderBy(g => g.First().Time); var actions = new List(); diff --git a/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs b/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs index 76b2631894..9b350278f3 100644 --- a/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs +++ b/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs @@ -72,6 +72,9 @@ namespace osu.Game.Rulesets.Osu.Replays public override Replay Generate() { + if (Beatmap.HitObjects.Count == 0) + return Replay; + buttonIndex = 0; AddFrameToReplay(new OsuReplayFrame(-100000, new Vector2(256, 500))); diff --git a/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs b/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs index 273f4e4105..db2e5948f5 100644 --- a/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs +++ b/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs @@ -30,6 +30,9 @@ namespace osu.Game.Rulesets.Taiko.Replays public override Replay Generate() { + if (Beatmap.HitObjects.Count == 0) + return Replay; + bool hitButton = true; Frames.Add(new TaikoReplayFrame(-100000)); From e8220cf1b62dd7fcdd2afa13bb39b53b8a3771a7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 28 Sep 2020 14:03:56 +0900 Subject: [PATCH 1279/1782] Allow attaching a replay to a FrameStabilityContainer when FrameStablePlayback is off --- .../Rulesets/UI/FrameStabilityContainer.cs | 72 ++++++++++++------- 1 file changed, 48 insertions(+), 24 deletions(-) diff --git a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs index d574991fa0..a4af92749f 100644 --- a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs +++ b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs @@ -123,39 +123,63 @@ namespace osu.Game.Rulesets.UI try { - if (!FrameStablePlayback) - return; - - if (firstConsumption) + if (FrameStablePlayback) { - // On the first update, frame-stability seeking would result in unexpected/unwanted behaviour. - // Instead we perform an initial seek to the proposed time. + 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. - // process frame (in addition to finally clause) to clear out ElapsedTime - manualClock.CurrentTime = newProposedTime; - framedClock.ProcessFrame(); + // process frame (in addition to finally clause) to clear out ElapsedTime + manualClock.CurrentTime = newProposedTime; + framedClock.ProcessFrame(); - firstConsumption = false; - } - else if (manualClock.CurrentTime < gameplayStartTime) - manualClock.CurrentTime = newProposedTime = Math.Min(gameplayStartTime, newProposedTime); - else if (Math.Abs(manualClock.CurrentTime - newProposedTime) > sixty_frame_time * 1.2f) - { - newProposedTime = newProposedTime > manualClock.CurrentTime - ? Math.Min(newProposedTime, manualClock.CurrentTime + sixty_frame_time) - : Math.Max(newProposedTime, manualClock.CurrentTime - sixty_frame_time); + firstConsumption = false; + } + else if (manualClock.CurrentTime < gameplayStartTime) + manualClock.CurrentTime = newProposedTime = Math.Min(gameplayStartTime, newProposedTime); + else if (Math.Abs(manualClock.CurrentTime - newProposedTime) > sixty_frame_time * 1.2f) + { + newProposedTime = newProposedTime > manualClock.CurrentTime + ? Math.Min(newProposedTime, manualClock.CurrentTime + sixty_frame_time) + : Math.Max(newProposedTime, manualClock.CurrentTime - sixty_frame_time); + } } if (isAttached) { - double? newTime = ReplayInputHandler.SetFrameFromTime(newProposedTime); + double? newTime; - if (newTime == null) + if (FrameStablePlayback) { - // we shouldn't execute for this time value. probably waiting on more replay data. - validState = false; - requireMoreUpdateLoops = true; - return; + // when stability is turned on, we shouldn't execute for time values the replay is unable to satisfy. + if ((newTime = ReplayInputHandler.SetFrameFromTime(newProposedTime)) == null) + { + // setting invalid state here ensures that gameplay will not continue (ie. our child + // hierarchy won't be updated). + validState = false; + + // potentially loop to catch-up playback. + requireMoreUpdateLoops = true; + + return; + } + } + else + { + // when stability is disabled, we don't really care about accuracy. + // looping over the replay will allow it to catch up and feed out the required values + // for the current time. + while ((newTime = ReplayInputHandler.SetFrameFromTime(newProposedTime)) != newProposedTime) + { + if (newTime == null) + { + // special case for when the replay actually can't arrive at the required time. + // protects from potential endless loop. + validState = false; + return; + } + } } newProposedTime = newTime.Value; From ff7c904996083e985dd41b389656b190f357b202 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 28 Sep 2020 14:03:37 +0900 Subject: [PATCH 1280/1782] Add autoplay mod in editor specific ruleset construction --- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index b9b7c1ef54..6e377ff207 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -76,7 +76,7 @@ namespace osu.Game.Rulesets.Edit try { - drawableRulesetWrapper = new DrawableEditRulesetWrapper(CreateDrawableRuleset(Ruleset, EditorBeatmap.PlayableBeatmap)) + drawableRulesetWrapper = new DrawableEditRulesetWrapper(CreateDrawableRuleset(Ruleset, EditorBeatmap.PlayableBeatmap, new[] { Ruleset.GetAutoplayMod() })) { Clock = EditorClock, ProcessCustomClock = false From 524c2b678c68b71d604342b32e5274fbb7684607 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 28 Sep 2020 14:15:54 +0900 Subject: [PATCH 1281/1782] Forcefully regenerate autoplay on editor changes --- .../Rulesets/Edit/DrawableEditRulesetWrapper.cs | 8 ++++++++ osu.Game/Rulesets/UI/DrawableRuleset.cs | 14 ++++++++++++-- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Edit/DrawableEditRulesetWrapper.cs b/osu.Game/Rulesets/Edit/DrawableEditRulesetWrapper.cs index 89e7866707..1070b8cbd2 100644 --- a/osu.Game/Rulesets/Edit/DrawableEditRulesetWrapper.cs +++ b/osu.Game/Rulesets/Edit/DrawableEditRulesetWrapper.cs @@ -45,15 +45,21 @@ namespace osu.Game.Rulesets.Edit base.LoadComplete(); beatmap.HitObjectAdded += addHitObject; + beatmap.HitObjectUpdated += updateReplay; beatmap.HitObjectRemoved += removeHitObject; } + private void updateReplay(HitObject obj = null) => + drawableRuleset.RegenerateAutoplay(); + private void addHitObject(HitObject hitObject) { var drawableObject = drawableRuleset.CreateDrawableRepresentation((TObject)hitObject); drawableRuleset.Playfield.Add(drawableObject); drawableRuleset.Playfield.PostProcess(); + + updateReplay(); } private void removeHitObject(HitObject hitObject) @@ -62,6 +68,8 @@ namespace osu.Game.Rulesets.Edit drawableRuleset.Playfield.Remove(drawableObject); drawableRuleset.Playfield.PostProcess(); + + drawableRuleset.RegenerateAutoplay(); } public override bool PropagatePositionalInputSubTree => false; diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index fbb9acfe90..50e9a93e22 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -151,8 +151,11 @@ namespace osu.Game.Rulesets.UI public virtual PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => new PlayfieldAdjustmentContainer(); + [Resolved] + private OsuConfigManager config { get; set; } + [BackgroundDependencyLoader] - private void load(OsuConfigManager config, CancellationToken? cancellationToken) + private void load(CancellationToken? cancellationToken) { InternalChildren = new Drawable[] { @@ -178,11 +181,18 @@ namespace osu.Game.Rulesets.UI .WithChild(ResumeOverlay))); } - applyRulesetMods(Mods, config); + RegenerateAutoplay(); loadObjects(cancellationToken); } + public void RegenerateAutoplay() + { + // for now this is applying mods which aren't just autoplay. + // we'll need to reconsider this flow in the future. + applyRulesetMods(Mods, config); + } + /// /// Creates and adds drawable representations of hit objects to the play field. /// From 7949eabaac45fe2a67ab5a4f283c8851e314300c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 28 Sep 2020 15:49:45 +0900 Subject: [PATCH 1282/1782] Remove left-over using --- osu.Game/Screens/Edit/EditorChangeHandler.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Screens/Edit/EditorChangeHandler.cs b/osu.Game/Screens/Edit/EditorChangeHandler.cs index 286fdbb020..617c436ee0 100644 --- a/osu.Game/Screens/Edit/EditorChangeHandler.cs +++ b/osu.Game/Screens/Edit/EditorChangeHandler.cs @@ -8,7 +8,6 @@ using System.Linq; using System.Text; using osu.Framework.Bindables; using osu.Framework.Extensions; -using osu.Framework.Logging; using osu.Game.Beatmaps.Formats; using osu.Game.Rulesets.Objects; From 467a16bf750600b0edc47674acd022334c632d89 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 28 Sep 2020 16:21:20 +0900 Subject: [PATCH 1283/1782] Fix fade out extension logic (and make it generally look better for sliders) --- .../Edit/DrawableOsuEditRuleset.cs | 42 +++++++++++++------ 1 file changed, 30 insertions(+), 12 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs b/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs index a8719e0aa8..01e59c9598 100644 --- a/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs +++ b/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs @@ -8,6 +8,7 @@ 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.Objects.Drawables; using osu.Game.Rulesets.Osu.UI; using osu.Game.Rulesets.UI; using osuTK; @@ -20,7 +21,7 @@ namespace osu.Game.Rulesets.Osu.Edit /// 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; + private const double editor_hit_object_fade_out_extension = 700; public DrawableOsuEditRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList mods) : base(ruleset, beatmap, mods) @@ -32,20 +33,37 @@ namespace osu.Game.Rulesets.Osu.Edit private void updateState(DrawableHitObject hitObject, ArmedState state) { - switch (state) + if (state == ArmedState.Idle) + return; + + // adjust the visuals of certain object types to make them stay on screen for longer than usual. + switch (hitObject) { - case ArmedState.Miss: - // Get the existing fade out transform - var existing = hitObject.Transforms.LastOrDefault(t => t.TargetMember == nameof(Alpha)); - if (existing == null) - return; - - hitObject.RemoveTransform(existing); - - using (hitObject.BeginAbsoluteSequence(existing.StartTime)) - hitObject.FadeOut(editor_hit_object_fade_out_extension).Expire(); + case DrawableSlider slider: + // no specifics to sliders but let them fade slower below. break; + + case DrawableHitCircle circle: // also handles slider heads + circle.ApproachCircle + .FadeOutFromOne(editor_hit_object_fade_out_extension) + .Expire(); + break; + + default: + // there are quite a few drawable hit types we don't want to extent (spinners, ticks etc.) + return; } + + // Get the existing fade out transform + var existing = hitObject.Transforms.LastOrDefault(t => t.TargetMember == nameof(Alpha)); + + if (existing == null) + return; + + hitObject.RemoveTransform(existing); + + using (hitObject.BeginAbsoluteSequence(existing.StartTime)) + hitObject.FadeOut(editor_hit_object_fade_out_extension).Expire(); } protected override Playfield CreatePlayfield() => new OsuPlayfieldNoCursor(); From 5237fa7bf24cdaca9e2a8c2e24bdceff5906ff84 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 28 Sep 2020 16:37:54 +0900 Subject: [PATCH 1284/1782] Remove unused local in case statement --- osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs b/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs index 01e59c9598..746ff4ac19 100644 --- a/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs +++ b/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs @@ -39,7 +39,11 @@ namespace osu.Game.Rulesets.Osu.Edit // adjust the visuals of certain object types to make them stay on screen for longer than usual. switch (hitObject) { - case DrawableSlider slider: + default: + // there are quite a few drawable hit types we don't want to extent (spinners, ticks etc.) + return; + + case DrawableSlider _: // no specifics to sliders but let them fade slower below. break; @@ -48,10 +52,6 @@ namespace osu.Game.Rulesets.Osu.Edit .FadeOutFromOne(editor_hit_object_fade_out_extension) .Expire(); break; - - default: - // there are quite a few drawable hit types we don't want to extent (spinners, ticks etc.) - return; } // Get the existing fade out transform From 8692c24dfc624f739d10209cafa173b125ff024d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 28 Sep 2020 17:20:36 +0900 Subject: [PATCH 1285/1782] Fix extending spinners in editor causing them to disappear temporarily --- .../Objects/Drawables/Pieces/DefaultSpinnerDisc.cs | 9 ++++----- osu.Game.Rulesets.Osu/Skinning/LegacyNewStyleSpinner.cs | 5 ++--- osu.Game.Rulesets.Osu/Skinning/LegacyOldStyleSpinner.cs | 5 ++--- 3 files changed, 8 insertions(+), 11 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/DefaultSpinnerDisc.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/DefaultSpinnerDisc.cs index 1476fe6010..e45ea9c6cc 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/DefaultSpinnerDisc.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/DefaultSpinnerDisc.cs @@ -3,7 +3,6 @@ using System; using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -93,7 +92,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces base.LoadComplete(); drawableSpinner.RotationTracker.Complete.BindValueChanged(complete => updateComplete(complete.NewValue, 200)); - drawableSpinner.State.BindValueChanged(updateStateTransforms, true); + drawableSpinner.ApplyCustomUpdateState += updateStateTransforms; } protected override void Update() @@ -123,7 +122,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces mainContainer.Rotation = drawableSpinner.RotationTracker.Rotation; } - private void updateStateTransforms(ValueChangedEvent state) + private void updateStateTransforms(DrawableHitObject drawableHitObject, ArmedState state) { centre.ScaleTo(0); mainContainer.ScaleTo(0); @@ -144,11 +143,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces } // transforms we have from completing the spinner will be rolled back, so reapply immediately. - updateComplete(state.NewValue == ArmedState.Hit, 0); + updateComplete(state == ArmedState.Hit, 0); using (BeginDelayedSequence(spinner.Duration, true)) { - switch (state.NewValue) + switch (state) { case ArmedState.Hit: this.ScaleTo(Scale * 1.2f, 320, Easing.Out); diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacyNewStyleSpinner.cs b/osu.Game.Rulesets.Osu/Skinning/LegacyNewStyleSpinner.cs index 739c87e037..734c66ce7d 100644 --- a/osu.Game.Rulesets.Osu/Skinning/LegacyNewStyleSpinner.cs +++ b/osu.Game.Rulesets.Osu/Skinning/LegacyNewStyleSpinner.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.Sprites; @@ -72,10 +71,10 @@ namespace osu.Game.Rulesets.Osu.Skinning base.LoadComplete(); this.FadeOut(); - drawableSpinner.State.BindValueChanged(updateStateTransforms, true); + drawableSpinner.ApplyCustomUpdateState += updateStateTransforms; } - private void updateStateTransforms(ValueChangedEvent state) + private void updateStateTransforms(DrawableHitObject drawableHitObject, ArmedState state) { var spinner = (Spinner)drawableSpinner.HitObject; diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacyOldStyleSpinner.cs b/osu.Game.Rulesets.Osu/Skinning/LegacyOldStyleSpinner.cs index e157842fd1..09f8894d53 100644 --- a/osu.Game.Rulesets.Osu/Skinning/LegacyOldStyleSpinner.cs +++ b/osu.Game.Rulesets.Osu/Skinning/LegacyOldStyleSpinner.cs @@ -3,7 +3,6 @@ using System; using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; @@ -86,10 +85,10 @@ namespace osu.Game.Rulesets.Osu.Skinning base.LoadComplete(); this.FadeOut(); - drawableSpinner.State.BindValueChanged(updateStateTransforms, true); + drawableSpinner.ApplyCustomUpdateState += updateStateTransforms; } - private void updateStateTransforms(ValueChangedEvent state) + private void updateStateTransforms(DrawableHitObject drawableHitObject, ArmedState state) { var spinner = drawableSpinner.HitObject; From 63b5b8b84187251bb51e72da4e295d00b7a8226d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 28 Sep 2020 17:32:57 +0900 Subject: [PATCH 1286/1782] Fix sliders not dragging correctly after snaking has begun Closes #10278. --- .../Edit/Blueprints/Sliders/Components/SliderBodyPiece.cs | 5 +++++ .../Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) 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 78f4c4d992..9349ef7a18 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderBodyPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderBodyPiece.cs @@ -15,6 +15,11 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components { private readonly ManualSliderBody body; + /// + /// Offset in absolute (local) coordinates from the start of the curve. + /// + public Vector2 PathStartLocation => body.PathOffset; + public SliderBodyPiece() { InternalChild = body = new ManualSliderBody diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs index 6633136673..94862eb205 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs @@ -190,7 +190,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders new OsuMenuItem("Add control point", MenuItemType.Standard, () => addControlPoint(rightClickPosition)), }; - public override Vector2 ScreenSpaceSelectionPoint => ((DrawableSlider)DrawableObject).HeadCircle.ScreenSpaceDrawQuad.Centre; + public override Vector2 ScreenSpaceSelectionPoint => BodyPiece.ToScreenSpace(BodyPiece.PathStartLocation); public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => BodyPiece.ReceivePositionalInputAt(screenSpacePos); From e60e47ff66b623c230cdfe832bc4b12d2688e479 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 28 Sep 2020 17:41:10 +0900 Subject: [PATCH 1287/1782] Unbind events on disposal --- .../Objects/Drawables/Pieces/DefaultSpinnerDisc.cs | 6 ++++++ osu.Game.Rulesets.Osu/Skinning/LegacyNewStyleSpinner.cs | 6 ++++++ osu.Game.Rulesets.Osu/Skinning/LegacyOldStyleSpinner.cs | 6 ++++++ 3 files changed, 18 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/DefaultSpinnerDisc.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/DefaultSpinnerDisc.cs index e45ea9c6cc..51d67c7f67 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/DefaultSpinnerDisc.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/DefaultSpinnerDisc.cs @@ -184,5 +184,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces return true; } } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + drawableSpinner.ApplyCustomUpdateState -= updateStateTransforms; + } } } diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacyNewStyleSpinner.cs b/osu.Game.Rulesets.Osu/Skinning/LegacyNewStyleSpinner.cs index 734c66ce7d..8baa6a3dd3 100644 --- a/osu.Game.Rulesets.Osu/Skinning/LegacyNewStyleSpinner.cs +++ b/osu.Game.Rulesets.Osu/Skinning/LegacyNewStyleSpinner.cs @@ -94,5 +94,11 @@ namespace osu.Game.Rulesets.Osu.Skinning Scale = new Vector2(final_scale * (0.8f + (float)Interpolation.ApplyEasing(Easing.Out, drawableSpinner.Progress) * 0.2f)); } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + drawableSpinner.ApplyCustomUpdateState -= updateStateTransforms; + } } } diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacyOldStyleSpinner.cs b/osu.Game.Rulesets.Osu/Skinning/LegacyOldStyleSpinner.cs index 09f8894d53..a895298ec3 100644 --- a/osu.Game.Rulesets.Osu/Skinning/LegacyOldStyleSpinner.cs +++ b/osu.Game.Rulesets.Osu/Skinning/LegacyOldStyleSpinner.cs @@ -126,5 +126,11 @@ namespace osu.Game.Rulesets.Osu.Skinning return (float)barCount / total_bars * final_metre_height; } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + drawableSpinner.ApplyCustomUpdateState -= updateStateTransforms; + } } } From b6bc829bd5a8ea160a26fd66f7f18362395d9bb5 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 28 Sep 2020 17:46:22 +0900 Subject: [PATCH 1288/1782] Guard against nulls (load not run) --- .../Objects/Drawables/Pieces/DefaultSpinnerDisc.cs | 4 +++- osu.Game.Rulesets.Osu/Skinning/LegacyNewStyleSpinner.cs | 4 +++- osu.Game.Rulesets.Osu/Skinning/LegacyOldStyleSpinner.cs | 4 +++- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/DefaultSpinnerDisc.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/DefaultSpinnerDisc.cs index 51d67c7f67..2862fe49bd 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/DefaultSpinnerDisc.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/DefaultSpinnerDisc.cs @@ -188,7 +188,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); - drawableSpinner.ApplyCustomUpdateState -= updateStateTransforms; + + if (drawableSpinner != null) + drawableSpinner.ApplyCustomUpdateState -= updateStateTransforms; } } } diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacyNewStyleSpinner.cs b/osu.Game.Rulesets.Osu/Skinning/LegacyNewStyleSpinner.cs index 8baa6a3dd3..bcb2af8e3e 100644 --- a/osu.Game.Rulesets.Osu/Skinning/LegacyNewStyleSpinner.cs +++ b/osu.Game.Rulesets.Osu/Skinning/LegacyNewStyleSpinner.cs @@ -98,7 +98,9 @@ namespace osu.Game.Rulesets.Osu.Skinning protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); - drawableSpinner.ApplyCustomUpdateState -= updateStateTransforms; + + if (drawableSpinner != null) + drawableSpinner.ApplyCustomUpdateState -= updateStateTransforms; } } } diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacyOldStyleSpinner.cs b/osu.Game.Rulesets.Osu/Skinning/LegacyOldStyleSpinner.cs index a895298ec3..a45d91801d 100644 --- a/osu.Game.Rulesets.Osu/Skinning/LegacyOldStyleSpinner.cs +++ b/osu.Game.Rulesets.Osu/Skinning/LegacyOldStyleSpinner.cs @@ -130,7 +130,9 @@ namespace osu.Game.Rulesets.Osu.Skinning protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); - drawableSpinner.ApplyCustomUpdateState -= updateStateTransforms; + + if (drawableSpinner != null) + drawableSpinner.ApplyCustomUpdateState -= updateStateTransforms; } } } From 4f0c0ea5f9bb4d3dc7b671af349cf6ab87d15313 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 28 Sep 2020 18:16:19 +0900 Subject: [PATCH 1289/1782] Fix hit samples playing while paused / seeking in the editor --- .../Objects/Drawables/DrawableHitObject.cs | 4 ++-- osu.Game/Screens/Edit/Editor.cs | 1 + osu.Game/Screens/Edit/EditorClock.cs | 22 +++++++++++++++++-- osu.Game/Screens/Play/GameplayClock.cs | 2 +- osu.Game/Screens/Play/ISeekableClock.cs | 13 +++++++++++ 5 files changed, 37 insertions(+), 5 deletions(-) create mode 100644 osu.Game/Screens/Play/ISeekableClock.cs diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 7c05bc9aa7..5b26607bf7 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -360,7 +360,7 @@ namespace osu.Game.Rulesets.Objects.Drawables } [Resolved(canBeNull: true)] - private GameplayClock gameplayClock { get; set; } + private ISeekableClock seekableClock { get; set; } /// /// Calculate the position to be used for sample playback at a specified X position (0..1). @@ -377,7 +377,7 @@ namespace osu.Game.Rulesets.Objects.Drawables /// /// Whether samples should currently be playing. Will be false during seek operations. /// - protected bool ShouldPlaySamples => gameplayClock?.IsSeeking != true; + protected bool ShouldPlaySamples => seekableClock?.IsSeeking != true; /// /// Plays all the hit sounds for this . diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index fd090e0959..1f5e261588 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -107,6 +107,7 @@ namespace osu.Game.Screens.Edit UpdateClockSource(); dependencies.CacheAs(clock); + dependencies.CacheAs(clock); AddInternal(clock); // todo: remove caching of this and consume via editorBeatmap? diff --git a/osu.Game/Screens/Edit/EditorClock.cs b/osu.Game/Screens/Edit/EditorClock.cs index ec203df064..ebc73c2bb8 100644 --- a/osu.Game/Screens/Edit/EditorClock.cs +++ b/osu.Game/Screens/Edit/EditorClock.cs @@ -7,17 +7,18 @@ using osu.Framework.Audio.Track; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Transforms; -using osu.Framework.Utils; using osu.Framework.Timing; +using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Screens.Play; namespace osu.Game.Screens.Edit { /// /// A decoupled clock which adds editor-specific functionality, such as snapping to a user-defined beat divisor. /// - public class EditorClock : Component, IFrameBasedClock, IAdjustableClock, ISourceChangeableClock + public class EditorClock : Component, IFrameBasedClock, IAdjustableClock, ISourceChangeableClock, ISeekableClock { public IBindable Track => track; @@ -211,8 +212,25 @@ namespace osu.Game.Screens.Edit private const double transform_time = 300; + public bool IsSeeking { get; private set; } + + protected override void Update() + { + base.Update(); + + if (IsSeeking) + { + // we are either running a seek tween or doing an immediate seek. + // in the case of an immediate seek the seeking bool will be set to false after one update. + // this allows for silencing hit sounds and the likes. + IsSeeking = Transforms.Any(); + } + } + public void SeekTo(double seekDestination) { + IsSeeking = true; + if (IsRunning) Seek(seekDestination); else diff --git a/osu.Game/Screens/Play/GameplayClock.cs b/osu.Game/Screens/Play/GameplayClock.cs index 4f2cf5005c..b10e50882c 100644 --- a/osu.Game/Screens/Play/GameplayClock.cs +++ b/osu.Game/Screens/Play/GameplayClock.cs @@ -14,7 +14,7 @@ namespace osu.Game.Screens.Play /// , as this should only be done once to ensure accuracy. /// /// - public class GameplayClock : IFrameBasedClock + public class GameplayClock : IFrameBasedClock, ISeekableClock { private readonly IFrameBasedClock underlyingClock; diff --git a/osu.Game/Screens/Play/ISeekableClock.cs b/osu.Game/Screens/Play/ISeekableClock.cs new file mode 100644 index 0000000000..9d992a45fd --- /dev/null +++ b/osu.Game/Screens/Play/ISeekableClock.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.Play +{ + public interface ISeekableClock + { + /// + /// Whether an ongoing seek operation is active. + /// + bool IsSeeking { get; } + } +} From 40a4654ef91c2ec9f92cf8b944eb48edd1ab9972 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Max=20H=C3=BCbner?= Date: Mon, 28 Sep 2020 12:21:43 +0200 Subject: [PATCH 1290/1782] Invert spinnerNoBlink to spinnerBlink locally --- osu.Game.Rulesets.Osu/Skinning/LegacyOldStyleSpinner.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacyOldStyleSpinner.cs b/osu.Game.Rulesets.Osu/Skinning/LegacyOldStyleSpinner.cs index ada3a825d0..cce50b24ac 100644 --- a/osu.Game.Rulesets.Osu/Skinning/LegacyOldStyleSpinner.cs +++ b/osu.Game.Rulesets.Osu/Skinning/LegacyOldStyleSpinner.cs @@ -26,7 +26,7 @@ namespace osu.Game.Rulesets.Osu.Skinning private Sprite metreSprite; private Container metre; - private bool spinnerNoBlink; + private bool spinnerBlink; private const float sprite_scale = 1 / 1.6f; private const float final_metre_height = 692 * sprite_scale; @@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.Osu.Skinning [BackgroundDependencyLoader] private void load(ISkinSource source, DrawableHitObject drawableObject) { - spinnerNoBlink = source.GetConfig(LegacySetting.SpinnerNoBlink)?.Value ?? false; + spinnerBlink = !source.GetConfig(LegacySetting.SpinnerNoBlink)?.Value ?? true; drawableSpinner = (DrawableSpinner)drawableObject; @@ -125,12 +125,12 @@ namespace osu.Game.Rulesets.Osu.Skinning progress *= 100; // the spinner should still blink at 100% progress. - if (!spinnerNoBlink) + if (spinnerBlink) progress = Math.Min(99, progress); int barCount = (int)progress / 10; - if (!spinnerNoBlink && RNG.NextBool(((int)progress % 10) / 10f)) + if (spinnerBlink && RNG.NextBool(((int)progress % 10) / 10f)) barCount++; return (float)barCount / total_bars * final_metre_height; From 54852991f36f2136dd5240058e5db758f81e65ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Max=20H=C3=BCbner?= Date: Mon, 28 Sep 2020 12:24:30 +0200 Subject: [PATCH 1291/1782] Move SpinnerNoBlink to OsuSkinConfiguration --- osu.Game.Rulesets.Osu/Skinning/LegacyOldStyleSpinner.cs | 3 +-- osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs | 3 ++- osu.Game/Skinning/LegacySkinConfiguration.cs | 3 +-- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacyOldStyleSpinner.cs b/osu.Game.Rulesets.Osu/Skinning/LegacyOldStyleSpinner.cs index cce50b24ac..63cd48676e 100644 --- a/osu.Game.Rulesets.Osu/Skinning/LegacyOldStyleSpinner.cs +++ b/osu.Game.Rulesets.Osu/Skinning/LegacyOldStyleSpinner.cs @@ -12,7 +12,6 @@ using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Skinning; using osuTK; -using static osu.Game.Skinning.LegacySkinConfiguration; namespace osu.Game.Rulesets.Osu.Skinning { @@ -34,7 +33,7 @@ namespace osu.Game.Rulesets.Osu.Skinning [BackgroundDependencyLoader] private void load(ISkinSource source, DrawableHitObject drawableObject) { - spinnerBlink = !source.GetConfig(LegacySetting.SpinnerNoBlink)?.Value ?? true; + spinnerBlink = !source.GetConfig(OsuSkinConfiguration.SpinnerNoBlink)?.Value ?? true; drawableSpinner = (DrawableSpinner)drawableObject; diff --git a/osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs b/osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs index e034e14eb0..63c9b53278 100644 --- a/osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs +++ b/osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs @@ -14,6 +14,7 @@ namespace osu.Game.Rulesets.Osu.Skinning CursorRotate, HitCircleOverlayAboveNumber, HitCircleOverlayAboveNumer, // Some old skins will have this typo - SpinnerFrequencyModulate + SpinnerFrequencyModulate, + SpinnerNoBlink } } diff --git a/osu.Game/Skinning/LegacySkinConfiguration.cs b/osu.Game/Skinning/LegacySkinConfiguration.cs index a0e8fb2f92..828804b9cb 100644 --- a/osu.Game/Skinning/LegacySkinConfiguration.cs +++ b/osu.Game/Skinning/LegacySkinConfiguration.cs @@ -18,8 +18,7 @@ namespace osu.Game.Skinning ComboPrefix, ComboOverlap, AnimationFramerate, - LayeredHitSounds, - SpinnerNoBlink + LayeredHitSounds } } } From 0900661b23e0cd3aea39142d551d442e8f084c91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 28 Sep 2020 16:34:04 +0200 Subject: [PATCH 1292/1782] Use IsHit for strong hit instead of checking result type --- osu.Game.Rulesets.Taiko/Skinning/LegacyHitExplosion.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/Skinning/LegacyHitExplosion.cs b/osu.Game.Rulesets.Taiko/Skinning/LegacyHitExplosion.cs index f24f75f097..58dfa1747a 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/LegacyHitExplosion.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/LegacyHitExplosion.cs @@ -96,7 +96,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning if (hit == null || nestedStrongHit == null) return false; - return hit.Result.Type == nestedStrongHit.Result.Type; + return nestedStrongHit.IsHit; } } } From f6f267a43a1651deaddb1f2a013b6efdde043a99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 28 Sep 2020 16:38:30 +0200 Subject: [PATCH 1293/1782] Switch to strong sprite exactly once --- osu.Game.Rulesets.Taiko/Skinning/LegacyHitExplosion.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Skinning/LegacyHitExplosion.cs b/osu.Game.Rulesets.Taiko/Skinning/LegacyHitExplosion.cs index 58dfa1747a..cd5c9c757f 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/LegacyHitExplosion.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/LegacyHitExplosion.cs @@ -17,6 +17,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning private DrawableHit hit; private DrawableStrongNestedHit nestedStrongHit; + private bool switchedToStrongSprite; /// /// Creates a new legacy hit explosion. @@ -84,16 +85,17 @@ namespace osu.Game.Rulesets.Taiko.Skinning { base.Update(); - if (shouldSwitchToStrongSprite() && strongSprite != null) + if (shouldSwitchToStrongSprite() && !switchedToStrongSprite) { sprite.FadeOut(50, Easing.OutQuint); strongSprite.FadeIn(50, Easing.OutQuint); + switchedToStrongSprite = true; } } private bool shouldSwitchToStrongSprite() { - if (hit == null || nestedStrongHit == null) + if (hit == null || nestedStrongHit == null || strongSprite == null) return false; return nestedStrongHit.IsHit; From 2fb9a5d7342e63569dff7705450539ece330736c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 28 Sep 2020 16:59:33 +0200 Subject: [PATCH 1294/1782] Remove no longer required field --- osu.Game.Rulesets.Taiko/Skinning/LegacyHitExplosion.cs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Skinning/LegacyHitExplosion.cs b/osu.Game.Rulesets.Taiko/Skinning/LegacyHitExplosion.cs index cd5c9c757f..19493271be 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/LegacyHitExplosion.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/LegacyHitExplosion.cs @@ -15,7 +15,6 @@ namespace osu.Game.Rulesets.Taiko.Skinning private readonly Drawable sprite; private readonly Drawable strongSprite; - private DrawableHit hit; private DrawableStrongNestedHit nestedStrongHit; private bool switchedToStrongSprite; @@ -58,11 +57,8 @@ namespace osu.Game.Rulesets.Taiko.Skinning })); } - if (judgedObject is DrawableHit h) - { - hit = h; + if (judgedObject is DrawableHit hit) nestedStrongHit = hit.NestedHitObjects.SingleOrDefault() as DrawableStrongNestedHit; - } } protected override void LoadComplete() @@ -95,7 +91,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning private bool shouldSwitchToStrongSprite() { - if (hit == null || nestedStrongHit == null || strongSprite == null) + if (nestedStrongHit == null || strongSprite == null) return false; return nestedStrongHit.IsHit; From 585b857a0c7f417a5fdfeb877d032324b4879f13 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 29 Sep 2020 12:17:38 +0900 Subject: [PATCH 1295/1782] Handle paused state correctly --- osu.Game/Screens/Edit/EditorClock.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/EditorClock.cs b/osu.Game/Screens/Edit/EditorClock.cs index ebc73c2bb8..99e5044b1f 100644 --- a/osu.Game/Screens/Edit/EditorClock.cs +++ b/osu.Game/Screens/Edit/EditorClock.cs @@ -220,10 +220,12 @@ namespace osu.Game.Screens.Edit if (IsSeeking) { + bool isPaused = track.Value?.IsRunning != true; + // we are either running a seek tween or doing an immediate seek. // in the case of an immediate seek the seeking bool will be set to false after one update. // this allows for silencing hit sounds and the likes. - IsSeeking = Transforms.Any(); + IsSeeking = isPaused || Transforms.Any(); } } From d6f3beffb648f1a0e059a5d641984522c799b77b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 29 Sep 2020 12:45:20 +0900 Subject: [PATCH 1296/1782] Use existing bindable flow instead --- .../Objects/Drawables/DrawableSlider.cs | 5 +-- .../Objects/Drawables/DrawableSpinner.cs | 5 +-- .../Gameplay/TestSceneSkinnableSound.cs | 2 +- .../Objects/Drawables/DrawableHitObject.cs | 9 ++---- osu.Game/Screens/Edit/Editor.cs | 2 +- osu.Game/Screens/Edit/EditorClock.cs | 29 +++++++++++++---- osu.Game/Screens/Play/GameplayClock.cs | 4 ++- .../Screens/Play/ISamplePlaybackDisabler.cs | 20 ++++++++++++ osu.Game/Screens/Play/ISeekableClock.cs | 13 -------- osu.Game/Skinning/SkinnableSound.cs | 32 +++++++++++-------- 10 files changed, 70 insertions(+), 51 deletions(-) create mode 100644 osu.Game/Screens/Play/ISamplePlaybackDisabler.cs delete mode 100644 osu.Game/Screens/Play/ISeekableClock.cs diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index 07f40f763b..68f203db47 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -112,10 +112,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables private void updateSlidingSample(ValueChangedEvent tracking) { - // note that samples will not start playing if exiting a seek operation in the middle of a slider. - // may be something we want to address at a later point, but not so easy to make happen right now - // (SkinnableSound would need to expose whether the sample is already playing and this logic would need to run in Update). - if (tracking.NewValue && ShouldPlaySamples) + if (tracking.NewValue) slidingSample?.Play(); else slidingSample?.Stop(); diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index a57bb466c7..b2a706833c 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -113,10 +113,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables private void updateSpinningSample(ValueChangedEvent tracking) { - // note that samples will not start playing if exiting a seek operation in the middle of a spinner. - // may be something we want to address at a later point, but not so easy to make happen right now - // (SkinnableSound would need to expose whether the sample is already playing and this logic would need to run in Update). - if (tracking.NewValue && ShouldPlaySamples) + if (tracking.NewValue) { spinningSample?.Play(); spinningSample?.VolumeTo(1, 200); diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs index ed75d83151..8b37cbd06f 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs @@ -22,7 +22,7 @@ namespace osu.Game.Tests.Visual.Gameplay { public class TestSceneSkinnableSound : OsuTestScene { - [Cached] + [Cached(typeof(ISamplePlaybackDisabler))] private GameplayClock gameplayClock = new GameplayClock(new FramedClock()); private TestSkinSourceContainer skinSource; diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 5b26607bf7..796b8f7aae 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -360,7 +360,7 @@ namespace osu.Game.Rulesets.Objects.Drawables } [Resolved(canBeNull: true)] - private ISeekableClock seekableClock { get; set; } + private ISamplePlaybackDisabler samplePlaybackDisabler { get; set; } /// /// Calculate the position to be used for sample playback at a specified X position (0..1). @@ -374,18 +374,13 @@ namespace osu.Game.Rulesets.Objects.Drawables return balance_adjust_amount * (userPositionalHitSounds.Value ? position - 0.5f : 0); } - /// - /// Whether samples should currently be playing. Will be false during seek operations. - /// - protected bool ShouldPlaySamples => seekableClock?.IsSeeking != true; - /// /// Plays all the hit sounds for this . /// This is invoked automatically when this is hit. /// public virtual void PlaySamples() { - if (Samples != null && ShouldPlaySamples) + if (Samples != null) { Samples.Balance.Value = CalculateSamplePlaybackBalance(SamplePlaybackPosition); Samples.Play(); diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 1f5e261588..a0692d94e6 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -107,7 +107,7 @@ namespace osu.Game.Screens.Edit UpdateClockSource(); dependencies.CacheAs(clock); - dependencies.CacheAs(clock); + dependencies.CacheAs(clock); AddInternal(clock); // todo: remove caching of this and consume via editorBeatmap? diff --git a/osu.Game/Screens/Edit/EditorClock.cs b/osu.Game/Screens/Edit/EditorClock.cs index 99e5044b1f..4b7cd82637 100644 --- a/osu.Game/Screens/Edit/EditorClock.cs +++ b/osu.Game/Screens/Edit/EditorClock.cs @@ -18,7 +18,7 @@ namespace osu.Game.Screens.Edit /// /// A decoupled clock which adds editor-specific functionality, such as snapping to a user-defined beat divisor. /// - public class EditorClock : Component, IFrameBasedClock, IAdjustableClock, ISourceChangeableClock, ISeekableClock + public class EditorClock : Component, IFrameBasedClock, IAdjustableClock, ISourceChangeableClock, ISamplePlaybackDisabler { public IBindable Track => track; @@ -32,6 +32,10 @@ namespace osu.Game.Screens.Edit private readonly DecoupleableInterpolatingFramedClock underlyingClock; + public IBindable SamplePlaybackDisabled => samplePlaybackDisabled; + + private readonly Bindable samplePlaybackDisabled = new Bindable(); + public EditorClock(WorkingBeatmap beatmap, BindableBeatDivisor beatDivisor) : this(beatmap.Beatmap.ControlPointInfo, beatmap.Track.Length, beatDivisor) { @@ -167,11 +171,14 @@ namespace osu.Game.Screens.Edit public void Stop() { + samplePlaybackDisabled.Value = true; underlyingClock.Stop(); } public bool Seek(double position) { + samplePlaybackDisabled.Value = true; + ClearTransforms(); return underlyingClock.Seek(position); } @@ -212,26 +219,34 @@ namespace osu.Game.Screens.Edit private const double transform_time = 300; - public bool IsSeeking { get; private set; } - protected override void Update() { base.Update(); - if (IsSeeking) + updateSeekingState(); + } + + private void updateSeekingState() + { + if (samplePlaybackDisabled.Value) { - bool isPaused = track.Value?.IsRunning != true; + if (track.Value?.IsRunning != true) + { + // seeking in the editor can happen while the track isn't running. + // in this case we always want to expose ourselves as seeking (to avoid sample playback). + return; + } // we are either running a seek tween or doing an immediate seek. // in the case of an immediate seek the seeking bool will be set to false after one update. // this allows for silencing hit sounds and the likes. - IsSeeking = isPaused || Transforms.Any(); + samplePlaybackDisabled.Value = Transforms.Any(); } } public void SeekTo(double seekDestination) { - IsSeeking = true; + samplePlaybackDisabled.Value = true; if (IsRunning) Seek(seekDestination); diff --git a/osu.Game/Screens/Play/GameplayClock.cs b/osu.Game/Screens/Play/GameplayClock.cs index b10e50882c..da4648fd2b 100644 --- a/osu.Game/Screens/Play/GameplayClock.cs +++ b/osu.Game/Screens/Play/GameplayClock.cs @@ -14,7 +14,7 @@ namespace osu.Game.Screens.Play /// , as this should only be done once to ensure accuracy. /// /// - public class GameplayClock : IFrameBasedClock, ISeekableClock + public class GameplayClock : IFrameBasedClock, ISamplePlaybackDisabler { private readonly IFrameBasedClock underlyingClock; @@ -48,5 +48,7 @@ namespace osu.Game.Screens.Play public FrameTimeInfo TimeInfo => underlyingClock.TimeInfo; public IClock Source => underlyingClock; + + public IBindable SamplePlaybackDisabled => IsPaused; } } diff --git a/osu.Game/Screens/Play/ISamplePlaybackDisabler.cs b/osu.Game/Screens/Play/ISamplePlaybackDisabler.cs new file mode 100644 index 0000000000..83e89d654b --- /dev/null +++ b/osu.Game/Screens/Play/ISamplePlaybackDisabler.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.Bindables; +using osu.Game.Skinning; + +namespace osu.Game.Screens.Play +{ + /// + /// Allows a component to disable sample playback dynamically as required. + /// Handled by . + /// + public interface ISamplePlaybackDisabler + { + /// + /// Whether sample playback should be disabled (or paused for looping samples). + /// + IBindable SamplePlaybackDisabled { get; } + } +} diff --git a/osu.Game/Screens/Play/ISeekableClock.cs b/osu.Game/Screens/Play/ISeekableClock.cs deleted file mode 100644 index 9d992a45fd..0000000000 --- a/osu.Game/Screens/Play/ISeekableClock.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.Screens.Play -{ - public interface ISeekableClock - { - /// - /// Whether an ongoing seek operation is active. - /// - bool IsSeeking { get; } - } -} diff --git a/osu.Game/Skinning/SkinnableSound.cs b/osu.Game/Skinning/SkinnableSound.cs index ba14049b41..704ba099c1 100644 --- a/osu.Game/Skinning/SkinnableSound.cs +++ b/osu.Game/Skinning/SkinnableSound.cs @@ -50,25 +50,28 @@ namespace osu.Game.Skinning InternalChild = samplesContainer = new AudioContainer(); } - private Bindable gameplayClockPaused; + private readonly IBindable samplePlaybackDisabled = new Bindable(); [BackgroundDependencyLoader(true)] - private void load(GameplayClock gameplayClock) + private void load(ISamplePlaybackDisabler samplePlaybackDisabler) { // if in a gameplay context, pause sample playback when gameplay is paused. - gameplayClockPaused = gameplayClock?.IsPaused.GetBoundCopy(); - gameplayClockPaused?.BindValueChanged(paused => + if (samplePlaybackDisabler != null) { - if (requestedPlaying) + samplePlaybackDisabled.BindTo(samplePlaybackDisabler.SamplePlaybackDisabled); + samplePlaybackDisabled.BindValueChanged(disabled => { - if (paused.NewValue) - stop(); - // it's not easy to know if a sample has finished playing (to end). - // to keep things simple only resume playing looping samples. - else if (Looping) - play(); - } - }); + if (requestedPlaying) + { + if (disabled.NewValue) + stop(); + // it's not easy to know if a sample has finished playing (to end). + // to keep things simple only resume playing looping samples. + else if (Looping) + play(); + } + }); + } } private bool looping; @@ -94,6 +97,9 @@ namespace osu.Game.Skinning private void play() { + if (samplePlaybackDisabled.Value) + return; + samplesContainer.ForEach(c => { if (PlayWhenZeroVolume || c.AggregateVolume.Value > 0) From c5f6b77bbaa03be219cf5978391852ff5019287a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 29 Sep 2020 13:42:17 +0900 Subject: [PATCH 1297/1782] Add missing cached type --- osu.Game/Screens/Play/GameplayClockContainer.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index 7a9cb3dddd..cc25a733f1 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -51,6 +51,7 @@ namespace osu.Game.Screens.Play /// The final clock which is exposed to underlying components. /// [Cached] + [Cached(typeof(ISamplePlaybackDisabler))] public readonly GameplayClock GameplayClock; private Bindable userAudioOffset; From 74e74e1c31acdd58bfb258ca6a3dc0b291c548e5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 29 Sep 2020 14:20:41 +0900 Subject: [PATCH 1298/1782] Fix pause loop sound not working because paused --- osu.Game/Screens/Play/PauseOverlay.cs | 12 +++++++++++- osu.Game/Skinning/SkinnableSound.cs | 6 ++++-- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Play/PauseOverlay.cs b/osu.Game/Screens/Play/PauseOverlay.cs index 65f34aba3e..9494971f8a 100644 --- a/osu.Game/Screens/Play/PauseOverlay.cs +++ b/osu.Game/Screens/Play/PauseOverlay.cs @@ -33,7 +33,7 @@ namespace osu.Game.Screens.Play AddButton("Retry", colours.YellowDark, () => OnRetry?.Invoke()); AddButton("Quit", new Color4(170, 27, 39, 255), () => OnQuit?.Invoke()); - AddInternal(pauseLoop = new SkinnableSound(new SampleInfo("pause-loop")) + AddInternal(pauseLoop = new UnpausableSkinnableSound(new SampleInfo("pause-loop")) { Looping = true, Volume = { Value = 0 } @@ -54,5 +54,15 @@ namespace osu.Game.Screens.Play pauseLoop.VolumeTo(0, TRANSITION_DURATION, Easing.OutQuad).Finally(_ => pauseLoop.Stop()); } + + private class UnpausableSkinnableSound : SkinnableSound + { + protected override bool PlayWhenPaused => true; + + public UnpausableSkinnableSound(SampleInfo sampleInfo) + : base(sampleInfo) + { + } + } } } diff --git a/osu.Game/Skinning/SkinnableSound.cs b/osu.Game/Skinning/SkinnableSound.cs index 704ba099c1..f3ab8b86bc 100644 --- a/osu.Game/Skinning/SkinnableSound.cs +++ b/osu.Game/Skinning/SkinnableSound.cs @@ -37,6 +37,8 @@ namespace osu.Game.Skinning /// protected bool PlayWhenZeroVolume => Looping; + protected virtual bool PlayWhenPaused => false; + private readonly AudioContainer samplesContainer; public SkinnableSound(ISampleInfo hitSamples) @@ -63,7 +65,7 @@ namespace osu.Game.Skinning { if (requestedPlaying) { - if (disabled.NewValue) + if (disabled.NewValue && !PlayWhenPaused) stop(); // it's not easy to know if a sample has finished playing (to end). // to keep things simple only resume playing looping samples. @@ -97,7 +99,7 @@ namespace osu.Game.Skinning private void play() { - if (samplePlaybackDisabled.Value) + if (samplePlaybackDisabled.Value && !PlayWhenPaused) return; samplesContainer.ForEach(c => From 136843c8e450507ad0527622af851d648afb1545 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 29 Sep 2020 14:09:51 +0900 Subject: [PATCH 1299/1782] Make DrawableStoryboardSample a SkinnableSound Allows sharing pause logic with gameplay samples. --- .../Gameplay/TestSceneStoryboardSamples.cs | 15 ++-- osu.Game/Rulesets/Mods/IApplicableToSample.cs | 4 +- osu.Game/Rulesets/Mods/ModRateAdjust.cs | 4 +- osu.Game/Rulesets/Mods/ModTimeRamp.cs | 6 +- osu.Game/Screens/Play/Player.cs | 80 ++++++++++--------- osu.Game/Skinning/SkinnableSound.cs | 39 ++++----- .../Drawables/DrawableStoryboardSample.cs | 41 ++++------ 7 files changed, 94 insertions(+), 95 deletions(-) diff --git a/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs b/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs index a690eb3b59..d46769a7c0 100644 --- a/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs +++ b/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs @@ -4,10 +4,12 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; using System.Threading.Tasks; using NUnit.Framework; using osu.Framework.Audio; using osu.Framework.Audio.Sample; +using osu.Framework.Graphics.Audio; using osu.Framework.IO.Stores; using osu.Framework.Testing; using osu.Game.Audio; @@ -106,9 +108,14 @@ namespace osu.Game.Tests.Gameplay Beatmap.Value = new TestCustomSkinWorkingBeatmap(new OsuRuleset().RulesetInfo, Audio); SelectedMods.Value = new[] { testedMod }; - Add(gameplayContainer = new GameplayClockContainer(Beatmap.Value, 0)); + var beatmapSkinSourceContainer = new BeatmapSkinProvidingContainer(Beatmap.Value.Skin); - gameplayContainer.Add(sample = new TestDrawableStoryboardSample(new StoryboardSampleInfo("test-sample", 1, 1)) + Add(gameplayContainer = new GameplayClockContainer(Beatmap.Value, 0) + { + Child = beatmapSkinSourceContainer + }); + + beatmapSkinSourceContainer.Add(sample = new TestDrawableStoryboardSample(new StoryboardSampleInfo("test-sample", 1, 1)) { Clock = gameplayContainer.GameplayClock }); @@ -116,7 +123,7 @@ namespace osu.Game.Tests.Gameplay AddStep("start", () => gameplayContainer.Start()); - AddAssert("sample playback rate matches mod rates", () => sample.Channel.AggregateFrequency.Value == expectedRate); + AddAssert("sample playback rate matches mod rates", () => sample.ChildrenOfType().First().AggregateFrequency.Value == expectedRate); } private class TestSkin : LegacySkin @@ -168,8 +175,6 @@ namespace osu.Game.Tests.Gameplay : base(sampleInfo) { } - - public new SampleChannel Channel => base.Channel; } } } diff --git a/osu.Game/Rulesets/Mods/IApplicableToSample.cs b/osu.Game/Rulesets/Mods/IApplicableToSample.cs index 559d127cfc..50a6d501b6 100644 --- a/osu.Game/Rulesets/Mods/IApplicableToSample.cs +++ b/osu.Game/Rulesets/Mods/IApplicableToSample.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 osu.Framework.Audio.Sample; +using osu.Framework.Graphics.Audio; namespace osu.Game.Rulesets.Mods { @@ -10,6 +10,6 @@ namespace osu.Game.Rulesets.Mods /// public interface IApplicableToSample : IApplicableMod { - void ApplyToSample(SampleChannel sample); + void ApplyToSample(DrawableSample sample); } } diff --git a/osu.Game/Rulesets/Mods/ModRateAdjust.cs b/osu.Game/Rulesets/Mods/ModRateAdjust.cs index fec21764b0..2150b0fb68 100644 --- a/osu.Game/Rulesets/Mods/ModRateAdjust.cs +++ b/osu.Game/Rulesets/Mods/ModRateAdjust.cs @@ -2,9 +2,9 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Audio; -using osu.Framework.Audio.Sample; using osu.Framework.Audio.Track; using osu.Framework.Bindables; +using osu.Framework.Graphics.Audio; namespace osu.Game.Rulesets.Mods { @@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Mods track.AddAdjustment(AdjustableProperty.Tempo, SpeedChange); } - public virtual void ApplyToSample(SampleChannel sample) + public virtual void ApplyToSample(DrawableSample sample) { sample.AddAdjustment(AdjustableProperty.Frequency, SpeedChange); } diff --git a/osu.Game/Rulesets/Mods/ModTimeRamp.cs b/osu.Game/Rulesets/Mods/ModTimeRamp.cs index 20c8d0f3e7..4d43ae73d3 100644 --- a/osu.Game/Rulesets/Mods/ModTimeRamp.cs +++ b/osu.Game/Rulesets/Mods/ModTimeRamp.cs @@ -6,11 +6,11 @@ using System.Linq; using osu.Framework.Audio; using osu.Framework.Audio.Track; using osu.Framework.Bindables; +using osu.Framework.Graphics.Audio; using osu.Game.Beatmaps; using osu.Game.Configuration; -using osu.Game.Rulesets.UI; using osu.Game.Rulesets.Objects; -using osu.Framework.Audio.Sample; +using osu.Game.Rulesets.UI; namespace osu.Game.Rulesets.Mods { @@ -59,7 +59,7 @@ namespace osu.Game.Rulesets.Mods AdjustPitch.TriggerChange(); } - public void ApplyToSample(SampleChannel sample) + public void ApplyToSample(DrawableSample sample) { sample.AddAdjustment(AdjustableProperty.Frequency, SpeedChange); } diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 8e2ed583f2..175722c44e 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -191,9 +191,25 @@ namespace osu.Game.Screens.Play dependencies.CacheAs(gameplayBeatmap); - addUnderlayComponents(GameplayClockContainer); - addGameplayComponents(GameplayClockContainer, Beatmap.Value, playableBeatmap); - addOverlayComponents(GameplayClockContainer, Beatmap.Value); + 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, playableBeatmap)); + + // load the skinning hierarchy first. + // this is intentionally done in two stages to ensure things are in a loaded state before exposing the ruleset to skin sources. + GameplayClockContainer.Add(beatmapSkinProvider.WithChild(rulesetSkinProvider)); + + rulesetSkinProvider.AddRange(new[] + { + // underlay and gameplay should have access the to skinning sources. + createUnderlayComponents(), + createGameplayComponents(Beatmap.Value, playableBeatmap) + }); + + // add the overlay components as a separate step as they proxy some elements from the above underlay/gameplay components. + GameplayClockContainer.Add(createOverlayComponents(Beatmap.Value)); if (!DrawableRuleset.AllowGameplayOverlays) { @@ -238,45 +254,31 @@ namespace osu.Game.Screens.Play breakTracker.IsBreakTime.BindValueChanged(onBreakTimeChanged, true); } - private void addUnderlayComponents(Container target) + private Drawable createUnderlayComponents() => + DimmableStoryboard = new DimmableStoryboard(Beatmap.Value.Storyboard) { RelativeSizeAxes = Axes.Both }; + + private Drawable createGameplayComponents(WorkingBeatmap working, IBeatmap playableBeatmap) => new ScalingContainer(ScalingMode.Gameplay) { - target.Add(DimmableStoryboard = new DimmableStoryboard(Beatmap.Value.Storyboard) { RelativeSizeAxes = Axes.Both }); - } - - private void addGameplayComponents(Container target, WorkingBeatmap working, IBeatmap playableBeatmap) - { - var beatmapSkinProvider = new BeatmapSkinProvidingContainer(working.Skin); - - // the beatmapSkinProvider is used as the fallback source here to allow the ruleset-specific skin implementation - // full access to all skin sources. - var rulesetSkinProvider = new SkinProvidingContainer(ruleset.CreateLegacySkinProvider(beatmapSkinProvider, playableBeatmap)); - - // load the skinning hierarchy first. - // this is intentionally done in two stages to ensure things are in a loaded state before exposing the ruleset to skin sources. - target.Add(new ScalingContainer(ScalingMode.Gameplay) - .WithChild(beatmapSkinProvider - .WithChild(target = rulesetSkinProvider))); - - target.AddRange(new Drawable[] + Children = new Drawable[] { - DrawableRuleset, + DrawableRuleset.With(r => + r.FrameStableComponents.Children = new Drawable[] + { + ScoreProcessor, + HealthProcessor, + breakTracker = new BreakTracker(DrawableRuleset.GameplayStartTime, ScoreProcessor) + { + Breaks = working.Beatmap.Breaks + } + }), new ComboEffects(ScoreProcessor) - }); + } + }; - DrawableRuleset.FrameStableComponents.AddRange(new Drawable[] - { - ScoreProcessor, - HealthProcessor, - breakTracker = new BreakTracker(DrawableRuleset.GameplayStartTime, ScoreProcessor) - { - Breaks = working.Beatmap.Breaks - } - }); - } - - private void addOverlayComponents(Container target, WorkingBeatmap working) + private Drawable createOverlayComponents(WorkingBeatmap working) => new Container { - target.AddRange(new[] + RelativeSizeAxes = Axes.Both, + Children = new[] { DimmableStoryboard.OverlayLayerContainer.CreateProxy(), BreakOverlay = new BreakOverlay(working.Beatmap.BeatmapInfo.LetterboxInBreaks, ScoreProcessor) @@ -342,8 +344,8 @@ namespace osu.Game.Screens.Play }, }, failAnimation = new FailAnimation(DrawableRuleset) { OnComplete = onFailComplete, }, - }); - } + } + }; private void onBreakTimeChanged(ValueChangedEvent isBreakTime) { diff --git a/osu.Game/Skinning/SkinnableSound.cs b/osu.Game/Skinning/SkinnableSound.cs index f3ab8b86bc..07b759843c 100644 --- a/osu.Game/Skinning/SkinnableSound.cs +++ b/osu.Game/Skinning/SkinnableSound.cs @@ -22,7 +22,10 @@ namespace osu.Game.Skinning [Resolved] private ISampleStore samples { get; set; } - private bool requestedPlaying; + /// + /// Whether playback of this sound has been requested, regardless of whether it could be played or not (due to being paused, for instance). + /// + protected bool PlaybackRequested; public override bool RemoveWhenNotAlive => false; public override bool RemoveCompletedTransforms => false; @@ -39,7 +42,7 @@ namespace osu.Game.Skinning protected virtual bool PlayWhenPaused => false; - private readonly AudioContainer samplesContainer; + protected readonly AudioContainer SamplesContainer; public SkinnableSound(ISampleInfo hitSamples) : this(new[] { hitSamples }) @@ -49,7 +52,7 @@ namespace osu.Game.Skinning public SkinnableSound(IEnumerable hitSamples) { this.hitSamples = hitSamples.ToArray(); - InternalChild = samplesContainer = new AudioContainer(); + InternalChild = SamplesContainer = new AudioContainer(); } private readonly IBindable samplePlaybackDisabled = new Bindable(); @@ -63,7 +66,7 @@ namespace osu.Game.Skinning samplePlaybackDisabled.BindTo(samplePlaybackDisabler.SamplePlaybackDisabled); samplePlaybackDisabled.BindValueChanged(disabled => { - if (requestedPlaying) + if (PlaybackRequested) { if (disabled.NewValue && !PlayWhenPaused) stop(); @@ -87,13 +90,13 @@ namespace osu.Game.Skinning looping = value; - samplesContainer.ForEach(c => c.Looping = looping); + SamplesContainer.ForEach(c => c.Looping = looping); } } public void Play() { - requestedPlaying = true; + PlaybackRequested = true; play(); } @@ -102,7 +105,7 @@ namespace osu.Game.Skinning if (samplePlaybackDisabled.Value && !PlayWhenPaused) return; - samplesContainer.ForEach(c => + SamplesContainer.ForEach(c => { if (PlayWhenZeroVolume || c.AggregateVolume.Value > 0) c.Play(); @@ -111,13 +114,13 @@ namespace osu.Game.Skinning public void Stop() { - requestedPlaying = false; + PlaybackRequested = false; stop(); } private void stop() { - samplesContainer.ForEach(c => c.Stop()); + SamplesContainer.ForEach(c => c.Stop()); } protected override void SkinChanged(ISkinSource skin, bool allowFallback) @@ -146,7 +149,7 @@ namespace osu.Game.Skinning return ch; }).Where(c => c != null); - samplesContainer.ChildrenEnumerable = channels.Select(c => new DrawableSample(c)); + SamplesContainer.ChildrenEnumerable = channels.Select(c => new DrawableSample(c)); // Start playback internally for the new samples if the previous ones were playing beforehand. if (wasPlaying) @@ -155,24 +158,24 @@ namespace osu.Game.Skinning #region Re-expose AudioContainer - public BindableNumber Volume => samplesContainer.Volume; + public BindableNumber Volume => SamplesContainer.Volume; - public BindableNumber Balance => samplesContainer.Balance; + public BindableNumber Balance => SamplesContainer.Balance; - public BindableNumber Frequency => samplesContainer.Frequency; + public BindableNumber Frequency => SamplesContainer.Frequency; - public BindableNumber Tempo => samplesContainer.Tempo; + public BindableNumber Tempo => SamplesContainer.Tempo; public void AddAdjustment(AdjustableProperty type, BindableNumber adjustBindable) - => samplesContainer.AddAdjustment(type, adjustBindable); + => SamplesContainer.AddAdjustment(type, adjustBindable); public void RemoveAdjustment(AdjustableProperty type, BindableNumber adjustBindable) - => samplesContainer.RemoveAdjustment(type, adjustBindable); + => SamplesContainer.RemoveAdjustment(type, adjustBindable); public void RemoveAllAdjustments(AdjustableProperty type) - => samplesContainer.RemoveAllAdjustments(type); + => SamplesContainer.RemoveAllAdjustments(type); - public bool IsPlaying => samplesContainer.Any(s => s.Playing); + public bool IsPlaying => SamplesContainer.Any(s => s.Playing); #endregion } diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs index 119c48836b..83e3b8203e 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs @@ -4,15 +4,13 @@ using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; -using osu.Framework.Audio.Sample; using osu.Framework.Bindables; -using osu.Framework.Graphics; -using osu.Game.Beatmaps; using osu.Game.Rulesets.Mods; +using osu.Game.Skinning; namespace osu.Game.Storyboards.Drawables { - public class DrawableStoryboardSample : Component + public class DrawableStoryboardSample : SkinnableSound { /// /// The amount of time allowable beyond the start time of the sample, for the sample to start. @@ -21,38 +19,37 @@ namespace osu.Game.Storyboards.Drawables private readonly StoryboardSampleInfo sampleInfo; - protected SampleChannel Channel { get; private set; } - public override bool RemoveWhenNotAlive => false; public DrawableStoryboardSample(StoryboardSampleInfo sampleInfo) + : base(sampleInfo) { this.sampleInfo = sampleInfo; LifetimeStart = sampleInfo.StartTime; } - [BackgroundDependencyLoader] - private void load(IBindable beatmap, IBindable> mods) - { - Channel = beatmap.Value.Skin.GetSample(sampleInfo); - if (Channel == null) - return; + [Resolved] + private IBindable> mods { get; set; } - Channel.Volume.Value = sampleInfo.Volume / 100.0; + protected override void SkinChanged(ISkinSource skin, bool allowFallback) + { + base.SkinChanged(skin, allowFallback); foreach (var mod in mods.Value.OfType()) - mod.ApplyToSample(Channel); + { + foreach (var sample in SamplesContainer) + mod.ApplyToSample(sample); + } } protected override void Update() { base.Update(); - // TODO: this logic will need to be consolidated with other game samples like hit sounds. if (Time.Current < sampleInfo.StartTime) { // We've rewound before the start time of the sample - Channel?.Stop(); + 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) @@ -63,8 +60,8 @@ namespace osu.Game.Storyboards.Drawables { // 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 - sampleInfo.StartTime < allowable_late_start) - Channel?.Play(); + if (!PlaybackRequested && Time.Current - sampleInfo.StartTime < allowable_late_start) + 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) @@ -72,13 +69,5 @@ namespace osu.Game.Storyboards.Drawables LifetimeEnd = sampleInfo.StartTime; } } - - protected override void Dispose(bool isDisposing) - { - Channel?.Stop(); - Channel = null; - - base.Dispose(isDisposing); - } } } From 56c8e4dacfba027675db746c668ac2541efddda0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 29 Sep 2020 14:18:54 +0900 Subject: [PATCH 1300/1782] Fix failing tests --- osu.Game.Tests/Visual/Gameplay/TestScenePause.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs index 420bf29429..ac0e8eb0d4 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs @@ -158,7 +158,7 @@ namespace osu.Game.Tests.Visual.Gameplay public void TestQuickRetryFromFailedGameplay() { AddUntilStep("wait for fail", () => Player.HasFailed); - AddStep("quick retry", () => Player.GameplayClockContainer.OfType().First().Action?.Invoke()); + AddStep("quick retry", () => Player.GameplayClockContainer.ChildrenOfType().First().Action?.Invoke()); confirmExited(); } @@ -167,7 +167,7 @@ namespace osu.Game.Tests.Visual.Gameplay public void TestQuickExitFromFailedGameplay() { AddUntilStep("wait for fail", () => Player.HasFailed); - AddStep("quick exit", () => Player.GameplayClockContainer.OfType().First().Action?.Invoke()); + AddStep("quick exit", () => Player.GameplayClockContainer.ChildrenOfType().First().Action?.Invoke()); confirmExited(); } @@ -183,7 +183,7 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestQuickExitFromGameplay() { - AddStep("quick exit", () => Player.GameplayClockContainer.OfType().First().Action?.Invoke()); + AddStep("quick exit", () => Player.GameplayClockContainer.ChildrenOfType().First().Action?.Invoke()); confirmExited(); } From 1a70002cdd0bfe2c0fafd2d5f8a9e552ebf5c267 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 29 Sep 2020 14:41:50 +0900 Subject: [PATCH 1301/1782] Split ignore into hit/miss --- osu.Game/Rulesets/Scoring/HitResult.cs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/osu.Game/Rulesets/Scoring/HitResult.cs b/osu.Game/Rulesets/Scoring/HitResult.cs index 1de62cf8e5..f5f2e269f0 100644 --- a/osu.Game/Rulesets/Scoring/HitResult.cs +++ b/osu.Game/Rulesets/Scoring/HitResult.cs @@ -13,12 +13,9 @@ namespace osu.Game.Rulesets.Scoring /// Indicates that the object has not been judged yet. /// [Description(@"")] - [Order(13)] + [Order(14)] None, - [Order(12)] - Ignore, - /// /// Indicates that the object has been judged as a miss. /// @@ -95,6 +92,12 @@ namespace osu.Game.Rulesets.Scoring [Description("L Bonus")] [Order(8)] LargeBonus, + + [Order(13)] + IgnoreMiss, + + [Order(12)] + IgnoreHit, } public static class HitResultExtensions @@ -151,7 +154,7 @@ namespace osu.Game.Rulesets.Scoring switch (result) { case HitResult.None: - case HitResult.Ignore: + case HitResult.IgnoreMiss: case HitResult.Miss: case HitResult.SmallTickMiss: case HitResult.LargeTickMiss: @@ -165,6 +168,6 @@ namespace osu.Game.Rulesets.Scoring /// /// Whether a is scorable. /// - public static bool IsScorable(this HitResult result) => result > HitResult.Ignore; + public static bool IsScorable(this HitResult result) => result >= HitResult.Miss && result < HitResult.IgnoreMiss; } } From 5d1c3773790beb4b40ae02b11a230ddef47d7e05 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 29 Sep 2020 15:07:55 +0900 Subject: [PATCH 1302/1782] Fix HitObject samples getting stuck in a playing state on seeking far into the future --- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs | 6 ++++++ osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs | 6 ++++++ osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs | 7 +++++++ osu.Game/Skinning/SkinnableSound.cs | 3 ++- 4 files changed, 21 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index 68f203db47..ba328e15c6 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -110,6 +110,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables } } + public override void StopAllSamples() + { + base.StopAllSamples(); + slidingSample?.Stop(); + } + private void updateSlidingSample(ValueChangedEvent tracking) { if (tracking.NewValue) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index b2a706833c..9e552981ea 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -124,6 +124,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables } } + public override void StopAllSamples() + { + base.StopAllSamples(); + spinningSample?.Stop(); + } + protected override void AddNestedHitObject(DrawableHitObject hitObject) { base.AddNestedHitObject(hitObject); diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 796b8f7aae..56e3a98ca3 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -387,6 +387,11 @@ namespace osu.Game.Rulesets.Objects.Drawables } } + /// + /// Stops playback of all samples. Automatically called when 's lifetime has been exceeded. + /// + public virtual void StopAllSamples() => Samples?.Stop(); + protected override void Update() { base.Update(); @@ -455,6 +460,8 @@ namespace osu.Game.Rulesets.Objects.Drawables foreach (var nested in NestedHitObjects) nested.OnKilled(); + StopAllSamples(); + UpdateResult(false); } diff --git a/osu.Game/Skinning/SkinnableSound.cs b/osu.Game/Skinning/SkinnableSound.cs index 07b759843c..c1f0b78d3b 100644 --- a/osu.Game/Skinning/SkinnableSound.cs +++ b/osu.Game/Skinning/SkinnableSound.cs @@ -73,7 +73,8 @@ namespace osu.Game.Skinning // it's not easy to know if a sample has finished playing (to end). // to keep things simple only resume playing looping samples. else if (Looping) - play(); + // schedule so we don't start playing a sample which is no longer alive. + Schedule(play); } }); } From 2f26728cdb6a28235a9e15c9e1acd0296938fb05 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 29 Sep 2020 15:29:56 +0900 Subject: [PATCH 1303/1782] Add test coverage of editor sample playback --- .../Editing/TestSceneEditorSamplePlayback.cs | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 osu.Game.Tests/Visual/Editing/TestSceneEditorSamplePlayback.cs diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorSamplePlayback.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorSamplePlayback.cs new file mode 100644 index 0000000000..039a21fd94 --- /dev/null +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorSamplePlayback.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.Linq; +using NUnit.Framework; +using osu.Framework.Graphics.Audio; +using osu.Framework.Testing; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Osu.Objects.Drawables; + +namespace osu.Game.Tests.Visual.Editing +{ + public class TestSceneEditorSamplePlayback : EditorTestScene + { + protected override Ruleset CreateEditorRuleset() => new OsuRuleset(); + + [Test] + public void TestSlidingSampleStopsOnSeek() + { + DrawableSlider slider = null; + DrawableSample[] samples = null; + + AddStep("get first slider", () => + { + slider = Editor.ChildrenOfType().OrderBy(s => s.HitObject.StartTime).First(); + samples = slider.ChildrenOfType().ToArray(); + }); + + AddStep("start playback", () => EditorClock.Start()); + + AddUntilStep("wait for slider sliding then seek", () => + { + if (!slider.Tracking.Value) + return false; + + if (!samples.Any(s => s.Playing)) + return false; + + EditorClock.Seek(20000); + return true; + }); + + AddAssert("slider samples are not playing", () => samples.Length == 5 && samples.All(s => s.Played && !s.Playing)); + } + } +} From cee58e89a34e6e072a5d6adeaefafb90bec7aa0e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 29 Sep 2020 16:32:02 +0900 Subject: [PATCH 1304/1782] Pad hit results --- osu.Game/Rulesets/Scoring/HitResult.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game/Rulesets/Scoring/HitResult.cs b/osu.Game/Rulesets/Scoring/HitResult.cs index f5f2e269f0..6abf91e9d3 100644 --- a/osu.Game/Rulesets/Scoring/HitResult.cs +++ b/osu.Game/Rulesets/Scoring/HitResult.cs @@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Scoring /// [Description(@"")] [Order(14)] - None, + None = 0, /// /// Indicates that the object has been judged as a miss. @@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Scoring /// [Description(@"Miss")] [Order(5)] - Miss, + Miss = 64, [Description(@"Meh")] [Order(4)] @@ -57,7 +57,7 @@ namespace osu.Game.Rulesets.Scoring /// Indicates small tick miss. /// [Order(11)] - SmallTickMiss, + SmallTickMiss = 128, /// /// Indicates a small tick hit. @@ -70,7 +70,7 @@ namespace osu.Game.Rulesets.Scoring /// Indicates a large tick miss. /// [Order(10)] - LargeTickMiss, + LargeTickMiss = 192, /// /// Indicates a large tick hit. @@ -84,17 +84,17 @@ namespace osu.Game.Rulesets.Scoring /// [Description("S Bonus")] [Order(9)] - SmallBonus, + SmallBonus = 254, /// /// Indicate a large bonus. /// [Description("L Bonus")] [Order(8)] - LargeBonus, + LargeBonus = 320, [Order(13)] - IgnoreMiss, + IgnoreMiss = 384, [Order(12)] IgnoreHit, From 07226c79b64918aeefcdb53df6dd339359700312 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 29 Sep 2020 16:32:50 +0900 Subject: [PATCH 1305/1782] Add xmldocs --- osu.Game/Rulesets/Scoring/HitResult.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Scoring/HitResult.cs b/osu.Game/Rulesets/Scoring/HitResult.cs index 6abf91e9d3..fc33ff9df2 100644 --- a/osu.Game/Rulesets/Scoring/HitResult.cs +++ b/osu.Game/Rulesets/Scoring/HitResult.cs @@ -87,15 +87,21 @@ namespace osu.Game.Rulesets.Scoring SmallBonus = 254, /// - /// Indicate a large bonus. + /// Indicates a large bonus. /// [Description("L Bonus")] [Order(8)] LargeBonus = 320, + /// + /// Indicates a miss that should be ignored for scoring purposes. + /// [Order(13)] IgnoreMiss = 384, + /// + /// Indicates a hit that should be ignored for scoring purposes. + /// [Order(12)] IgnoreHit, } From 519f376e7b5377b8965e05aa34815cee399428c4 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 29 Sep 2020 14:26:12 +0900 Subject: [PATCH 1306/1782] Standardise Judgement across all rulesets --- osu.Game/Rulesets/Judgements/Judgement.cs | 122 +++++++++++++++++++--- 1 file changed, 108 insertions(+), 14 deletions(-) diff --git a/osu.Game/Rulesets/Judgements/Judgement.cs b/osu.Game/Rulesets/Judgements/Judgement.cs index 9105b920ca..ea7a8d8d25 100644 --- a/osu.Game/Rulesets/Judgements/Judgement.cs +++ b/osu.Game/Rulesets/Judgements/Judgement.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Scoring; @@ -11,31 +12,69 @@ namespace osu.Game.Rulesets.Judgements /// public class Judgement { + /// + /// The score awarded for a small bonus. + /// + public const double SMALL_BONUS_SCORE = 10; + + /// + /// The score awarded for a large bonus. + /// + public const double LARGE_BONUS_SCORE = 50; + /// /// The default health increase for a maximum judgement, as a proportion of total health. /// By default, each maximum judgement restores 5% of total health. /// protected const double DEFAULT_MAX_HEALTH_INCREASE = 0.05; + /// + /// Whether this should affect the current combo. + /// + [Obsolete("Has no effect. Use HitResult members instead (e.g. use small-tick or bonus to not affect combo).")] // Can be removed 20210328 + public virtual bool AffectsCombo => true; + + /// + /// Whether this should be counted as base (combo) or bonus score. + /// + [Obsolete("Has no effect. Use HitResult members instead (e.g. use small-tick or bonus to not affect combo).")] // Can be removed 20210328 + public virtual bool IsBonus => !AffectsCombo; + /// /// The maximum that can be achieved. /// public virtual HitResult MaxResult => HitResult.Perfect; /// - /// Whether this should affect the current combo. + /// The minimum that can be achieved - the inverse of . /// - public virtual bool AffectsCombo => true; + public HitResult MinResult + { + get + { + switch (MaxResult) + { + case HitResult.SmallBonus: + case HitResult.LargeBonus: + case HitResult.IgnoreHit: + return HitResult.IgnoreMiss; - /// - /// Whether this should be counted as base (combo) or bonus score. - /// - public virtual bool IsBonus => !AffectsCombo; + case HitResult.SmallTickHit: + return HitResult.SmallTickMiss; + + case HitResult.LargeTickHit: + return HitResult.LargeTickMiss; + + default: + return HitResult.Miss; + } + } + } /// /// The numeric score representation for the maximum achievable result. /// - public int MaxNumericResult => NumericResultFor(MaxResult); + public double MaxNumericResult => ToNumericResult(MaxResult); /// /// The health increase for the maximum achievable result. @@ -43,18 +82,19 @@ namespace osu.Game.Rulesets.Judgements public double MaxHealthIncrease => HealthIncreaseFor(MaxResult); /// - /// Retrieves the numeric score representation of a . + /// Retrieves the numeric score representation of a . /// - /// The to find the numeric score representation for. + /// The to find the numeric score representation for. /// The numeric score representation of . - protected virtual int NumericResultFor(HitResult result) => result > HitResult.Miss ? 1 : 0; + [Obsolete("Has no effect. Use ToNumericResult(HitResult) (standardised across all rulesets).")] // Can be removed 20210328 + protected virtual int NumericResultFor(HitResult result) => result == HitResult.Miss ? 0 : 1; /// /// Retrieves the numeric score representation of a . /// /// The to find the numeric score representation for. /// The numeric score representation of . - public int NumericResultFor(JudgementResult result) => NumericResultFor(result.Type); + public double NumericResultFor(JudgementResult result) => ToNumericResult(result.Type); /// /// Retrieves the numeric health increase of a . @@ -65,6 +105,21 @@ namespace osu.Game.Rulesets.Judgements { switch (result) { + default: + return 0; + + case HitResult.SmallTickHit: + return DEFAULT_MAX_HEALTH_INCREASE * 0.05; + + case HitResult.SmallTickMiss: + return -DEFAULT_MAX_HEALTH_INCREASE * 0.05; + + case HitResult.LargeTickHit: + return DEFAULT_MAX_HEALTH_INCREASE * 0.1; + + case HitResult.LargeTickMiss: + return -DEFAULT_MAX_HEALTH_INCREASE * 0.1; + case HitResult.Miss: return -DEFAULT_MAX_HEALTH_INCREASE; @@ -83,8 +138,11 @@ namespace osu.Game.Rulesets.Judgements case HitResult.Perfect: return DEFAULT_MAX_HEALTH_INCREASE * 1.05; - default: - return 0; + case HitResult.SmallBonus: + return DEFAULT_MAX_HEALTH_INCREASE * 0.1; + + case HitResult.LargeBonus: + return DEFAULT_MAX_HEALTH_INCREASE * 0.2; } } @@ -95,6 +153,42 @@ namespace osu.Game.Rulesets.Judgements /// The numeric health increase of . public double HealthIncreaseFor(JudgementResult result) => HealthIncreaseFor(result.Type); - public override string ToString() => $"AffectsCombo:{AffectsCombo} MaxResult:{MaxResult} MaxScore:{MaxNumericResult}"; + public override string ToString() => $"MaxResult:{MaxResult} MaxScore:{MaxNumericResult}"; + + public static double ToNumericResult(HitResult result) + { + switch (result) + { + default: + return 0; + + case HitResult.SmallTickHit: + return 1 / 30d; + + case HitResult.LargeTickHit: + return 1 / 10d; + + case HitResult.Meh: + return 1 / 6d; + + case HitResult.Ok: + return 1 / 3d; + + case HitResult.Good: + return 2 / 3d; + + case HitResult.Great: + return 1d; + + case HitResult.Perfect: + return 7 / 6d; + + case HitResult.SmallBonus: + return SMALL_BONUS_SCORE; + + case HitResult.LargeBonus: + return LARGE_BONUS_SCORE; + } + } } } From 4ca9a69de25dcf23cdfab050ed2632057ce50e19 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 29 Sep 2020 14:26:36 +0900 Subject: [PATCH 1307/1782] Use new hit results in catch --- .../Judgements/CatchBananaJudgement.cs | 26 +------------------ .../Judgements/CatchDropletJudgement.cs | 12 +-------- .../Judgements/CatchJudgement.cs | 14 +--------- .../Judgements/CatchTinyDropletJudgement.cs | 26 +------------------ .../Drawables/DrawableCatchHitObject.cs | 3 +-- .../UI/CatchComboDisplay.cs | 4 +-- osu.Game.Rulesets.Catch/UI/CatcherArea.cs | 3 ++- 7 files changed, 9 insertions(+), 79 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Judgements/CatchBananaJudgement.cs b/osu.Game.Rulesets.Catch/Judgements/CatchBananaJudgement.cs index a7449ba4e1..b919102215 100644 --- a/osu.Game.Rulesets.Catch/Judgements/CatchBananaJudgement.cs +++ b/osu.Game.Rulesets.Catch/Judgements/CatchBananaJudgement.cs @@ -8,31 +8,7 @@ namespace osu.Game.Rulesets.Catch.Judgements { public class CatchBananaJudgement : CatchJudgement { - public override bool AffectsCombo => false; - - protected override int NumericResultFor(HitResult result) - { - switch (result) - { - default: - return 0; - - case HitResult.Perfect: - return 1100; - } - } - - protected override double HealthIncreaseFor(HitResult result) - { - switch (result) - { - default: - return 0; - - case HitResult.Perfect: - return DEFAULT_MAX_HEALTH_INCREASE * 0.75; - } - } + public override HitResult MaxResult => HitResult.LargeBonus; public override bool ShouldExplodeFor(JudgementResult result) => true; } diff --git a/osu.Game.Rulesets.Catch/Judgements/CatchDropletJudgement.cs b/osu.Game.Rulesets.Catch/Judgements/CatchDropletJudgement.cs index e87ecba749..8fd7b93e4c 100644 --- a/osu.Game.Rulesets.Catch/Judgements/CatchDropletJudgement.cs +++ b/osu.Game.Rulesets.Catch/Judgements/CatchDropletJudgement.cs @@ -7,16 +7,6 @@ namespace osu.Game.Rulesets.Catch.Judgements { public class CatchDropletJudgement : CatchJudgement { - protected override int NumericResultFor(HitResult result) - { - switch (result) - { - default: - return 0; - - case HitResult.Perfect: - return 30; - } - } + public override HitResult MaxResult => HitResult.LargeTickHit; } } diff --git a/osu.Game.Rulesets.Catch/Judgements/CatchJudgement.cs b/osu.Game.Rulesets.Catch/Judgements/CatchJudgement.cs index 2149ed9712..ccafe0abc4 100644 --- a/osu.Game.Rulesets.Catch/Judgements/CatchJudgement.cs +++ b/osu.Game.Rulesets.Catch/Judgements/CatchJudgement.cs @@ -9,19 +9,7 @@ namespace osu.Game.Rulesets.Catch.Judgements { public class CatchJudgement : Judgement { - public override HitResult MaxResult => HitResult.Perfect; - - protected override int NumericResultFor(HitResult result) - { - switch (result) - { - default: - return 0; - - case HitResult.Perfect: - return 300; - } - } + public override HitResult MaxResult => HitResult.Great; /// /// Whether fruit on the platter should explode or drop. diff --git a/osu.Game.Rulesets.Catch/Judgements/CatchTinyDropletJudgement.cs b/osu.Game.Rulesets.Catch/Judgements/CatchTinyDropletJudgement.cs index d607b49ea4..d957d4171b 100644 --- a/osu.Game.Rulesets.Catch/Judgements/CatchTinyDropletJudgement.cs +++ b/osu.Game.Rulesets.Catch/Judgements/CatchTinyDropletJudgement.cs @@ -7,30 +7,6 @@ namespace osu.Game.Rulesets.Catch.Judgements { public class CatchTinyDropletJudgement : CatchJudgement { - public override bool AffectsCombo => false; - - protected override int NumericResultFor(HitResult result) - { - switch (result) - { - default: - return 0; - - case HitResult.Perfect: - return 10; - } - } - - protected override double HealthIncreaseFor(HitResult result) - { - switch (result) - { - default: - return 0; - - case HitResult.Perfect: - return 0.02; - } - } + public override HitResult MaxResult => HitResult.SmallTickHit; } } diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableCatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableCatchHitObject.cs index 2fe017dc62..d03a764bda 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableCatchHitObject.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableCatchHitObject.cs @@ -8,7 +8,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; -using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Catch.UI; using osuTK; using osuTK.Graphics; @@ -86,7 +85,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables if (CheckPosition == null) return; if (timeOffset >= 0 && Result != null) - ApplyResult(r => r.Type = CheckPosition.Invoke(HitObject) ? HitResult.Perfect : HitResult.Miss); + ApplyResult(r => r.Type = CheckPosition.Invoke(HitObject) ? r.Judgement.MaxResult : r.Judgement.MinResult); } protected override void UpdateStateTransforms(ArmedState state) diff --git a/osu.Game.Rulesets.Catch/UI/CatchComboDisplay.cs b/osu.Game.Rulesets.Catch/UI/CatchComboDisplay.cs index 58a3140bb5..cc01009dd9 100644 --- a/osu.Game.Rulesets.Catch/UI/CatchComboDisplay.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchComboDisplay.cs @@ -33,7 +33,7 @@ namespace osu.Game.Rulesets.Catch.UI public void OnNewResult(DrawableCatchHitObject judgedObject, JudgementResult result) { - if (!result.Judgement.AffectsCombo || !result.HasResult) + if (!result.Type.AffectsCombo() || !result.HasResult) return; if (result.Type == HitResult.Miss) @@ -47,7 +47,7 @@ namespace osu.Game.Rulesets.Catch.UI public void OnRevertResult(DrawableCatchHitObject judgedObject, JudgementResult result) { - if (!result.Judgement.AffectsCombo || !result.HasResult) + if (!result.Type.AffectsCombo() || !result.HasResult) return; updateCombo(result.ComboAtJudgement, judgedObject.AccentColour.Value); diff --git a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs index d3e63b0333..5e794a76aa 100644 --- a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs +++ b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs @@ -11,6 +11,7 @@ using osu.Game.Rulesets.Catch.Objects.Drawables; using osu.Game.Rulesets.Catch.Replays; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; using osuTK; @@ -52,7 +53,7 @@ namespace osu.Game.Rulesets.Catch.UI public void OnNewResult(DrawableCatchHitObject fruit, JudgementResult result) { - if (result.Judgement is IgnoreJudgement) + if (!result.Type.IsScorable()) return; void runAfterLoaded(Action action) From b1877b649ba53a00dc7c083c6d5799f6652e5d68 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 29 Sep 2020 14:29:43 +0900 Subject: [PATCH 1308/1782] Use new hit results in mania --- .../Judgements/HoldNoteTickJudgement.cs | 14 +---------- .../Judgements/ManiaJudgement.cs | 24 ------------------- .../Objects/Drawables/DrawableHoldNote.cs | 2 +- .../Objects/Drawables/DrawableHoldNoteTick.cs | 5 ++-- .../Skinning/ManiaLegacySkinTransformer.cs | 3 +++ 5 files changed, 7 insertions(+), 41 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Judgements/HoldNoteTickJudgement.cs b/osu.Game.Rulesets.Mania/Judgements/HoldNoteTickJudgement.cs index 28e5d2cc1b..ee6cbbc828 100644 --- a/osu.Game.Rulesets.Mania/Judgements/HoldNoteTickJudgement.cs +++ b/osu.Game.Rulesets.Mania/Judgements/HoldNoteTickJudgement.cs @@ -7,18 +7,6 @@ namespace osu.Game.Rulesets.Mania.Judgements { public class HoldNoteTickJudgement : ManiaJudgement { - protected override int NumericResultFor(HitResult result) => result == MaxResult ? 20 : 0; - - protected override double HealthIncreaseFor(HitResult result) - { - switch (result) - { - default: - return 0; - - case HitResult.Perfect: - return 0.01; - } - } + public override HitResult MaxResult => HitResult.LargeTickHit; } } diff --git a/osu.Game.Rulesets.Mania/Judgements/ManiaJudgement.cs b/osu.Game.Rulesets.Mania/Judgements/ManiaJudgement.cs index 53967ffa05..220dedc4a4 100644 --- a/osu.Game.Rulesets.Mania/Judgements/ManiaJudgement.cs +++ b/osu.Game.Rulesets.Mania/Judgements/ManiaJudgement.cs @@ -2,34 +2,10 @@ // See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Judgements; -using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Mania.Judgements { public class ManiaJudgement : Judgement { - protected override int NumericResultFor(HitResult result) - { - switch (result) - { - default: - return 0; - - case HitResult.Meh: - return 50; - - case HitResult.Ok: - return 100; - - case HitResult.Good: - return 200; - - case HitResult.Great: - return 300; - - case HitResult.Perfect: - return 350; - } - } } } diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs index 2ebcc5451a..ba6cad978d 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs @@ -239,7 +239,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables { if (Tail.AllJudged) { - ApplyResult(r => r.Type = HitResult.Perfect); + ApplyResult(r => r.Type = r.Judgement.MaxResult); endHold(); } diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTick.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTick.cs index 9b0322a6cd..98931dceed 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTick.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTick.cs @@ -8,7 +8,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; -using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Mania.Objects.Drawables { @@ -73,9 +72,9 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables var startTime = HoldStartTime?.Invoke(); if (startTime == null || startTime > HitObject.StartTime) - ApplyResult(r => r.Type = HitResult.Miss); + ApplyResult(r => r.Type = r.Judgement.MinResult); else - ApplyResult(r => r.Type = HitResult.Perfect); + ApplyResult(r => r.Type = r.Judgement.MaxResult); } } } diff --git a/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs index 439e6f7df2..3724269f4d 100644 --- a/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs @@ -130,6 +130,9 @@ namespace osu.Game.Rulesets.Mania.Skinning private Drawable getResult(HitResult result) { + if (!hitresult_mapping.ContainsKey(result)) + return null; + string filename = this.GetManiaSkinConfig(hitresult_mapping[result])?.Value ?? default_hitresult_skin_filenames[result]; From a77741927cf3e0b84e72cfde6210769047dd8d58 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 29 Sep 2020 14:35:43 +0900 Subject: [PATCH 1309/1782] Use new hit results in osu --- osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs | 2 +- osu.Game.Rulesets.Osu/Judgements/OsuIgnoreJudgement.cs | 6 +----- .../Objects/Drawables/DrawableSliderRepeat.cs | 3 +-- .../Objects/Drawables/DrawableSliderTail.cs | 3 +-- .../Objects/Drawables/DrawableSliderTick.cs | 3 +-- .../Objects/Drawables/DrawableSpinnerTick.cs | 4 +--- .../Objects/Drawables/Pieces/SpinnerBonusDisplay.cs | 3 ++- osu.Game.Rulesets.Osu/Objects/SliderRepeat.cs | 2 +- osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs | 4 +--- osu.Game.Rulesets.Osu/Objects/SliderTick.cs | 2 +- osu.Game.Rulesets.Osu/Objects/SpinnerBonusTick.cs | 6 +----- osu.Game.Rulesets.Osu/Objects/SpinnerTick.cs | 8 +------- 12 files changed, 13 insertions(+), 33 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs index f7909071ea..dbab048b25 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs @@ -146,7 +146,7 @@ namespace osu.Game.Rulesets.Osu.Tests { // multipled by 2 to nullify the score multiplier. (autoplay mod selected) var totalScore = ((ScoreExposedPlayer)Player).ScoreProcessor.TotalScore.Value * 2; - return totalScore == (int)(drawableSpinner.RotationTracker.RateAdjustedRotation / 360) * SpinnerTick.SCORE_PER_TICK; + return totalScore == (int)(drawableSpinner.RotationTracker.RateAdjustedRotation / 360) * new SpinnerTick().CreateJudgement().MaxNumericResult; }); addSeekStep(0); diff --git a/osu.Game.Rulesets.Osu/Judgements/OsuIgnoreJudgement.cs b/osu.Game.Rulesets.Osu/Judgements/OsuIgnoreJudgement.cs index e528f65dca..1999785efe 100644 --- a/osu.Game.Rulesets.Osu/Judgements/OsuIgnoreJudgement.cs +++ b/osu.Game.Rulesets.Osu/Judgements/OsuIgnoreJudgement.cs @@ -7,10 +7,6 @@ namespace osu.Game.Rulesets.Osu.Judgements { public class OsuIgnoreJudgement : OsuJudgement { - public override bool AffectsCombo => false; - - protected override int NumericResultFor(HitResult result) => 0; - - protected override double HealthIncreaseFor(HitResult result) => 0; + public override HitResult MaxResult => HitResult.IgnoreHit; } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs index d79ecb7b4e..f65077685f 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs @@ -9,7 +9,6 @@ using osu.Framework.Graphics; using osu.Framework.Utils; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces; -using osu.Game.Rulesets.Scoring; using osuTK; namespace osu.Game.Rulesets.Osu.Objects.Drawables @@ -50,7 +49,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables protected override void CheckForResult(bool userTriggered, double timeOffset) { if (sliderRepeat.StartTime <= Time.Current) - ApplyResult(r => r.Type = drawableSlider.Tracking.Value ? HitResult.Great : HitResult.Miss); + ApplyResult(r => r.Type = drawableSlider.Tracking.Value ? r.Judgement.MaxResult : r.Judgement.MinResult); } protected override void UpdateInitialTransforms() diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs index 29a4929c1b..0939e2847a 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.Scoring; using osuTK; namespace osu.Game.Rulesets.Osu.Objects.Drawables @@ -46,7 +45,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables protected override void CheckForResult(bool userTriggered, double timeOffset) { if (!userTriggered && timeOffset >= 0) - ApplyResult(r => r.Type = Tracking ? r.Judgement.MaxResult : HitResult.Miss); + ApplyResult(r => r.Type = Tracking ? r.Judgement.MaxResult : r.Judgement.MinResult); } private void updatePosition() => Position = HitObject.Position - slider.Position; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs index 66eb60aa28..9b68b446a4 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs @@ -10,7 +10,6 @@ using osuTK.Graphics; using osu.Framework.Graphics.Shapes; using osu.Game.Skinning; using osu.Framework.Graphics.Containers; -using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Osu.Objects.Drawables { @@ -64,7 +63,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables protected override void CheckForResult(bool userTriggered, double timeOffset) { if (timeOffset >= 0) - ApplyResult(r => r.Type = Tracking ? r.Judgement.MaxResult : HitResult.Miss); + ApplyResult(r => r.Type = Tracking ? r.Judgement.MaxResult : r.Judgement.MinResult); } protected override void UpdateInitialTransforms() diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinnerTick.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinnerTick.cs index c390b673be..e9cede1398 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinnerTick.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinnerTick.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.Game.Rulesets.Scoring; - namespace osu.Game.Rulesets.Osu.Objects.Drawables { public class DrawableSpinnerTick : DrawableOsuHitObject @@ -18,6 +16,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables /// Apply a judgement result. /// /// Whether this tick was reached. - internal void TriggerResult(bool hit) => ApplyResult(r => r.Type = hit ? r.Judgement.MaxResult : HitResult.Miss); + internal void TriggerResult(bool hit) => ApplyResult(r => r.Type = hit ? r.Judgement.MaxResult : r.Judgement.MinResult); } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerBonusDisplay.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerBonusDisplay.cs index b499d7a92b..1668cd73bd 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerBonusDisplay.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerBonusDisplay.cs @@ -5,6 +5,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; +using osu.Game.Rulesets.Judgements; namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces { @@ -36,7 +37,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces return; displayedCount = count; - bonusCounter.Text = $"{SpinnerBonusTick.SCORE_PER_TICK * count}"; + bonusCounter.Text = $"{Judgement.LARGE_BONUS_SCORE * count}"; bonusCounter.FadeOutFromOne(1500); bonusCounter.ScaleTo(1.5f).Then().ScaleTo(1f, 1000, Easing.OutQuint); } diff --git a/osu.Game.Rulesets.Osu/Objects/SliderRepeat.cs b/osu.Game.Rulesets.Osu/Objects/SliderRepeat.cs index ac6c6905e4..b6c58a75d1 100644 --- a/osu.Game.Rulesets.Osu/Objects/SliderRepeat.cs +++ b/osu.Game.Rulesets.Osu/Objects/SliderRepeat.cs @@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.Osu.Objects public class SliderRepeatJudgement : OsuJudgement { - protected override int NumericResultFor(HitResult result) => result == MaxResult ? 30 : 0; + public override HitResult MaxResult => HitResult.LargeTickHit; } } } diff --git a/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs b/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs index 1e54b576f1..aff3f38e17 100644 --- a/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs @@ -29,9 +29,7 @@ namespace osu.Game.Rulesets.Osu.Objects public class SliderTailJudgement : OsuJudgement { - protected override int NumericResultFor(HitResult result) => 0; - - public override bool AffectsCombo => false; + public override HitResult MaxResult => HitResult.IgnoreHit; } } } diff --git a/osu.Game.Rulesets.Osu/Objects/SliderTick.cs b/osu.Game.Rulesets.Osu/Objects/SliderTick.cs index 22f3f559db..a427ee1955 100644 --- a/osu.Game.Rulesets.Osu/Objects/SliderTick.cs +++ b/osu.Game.Rulesets.Osu/Objects/SliderTick.cs @@ -36,7 +36,7 @@ namespace osu.Game.Rulesets.Osu.Objects public class SliderTickJudgement : OsuJudgement { - protected override int NumericResultFor(HitResult result) => result == MaxResult ? 10 : 0; + public override HitResult MaxResult => HitResult.LargeTickHit; } } } diff --git a/osu.Game.Rulesets.Osu/Objects/SpinnerBonusTick.cs b/osu.Game.Rulesets.Osu/Objects/SpinnerBonusTick.cs index 0b1232b8db..235dc8710a 100644 --- a/osu.Game.Rulesets.Osu/Objects/SpinnerBonusTick.cs +++ b/osu.Game.Rulesets.Osu/Objects/SpinnerBonusTick.cs @@ -9,8 +9,6 @@ namespace osu.Game.Rulesets.Osu.Objects { public class SpinnerBonusTick : SpinnerTick { - public new const int SCORE_PER_TICK = 50; - public SpinnerBonusTick() { Samples.Add(new HitSampleInfo { Name = "spinnerbonus" }); @@ -20,9 +18,7 @@ namespace osu.Game.Rulesets.Osu.Objects public class OsuSpinnerBonusTickJudgement : OsuSpinnerTickJudgement { - protected override int NumericResultFor(HitResult result) => result == MaxResult ? SCORE_PER_TICK : 0; - - protected override double HealthIncreaseFor(HitResult result) => base.HealthIncreaseFor(result) * 2; + public override HitResult MaxResult => HitResult.LargeBonus; } } } diff --git a/osu.Game.Rulesets.Osu/Objects/SpinnerTick.cs b/osu.Game.Rulesets.Osu/Objects/SpinnerTick.cs index f54e7a9a15..d715b9a428 100644 --- a/osu.Game.Rulesets.Osu/Objects/SpinnerTick.cs +++ b/osu.Game.Rulesets.Osu/Objects/SpinnerTick.cs @@ -9,19 +9,13 @@ namespace osu.Game.Rulesets.Osu.Objects { public class SpinnerTick : OsuHitObject { - public const int SCORE_PER_TICK = 10; - public override Judgement CreateJudgement() => new OsuSpinnerTickJudgement(); protected override HitWindows CreateHitWindows() => HitWindows.Empty; public class OsuSpinnerTickJudgement : OsuJudgement { - public override bool AffectsCombo => false; - - protected override int NumericResultFor(HitResult result) => result == MaxResult ? SCORE_PER_TICK : 0; - - protected override double HealthIncreaseFor(HitResult result) => result == MaxResult ? 0.6 * base.HealthIncreaseFor(result) : 0; + public override HitResult MaxResult => HitResult.SmallBonus; } } } From c45b5690cfcc0469d01e27689f73e8c262a4a449 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 29 Sep 2020 15:13:11 +0900 Subject: [PATCH 1310/1782] Use new hit results in taiko --- .../Judgements/TaikoDrumRollTickJudgement.cs | 14 +------------- .../Judgements/TaikoJudgement.cs | 15 --------------- .../Judgements/TaikoStrongJudgement.cs | 4 ++-- .../Objects/Drawables/DrawableDrumRoll.cs | 4 ++-- .../Objects/Drawables/DrawableDrumRollTick.cs | 7 +++---- .../Objects/Drawables/DrawableHit.cs | 6 +++--- .../Objects/Drawables/DrawableSwell.cs | 6 +++--- .../Objects/Drawables/DrawableSwellTick.cs | 5 ++--- .../Skinning/LegacyTaikoScroller.cs | 2 +- osu.Game.Rulesets.Taiko/UI/DrawableTaikoMascot.cs | 5 +++-- 10 files changed, 20 insertions(+), 48 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Judgements/TaikoDrumRollTickJudgement.cs b/osu.Game.Rulesets.Taiko/Judgements/TaikoDrumRollTickJudgement.cs index a617028f1c..0551df3211 100644 --- a/osu.Game.Rulesets.Taiko/Judgements/TaikoDrumRollTickJudgement.cs +++ b/osu.Game.Rulesets.Taiko/Judgements/TaikoDrumRollTickJudgement.cs @@ -7,19 +7,7 @@ namespace osu.Game.Rulesets.Taiko.Judgements { public class TaikoDrumRollTickJudgement : TaikoJudgement { - public override bool AffectsCombo => false; - - protected override int NumericResultFor(HitResult result) - { - switch (result) - { - case HitResult.Great: - return 200; - - default: - return 0; - } - } + public override HitResult MaxResult => HitResult.SmallTickHit; protected override double HealthIncreaseFor(HitResult result) { diff --git a/osu.Game.Rulesets.Taiko/Judgements/TaikoJudgement.cs b/osu.Game.Rulesets.Taiko/Judgements/TaikoJudgement.cs index eb5f443365..3d22860814 100644 --- a/osu.Game.Rulesets.Taiko/Judgements/TaikoJudgement.cs +++ b/osu.Game.Rulesets.Taiko/Judgements/TaikoJudgement.cs @@ -10,21 +10,6 @@ namespace osu.Game.Rulesets.Taiko.Judgements { public override HitResult MaxResult => HitResult.Great; - protected override int NumericResultFor(HitResult result) - { - switch (result) - { - case HitResult.Good: - return 100; - - case HitResult.Great: - return 300; - - default: - return 0; - } - } - protected override double HealthIncreaseFor(HitResult result) { switch (result) diff --git a/osu.Game.Rulesets.Taiko/Judgements/TaikoStrongJudgement.cs b/osu.Game.Rulesets.Taiko/Judgements/TaikoStrongJudgement.cs index e045ea324f..06495ad9f4 100644 --- a/osu.Game.Rulesets.Taiko/Judgements/TaikoStrongJudgement.cs +++ b/osu.Game.Rulesets.Taiko/Judgements/TaikoStrongJudgement.cs @@ -7,9 +7,9 @@ namespace osu.Game.Rulesets.Taiko.Judgements { public class TaikoStrongJudgement : TaikoJudgement { + public override HitResult MaxResult => HitResult.SmallBonus; + // MainObject already changes the HP protected override double HealthIncreaseFor(HitResult result) => 0; - - public override bool AffectsCombo => false; } } diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs index 2c1c2d2bc1..286feac5ba 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs @@ -129,7 +129,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables if (countHit >= HitObject.RequiredGoodHits) { - ApplyResult(r => r.Type = countHit >= HitObject.RequiredGreatHits ? HitResult.Great : HitResult.Good); + ApplyResult(r => r.Type = countHit >= HitObject.RequiredGreatHits ? HitResult.Great : HitResult.Ok); } else ApplyResult(r => r.Type = HitResult.Miss); @@ -174,7 +174,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables if (!MainObject.Judged) return; - ApplyResult(r => r.Type = MainObject.IsHit ? HitResult.Great : HitResult.Miss); + ApplyResult(r => r.Type = MainObject.IsHit ? r.Judgement.MaxResult : r.Judgement.MinResult); } public override bool OnPressed(TaikoAction action) => false; diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs index 62405cf047..9d7dcc7218 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs @@ -4,7 +4,6 @@ using System; using osu.Framework.Graphics; using osu.Game.Rulesets.Objects.Drawables; -using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces; using osu.Game.Skinning; @@ -34,14 +33,14 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables if (!userTriggered) { if (timeOffset > HitObject.HitWindow) - ApplyResult(r => r.Type = HitResult.Miss); + ApplyResult(r => r.Type = r.Judgement.MinResult); return; } if (Math.Abs(timeOffset) > HitObject.HitWindow) return; - ApplyResult(r => r.Type = HitResult.Great); + ApplyResult(r => r.Type = r.Judgement.MaxResult); } protected override void UpdateStateTransforms(ArmedState state) @@ -74,7 +73,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables if (!MainObject.Judged) return; - ApplyResult(r => r.Type = MainObject.IsHit ? HitResult.Great : HitResult.Miss); + ApplyResult(r => r.Type = MainObject.IsHit ? r.Judgement.MaxResult : r.Judgement.MinResult); } public override bool OnPressed(TaikoAction action) => false; diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs index 3a6eaa83db..03df28f850 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs @@ -257,19 +257,19 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables if (!MainObject.Result.IsHit) { - ApplyResult(r => r.Type = HitResult.Miss); + ApplyResult(r => r.Type = r.Judgement.MinResult); return; } if (!userTriggered) { if (timeOffset - MainObject.Result.TimeOffset > second_hit_window) - ApplyResult(r => r.Type = HitResult.Miss); + ApplyResult(r => r.Type = r.Judgement.MinResult); return; } if (Math.Abs(timeOffset - MainObject.Result.TimeOffset) <= second_hit_window) - ApplyResult(r => r.Type = MainObject.Result.Type); + ApplyResult(r => r.Type = r.Judgement.MaxResult); } public override bool OnPressed(TaikoAction action) diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs index 7294587b10..11ff0729e2 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs @@ -175,7 +175,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables } } - nextTick?.TriggerResult(HitResult.Great); + nextTick?.TriggerResult(true); var numHits = ticks.Count(r => r.IsHit); @@ -208,10 +208,10 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables continue; } - tick.TriggerResult(HitResult.Miss); + tick.TriggerResult(false); } - var hitResult = numHits > HitObject.RequiredHits / 2 ? HitResult.Good : HitResult.Miss; + var hitResult = numHits > HitObject.RequiredHits / 2 ? HitResult.Ok : HitResult.Miss; ApplyResult(r => r.Type = hitResult); } diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwellTick.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwellTick.cs index 1685576f0d..6202583494 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwellTick.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwellTick.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; -using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces; using osu.Game.Skinning; @@ -19,10 +18,10 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables protected override void UpdateInitialTransforms() => this.FadeOut(); - public void TriggerResult(HitResult type) + public void TriggerResult(bool hit) { HitObject.StartTime = Time.Current; - ApplyResult(r => r.Type = type); + ApplyResult(r => r.Type = hit ? r.Judgement.MaxResult : r.Judgement.MinResult); } protected override void CheckForResult(bool userTriggered, double timeOffset) diff --git a/osu.Game.Rulesets.Taiko/Skinning/LegacyTaikoScroller.cs b/osu.Game.Rulesets.Taiko/Skinning/LegacyTaikoScroller.cs index 03813e0a99..928072c491 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/LegacyTaikoScroller.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/LegacyTaikoScroller.cs @@ -42,7 +42,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning var r = result.NewValue; // always ignore hitobjects that don't affect combo (drumroll ticks etc.) - if (r?.Judgement.AffectsCombo == false) + if (r?.Type.AffectsCombo() == false) return; passing = r == null || r.Type > HitResult.Miss; diff --git a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoMascot.cs b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoMascot.cs index b937beae3c..6a16f311bf 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoMascot.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoMascot.cs @@ -11,6 +11,7 @@ using osu.Framework.Graphics.Textures; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics.Containers; using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Taiko.Judgements; using osu.Game.Screens.Play; @@ -77,7 +78,7 @@ namespace osu.Game.Rulesets.Taiko.UI lastObjectHit = true; } - if (!result.Judgement.AffectsCombo) + if (!result.Type.AffectsCombo()) return; lastObjectHit = result.IsHit; @@ -115,7 +116,7 @@ namespace osu.Game.Rulesets.Taiko.UI } private bool triggerComboClear(JudgementResult judgementResult) - => (judgementResult.ComboAtJudgement + 1) % 50 == 0 && judgementResult.Judgement.AffectsCombo && judgementResult.IsHit; + => (judgementResult.ComboAtJudgement + 1) % 50 == 0 && judgementResult.Type.AffectsCombo() && judgementResult.IsHit; private bool triggerSwellClear(JudgementResult judgementResult) => judgementResult.Judgement is TaikoSwellJudgement && judgementResult.IsHit; From 6264a01eccff56acb06f74b04d2a40534bf263d8 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 29 Sep 2020 15:14:03 +0900 Subject: [PATCH 1311/1782] Add guard against using the wrong hit result --- .../Objects/Drawables/DrawableHitObject.cs | 15 ++++++++++++++ osu.Game/Rulesets/Scoring/HitResult.cs | 20 +++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 7c05bc9aa7..2a3e3716e5 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -475,6 +475,21 @@ 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)}."); + // Some (especially older) rulesets use scorable judgements instead of the newer ignorehit/ignoremiss judgements. + if (Result.Judgement.MaxResult == HitResult.IgnoreHit) + { + if (Result.Type == HitResult.Miss) + Result.Type = HitResult.IgnoreMiss; + else if (Result.Type >= HitResult.Meh && Result.Type <= HitResult.Perfect) + Result.Type = HitResult.IgnoreHit; + } + + if (!Result.Type.IsValidHitResult(Result.Judgement.MinResult, Result.Judgement.MaxResult)) + { + throw new InvalidOperationException( + $"{GetType().ReadableName()} applied an invalid hit result (was: {Result.Type}, expected: [{Result.Judgement.MinResult} ... {Result.Judgement.MaxResult}])."); + } + // Ensure that the judgement is given a valid time offset, because this may not get set by the caller var endTime = HitObject.GetEndTime(); diff --git a/osu.Game/Rulesets/Scoring/HitResult.cs b/osu.Game/Rulesets/Scoring/HitResult.cs index fc33ff9df2..7a02db190a 100644 --- a/osu.Game/Rulesets/Scoring/HitResult.cs +++ b/osu.Game/Rulesets/Scoring/HitResult.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System.ComponentModel; +using System.Diagnostics; using osu.Game.Utils; namespace osu.Game.Rulesets.Scoring @@ -175,5 +176,24 @@ namespace osu.Game.Rulesets.Scoring /// Whether a is scorable. /// public static bool IsScorable(this HitResult result) => result >= HitResult.Miss && result < HitResult.IgnoreMiss; + + /// + /// Whether a is valid within a given range. + /// + /// The to check. + /// The minimum . + /// The maximum . + /// Whether falls between and . + public static bool IsValidHitResult(this HitResult result, HitResult minResult, HitResult maxResult) + { + if (result == HitResult.None) + return false; + + if (result == minResult || result == maxResult) + return true; + + Debug.Assert(minResult <= maxResult); + return result > minResult && result < maxResult; + } } } From a1394c183056c52a4d1648d6120a2a65e85fa21b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 29 Sep 2020 15:20:47 +0900 Subject: [PATCH 1312/1782] Fix a few missed judgements --- .../Judgements/OsuJudgement.cs | 18 ------------------ .../Rulesets/Judgements/IgnoreJudgement.cs | 6 +----- .../Rulesets/Judgements/JudgementResult.cs | 2 +- 3 files changed, 2 insertions(+), 24 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Judgements/OsuJudgement.cs b/osu.Game.Rulesets.Osu/Judgements/OsuJudgement.cs index bf30fbc351..1a88e2a8b2 100644 --- a/osu.Game.Rulesets.Osu/Judgements/OsuJudgement.cs +++ b/osu.Game.Rulesets.Osu/Judgements/OsuJudgement.cs @@ -9,23 +9,5 @@ namespace osu.Game.Rulesets.Osu.Judgements public class OsuJudgement : Judgement { public override HitResult MaxResult => HitResult.Great; - - protected override int NumericResultFor(HitResult result) - { - switch (result) - { - default: - return 0; - - case HitResult.Meh: - return 50; - - case HitResult.Good: - return 100; - - case HitResult.Great: - return 300; - } - } } } diff --git a/osu.Game/Rulesets/Judgements/IgnoreJudgement.cs b/osu.Game/Rulesets/Judgements/IgnoreJudgement.cs index 1871249c94..d2a434058d 100644 --- a/osu.Game/Rulesets/Judgements/IgnoreJudgement.cs +++ b/osu.Game/Rulesets/Judgements/IgnoreJudgement.cs @@ -7,10 +7,6 @@ namespace osu.Game.Rulesets.Judgements { public class IgnoreJudgement : Judgement { - public override bool AffectsCombo => false; - - protected override int NumericResultFor(HitResult result) => 0; - - protected override double HealthIncreaseFor(HitResult result) => 0; + public override HitResult MaxResult => HitResult.IgnoreHit; } } diff --git a/osu.Game/Rulesets/Judgements/JudgementResult.cs b/osu.Game/Rulesets/Judgements/JudgementResult.cs index 59a7917e55..3a35fd4433 100644 --- a/osu.Game/Rulesets/Judgements/JudgementResult.cs +++ b/osu.Game/Rulesets/Judgements/JudgementResult.cs @@ -64,7 +64,7 @@ namespace osu.Game.Rulesets.Judgements /// /// Whether a successful hit occurred. /// - public bool IsHit => Type > HitResult.Miss; + public bool IsHit => Type.IsHit(); /// /// Creates a new . From 31fae045fa26854c3d4af4c988b03ea792aa9137 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 29 Sep 2020 15:25:31 +0900 Subject: [PATCH 1313/1782] Update judgement processors with new hit results --- .../Scoring/CatchScoreProcessor.cs | 1 - .../Scoring/ManiaScoreProcessor.cs | 2 - .../Scoring/OsuScoreProcessor.cs | 2 - .../Scoring/TaikoScoreProcessor.cs | 2 - .../TestSceneDrainingHealthProcessor.cs | 16 ++--- .../Gameplay/TestSceneScoreProcessor.cs | 32 +++------- .../Scoring/DrainingHealthProcessor.cs | 2 +- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 61 ++++++++----------- osu.Game/Scoring/ScoreManager.cs | 2 +- 9 files changed, 47 insertions(+), 73 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs b/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs index 4c7bc4ab73..2cc05826b4 100644 --- a/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs +++ b/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs @@ -7,6 +7,5 @@ namespace osu.Game.Rulesets.Catch.Scoring { public class CatchScoreProcessor : ScoreProcessor { - public override HitWindows CreateHitWindows() => new CatchHitWindows(); } } diff --git a/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs b/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs index 4b2f643333..71cc0bdf1f 100644 --- a/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs +++ b/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs @@ -10,7 +10,5 @@ namespace osu.Game.Rulesets.Mania.Scoring protected override double DefaultAccuracyPortion => 0.95; protected override double DefaultComboPortion => 0.05; - - public override HitWindows CreateHitWindows() => new ManiaHitWindows(); } } diff --git a/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs b/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs index 86ec76e373..44118227d9 100644 --- a/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs +++ b/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs @@ -25,7 +25,5 @@ namespace osu.Game.Rulesets.Osu.Scoring return new OsuJudgementResult(hitObject, judgement); } } - - public override HitWindows CreateHitWindows() => new OsuHitWindows(); } } diff --git a/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs b/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs index e29ea87d25..1829ea2513 100644 --- a/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs +++ b/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs @@ -10,7 +10,5 @@ namespace osu.Game.Rulesets.Taiko.Scoring protected override double DefaultAccuracyPortion => 0.75; protected override double DefaultComboPortion => 0.25; - - public override HitWindows CreateHitWindows() => new TaikoHitWindows(); } } diff --git a/osu.Game.Tests/Gameplay/TestSceneDrainingHealthProcessor.cs b/osu.Game.Tests/Gameplay/TestSceneDrainingHealthProcessor.cs index 460ad1b898..0bb2c4b60c 100644 --- a/osu.Game.Tests/Gameplay/TestSceneDrainingHealthProcessor.cs +++ b/osu.Game.Tests/Gameplay/TestSceneDrainingHealthProcessor.cs @@ -167,7 +167,7 @@ namespace osu.Game.Tests.Gameplay beatmap.HitObjects.Add(new JudgeableHitObject { StartTime = 0 }); for (double time = 0; time < 5000; time += 100) - beatmap.HitObjects.Add(new JudgeableHitObject(false) { StartTime = time }); + beatmap.HitObjects.Add(new JudgeableHitObject(HitResult.LargeBonus) { StartTime = time }); beatmap.HitObjects.Add(new JudgeableHitObject { StartTime = 5000 }); createProcessor(beatmap); @@ -215,23 +215,23 @@ namespace osu.Game.Tests.Gameplay private class JudgeableHitObject : HitObject { - private readonly bool affectsCombo; + private readonly HitResult maxResult; - public JudgeableHitObject(bool affectsCombo = true) + public JudgeableHitObject(HitResult maxResult = HitResult.Perfect) { - this.affectsCombo = affectsCombo; + this.maxResult = maxResult; } - public override Judgement CreateJudgement() => new TestJudgement(affectsCombo); + public override Judgement CreateJudgement() => new TestJudgement(maxResult); protected override HitWindows CreateHitWindows() => new HitWindows(); private class TestJudgement : Judgement { - public override bool AffectsCombo { get; } + public override HitResult MaxResult { get; } - public TestJudgement(bool affectsCombo) + public TestJudgement(HitResult maxResult) { - AffectsCombo = affectsCombo; + MaxResult = maxResult; } } } diff --git a/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs b/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs index c9ab4fa489..432e3df95e 100644 --- a/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs +++ b/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs @@ -17,13 +17,13 @@ namespace osu.Game.Tests.Gameplay [Test] public void TestNoScoreIncreaseFromMiss() { - var beatmap = new Beatmap { HitObjects = { new TestHitObject() } }; + var beatmap = new Beatmap { HitObjects = { new HitObject() } }; var scoreProcessor = new ScoreProcessor(); scoreProcessor.ApplyBeatmap(beatmap); // Apply a miss judgement - scoreProcessor.ApplyResult(new JudgementResult(new TestHitObject(), new TestJudgement()) { Type = HitResult.Miss }); + scoreProcessor.ApplyResult(new JudgementResult(new HitObject(), new TestJudgement()) { Type = HitResult.Miss }); Assert.That(scoreProcessor.TotalScore.Value, Is.EqualTo(0.0)); } @@ -31,37 +31,25 @@ namespace osu.Game.Tests.Gameplay [Test] public void TestOnlyBonusScore() { - var beatmap = new Beatmap { HitObjects = { new TestBonusHitObject() } }; + var beatmap = new Beatmap { HitObjects = { new HitObject() } }; var scoreProcessor = new ScoreProcessor(); scoreProcessor.ApplyBeatmap(beatmap); // Apply a judgement - scoreProcessor.ApplyResult(new JudgementResult(new TestBonusHitObject(), new TestBonusJudgement()) { Type = HitResult.Perfect }); + scoreProcessor.ApplyResult(new JudgementResult(new HitObject(), new TestJudgement(HitResult.LargeBonus)) { Type = HitResult.LargeBonus }); - Assert.That(scoreProcessor.TotalScore.Value, Is.EqualTo(100)); - } - - private class TestHitObject : HitObject - { - public override Judgement CreateJudgement() => new TestJudgement(); + Assert.That(scoreProcessor.TotalScore.Value, Is.EqualTo(Judgement.LARGE_BONUS_SCORE)); } private class TestJudgement : Judgement { - protected override int NumericResultFor(HitResult result) => 100; - } + public override HitResult MaxResult { get; } - private class TestBonusHitObject : HitObject - { - public override Judgement CreateJudgement() => new TestBonusJudgement(); - } - - private class TestBonusJudgement : Judgement - { - public override bool AffectsCombo => false; - - protected override int NumericResultFor(HitResult result) => 100; + public TestJudgement(HitResult maxResult = HitResult.Perfect) + { + MaxResult = maxResult; + } } } } diff --git a/osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs b/osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs index 130907b242..91793e3f70 100644 --- a/osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs +++ b/osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs @@ -115,7 +115,7 @@ namespace osu.Game.Rulesets.Scoring { base.ApplyResultInternal(result); - if (!result.Judgement.IsBonus) + if (!result.Type.IsBonus()) healthIncreases.Add((result.HitObject.GetEndTime() + result.TimeOffset, GetHealthIncreaseFor(result))); } diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 6fa5a87c8e..7a5b707357 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -71,7 +71,6 @@ namespace osu.Game.Rulesets.Scoring private double maxBaseScore; private double rollingMaxBaseScore; private double baseScore; - private double bonusScore; private readonly List hitEvents = new List(); private HitObject lastHitObject; @@ -116,14 +115,15 @@ namespace osu.Game.Rulesets.Scoring if (result.FailedAtJudgement) return; - if (result.Judgement.AffectsCombo) + if (!result.Type.IsScorable()) + return; + + if (result.Type.AffectsCombo()) { switch (result.Type) { - case HitResult.None: - break; - case HitResult.Miss: + case HitResult.LargeTickMiss: Combo.Value = 0; break; @@ -133,22 +133,16 @@ namespace osu.Game.Rulesets.Scoring } } - double scoreIncrease = result.Type == HitResult.Miss ? 0 : result.Judgement.NumericResultFor(result); + double scoreIncrease = result.Type.IsHit() ? result.Judgement.NumericResultFor(result) : 0; - if (result.Judgement.IsBonus) + if (!result.Type.IsBonus()) { - if (result.IsHit) - bonusScore += scoreIncrease; - } - else - { - if (result.HasResult) - scoreResultCounts[result.Type] = scoreResultCounts.GetOrDefault(result.Type) + 1; - baseScore += scoreIncrease; rollingMaxBaseScore += result.Judgement.MaxNumericResult; } + scoreResultCounts[result.Type] = scoreResultCounts.GetOrDefault(result.Type) + 1; + hitEvents.Add(CreateHitEvent(result)); lastHitObject = result.HitObject; @@ -171,22 +165,19 @@ namespace osu.Game.Rulesets.Scoring if (result.FailedAtJudgement) return; - double scoreIncrease = result.Type == HitResult.Miss ? 0 : result.Judgement.NumericResultFor(result); + if (!result.Type.IsScorable()) + return; - if (result.Judgement.IsBonus) - { - if (result.IsHit) - bonusScore -= scoreIncrease; - } - else - { - if (result.HasResult) - scoreResultCounts[result.Type] = scoreResultCounts.GetOrDefault(result.Type) - 1; + double scoreIncrease = result.Type.IsHit() ? result.Judgement.NumericResultFor(result) : 0; + if (!result.Type.IsBonus()) + { baseScore -= scoreIncrease; rollingMaxBaseScore -= result.Judgement.MaxNumericResult; } + scoreResultCounts[result.Type] = scoreResultCounts.GetOrDefault(result.Type) - 1; + Debug.Assert(hitEvents.Count > 0); lastHitObject = hitEvents[^1].LastHitObject; hitEvents.RemoveAt(hitEvents.Count - 1); @@ -207,7 +198,7 @@ namespace osu.Game.Rulesets.Scoring return GetScore(mode, maxHighestCombo, maxBaseScore > 0 ? baseScore / maxBaseScore : 0, maxHighestCombo > 0 ? (double)HighestCombo.Value / maxHighestCombo : 0, - bonusScore); + scoreResultCounts); } /// @@ -217,9 +208,9 @@ namespace osu.Game.Rulesets.Scoring /// The maximum combo achievable in the beatmap. /// The accuracy percentage achieved by the player. /// The proportion of achieved by the player. - /// Any bonus score to be added. + /// Any statistics to be factored in. /// The total score. - public double GetScore(ScoringMode mode, int maxCombo, double accuracyRatio, double comboRatio, double bonusScore) + public double GetScore(ScoringMode mode, int maxCombo, double accuracyRatio, double comboRatio, Dictionary statistics) { switch (mode) { @@ -228,14 +219,18 @@ namespace osu.Game.Rulesets.Scoring double accuracyScore = accuracyPortion * accuracyRatio; double comboScore = comboPortion * comboRatio; - return (max_score * (accuracyScore + comboScore) + bonusScore) * scoreMultiplier; + return (max_score * (accuracyScore + comboScore) + getBonusScore(statistics)) * 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 + (accuracyRatio * maxCombo * 300) * (1 + Math.Max(0, (comboRatio * maxCombo) - 1) * scoreMultiplier / 25); + return getBonusScore(statistics) + (accuracyRatio * maxCombo * 300) * (1 + Math.Max(0, (comboRatio * maxCombo) - 1) * scoreMultiplier / 25); } } + private double getBonusScore(Dictionary statistics) + => statistics.GetOrDefault(HitResult.SmallBonus) * Judgement.SMALL_BONUS_SCORE + + statistics.GetOrDefault(HitResult.LargeBonus) * Judgement.LARGE_BONUS_SCORE; + private ScoreRank rankFrom(double acc) { if (acc == 1) @@ -282,7 +277,6 @@ namespace osu.Game.Rulesets.Scoring baseScore = 0; rollingMaxBaseScore = 0; - bonusScore = 0; TotalScore.Value = 0; Accuracy.Value = 1; @@ -309,9 +303,7 @@ namespace osu.Game.Rulesets.Scoring 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))) + foreach (var result in Enum.GetValues(typeof(HitResult)).OfType().Where(r => r.IsScorable())) score.Statistics[result] = GetStatistic(result); score.HitEvents = hitEvents; @@ -320,6 +312,7 @@ namespace osu.Game.Rulesets.Scoring /// /// Create a for this processor. /// + [Obsolete("Method is now unused.")] // Can be removed 20210328 public virtual HitWindows CreateHitWindows() => new HitWindows(); } diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index 619ca76598..561ca631b3 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -181,7 +181,7 @@ namespace osu.Game.Scoring scoreProcessor.Mods.Value = score.Mods; - Value = (long)Math.Round(scoreProcessor.GetScore(ScoringMode.Value, beatmapMaxCombo, score.Accuracy, (double)score.MaxCombo / beatmapMaxCombo, 0)); + Value = (long)Math.Round(scoreProcessor.GetScore(ScoringMode.Value, beatmapMaxCombo, score.Accuracy, (double)score.MaxCombo / beatmapMaxCombo, score.Statistics)); } } From bc8f6a58fd307302cb998e1e7af9fc79dd443740 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 29 Sep 2020 15:26:42 +0900 Subject: [PATCH 1314/1782] Update PF/SD with new hit results --- osu.Game/Rulesets/Mods/ModPerfect.cs | 3 +-- osu.Game/Rulesets/Mods/ModSuddenDeath.cs | 4 +++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game/Rulesets/Mods/ModPerfect.cs b/osu.Game/Rulesets/Mods/ModPerfect.cs index 65f1a972ed..df0fc9c4b6 100644 --- a/osu.Game/Rulesets/Mods/ModPerfect.cs +++ b/osu.Game/Rulesets/Mods/ModPerfect.cs @@ -16,8 +16,7 @@ namespace osu.Game.Rulesets.Mods public override string Description => "SS or quit."; protected override bool FailCondition(HealthProcessor healthProcessor, JudgementResult result) - => !(result.Judgement is IgnoreJudgement) - && result.Judgement.AffectsCombo + => result.Type.AffectsAccuracy() && result.Type != result.Judgement.MaxResult; } } diff --git a/osu.Game/Rulesets/Mods/ModSuddenDeath.cs b/osu.Game/Rulesets/Mods/ModSuddenDeath.cs index df10262845..ae71041a64 100644 --- a/osu.Game/Rulesets/Mods/ModSuddenDeath.cs +++ b/osu.Game/Rulesets/Mods/ModSuddenDeath.cs @@ -29,6 +29,8 @@ namespace osu.Game.Rulesets.Mods healthProcessor.FailConditions += FailCondition; } - protected virtual bool FailCondition(HealthProcessor healthProcessor, JudgementResult result) => !result.IsHit && result.Judgement.AffectsCombo; + protected virtual bool FailCondition(HealthProcessor healthProcessor, JudgementResult result) + => result.Type.AffectsCombo() + && !result.IsHit; } } From 4ef7ab28728e9c4543b5a049b153f742a925378b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 29 Sep 2020 15:36:08 +0900 Subject: [PATCH 1315/1782] Fix tests --- .../Mods/TestSceneCatchModPerfect.cs | 2 +- .../Skinning/TestSceneDrawableTaikoMascot.cs | 4 ++-- osu.Game.Tests/Gameplay/TestSceneDrainingHealthProcessor.cs | 2 +- osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModPerfect.cs b/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModPerfect.cs index c1b7214d72..3e06e78dba 100644 --- a/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModPerfect.cs +++ b/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModPerfect.cs @@ -50,7 +50,7 @@ namespace osu.Game.Rulesets.Catch.Tests.Mods public void TestDroplet(bool shouldMiss) => CreateHitObjectTest(new HitObjectTestData(new Droplet { StartTime = 1000 }), shouldMiss); // We only care about testing misses, hits are tested via JuiceStream - [TestCase(false)] + [TestCase(true)] public void TestTinyDroplet(bool shouldMiss) => CreateHitObjectTest(new HitObjectTestData(new TinyDroplet { StartTime = 1000 }), shouldMiss); } } diff --git a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableTaikoMascot.cs b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableTaikoMascot.cs index 36289bda93..ae42bf8a90 100644 --- a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableTaikoMascot.cs +++ b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableTaikoMascot.cs @@ -92,7 +92,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning createDrawableRuleset(); assertStateAfterResult(new JudgementResult(new Hit(), new TaikoJudgement()) { Type = HitResult.Great }, TaikoMascotAnimationState.Idle); - assertStateAfterResult(new JudgementResult(new StrongHitObject(), new TaikoStrongJudgement()) { Type = HitResult.Miss }, TaikoMascotAnimationState.Idle); + assertStateAfterResult(new JudgementResult(new StrongHitObject(), new TaikoStrongJudgement()) { Type = HitResult.IgnoreMiss }, TaikoMascotAnimationState.Idle); } [Test] @@ -103,7 +103,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning createDrawableRuleset(); assertStateAfterResult(new JudgementResult(new Hit(), new TaikoJudgement()) { Type = HitResult.Good }, TaikoMascotAnimationState.Kiai); - assertStateAfterResult(new JudgementResult(new Hit(), new TaikoStrongJudgement()) { Type = HitResult.Miss }, TaikoMascotAnimationState.Kiai); + assertStateAfterResult(new JudgementResult(new Hit(), new TaikoStrongJudgement()) { Type = HitResult.IgnoreMiss }, TaikoMascotAnimationState.Kiai); assertStateAfterResult(new JudgementResult(new Hit(), new TaikoJudgement()) { Type = HitResult.Miss }, TaikoMascotAnimationState.Fail); } diff --git a/osu.Game.Tests/Gameplay/TestSceneDrainingHealthProcessor.cs b/osu.Game.Tests/Gameplay/TestSceneDrainingHealthProcessor.cs index efeb5eeba2..7264083338 100644 --- a/osu.Game.Tests/Gameplay/TestSceneDrainingHealthProcessor.cs +++ b/osu.Game.Tests/Gameplay/TestSceneDrainingHealthProcessor.cs @@ -263,7 +263,7 @@ namespace osu.Game.Tests.Gameplay public double Duration { get; set; } = 5000; public JudgeableLongHitObject() - : base(false) + : base(HitResult.LargeBonus) { } diff --git a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs index 64d1024efb..84b7bc3723 100644 --- a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs +++ b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs @@ -35,10 +35,10 @@ namespace osu.Game.Tests.Rulesets.Scoring } [TestCase(ScoringMode.Standardised, HitResult.Meh, 750_000)] - [TestCase(ScoringMode.Standardised, HitResult.Good, 800_000)] + [TestCase(ScoringMode.Standardised, HitResult.Good, 900_000)] [TestCase(ScoringMode.Standardised, HitResult.Great, 1_000_000)] [TestCase(ScoringMode.Classic, HitResult.Meh, 50)] - [TestCase(ScoringMode.Classic, HitResult.Good, 100)] + [TestCase(ScoringMode.Classic, HitResult.Good, 200)] [TestCase(ScoringMode.Classic, HitResult.Great, 300)] public void TestSingleOsuHit(ScoringMode scoringMode, HitResult hitResult, int expectedScore) { From e789e06c86ac973754a4f71b053b7b1becdb97f7 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 29 Sep 2020 16:00:45 +0900 Subject: [PATCH 1316/1782] Don't display hold note tick judgements --- .../Objects/Drawables/DrawableHoldNoteTick.cs | 2 ++ osu.Game.Rulesets.Mania/UI/Column.cs | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTick.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTick.cs index 98931dceed..f265419aa0 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTick.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTick.cs @@ -16,6 +16,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables /// public class DrawableHoldNoteTick : DrawableManiaHitObject { + public override bool DisplayResult => false; + /// /// References the time at which the user started holding the hold note. /// diff --git a/osu.Game.Rulesets.Mania/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs index 9aabcc6699..c28a1c13d8 100644 --- a/osu.Game.Rulesets.Mania/UI/Column.cs +++ b/osu.Game.Rulesets.Mania/UI/Column.cs @@ -114,7 +114,7 @@ namespace osu.Game.Rulesets.Mania.UI if (result.IsHit) hitPolicy.HandleHit(judgedObject); - if (!result.IsHit || !judgedObject.DisplayResult || !DisplayJudgements.Value) + if (!result.IsHit || !DisplayJudgements.Value) return; HitObjectArea.Explosions.Add(hitExplosionPool.Get(e => e.Apply(result))); From 903bcd747e777757150310877bf256594b818bcf Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 29 Sep 2020 16:39:29 +0900 Subject: [PATCH 1317/1782] Revert unintended changes --- osu.Game.Rulesets.Catch/Judgements/CatchJudgement.cs | 2 +- osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs | 2 +- osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Judgements/CatchJudgement.cs b/osu.Game.Rulesets.Catch/Judgements/CatchJudgement.cs index ccafe0abc4..fd61647a7c 100644 --- a/osu.Game.Rulesets.Catch/Judgements/CatchJudgement.cs +++ b/osu.Game.Rulesets.Catch/Judgements/CatchJudgement.cs @@ -9,7 +9,7 @@ namespace osu.Game.Rulesets.Catch.Judgements { public class CatchJudgement : Judgement { - public override HitResult MaxResult => HitResult.Great; + public override HitResult MaxResult => HitResult.Perfect; /// /// Whether fruit on the platter should explode or drop. diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs index 286feac5ba..dfab24f239 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs @@ -129,7 +129,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables if (countHit >= HitObject.RequiredGoodHits) { - ApplyResult(r => r.Type = countHit >= HitObject.RequiredGreatHits ? HitResult.Great : HitResult.Ok); + ApplyResult(r => r.Type = countHit >= HitObject.RequiredGreatHits ? HitResult.Great : HitResult.Good); } else ApplyResult(r => r.Type = HitResult.Miss); diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs index 11ff0729e2..999a159cce 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs @@ -211,7 +211,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables tick.TriggerResult(false); } - var hitResult = numHits > HitObject.RequiredHits / 2 ? HitResult.Ok : HitResult.Miss; + var hitResult = numHits > HitObject.RequiredHits / 2 ? HitResult.Good : HitResult.Miss; ApplyResult(r => r.Type = hitResult); } From f439c1afbc7310991681ba2bd1867a7540494508 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 29 Sep 2020 17:16:55 +0900 Subject: [PATCH 1318/1782] Make osu/taiko/catch use Ok+Great --- .../TestSceneComboCounter.cs | 2 +- .../Difficulty/CatchPerformanceCalculator.cs | 2 +- .../Judgements/CatchJudgement.cs | 2 +- .../Scoring/CatchHitWindows.cs | 2 +- .../Difficulty/OsuPerformanceCalculator.cs | 10 ++--- .../Objects/Drawables/DrawableSpinner.cs | 2 +- .../Replays/OsuAutoGenerator.cs | 6 +-- .../Scoring/OsuHitWindows.cs | 4 +- .../Skinning/TestSceneDrawableTaikoMascot.cs | 6 +-- .../Skinning/TestSceneHitExplosion.cs | 4 +- .../Skinning/TestSceneTaikoScroller.cs | 2 +- .../TestSceneFlyingHits.cs | 2 +- .../TestSceneHits.cs | 8 ++-- .../Difficulty/TaikoPerformanceCalculator.cs | 6 +-- .../Judgements/TaikoJudgement.cs | 2 +- .../Objects/Drawables/DrawableDrumRoll.cs | 2 +- .../Objects/Drawables/DrawableSwell.cs | 2 +- .../Scoring/TaikoHitWindows.cs | 4 +- .../Skinning/TaikoLegacySkinTransformer.cs | 8 ++-- .../TaikoSkinComponents.cs | 4 +- .../UI/DrawableTaikoJudgement.cs | 2 +- osu.Game.Rulesets.Taiko/UI/HitExplosion.cs | 6 +-- .../Rulesets/Scoring/ScoreProcessorTest.cs | 4 +- .../Visual/Gameplay/TestSceneHitErrorMeter.cs | 2 +- .../Scoring/Legacy/ScoreInfoExtensions.cs | 37 +------------------ osu.Game/Skinning/LegacySkin.cs | 2 +- osu.Game/Tests/TestScoreInfo.cs | 4 +- 27 files changed, 53 insertions(+), 84 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneComboCounter.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneComboCounter.cs index e79792e04a..c7b322c8a0 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneComboCounter.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneComboCounter.cs @@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Catch.Tests [Test] public void TestCatchComboCounter() { - AddRepeatStep("perform hit", () => performJudgement(HitResult.Perfect), 20); + AddRepeatStep("perform hit", () => performJudgement(HitResult.Great), 20); AddStep("perform miss", () => performJudgement(HitResult.Miss)); AddStep("randomize judged object colour", () => diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs index d700f79e5b..a4b9ca35eb 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs @@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty { mods = Score.Mods; - fruitsHit = Score.Statistics.GetOrDefault(HitResult.Perfect); + fruitsHit = Score.Statistics.GetOrDefault(HitResult.Great); ticksHit = Score.Statistics.GetOrDefault(HitResult.LargeTickHit); tinyTicksHit = Score.Statistics.GetOrDefault(HitResult.SmallTickHit); tinyTicksMissed = Score.Statistics.GetOrDefault(HitResult.SmallTickMiss); diff --git a/osu.Game.Rulesets.Catch/Judgements/CatchJudgement.cs b/osu.Game.Rulesets.Catch/Judgements/CatchJudgement.cs index fd61647a7c..ccafe0abc4 100644 --- a/osu.Game.Rulesets.Catch/Judgements/CatchJudgement.cs +++ b/osu.Game.Rulesets.Catch/Judgements/CatchJudgement.cs @@ -9,7 +9,7 @@ namespace osu.Game.Rulesets.Catch.Judgements { public class CatchJudgement : Judgement { - public override HitResult MaxResult => HitResult.Perfect; + public override HitResult MaxResult => HitResult.Great; /// /// Whether fruit on the platter should explode or drop. diff --git a/osu.Game.Rulesets.Catch/Scoring/CatchHitWindows.cs b/osu.Game.Rulesets.Catch/Scoring/CatchHitWindows.cs index ff793a372e..0a444d923e 100644 --- a/osu.Game.Rulesets.Catch/Scoring/CatchHitWindows.cs +++ b/osu.Game.Rulesets.Catch/Scoring/CatchHitWindows.cs @@ -11,7 +11,7 @@ namespace osu.Game.Rulesets.Catch.Scoring { switch (result) { - case HitResult.Perfect: + case HitResult.Great: case HitResult.Miss: return true; } diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index 6f4c0f9cfa..02577461f0 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty private double accuracy; private int scoreMaxCombo; private int countGreat; - private int countGood; + private int countOk; private int countMeh; private int countMiss; @@ -47,7 +47,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty accuracy = Score.Accuracy; scoreMaxCombo = Score.MaxCombo; countGreat = Score.Statistics.GetOrDefault(HitResult.Great); - countGood = Score.Statistics.GetOrDefault(HitResult.Good); + countOk = Score.Statistics.GetOrDefault(HitResult.Ok); countMeh = Score.Statistics.GetOrDefault(HitResult.Meh); countMiss = Score.Statistics.GetOrDefault(HitResult.Miss); @@ -181,7 +181,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty int amountHitObjectsWithAccuracy = countHitCircles; if (amountHitObjectsWithAccuracy > 0) - betterAccuracyPercentage = ((countGreat - (totalHits - amountHitObjectsWithAccuracy)) * 6 + countGood * 2 + countMeh) / (double)(amountHitObjectsWithAccuracy * 6); + betterAccuracyPercentage = ((countGreat - (totalHits - amountHitObjectsWithAccuracy)) * 6 + countOk * 2 + countMeh) / (double)(amountHitObjectsWithAccuracy * 6); else betterAccuracyPercentage = 0; @@ -204,7 +204,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty return accuracyValue; } - private int totalHits => countGreat + countGood + countMeh + countMiss; - private int totalSuccessfulHits => countGreat + countGood + countMeh; + private int totalHits => countGreat + countOk + countMeh + countMiss; + private int totalSuccessfulHits => countGreat + countOk + countMeh; } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index a57bb466c7..d77213f3ed 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -217,7 +217,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables if (Progress >= 1) r.Type = HitResult.Great; else if (Progress > .9) - r.Type = HitResult.Good; + r.Type = HitResult.Ok; else if (Progress > .75) r.Type = HitResult.Meh; else if (Time.Current >= Spinner.EndTime) diff --git a/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs b/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs index 9b350278f3..954a217473 100644 --- a/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs +++ b/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs @@ -137,13 +137,13 @@ namespace osu.Game.Rulesets.Osu.Replays if (!(h is Spinner)) AddFrameToReplay(new OsuReplayFrame(h.StartTime - hitWindows.WindowFor(HitResult.Meh), new Vector2(h.StackedPosition.X, h.StackedPosition.Y))); } - else if (h.StartTime - hitWindows.WindowFor(HitResult.Good) > endTime + hitWindows.WindowFor(HitResult.Good) + 50) + else if (h.StartTime - hitWindows.WindowFor(HitResult.Ok) > endTime + hitWindows.WindowFor(HitResult.Ok) + 50) { if (!(prev is Spinner) && h.StartTime - endTime < 1000) - AddFrameToReplay(new OsuReplayFrame(endTime + hitWindows.WindowFor(HitResult.Good), new Vector2(prev.StackedEndPosition.X, prev.StackedEndPosition.Y))); + AddFrameToReplay(new OsuReplayFrame(endTime + hitWindows.WindowFor(HitResult.Ok), new Vector2(prev.StackedEndPosition.X, prev.StackedEndPosition.Y))); if (!(h is Spinner)) - AddFrameToReplay(new OsuReplayFrame(h.StartTime - hitWindows.WindowFor(HitResult.Good), new Vector2(h.StackedPosition.X, h.StackedPosition.Y))); + AddFrameToReplay(new OsuReplayFrame(h.StartTime - hitWindows.WindowFor(HitResult.Ok), 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 index 6f2998006f..dafe63a6d1 100644 --- a/osu.Game.Rulesets.Osu/Scoring/OsuHitWindows.cs +++ b/osu.Game.Rulesets.Osu/Scoring/OsuHitWindows.cs @@ -10,7 +10,7 @@ namespace osu.Game.Rulesets.Osu.Scoring private static readonly DifficultyRange[] osu_ranges = { new DifficultyRange(HitResult.Great, 80, 50, 20), - new DifficultyRange(HitResult.Good, 140, 100, 60), + new DifficultyRange(HitResult.Ok, 140, 100, 60), new DifficultyRange(HitResult.Meh, 200, 150, 100), new DifficultyRange(HitResult.Miss, 400, 400, 400), }; @@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Osu.Scoring switch (result) { case HitResult.Great: - case HitResult.Good: + case HitResult.Ok: case HitResult.Meh: case HitResult.Miss: return true; diff --git a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableTaikoMascot.cs b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableTaikoMascot.cs index ae42bf8a90..99e103da3b 100644 --- a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableTaikoMascot.cs +++ b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableTaikoMascot.cs @@ -102,7 +102,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning createDrawableRuleset(); - assertStateAfterResult(new JudgementResult(new Hit(), new TaikoJudgement()) { Type = HitResult.Good }, TaikoMascotAnimationState.Kiai); + assertStateAfterResult(new JudgementResult(new Hit(), new TaikoJudgement()) { Type = HitResult.Ok }, TaikoMascotAnimationState.Kiai); assertStateAfterResult(new JudgementResult(new Hit(), new TaikoStrongJudgement()) { Type = HitResult.IgnoreMiss }, TaikoMascotAnimationState.Kiai); assertStateAfterResult(new JudgementResult(new Hit(), new TaikoJudgement()) { Type = HitResult.Miss }, TaikoMascotAnimationState.Fail); } @@ -117,7 +117,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning assertStateAfterResult(new JudgementResult(new Hit(), new TaikoJudgement()) { Type = HitResult.Great }, TaikoMascotAnimationState.Idle); assertStateAfterResult(new JudgementResult(new Hit(), new TaikoJudgement()) { Type = HitResult.Miss }, TaikoMascotAnimationState.Fail); assertStateAfterResult(new JudgementResult(new DrumRoll(), new TaikoDrumRollJudgement()) { Type = HitResult.Great }, TaikoMascotAnimationState.Idle); - assertStateAfterResult(new JudgementResult(new Hit(), new TaikoJudgement()) { Type = HitResult.Good }, TaikoMascotAnimationState.Idle); + assertStateAfterResult(new JudgementResult(new Hit(), new TaikoJudgement()) { Type = HitResult.Ok }, TaikoMascotAnimationState.Idle); } [TestCase(true)] @@ -130,7 +130,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning AddRepeatStep("reach 49 combo", () => applyNewResult(new JudgementResult(new Hit(), new TaikoJudgement()) { Type = HitResult.Great }), 49); - assertStateAfterResult(new JudgementResult(new Hit(), new TaikoJudgement()) { Type = HitResult.Good }, TaikoMascotAnimationState.Clear); + assertStateAfterResult(new JudgementResult(new Hit(), new TaikoJudgement()) { Type = HitResult.Ok }, TaikoMascotAnimationState.Clear); } [TestCase(true, TaikoMascotAnimationState.Kiai)] diff --git a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneHitExplosion.cs b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneHitExplosion.cs index 45c94a8a86..fecb5d4a74 100644 --- a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneHitExplosion.cs +++ b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneHitExplosion.cs @@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning public void TestNormalHit() { AddStep("Great", () => SetContents(() => getContentFor(createHit(HitResult.Great)))); - AddStep("Good", () => SetContents(() => getContentFor(createHit(HitResult.Good)))); + AddStep("Ok", () => SetContents(() => getContentFor(createHit(HitResult.Ok)))); AddStep("Miss", () => SetContents(() => getContentFor(createHit(HitResult.Miss)))); } @@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning public void TestStrongHit([Values(false, true)] bool hitBoth) { AddStep("Great", () => SetContents(() => getContentFor(createStrongHit(HitResult.Great, hitBoth)))); - AddStep("Good", () => SetContents(() => getContentFor(createStrongHit(HitResult.Good, hitBoth)))); + AddStep("Good", () => SetContents(() => getContentFor(createStrongHit(HitResult.Ok, hitBoth)))); } private Drawable getContentFor(DrawableTestHit hit) diff --git a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneTaikoScroller.cs b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneTaikoScroller.cs index 16ef5b968d..114038b81c 100644 --- a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneTaikoScroller.cs +++ b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneTaikoScroller.cs @@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning })); AddToggleStep("Toggle passing", passing => this.ChildrenOfType().ForEach(s => s.LastResult.Value = - new JudgementResult(null, new Judgement()) { Type = passing ? HitResult.Perfect : HitResult.Miss })); + new JudgementResult(null, new Judgement()) { Type = passing ? HitResult.Great : HitResult.Miss })); AddToggleStep("toggle playback direction", reversed => this.reversed = reversed); } diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneFlyingHits.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneFlyingHits.cs index 7492a76a67..63854e7ead 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TestSceneFlyingHits.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneFlyingHits.cs @@ -42,7 +42,7 @@ namespace osu.Game.Rulesets.Taiko.Tests DrawableDrumRollTick h; DrawableRuleset.Playfield.Add(h = new DrawableDrumRollTick(tick) { JudgementType = hitType }); - ((TaikoPlayfield)DrawableRuleset.Playfield).OnNewResult(h, new JudgementResult(tick, new TaikoDrumRollTickJudgement()) { Type = HitResult.Perfect }); + ((TaikoPlayfield)DrawableRuleset.Playfield).OnNewResult(h, new JudgementResult(tick, new TaikoDrumRollTickJudgement()) { Type = HitResult.Great }); } } } diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneHits.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneHits.cs index b6cfe368f7..0f605be8f9 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TestSceneHits.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneHits.cs @@ -105,7 +105,7 @@ namespace osu.Game.Rulesets.Taiko.Tests private void addHitJudgement(bool kiai) { - HitResult hitResult = RNG.Next(2) == 0 ? HitResult.Good : HitResult.Great; + HitResult hitResult = RNG.Next(2) == 0 ? HitResult.Ok : HitResult.Great; var cpi = new ControlPointInfo(); cpi.Add(0, new EffectControlPoint { KiaiMode = kiai }); @@ -113,7 +113,7 @@ namespace osu.Game.Rulesets.Taiko.Tests Hit hit = new Hit(); hit.ApplyDefaults(cpi, new BeatmapDifficulty()); - var h = new DrawableTestHit(hit) { X = RNG.NextSingle(hitResult == HitResult.Good ? -0.1f : -0.05f, hitResult == HitResult.Good ? 0.1f : 0.05f) }; + var h = new DrawableTestHit(hit) { X = RNG.NextSingle(hitResult == HitResult.Ok ? -0.1f : -0.05f, hitResult == HitResult.Ok ? 0.1f : 0.05f) }; DrawableRuleset.Playfield.Add(h); @@ -122,7 +122,7 @@ namespace osu.Game.Rulesets.Taiko.Tests private void addStrongHitJudgement(bool kiai) { - HitResult hitResult = RNG.Next(2) == 0 ? HitResult.Good : HitResult.Great; + HitResult hitResult = RNG.Next(2) == 0 ? HitResult.Ok : HitResult.Great; var cpi = new ControlPointInfo(); cpi.Add(0, new EffectControlPoint { KiaiMode = kiai }); @@ -130,7 +130,7 @@ namespace osu.Game.Rulesets.Taiko.Tests Hit hit = new Hit(); hit.ApplyDefaults(cpi, new BeatmapDifficulty()); - var h = new DrawableTestHit(hit) { X = RNG.NextSingle(hitResult == HitResult.Good ? -0.1f : -0.05f, hitResult == HitResult.Good ? 0.1f : 0.05f) }; + var h = new DrawableTestHit(hit) { X = RNG.NextSingle(hitResult == HitResult.Ok ? -0.1f : -0.05f, hitResult == HitResult.Ok ? 0.1f : 0.05f) }; DrawableRuleset.Playfield.Add(h); diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs index b9d95a6ba6..c04fffa2e7 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs @@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty private Mod[] mods; private int countGreat; - private int countGood; + private int countOk; private int countMeh; private int countMiss; @@ -33,7 +33,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty { mods = Score.Mods; countGreat = Score.Statistics.GetOrDefault(HitResult.Great); - countGood = Score.Statistics.GetOrDefault(HitResult.Good); + countOk = Score.Statistics.GetOrDefault(HitResult.Ok); countMeh = Score.Statistics.GetOrDefault(HitResult.Meh); countMiss = Score.Statistics.GetOrDefault(HitResult.Miss); @@ -102,6 +102,6 @@ namespace osu.Game.Rulesets.Taiko.Difficulty return accValue * Math.Min(1.15, Math.Pow(totalHits / 1500.0, 0.3)); } - private int totalHits => countGreat + countGood + countMeh + countMiss; + private int totalHits => countGreat + countOk + countMeh + countMiss; } } diff --git a/osu.Game.Rulesets.Taiko/Judgements/TaikoJudgement.cs b/osu.Game.Rulesets.Taiko/Judgements/TaikoJudgement.cs index 3d22860814..e272c1a4ef 100644 --- a/osu.Game.Rulesets.Taiko/Judgements/TaikoJudgement.cs +++ b/osu.Game.Rulesets.Taiko/Judgements/TaikoJudgement.cs @@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Taiko.Judgements case HitResult.Miss: return -1.0; - case HitResult.Good: + case HitResult.Ok: return 1.1; case HitResult.Great: diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs index dfab24f239..286feac5ba 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs @@ -129,7 +129,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables if (countHit >= HitObject.RequiredGoodHits) { - ApplyResult(r => r.Type = countHit >= HitObject.RequiredGreatHits ? HitResult.Great : HitResult.Good); + ApplyResult(r => r.Type = countHit >= HitObject.RequiredGreatHits ? HitResult.Great : HitResult.Ok); } else ApplyResult(r => r.Type = HitResult.Miss); diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs index 999a159cce..11ff0729e2 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs @@ -211,7 +211,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables tick.TriggerResult(false); } - var hitResult = numHits > HitObject.RequiredHits / 2 ? HitResult.Good : HitResult.Miss; + var hitResult = numHits > HitObject.RequiredHits / 2 ? HitResult.Ok : HitResult.Miss; ApplyResult(r => r.Type = hitResult); } diff --git a/osu.Game.Rulesets.Taiko/Scoring/TaikoHitWindows.cs b/osu.Game.Rulesets.Taiko/Scoring/TaikoHitWindows.cs index 9d273392ff..cf806c0c97 100644 --- a/osu.Game.Rulesets.Taiko/Scoring/TaikoHitWindows.cs +++ b/osu.Game.Rulesets.Taiko/Scoring/TaikoHitWindows.cs @@ -10,7 +10,7 @@ namespace osu.Game.Rulesets.Taiko.Scoring private static readonly DifficultyRange[] taiko_ranges = { new DifficultyRange(HitResult.Great, 50, 35, 20), - new DifficultyRange(HitResult.Good, 120, 80, 50), + new DifficultyRange(HitResult.Ok, 120, 80, 50), new DifficultyRange(HitResult.Miss, 135, 95, 70), }; @@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Taiko.Scoring switch (result) { case HitResult.Great: - case HitResult.Good: + case HitResult.Ok: case HitResult.Miss: return true; } diff --git a/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacySkinTransformer.cs b/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacySkinTransformer.cs index c222ccb51f..73a56f3fbc 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacySkinTransformer.cs @@ -74,8 +74,8 @@ namespace osu.Game.Rulesets.Taiko.Skinning return null; - case TaikoSkinComponents.TaikoExplosionGood: - case TaikoSkinComponents.TaikoExplosionGoodStrong: + case TaikoSkinComponents.TaikoExplosionOk: + case TaikoSkinComponents.TaikoExplosionOkStrong: case TaikoSkinComponents.TaikoExplosionGreat: case TaikoSkinComponents.TaikoExplosionGreatStrong: case TaikoSkinComponents.TaikoExplosionMiss: @@ -106,10 +106,10 @@ namespace osu.Game.Rulesets.Taiko.Skinning case TaikoSkinComponents.TaikoExplosionMiss: return "taiko-hit0"; - case TaikoSkinComponents.TaikoExplosionGood: + case TaikoSkinComponents.TaikoExplosionOk: return "taiko-hit100"; - case TaikoSkinComponents.TaikoExplosionGoodStrong: + case TaikoSkinComponents.TaikoExplosionOkStrong: return "taiko-hit100k"; case TaikoSkinComponents.TaikoExplosionGreat: diff --git a/osu.Game.Rulesets.Taiko/TaikoSkinComponents.cs b/osu.Game.Rulesets.Taiko/TaikoSkinComponents.cs index 0d785adb4a..b274608d84 100644 --- a/osu.Game.Rulesets.Taiko/TaikoSkinComponents.cs +++ b/osu.Game.Rulesets.Taiko/TaikoSkinComponents.cs @@ -16,8 +16,8 @@ namespace osu.Game.Rulesets.Taiko PlayfieldBackgroundRight, BarLine, TaikoExplosionMiss, - TaikoExplosionGood, - TaikoExplosionGoodStrong, + TaikoExplosionOk, + TaikoExplosionOkStrong, TaikoExplosionGreat, TaikoExplosionGreatStrong, Scroller, diff --git a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoJudgement.cs b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoJudgement.cs index f91bbb14e8..cbfc5a8628 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoJudgement.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoJudgement.cs @@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Taiko.UI { switch (Result.Type) { - case HitResult.Good: + case HitResult.Ok: JudgementBody.Colour = colours.GreenLight; break; diff --git a/osu.Game.Rulesets.Taiko/UI/HitExplosion.cs b/osu.Game.Rulesets.Taiko/UI/HitExplosion.cs index efd1b25046..6d800b812d 100644 --- a/osu.Game.Rulesets.Taiko/UI/HitExplosion.cs +++ b/osu.Game.Rulesets.Taiko/UI/HitExplosion.cs @@ -60,10 +60,10 @@ namespace osu.Game.Rulesets.Taiko.UI case HitResult.Miss: return TaikoSkinComponents.TaikoExplosionMiss; - case HitResult.Good: + case HitResult.Ok: return useStrongExplosion(judgedObject) - ? TaikoSkinComponents.TaikoExplosionGoodStrong - : TaikoSkinComponents.TaikoExplosionGood; + ? TaikoSkinComponents.TaikoExplosionOkStrong + : TaikoSkinComponents.TaikoExplosionOk; case HitResult.Great: return useStrongExplosion(judgedObject) diff --git a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs index 84b7bc3723..ace57aad1d 100644 --- a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs +++ b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs @@ -35,10 +35,10 @@ namespace osu.Game.Tests.Rulesets.Scoring } [TestCase(ScoringMode.Standardised, HitResult.Meh, 750_000)] - [TestCase(ScoringMode.Standardised, HitResult.Good, 900_000)] + [TestCase(ScoringMode.Standardised, HitResult.Ok, 800_000)] [TestCase(ScoringMode.Standardised, HitResult.Great, 1_000_000)] [TestCase(ScoringMode.Classic, HitResult.Meh, 50)] - [TestCase(ScoringMode.Classic, HitResult.Good, 200)] + [TestCase(ScoringMode.Classic, HitResult.Ok, 100)] [TestCase(ScoringMode.Classic, HitResult.Great, 300)] public void TestSingleOsuHit(ScoringMode scoringMode, HitResult hitResult, int expectedScore) { diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs index 253b8d9c55..377f305d63 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs @@ -98,7 +98,7 @@ namespace osu.Game.Tests.Visual.Gameplay Children = new[] { new OsuSpriteText { Text = $@"Great: {hitWindows?.WindowFor(HitResult.Great)}" }, - new OsuSpriteText { Text = $@"Good: {hitWindows?.WindowFor(HitResult.Good)}" }, + new OsuSpriteText { Text = $@"Good: {hitWindows?.WindowFor(HitResult.Ok)}" }, new OsuSpriteText { Text = $@"Meh: {hitWindows?.WindowFor(HitResult.Meh)}" }, } }); diff --git a/osu.Game/Scoring/Legacy/ScoreInfoExtensions.cs b/osu.Game/Scoring/Legacy/ScoreInfoExtensions.cs index 6f73a284a2..b58f65800d 100644 --- a/osu.Game/Scoring/Legacy/ScoreInfoExtensions.cs +++ b/osu.Game/Scoring/Legacy/ScoreInfoExtensions.cs @@ -28,37 +28,9 @@ namespace osu.Game.Scoring.Legacy } } - public static int? GetCount300(this ScoreInfo scoreInfo) - { - switch (scoreInfo.Ruleset?.ID ?? scoreInfo.RulesetID) - { - case 0: - case 1: - case 3: - return getCount(scoreInfo, HitResult.Great); + public static int? GetCount300(this ScoreInfo scoreInfo) => getCount(scoreInfo, HitResult.Great); - case 2: - return getCount(scoreInfo, HitResult.Perfect); - } - - 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; - } - } + public static void SetCount300(this ScoreInfo scoreInfo, int value) => scoreInfo.Statistics[HitResult.Great] = value; public static int? GetCountKatu(this ScoreInfo scoreInfo) { @@ -94,8 +66,6 @@ namespace osu.Game.Scoring.Legacy { case 0: case 1: - return getCount(scoreInfo, HitResult.Good); - case 3: return getCount(scoreInfo, HitResult.Ok); @@ -112,9 +82,6 @@ namespace osu.Game.Scoring.Legacy { case 0: case 1: - scoreInfo.Statistics[HitResult.Good] = value; - break; - case 3: scoreInfo.Statistics[HitResult.Ok] = value; break; diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 5caf07b554..e38913b13a 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -336,7 +336,7 @@ namespace osu.Game.Skinning case HitResult.Meh: return this.GetAnimation("hit50", true, false); - case HitResult.Good: + case HitResult.Ok: return this.GetAnimation("hit100", true, false); case HitResult.Great: diff --git a/osu.Game/Tests/TestScoreInfo.cs b/osu.Game/Tests/TestScoreInfo.cs index 704d01e479..9090a12d3f 100644 --- a/osu.Game/Tests/TestScoreInfo.cs +++ b/osu.Game/Tests/TestScoreInfo.cs @@ -35,8 +35,10 @@ namespace osu.Game.Tests Statistics[HitResult.Miss] = 1; Statistics[HitResult.Meh] = 50; - Statistics[HitResult.Good] = 100; + Statistics[HitResult.Ok] = 100; + Statistics[HitResult.Good] = 200; Statistics[HitResult.Great] = 300; + Statistics[HitResult.Perfect] = 320; Statistics[HitResult.SmallTickHit] = 50; Statistics[HitResult.SmallTickMiss] = 25; Statistics[HitResult.LargeTickHit] = 100; From 91262620d3c72bb14880d1e7262c734aa4672fd2 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 29 Sep 2020 17:17:06 +0900 Subject: [PATCH 1319/1782] Remove XMLDocs from Ok/Perfect hit results --- osu.Game/Rulesets/Scoring/HitResult.cs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/osu.Game/Rulesets/Scoring/HitResult.cs b/osu.Game/Rulesets/Scoring/HitResult.cs index 7a02db190a..d979c342e1 100644 --- a/osu.Game/Rulesets/Scoring/HitResult.cs +++ b/osu.Game/Rulesets/Scoring/HitResult.cs @@ -32,9 +32,6 @@ namespace osu.Game.Rulesets.Scoring [Order(4)] Meh, - /// - /// Optional judgement. - /// [Description(@"OK")] [Order(3)] Ok, @@ -47,9 +44,6 @@ namespace osu.Game.Rulesets.Scoring [Order(1)] Great, - /// - /// Optional judgement. - /// [Description(@"Perfect")] [Order(0)] Perfect, From 53b3d238427d456be3e5acbd79d221b7d1937136 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 29 Sep 2020 17:26:49 +0900 Subject: [PATCH 1320/1782] Expose HitObjectComposer for other components in the Compose csreen to use --- .../Screens/Edit/Compose/ComposeScreen.cs | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/ComposeScreen.cs b/osu.Game/Screens/Edit/Compose/ComposeScreen.cs index d7a4661fa0..5282b4d998 100644 --- a/osu.Game/Screens/Edit/Compose/ComposeScreen.cs +++ b/osu.Game/Screens/Edit/Compose/ComposeScreen.cs @@ -1,8 +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.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Game.Beatmaps; +using osu.Game.Rulesets; using osu.Game.Rulesets.Edit; using osu.Game.Screens.Edit.Compose.Components.Timeline; using osu.Game.Skinning; @@ -18,11 +22,23 @@ namespace osu.Game.Screens.Edit.Compose { } - protected override Drawable CreateMainContent() + private Ruleset ruleset; + + protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) { - var ruleset = Beatmap.Value.BeatmapInfo.Ruleset?.CreateInstance(); + var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); + + ruleset = parent.Get>().Value.BeatmapInfo.Ruleset?.CreateInstance(); composer = ruleset?.CreateHitObjectComposer(); + // make the composer available to the timeline and other components in this screen. + dependencies.CacheAs(composer); + + return dependencies; + } + + protected override Drawable CreateMainContent() + { if (ruleset == null || composer == null) return new ScreenWhiteBox.UnderConstructionMessage(ruleset == null ? "This beatmap" : $"{ruleset.Description}'s composer"); From f16fc2907188da26ff64cb7743eb1e0a2319f444 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 29 Sep 2020 16:42:54 +0900 Subject: [PATCH 1321/1782] Add combo colour display support --- .../Timeline/TimelineHitObjectBlueprint.cs | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs index b95b3842b3..7dfaf02af4 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Linq; using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -15,6 +16,7 @@ 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.Drawables; using osu.Game.Rulesets.Objects.Types; using osuTK; using osuTK.Graphics; @@ -34,6 +36,10 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private readonly List shadowComponents = new List(); + private DrawableHitObject drawableHitObject; + + private Bindable comboColour; + private const float thickness = 5; private const float shadow_radius = 5; @@ -123,6 +129,30 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline updateShadows(); } + [BackgroundDependencyLoader(true)] + private void load(HitObjectComposer composer) + { + if (composer != null) + { + // best effort to get the drawable representation for grabbing colour and what not. + drawableHitObject = composer.HitObjects.FirstOrDefault(d => d.HitObject == HitObject); + } + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + if (drawableHitObject != null) + { + comboColour = drawableHitObject.AccentColour.GetBoundCopy(); + comboColour.BindValueChanged(colour => + { + Colour = drawableHitObject.AccentColour.Value; + }, true); + } + } + protected override void Update() { base.Update(); From 8d8d45a0c068a9348e1ba37eecdf673900dae70d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 29 Sep 2020 17:04:59 +0900 Subject: [PATCH 1322/1782] Add combo index display support --- .../Timeline/TimelineHitObjectBlueprint.cs | 48 +++++++++++++++++-- 1 file changed, 44 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs index 7dfaf02af4..9aa0fee96e 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs @@ -13,6 +13,8 @@ using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; @@ -40,11 +42,17 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private Bindable comboColour; + private readonly Container mainComponents; + + private readonly OsuSpriteText comboIndexText; + + private Bindable comboIndex; + private const float thickness = 5; private const float shadow_radius = 5; - private const float circle_size = 16; + private const float circle_size = 24; public TimelineHitObjectBlueprint(HitObject hitObject) : base(hitObject) @@ -60,6 +68,23 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; + AddRangeInternal(new Drawable[] + { + mainComponents = new Container + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + }, + comboIndexText = new OsuSpriteText + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.Centre, + Font = OsuFont.Numeric.With(size: circle_size / 2, weight: FontWeight.Black), + }, + }); + circle = new Circle { Size = new Vector2(circle_size), @@ -83,7 +108,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline DragBar dragBarUnderlay; Container extensionBar; - AddRangeInternal(new Drawable[] + mainComponents.AddRange(new Drawable[] { extensionBar = new Container { @@ -123,7 +148,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline } else { - AddInternal(circle); + mainComponents.Add(circle); } updateShadows(); @@ -143,12 +168,27 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { base.LoadComplete(); + if (HitObject is IHasComboInformation comboInfo) + { + comboIndex = comboInfo.IndexInCurrentComboBindable.GetBoundCopy(); + comboIndex.BindValueChanged(combo => + { + comboIndexText.Text = (combo.NewValue + 1).ToString(); + }, true); + } + if (drawableHitObject != null) { comboColour = drawableHitObject.AccentColour.GetBoundCopy(); comboColour.BindValueChanged(colour => { - Colour = drawableHitObject.AccentColour.Value; + mainComponents.Colour = drawableHitObject.AccentColour.Value; + + var col = mainComponents.Colour.AverageColour.Linear; + float brightness = col.R + col.G + col.B; + + // decide the combo index colour based on brightness? + comboIndexText.Colour = brightness > 0.5f ? Color4.Black : Color4.White; }, true); } } From c47652c97a22350e3d14269fc4d6936c2856e444 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 29 Sep 2020 17:22:47 +0900 Subject: [PATCH 1323/1782] Add gradient to hide subtractive colour issues Good thing is looks better than without. --- .../Compose/Components/Timeline/TimelineHitObjectBlueprint.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs index 9aa0fee96e..11f44c59ac 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs @@ -8,6 +8,7 @@ using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Primitives; @@ -182,7 +183,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline comboColour = drawableHitObject.AccentColour.GetBoundCopy(); comboColour.BindValueChanged(colour => { - mainComponents.Colour = drawableHitObject.AccentColour.Value; + mainComponents.Colour = ColourInfo.GradientHorizontal(drawableHitObject.AccentColour.Value, Color4.White); var col = mainComponents.Colour.AverageColour.Linear; float brightness = col.R + col.G + col.B; From 6e1ea004436fcdc09da0b6599d37a75e778b5a18 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 29 Sep 2020 17:34:50 +0900 Subject: [PATCH 1324/1782] Don't apply gradient to non-duration objects --- .../Components/Timeline/TimelineHitObjectBlueprint.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs index 11f44c59ac..455f1e17bd 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs @@ -91,9 +91,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline Size = new Vector2(circle_size), Anchor = Anchor.CentreLeft, Origin = Anchor.Centre, - RelativePositionAxes = Axes.X, - AlwaysPresent = true, - Colour = Color4.White, EdgeEffect = new EdgeEffectParameters { Type = EdgeEffectType.Shadow, @@ -183,7 +180,10 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline comboColour = drawableHitObject.AccentColour.GetBoundCopy(); comboColour.BindValueChanged(colour => { - mainComponents.Colour = ColourInfo.GradientHorizontal(drawableHitObject.AccentColour.Value, Color4.White); + if (HitObject is IHasDuration) + mainComponents.Colour = ColourInfo.GradientHorizontal(drawableHitObject.AccentColour.Value, Color4.White); + else + mainComponents.Colour = drawableHitObject.AccentColour.Value; var col = mainComponents.Colour.AverageColour.Linear; float brightness = col.R + col.G + col.B; From 379a4cca85bec432dc2fa18da48fbdd7e09e8158 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 29 Sep 2020 17:48:44 +0900 Subject: [PATCH 1325/1782] Adjust hold note tests --- .../TestSceneHoldNoteInput.cs | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs index 95072cf4f8..5cb1519196 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs @@ -45,9 +45,9 @@ namespace osu.Game.Rulesets.Mania.Tests }); assertHeadJudgement(HitResult.Miss); - assertTickJudgement(HitResult.Miss); + assertTickJudgement(HitResult.LargeTickMiss); assertTailJudgement(HitResult.Miss); - assertNoteJudgement(HitResult.Perfect); + assertNoteJudgement(HitResult.IgnoreHit); } /// @@ -64,7 +64,7 @@ namespace osu.Game.Rulesets.Mania.Tests }); assertHeadJudgement(HitResult.Miss); - assertTickJudgement(HitResult.Miss); + assertTickJudgement(HitResult.LargeTickMiss); assertTailJudgement(HitResult.Miss); } @@ -82,7 +82,7 @@ namespace osu.Game.Rulesets.Mania.Tests }); assertHeadJudgement(HitResult.Miss); - assertTickJudgement(HitResult.Miss); + assertTickJudgement(HitResult.LargeTickMiss); assertTailJudgement(HitResult.Miss); } @@ -102,7 +102,7 @@ namespace osu.Game.Rulesets.Mania.Tests }); assertHeadJudgement(HitResult.Perfect); - assertTickJudgement(HitResult.Perfect); + assertTickJudgement(HitResult.LargeTickHit); assertTailJudgement(HitResult.Miss); } @@ -122,7 +122,7 @@ namespace osu.Game.Rulesets.Mania.Tests }); assertHeadJudgement(HitResult.Perfect); - assertTickJudgement(HitResult.Perfect); + assertTickJudgement(HitResult.LargeTickHit); assertTailJudgement(HitResult.Perfect); } @@ -141,7 +141,7 @@ namespace osu.Game.Rulesets.Mania.Tests }); assertHeadJudgement(HitResult.Perfect); - assertTickJudgement(HitResult.Miss); + assertTickJudgement(HitResult.LargeTickMiss); assertTailJudgement(HitResult.Miss); } @@ -161,7 +161,7 @@ namespace osu.Game.Rulesets.Mania.Tests }); assertHeadJudgement(HitResult.Perfect); - assertTickJudgement(HitResult.Perfect); + assertTickJudgement(HitResult.LargeTickHit); assertTailJudgement(HitResult.Miss); } @@ -181,7 +181,7 @@ namespace osu.Game.Rulesets.Mania.Tests }); assertHeadJudgement(HitResult.Perfect); - assertTickJudgement(HitResult.Perfect); + assertTickJudgement(HitResult.LargeTickHit); assertTailJudgement(HitResult.Meh); } @@ -199,7 +199,7 @@ namespace osu.Game.Rulesets.Mania.Tests }); assertHeadJudgement(HitResult.Miss); - assertTickJudgement(HitResult.Perfect); + assertTickJudgement(HitResult.LargeTickHit); assertTailJudgement(HitResult.Miss); } @@ -217,7 +217,7 @@ namespace osu.Game.Rulesets.Mania.Tests }); assertHeadJudgement(HitResult.Miss); - assertTickJudgement(HitResult.Perfect); + assertTickJudgement(HitResult.LargeTickHit); assertTailJudgement(HitResult.Meh); } @@ -235,7 +235,7 @@ namespace osu.Game.Rulesets.Mania.Tests }); assertHeadJudgement(HitResult.Miss); - assertTickJudgement(HitResult.Miss); + assertTickJudgement(HitResult.LargeTickMiss); assertTailJudgement(HitResult.Meh); } @@ -280,10 +280,10 @@ namespace osu.Game.Rulesets.Mania.Tests }, beatmap); AddAssert("first hold note missed", () => judgementResults.Where(j => beatmap.HitObjects[0].NestedHitObjects.Contains(j.HitObject)) - .All(j => j.Type == HitResult.Miss)); + .All(j => !j.Type.IsHit())); AddAssert("second hold note missed", () => judgementResults.Where(j => beatmap.HitObjects[1].NestedHitObjects.Contains(j.HitObject)) - .All(j => j.Type == HitResult.Perfect)); + .All(j => j.Type.IsHit())); } private void assertHeadJudgement(HitResult result) From cc9fa4675c00a905272c63d2e963d48d0fb2b94a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 29 Sep 2020 17:59:42 +0900 Subject: [PATCH 1326/1782] Adjust HP increases --- osu.Game/Rulesets/Judgements/Judgement.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Rulesets/Judgements/Judgement.cs b/osu.Game/Rulesets/Judgements/Judgement.cs index ea7a8d8d25..d80ebfe402 100644 --- a/osu.Game/Rulesets/Judgements/Judgement.cs +++ b/osu.Game/Rulesets/Judgements/Judgement.cs @@ -124,19 +124,19 @@ namespace osu.Game.Rulesets.Judgements return -DEFAULT_MAX_HEALTH_INCREASE; case HitResult.Meh: - return -DEFAULT_MAX_HEALTH_INCREASE * 0.05; + return -DEFAULT_MAX_HEALTH_INCREASE * 0.5; case HitResult.Ok: - return -DEFAULT_MAX_HEALTH_INCREASE * 0.01; + return -DEFAULT_MAX_HEALTH_INCREASE * 0.3; case HitResult.Good: - return DEFAULT_MAX_HEALTH_INCREASE * 0.5; + return DEFAULT_MAX_HEALTH_INCREASE * 0.1; case HitResult.Great: - return DEFAULT_MAX_HEALTH_INCREASE; + return DEFAULT_MAX_HEALTH_INCREASE * 0.8; case HitResult.Perfect: - return DEFAULT_MAX_HEALTH_INCREASE * 1.05; + return DEFAULT_MAX_HEALTH_INCREASE; case HitResult.SmallBonus: return DEFAULT_MAX_HEALTH_INCREASE * 0.1; From 4c872094c9a3eb1ff64468b30f999c486ae8d73f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 29 Sep 2020 18:29:50 +0900 Subject: [PATCH 1327/1782] Adjust slider tests --- osu.Game.Rulesets.Osu.Tests/TestSceneOutOfOrderHits.cs | 10 +++++----- osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs | 8 ++++---- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneOutOfOrderHits.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneOutOfOrderHits.cs index 854626d362..32a36ab317 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneOutOfOrderHits.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneOutOfOrderHits.cs @@ -209,9 +209,9 @@ namespace osu.Game.Rulesets.Osu.Tests }); addJudgementAssert(hitObjects[0], HitResult.Great); - addJudgementAssert(hitObjects[1], HitResult.Great); + addJudgementAssert(hitObjects[1], HitResult.IgnoreHit); addJudgementAssert("slider head", () => ((Slider)hitObjects[1]).HeadCircle, HitResult.Miss); - addJudgementAssert("slider tick", () => ((Slider)hitObjects[1]).NestedHitObjects[1] as SliderTick, HitResult.Great); + addJudgementAssert("slider tick", () => ((Slider)hitObjects[1]).NestedHitObjects[1] as SliderTick, HitResult.LargeTickHit); } /// @@ -252,9 +252,9 @@ namespace osu.Game.Rulesets.Osu.Tests }); addJudgementAssert(hitObjects[0], HitResult.Great); - addJudgementAssert(hitObjects[1], HitResult.Great); + addJudgementAssert(hitObjects[1], HitResult.IgnoreHit); addJudgementAssert("slider head", () => ((Slider)hitObjects[1]).HeadCircle, HitResult.Great); - addJudgementAssert("slider tick", () => ((Slider)hitObjects[1]).NestedHitObjects[1] as SliderTick, HitResult.Great); + addJudgementAssert("slider tick", () => ((Slider)hitObjects[1]).NestedHitObjects[1] as SliderTick, HitResult.LargeTickHit); } /// @@ -331,7 +331,7 @@ namespace osu.Game.Rulesets.Osu.Tests }); addJudgementAssert(hitObjects[0], HitResult.Great); - addJudgementAssert(hitObjects[1], HitResult.Great); + addJudgementAssert(hitObjects[1], HitResult.IgnoreHit); } private void addJudgementAssert(OsuHitObject hitObject, HitResult result) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs index b543b6fa94..3d8a52a864 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs @@ -312,13 +312,13 @@ namespace osu.Game.Rulesets.Osu.Tests AddAssert("Tracking dropped", assertMidSliderJudgementFail); } - private bool assertGreatJudge() => judgementResults.Any() && judgementResults.All(t => t.Type == HitResult.Great); + private bool assertGreatJudge() => judgementResults.Any() && judgementResults.All(t => t.Type.IsHit()); - private bool assertHeadMissTailTracked() => judgementResults[^2].Type == HitResult.Great && judgementResults.First().Type == HitResult.Miss; + private bool assertHeadMissTailTracked() => judgementResults[^2].Type == HitResult.IgnoreHit && judgementResults.First().Type == HitResult.Miss; - private bool assertMidSliderJudgements() => judgementResults[^2].Type == HitResult.Great; + private bool assertMidSliderJudgements() => judgementResults[^2].Type == HitResult.IgnoreHit; - private bool assertMidSliderJudgementFail() => judgementResults[^2].Type == HitResult.Miss; + private bool assertMidSliderJudgementFail() => judgementResults[^2].Type == HitResult.IgnoreMiss; private ScoreAccessibleReplayPlayer currentPlayer; From 297168ecc41e7dd053fac1d0a17c0ac898315e79 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 29 Sep 2020 18:55:06 +0900 Subject: [PATCH 1328/1782] Fix scores sometimes not being re-standardised correctly --- .../Requests/Responses/APILegacyScoreInfo.cs | 1 + osu.Game/Scoring/ScoreInfo.cs | 28 +++++++++++++++ osu.Game/Scoring/ScoreManager.cs | 34 ++++++++++++++----- 3 files changed, 54 insertions(+), 9 deletions(-) diff --git a/osu.Game/Online/API/Requests/Responses/APILegacyScoreInfo.cs b/osu.Game/Online/API/Requests/Responses/APILegacyScoreInfo.cs index b941cd8973..3d3c07a5ad 100644 --- a/osu.Game/Online/API/Requests/Responses/APILegacyScoreInfo.cs +++ b/osu.Game/Online/API/Requests/Responses/APILegacyScoreInfo.cs @@ -38,6 +38,7 @@ namespace osu.Game.Online.API.Requests.Responses Rank = Rank, Ruleset = ruleset, Mods = mods, + IsLegacyScore = true }; if (Statistics != null) diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index 4ed3f92e25..0206989231 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -185,6 +185,34 @@ namespace osu.Game.Scoring [JsonProperty("position")] public int? Position { get; set; } + private bool isLegacyScore; + + /// + /// Whether this represents a legacy (osu!stable) score. + /// + [JsonIgnore] + [NotMapped] + public bool IsLegacyScore + { + get + { + if (isLegacyScore) + return true; + + // The above check will catch legacy online scores that have an appropriate UserString + UserId. + // For non-online scores such as those imported in, a heuristic is used based on the following table: + // + // Mode | UserString | UserId + // --------------- | ---------- | --------- + // stable | | 1 + // lazer | | + // lazer (offline) | Guest | 1 + + return ID > 0 && UserID == 1 && UserString != "Guest"; + } + set => isLegacyScore = value; + } + public IEnumerable<(HitResult result, int count, int? maxCount)> GetStatisticsForDisplay() { foreach (var key in OrderAttributeUtils.GetValuesInOrder()) diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index 561ca631b3..8e8147ff39 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -10,6 +10,7 @@ using System.Threading; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore; using osu.Framework.Bindables; +using osu.Framework.Extensions; using osu.Framework.Logging; using osu.Framework.Platform; using osu.Game.Beatmaps; @@ -149,23 +150,38 @@ namespace osu.Game.Scoring return; } - int? beatmapMaxCombo = score.Beatmap.MaxCombo; + int beatmapMaxCombo; - if (beatmapMaxCombo == null) + if (score.IsLegacyScore) { - if (score.Beatmap.ID == 0 || difficulties == null) + // This score is guaranteed to be an osu!stable score. + // The combo must be determined through either the beatmap's max combo value or the difficulty calculator, as lazer's scoring has changed and the score statistics cannot be used. + if (score.Beatmap.MaxCombo == null) { - // We don't have enough information (max combo) to compute the score, so let's use the provided score. - Value = score.TotalScore; + if (score.Beatmap.ID == 0 || difficulties == null) + { + // We don't have enough information (max combo) to compute the score, so use the provided score. + Value = score.TotalScore; + return; + } + + // We can compute the max combo locally after the async beatmap difficulty computation. + difficultyBindable = difficulties().GetBindableDifficulty(score.Beatmap, score.Ruleset, score.Mods, (difficultyCancellationSource = new CancellationTokenSource()).Token); + difficultyBindable.BindValueChanged(d => updateScore(d.NewValue.MaxCombo), true); + return; } - // We can compute the max combo locally after the async beatmap difficulty computation. - difficultyBindable = difficulties().GetBindableDifficulty(score.Beatmap, score.Ruleset, score.Mods, (difficultyCancellationSource = new CancellationTokenSource()).Token); - difficultyBindable.BindValueChanged(d => updateScore(d.NewValue.MaxCombo), true); + beatmapMaxCombo = score.Beatmap.MaxCombo.Value; } else - updateScore(beatmapMaxCombo.Value); + { + // This score is guaranteed to be an osu!lazer score. + // The combo must be determined through the score's statistics, as both the beatmap's max combo and the difficulty calculator will provide osu!stable combo values. + beatmapMaxCombo = Enum.GetValues(typeof(HitResult)).OfType().Where(r => r.AffectsCombo()).Select(r => score.Statistics.GetOrDefault(r)).Sum(); + } + + updateScore(beatmapMaxCombo); } private void updateScore(int beatmapMaxCombo) From cd794eaa65ac483ce7f59d8a322b7e5890ba16fa Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 29 Sep 2020 19:07:40 +0900 Subject: [PATCH 1329/1782] Add basic selection box with drag handles --- .../Editing/TestSceneBlueprintSelectBox.cs | 39 +++ .../Compose/Components/ComposeSelectionBox.cs | 308 ++++++++++++++++++ .../Edit/Compose/Components/DragBox.cs | 2 +- .../Compose/Components/SelectionHandler.cs | 16 +- 4 files changed, 349 insertions(+), 16 deletions(-) create mode 100644 osu.Game.Tests/Visual/Editing/TestSceneBlueprintSelectBox.cs create mode 100644 osu.Game/Screens/Edit/Compose/Components/ComposeSelectionBox.cs diff --git a/osu.Game.Tests/Visual/Editing/TestSceneBlueprintSelectBox.cs b/osu.Game.Tests/Visual/Editing/TestSceneBlueprintSelectBox.cs new file mode 100644 index 0000000000..dd44472c09 --- /dev/null +++ b/osu.Game.Tests/Visual/Editing/TestSceneBlueprintSelectBox.cs @@ -0,0 +1,39 @@ +// 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.Screens.Edit.Compose.Components; +using osuTK; + +namespace osu.Game.Tests.Visual.Editing +{ + public class TestSceneComposeSelectBox : OsuTestScene + { + public TestSceneComposeSelectBox() + { + ComposeSelectionBox selectionBox = null; + + AddStep("create box", () => + Child = new Container + { + Size = new Vector2(300), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Children = new Drawable[] + { + selectionBox = new ComposeSelectionBox + { + CanRotate = true, + CanScaleX = true, + CanScaleY = true + } + } + }); + + AddToggleStep("toggle rotation", state => selectionBox.CanRotate = state); + AddToggleStep("toggle x", state => selectionBox.CanScaleX = state); + AddToggleStep("toggle y", state => selectionBox.CanScaleY = state); + } + } +} diff --git a/osu.Game/Screens/Edit/Compose/Components/ComposeSelectionBox.cs b/osu.Game/Screens/Edit/Compose/Components/ComposeSelectionBox.cs new file mode 100644 index 0000000000..c7fc078b98 --- /dev/null +++ b/osu.Game/Screens/Edit/Compose/Components/ComposeSelectionBox.cs @@ -0,0 +1,308 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Input.Events; +using osu.Game.Graphics; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Screens.Edit.Compose.Components +{ + public class ComposeSelectionBox : CompositeDrawable + { + public Action OnRotation; + public Action OnScaleX; + public Action OnScaleY; + + private bool canRotate; + + public bool CanRotate + { + get => canRotate; + set + { + canRotate = value; + recreate(); + } + } + + private bool canScaleX; + + public bool CanScaleX + { + get => canScaleX; + set + { + canScaleX = value; + recreate(); + } + } + + private bool canScaleY; + + public bool CanScaleY + { + get => canScaleY; + set + { + canScaleY = value; + recreate(); + } + } + + public const float BORDER_RADIUS = 3; + + [Resolved] + private OsuColour colours { get; set; } + + [BackgroundDependencyLoader] + private void load() + { + RelativeSizeAxes = Axes.Both; + + recreate(); + } + + private void recreate() + { + if (LoadState < LoadState.Loading) + return; + + InternalChildren = new Drawable[] + { + new Container + { + Masking = true, + BorderThickness = BORDER_RADIUS, + BorderColour = colours.YellowDark, + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + + AlwaysPresent = true, + Alpha = 0 + }, + } + }, + }; + + if (CanRotate) + { + const float separation = 40; + + AddRangeInternal(new Drawable[] + { + new Box + { + Colour = colours.YellowLight, + Blending = BlendingParameters.Additive, + Alpha = 0.3f, + Size = new Vector2(BORDER_RADIUS, separation), + Anchor = Anchor.TopCentre, + Origin = Anchor.BottomCentre, + }, + new RotationDragHandle + { + Anchor = Anchor.TopCentre, + Y = -separation, + HandleDrag = e => OnRotation?.Invoke(e) + } + }); + } + + if (CanScaleY) + { + AddRangeInternal(new[] + { + new DragHandle + { + Anchor = Anchor.TopCentre, + HandleDrag = e => OnScaleY?.Invoke(e, Anchor.TopCentre) + }, + new DragHandle + { + Anchor = Anchor.BottomCentre, + HandleDrag = e => OnScaleY?.Invoke(e, Anchor.BottomCentre) + }, + }); + } + + if (CanScaleX) + { + AddRangeInternal(new[] + { + new DragHandle + { + Anchor = Anchor.CentreLeft, + HandleDrag = e => OnScaleX?.Invoke(e, Anchor.CentreLeft) + }, + new DragHandle + { + Anchor = Anchor.CentreRight, + HandleDrag = e => OnScaleX?.Invoke(e, Anchor.CentreRight) + }, + }); + } + + if (CanScaleX && CanScaleY) + { + AddRangeInternal(new[] + { + new DragHandle + { + Anchor = Anchor.TopLeft, + HandleDrag = e => + { + OnScaleX?.Invoke(e, Anchor.TopLeft); + OnScaleY?.Invoke(e, Anchor.TopLeft); + } + }, + new DragHandle + { + Anchor = Anchor.TopRight, + HandleDrag = e => + { + OnScaleX?.Invoke(e, Anchor.TopRight); + OnScaleY?.Invoke(e, Anchor.TopRight); + } + }, + new DragHandle + { + Anchor = Anchor.BottomLeft, + HandleDrag = e => + { + OnScaleX?.Invoke(e, Anchor.BottomLeft); + OnScaleY?.Invoke(e, Anchor.BottomLeft); + } + }, + new DragHandle + { + Anchor = Anchor.BottomRight, + HandleDrag = e => + { + OnScaleX?.Invoke(e, Anchor.BottomRight); + OnScaleY?.Invoke(e, Anchor.BottomRight); + } + }, + }); + } + } + + private class RotationDragHandle : DragHandle + { + private SpriteIcon icon; + + [BackgroundDependencyLoader] + private void load() + { + Size *= 2; + + AddInternal(icon = new SpriteIcon + { + RelativeSizeAxes = Axes.Both, + Size = new Vector2(0.5f), + Icon = FontAwesome.Solid.Redo, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }); + } + + protected override void UpdateHoverState() + { + base.UpdateHoverState(); + icon.Colour = !HandlingMouse && IsHovered ? Color4.White : Color4.Black; + } + } + + private class DragHandle : Container + { + public Action HandleDrag { get; set; } + + private Circle circle; + + [Resolved] + private OsuColour colours { get; set; } + + [BackgroundDependencyLoader] + private void load() + { + Size = new Vector2(10); + Origin = Anchor.Centre; + + InternalChildren = new Drawable[] + { + circle = new Circle + { + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + UpdateHoverState(); + } + + protected override bool OnHover(HoverEvent e) + { + UpdateHoverState(); + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + base.OnHoverLost(e); + UpdateHoverState(); + } + + protected bool HandlingMouse; + + protected override bool OnMouseDown(MouseDownEvent e) + { + HandlingMouse = true; + UpdateHoverState(); + return true; + } + + protected override bool OnDragStart(DragStartEvent e) => true; + + protected override void OnDrag(DragEvent e) + { + HandleDrag?.Invoke(e); + base.OnDrag(e); + } + + protected override void OnDragEnd(DragEndEvent e) + { + HandlingMouse = false; + UpdateHoverState(); + base.OnDragEnd(e); + } + + protected override void OnMouseUp(MouseUpEvent e) + { + HandlingMouse = false; + UpdateHoverState(); + base.OnMouseUp(e); + } + + protected virtual void UpdateHoverState() + { + circle.Colour = HandlingMouse ? colours.GrayF : (IsHovered ? colours.Red : colours.YellowDark); + this.ScaleTo(HandlingMouse || IsHovered ? 1.5f : 1, 100, Easing.OutQuint); + } + } + } +} diff --git a/osu.Game/Screens/Edit/Compose/Components/DragBox.cs b/osu.Game/Screens/Edit/Compose/Components/DragBox.cs index 0615ebfc20..0ec981203a 100644 --- a/osu.Game/Screens/Edit/Compose/Components/DragBox.cs +++ b/osu.Game/Screens/Edit/Compose/Components/DragBox.cs @@ -45,7 +45,7 @@ namespace osu.Game.Screens.Edit.Compose.Components { Masking = true, BorderColour = Color4.White, - BorderThickness = SelectionHandler.BORDER_RADIUS, + BorderThickness = ComposeSelectionBox.BORDER_RADIUS, Child = new Box { RelativeSizeAxes = Axes.Both, diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index a0220cf987..ef97403d02 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -32,8 +32,6 @@ namespace osu.Game.Screens.Edit.Compose.Components /// public class SelectionHandler : CompositeDrawable, IKeyBindingHandler, IHasContextMenu { - public const float BORDER_RADIUS = 2; - public IEnumerable SelectedBlueprints => selectedBlueprints; private readonly List selectedBlueprints; @@ -69,19 +67,7 @@ namespace osu.Game.Screens.Edit.Compose.Components { Children = new Drawable[] { - new Container - { - RelativeSizeAxes = Axes.Both, - Masking = true, - BorderThickness = BORDER_RADIUS, - BorderColour = colours.YellowDark, - Child = new Box - { - RelativeSizeAxes = Axes.Both, - AlwaysPresent = true, - Alpha = 0 - } - }, + new ComposeSelectionBox(), new Container { Name = "info text", From 265bba1a886db2e988bbed5a502597128badda1b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 29 Sep 2020 19:19:48 +0900 Subject: [PATCH 1330/1782] Add test coverage of event handling --- .../Editing/TestSceneBlueprintSelectBox.cs | 34 +++++++++++++++++-- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneBlueprintSelectBox.cs b/osu.Game.Tests/Visual/Editing/TestSceneBlueprintSelectBox.cs index dd44472c09..4b12000fc3 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneBlueprintSelectBox.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneBlueprintSelectBox.cs @@ -3,6 +3,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Input.Events; using osu.Game.Screens.Edit.Compose.Components; using osuTK; @@ -10,23 +11,29 @@ namespace osu.Game.Tests.Visual.Editing { public class TestSceneComposeSelectBox : OsuTestScene { + private Container selectionArea; + public TestSceneComposeSelectBox() { ComposeSelectionBox selectionBox = null; AddStep("create box", () => - Child = new Container + Child = selectionArea = new Container { Size = new Vector2(300), + Position = -new Vector2(150), Anchor = Anchor.Centre, - Origin = Anchor.Centre, Children = new Drawable[] { selectionBox = new ComposeSelectionBox { CanRotate = true, CanScaleX = true, - CanScaleY = true + CanScaleY = true, + + OnRotation = handleRotation, + OnScaleX = handleScaleX, + OnScaleY = handleScaleY, } } }); @@ -35,5 +42,26 @@ namespace osu.Game.Tests.Visual.Editing AddToggleStep("toggle x", state => selectionBox.CanScaleX = state); AddToggleStep("toggle y", state => selectionBox.CanScaleY = state); } + + private void handleScaleY(DragEvent e, Anchor reference) + { + int direction = (reference & Anchor.y0) > 0 ? -1 : 1; + if (direction < 0) + selectionArea.Y += e.Delta.Y; + selectionArea.Height += direction * e.Delta.Y; + } + + private void handleScaleX(DragEvent e, Anchor reference) + { + int direction = (reference & Anchor.x0) > 0 ? -1 : 1; + if (direction < 0) + selectionArea.X += e.Delta.X; + selectionArea.Width += direction * e.Delta.X; + } + + private void handleRotation(DragEvent e) + { + selectionArea.Rotation += e.Delta.X; + } } } From 0a10e40ce0091d40dcd0bc7e7cecebffe912ea49 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 29 Sep 2020 19:43:50 +0900 Subject: [PATCH 1331/1782] Add scaling support to osu! editor --- .../Edit/OsuSelectionHandler.cs | 97 ++++++++++++++++++- .../Compose/Components/SelectionHandler.cs | 4 +- 2 files changed, 96 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs index 9418565907..e29536d6b2 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs @@ -2,6 +2,8 @@ // See the LICENCE file in the repository root for full licence text. using System.Linq; +using osu.Framework.Graphics; +using osu.Framework.Input.Events; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Screens.Edit.Compose.Components; using osuTK; @@ -10,7 +12,55 @@ namespace osu.Game.Rulesets.Osu.Edit { public class OsuSelectionHandler : SelectionHandler { - public override bool HandleMovement(MoveSelectionEvent moveEvent) + public override ComposeSelectionBox CreateSelectionBox() + => new ComposeSelectionBox + { + CanRotate = true, + CanScaleX = true, + CanScaleY = true, + + // OnRotation = handleRotation, + OnScaleX = handleScaleX, + OnScaleY = handleScaleY, + }; + + private void handleScaleY(DragEvent e, Anchor reference) + { + int direction = (reference & Anchor.y0) > 0 ? -1 : 1; + + if (direction < 0) + { + // when resizing from a top drag handle, we want to move the selection first + if (!moveSelection(new Vector2(0, e.Delta.Y))) + return; + } + + scaleSelection(new Vector2(0, direction * e.Delta.Y)); + } + + private void handleScaleX(DragEvent e, Anchor reference) + { + int direction = (reference & Anchor.x0) > 0 ? -1 : 1; + + if (direction < 0) + { + // when resizing from a top drag handle, we want to move the selection first + if (!moveSelection(new Vector2(e.Delta.X, 0))) + return; + } + + scaleSelection(new Vector2(direction * e.Delta.X, 0)); + } + + private void handleRotation(DragEvent e) + { + // selectionArea.Rotation += e.Delta.X; + } + + public override bool HandleMovement(MoveSelectionEvent moveEvent) => + moveSelection(moveEvent.InstantDelta); + + private bool scaleSelection(Vector2 scale) { Vector2 minPosition = new Vector2(float.MaxValue, float.MaxValue); Vector2 maxPosition = new Vector2(float.MinValue, float.MinValue); @@ -25,8 +75,47 @@ namespace osu.Game.Rulesets.Osu.Edit } // 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)); + minPosition = Vector2.ComponentMin(minPosition, Vector2.ComponentMin(h.EndPosition, h.Position)); + maxPosition = Vector2.ComponentMax(maxPosition, Vector2.ComponentMax(h.EndPosition, h.Position)); + } + + Vector2 size = maxPosition - minPosition; + Vector2 newSize = size + scale; + + foreach (var h in SelectedHitObjects.OfType()) + { + if (h is Spinner) + { + // Spinners don't support position adjustments + continue; + } + + if (scale.X != 1) + h.Position = new Vector2(minPosition.X + (h.X - minPosition.X) / size.X * newSize.X, h.Y); + if (scale.Y != 1) + h.Position = new Vector2(h.X, minPosition.Y + (h.Y - minPosition.Y) / size.Y * newSize.Y); + } + + return true; + } + + private bool moveSelection(Vector2 delta) + { + 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 + delta, h.Position + delta)); + maxPosition = Vector2.ComponentMax(maxPosition, Vector2.ComponentMax(h.EndPosition + delta, h.Position + delta)); } if (minPosition.X < 0 || minPosition.Y < 0 || maxPosition.X > DrawWidth || maxPosition.Y > DrawHeight) @@ -40,7 +129,7 @@ namespace osu.Game.Rulesets.Osu.Edit continue; } - h.Position += moveEvent.InstantDelta; + h.Position += delta; } return true; diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index ef97403d02..39e413ef05 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -67,7 +67,7 @@ namespace osu.Game.Screens.Edit.Compose.Components { Children = new Drawable[] { - new ComposeSelectionBox(), + CreateSelectionBox(), new Container { Name = "info text", @@ -91,6 +91,8 @@ namespace osu.Game.Screens.Edit.Compose.Components }; } + public virtual ComposeSelectionBox CreateSelectionBox() => new ComposeSelectionBox(); + #region User Input Handling /// From 33b24b6f46a6b9ffa596a443d5ddaf67aa40940e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 29 Sep 2020 19:50:03 +0900 Subject: [PATCH 1332/1782] Refactor to be able to get a quad for the current selection --- .../Edit/OsuSelectionHandler.cs | 69 ++++++++++++++----- 1 file changed, 52 insertions(+), 17 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs index e29536d6b2..7f4ee54243 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs @@ -1,8 +1,10 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Linq; using osu.Framework.Graphics; +using osu.Framework.Graphics.Primitives; using osu.Framework.Input.Events; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Screens.Edit.Compose.Components; @@ -54,7 +56,6 @@ namespace osu.Game.Rulesets.Osu.Edit private void handleRotation(DragEvent e) { - // selectionArea.Rotation += e.Delta.X; } public override bool HandleMovement(MoveSelectionEvent moveEvent) => @@ -62,24 +63,11 @@ namespace osu.Game.Rulesets.Osu.Edit private bool scaleSelection(Vector2 scale) { - Vector2 minPosition = new Vector2(float.MaxValue, float.MaxValue); - Vector2 maxPosition = new Vector2(float.MinValue, float.MinValue); + Quad quad = getSelectionQuad(); - // 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; - } + Vector2 minPosition = quad.TopLeft; - // Stacking is not considered - minPosition = Vector2.ComponentMin(minPosition, Vector2.ComponentMin(h.EndPosition, h.Position)); - maxPosition = Vector2.ComponentMax(maxPosition, Vector2.ComponentMax(h.EndPosition, h.Position)); - } - - Vector2 size = maxPosition - minPosition; + Vector2 size = quad.Size; Vector2 newSize = size + scale; foreach (var h in SelectedHitObjects.OfType()) @@ -134,5 +122,52 @@ namespace osu.Game.Rulesets.Osu.Edit return true; } + + private Quad getSelectionQuad() + { + 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, h.Position)); + maxPosition = Vector2.ComponentMax(maxPosition, Vector2.ComponentMax(h.EndPosition, h.Position)); + } + + Vector2 size = maxPosition - minPosition; + + return new Quad(minPosition.X, minPosition.Y, size.X, size.Y); + } + + /// + /// Returns rotated position from a given point. + /// + /// The point. + /// The center to rotate around. + /// The angle to rotate (in degrees). + internal static Vector2 Rotate(Vector2 p, Vector2 center, int angle) + { + angle = -angle; + + p.X -= center.X; + p.Y -= center.Y; + + Vector2 ret; + ret.X = (float)(p.X * Math.Cos(angle / 180f * Math.PI) + p.Y * Math.Sin(angle / 180f * Math.PI)); + ret.Y = (float)(p.X * -Math.Sin(angle / 180f * Math.PI) + p.Y * Math.Cos(angle / 180f * Math.PI)); + + ret.X += center.X; + ret.Y += center.Y; + + return ret; + } } } From 934db14e037cedb82666904b7f390df62b426f90 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 29 Sep 2020 20:00:19 +0900 Subject: [PATCH 1333/1782] Add rotation support --- .../Edit/OsuSelectionHandler.cs | 37 ++++++++++++++++++- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs index 7f4ee54243..84056a69c7 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs @@ -6,6 +6,7 @@ using System.Linq; using osu.Framework.Graphics; using osu.Framework.Graphics.Primitives; using osu.Framework.Input.Events; +using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Screens.Edit.Compose.Components; using osuTK; @@ -21,7 +22,7 @@ namespace osu.Game.Rulesets.Osu.Edit CanScaleX = true, CanScaleY = true, - // OnRotation = handleRotation, + OnRotation = handleRotation, OnScaleX = handleScaleX, OnScaleY = handleScaleY, }; @@ -54,13 +55,45 @@ namespace osu.Game.Rulesets.Osu.Edit scaleSelection(new Vector2(direction * e.Delta.X, 0)); } + private Vector2? centre; + private void handleRotation(DragEvent e) { + rotateSelection(e.Delta.X); } public override bool HandleMovement(MoveSelectionEvent moveEvent) => moveSelection(moveEvent.InstantDelta); + private bool rotateSelection(in float delta) + { + Quad quad = getSelectionQuad(); + + if (!centre.HasValue) + centre = quad.Centre; + + foreach (var h in SelectedHitObjects.OfType()) + { + if (h is Spinner) + { + // Spinners don't support position adjustments + continue; + } + + h.Position = Rotate(h.Position, centre.Value, delta); + + if (h is IHasPath path) + { + foreach (var point in path.Path.ControlPoints) + { + point.Position.Value = Rotate(point.Position.Value, Vector2.Zero, delta); + } + } + } + + return true; + } + private bool scaleSelection(Vector2 scale) { Quad quad = getSelectionQuad(); @@ -153,7 +186,7 @@ namespace osu.Game.Rulesets.Osu.Edit /// The point. /// The center to rotate around. /// The angle to rotate (in degrees). - internal static Vector2 Rotate(Vector2 p, Vector2 center, int angle) + internal static Vector2 Rotate(Vector2 p, Vector2 center, float angle) { angle = -angle; From a2e2cca396e2765221185f95644157afc0b51bd4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 29 Sep 2020 20:08:28 +0900 Subject: [PATCH 1334/1782] Add proper change handler support --- .../Edit/OsuSelectionHandler.cs | 14 ++++ .../Compose/Components/ComposeSelectionBox.cs | 64 ++++++++++++++++--- 2 files changed, 68 insertions(+), 10 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs index 84056a69c7..126fdf0932 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs @@ -22,11 +22,25 @@ namespace osu.Game.Rulesets.Osu.Edit CanScaleX = true, CanScaleY = true, + OperationStarted = onStart, + OperationEnded = onEnd, + OnRotation = handleRotation, OnScaleX = handleScaleX, OnScaleY = handleScaleY, }; + private void onEnd() + { + ChangeHandler.EndChange(); + centre = null; + } + + private void onStart() + { + ChangeHandler.BeginChange(); + } + private void handleScaleY(DragEvent e, Anchor reference) { int direction = (reference & Anchor.y0) > 0 ? -1 : 1; diff --git a/osu.Game/Screens/Edit/Compose/Components/ComposeSelectionBox.cs b/osu.Game/Screens/Edit/Compose/Components/ComposeSelectionBox.cs index c7fc078b98..dba1965569 100644 --- a/osu.Game/Screens/Edit/Compose/Components/ComposeSelectionBox.cs +++ b/osu.Game/Screens/Edit/Compose/Components/ComposeSelectionBox.cs @@ -20,6 +20,9 @@ namespace osu.Game.Screens.Edit.Compose.Components public Action OnScaleX; public Action OnScaleY; + public Action OperationStarted; + public Action OperationEnded; + private bool canRotate; public bool CanRotate @@ -114,7 +117,9 @@ namespace osu.Game.Screens.Edit.Compose.Components { Anchor = Anchor.TopCentre, Y = -separation, - HandleDrag = e => OnRotation?.Invoke(e) + HandleDrag = e => OnRotation?.Invoke(e), + OperationStarted = operationStarted, + OperationEnded = operationEnded } }); } @@ -126,12 +131,16 @@ namespace osu.Game.Screens.Edit.Compose.Components new DragHandle { Anchor = Anchor.TopCentre, - HandleDrag = e => OnScaleY?.Invoke(e, Anchor.TopCentre) + HandleDrag = e => OnScaleY?.Invoke(e, Anchor.TopCentre), + OperationStarted = operationStarted, + OperationEnded = operationEnded }, new DragHandle { Anchor = Anchor.BottomCentre, - HandleDrag = e => OnScaleY?.Invoke(e, Anchor.BottomCentre) + HandleDrag = e => OnScaleY?.Invoke(e, Anchor.BottomCentre), + OperationStarted = operationStarted, + OperationEnded = operationEnded }, }); } @@ -143,12 +152,16 @@ namespace osu.Game.Screens.Edit.Compose.Components new DragHandle { Anchor = Anchor.CentreLeft, - HandleDrag = e => OnScaleX?.Invoke(e, Anchor.CentreLeft) + HandleDrag = e => OnScaleX?.Invoke(e, Anchor.CentreLeft), + OperationStarted = operationStarted, + OperationEnded = operationEnded }, new DragHandle { Anchor = Anchor.CentreRight, - HandleDrag = e => OnScaleX?.Invoke(e, Anchor.CentreRight) + HandleDrag = e => OnScaleX?.Invoke(e, Anchor.CentreRight), + OperationStarted = operationStarted, + OperationEnded = operationEnded }, }); } @@ -164,7 +177,9 @@ namespace osu.Game.Screens.Edit.Compose.Components { OnScaleX?.Invoke(e, Anchor.TopLeft); OnScaleY?.Invoke(e, Anchor.TopLeft); - } + }, + OperationStarted = operationStarted, + OperationEnded = operationEnded }, new DragHandle { @@ -173,7 +188,9 @@ namespace osu.Game.Screens.Edit.Compose.Components { OnScaleX?.Invoke(e, Anchor.TopRight); OnScaleY?.Invoke(e, Anchor.TopRight); - } + }, + OperationStarted = operationStarted, + OperationEnded = operationEnded }, new DragHandle { @@ -182,7 +199,9 @@ namespace osu.Game.Screens.Edit.Compose.Components { OnScaleX?.Invoke(e, Anchor.BottomLeft); OnScaleY?.Invoke(e, Anchor.BottomLeft); - } + }, + OperationStarted = operationStarted, + OperationEnded = operationEnded }, new DragHandle { @@ -191,12 +210,28 @@ namespace osu.Game.Screens.Edit.Compose.Components { OnScaleX?.Invoke(e, Anchor.BottomRight); OnScaleY?.Invoke(e, Anchor.BottomRight); - } + }, + OperationStarted = operationStarted, + OperationEnded = operationEnded }, }); } } + private int activeOperations; + + private void operationEnded() + { + if (--activeOperations == 0) + OperationEnded?.Invoke(); + } + + private void operationStarted() + { + if (activeOperations++ == 0) + OperationStarted?.Invoke(); + } + private class RotationDragHandle : DragHandle { private SpriteIcon icon; @@ -225,6 +260,9 @@ namespace osu.Game.Screens.Edit.Compose.Components private class DragHandle : Container { + public Action OperationStarted; + public Action OperationEnded; + public Action HandleDrag { get; set; } private Circle circle; @@ -276,7 +314,11 @@ namespace osu.Game.Screens.Edit.Compose.Components return true; } - protected override bool OnDragStart(DragStartEvent e) => true; + protected override bool OnDragStart(DragStartEvent e) + { + OperationStarted?.Invoke(); + return true; + } protected override void OnDrag(DragEvent e) { @@ -287,6 +329,8 @@ namespace osu.Game.Screens.Edit.Compose.Components protected override void OnDragEnd(DragEndEvent e) { HandlingMouse = false; + OperationEnded?.Invoke(); + UpdateHoverState(); base.OnDragEnd(e); } From 5ae6b2cf5b0bea7c5f53fd3c84934a4b57492f34 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 29 Sep 2020 20:10:17 +0900 Subject: [PATCH 1335/1782] Fix syntax --- osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs index 126fdf0932..505b84e699 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs @@ -83,8 +83,7 @@ namespace osu.Game.Rulesets.Osu.Edit { Quad quad = getSelectionQuad(); - if (!centre.HasValue) - centre = quad.Centre; + centre ??= quad.Centre; foreach (var h in SelectedHitObjects.OfType()) { From f93c72dd920a2d239919380bb954dfa2cfdd4cb7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 29 Sep 2020 20:21:13 +0900 Subject: [PATCH 1336/1782] Fix non-matching filename --- ...estSceneBlueprintSelectBox.cs => TestSceneComposeSelectBox.cs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename osu.Game.Tests/Visual/Editing/{TestSceneBlueprintSelectBox.cs => TestSceneComposeSelectBox.cs} (100%) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneBlueprintSelectBox.cs b/osu.Game.Tests/Visual/Editing/TestSceneComposeSelectBox.cs similarity index 100% rename from osu.Game.Tests/Visual/Editing/TestSceneBlueprintSelectBox.cs rename to osu.Game.Tests/Visual/Editing/TestSceneComposeSelectBox.cs From 42f666cd24456ac823736fb2a6f5a876c092e735 Mon Sep 17 00:00:00 2001 From: Shane Woolcock Date: Tue, 29 Sep 2020 23:04:03 +0930 Subject: [PATCH 1337/1782] Set icon for SDL desktop window --- osu.Desktop/OsuGameDesktop.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Desktop/OsuGameDesktop.cs b/osu.Desktop/OsuGameDesktop.cs index 2079f136d2..659730630a 100644 --- a/osu.Desktop/OsuGameDesktop.cs +++ b/osu.Desktop/OsuGameDesktop.cs @@ -138,6 +138,7 @@ namespace osu.Desktop // SDL2 DesktopWindow case DesktopWindow desktopWindow: desktopWindow.CursorState.Value |= CursorState.Hidden; + desktopWindow.SetIconFromStream(Assembly.GetExecutingAssembly().GetManifestResourceStream(GetType(), "lazer.ico")); desktopWindow.Title = Name; desktopWindow.DragDrop += f => fileDrop(new[] { f }); break; From 1386c9fe668e13c89259c44d92e704503eff0e64 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 30 Sep 2020 12:45:43 +0900 Subject: [PATCH 1338/1782] Standardise time display formats across the editor --- .../Extensions/EditorDisplayExtensions.cs | 26 +++++++++++++++++++ .../Edit/Components/TimeInfoContainer.cs | 6 ++--- .../Screens/Edit/Timing/ControlPointTable.cs | 3 ++- 3 files changed, 30 insertions(+), 5 deletions(-) create mode 100644 osu.Game/Extensions/EditorDisplayExtensions.cs diff --git a/osu.Game/Extensions/EditorDisplayExtensions.cs b/osu.Game/Extensions/EditorDisplayExtensions.cs new file mode 100644 index 0000000000..f749b88b46 --- /dev/null +++ b/osu.Game/Extensions/EditorDisplayExtensions.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; + +namespace osu.Game.Extensions +{ + public static class EditorDisplayExtensions + { + /// + /// Get an editor formatted string (mm:ss:mss) + /// + /// A time value in milliseconds. + /// An editor formatted display string. + public static string ToEditorFormattedString(this double milliseconds) => + ToEditorFormattedString(TimeSpan.FromMilliseconds(milliseconds)); + + /// + /// Get an editor formatted string (mm:ss:mss) + /// + /// A time value. + /// An editor formatted display string. + public static string ToEditorFormattedString(this TimeSpan timeSpan) => + $"{(timeSpan < TimeSpan.Zero ? "-" : string.Empty)}{timeSpan:mm\\:ss\\:fff}"; + } +} diff --git a/osu.Game/Screens/Edit/Components/TimeInfoContainer.cs b/osu.Game/Screens/Edit/Components/TimeInfoContainer.cs index c68eeeb4f9..0a8c339559 100644 --- a/osu.Game/Screens/Edit/Components/TimeInfoContainer.cs +++ b/osu.Game/Screens/Edit/Components/TimeInfoContainer.cs @@ -3,8 +3,8 @@ using osu.Framework.Graphics; using osu.Game.Graphics.Sprites; -using System; using osu.Framework.Allocation; +using osu.Game.Extensions; using osu.Game.Graphics; namespace osu.Game.Screens.Edit.Components @@ -35,9 +35,7 @@ namespace osu.Game.Screens.Edit.Components protected override void Update() { base.Update(); - - var timespan = TimeSpan.FromMilliseconds(editorClock.CurrentTime); - trackTimer.Text = $"{(timespan < TimeSpan.Zero ? "-" : string.Empty)}{timespan:mm\\:ss\\:fff}"; + trackTimer.Text = editorClock.CurrentTime.ToEditorFormattedString(); } } } diff --git a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs index c0c0bcead2..87af4546f1 100644 --- a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs +++ b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs @@ -11,6 +11,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Extensions; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; @@ -89,7 +90,7 @@ namespace osu.Game.Screens.Edit.Timing }, new OsuSpriteText { - Text = $"{group.Time:n0}ms", + Text = group.Time.ToEditorFormattedString(), Font = OsuFont.GetFont(size: text_size, weight: FontWeight.Bold) }, new ControlGroupAttributes(group), From 99a3801267d7e45daea36638c695d146385c7072 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 30 Sep 2020 13:02:05 +0900 Subject: [PATCH 1339/1782] Tidy up scale/rotation operation code --- .../Edit/OsuSelectionHandler.cs | 64 ++++++++----------- 1 file changed, 28 insertions(+), 36 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs index 505b84e699..c7be921a4e 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs @@ -6,6 +6,7 @@ using System.Linq; using osu.Framework.Graphics; using osu.Framework.Graphics.Primitives; using osu.Framework.Input.Events; +using osu.Framework.Utils; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Screens.Edit.Compose.Components; @@ -22,24 +23,25 @@ namespace osu.Game.Rulesets.Osu.Edit CanScaleX = true, CanScaleY = true, - OperationStarted = onStart, - OperationEnded = onEnd, + OperationStarted = () => ChangeHandler.BeginChange(), + OperationEnded = () => + { + ChangeHandler.EndChange(); + referenceOrigin = null; + }, - OnRotation = handleRotation, + OnRotation = e => rotateSelection(e.Delta.X), OnScaleX = handleScaleX, OnScaleY = handleScaleY, }; - private void onEnd() - { - ChangeHandler.EndChange(); - centre = null; - } + public override bool HandleMovement(MoveSelectionEvent moveEvent) => + moveSelection(moveEvent.InstantDelta); - private void onStart() - { - ChangeHandler.BeginChange(); - } + /// + /// During a transform, the initial origin is stored so it can be used throughout the operation. + /// + private Vector2? referenceOrigin; private void handleScaleY(DragEvent e, Anchor reference) { @@ -61,7 +63,7 @@ namespace osu.Game.Rulesets.Osu.Edit if (direction < 0) { - // when resizing from a top drag handle, we want to move the selection first + // when resizing from a left drag handle, we want to move the selection first if (!moveSelection(new Vector2(e.Delta.X, 0))) return; } @@ -69,21 +71,11 @@ namespace osu.Game.Rulesets.Osu.Edit scaleSelection(new Vector2(direction * e.Delta.X, 0)); } - private Vector2? centre; - - private void handleRotation(DragEvent e) - { - rotateSelection(e.Delta.X); - } - - public override bool HandleMovement(MoveSelectionEvent moveEvent) => - moveSelection(moveEvent.InstantDelta); - private bool rotateSelection(in float delta) { Quad quad = getSelectionQuad(); - centre ??= quad.Centre; + referenceOrigin ??= quad.Centre; foreach (var h in SelectedHitObjects.OfType()) { @@ -93,13 +85,13 @@ namespace osu.Game.Rulesets.Osu.Edit continue; } - h.Position = Rotate(h.Position, centre.Value, delta); + h.Position = rotatePointAroundOrigin(h.Position, referenceOrigin.Value, delta); if (h is IHasPath path) { foreach (var point in path.Path.ControlPoints) { - point.Position.Value = Rotate(point.Position.Value, Vector2.Zero, delta); + point.Position.Value = rotatePointAroundOrigin(point.Position.Value, Vector2.Zero, delta); } } } @@ -194,24 +186,24 @@ namespace osu.Game.Rulesets.Osu.Edit } /// - /// Returns rotated position from a given point. + /// Rotate a point around an arbitrary origin. /// - /// The point. - /// The center to rotate around. + /// The point. + /// The centre origin to rotate around. /// The angle to rotate (in degrees). - internal static Vector2 Rotate(Vector2 p, Vector2 center, float angle) + private static Vector2 rotatePointAroundOrigin(Vector2 point, Vector2 origin, float angle) { angle = -angle; - p.X -= center.X; - p.Y -= center.Y; + point.X -= origin.X; + point.Y -= origin.Y; Vector2 ret; - ret.X = (float)(p.X * Math.Cos(angle / 180f * Math.PI) + p.Y * Math.Sin(angle / 180f * Math.PI)); - ret.Y = (float)(p.X * -Math.Sin(angle / 180f * Math.PI) + p.Y * Math.Cos(angle / 180f * Math.PI)); + ret.X = (float)(point.X * Math.Cos(MathUtils.DegreesToRadians(angle)) + point.Y * Math.Sin(angle / 180f * Math.PI)); + ret.Y = (float)(point.X * -Math.Sin(MathUtils.DegreesToRadians(angle)) + point.Y * Math.Cos(angle / 180f * Math.PI)); - ret.X += center.X; - ret.Y += center.Y; + ret.X += origin.X; + ret.Y += origin.Y; return ret; } From f2c26c0927c1cc7dfc5d55b74218be066eead32b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 30 Sep 2020 13:07:24 +0900 Subject: [PATCH 1340/1782] Move information text underneath the selection box --- osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index 39e413ef05..afaa5b0f3d 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -67,7 +67,7 @@ namespace osu.Game.Screens.Edit.Compose.Components { Children = new Drawable[] { - CreateSelectionBox(), + // todo: should maybe be inside the SelectionBox? new Container { Name = "info text", @@ -86,7 +86,8 @@ namespace osu.Game.Screens.Edit.Compose.Components Font = OsuFont.Default.With(size: 11) } } - } + }, + CreateSelectionBox(), } }; } From 39b55a85df11a2dc36257990d176245ebb9d2500 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 30 Sep 2020 13:52:57 +0900 Subject: [PATCH 1341/1782] Move a lot of the implementation to base SelectionHandler --- .../Edit/OsuSelectionHandler.cs | 56 +++++++++------- .../Compose/Components/SelectionHandler.cs | 67 ++++++++++++++++++- 2 files changed, 94 insertions(+), 29 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs index c7be921a4e..a2642bda83 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs @@ -5,7 +5,6 @@ using System; using System.Linq; using osu.Framework.Graphics; using osu.Framework.Graphics.Primitives; -using osu.Framework.Input.Events; using osu.Framework.Utils; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Objects; @@ -16,24 +15,22 @@ namespace osu.Game.Rulesets.Osu.Edit { public class OsuSelectionHandler : SelectionHandler { - public override ComposeSelectionBox CreateSelectionBox() - => new ComposeSelectionBox - { - CanRotate = true, - CanScaleX = true, - CanScaleY = true, + protected override void OnSelectionChanged() + { + base.OnSelectionChanged(); - OperationStarted = () => ChangeHandler.BeginChange(), - OperationEnded = () => - { - ChangeHandler.EndChange(); - referenceOrigin = null; - }, + bool canOperate = SelectedHitObjects.Count() > 1 || SelectedHitObjects.Any(s => s is Slider); - OnRotation = e => rotateSelection(e.Delta.X), - OnScaleX = handleScaleX, - OnScaleY = handleScaleY, - }; + SelectionBox.CanRotate = canOperate; + SelectionBox.CanScaleX = canOperate; + SelectionBox.CanScaleY = canOperate; + } + + protected override void OnDragOperationEnded() + { + base.OnDragOperationEnded(); + referenceOrigin = null; + } public override bool HandleMovement(MoveSelectionEvent moveEvent) => moveSelection(moveEvent.InstantDelta); @@ -43,35 +40,35 @@ namespace osu.Game.Rulesets.Osu.Edit /// private Vector2? referenceOrigin; - private void handleScaleY(DragEvent e, Anchor reference) + public override bool HandleScaleY(in float scale, Anchor reference) { int direction = (reference & Anchor.y0) > 0 ? -1 : 1; if (direction < 0) { // when resizing from a top drag handle, we want to move the selection first - if (!moveSelection(new Vector2(0, e.Delta.Y))) - return; + if (!moveSelection(new Vector2(0, scale))) + return false; } - scaleSelection(new Vector2(0, direction * e.Delta.Y)); + return scaleSelection(new Vector2(0, direction * scale)); } - private void handleScaleX(DragEvent e, Anchor reference) + public override bool HandleScaleX(in float scale, Anchor reference) { int direction = (reference & Anchor.x0) > 0 ? -1 : 1; if (direction < 0) { // when resizing from a left drag handle, we want to move the selection first - if (!moveSelection(new Vector2(e.Delta.X, 0))) - return; + if (!moveSelection(new Vector2(scale, 0))) + return false; } - scaleSelection(new Vector2(direction * e.Delta.X, 0)); + return scaleSelection(new Vector2(direction * scale, 0)); } - private bool rotateSelection(in float delta) + public override bool HandleRotation(float delta) { Quad quad = getSelectionQuad(); @@ -96,6 +93,7 @@ namespace osu.Game.Rulesets.Osu.Edit } } + // todo: not always return true; } @@ -161,8 +159,14 @@ namespace osu.Game.Rulesets.Osu.Edit return true; } + /// + /// Returns a gamefield-space quad surrounding the current selection. + /// private Quad getSelectionQuad() { + if (!SelectedHitObjects.Any()) + return new Quad(); + Vector2 minPosition = new Vector2(float.MaxValue, float.MaxValue); Vector2 maxPosition = new Vector2(float.MinValue, float.MinValue); diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index afaa5b0f3d..6cd503b580 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -43,6 +43,8 @@ namespace osu.Game.Screens.Edit.Compose.Components private OsuSpriteText selectionDetailsText; + protected ComposeSelectionBox SelectionBox { get; private set; } + [Resolved(CanBeNull = true)] protected EditorBeatmap EditorBeatmap { get; private set; } @@ -87,12 +89,37 @@ namespace osu.Game.Screens.Edit.Compose.Components } } }, - CreateSelectionBox(), + SelectionBox = CreateSelectionBox(), } }; } - public virtual ComposeSelectionBox CreateSelectionBox() => new ComposeSelectionBox(); + public ComposeSelectionBox CreateSelectionBox() + => new ComposeSelectionBox + { + OperationStarted = OnDragOperationBegan, + OperationEnded = OnDragOperationEnded, + + OnRotation = e => HandleRotation(e.Delta.X), + OnScaleX = (e, anchor) => HandleScaleX(e.Delta.X, anchor), + OnScaleY = (e, anchor) => HandleScaleY(e.Delta.Y, anchor), + }; + + /// + /// Fired when a drag operation ends from the selection box. + /// + protected virtual void OnDragOperationBegan() + { + ChangeHandler.BeginChange(); + } + + /// + /// Fired when a drag operation begins from the selection box. + /// + protected virtual void OnDragOperationEnded() + { + ChangeHandler.EndChange(); + } #region User Input Handling @@ -108,7 +135,30 @@ namespace osu.Game.Screens.Edit.Compose.Components /// Whether any s could be moved. /// Returning true will also propagate StartTime changes provided by the closest . /// - public virtual bool HandleMovement(MoveSelectionEvent moveEvent) => true; + public virtual bool HandleMovement(MoveSelectionEvent moveEvent) => false; + + /// + /// Handles the selected s being rotated. + /// + /// The delta angle to apply to the selection. + /// Whether any s could be moved. + public virtual bool HandleRotation(float angle) => false; + + /// + /// Handles the selected s being scaled in a vertical direction. + /// + /// The delta scale to apply. + /// The point of reference where the scale is originating from. + /// Whether any s could be moved. + public virtual bool HandleScaleY(in float scale, Anchor anchor) => false; + + /// + /// Handles the selected s being scaled in a horizontal direction. + /// + /// The delta scale to apply. + /// The point of reference where the scale is originating from. + /// Whether any s could be moved. + public virtual bool HandleScaleX(in float scale, Anchor anchor) => false; public bool OnPressed(PlatformAction action) { @@ -211,11 +261,22 @@ namespace osu.Game.Screens.Edit.Compose.Components selectionDetailsText.Text = count > 0 ? count.ToString() : string.Empty; if (count > 0) + { Show(); + OnSelectionChanged(); + } else Hide(); } + /// + /// Triggered whenever more than one object is selected, on each change. + /// Should update the selection box's state to match supported operations. + /// + protected virtual void OnSelectionChanged() + { + } + protected override void Update() { base.Update(); From 313b0d149fa8d5da93dade143312f8d0d437d0f0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 30 Sep 2020 14:41:32 +0900 Subject: [PATCH 1342/1782] Refactor scale and rotation operations to share code better Also adds support for scaling individual sliders. --- .../Edit/OsuSelectionHandler.cs | 160 ++++++++---------- 1 file changed, 69 insertions(+), 91 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs index a2642bda83..706c41c2e3 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.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.Framework.Graphics; using osu.Framework.Graphics.Primitives; @@ -40,84 +41,70 @@ namespace osu.Game.Rulesets.Osu.Edit /// private Vector2? referenceOrigin; - public override bool HandleScaleY(in float scale, Anchor reference) - { - int direction = (reference & Anchor.y0) > 0 ? -1 : 1; + public override bool HandleScaleY(in float scale, Anchor reference) => + scaleSelection(new Vector2(0, ((reference & Anchor.y0) > 0 ? -1 : 1) * scale), reference); - if (direction < 0) - { - // when resizing from a top drag handle, we want to move the selection first - if (!moveSelection(new Vector2(0, scale))) - return false; - } - - return scaleSelection(new Vector2(0, direction * scale)); - } - - public override bool HandleScaleX(in float scale, Anchor reference) - { - int direction = (reference & Anchor.x0) > 0 ? -1 : 1; - - if (direction < 0) - { - // when resizing from a left drag handle, we want to move the selection first - if (!moveSelection(new Vector2(scale, 0))) - return false; - } - - return scaleSelection(new Vector2(direction * scale, 0)); - } + public override bool HandleScaleX(in float scale, Anchor reference) => + scaleSelection(new Vector2(((reference & Anchor.x0) > 0 ? -1 : 1) * scale, 0), reference); public override bool HandleRotation(float delta) { - Quad quad = getSelectionQuad(); + var hitObjects = selectedMovableObjects; + + Quad quad = getSurroundingQuad(hitObjects); referenceOrigin ??= quad.Centre; - foreach (var h in SelectedHitObjects.OfType()) + foreach (var h in hitObjects) { - if (h is Spinner) - { - // Spinners don't support position adjustments - continue; - } - h.Position = rotatePointAroundOrigin(h.Position, referenceOrigin.Value, delta); if (h is IHasPath path) { foreach (var point in path.Path.ControlPoints) - { point.Position.Value = rotatePointAroundOrigin(point.Position.Value, Vector2.Zero, delta); - } } } - // todo: not always + // this isn't always the case but let's be lenient for now. return true; } - private bool scaleSelection(Vector2 scale) + private bool scaleSelection(Vector2 scale, Anchor reference) { - Quad quad = getSelectionQuad(); + var hitObjects = selectedMovableObjects; - Vector2 minPosition = quad.TopLeft; - - Vector2 size = quad.Size; - Vector2 newSize = size + scale; - - foreach (var h in SelectedHitObjects.OfType()) + // for the time being, allow resizing of slider paths only if the slider is + // the only hit object selected. with a group selection, it's likely the user + // is not looking to change the duration of the slider but expand the whole pattern. + if (hitObjects.Length == 1 && hitObjects.First() is Slider slider) { - if (h is Spinner) - { - // Spinners don't support position adjustments - continue; - } + var quad = getSurroundingQuad(slider.Path.ControlPoints.Select(p => p.Position.Value)); + Vector2 delta = Vector2.One + new Vector2(scale.X / quad.Width, scale.Y / quad.Height); - if (scale.X != 1) - h.Position = new Vector2(minPosition.X + (h.X - minPosition.X) / size.X * newSize.X, h.Y); - if (scale.Y != 1) - h.Position = new Vector2(h.X, minPosition.Y + (h.Y - minPosition.Y) / size.Y * newSize.Y); + foreach (var point in slider.Path.ControlPoints) + point.Position.Value *= delta; + } + else + { + // move the selection before scaling if dragging from top or left anchors. + if ((reference & Anchor.x0) > 0 && !moveSelection(new Vector2(-scale.X, 0))) return false; + if ((reference & Anchor.y0) > 0 && !moveSelection(new Vector2(0, -scale.Y))) return false; + + Quad quad = getSurroundingQuad(hitObjects); + + Vector2 minPosition = quad.TopLeft; + + Vector2 size = quad.Size; + Vector2 newSize = size + scale; + + foreach (var h in hitObjects) + { + if (scale.X != 1) + h.Position = new Vector2(minPosition.X + (h.X - minPosition.X) / size.X * newSize.X, h.Y); + if (scale.Y != 1) + h.Position = new Vector2(h.X, minPosition.Y + (h.Y - minPosition.Y) / size.Y * newSize.Y); + } } return true; @@ -125,44 +112,34 @@ namespace osu.Game.Rulesets.Osu.Edit private bool moveSelection(Vector2 delta) { - Vector2 minPosition = new Vector2(float.MaxValue, float.MaxValue); - Vector2 maxPosition = new Vector2(float.MinValue, float.MinValue); + var hitObjects = selectedMovableObjects; - // 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; - } + Quad quad = getSurroundingQuad(hitObjects); - // Stacking is not considered - minPosition = Vector2.ComponentMin(minPosition, Vector2.ComponentMin(h.EndPosition + delta, h.Position + delta)); - maxPosition = Vector2.ComponentMax(maxPosition, Vector2.ComponentMax(h.EndPosition + delta, h.Position + delta)); - } - - if (minPosition.X < 0 || minPosition.Y < 0 || maxPosition.X > DrawWidth || maxPosition.Y > DrawHeight) + if (quad.TopLeft.X + delta.X < 0 || + quad.TopLeft.Y + delta.Y < 0 || + quad.BottomRight.X + delta.X > DrawWidth || + quad.BottomRight.Y + delta.Y > DrawHeight) return false; - foreach (var h in SelectedHitObjects.OfType()) - { - if (h is Spinner) - { - // Spinners don't support position adjustments - continue; - } - + foreach (var h in hitObjects) h.Position += delta; - } return true; } /// - /// Returns a gamefield-space quad surrounding the current selection. + /// Returns a gamefield-space quad surrounding the provided hit objects. /// - private Quad getSelectionQuad() + /// The hit objects to calculate a quad for. + private Quad getSurroundingQuad(OsuHitObject[] hitObjects) => + getSurroundingQuad(hitObjects.SelectMany(h => new[] { h.Position, h.EndPosition })); + + /// + /// Returns a gamefield-space quad surrounding the provided points. + /// + /// The points to calculate a quad for. + private Quad getSurroundingQuad(IEnumerable points) { if (!SelectedHitObjects.Any()) return new Quad(); @@ -171,17 +148,10 @@ namespace osu.Game.Rulesets.Osu.Edit 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()) + foreach (var p in points) { - if (h is Spinner) - { - // Spinners don't support position adjustments - continue; - } - - // Stacking is not considered - minPosition = Vector2.ComponentMin(minPosition, Vector2.ComponentMin(h.EndPosition, h.Position)); - maxPosition = Vector2.ComponentMax(maxPosition, Vector2.ComponentMax(h.EndPosition, h.Position)); + minPosition = Vector2.ComponentMin(minPosition, p); + maxPosition = Vector2.ComponentMax(maxPosition, p); } Vector2 size = maxPosition - minPosition; @@ -189,6 +159,14 @@ namespace osu.Game.Rulesets.Osu.Edit return new Quad(minPosition.X, minPosition.Y, size.X, size.Y); } + /// + /// All osu! hitobjects which can be moved/rotated/scaled. + /// + private OsuHitObject[] selectedMovableObjects => SelectedHitObjects + .OfType() + .Where(h => !(h is Spinner)) + .ToArray(); + /// /// Rotate a point around an arbitrary origin. /// From f1298bed798f8d31de5b17571f0c4f7bb4362f7e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 30 Sep 2020 15:08:56 +0900 Subject: [PATCH 1343/1782] Combine scale operations and tidy up scale drag handle construction --- .../Edit/OsuSelectionHandler.cs | 58 +++++------ .../Editing/TestSceneComposeSelectBox.cs | 31 +++--- .../Compose/Components/ComposeSelectionBox.cs | 99 +++++-------------- .../Compose/Components/SelectionHandler.cs | 19 +--- 4 files changed, 77 insertions(+), 130 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs index 706c41c2e3..1f250f078d 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs @@ -41,37 +41,16 @@ namespace osu.Game.Rulesets.Osu.Edit /// private Vector2? referenceOrigin; - public override bool HandleScaleY(in float scale, Anchor reference) => - scaleSelection(new Vector2(0, ((reference & Anchor.y0) > 0 ? -1 : 1) * scale), reference); - - public override bool HandleScaleX(in float scale, Anchor reference) => - scaleSelection(new Vector2(((reference & Anchor.x0) > 0 ? -1 : 1) * scale, 0), reference); - - public override bool HandleRotation(float delta) + public override bool HandleScale(Vector2 scale, Anchor reference) { - var hitObjects = selectedMovableObjects; + // cancel out scale in axes we don't care about (based on which drag handle was used). + if ((reference & Anchor.x1) > 0) scale.X = 0; + if ((reference & Anchor.y1) > 0) scale.Y = 0; - Quad quad = getSurroundingQuad(hitObjects); + // reverse the scale direction if dragging from top or left. + if ((reference & Anchor.x0) > 0) scale.X = -scale.X; + if ((reference & Anchor.y0) > 0) scale.Y = -scale.Y; - referenceOrigin ??= quad.Centre; - - foreach (var h in hitObjects) - { - h.Position = rotatePointAroundOrigin(h.Position, referenceOrigin.Value, delta); - - if (h is IHasPath path) - { - foreach (var point in path.Path.ControlPoints) - point.Position.Value = rotatePointAroundOrigin(point.Position.Value, Vector2.Zero, delta); - } - } - - // this isn't always the case but let's be lenient for now. - return true; - } - - private bool scaleSelection(Vector2 scale, Anchor reference) - { var hitObjects = selectedMovableObjects; // for the time being, allow resizing of slider paths only if the slider is @@ -110,6 +89,29 @@ namespace osu.Game.Rulesets.Osu.Edit return true; } + public override bool HandleRotation(float delta) + { + var hitObjects = selectedMovableObjects; + + Quad quad = getSurroundingQuad(hitObjects); + + referenceOrigin ??= quad.Centre; + + foreach (var h in hitObjects) + { + h.Position = rotatePointAroundOrigin(h.Position, referenceOrigin.Value, delta); + + if (h is IHasPath path) + { + foreach (var point in path.Path.ControlPoints) + point.Position.Value = rotatePointAroundOrigin(point.Position.Value, Vector2.Zero, delta); + } + } + + // this isn't always the case but let's be lenient for now. + return true; + } + private bool moveSelection(Vector2 delta) { var hitObjects = selectedMovableObjects; diff --git a/osu.Game.Tests/Visual/Editing/TestSceneComposeSelectBox.cs b/osu.Game.Tests/Visual/Editing/TestSceneComposeSelectBox.cs index 4b12000fc3..a1fb91024b 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneComposeSelectBox.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneComposeSelectBox.cs @@ -32,8 +32,7 @@ namespace osu.Game.Tests.Visual.Editing CanScaleY = true, OnRotation = handleRotation, - OnScaleX = handleScaleX, - OnScaleY = handleScaleY, + OnScale = handleScale } } }); @@ -43,24 +42,28 @@ namespace osu.Game.Tests.Visual.Editing AddToggleStep("toggle y", state => selectionBox.CanScaleY = state); } - private void handleScaleY(DragEvent e, Anchor reference) + private void handleScale(DragEvent e, Anchor reference) { - int direction = (reference & Anchor.y0) > 0 ? -1 : 1; - if (direction < 0) - selectionArea.Y += e.Delta.Y; - selectionArea.Height += direction * e.Delta.Y; - } + if ((reference & Anchor.y1) == 0) + { + int directionY = (reference & Anchor.y0) > 0 ? -1 : 1; + if (directionY < 0) + selectionArea.Y += e.Delta.Y; + selectionArea.Height += directionY * e.Delta.Y; + } - private void handleScaleX(DragEvent e, Anchor reference) - { - int direction = (reference & Anchor.x0) > 0 ? -1 : 1; - if (direction < 0) - selectionArea.X += e.Delta.X; - selectionArea.Width += direction * e.Delta.X; + if ((reference & Anchor.x1) == 0) + { + int directionX = (reference & Anchor.x0) > 0 ? -1 : 1; + if (directionX < 0) + selectionArea.X += e.Delta.X; + selectionArea.Width += directionX * e.Delta.X; + } } private void handleRotation(DragEvent e) { + // kinda silly and wrong, but just showing that the drag handles work. selectionArea.Rotation += e.Delta.X; } } diff --git a/osu.Game/Screens/Edit/Compose/Components/ComposeSelectionBox.cs b/osu.Game/Screens/Edit/Compose/Components/ComposeSelectionBox.cs index dba1965569..424705c755 100644 --- a/osu.Game/Screens/Edit/Compose/Components/ComposeSelectionBox.cs +++ b/osu.Game/Screens/Edit/Compose/Components/ComposeSelectionBox.cs @@ -17,8 +17,7 @@ namespace osu.Game.Screens.Edit.Compose.Components public class ComposeSelectionBox : CompositeDrawable { public Action OnRotation; - public Action OnScaleX; - public Action OnScaleY; + public Action OnScale; public Action OperationStarted; public Action OperationEnded; @@ -128,20 +127,8 @@ namespace osu.Game.Screens.Edit.Compose.Components { AddRangeInternal(new[] { - new DragHandle - { - Anchor = Anchor.TopCentre, - HandleDrag = e => OnScaleY?.Invoke(e, Anchor.TopCentre), - OperationStarted = operationStarted, - OperationEnded = operationEnded - }, - new DragHandle - { - Anchor = Anchor.BottomCentre, - HandleDrag = e => OnScaleY?.Invoke(e, Anchor.BottomCentre), - OperationStarted = operationStarted, - OperationEnded = operationEnded - }, + createDragHandle(Anchor.TopCentre), + createDragHandle(Anchor.BottomCentre), }); } @@ -149,20 +136,8 @@ namespace osu.Game.Screens.Edit.Compose.Components { AddRangeInternal(new[] { - new DragHandle - { - Anchor = Anchor.CentreLeft, - HandleDrag = e => OnScaleX?.Invoke(e, Anchor.CentreLeft), - OperationStarted = operationStarted, - OperationEnded = operationEnded - }, - new DragHandle - { - Anchor = Anchor.CentreRight, - HandleDrag = e => OnScaleX?.Invoke(e, Anchor.CentreRight), - OperationStarted = operationStarted, - OperationEnded = operationEnded - }, + createDragHandle(Anchor.CentreLeft), + createDragHandle(Anchor.CentreRight), }); } @@ -170,52 +145,20 @@ namespace osu.Game.Screens.Edit.Compose.Components { AddRangeInternal(new[] { - new DragHandle - { - Anchor = Anchor.TopLeft, - HandleDrag = e => - { - OnScaleX?.Invoke(e, Anchor.TopLeft); - OnScaleY?.Invoke(e, Anchor.TopLeft); - }, - OperationStarted = operationStarted, - OperationEnded = operationEnded - }, - new DragHandle - { - Anchor = Anchor.TopRight, - HandleDrag = e => - { - OnScaleX?.Invoke(e, Anchor.TopRight); - OnScaleY?.Invoke(e, Anchor.TopRight); - }, - OperationStarted = operationStarted, - OperationEnded = operationEnded - }, - new DragHandle - { - Anchor = Anchor.BottomLeft, - HandleDrag = e => - { - OnScaleX?.Invoke(e, Anchor.BottomLeft); - OnScaleY?.Invoke(e, Anchor.BottomLeft); - }, - OperationStarted = operationStarted, - OperationEnded = operationEnded - }, - new DragHandle - { - Anchor = Anchor.BottomRight, - HandleDrag = e => - { - OnScaleX?.Invoke(e, Anchor.BottomRight); - OnScaleY?.Invoke(e, Anchor.BottomRight); - }, - OperationStarted = operationStarted, - OperationEnded = operationEnded - }, + createDragHandle(Anchor.TopLeft), + createDragHandle(Anchor.TopRight), + createDragHandle(Anchor.BottomLeft), + createDragHandle(Anchor.BottomRight), }); } + + ScaleDragHandle createDragHandle(Anchor anchor) => + new ScaleDragHandle(anchor) + { + HandleDrag = e => OnScale?.Invoke(e, anchor), + OperationStarted = operationStarted, + OperationEnded = operationEnded + }; } private int activeOperations; @@ -232,6 +175,14 @@ namespace osu.Game.Screens.Edit.Compose.Components OperationStarted?.Invoke(); } + private class ScaleDragHandle : DragHandle + { + public ScaleDragHandle(Anchor anchor) + { + Anchor = anchor; + } + } + private class RotationDragHandle : DragHandle { private SpriteIcon icon; diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index 6cd503b580..5ed9bb65a8 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.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; @@ -101,8 +101,7 @@ namespace osu.Game.Screens.Edit.Compose.Components OperationEnded = OnDragOperationEnded, OnRotation = e => HandleRotation(e.Delta.X), - OnScaleX = (e, anchor) => HandleScaleX(e.Delta.X, anchor), - OnScaleY = (e, anchor) => HandleScaleY(e.Delta.Y, anchor), + OnScale = (e, anchor) => HandleScale(e.Delta, anchor), }; /// @@ -145,20 +144,12 @@ namespace osu.Game.Screens.Edit.Compose.Components public virtual bool HandleRotation(float angle) => false; /// - /// Handles the selected s being scaled in a vertical direction. + /// Handles the selected s being scaled. /// - /// The delta scale to apply. + /// The delta scale to apply, in playfield local coordinates. /// The point of reference where the scale is originating from. /// Whether any s could be moved. - public virtual bool HandleScaleY(in float scale, Anchor anchor) => false; - - /// - /// Handles the selected s being scaled in a horizontal direction. - /// - /// The delta scale to apply. - /// The point of reference where the scale is originating from. - /// Whether any s could be moved. - public virtual bool HandleScaleX(in float scale, Anchor anchor) => false; + public virtual bool HandleScale(Vector2 scale, Anchor anchor) => false; public bool OnPressed(PlatformAction action) { From 7fad9ce34ac7a94847143b3a5ffeeb62fba5c1b9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 30 Sep 2020 15:17:27 +0900 Subject: [PATCH 1344/1782] Simplify HandleScale method --- .../Edit/OsuSelectionHandler.cs | 38 +++++++++---------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs index 1f250f078d..6b4f13db35 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs @@ -43,13 +43,7 @@ namespace osu.Game.Rulesets.Osu.Edit public override bool HandleScale(Vector2 scale, Anchor reference) { - // cancel out scale in axes we don't care about (based on which drag handle was used). - if ((reference & Anchor.x1) > 0) scale.X = 0; - if ((reference & Anchor.y1) > 0) scale.Y = 0; - - // reverse the scale direction if dragging from top or left. - if ((reference & Anchor.x0) > 0) scale.X = -scale.X; - if ((reference & Anchor.y0) > 0) scale.Y = -scale.Y; + adjustScaleFromAnchor(ref scale, reference); var hitObjects = selectedMovableObjects; @@ -58,11 +52,11 @@ namespace osu.Game.Rulesets.Osu.Edit // is not looking to change the duration of the slider but expand the whole pattern. if (hitObjects.Length == 1 && hitObjects.First() is Slider slider) { - var quad = getSurroundingQuad(slider.Path.ControlPoints.Select(p => p.Position.Value)); - Vector2 delta = Vector2.One + new Vector2(scale.X / quad.Width, scale.Y / quad.Height); + Quad quad = getSurroundingQuad(slider.Path.ControlPoints.Select(p => p.Position.Value)); + Vector2 pathRelativeDeltaScale = new Vector2(1 + scale.X / quad.Width, 1 + scale.Y / quad.Height); foreach (var point in slider.Path.ControlPoints) - point.Position.Value *= delta; + point.Position.Value *= pathRelativeDeltaScale; } else { @@ -72,23 +66,29 @@ namespace osu.Game.Rulesets.Osu.Edit Quad quad = getSurroundingQuad(hitObjects); - Vector2 minPosition = quad.TopLeft; - - Vector2 size = quad.Size; - Vector2 newSize = size + scale; - foreach (var h in hitObjects) { - if (scale.X != 1) - h.Position = new Vector2(minPosition.X + (h.X - minPosition.X) / size.X * newSize.X, h.Y); - if (scale.Y != 1) - h.Position = new Vector2(h.X, minPosition.Y + (h.Y - minPosition.Y) / size.Y * newSize.Y); + h.Position = new Vector2( + quad.TopLeft.X + (h.X - quad.TopLeft.X) / quad.Width * (quad.Width + scale.X), + quad.TopLeft.Y + (h.Y - quad.TopLeft.Y) / quad.Height * (quad.Height + scale.Y) + ); } } return true; } + private static void adjustScaleFromAnchor(ref Vector2 scale, Anchor reference) + { + // cancel out scale in axes we don't care about (based on which drag handle was used). + if ((reference & Anchor.x1) > 0) scale.X = 0; + if ((reference & Anchor.y1) > 0) scale.Y = 0; + + // reverse the scale direction if dragging from top or left. + if ((reference & Anchor.x0) > 0) scale.X = -scale.X; + if ((reference & Anchor.y0) > 0) scale.Y = -scale.Y; + } + public override bool HandleRotation(float delta) { var hitObjects = selectedMovableObjects; From ae9e884a483fd5940a429a4b924c5d1cb059653e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 30 Sep 2020 15:35:25 +0900 Subject: [PATCH 1345/1782] Fix header casing --- osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index 5ed9bb65a8..ee094c6246 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.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; From 414c40d29849932734343123a8b89bf0725381c0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 30 Sep 2020 15:45:14 +0900 Subject: [PATCH 1346/1782] Reverse inheritance order of SkinnableSound's pause logic --- .../Objects/Drawables/DrawableSlider.cs | 4 +- .../Objects/Drawables/DrawableSpinner.cs | 4 +- .../Audio/DrumSampleContainer.cs | 8 +-- .../Gameplay/TestSceneSkinnableSound.cs | 4 +- osu.Game/Rulesets/Mods/ModNightcore.cs | 16 ++--- .../Objects/Drawables/DrawableHitObject.cs | 4 +- .../Screens/Play/ISamplePlaybackDisabler.cs | 2 +- osu.Game/Screens/Play/PauseOverlay.cs | 12 +--- osu.Game/Skinning/PausableSkinnableSound.cs | 66 +++++++++++++++++++ osu.Game/Skinning/SkinnableSound.cs | 50 +------------- 10 files changed, 91 insertions(+), 79 deletions(-) create mode 100644 osu.Game/Skinning/PausableSkinnableSound.cs diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index 68f203db47..b73ae5eeb9 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -87,7 +87,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables Tracking.BindValueChanged(updateSlidingSample); } - private SkinnableSound slidingSample; + private PausableSkinnableSound slidingSample; protected override void LoadSamples() { @@ -103,7 +103,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables var clone = HitObject.SampleControlPoint.ApplyTo(firstSample); clone.Name = "sliderslide"; - AddInternal(slidingSample = new SkinnableSound(clone) + AddInternal(slidingSample = new PausableSkinnableSound(clone) { Looping = true }); diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index b2a706833c..6488c60acf 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -84,7 +84,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables isSpinning.BindValueChanged(updateSpinningSample); } - private SkinnableSound spinningSample; + private PausableSkinnableSound spinningSample; private const float spinning_sample_initial_frequency = 1.0f; private const float spinning_sample_modulated_base_frequency = 0.5f; @@ -102,7 +102,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables var clone = HitObject.SampleControlPoint.ApplyTo(firstSample); clone.Name = "spinnerspin"; - AddInternal(spinningSample = new SkinnableSound(clone) + AddInternal(spinningSample = new PausableSkinnableSound(clone) { Volume = { Value = 0 }, Looping = true, diff --git a/osu.Game.Rulesets.Taiko/Audio/DrumSampleContainer.cs b/osu.Game.Rulesets.Taiko/Audio/DrumSampleContainer.cs index 7c39c040b1..fcf7c529f5 100644 --- a/osu.Game.Rulesets.Taiko/Audio/DrumSampleContainer.cs +++ b/osu.Game.Rulesets.Taiko/Audio/DrumSampleContainer.cs @@ -42,9 +42,9 @@ namespace osu.Game.Rulesets.Taiko.Audio } } - private SkinnableSound addSound(HitSampleInfo hitSampleInfo, double lifetimeStart, double lifetimeEnd) + private PausableSkinnableSound addSound(HitSampleInfo hitSampleInfo, double lifetimeStart, double lifetimeEnd) { - var drawable = new SkinnableSound(hitSampleInfo) + var drawable = new PausableSkinnableSound(hitSampleInfo) { LifetimeStart = lifetimeStart, LifetimeEnd = lifetimeEnd @@ -57,8 +57,8 @@ namespace osu.Game.Rulesets.Taiko.Audio public class DrumSample { - public SkinnableSound Centre; - public SkinnableSound Rim; + public PausableSkinnableSound Centre; + public PausableSkinnableSound Rim; } } } diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs index 8b37cbd06f..8f2011e5dd 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs @@ -26,7 +26,7 @@ namespace osu.Game.Tests.Visual.Gameplay private GameplayClock gameplayClock = new GameplayClock(new FramedClock()); private TestSkinSourceContainer skinSource; - private SkinnableSound skinnableSound; + private PausableSkinnableSound skinnableSound; [SetUp] public void SetUp() => Schedule(() => @@ -39,7 +39,7 @@ namespace osu.Game.Tests.Visual.Gameplay { Clock = gameplayClock, RelativeSizeAxes = Axes.Both, - Child = skinnableSound = new SkinnableSound(new SampleInfo("normal-sliderslide")) + Child = skinnableSound = new PausableSkinnableSound(new SampleInfo("normal-sliderslide")) }, }; }); diff --git a/osu.Game/Rulesets/Mods/ModNightcore.cs b/osu.Game/Rulesets/Mods/ModNightcore.cs index 4004953cd1..282de3a8e1 100644 --- a/osu.Game/Rulesets/Mods/ModNightcore.cs +++ b/osu.Game/Rulesets/Mods/ModNightcore.cs @@ -52,10 +52,10 @@ namespace osu.Game.Rulesets.Mods public class NightcoreBeatContainer : BeatSyncedContainer { - private SkinnableSound hatSample; - private SkinnableSound clapSample; - private SkinnableSound kickSample; - private SkinnableSound finishSample; + private PausableSkinnableSound hatSample; + private PausableSkinnableSound clapSample; + private PausableSkinnableSound kickSample; + private PausableSkinnableSound finishSample; private int? firstBeat; @@ -69,10 +69,10 @@ namespace osu.Game.Rulesets.Mods { InternalChildren = new Drawable[] { - hatSample = new SkinnableSound(new SampleInfo("nightcore-hat")), - clapSample = new SkinnableSound(new SampleInfo("nightcore-clap")), - kickSample = new SkinnableSound(new SampleInfo("nightcore-kick")), - finishSample = new SkinnableSound(new SampleInfo("nightcore-finish")), + hatSample = new PausableSkinnableSound(new SampleInfo("nightcore-hat")), + clapSample = new PausableSkinnableSound(new SampleInfo("nightcore-clap")), + kickSample = new PausableSkinnableSound(new SampleInfo("nightcore-kick")), + finishSample = new PausableSkinnableSound(new SampleInfo("nightcore-finish")), }; } diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 796b8f7aae..59a3381b8b 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 /// public readonly Bindable AccentColour = new Bindable(Color4.Gray); - protected SkinnableSound Samples { get; private set; } + protected PausableSkinnableSound Samples { get; private set; } public virtual IEnumerable GetSamples() => HitObject.Samples; @@ -179,7 +179,7 @@ namespace osu.Game.Rulesets.Objects.Drawables + $" This is an indication that {nameof(HitObject.ApplyDefaults)} has not been invoked on {this}."); } - Samples = new SkinnableSound(samples.Select(s => HitObject.SampleControlPoint.ApplyTo(s))); + Samples = new PausableSkinnableSound(samples.Select(s => HitObject.SampleControlPoint.ApplyTo(s))); AddInternal(Samples); } diff --git a/osu.Game/Screens/Play/ISamplePlaybackDisabler.cs b/osu.Game/Screens/Play/ISamplePlaybackDisabler.cs index 83e89d654b..6b37021fe6 100644 --- a/osu.Game/Screens/Play/ISamplePlaybackDisabler.cs +++ b/osu.Game/Screens/Play/ISamplePlaybackDisabler.cs @@ -8,7 +8,7 @@ namespace osu.Game.Screens.Play { /// /// Allows a component to disable sample playback dynamically as required. - /// Handled by . + /// Handled by . /// public interface ISamplePlaybackDisabler { diff --git a/osu.Game/Screens/Play/PauseOverlay.cs b/osu.Game/Screens/Play/PauseOverlay.cs index 9494971f8a..65f34aba3e 100644 --- a/osu.Game/Screens/Play/PauseOverlay.cs +++ b/osu.Game/Screens/Play/PauseOverlay.cs @@ -33,7 +33,7 @@ namespace osu.Game.Screens.Play AddButton("Retry", colours.YellowDark, () => OnRetry?.Invoke()); AddButton("Quit", new Color4(170, 27, 39, 255), () => OnQuit?.Invoke()); - AddInternal(pauseLoop = new UnpausableSkinnableSound(new SampleInfo("pause-loop")) + AddInternal(pauseLoop = new SkinnableSound(new SampleInfo("pause-loop")) { Looping = true, Volume = { Value = 0 } @@ -54,15 +54,5 @@ namespace osu.Game.Screens.Play pauseLoop.VolumeTo(0, TRANSITION_DURATION, Easing.OutQuad).Finally(_ => pauseLoop.Stop()); } - - private class UnpausableSkinnableSound : SkinnableSound - { - protected override bool PlayWhenPaused => true; - - public UnpausableSkinnableSound(SampleInfo sampleInfo) - : base(sampleInfo) - { - } - } } } diff --git a/osu.Game/Skinning/PausableSkinnableSound.cs b/osu.Game/Skinning/PausableSkinnableSound.cs new file mode 100644 index 0000000000..991278fb98 --- /dev/null +++ b/osu.Game/Skinning/PausableSkinnableSound.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.Collections.Generic; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Game.Audio; +using osu.Game.Screens.Play; + +namespace osu.Game.Skinning +{ + public class PausableSkinnableSound : SkinnableSound + { + protected bool RequestedPlaying { get; private set; } + + public PausableSkinnableSound(ISampleInfo hitSamples) + : base(hitSamples) + { + } + + public PausableSkinnableSound(IEnumerable hitSamples) + : base(hitSamples) + { + } + + private readonly IBindable samplePlaybackDisabled = new Bindable(); + + [BackgroundDependencyLoader(true)] + private void load(ISamplePlaybackDisabler samplePlaybackDisabler) + { + // if in a gameplay context, pause sample playback when gameplay is paused. + if (samplePlaybackDisabler != null) + { + samplePlaybackDisabled.BindTo(samplePlaybackDisabler.SamplePlaybackDisabled); + samplePlaybackDisabled.BindValueChanged(disabled => + { + if (RequestedPlaying) + { + if (disabled.NewValue) + base.Stop(); + // it's not easy to know if a sample has finished playing (to end). + // to keep things simple only resume playing looping samples. + else if (Looping) + base.Play(); + } + }); + } + } + + public override void Play() + { + RequestedPlaying = true; + + if (samplePlaybackDisabled.Value) + return; + + base.Play(); + } + + public override void Stop() + { + RequestedPlaying = false; + base.Stop(); + } + } +} diff --git a/osu.Game/Skinning/SkinnableSound.cs b/osu.Game/Skinning/SkinnableSound.cs index f3ab8b86bc..91bdcd7444 100644 --- a/osu.Game/Skinning/SkinnableSound.cs +++ b/osu.Game/Skinning/SkinnableSound.cs @@ -11,7 +11,6 @@ using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics.Audio; using osu.Framework.Graphics.Containers; using osu.Game.Audio; -using osu.Game.Screens.Play; namespace osu.Game.Skinning { @@ -22,8 +21,6 @@ namespace osu.Game.Skinning [Resolved] private ISampleStore samples { get; set; } - private bool requestedPlaying; - public override bool RemoveWhenNotAlive => false; public override bool RemoveCompletedTransforms => false; @@ -37,8 +34,6 @@ namespace osu.Game.Skinning /// protected bool PlayWhenZeroVolume => Looping; - protected virtual bool PlayWhenPaused => false; - private readonly AudioContainer samplesContainer; public SkinnableSound(ISampleInfo hitSamples) @@ -52,30 +47,6 @@ namespace osu.Game.Skinning InternalChild = samplesContainer = new AudioContainer(); } - private readonly IBindable samplePlaybackDisabled = new Bindable(); - - [BackgroundDependencyLoader(true)] - private void load(ISamplePlaybackDisabler samplePlaybackDisabler) - { - // if in a gameplay context, pause sample playback when gameplay is paused. - if (samplePlaybackDisabler != null) - { - samplePlaybackDisabled.BindTo(samplePlaybackDisabler.SamplePlaybackDisabled); - samplePlaybackDisabled.BindValueChanged(disabled => - { - if (requestedPlaying) - { - if (disabled.NewValue && !PlayWhenPaused) - stop(); - // it's not easy to know if a sample has finished playing (to end). - // to keep things simple only resume playing looping samples. - else if (Looping) - play(); - } - }); - } - } - private bool looping; public bool Looping @@ -91,17 +62,8 @@ namespace osu.Game.Skinning } } - public void Play() + public virtual void Play() { - requestedPlaying = true; - play(); - } - - private void play() - { - if (samplePlaybackDisabled.Value && !PlayWhenPaused) - return; - samplesContainer.ForEach(c => { if (PlayWhenZeroVolume || c.AggregateVolume.Value > 0) @@ -109,13 +71,7 @@ namespace osu.Game.Skinning }); } - public void Stop() - { - requestedPlaying = false; - stop(); - } - - private void stop() + public virtual void Stop() { samplesContainer.ForEach(c => c.Stop()); } @@ -150,7 +106,7 @@ namespace osu.Game.Skinning // Start playback internally for the new samples if the previous ones were playing beforehand. if (wasPlaying) - play(); + Play(); } #region Re-expose AudioContainer From 6cceb42ad5f21861b839fca68f358183ad1b3da6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 30 Sep 2020 15:50:53 +0900 Subject: [PATCH 1347/1782] Remove unused DI resolution --- osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 59a3381b8b..eb12c1cdfc 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -17,7 +17,6 @@ using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Scoring; using osu.Game.Skinning; using osu.Game.Configuration; -using osu.Game.Screens.Play; using osuTK.Graphics; namespace osu.Game.Rulesets.Objects.Drawables @@ -359,9 +358,6 @@ namespace osu.Game.Rulesets.Objects.Drawables { } - [Resolved(canBeNull: true)] - private ISamplePlaybackDisabler samplePlaybackDisabler { get; set; } - /// /// Calculate the position to be used for sample playback at a specified X position (0..1). /// From a40c2ea5ee54863c0991c5be9fdcfc5fa76cbc03 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 30 Sep 2020 16:02:22 +0900 Subject: [PATCH 1348/1782] Simplify control point group binding/update logic --- osu.Game/Screens/Edit/Timing/TimingScreen.cs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/TimingScreen.cs b/osu.Game/Screens/Edit/Timing/TimingScreen.cs index d7da29218f..1d550b7cc3 100644 --- a/osu.Game/Screens/Edit/Timing/TimingScreen.cs +++ b/osu.Game/Screens/Edit/Timing/TimingScreen.cs @@ -63,7 +63,7 @@ namespace osu.Game.Screens.Edit.Timing private OsuButton deleteButton; private ControlPointTable table; - private IBindableList controlGroups; + private BindableList controlGroups; [Resolved] private EditorClock clock { get; set; } @@ -128,12 +128,14 @@ namespace osu.Game.Screens.Edit.Timing selectedGroup.BindValueChanged(selected => { deleteButton.Enabled.Value = selected.NewValue != null; }, true); - controlGroups = Beatmap.Value.Beatmap.ControlPointInfo.Groups.GetBoundCopy(); - controlGroups.CollectionChanged += (sender, args) => createContent(); - createContent(); - } + // todo: remove cast after https://github.com/ppy/osu-framework/pull/3906 is merged + controlGroups = (BindableList)Beatmap.Value.Beatmap.ControlPointInfo.Groups.GetBoundCopy(); - private void createContent() => table.ControlGroups = controlGroups; + controlGroups.BindCollectionChanged((sender, args) => + { + table.ControlGroups = controlGroups; + }, true); + } private void delete() { From 2ef09a8730085609f3ecb5eeba827e5a15554f60 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 30 Sep 2020 16:06:58 +0900 Subject: [PATCH 1349/1782] Populate test scene with control points --- osu.Game.Tests/Visual/Editing/TestSceneTimingScreen.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneTimingScreen.cs b/osu.Game.Tests/Visual/Editing/TestSceneTimingScreen.cs index 09f5ac2224..cda4ade6e1 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneTimingScreen.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneTimingScreen.cs @@ -4,6 +4,7 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Beatmaps; using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Timing; @@ -19,7 +20,7 @@ namespace osu.Game.Tests.Visual.Editing public TestSceneTimingScreen() { - editorBeatmap = new EditorBeatmap(new OsuBeatmap()); + editorBeatmap = new EditorBeatmap(CreateBeatmap(new OsuRuleset().RulesetInfo)); } [BackgroundDependencyLoader] From 1dd354120b1013759d210e49902e08393c6d18b9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 30 Sep 2020 16:16:14 +0900 Subject: [PATCH 1350/1782] Fix beatmap potentially changing in test scene --- osu.Game.Tests/Visual/Editing/TestSceneTimingScreen.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneTimingScreen.cs b/osu.Game.Tests/Visual/Editing/TestSceneTimingScreen.cs index cda4ade6e1..04cdfd7a67 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneTimingScreen.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneTimingScreen.cs @@ -5,7 +5,6 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Osu; -using osu.Game.Rulesets.Osu.Beatmaps; using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Timing; @@ -27,7 +26,15 @@ namespace osu.Game.Tests.Visual.Editing private void load() { Beatmap.Value = CreateWorkingBeatmap(editorBeatmap.PlayableBeatmap); + Beatmap.Disabled = true; + Child = new TimingScreen(); } + + protected override void Dispose(bool isDisposing) + { + Beatmap.Disabled = false; + base.Dispose(isDisposing); + } } } From e760ed8e01921c42c6adc6e8039b475f0b5758b0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 30 Sep 2020 16:39:02 +0900 Subject: [PATCH 1351/1782] Fix scroll wheel being handled by base test scene --- osu.Game.Tests/Visual/Editing/TestSceneTimingScreen.cs | 2 ++ osu.Game/Tests/Visual/EditorClockTestScene.cs | 5 +++++ 2 files changed, 7 insertions(+) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneTimingScreen.cs b/osu.Game.Tests/Visual/Editing/TestSceneTimingScreen.cs index 04cdfd7a67..b82e776164 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneTimingScreen.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneTimingScreen.cs @@ -17,6 +17,8 @@ namespace osu.Game.Tests.Visual.Editing [Cached(typeof(IBeatSnapProvider))] private readonly EditorBeatmap editorBeatmap; + protected override bool ScrollUsingMouseWheel => false; + public TestSceneTimingScreen() { editorBeatmap = new EditorBeatmap(CreateBeatmap(new OsuRuleset().RulesetInfo)); diff --git a/osu.Game/Tests/Visual/EditorClockTestScene.cs b/osu.Game/Tests/Visual/EditorClockTestScene.cs index f0ec638fc9..693c9cb792 100644 --- a/osu.Game/Tests/Visual/EditorClockTestScene.cs +++ b/osu.Game/Tests/Visual/EditorClockTestScene.cs @@ -20,6 +20,8 @@ namespace osu.Game.Tests.Visual protected readonly BindableBeatDivisor BeatDivisor = new BindableBeatDivisor(); protected new readonly EditorClock Clock; + protected virtual bool ScrollUsingMouseWheel => true; + protected EditorClockTestScene() { Clock = new EditorClock(new ControlPointInfo(), 5000, BeatDivisor) { IsCoupled = false }; @@ -57,6 +59,9 @@ namespace osu.Game.Tests.Visual protected override bool OnScroll(ScrollEvent e) { + if (!ScrollUsingMouseWheel) + return false; + if (e.ScrollDelta.Y > 0) Clock.SeekBackward(true); else From 5b200a8ca41f44084a3c0cc3703b50042a76a3bc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 30 Sep 2020 16:39:27 +0900 Subject: [PATCH 1352/1782] Change default zoom of timing screen timeline to most zoomed out --- .../Edit/Compose/Components/Timeline/TimelineArea.cs | 10 +++++----- osu.Game/Screens/Edit/EditorScreenWithTimeline.cs | 10 +++++++++- osu.Game/Screens/Edit/Timing/TimingScreen.cs | 7 +++++++ 3 files changed, 21 insertions(+), 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 b99a053859..d870eb5279 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs @@ -14,9 +14,9 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { public class TimelineArea : Container { - private readonly Timeline timeline = new Timeline { RelativeSizeAxes = Axes.Both }; + public readonly Timeline Timeline = new Timeline { RelativeSizeAxes = Axes.Both }; - protected override Container Content => timeline; + protected override Container Content => Timeline; [BackgroundDependencyLoader] private void load() @@ -107,7 +107,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline } } }, - timeline + Timeline }, }, ColumnDimensions = new[] @@ -121,9 +121,9 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline waveformCheckbox.Current.Value = true; - timeline.WaveformVisible.BindTo(waveformCheckbox.Current); + Timeline.WaveformVisible.BindTo(waveformCheckbox.Current); } - private void changeZoom(float change) => timeline.Zoom += change; + private void changeZoom(float change) => Timeline.Zoom += change; } } diff --git a/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs b/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs index 34eddbefad..d6d782e70c 100644 --- a/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs +++ b/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs @@ -115,10 +115,18 @@ namespace osu.Game.Screens.Edit new TimelineTickDisplay(), CreateTimelineContent(), } - }, timelineContainer.Add); + }, t => + { + timelineContainer.Add(t); + OnTimelineLoaded(t); + }); }); } + protected virtual void OnTimelineLoaded(TimelineArea timelineArea) + { + } + protected abstract Drawable CreateMainContent(); protected virtual Drawable CreateTimelineContent() => new Container(); diff --git a/osu.Game/Screens/Edit/Timing/TimingScreen.cs b/osu.Game/Screens/Edit/Timing/TimingScreen.cs index 1d550b7cc3..66c1f4895b 100644 --- a/osu.Game/Screens/Edit/Timing/TimingScreen.cs +++ b/osu.Game/Screens/Edit/Timing/TimingScreen.cs @@ -12,6 +12,7 @@ using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; +using osu.Game.Screens.Edit.Compose.Components.Timeline; using osuTK; namespace osu.Game.Screens.Edit.Timing @@ -58,6 +59,12 @@ namespace osu.Game.Screens.Edit.Timing }); } + protected override void OnTimelineLoaded(TimelineArea timelineArea) + { + base.OnTimelineLoaded(timelineArea); + timelineArea.Timeline.Zoom = timelineArea.Timeline.MinZoom; + } + public class ControlPointList : CompositeDrawable { private OsuButton deleteButton; From 698042268ff4463a4b18544b68e3dea806c6524a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 30 Sep 2020 16:58:19 +0900 Subject: [PATCH 1353/1782] Show control points in timing screen timeline --- osu.Game/Screens/Edit/Timing/TimingScreen.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game/Screens/Edit/Timing/TimingScreen.cs b/osu.Game/Screens/Edit/Timing/TimingScreen.cs index 66c1f4895b..4ab27ff1c0 100644 --- a/osu.Game/Screens/Edit/Timing/TimingScreen.cs +++ b/osu.Game/Screens/Edit/Timing/TimingScreen.cs @@ -12,6 +12,7 @@ using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; +using osu.Game.Screens.Edit.Components.Timelines.Summary.Parts; using osu.Game.Screens.Edit.Compose.Components.Timeline; using osuTK; @@ -30,6 +31,11 @@ namespace osu.Game.Screens.Edit.Timing { } + protected override Drawable CreateTimelineContent() => new ControlPointPart + { + RelativeSizeAxes = Axes.Both, + }; + protected override Drawable CreateMainContent() => new GridContainer { RelativeSizeAxes = Axes.Both, From 3422db1bb291f7daf0b42887edb24a54ac3637b5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 30 Sep 2020 17:10:22 +0900 Subject: [PATCH 1354/1782] Use top-left colour for deciding the text colour (gradient was added in some cases) --- .../Compose/Components/Timeline/TimelineHitObjectBlueprint.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs index 455f1e17bd..bc2ccfc605 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs @@ -185,7 +185,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline else mainComponents.Colour = drawableHitObject.AccentColour.Value; - var col = mainComponents.Colour.AverageColour.Linear; + var col = mainComponents.Colour.TopLeft.Linear; float brightness = col.R + col.G + col.B; // decide the combo index colour based on brightness? From b0f8e11bd46b54ab14ee678a10b576daa7ed4278 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 30 Sep 2020 17:34:14 +0900 Subject: [PATCH 1355/1782] Fix incorrect caching --- 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 e404d7ca42..9f8e55f577 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -53,7 +53,7 @@ namespace osu.Game.Screens.Play /// public GameplayClock GameplayClock => localGameplayClock; - [Cached] + [Cached(typeof(GameplayClock))] [Cached(typeof(ISamplePlaybackDisabler))] private readonly LocalGameplayClock localGameplayClock; From bc943dee53e7208e57aa1dc77f38bfcac3e818a5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 30 Sep 2020 17:52:12 +0900 Subject: [PATCH 1356/1782] Add textbox entry for speed multiplier and volume --- .../Screens/Edit/Timing/DifficultySection.cs | 12 +-- osu.Game/Screens/Edit/Timing/SampleSection.cs | 12 ++- .../Edit/Timing/SliderWithTextBoxInput.cs | 74 +++++++++++++++++++ 3 files changed, 83 insertions(+), 15 deletions(-) create mode 100644 osu.Game/Screens/Edit/Timing/SliderWithTextBoxInput.cs diff --git a/osu.Game/Screens/Edit/Timing/DifficultySection.cs b/osu.Game/Screens/Edit/Timing/DifficultySection.cs index 58a7f97e5f..78766d9777 100644 --- a/osu.Game/Screens/Edit/Timing/DifficultySection.cs +++ b/osu.Game/Screens/Edit/Timing/DifficultySection.cs @@ -2,27 +2,23 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; -using osu.Framework.Graphics; using osu.Framework.Bindables; using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Overlays.Settings; namespace osu.Game.Screens.Edit.Timing { internal class DifficultySection : Section { - private SettingsSlider multiplier; + private SliderWithTextBoxInput multiplierSlider; [BackgroundDependencyLoader] private void load() { Flow.AddRange(new[] { - multiplier = new SettingsSlider + multiplierSlider = new SliderWithTextBoxInput("Speed Multiplier") { - LabelText = "Speed Multiplier", - Bindable = new DifficultyControlPoint().SpeedMultiplierBindable, - RelativeSizeAxes = Axes.X, + Current = new DifficultyControlPoint().SpeedMultiplierBindable } }); } @@ -31,7 +27,7 @@ namespace osu.Game.Screens.Edit.Timing { if (point.NewValue != null) { - multiplier.Bindable = point.NewValue.SpeedMultiplierBindable; + multiplierSlider.Current = point.NewValue.SpeedMultiplierBindable; } } diff --git a/osu.Game/Screens/Edit/Timing/SampleSection.cs b/osu.Game/Screens/Edit/Timing/SampleSection.cs index 4665c77991..de986e28ca 100644 --- a/osu.Game/Screens/Edit/Timing/SampleSection.cs +++ b/osu.Game/Screens/Edit/Timing/SampleSection.cs @@ -2,18 +2,17 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; -using osu.Framework.Graphics; using osu.Framework.Bindables; +using osu.Framework.Graphics; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics.UserInterfaceV2; -using osu.Game.Overlays.Settings; namespace osu.Game.Screens.Edit.Timing { internal class SampleSection : Section { private LabelledTextBox bank; - private SettingsSlider volume; + private SliderWithTextBoxInput volume; [BackgroundDependencyLoader] private void load() @@ -24,10 +23,9 @@ namespace osu.Game.Screens.Edit.Timing { Label = "Bank Name", }, - volume = new SettingsSlider + volume = new SliderWithTextBoxInput("Volume") { - Bindable = new SampleControlPoint().SampleVolumeBindable, - LabelText = "Volume", + Current = new SampleControlPoint().SampleVolumeBindable, } }); } @@ -37,7 +35,7 @@ namespace osu.Game.Screens.Edit.Timing if (point.NewValue != null) { bank.Current = point.NewValue.SampleBankBindable; - volume.Bindable = point.NewValue.SampleVolumeBindable; + volume.Current = point.NewValue.SampleVolumeBindable; } } diff --git a/osu.Game/Screens/Edit/Timing/SliderWithTextBoxInput.cs b/osu.Game/Screens/Edit/Timing/SliderWithTextBoxInput.cs new file mode 100644 index 0000000000..07b914c506 --- /dev/null +++ b/osu.Game/Screens/Edit/Timing/SliderWithTextBoxInput.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; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.UserInterface; +using osu.Game.Graphics.UserInterfaceV2; +using osu.Game.Overlays.Settings; + +namespace osu.Game.Screens.Edit.Timing +{ + internal class SliderWithTextBoxInput : CompositeDrawable, IHasCurrentValue + where T : struct, IEquatable, IComparable, IConvertible + { + private readonly SettingsSlider slider; + + public SliderWithTextBoxInput(string labelText) + { + LabelledTextBox textbox; + + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + + InternalChildren = new Drawable[] + { + new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + textbox = new LabelledTextBox + { + Label = labelText, + }, + slider = new SettingsSlider + { + RelativeSizeAxes = Axes.X, + } + } + }, + }; + + textbox.OnCommit += (t, isNew) => + { + if (!isNew) return; + + try + { + slider.Bindable.Parse(t.Text); + } + catch + { + // will restore the previous text value on failure. + Current.TriggerChange(); + } + }; + + Current.BindValueChanged(val => + { + textbox.Text = val.NewValue.ToString(); + }, true); + } + + public Bindable Current + { + get => slider.Bindable; + set => slider.Bindable = value; + } + } +} From 44fc0c672304486c7706334ab990802f03aaa793 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 30 Sep 2020 18:08:55 +0900 Subject: [PATCH 1357/1782] Fix default value of bpm being too high --- osu.Game/Screens/Edit/Timing/TimingSection.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/TimingSection.cs b/osu.Game/Screens/Edit/Timing/TimingSection.cs index 879363ba08..cc79dd2acc 100644 --- a/osu.Game/Screens/Edit/Timing/TimingSection.cs +++ b/osu.Game/Screens/Edit/Timing/TimingSection.cs @@ -103,12 +103,17 @@ namespace osu.Game.Screens.Edit.Timing private const double sane_maximum = 240; private readonly BindableNumber beatLengthBindable = new TimingControlPoint().BeatLengthBindable; - private readonly BindableDouble bpmBindable = new BindableDouble(); + + private readonly BindableDouble bpmBindable = new BindableDouble(60000 / TimingControlPoint.DEFAULT_BEAT_LENGTH) + { + MinValue = sane_minimum, + MaxValue = sane_maximum, + }; public BPMSlider() { beatLengthBindable.BindValueChanged(beatLength => updateCurrent(beatLengthToBpm(beatLength.NewValue)), true); - bpmBindable.BindValueChanged(bpm => bpmBindable.Default = beatLengthBindable.Value = beatLengthToBpm(bpm.NewValue)); + bpmBindable.BindValueChanged(bpm => beatLengthBindable.Value = beatLengthToBpm(bpm.NewValue)); base.Bindable = bpmBindable; } From 5242f5648dd26d89829d130f78459589f220434b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 30 Sep 2020 18:34:13 +0900 Subject: [PATCH 1358/1782] Fix timeline control point display not updating with changes --- .../Summary/Parts/ControlPointPart.cs | 70 +++++++------------ .../Summary/Parts/GroupVisualisation.cs | 46 ++++++++++++ 2 files changed, 71 insertions(+), 45 deletions(-) create mode 100644 osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/GroupVisualisation.cs diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointPart.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointPart.cs index 102955657e..6edb49ba1b 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointPart.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointPart.cs @@ -1,70 +1,50 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Collections.Specialized; using System.Linq; -using osu.Framework.Allocation; -using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Framework.Bindables; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Graphics; -using osu.Game.Screens.Edit.Components.Timelines.Summary.Visualisations; namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts { /// /// The part of the timeline that displays the control points. /// - public class ControlPointPart : TimelinePart + public class ControlPointPart : TimelinePart { + private BindableList controlPointGroups; + protected override void LoadBeatmap(WorkingBeatmap beatmap) { base.LoadBeatmap(beatmap); - ControlPointInfo cpi = beatmap.Beatmap.ControlPointInfo; - - cpi.TimingPoints.ForEach(addTimingPoint); - - // Consider all non-timing points as the same type - cpi.SamplePoints.Select(c => (ControlPoint)c) - .Concat(cpi.EffectPoints) - .Concat(cpi.DifficultyPoints) - .Distinct() - // Non-timing points should not be added where there are timing points - .Where(c => cpi.TimingPointAt(c.Time).Time != c.Time) - .ForEach(addNonTimingPoint); - } - - private void addTimingPoint(ControlPoint controlPoint) => Add(new TimingPointVisualisation(controlPoint)); - private void addNonTimingPoint(ControlPoint controlPoint) => Add(new NonTimingPointVisualisation(controlPoint)); - - private class TimingPointVisualisation : ControlPointVisualisation - { - public TimingPointVisualisation(ControlPoint controlPoint) - : base(controlPoint) + controlPointGroups = (BindableList)beatmap.Beatmap.ControlPointInfo.Groups.GetBoundCopy(); + controlPointGroups.BindCollectionChanged((sender, args) => { - } + switch (args.Action) + { + case NotifyCollectionChangedAction.Reset: + Clear(); + break; - [BackgroundDependencyLoader] - private void load(OsuColour colours) => Colour = colours.YellowDark; - } + case NotifyCollectionChangedAction.Add: + foreach (var group in args.NewItems.OfType()) + Add(new GroupVisualisation(group)); + break; - private class NonTimingPointVisualisation : ControlPointVisualisation - { - public NonTimingPointVisualisation(ControlPoint controlPoint) - : base(controlPoint) - { - } + case NotifyCollectionChangedAction.Remove: + foreach (var group in args.OldItems.OfType()) + { + var matching = Children.SingleOrDefault(gv => gv.Group == group); - [BackgroundDependencyLoader] - private void load(OsuColour colours) => Colour = colours.Green; - } + matching?.Expire(); + } - private abstract class ControlPointVisualisation : PointVisualisation - { - protected ControlPointVisualisation(ControlPoint controlPoint) - : base(controlPoint.Time) - { - } + break; + } + }, true); } } } diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/GroupVisualisation.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/GroupVisualisation.cs new file mode 100644 index 0000000000..b9eb53b697 --- /dev/null +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/GroupVisualisation.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 System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Graphics; +using osu.Game.Screens.Edit.Components.Timelines.Summary.Visualisations; +using osuTK.Graphics; + +namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts +{ + public class GroupVisualisation : PointVisualisation + { + public readonly ControlPointGroup Group; + + private BindableList controlPoints; + + [Resolved] + private OsuColour colours { get; set; } + + public GroupVisualisation(ControlPointGroup group) + : base(group.Time) + { + Group = group; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + controlPoints = (BindableList)Group.ControlPoints.GetBoundCopy(); + controlPoints.BindCollectionChanged((_, __) => + { + if (controlPoints.Count == 0) + { + Colour = Color4.Transparent; + return; + } + + Colour = controlPoints.Any(c => c is TimingControlPoint) ? colours.YellowDark : colours.Green; + }, true); + } + } +} From fab11a8241e470cd428d33a9084e0c927242c542 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 30 Sep 2020 18:36:40 +0900 Subject: [PATCH 1359/1782] 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 dc3e14c141..afc5d4ec52 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,6 +52,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 6412f707d0..5fa1685d9b 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -24,7 +24,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index f1e13169a5..60708a39e2 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -80,7 +80,7 @@ - + From a11c74d60044d0ba9ee39e7a1e8e91674577b3cc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 30 Sep 2020 20:27:02 +0900 Subject: [PATCH 1360/1782] Update to consume framework fixes --- osu.Game/Screens/Edit/Timing/TimingScreen.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/TimingScreen.cs b/osu.Game/Screens/Edit/Timing/TimingScreen.cs index 4ab27ff1c0..0a0cfe193d 100644 --- a/osu.Game/Screens/Edit/Timing/TimingScreen.cs +++ b/osu.Game/Screens/Edit/Timing/TimingScreen.cs @@ -76,7 +76,7 @@ namespace osu.Game.Screens.Edit.Timing private OsuButton deleteButton; private ControlPointTable table; - private BindableList controlGroups; + private IBindableList controlGroups; [Resolved] private EditorClock clock { get; set; } @@ -141,8 +141,7 @@ namespace osu.Game.Screens.Edit.Timing selectedGroup.BindValueChanged(selected => { deleteButton.Enabled.Value = selected.NewValue != null; }, true); - // todo: remove cast after https://github.com/ppy/osu-framework/pull/3906 is merged - controlGroups = (BindableList)Beatmap.Value.Beatmap.ControlPointInfo.Groups.GetBoundCopy(); + controlGroups = Beatmap.Value.Beatmap.ControlPointInfo.Groups.GetBoundCopy(); controlGroups.BindCollectionChanged((sender, args) => { From fa742a2ef1567072065c8bb3d7b8de7ccf5def1d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 30 Sep 2020 20:28:02 +0900 Subject: [PATCH 1361/1782] Update to consume framework fixes --- .../Components/Timelines/Summary/Parts/ControlPointPart.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointPart.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointPart.cs index 6edb49ba1b..8c0e31c04c 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointPart.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointPart.cs @@ -14,13 +14,13 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts /// public class ControlPointPart : TimelinePart { - private BindableList controlPointGroups; + private IBindableList controlPointGroups; protected override void LoadBeatmap(WorkingBeatmap beatmap) { base.LoadBeatmap(beatmap); - controlPointGroups = (BindableList)beatmap.Beatmap.ControlPointInfo.Groups.GetBoundCopy(); + controlPointGroups = beatmap.Beatmap.ControlPointInfo.Groups.GetBoundCopy(); controlPointGroups.BindCollectionChanged((sender, args) => { switch (args.Action) From 77651be2caff6e95fb52801462130197d9e02183 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 30 Sep 2020 21:32:50 +0900 Subject: [PATCH 1362/1782] Remove padding from HitResult --- osu.Game/Rulesets/Scoring/HitResult.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game/Rulesets/Scoring/HitResult.cs b/osu.Game/Rulesets/Scoring/HitResult.cs index fc33ff9df2..2b9b1a6c8e 100644 --- a/osu.Game/Rulesets/Scoring/HitResult.cs +++ b/osu.Game/Rulesets/Scoring/HitResult.cs @@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Scoring /// [Description(@"")] [Order(14)] - None = 0, + None, /// /// Indicates that the object has been judged as a miss. @@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Scoring /// [Description(@"Miss")] [Order(5)] - Miss = 64, + Miss, [Description(@"Meh")] [Order(4)] @@ -57,7 +57,7 @@ namespace osu.Game.Rulesets.Scoring /// Indicates small tick miss. /// [Order(11)] - SmallTickMiss = 128, + SmallTickMiss, /// /// Indicates a small tick hit. @@ -70,7 +70,7 @@ namespace osu.Game.Rulesets.Scoring /// Indicates a large tick miss. /// [Order(10)] - LargeTickMiss = 192, + LargeTickMiss, /// /// Indicates a large tick hit. @@ -84,20 +84,20 @@ namespace osu.Game.Rulesets.Scoring /// [Description("S Bonus")] [Order(9)] - SmallBonus = 254, + SmallBonus, /// /// Indicates a large bonus. /// [Description("L Bonus")] [Order(8)] - LargeBonus = 320, + LargeBonus, /// /// Indicates a miss that should be ignored for scoring purposes. /// [Order(13)] - IgnoreMiss = 384, + IgnoreMiss, /// /// Indicates a hit that should be ignored for scoring purposes. From 917e8fc3ba041d40e09396f39f020d2b508ac16e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 1 Oct 2020 00:53:01 +0900 Subject: [PATCH 1363/1782] Add difficulty rating to StarDifficulty --- osu.Game/Beatmaps/BeatmapDifficultyManager.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/osu.Game/Beatmaps/BeatmapDifficultyManager.cs b/osu.Game/Beatmaps/BeatmapDifficultyManager.cs index e9d26683c3..159a229499 100644 --- a/osu.Game/Beatmaps/BeatmapDifficultyManager.cs +++ b/osu.Game/Beatmaps/BeatmapDifficultyManager.cs @@ -307,5 +307,19 @@ namespace osu.Game.Beatmaps // Todo: Add more members (BeatmapInfo.DifficultyRating? Attributes? Etc...) } + + public DifficultyRating DifficultyRating + { + get + { + if (Stars < 2.0) return DifficultyRating.Easy; + if (Stars < 2.7) return DifficultyRating.Normal; + if (Stars < 4.0) return DifficultyRating.Hard; + if (Stars < 5.3) return DifficultyRating.Insane; + if (Stars < 6.5) return DifficultyRating.Expert; + + return DifficultyRating.ExpertPlus; + } + } } } From fde00d343197d16ed0140d412b4fa4017e369907 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 1 Oct 2020 00:53:25 +0900 Subject: [PATCH 1364/1782] Make DifficultyIcon support dynamic star rating --- osu.Game/Beatmaps/Drawables/DifficultyIcon.cs | 135 +++++++++++++++--- .../Drawables/GroupedDifficultyIcon.cs | 2 +- .../Screens/Multi/Components/ModeTypeInfo.cs | 2 +- .../Screens/Multi/DrawableRoomPlaylistItem.cs | 2 +- 4 files changed, 117 insertions(+), 24 deletions(-) diff --git a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs index 8a0d981e49..d5e4b13a84 100644 --- a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs +++ b/osu.Game/Beatmaps/Drawables/DifficultyIcon.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.Threading; +using JetBrains.Annotations; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -14,6 +18,7 @@ using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Rulesets; +using osu.Game.Rulesets.Mods; using osuTK; using osuTK.Graphics; @@ -21,9 +26,6 @@ namespace osu.Game.Beatmaps.Drawables { public class DifficultyIcon : CompositeDrawable, IHasCustomTooltip { - private readonly BeatmapInfo beatmap; - private readonly RulesetInfo ruleset; - private readonly Container iconContainer; /// @@ -35,23 +37,49 @@ namespace osu.Game.Beatmaps.Drawables set => iconContainer.Size = value; } - public DifficultyIcon(BeatmapInfo beatmap, RulesetInfo ruleset = null, bool shouldShowTooltip = true) + [NotNull] + private readonly BeatmapInfo beatmap; + + [CanBeNull] + private readonly RulesetInfo ruleset; + + [CanBeNull] + private readonly IReadOnlyList mods; + + private readonly bool shouldShowTooltip; + private readonly IBindable difficultyBindable = new Bindable(); + + private Drawable background; + + /// + /// Creates a new with a given and combination. + /// + /// The beatmap to show the difficulty of. + /// The ruleset to show the difficulty with. + /// The mods to show the difficulty with. + /// Whether to display a tooltip when hovered. + public DifficultyIcon([NotNull] BeatmapInfo beatmap, [CanBeNull] RulesetInfo ruleset, [CanBeNull] IReadOnlyList mods, bool shouldShowTooltip = true) + : this(beatmap, shouldShowTooltip) + { + this.ruleset = ruleset ?? beatmap.Ruleset; + this.mods = mods ?? Array.Empty(); + } + + /// + /// Creates a new that follows the currently-selected ruleset and mods. + /// + /// The beatmap to show the difficulty of. + /// Whether to display a tooltip when hovered. + public DifficultyIcon([NotNull] BeatmapInfo beatmap, bool shouldShowTooltip = true) { this.beatmap = beatmap ?? throw new ArgumentNullException(nameof(beatmap)); - - this.ruleset = ruleset ?? beatmap.Ruleset; - if (shouldShowTooltip) - TooltipContent = beatmap; + this.shouldShowTooltip = shouldShowTooltip; AutoSizeAxes = Axes.Both; InternalChild = iconContainer = new Container { Size = new Vector2(20f) }; } - public ITooltip GetCustomTooltip() => new DifficultyIconTooltip(); - - public object TooltipContent { get; } - [BackgroundDependencyLoader] private void load(OsuColour colours) { @@ -70,10 +98,10 @@ namespace osu.Game.Beatmaps.Drawables Type = EdgeEffectType.Shadow, Radius = 5, }, - Child = new Box + Child = background = new Box { RelativeSizeAxes = Axes.Both, - Colour = colours.ForDifficultyRating(beatmap.DifficultyRating), + Colour = colours.ForDifficultyRating(beatmap.DifficultyRating) // Default value that will be re-populated once difficulty calculation completes }, }, new ConstrainedIconContainer @@ -82,16 +110,73 @@ 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 = beatmap.Ruleset?.CreateInstance()?.CreateIcon() ?? new SpriteIcon { Icon = FontAwesome.Regular.QuestionCircle } + }, + new DelayedLoadUnloadWrapper(() => new DifficultyRetriever(beatmap, ruleset, mods) { StarDifficulty = { BindTarget = difficultyBindable } }, 0), }; + + difficultyBindable.BindValueChanged(difficulty => background.Colour = colours.ForDifficultyRating(difficulty.NewValue.DifficultyRating)); + } + + public ITooltip GetCustomTooltip() => new DifficultyIconTooltip(); + + public object TooltipContent => shouldShowTooltip ? new DifficultyIconTooltipContent(beatmap, difficultyBindable) : null; + + private class DifficultyRetriever : Drawable + { + public readonly Bindable StarDifficulty = new Bindable(); + + private readonly BeatmapInfo beatmap; + private readonly RulesetInfo ruleset; + private readonly IReadOnlyList mods; + + private CancellationTokenSource difficultyCancellation; + + [Resolved] + private BeatmapDifficultyManager difficultyManager { get; set; } + + public DifficultyRetriever(BeatmapInfo beatmap, RulesetInfo ruleset, IReadOnlyList mods) + { + this.beatmap = beatmap; + this.ruleset = ruleset; + this.mods = mods; + } + + private IBindable localStarDifficulty; + + [BackgroundDependencyLoader] + private void load() + { + difficultyCancellation = new CancellationTokenSource(); + localStarDifficulty = ruleset != null + ? difficultyManager.GetBindableDifficulty(beatmap, ruleset, mods, difficultyCancellation.Token) + : difficultyManager.GetBindableDifficulty(beatmap, difficultyCancellation.Token); + localStarDifficulty.BindValueChanged(difficulty => StarDifficulty.Value = difficulty.NewValue); + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + difficultyCancellation?.Cancel(); + } + } + + private class DifficultyIconTooltipContent + { + public readonly BeatmapInfo Beatmap; + public readonly IBindable Difficulty; + + public DifficultyIconTooltipContent(BeatmapInfo beatmap, IBindable difficulty) + { + Beatmap = beatmap; + Difficulty = difficulty; + } } private class DifficultyIconTooltip : VisibilityContainer, ITooltip { private readonly OsuSpriteText difficultyName, starRating; private readonly Box background; - private readonly FillFlowContainer difficultyFlow; public DifficultyIconTooltip() @@ -159,14 +244,22 @@ namespace osu.Game.Beatmaps.Drawables background.Colour = colours.Gray3; } + private readonly IBindable starDifficulty = new Bindable(); + public bool SetContent(object content) { - if (!(content is BeatmapInfo beatmap)) + if (!(content is DifficultyIconTooltipContent iconContent)) return false; - difficultyName.Text = beatmap.Version; - starRating.Text = $"{beatmap.StarDifficulty:0.##}"; - difficultyFlow.Colour = colours.ForDifficultyRating(beatmap.DifficultyRating, true); + difficultyName.Text = iconContent.Beatmap.Version; + + starDifficulty.UnbindAll(); + starDifficulty.BindTo(iconContent.Difficulty); + starDifficulty.BindValueChanged(difficulty => + { + starRating.Text = $"{difficulty.NewValue.Stars:0.##}"; + difficultyFlow.Colour = colours.ForDifficultyRating(difficulty.NewValue.DifficultyRating, true); + }, true); return true; } diff --git a/osu.Game/Beatmaps/Drawables/GroupedDifficultyIcon.cs b/osu.Game/Beatmaps/Drawables/GroupedDifficultyIcon.cs index fbad113caa..fcee4c2f1a 100644 --- a/osu.Game/Beatmaps/Drawables/GroupedDifficultyIcon.cs +++ b/osu.Game/Beatmaps/Drawables/GroupedDifficultyIcon.cs @@ -20,7 +20,7 @@ namespace osu.Game.Beatmaps.Drawables public class GroupedDifficultyIcon : DifficultyIcon { public GroupedDifficultyIcon(List beatmaps, RulesetInfo ruleset, Color4 counterColour) - : base(beatmaps.OrderBy(b => b.StarDifficulty).Last(), ruleset, false) + : base(beatmaps.OrderBy(b => b.StarDifficulty).Last(), ruleset, null, false) { AddInternal(new OsuSpriteText { diff --git a/osu.Game/Screens/Multi/Components/ModeTypeInfo.cs b/osu.Game/Screens/Multi/Components/ModeTypeInfo.cs index 0015feb26a..f07bd8c3b2 100644 --- a/osu.Game/Screens/Multi/Components/ModeTypeInfo.cs +++ b/osu.Game/Screens/Multi/Components/ModeTypeInfo.cs @@ -60,7 +60,7 @@ namespace osu.Game.Screens.Multi.Components if (item?.Beatmap != null) { drawableRuleset.FadeIn(transition_duration); - drawableRuleset.Child = new DifficultyIcon(item.Beatmap.Value, item.Ruleset.Value) { Size = new Vector2(height) }; + drawableRuleset.Child = new DifficultyIcon(item.Beatmap.Value, item.Ruleset.Value, item.RequiredMods) { Size = new Vector2(height) }; } else drawableRuleset.FadeOut(transition_duration); diff --git a/osu.Game/Screens/Multi/DrawableRoomPlaylistItem.cs b/osu.Game/Screens/Multi/DrawableRoomPlaylistItem.cs index b007e0349d..bda00b65b5 100644 --- a/osu.Game/Screens/Multi/DrawableRoomPlaylistItem.cs +++ b/osu.Game/Screens/Multi/DrawableRoomPlaylistItem.cs @@ -103,7 +103,7 @@ namespace osu.Game.Screens.Multi private void refresh() { - difficultyIconContainer.Child = new DifficultyIcon(beatmap.Value, ruleset.Value) { Size = new Vector2(32) }; + difficultyIconContainer.Child = new DifficultyIcon(beatmap.Value, ruleset.Value, requiredMods) { Size = new Vector2(32) }; beatmapText.Clear(); beatmapText.AddLink(Item.Beatmap.ToString(), LinkAction.OpenBeatmap, Item.Beatmap.Value.OnlineBeatmapID.ToString()); From 2213db20886ab1952bf33dc370cb67efa3b0e681 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 1 Oct 2020 00:59:41 +0900 Subject: [PATCH 1365/1782] Use the given ruleset by default --- 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 d5e4b13a84..9ffe813187 100644 --- a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs +++ b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs @@ -110,7 +110,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 = beatmap.Ruleset?.CreateInstance()?.CreateIcon() ?? new SpriteIcon { Icon = FontAwesome.Regular.QuestionCircle } + Icon = (ruleset ?? beatmap.Ruleset)?.CreateInstance()?.CreateIcon() ?? new SpriteIcon { Icon = FontAwesome.Regular.QuestionCircle } }, new DelayedLoadUnloadWrapper(() => new DifficultyRetriever(beatmap, ruleset, mods) { StarDifficulty = { BindTarget = difficultyBindable } }, 0), }; From ca9f5b447ee643dc3ebe558b9e21707e1e41d2e3 Mon Sep 17 00:00:00 2001 From: Ganendra Afrasya Date: Thu, 1 Oct 2020 02:02:27 +0700 Subject: [PATCH 1366/1782] Fix UserListPanel background position --- osu.Game/Users/UserListPanel.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Users/UserListPanel.cs b/osu.Game/Users/UserListPanel.cs index 9c95eff739..cc4fca9b94 100644 --- a/osu.Game/Users/UserListPanel.cs +++ b/osu.Game/Users/UserListPanel.cs @@ -26,6 +26,8 @@ namespace osu.Game.Users private void load() { Background.Width = 0.5f; + Background.Origin = Anchor.CentreRight; + Background.Anchor = Anchor.CentreRight; Background.Colour = ColourInfo.GradientHorizontal(Color4.White.Opacity(1), Color4.White.Opacity(0.3f)); } From 6b416c9881ab19dcc2291849777f729b6a422e57 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 1 Oct 2020 12:09:12 +0900 Subject: [PATCH 1367/1782] Rename method and improve method implementation --- osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs index 3d8a52a864..d5c3538c81 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs @@ -73,7 +73,7 @@ namespace osu.Game.Rulesets.Osu.Tests new OsuReplayFrame { Position = new Vector2(0, 0), Actions = { OsuAction.RightButton }, Time = time_during_slide_2 }, }); - AddAssert("Tracking retained", assertGreatJudge); + AddAssert("Tracking retained", assertMaxJudge); } /// @@ -94,7 +94,7 @@ namespace osu.Game.Rulesets.Osu.Tests new OsuReplayFrame { Position = new Vector2(0, 0), Actions = { OsuAction.RightButton }, Time = time_during_slide_1 }, }); - AddAssert("Tracking retained", assertGreatJudge); + AddAssert("Tracking retained", assertMaxJudge); } /// @@ -115,7 +115,7 @@ namespace osu.Game.Rulesets.Osu.Tests new OsuReplayFrame { Position = new Vector2(0, 0), Actions = { OsuAction.RightButton }, Time = time_during_slide_1 }, }); - AddAssert("Tracking retained", assertGreatJudge); + AddAssert("Tracking retained", assertMaxJudge); } /// @@ -288,7 +288,7 @@ namespace osu.Game.Rulesets.Osu.Tests new OsuReplayFrame { Position = new Vector2(slider_path_length, OsuHitObject.OBJECT_RADIUS * 1.199f), Actions = { OsuAction.LeftButton }, Time = time_slider_end }, }); - AddAssert("Tracking kept", assertGreatJudge); + AddAssert("Tracking kept", assertMaxJudge); } /// @@ -312,7 +312,7 @@ namespace osu.Game.Rulesets.Osu.Tests AddAssert("Tracking dropped", assertMidSliderJudgementFail); } - private bool assertGreatJudge() => judgementResults.Any() && judgementResults.All(t => t.Type.IsHit()); + private bool assertMaxJudge() => judgementResults.Any() && judgementResults.All(t => t.Type == t.Judgement.MaxResult); private bool assertHeadMissTailTracked() => judgementResults[^2].Type == HitResult.IgnoreHit && judgementResults.First().Type == HitResult.Miss; From 806d8b4b1dddcc35bb7512dd60cc078a6000e95f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 1 Oct 2020 12:13:24 +0900 Subject: [PATCH 1368/1782] Make scoring int-based again --- osu.Game/Rulesets/Judgements/Judgement.cs | 24 +++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/osu.Game/Rulesets/Judgements/Judgement.cs b/osu.Game/Rulesets/Judgements/Judgement.cs index ea7a8d8d25..c7d572b629 100644 --- a/osu.Game/Rulesets/Judgements/Judgement.cs +++ b/osu.Game/Rulesets/Judgements/Judgement.cs @@ -15,12 +15,12 @@ namespace osu.Game.Rulesets.Judgements /// /// The score awarded for a small bonus. /// - public const double SMALL_BONUS_SCORE = 10; + public const int SMALL_BONUS_SCORE = 10; /// /// The score awarded for a large bonus. /// - public const double LARGE_BONUS_SCORE = 50; + public const int LARGE_BONUS_SCORE = 50; /// /// The default health increase for a maximum judgement, as a proportion of total health. @@ -74,7 +74,7 @@ namespace osu.Game.Rulesets.Judgements /// /// The numeric score representation for the maximum achievable result. /// - public double MaxNumericResult => ToNumericResult(MaxResult); + public int MaxNumericResult => ToNumericResult(MaxResult); /// /// The health increase for the maximum achievable result. @@ -94,7 +94,7 @@ namespace osu.Game.Rulesets.Judgements /// /// The to find the numeric score representation for. /// The numeric score representation of . - public double NumericResultFor(JudgementResult result) => ToNumericResult(result.Type); + public int NumericResultFor(JudgementResult result) => ToNumericResult(result.Type); /// /// Retrieves the numeric health increase of a . @@ -155,7 +155,7 @@ namespace osu.Game.Rulesets.Judgements public override string ToString() => $"MaxResult:{MaxResult} MaxScore:{MaxNumericResult}"; - public static double ToNumericResult(HitResult result) + public static int ToNumericResult(HitResult result) { switch (result) { @@ -163,25 +163,25 @@ namespace osu.Game.Rulesets.Judgements return 0; case HitResult.SmallTickHit: - return 1 / 30d; + return 10; case HitResult.LargeTickHit: - return 1 / 10d; + return 30; case HitResult.Meh: - return 1 / 6d; + return 50; case HitResult.Ok: - return 1 / 3d; + return 100; case HitResult.Good: - return 2 / 3d; + return 200; case HitResult.Great: - return 1d; + return 300; case HitResult.Perfect: - return 7 / 6d; + return 350; case HitResult.SmallBonus: return SMALL_BONUS_SCORE; From 3a26bd8d9ba87590f2e6f8d35afd9c255f543979 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 1 Oct 2020 12:14:16 +0900 Subject: [PATCH 1369/1782] Adjust obsoletion + xmldoc of NumericResultFor() --- osu.Game/Rulesets/Judgements/Judgement.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Rulesets/Judgements/Judgement.cs b/osu.Game/Rulesets/Judgements/Judgement.cs index c7d572b629..738ae0d6d2 100644 --- a/osu.Game/Rulesets/Judgements/Judgement.cs +++ b/osu.Game/Rulesets/Judgements/Judgement.cs @@ -82,12 +82,12 @@ namespace osu.Game.Rulesets.Judgements public double MaxHealthIncrease => HealthIncreaseFor(MaxResult); /// - /// Retrieves the numeric score representation of a . + /// Retrieves the numeric score representation of a . /// - /// The to find the numeric score representation for. + /// The to find the numeric score representation for. /// The numeric score representation of . - [Obsolete("Has no effect. Use ToNumericResult(HitResult) (standardised across all rulesets).")] // Can be removed 20210328 - protected virtual int NumericResultFor(HitResult result) => result == HitResult.Miss ? 0 : 1; + [Obsolete("Has no effect. Use ToNumericResult(HitResult) (standardised across all rulesets).")] // Can be made non-virtual 20210328 + protected virtual int NumericResultFor(HitResult result) => ToNumericResult(result); /// /// Retrieves the numeric score representation of a . From 3c9ee6abc11f227eb6a18d10b66f0c7e6aedf04b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 1 Oct 2020 12:15:34 +0900 Subject: [PATCH 1370/1782] Use local static to determine score per spinner tick --- .../Objects/Drawables/Pieces/SpinnerBonusDisplay.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerBonusDisplay.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerBonusDisplay.cs index 1668cd73bd..f483bb1b26 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerBonusDisplay.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerBonusDisplay.cs @@ -5,7 +5,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; -using osu.Game.Rulesets.Judgements; namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces { @@ -14,6 +13,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces /// public class SpinnerBonusDisplay : CompositeDrawable { + private static readonly int score_per_tick = new SpinnerBonusTick().CreateJudgement().MaxNumericResult; + private readonly OsuSpriteText bonusCounter; public SpinnerBonusDisplay() @@ -37,7 +38,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces return; displayedCount = count; - bonusCounter.Text = $"{Judgement.LARGE_BONUS_SCORE * count}"; + bonusCounter.Text = $"{score_per_tick * count}"; bonusCounter.FadeOutFromOne(1500); bonusCounter.ScaleTo(1.5f).Then().ScaleTo(1f, 1000, Easing.OutQuint); } From c9f38f7bb6af116fa7ec813ceb3f100dcad30adb Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 1 Oct 2020 12:28:33 +0900 Subject: [PATCH 1371/1782] Add obsoletion notice --- .../Rulesets/Objects/Drawables/DrawableHitObject.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 2a3e3716e5..a48627208d 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -10,6 +10,7 @@ using osu.Framework.Bindables; using osu.Framework.Extensions.TypeExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Primitives; +using osu.Framework.Logging; using osu.Framework.Threading; using osu.Game.Audio; using osu.Game.Rulesets.Judgements; @@ -476,12 +477,21 @@ namespace osu.Game.Rulesets.Objects.Drawables throw new InvalidOperationException($"{GetType().ReadableName()} applied a {nameof(JudgementResult)} but did not update {nameof(JudgementResult.Type)}."); // Some (especially older) rulesets use scorable judgements instead of the newer ignorehit/ignoremiss judgements. + // Can be removed 20210328 if (Result.Judgement.MaxResult == HitResult.IgnoreHit) { + HitResult originalType = Result.Type; + if (Result.Type == HitResult.Miss) Result.Type = HitResult.IgnoreMiss; else if (Result.Type >= HitResult.Meh && Result.Type <= HitResult.Perfect) Result.Type = HitResult.IgnoreHit; + + if (Result.Type != originalType) + { + Logger.Log($"{GetType().ReadableName()} applied an invalid hit result ({originalType}) when {nameof(HitResult.IgnoreMiss)} or {nameof(HitResult.IgnoreHit)} is expected.\n" + + $"This has been automatically adjusted to {Result.Type}, and support will be removed from 2020-03-28 onwards.", level: LogLevel.Important); + } } if (!Result.Type.IsValidHitResult(Result.Judgement.MinResult, Result.Judgement.MaxResult)) From 61e62929eeccb402000af318ba438c0c78e2e6ec Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 1 Oct 2020 12:51:33 +0900 Subject: [PATCH 1372/1782] Apply changes in line with framework event logic update --- osu.Game.Tournament/Components/DateTextBox.cs | 2 +- osu.Game/Online/Chat/StandAloneChatDisplay.cs | 3 ++- osu.Game/Overlays/ChatOverlay.cs | 3 ++- osu.Game/Overlays/Music/PlaylistOverlay.cs | 2 +- osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs | 3 ++- 5 files changed, 8 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tournament/Components/DateTextBox.cs b/osu.Game.Tournament/Components/DateTextBox.cs index aee5241e35..a1b5ac38ea 100644 --- a/osu.Game.Tournament/Components/DateTextBox.cs +++ b/osu.Game.Tournament/Components/DateTextBox.cs @@ -28,7 +28,7 @@ namespace osu.Game.Tournament.Components { base.Bindable = new Bindable(); - ((OsuTextBox)Control).OnCommit = (sender, newText) => + ((OsuTextBox)Control).OnCommit += (sender, newText) => { try { diff --git a/osu.Game/Online/Chat/StandAloneChatDisplay.cs b/osu.Game/Online/Chat/StandAloneChatDisplay.cs index f8810c778f..8b0caddbc6 100644 --- a/osu.Game/Online/Chat/StandAloneChatDisplay.cs +++ b/osu.Game/Online/Chat/StandAloneChatDisplay.cs @@ -59,12 +59,13 @@ namespace osu.Game.Online.Chat RelativeSizeAxes = Axes.X, Height = textbox_height, PlaceholderText = "type your message", - OnCommit = postMessage, ReleaseFocusOnCommit = false, HoldFocus = true, Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft, }); + + textbox.OnCommit += postMessage; } Channel.BindValueChanged(channelChanged); diff --git a/osu.Game/Overlays/ChatOverlay.cs b/osu.Game/Overlays/ChatOverlay.cs index c53eccf78b..8bc7e21047 100644 --- a/osu.Game/Overlays/ChatOverlay.cs +++ b/osu.Game/Overlays/ChatOverlay.cs @@ -146,7 +146,6 @@ namespace osu.Game.Overlays RelativeSizeAxes = Axes.Both, Height = 1, PlaceholderText = "type your message", - OnCommit = postMessage, ReleaseFocusOnCommit = false, HoldFocus = true, } @@ -186,6 +185,8 @@ namespace osu.Game.Overlays }, }; + textbox.OnCommit += postMessage; + ChannelTabControl.Current.ValueChanged += current => channelManager.CurrentChannel.Value = current.NewValue; ChannelTabControl.ChannelSelectorActive.ValueChanged += active => ChannelSelectionOverlay.State.Value = active.NewValue ? Visibility.Visible : Visibility.Hidden; ChannelSelectionOverlay.State.ValueChanged += state => diff --git a/osu.Game/Overlays/Music/PlaylistOverlay.cs b/osu.Game/Overlays/Music/PlaylistOverlay.cs index 050e687dfb..b8d04eab4e 100644 --- a/osu.Game/Overlays/Music/PlaylistOverlay.cs +++ b/osu.Game/Overlays/Music/PlaylistOverlay.cs @@ -75,7 +75,7 @@ namespace osu.Game.Overlays.Music }, }; - filter.Search.OnCommit = (sender, newText) => + filter.Search.OnCommit += (sender, newText) => { BeatmapInfo toSelect = list.FirstVisibleSet?.Beatmaps?.FirstOrDefault(); diff --git a/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs b/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs index 34e5da4ef4..f96e204f62 100644 --- a/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs @@ -236,7 +236,6 @@ namespace osu.Game.Overlays.Settings.Sections.General PlaceholderText = "password", RelativeSizeAxes = Axes.X, TabbableContentContainer = this, - OnCommit = (sender, newText) => performLogin() }, new SettingsCheckbox { @@ -276,6 +275,8 @@ namespace osu.Game.Overlays.Settings.Sections.General } } }; + + password.OnCommit += (sender, newText) => performLogin(); } public override bool AcceptsFocus => true; From e0a0902a15f9e1dac34b8795035c2ef48e47dbec Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 1 Oct 2020 13:06:24 +0900 Subject: [PATCH 1373/1782] Ensure textbox always reverts to sane state on out-of-range failures --- .../Edit/Timing/SliderWithTextBoxInput.cs | 7 +++++-- osu.Game/Screens/Edit/Timing/TimingSection.cs | 19 ++++++++++--------- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/SliderWithTextBoxInput.cs b/osu.Game/Screens/Edit/Timing/SliderWithTextBoxInput.cs index 07b914c506..14023b0c35 100644 --- a/osu.Game/Screens/Edit/Timing/SliderWithTextBoxInput.cs +++ b/osu.Game/Screens/Edit/Timing/SliderWithTextBoxInput.cs @@ -54,9 +54,12 @@ namespace osu.Game.Screens.Edit.Timing } catch { - // will restore the previous text value on failure. - Current.TriggerChange(); + // TriggerChange below will restore the previous text value on failure. } + + // This is run regardless of parsing success as the parsed number may not actually trigger a change + // due to bindable clamping. Even in such a case we want to update the textbox to a sane visual state. + Current.TriggerChange(); }; Current.BindValueChanged(val => diff --git a/osu.Game/Screens/Edit/Timing/TimingSection.cs b/osu.Game/Screens/Edit/Timing/TimingSection.cs index cc79dd2acc..0202441537 100644 --- a/osu.Game/Screens/Edit/Timing/TimingSection.cs +++ b/osu.Game/Screens/Edit/Timing/TimingSection.cs @@ -65,18 +65,19 @@ namespace osu.Game.Screens.Edit.Timing { if (!isNew) return; - if (double.TryParse(Current.Value, out double doubleVal)) + try { - try - { + if (double.TryParse(Current.Value, out double doubleVal) && doubleVal > 0) beatLengthBindable.Value = beatLengthToBpm(doubleVal); - } - catch - { - // will restore the previous text value on failure. - beatLengthBindable.TriggerChange(); - } } + catch + { + // TriggerChange below will restore the previous text value on failure. + } + + // This is run regardless of parsing success as the parsed number may not actually trigger a change + // due to bindable clamping. Even in such a case we want to update the textbox to a sane visual state. + beatLengthBindable.TriggerChange(); }; beatLengthBindable.BindValueChanged(val => From b1f2bdd579ee4a6b91d3f5b3b78e62ea204a97fa Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 1 Oct 2020 13:47:49 +0900 Subject: [PATCH 1374/1782] Add missing xmldoc --- .../Edit/Compose/Components/ComposeSelectionBox.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/osu.Game/Screens/Edit/Compose/Components/ComposeSelectionBox.cs b/osu.Game/Screens/Edit/Compose/Components/ComposeSelectionBox.cs index 424705c755..530c6007cf 100644 --- a/osu.Game/Screens/Edit/Compose/Components/ComposeSelectionBox.cs +++ b/osu.Game/Screens/Edit/Compose/Components/ComposeSelectionBox.cs @@ -24,6 +24,9 @@ namespace osu.Game.Screens.Edit.Compose.Components private bool canRotate; + /// + /// Whether rotation support should be enabled. + /// public bool CanRotate { get => canRotate; @@ -36,6 +39,9 @@ namespace osu.Game.Screens.Edit.Compose.Components private bool canScaleX; + /// + /// Whether vertical scale support should be enabled. + /// public bool CanScaleX { get => canScaleX; @@ -48,6 +54,9 @@ namespace osu.Game.Screens.Edit.Compose.Components private bool canScaleY; + /// + /// Whether horizontal scale support should be enabled. + /// public bool CanScaleY { get => canScaleY; From 02f14ab4b0045249c9a9bf681ee324c8ed5dfdbc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 1 Oct 2020 16:24:04 +0900 Subject: [PATCH 1375/1782] Rename operation start/end to be more encompassing --- osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs | 4 ++-- .../Screens/Edit/Compose/Components/SelectionHandler.cs | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs index 6b4f13db35..f275e08234 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs @@ -27,9 +27,9 @@ namespace osu.Game.Rulesets.Osu.Edit SelectionBox.CanScaleY = canOperate; } - protected override void OnDragOperationEnded() + protected override void OnOperationEnded() { - base.OnDragOperationEnded(); + base.OnOperationEnded(); referenceOrigin = null; } diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index ee094c6246..435f84996a 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -97,8 +97,8 @@ namespace osu.Game.Screens.Edit.Compose.Components public ComposeSelectionBox CreateSelectionBox() => new ComposeSelectionBox { - OperationStarted = OnDragOperationBegan, - OperationEnded = OnDragOperationEnded, + OperationStarted = OnOperationBegan, + OperationEnded = OnOperationEnded, OnRotation = e => HandleRotation(e.Delta.X), OnScale = (e, anchor) => HandleScale(e.Delta, anchor), @@ -107,7 +107,7 @@ namespace osu.Game.Screens.Edit.Compose.Components /// /// Fired when a drag operation ends from the selection box. /// - protected virtual void OnDragOperationBegan() + protected virtual void OnOperationBegan() { ChangeHandler.BeginChange(); } @@ -115,7 +115,7 @@ namespace osu.Game.Screens.Edit.Compose.Components /// /// Fired when a drag operation begins from the selection box. /// - protected virtual void OnDragOperationEnded() + protected virtual void OnOperationEnded() { ChangeHandler.EndChange(); } From 983b693858195cfbcece3c1f76a52c2dc7e59a91 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 1 Oct 2020 16:24:50 +0900 Subject: [PATCH 1376/1782] Add flip logic to OsuSelectionHandler --- .../Edit/OsuSelectionHandler.cs | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs index f275e08234..2bd4bc5015 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs @@ -41,6 +41,45 @@ namespace osu.Game.Rulesets.Osu.Edit /// private Vector2? referenceOrigin; + public override bool HandleFlip(Direction direction) + { + var hitObjects = selectedMovableObjects; + + var selectedObjectsQuad = getSurroundingQuad(hitObjects); + var centre = selectedObjectsQuad.Centre; + + foreach (var h in hitObjects) + { + var pos = h.Position; + + switch (direction) + { + case Direction.Horizontal: + pos.X = centre.X - (pos.X - centre.X); + break; + + case Direction.Vertical: + pos.Y = centre.Y - (pos.Y - centre.Y); + break; + } + + h.Position = pos; + + if (h is Slider slider) + { + foreach (var point in slider.Path.ControlPoints) + { + point.Position.Value = new Vector2( + (direction == Direction.Horizontal ? -1 : 1) * point.Position.Value.X, + (direction == Direction.Vertical ? -1 : 1) * point.Position.Value.Y + ); + } + } + } + + return true; + } + public override bool HandleScale(Vector2 scale, Anchor reference) { adjustScaleFromAnchor(ref scale, reference); From 78c5d5707496f4b7af3277c805063a0b7e5a5ec7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 1 Oct 2020 16:25:29 +0900 Subject: [PATCH 1377/1782] Add flip event flow and stop passing raw input events to handle methods --- .../Visual/Editing/TestSceneComposeSelectBox.cs | 17 ++++++++--------- .../Compose/Components/ComposeSelectionBox.cs | 5 +++-- .../Edit/Compose/Components/SelectionHandler.cs | 12 ++++++++++-- 3 files changed, 21 insertions(+), 13 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneComposeSelectBox.cs b/osu.Game.Tests/Visual/Editing/TestSceneComposeSelectBox.cs index a1fb91024b..2e0be95ff7 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneComposeSelectBox.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneComposeSelectBox.cs @@ -3,7 +3,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Input.Events; using osu.Game.Screens.Edit.Compose.Components; using osuTK; @@ -20,7 +19,7 @@ namespace osu.Game.Tests.Visual.Editing AddStep("create box", () => Child = selectionArea = new Container { - Size = new Vector2(300), + Size = new Vector2(400), Position = -new Vector2(150), Anchor = Anchor.Centre, Children = new Drawable[] @@ -42,29 +41,29 @@ namespace osu.Game.Tests.Visual.Editing AddToggleStep("toggle y", state => selectionBox.CanScaleY = state); } - private void handleScale(DragEvent e, Anchor reference) + private void handleScale(Vector2 amount, Anchor reference) { if ((reference & Anchor.y1) == 0) { int directionY = (reference & Anchor.y0) > 0 ? -1 : 1; if (directionY < 0) - selectionArea.Y += e.Delta.Y; - selectionArea.Height += directionY * e.Delta.Y; + selectionArea.Y += amount.Y; + selectionArea.Height += directionY * amount.Y; } if ((reference & Anchor.x1) == 0) { int directionX = (reference & Anchor.x0) > 0 ? -1 : 1; if (directionX < 0) - selectionArea.X += e.Delta.X; - selectionArea.Width += directionX * e.Delta.X; + selectionArea.X += amount.X; + selectionArea.Width += directionX * amount.X; } } - private void handleRotation(DragEvent e) + private void handleRotation(float angle) { // kinda silly and wrong, but just showing that the drag handles work. - selectionArea.Rotation += e.Delta.X; + selectionArea.Rotation += angle; } } } diff --git a/osu.Game/Screens/Edit/Compose/Components/ComposeSelectionBox.cs b/osu.Game/Screens/Edit/Compose/Components/ComposeSelectionBox.cs index 530c6007cf..c457a68368 100644 --- a/osu.Game/Screens/Edit/Compose/Components/ComposeSelectionBox.cs +++ b/osu.Game/Screens/Edit/Compose/Components/ComposeSelectionBox.cs @@ -16,8 +16,9 @@ namespace osu.Game.Screens.Edit.Compose.Components { public class ComposeSelectionBox : CompositeDrawable { - public Action OnRotation; - public Action OnScale; + public Action OnRotation; + public Action OnScale; + public Action OnFlip; public Action OperationStarted; public Action OperationEnded; diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index 435f84996a..1c2f09f831 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -100,8 +100,9 @@ namespace osu.Game.Screens.Edit.Compose.Components OperationStarted = OnOperationBegan, OperationEnded = OnOperationEnded, - OnRotation = e => HandleRotation(e.Delta.X), - OnScale = (e, anchor) => HandleScale(e.Delta, anchor), + OnRotation = angle => HandleRotation(angle), + OnScale = (amount, anchor) => HandleScale(amount, anchor), + OnFlip = direction => HandleFlip(direction), }; /// @@ -151,6 +152,13 @@ namespace osu.Game.Screens.Edit.Compose.Components /// Whether any s could be moved. public virtual bool HandleScale(Vector2 scale, Anchor anchor) => false; + /// + /// Handled the selected s being flipped. + /// + /// The direction to flip + /// Whether any s could be moved. + public virtual bool HandleFlip(Direction direction) => false; + public bool OnPressed(PlatformAction action) { switch (action.ActionMethod) From 4e6a505a99740bee31ace700aa7994b994d0188d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 1 Oct 2020 16:25:40 +0900 Subject: [PATCH 1378/1782] Add new icons and tooltips --- .../Compose/Components/ComposeSelectionBox.cs | 180 ++++++++++++------ 1 file changed, 124 insertions(+), 56 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/ComposeSelectionBox.cs b/osu.Game/Screens/Edit/Compose/Components/ComposeSelectionBox.cs index c457a68368..a26533fdb5 100644 --- a/osu.Game/Screens/Edit/Compose/Components/ComposeSelectionBox.cs +++ b/osu.Game/Screens/Edit/Compose/Components/ComposeSelectionBox.cs @@ -5,6 +5,7 @@ using System; 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.Sprites; using osu.Framework.Input.Events; @@ -68,6 +69,8 @@ namespace osu.Game.Screens.Edit.Compose.Components } } + private FillFlowContainer buttons; + public const float BORDER_RADIUS = 3; [Resolved] @@ -105,72 +108,114 @@ namespace osu.Game.Screens.Edit.Compose.Components }, } }, + buttons = new FillFlowContainer + { + Y = 20, + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Anchor = Anchor.BottomCentre, + Origin = Anchor.Centre + } }; - if (CanRotate) + if (CanScaleX) addXScaleComponents(); + if (CanScaleX && CanScaleY) addFullScaleComponents(); + if (CanScaleY) addYScaleComponents(); + if (CanRotate) addRotationComponents(); + } + + private void addRotationComponents() + { + const float separation = 40; + + buttons.Insert(-1, new DragHandleButton(FontAwesome.Solid.Undo, "Rotate 90 degrees counter-clockwise") { - const float separation = 40; + OperationStarted = operationStarted, + OperationEnded = operationEnded, + Action = () => OnRotation?.Invoke(-90) + }); - AddRangeInternal(new Drawable[] - { - new Box - { - Colour = colours.YellowLight, - Blending = BlendingParameters.Additive, - Alpha = 0.3f, - Size = new Vector2(BORDER_RADIUS, separation), - Anchor = Anchor.TopCentre, - Origin = Anchor.BottomCentre, - }, - new RotationDragHandle - { - Anchor = Anchor.TopCentre, - Y = -separation, - HandleDrag = e => OnRotation?.Invoke(e), - OperationStarted = operationStarted, - OperationEnded = operationEnded - } - }); - } - - if (CanScaleY) + buttons.Add(new DragHandleButton(FontAwesome.Solid.Redo, "Rotate 90 degrees clockwise") { - AddRangeInternal(new[] - { - createDragHandle(Anchor.TopCentre), - createDragHandle(Anchor.BottomCentre), - }); - } + OperationStarted = operationStarted, + OperationEnded = operationEnded, + Action = () => OnRotation?.Invoke(90) + }); - if (CanScaleX) + AddRangeInternal(new Drawable[] { - AddRangeInternal(new[] + new Box { - createDragHandle(Anchor.CentreLeft), - createDragHandle(Anchor.CentreRight), - }); - } - - if (CanScaleX && CanScaleY) - { - AddRangeInternal(new[] + Depth = float.MaxValue, + Colour = colours.YellowLight, + Blending = BlendingParameters.Additive, + Alpha = 0.3f, + Size = new Vector2(BORDER_RADIUS, separation), + Anchor = Anchor.TopCentre, + Origin = Anchor.BottomCentre, + }, + new DragHandleButton(FontAwesome.Solid.Redo, "Free rotate") { - createDragHandle(Anchor.TopLeft), - createDragHandle(Anchor.TopRight), - createDragHandle(Anchor.BottomLeft), - createDragHandle(Anchor.BottomRight), - }); - } - - ScaleDragHandle createDragHandle(Anchor anchor) => - new ScaleDragHandle(anchor) - { - HandleDrag = e => OnScale?.Invoke(e, anchor), + Anchor = Anchor.TopCentre, + Y = -separation, + HandleDrag = e => OnRotation?.Invoke(e.Delta.X), OperationStarted = operationStarted, OperationEnded = operationEnded - }; + } + }); } + private void addYScaleComponents() + { + buttons.Add(new DragHandleButton(FontAwesome.Solid.ArrowsAltV, "Flip vertically") + { + OperationStarted = operationStarted, + OperationEnded = operationEnded, + Action = () => OnFlip?.Invoke(Direction.Vertical) + }); + + AddRangeInternal(new[] + { + createDragHandle(Anchor.TopCentre), + createDragHandle(Anchor.BottomCentre), + }); + } + + private void addFullScaleComponents() + { + AddRangeInternal(new[] + { + createDragHandle(Anchor.TopLeft), + createDragHandle(Anchor.TopRight), + createDragHandle(Anchor.BottomLeft), + createDragHandle(Anchor.BottomRight), + }); + } + + private void addXScaleComponents() + { + buttons.Add(new DragHandleButton(FontAwesome.Solid.ArrowsAltH, "Flip horizontally") + { + OperationStarted = operationStarted, + OperationEnded = operationEnded, + Action = () => OnFlip?.Invoke(Direction.Horizontal) + }); + + AddRangeInternal(new[] + { + createDragHandle(Anchor.CentreLeft), + createDragHandle(Anchor.CentreRight), + }); + } + + private ScaleDragHandle createDragHandle(Anchor anchor) => + new ScaleDragHandle(anchor) + { + HandleDrag = e => OnScale?.Invoke(e.Delta, anchor), + OperationStarted = operationStarted, + OperationEnded = operationEnded + }; + private int activeOperations; private void operationEnded() @@ -193,30 +238,53 @@ namespace osu.Game.Screens.Edit.Compose.Components } } - private class RotationDragHandle : DragHandle + private sealed class DragHandleButton : DragHandle, IHasTooltip { private SpriteIcon icon; + private readonly IconUsage iconUsage; + + public Action Action; + + public DragHandleButton(IconUsage iconUsage, string tooltip) + { + this.iconUsage = iconUsage; + + TooltipText = tooltip; + + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + } + [BackgroundDependencyLoader] private void load() { Size *= 2; - AddInternal(icon = new SpriteIcon { RelativeSizeAxes = Axes.Both, Size = new Vector2(0.5f), - Icon = FontAwesome.Solid.Redo, + Icon = iconUsage, Anchor = Anchor.Centre, Origin = Anchor.Centre, }); } + protected override bool OnClick(ClickEvent e) + { + OperationStarted?.Invoke(); + Action?.Invoke(); + OperationEnded?.Invoke(); + return true; + } + protected override void UpdateHoverState() { base.UpdateHoverState(); icon.Colour = !HandlingMouse && IsHovered ? Color4.White : Color4.Black; } + + public string TooltipText { get; } } private class DragHandle : Container From db1ad4243ec35e224580d00af45d9ee288296958 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 1 Oct 2020 16:27:42 +0900 Subject: [PATCH 1379/1782] Remove need for ScaleDragHandle class --- .../Edit/Compose/Components/ComposeSelectionBox.cs | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/ComposeSelectionBox.cs b/osu.Game/Screens/Edit/Compose/Components/ComposeSelectionBox.cs index a26533fdb5..ef7bc0ba36 100644 --- a/osu.Game/Screens/Edit/Compose/Components/ComposeSelectionBox.cs +++ b/osu.Game/Screens/Edit/Compose/Components/ComposeSelectionBox.cs @@ -208,9 +208,10 @@ namespace osu.Game.Screens.Edit.Compose.Components }); } - private ScaleDragHandle createDragHandle(Anchor anchor) => - new ScaleDragHandle(anchor) + private DragHandle createDragHandle(Anchor anchor) => + new DragHandle { + Anchor = anchor, HandleDrag = e => OnScale?.Invoke(e.Delta, anchor), OperationStarted = operationStarted, OperationEnded = operationEnded @@ -230,14 +231,6 @@ namespace osu.Game.Screens.Edit.Compose.Components OperationStarted?.Invoke(); } - private class ScaleDragHandle : DragHandle - { - public ScaleDragHandle(Anchor anchor) - { - Anchor = anchor; - } - } - private sealed class DragHandleButton : DragHandle, IHasTooltip { private SpriteIcon icon; From 1aff263419080160c9bbe078fb26005ae41ef0cb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 1 Oct 2020 16:34:34 +0900 Subject: [PATCH 1380/1782] Split out classes and simplify construction of buttons --- .../Editing/TestSceneComposeSelectBox.cs | 4 +- .../Compose/Components/ComposeSelectionBox.cs | 374 ------------------ .../Edit/Compose/Components/DragBox.cs | 2 +- .../Edit/Compose/Components/SelectionBox.cs | 210 ++++++++++ .../Components/SelectionBoxDragHandle.cs | 105 +++++ .../SelectionBoxDragHandleButton.cs | 66 ++++ .../Compose/Components/SelectionHandler.cs | 6 +- 7 files changed, 387 insertions(+), 380 deletions(-) delete mode 100644 osu.Game/Screens/Edit/Compose/Components/ComposeSelectionBox.cs create mode 100644 osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs create mode 100644 osu.Game/Screens/Edit/Compose/Components/SelectionBoxDragHandle.cs create mode 100644 osu.Game/Screens/Edit/Compose/Components/SelectionBoxDragHandleButton.cs diff --git a/osu.Game.Tests/Visual/Editing/TestSceneComposeSelectBox.cs b/osu.Game.Tests/Visual/Editing/TestSceneComposeSelectBox.cs index 2e0be95ff7..da98a7a024 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneComposeSelectBox.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneComposeSelectBox.cs @@ -14,7 +14,7 @@ namespace osu.Game.Tests.Visual.Editing public TestSceneComposeSelectBox() { - ComposeSelectionBox selectionBox = null; + SelectionBox selectionBox = null; AddStep("create box", () => Child = selectionArea = new Container @@ -24,7 +24,7 @@ namespace osu.Game.Tests.Visual.Editing Anchor = Anchor.Centre, Children = new Drawable[] { - selectionBox = new ComposeSelectionBox + selectionBox = new SelectionBox { CanRotate = true, CanScaleX = true, diff --git a/osu.Game/Screens/Edit/Compose/Components/ComposeSelectionBox.cs b/osu.Game/Screens/Edit/Compose/Components/ComposeSelectionBox.cs deleted file mode 100644 index ef7bc0ba36..0000000000 --- a/osu.Game/Screens/Edit/Compose/Components/ComposeSelectionBox.cs +++ /dev/null @@ -1,374 +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; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Cursor; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.Sprites; -using osu.Framework.Input.Events; -using osu.Game.Graphics; -using osuTK; -using osuTK.Graphics; - -namespace osu.Game.Screens.Edit.Compose.Components -{ - public class ComposeSelectionBox : CompositeDrawable - { - public Action OnRotation; - public Action OnScale; - public Action OnFlip; - - public Action OperationStarted; - public Action OperationEnded; - - private bool canRotate; - - /// - /// Whether rotation support should be enabled. - /// - public bool CanRotate - { - get => canRotate; - set - { - canRotate = value; - recreate(); - } - } - - private bool canScaleX; - - /// - /// Whether vertical scale support should be enabled. - /// - public bool CanScaleX - { - get => canScaleX; - set - { - canScaleX = value; - recreate(); - } - } - - private bool canScaleY; - - /// - /// Whether horizontal scale support should be enabled. - /// - public bool CanScaleY - { - get => canScaleY; - set - { - canScaleY = value; - recreate(); - } - } - - private FillFlowContainer buttons; - - public const float BORDER_RADIUS = 3; - - [Resolved] - private OsuColour colours { get; set; } - - [BackgroundDependencyLoader] - private void load() - { - RelativeSizeAxes = Axes.Both; - - recreate(); - } - - private void recreate() - { - if (LoadState < LoadState.Loading) - return; - - InternalChildren = new Drawable[] - { - new Container - { - Masking = true, - BorderThickness = BORDER_RADIUS, - BorderColour = colours.YellowDark, - RelativeSizeAxes = Axes.Both, - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - - AlwaysPresent = true, - Alpha = 0 - }, - } - }, - buttons = new FillFlowContainer - { - Y = 20, - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Anchor = Anchor.BottomCentre, - Origin = Anchor.Centre - } - }; - - if (CanScaleX) addXScaleComponents(); - if (CanScaleX && CanScaleY) addFullScaleComponents(); - if (CanScaleY) addYScaleComponents(); - if (CanRotate) addRotationComponents(); - } - - private void addRotationComponents() - { - const float separation = 40; - - buttons.Insert(-1, new DragHandleButton(FontAwesome.Solid.Undo, "Rotate 90 degrees counter-clockwise") - { - OperationStarted = operationStarted, - OperationEnded = operationEnded, - Action = () => OnRotation?.Invoke(-90) - }); - - buttons.Add(new DragHandleButton(FontAwesome.Solid.Redo, "Rotate 90 degrees clockwise") - { - OperationStarted = operationStarted, - OperationEnded = operationEnded, - Action = () => OnRotation?.Invoke(90) - }); - - AddRangeInternal(new Drawable[] - { - new Box - { - Depth = float.MaxValue, - Colour = colours.YellowLight, - Blending = BlendingParameters.Additive, - Alpha = 0.3f, - Size = new Vector2(BORDER_RADIUS, separation), - Anchor = Anchor.TopCentre, - Origin = Anchor.BottomCentre, - }, - new DragHandleButton(FontAwesome.Solid.Redo, "Free rotate") - { - Anchor = Anchor.TopCentre, - Y = -separation, - HandleDrag = e => OnRotation?.Invoke(e.Delta.X), - OperationStarted = operationStarted, - OperationEnded = operationEnded - } - }); - } - - private void addYScaleComponents() - { - buttons.Add(new DragHandleButton(FontAwesome.Solid.ArrowsAltV, "Flip vertically") - { - OperationStarted = operationStarted, - OperationEnded = operationEnded, - Action = () => OnFlip?.Invoke(Direction.Vertical) - }); - - AddRangeInternal(new[] - { - createDragHandle(Anchor.TopCentre), - createDragHandle(Anchor.BottomCentre), - }); - } - - private void addFullScaleComponents() - { - AddRangeInternal(new[] - { - createDragHandle(Anchor.TopLeft), - createDragHandle(Anchor.TopRight), - createDragHandle(Anchor.BottomLeft), - createDragHandle(Anchor.BottomRight), - }); - } - - private void addXScaleComponents() - { - buttons.Add(new DragHandleButton(FontAwesome.Solid.ArrowsAltH, "Flip horizontally") - { - OperationStarted = operationStarted, - OperationEnded = operationEnded, - Action = () => OnFlip?.Invoke(Direction.Horizontal) - }); - - AddRangeInternal(new[] - { - createDragHandle(Anchor.CentreLeft), - createDragHandle(Anchor.CentreRight), - }); - } - - private DragHandle createDragHandle(Anchor anchor) => - new DragHandle - { - Anchor = anchor, - HandleDrag = e => OnScale?.Invoke(e.Delta, anchor), - OperationStarted = operationStarted, - OperationEnded = operationEnded - }; - - private int activeOperations; - - private void operationEnded() - { - if (--activeOperations == 0) - OperationEnded?.Invoke(); - } - - private void operationStarted() - { - if (activeOperations++ == 0) - OperationStarted?.Invoke(); - } - - private sealed class DragHandleButton : DragHandle, IHasTooltip - { - private SpriteIcon icon; - - private readonly IconUsage iconUsage; - - public Action Action; - - public DragHandleButton(IconUsage iconUsage, string tooltip) - { - this.iconUsage = iconUsage; - - TooltipText = tooltip; - - Anchor = Anchor.Centre; - Origin = Anchor.Centre; - } - - [BackgroundDependencyLoader] - private void load() - { - Size *= 2; - AddInternal(icon = new SpriteIcon - { - RelativeSizeAxes = Axes.Both, - Size = new Vector2(0.5f), - Icon = iconUsage, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - }); - } - - protected override bool OnClick(ClickEvent e) - { - OperationStarted?.Invoke(); - Action?.Invoke(); - OperationEnded?.Invoke(); - return true; - } - - protected override void UpdateHoverState() - { - base.UpdateHoverState(); - icon.Colour = !HandlingMouse && IsHovered ? Color4.White : Color4.Black; - } - - public string TooltipText { get; } - } - - private class DragHandle : Container - { - public Action OperationStarted; - public Action OperationEnded; - - public Action HandleDrag { get; set; } - - private Circle circle; - - [Resolved] - private OsuColour colours { get; set; } - - [BackgroundDependencyLoader] - private void load() - { - Size = new Vector2(10); - Origin = Anchor.Centre; - - InternalChildren = new Drawable[] - { - circle = new Circle - { - RelativeSizeAxes = Axes.Both, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - }, - }; - } - - protected override void LoadComplete() - { - base.LoadComplete(); - UpdateHoverState(); - } - - protected override bool OnHover(HoverEvent e) - { - UpdateHoverState(); - return base.OnHover(e); - } - - protected override void OnHoverLost(HoverLostEvent e) - { - base.OnHoverLost(e); - UpdateHoverState(); - } - - protected bool HandlingMouse; - - protected override bool OnMouseDown(MouseDownEvent e) - { - HandlingMouse = true; - UpdateHoverState(); - return true; - } - - protected override bool OnDragStart(DragStartEvent e) - { - OperationStarted?.Invoke(); - return true; - } - - protected override void OnDrag(DragEvent e) - { - HandleDrag?.Invoke(e); - base.OnDrag(e); - } - - protected override void OnDragEnd(DragEndEvent e) - { - HandlingMouse = false; - OperationEnded?.Invoke(); - - UpdateHoverState(); - base.OnDragEnd(e); - } - - protected override void OnMouseUp(MouseUpEvent e) - { - HandlingMouse = false; - UpdateHoverState(); - base.OnMouseUp(e); - } - - protected virtual void UpdateHoverState() - { - circle.Colour = HandlingMouse ? colours.GrayF : (IsHovered ? colours.Red : colours.YellowDark); - this.ScaleTo(HandlingMouse || IsHovered ? 1.5f : 1, 100, Easing.OutQuint); - } - } - } -} diff --git a/osu.Game/Screens/Edit/Compose/Components/DragBox.cs b/osu.Game/Screens/Edit/Compose/Components/DragBox.cs index 0ec981203a..eaee2cd1e2 100644 --- a/osu.Game/Screens/Edit/Compose/Components/DragBox.cs +++ b/osu.Game/Screens/Edit/Compose/Components/DragBox.cs @@ -45,7 +45,7 @@ namespace osu.Game.Screens.Edit.Compose.Components { Masking = true, BorderColour = Color4.White, - BorderThickness = ComposeSelectionBox.BORDER_RADIUS, + BorderThickness = SelectionBox.BORDER_RADIUS, Child = new Box { RelativeSizeAxes = Axes.Both, diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs new file mode 100644 index 0000000000..ac6a7da361 --- /dev/null +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs @@ -0,0 +1,210 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; +using osu.Game.Graphics; +using osuTK; + +namespace osu.Game.Screens.Edit.Compose.Components +{ + public class SelectionBox : CompositeDrawable + { + public Action OnRotation; + public Action OnScale; + public Action OnFlip; + + public Action OperationStarted; + public Action OperationEnded; + + private bool canRotate; + + /// + /// Whether rotation support should be enabled. + /// + public bool CanRotate + { + get => canRotate; + set + { + canRotate = value; + recreate(); + } + } + + private bool canScaleX; + + /// + /// Whether vertical scale support should be enabled. + /// + public bool CanScaleX + { + get => canScaleX; + set + { + canScaleX = value; + recreate(); + } + } + + private bool canScaleY; + + /// + /// Whether horizontal scale support should be enabled. + /// + public bool CanScaleY + { + get => canScaleY; + set + { + canScaleY = value; + recreate(); + } + } + + private FillFlowContainer buttons; + + public const float BORDER_RADIUS = 3; + + [Resolved] + private OsuColour colours { get; set; } + + [BackgroundDependencyLoader] + private void load() + { + RelativeSizeAxes = Axes.Both; + + recreate(); + } + + private void recreate() + { + if (LoadState < LoadState.Loading) + return; + + InternalChildren = new Drawable[] + { + new Container + { + Masking = true, + BorderThickness = BORDER_RADIUS, + BorderColour = colours.YellowDark, + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + + AlwaysPresent = true, + Alpha = 0 + }, + } + }, + buttons = new FillFlowContainer + { + Y = 20, + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Anchor = Anchor.BottomCentre, + Origin = Anchor.Centre + } + }; + + if (CanScaleX) addXScaleComponents(); + if (CanScaleX && CanScaleY) addFullScaleComponents(); + if (CanScaleY) addYScaleComponents(); + if (CanRotate) addRotationComponents(); + } + + private void addRotationComponents() + { + const float separation = 40; + + addButton(FontAwesome.Solid.Undo, "Rotate 90 degrees counter-clockwise", () => OnRotation?.Invoke(-90)); + addButton(FontAwesome.Solid.Redo, "Rotate 90 degrees clockwise", () => OnRotation?.Invoke(90)); + + AddRangeInternal(new Drawable[] + { + new Box + { + Depth = float.MaxValue, + Colour = colours.YellowLight, + Blending = BlendingParameters.Additive, + Alpha = 0.3f, + Size = new Vector2(BORDER_RADIUS, separation), + Anchor = Anchor.TopCentre, + Origin = Anchor.BottomCentre, + }, + new SelectionBoxDragHandleButton(FontAwesome.Solid.Redo, "Free rotate") + { + Anchor = Anchor.TopCentre, + Y = -separation, + HandleDrag = e => OnRotation?.Invoke(e.Delta.X), + OperationStarted = operationStarted, + OperationEnded = operationEnded + } + }); + } + + private void addYScaleComponents() + { + addButton(FontAwesome.Solid.ArrowsAltV, "Flip vertically", () => OnFlip?.Invoke(Direction.Vertical)); + + addDragHandle(Anchor.TopCentre); + addDragHandle(Anchor.BottomCentre); + } + + private void addFullScaleComponents() + { + addDragHandle(Anchor.TopLeft); + addDragHandle(Anchor.TopRight); + addDragHandle(Anchor.BottomLeft); + addDragHandle(Anchor.BottomRight); + } + + private void addXScaleComponents() + { + addButton(FontAwesome.Solid.ArrowsAltH, "Flip horizontally", () => OnFlip?.Invoke(Direction.Horizontal)); + + addDragHandle(Anchor.CentreLeft); + addDragHandle(Anchor.CentreRight); + } + + private void addButton(IconUsage icon, string tooltip, Action action) + { + buttons.Add(new SelectionBoxDragHandleButton(icon, tooltip) + { + OperationStarted = operationStarted, + OperationEnded = operationEnded, + Action = action + }); + } + + private void addDragHandle(Anchor anchor) => AddInternal(new SelectionBoxDragHandle + { + Anchor = anchor, + HandleDrag = e => OnScale?.Invoke(e.Delta, anchor), + OperationStarted = operationStarted, + OperationEnded = operationEnded + }); + + private int activeOperations; + + private void operationEnded() + { + if (--activeOperations == 0) + OperationEnded?.Invoke(); + } + + private void operationStarted() + { + if (activeOperations++ == 0) + OperationStarted?.Invoke(); + } + } +} diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxDragHandle.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxDragHandle.cs new file mode 100644 index 0000000000..921b4eb042 --- /dev/null +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxDragHandle.cs @@ -0,0 +1,105 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +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; + +namespace osu.Game.Screens.Edit.Compose.Components +{ + public class SelectionBoxDragHandle : Container + { + public Action OperationStarted; + public Action OperationEnded; + + public Action HandleDrag { get; set; } + + private Circle circle; + + [Resolved] + private OsuColour colours { get; set; } + + [BackgroundDependencyLoader] + private void load() + { + Size = new Vector2(10); + Origin = Anchor.Centre; + + InternalChildren = new Drawable[] + { + circle = new Circle + { + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + UpdateHoverState(); + } + + protected override bool OnHover(HoverEvent e) + { + UpdateHoverState(); + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + base.OnHoverLost(e); + UpdateHoverState(); + } + + protected bool HandlingMouse; + + protected override bool OnMouseDown(MouseDownEvent e) + { + HandlingMouse = true; + UpdateHoverState(); + return true; + } + + protected override bool OnDragStart(DragStartEvent e) + { + OperationStarted?.Invoke(); + return true; + } + + protected override void OnDrag(DragEvent e) + { + HandleDrag?.Invoke(e); + base.OnDrag(e); + } + + protected override void OnDragEnd(DragEndEvent e) + { + HandlingMouse = false; + OperationEnded?.Invoke(); + + UpdateHoverState(); + base.OnDragEnd(e); + } + + protected override void OnMouseUp(MouseUpEvent e) + { + HandlingMouse = false; + UpdateHoverState(); + base.OnMouseUp(e); + } + + protected virtual void UpdateHoverState() + { + circle.Colour = HandlingMouse ? colours.GrayF : (IsHovered ? colours.Red : colours.YellowDark); + this.ScaleTo(HandlingMouse || IsHovered ? 1.5f : 1, 100, Easing.OutQuint); + } + } +} diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxDragHandleButton.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxDragHandleButton.cs new file mode 100644 index 0000000000..74ae949389 --- /dev/null +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxDragHandleButton.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 osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Input.Events; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Screens.Edit.Compose.Components +{ + /// + /// A drag "handle" which shares the visual appearance but behaves more like a clickable button. + /// + public sealed class SelectionBoxDragHandleButton : SelectionBoxDragHandle, IHasTooltip + { + private SpriteIcon icon; + + private readonly IconUsage iconUsage; + + public Action Action; + + public SelectionBoxDragHandleButton(IconUsage iconUsage, string tooltip) + { + this.iconUsage = iconUsage; + + TooltipText = tooltip; + + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + } + + [BackgroundDependencyLoader] + private void load() + { + Size *= 2; + AddInternal(icon = new SpriteIcon + { + RelativeSizeAxes = Axes.Both, + Size = new Vector2(0.5f), + Icon = iconUsage, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }); + } + + protected override bool OnClick(ClickEvent e) + { + OperationStarted?.Invoke(); + Action?.Invoke(); + OperationEnded?.Invoke(); + return true; + } + + protected override void UpdateHoverState() + { + base.UpdateHoverState(); + icon.Colour = !HandlingMouse && IsHovered ? Color4.White : Color4.Black; + } + + public string TooltipText { get; } + } +} diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index 1c2f09f831..fdf8dbe44e 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -43,7 +43,7 @@ namespace osu.Game.Screens.Edit.Compose.Components private OsuSpriteText selectionDetailsText; - protected ComposeSelectionBox SelectionBox { get; private set; } + protected SelectionBox SelectionBox { get; private set; } [Resolved(CanBeNull = true)] protected EditorBeatmap EditorBeatmap { get; private set; } @@ -94,8 +94,8 @@ namespace osu.Game.Screens.Edit.Compose.Components }; } - public ComposeSelectionBox CreateSelectionBox() - => new ComposeSelectionBox + public SelectionBox CreateSelectionBox() + => new SelectionBox { OperationStarted = OnOperationBegan, OperationEnded = OnOperationEnded, From 60e6cfa45cbfdd11768610ac6e10eb68497ea834 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 1 Oct 2020 16:36:03 +0900 Subject: [PATCH 1381/1782] Avoid recreating child hierarchy when unnecessary --- osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs index ac6a7da361..64191e48e2 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs @@ -31,6 +31,8 @@ namespace osu.Game.Screens.Edit.Compose.Components get => canRotate; set { + if (canRotate == value) return; + canRotate = value; recreate(); } @@ -46,6 +48,8 @@ namespace osu.Game.Screens.Edit.Compose.Components get => canScaleX; set { + if (canScaleX == value) return; + canScaleX = value; recreate(); } @@ -61,6 +65,8 @@ namespace osu.Game.Screens.Edit.Compose.Components get => canScaleY; set { + if (canScaleY == value) return; + canScaleY = value; recreate(); } From 482c23901b4b9aed6f62be5d2fbe719874e72ff6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 1 Oct 2020 16:54:56 +0900 Subject: [PATCH 1382/1782] Check RequestedPlaying state before allowing scheduled resume of looped sample --- osu.Game/Skinning/PausableSkinnableSound.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game/Skinning/PausableSkinnableSound.cs b/osu.Game/Skinning/PausableSkinnableSound.cs index d080e2ccd9..9819574b1d 100644 --- a/osu.Game/Skinning/PausableSkinnableSound.cs +++ b/osu.Game/Skinning/PausableSkinnableSound.cs @@ -41,8 +41,14 @@ namespace osu.Game.Skinning // it's not easy to know if a sample has finished playing (to end). // to keep things simple only resume playing looping samples. else if (Looping) + { // schedule so we don't start playing a sample which is no longer alive. - Schedule(base.Play); + Schedule(() => + { + if (RequestedPlaying) + base.Play(); + }); + } } }); } From 538973e3942ea8c589e5e98f35890605cb69f5b7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 1 Oct 2020 17:06:05 +0900 Subject: [PATCH 1383/1782] Use float methods for math operations --- osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs index 6b4f13db35..daf4a0102b 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs @@ -183,8 +183,8 @@ namespace osu.Game.Rulesets.Osu.Edit point.Y -= origin.Y; Vector2 ret; - ret.X = (float)(point.X * Math.Cos(MathUtils.DegreesToRadians(angle)) + point.Y * Math.Sin(angle / 180f * Math.PI)); - ret.Y = (float)(point.X * -Math.Sin(MathUtils.DegreesToRadians(angle)) + point.Y * Math.Cos(angle / 180f * Math.PI)); + ret.X = point.X * MathF.Cos(MathUtils.DegreesToRadians(angle)) + point.Y * MathF.Sin(angle / 180f * MathF.PI); + ret.Y = point.X * -MathF.Sin(MathUtils.DegreesToRadians(angle)) + point.Y * MathF.Cos(angle / 180f * MathF.PI); ret.X += origin.X; ret.Y += origin.Y; From b6dc8bb2d3f16fa41934187fe9957865db1d2f20 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 1 Oct 2020 18:10:05 +0900 Subject: [PATCH 1384/1782] Fix remaining manual degree-to-radian conversions --- osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs index daf4a0102b..a0f70ce408 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs @@ -183,8 +183,8 @@ namespace osu.Game.Rulesets.Osu.Edit point.Y -= origin.Y; Vector2 ret; - ret.X = point.X * MathF.Cos(MathUtils.DegreesToRadians(angle)) + point.Y * MathF.Sin(angle / 180f * MathF.PI); - ret.Y = point.X * -MathF.Sin(MathUtils.DegreesToRadians(angle)) + point.Y * MathF.Cos(angle / 180f * MathF.PI); + ret.X = point.X * MathF.Cos(MathUtils.DegreesToRadians(angle)) + point.Y * MathF.Sin(MathUtils.DegreesToRadians(angle)); + ret.Y = point.X * -MathF.Sin(MathUtils.DegreesToRadians(angle)) + point.Y * MathF.Cos(MathUtils.DegreesToRadians(angle)); ret.X += origin.X; ret.Y += origin.Y; From 0d03084cdc03c849e25320913ae2cff97bdda723 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 1 Oct 2020 17:19:35 +0900 Subject: [PATCH 1385/1782] Move control point display to the base timeline class We want them to display on all screens with a timeline as they are quite useful in all cases. --- .../Compose/Components/Timeline/Timeline.cs | 28 ++++++++++++++----- .../Components/Timeline/TimelineArea.cs | 17 ++++++++--- osu.Game/Screens/Edit/Timing/TimingScreen.cs | 6 ---- 3 files changed, 34 insertions(+), 17 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs index ed3d328330..a93ad9ac0d 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs @@ -12,6 +12,7 @@ using osu.Framework.Input.Events; using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Rulesets.Edit; +using osu.Game.Screens.Edit.Components.Timelines.Summary.Parts; using osuTK; namespace osu.Game.Screens.Edit.Compose.Components.Timeline @@ -21,6 +22,9 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline public class Timeline : ZoomableScrollContainer, IPositionSnapProvider { public readonly Bindable WaveformVisible = new Bindable(); + + public readonly Bindable ControlPointsVisible = new Bindable(); + public readonly IBindable Beatmap = new Bindable(); [Resolved] @@ -56,24 +60,34 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline } private WaveformGraph waveform; + private ControlPointPart controlPoints; [BackgroundDependencyLoader] private void load(IBindable beatmap, OsuColour colours) { - Add(waveform = new WaveformGraph + AddRange(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 + }, + controlPoints = new ControlPointPart + { + RelativeSizeAxes = Axes.Both + }, + new TimelineTickDisplay(), }); // We don't want the centre marker to scroll AddInternal(new CentreMarker { Depth = float.MaxValue }); WaveformVisible.ValueChanged += visible => waveform.FadeTo(visible.NewValue ? 1 : 0, 200, Easing.OutQuint); + ControlPointsVisible.ValueChanged += visible => controlPoints.FadeTo(visible.NewValue ? 1 : 0, 200, Easing.OutQuint); Beatmap.BindTo(beatmap); Beatmap.BindValueChanged(b => diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs index d870eb5279..1d2d46517b 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs @@ -25,6 +25,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline CornerRadius = 5; OsuCheckbox waveformCheckbox; + OsuCheckbox controlPointsCheckbox; InternalChildren = new Drawable[] { @@ -57,12 +58,21 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline Origin = Anchor.CentreLeft, AutoSizeAxes = Axes.Y, Width = 160, - Padding = new MarginPadding { Horizontal = 15 }, + Padding = new MarginPadding { Horizontal = 10 }, Direction = FillDirection.Vertical, Spacing = new Vector2(0, 4), Children = new[] { - waveformCheckbox = new OsuCheckbox { LabelText = "Waveform" } + waveformCheckbox = new OsuCheckbox + { + LabelText = "Waveform", + Current = { Value = true }, + }, + controlPointsCheckbox = new OsuCheckbox + { + LabelText = "Control Points", + Current = { Value = true }, + } } } } @@ -119,9 +129,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline } }; - waveformCheckbox.Current.Value = true; - Timeline.WaveformVisible.BindTo(waveformCheckbox.Current); + Timeline.ControlPointsVisible.BindTo(controlPointsCheckbox.Current); } private void changeZoom(float change) => Timeline.Zoom += change; diff --git a/osu.Game/Screens/Edit/Timing/TimingScreen.cs b/osu.Game/Screens/Edit/Timing/TimingScreen.cs index 0a0cfe193d..269874fea8 100644 --- a/osu.Game/Screens/Edit/Timing/TimingScreen.cs +++ b/osu.Game/Screens/Edit/Timing/TimingScreen.cs @@ -12,7 +12,6 @@ using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; -using osu.Game.Screens.Edit.Components.Timelines.Summary.Parts; using osu.Game.Screens.Edit.Compose.Components.Timeline; using osuTK; @@ -31,11 +30,6 @@ namespace osu.Game.Screens.Edit.Timing { } - protected override Drawable CreateTimelineContent() => new ControlPointPart - { - RelativeSizeAxes = Axes.Both, - }; - protected override Drawable CreateMainContent() => new GridContainer { RelativeSizeAxes = Axes.Both, From b654396a4cb89def74d240b795cc73dbf2602534 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 1 Oct 2020 17:40:31 +0900 Subject: [PATCH 1386/1782] Move ticks display to timeline --- osu.Game/Screens/Edit/EditorScreenWithTimeline.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs b/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs index d6d782e70c..b9457f422a 100644 --- a/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs +++ b/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs @@ -112,7 +112,6 @@ namespace osu.Game.Screens.Edit RelativeSizeAxes = Axes.Both, Children = new[] { - new TimelineTickDisplay(), CreateTimelineContent(), } }, t => From 00a19b4879954b5ab4f143f417eb477763f4e5f0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 1 Oct 2020 18:14:10 +0900 Subject: [PATCH 1387/1782] Also add toggle for ticks display --- .../Screens/Edit/Compose/Components/Timeline/Timeline.cs | 6 +++++- .../Edit/Compose/Components/Timeline/TimelineArea.cs | 7 +++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs index a93ad9ac0d..e6b0dd715a 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs @@ -25,6 +25,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline public readonly Bindable ControlPointsVisible = new Bindable(); + public readonly Bindable TicksVisible = new Bindable(); + public readonly IBindable Beatmap = new Bindable(); [Resolved] @@ -61,6 +63,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private WaveformGraph waveform; private ControlPointPart controlPoints; + private TimelineTickDisplay ticks; [BackgroundDependencyLoader] private void load(IBindable beatmap, OsuColour colours) @@ -80,7 +83,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { RelativeSizeAxes = Axes.Both }, - new TimelineTickDisplay(), + ticks = new TimelineTickDisplay(), }); // We don't want the centre marker to scroll @@ -88,6 +91,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline WaveformVisible.ValueChanged += visible => waveform.FadeTo(visible.NewValue ? 1 : 0, 200, Easing.OutQuint); ControlPointsVisible.ValueChanged += visible => controlPoints.FadeTo(visible.NewValue ? 1 : 0, 200, Easing.OutQuint); + TicksVisible.ValueChanged += visible => ticks.FadeTo(visible.NewValue ? 1 : 0, 200, Easing.OutQuint); Beatmap.BindTo(beatmap); Beatmap.BindValueChanged(b => diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs index 1d2d46517b..0ec48e04c6 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs @@ -26,6 +26,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline OsuCheckbox waveformCheckbox; OsuCheckbox controlPointsCheckbox; + OsuCheckbox ticksCheckbox; InternalChildren = new Drawable[] { @@ -72,6 +73,11 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { LabelText = "Control Points", Current = { Value = true }, + }, + ticksCheckbox = new OsuCheckbox + { + LabelText = "Ticks", + Current = { Value = true }, } } } @@ -131,6 +137,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline Timeline.WaveformVisible.BindTo(waveformCheckbox.Current); Timeline.ControlPointsVisible.BindTo(controlPointsCheckbox.Current); + Timeline.TicksVisible.BindTo(ticksCheckbox.Current); } private void changeZoom(float change) => Timeline.Zoom += change; From ffc1e9c35881288b24a2e11b5a405190bb14e860 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 1 Oct 2020 18:23:38 +0900 Subject: [PATCH 1388/1782] 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 afc5d4ec52..78ceaa8616 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,6 +52,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 5fa1685d9b..3a839ac1a4 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -24,7 +24,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 60708a39e2..48c91f0d53 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -80,7 +80,7 @@ - + From 70d475be1fec41d3e565fb38cd1e1f768c8525a4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 1 Oct 2020 18:54:59 +0900 Subject: [PATCH 1389/1782] Fix elements appearing in front of hitobjects --- .../Compose/Components/Timeline/Timeline.cs | 30 ++++++++++++------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs index e6b0dd715a..3e54813a14 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs @@ -8,6 +8,7 @@ using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Audio; +using osu.Framework.Graphics.Containers; using osu.Framework.Input.Events; using osu.Game.Beatmaps; using osu.Game.Graphics; @@ -70,20 +71,27 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { AddRange(new Drawable[] { - waveform = new WaveformGraph + new Container { RelativeSizeAxes = Axes.Both, - Colour = colours.Blue.Opacity(0.2f), - LowColour = colours.BlueLighter, - MidColour = colours.BlueDark, - HighColour = colours.BlueDarker, - Depth = float.MaxValue + Depth = float.MaxValue, + Children = new Drawable[] + { + waveform = new WaveformGraph + { + RelativeSizeAxes = Axes.Both, + Colour = colours.Blue.Opacity(0.2f), + LowColour = colours.BlueLighter, + MidColour = colours.BlueDark, + HighColour = colours.BlueDarker, + }, + controlPoints = new ControlPointPart + { + RelativeSizeAxes = Axes.Both + }, + ticks = new TimelineTickDisplay(), + } }, - controlPoints = new ControlPointPart - { - RelativeSizeAxes = Axes.Both - }, - ticks = new TimelineTickDisplay(), }); // We don't want the centre marker to scroll From 70931abcb0a2b80cfc9aaecbddbc71e6c7ff5b89 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 1 Oct 2020 17:54:54 +0900 Subject: [PATCH 1390/1782] Separate out timeline control point display from summary timeline display --- .../Timeline/DifficultyPointPiece.cs | 66 +++++++++++++++++++ .../Compose/Components/Timeline/Timeline.cs | 10 ++- .../Timeline/TimelineControlPointDisplay.cs | 57 ++++++++++++++++ .../Timeline/TimelineControlPointGroup.cs | 55 ++++++++++++++++ 4 files changed, 182 insertions(+), 6 deletions(-) create mode 100644 osu.Game/Screens/Edit/Compose/Components/Timeline/DifficultyPointPiece.cs create mode 100644 osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineControlPointDisplay.cs create mode 100644 osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineControlPointGroup.cs diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/DifficultyPointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/DifficultyPointPiece.cs new file mode 100644 index 0000000000..4d5970d7e7 --- /dev/null +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/DifficultyPointPiece.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 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.Sprites; +using osuTK.Graphics; + +namespace osu.Game.Screens.Edit.Compose.Components.Timeline +{ + public class DifficultyPointPiece : CompositeDrawable + { + private OsuSpriteText speedMultiplierText; + private readonly BindableNumber speedMultiplier; + + public DifficultyPointPiece(DifficultyControlPoint point) + { + speedMultiplier = point.SpeedMultiplierBindable.GetBoundCopy(); + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + RelativeSizeAxes = Axes.Y; + AutoSizeAxes = Axes.X; + + Color4 colour = colours.GreenDark; + + InternalChildren = new Drawable[] + { + new Box + { + Colour = colour, + Width = 2, + RelativeSizeAxes = Axes.Y, + }, + new Container + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + AutoSizeAxes = Axes.Both, + Children = new Drawable[] + { + new Box + { + Colour = colour, + RelativeSizeAxes = Axes.Both, + }, + speedMultiplierText = new OsuSpriteText + { + Font = OsuFont.Default.With(weight: FontWeight.Bold), + Colour = Color4.White, + } + } + }, + }; + + speedMultiplier.BindValueChanged(multiplier => speedMultiplierText.Text = $"{multiplier.NewValue:n2}x", true); + } + } +} diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs index 3e54813a14..3d2e2ebef7 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs @@ -13,7 +13,6 @@ using osu.Framework.Input.Events; using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Rulesets.Edit; -using osu.Game.Screens.Edit.Components.Timelines.Summary.Parts; using osuTK; namespace osu.Game.Screens.Edit.Compose.Components.Timeline @@ -63,9 +62,11 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline } private WaveformGraph waveform; - private ControlPointPart controlPoints; + private TimelineTickDisplay ticks; + private TimelineControlPointDisplay controlPoints; + [BackgroundDependencyLoader] private void load(IBindable beatmap, OsuColour colours) { @@ -85,10 +86,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline MidColour = colours.BlueDark, HighColour = colours.BlueDarker, }, - controlPoints = new ControlPointPart - { - RelativeSizeAxes = Axes.Both - }, + controlPoints = new TimelineControlPointDisplay(), ticks = new TimelineTickDisplay(), } }, diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineControlPointDisplay.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineControlPointDisplay.cs new file mode 100644 index 0000000000..3f13e8e5d4 --- /dev/null +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineControlPointDisplay.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 System.Collections.Specialized; +using System.Linq; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Screens.Edit.Components.Timelines.Summary.Parts; + +namespace osu.Game.Screens.Edit.Compose.Components.Timeline +{ + /// + /// The part of the timeline that displays the control points. + /// + public class TimelineControlPointDisplay : TimelinePart + { + private IBindableList controlPointGroups; + + public TimelineControlPointDisplay() + { + RelativeSizeAxes = Axes.Both; + } + + protected override void LoadBeatmap(WorkingBeatmap beatmap) + { + base.LoadBeatmap(beatmap); + + controlPointGroups = beatmap.Beatmap.ControlPointInfo.Groups.GetBoundCopy(); + controlPointGroups.BindCollectionChanged((sender, args) => + { + switch (args.Action) + { + case NotifyCollectionChangedAction.Reset: + Clear(); + break; + + case NotifyCollectionChangedAction.Add: + foreach (var group in args.NewItems.OfType()) + Add(new TimelineControlPointGroup(group)); + break; + + case NotifyCollectionChangedAction.Remove: + foreach (var group in args.OldItems.OfType()) + { + var matching = Children.SingleOrDefault(gv => gv.Group == group); + + matching?.Expire(); + } + + break; + } + }, true); + } + } +} diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineControlPointGroup.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineControlPointGroup.cs new file mode 100644 index 0000000000..5429e7c55b --- /dev/null +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineControlPointGroup.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.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Graphics; + +namespace osu.Game.Screens.Edit.Compose.Components.Timeline +{ + public class TimelineControlPointGroup : CompositeDrawable + { + public readonly ControlPointGroup Group; + + private BindableList controlPoints; + + [Resolved] + private OsuColour colours { get; set; } + + public TimelineControlPointGroup(ControlPointGroup group) + { + Origin = Anchor.TopCentre; + + Group = group; + + RelativePositionAxes = Axes.X; + RelativeSizeAxes = Axes.Y; + + Width = 1; + + X = (float)group.Time; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + controlPoints = (BindableList)Group.ControlPoints.GetBoundCopy(); + controlPoints.BindCollectionChanged((_, __) => + { + foreach (var point in controlPoints) + { + switch (point) + { + case DifficultyControlPoint difficultyPoint: + AddInternal(new DifficultyPointPiece(difficultyPoint)); + break; + } + } + }, true); + } + } +} From 0bced34272de9f403bd85c47a5d99ffb844870e3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 1 Oct 2020 18:07:39 +0900 Subject: [PATCH 1391/1782] Add visualisation of bpm (timing) changes to timeline --- .../Timeline/TimelineControlPointGroup.cs | 11 ++-- .../Components/Timeline/TimingPointPiece.cs | 57 +++++++++++++++++++ 2 files changed, 64 insertions(+), 4 deletions(-) create mode 100644 osu.Game/Screens/Edit/Compose/Components/Timeline/TimingPointPiece.cs diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineControlPointGroup.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineControlPointGroup.cs index 5429e7c55b..05a7f6e493 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineControlPointGroup.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineControlPointGroup.cs @@ -21,14 +21,11 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline public TimelineControlPointGroup(ControlPointGroup group) { - Origin = Anchor.TopCentre; - Group = group; RelativePositionAxes = Axes.X; RelativeSizeAxes = Axes.Y; - - Width = 1; + AutoSizeAxes = Axes.X; X = (float)group.Time; } @@ -40,6 +37,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline controlPoints = (BindableList)Group.ControlPoints.GetBoundCopy(); controlPoints.BindCollectionChanged((_, __) => { + ClearInternal(); + foreach (var point in controlPoints) { switch (point) @@ -47,6 +46,10 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline case DifficultyControlPoint difficultyPoint: AddInternal(new DifficultyPointPiece(difficultyPoint)); break; + + case TimingControlPoint timingPoint: + AddInternal(new TimingPointPiece(timingPoint)); + break; } } }, true); diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimingPointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimingPointPiece.cs new file mode 100644 index 0000000000..de7cfecbf0 --- /dev/null +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimingPointPiece.cs @@ -0,0 +1,57 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Colour; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; + +namespace osu.Game.Screens.Edit.Compose.Components.Timeline +{ + public class TimingPointPiece : CompositeDrawable + { + private readonly BindableNumber beatLength; + private OsuSpriteText bpmText; + + public TimingPointPiece(TimingControlPoint point) + { + beatLength = point.BeatLengthBindable.GetBoundCopy(); + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + Origin = Anchor.CentreLeft; + Anchor = Anchor.CentreLeft; + + AutoSizeAxes = Axes.Both; + + InternalChildren = new Drawable[] + { + new Box + { + Alpha = 0.9f, + Colour = ColourInfo.GradientHorizontal(colours.YellowDark, colours.YellowDark.Opacity(0.5f)), + RelativeSizeAxes = Axes.Both, + }, + bpmText = new OsuSpriteText + { + Alpha = 0.9f, + Padding = new MarginPadding(3), + Font = OsuFont.Default.With(size: 40) + } + }; + + beatLength.BindValueChanged(beatLength => + { + bpmText.Text = $"{60000 / beatLength.NewValue:n1} BPM"; + }, true); + } + } +} From b75c202a7e5547b4cf4d12c15bc1537418150ab8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 1 Oct 2020 18:49:48 +0900 Subject: [PATCH 1392/1782] Add sample control point display in timeline --- .../Timeline/DifficultyPointPiece.cs | 2 - .../Components/Timeline/SamplePointPiece.cs | 81 +++++++++++++++++++ .../Timeline/TimelineControlPointGroup.cs | 4 + 3 files changed, 85 insertions(+), 2 deletions(-) create mode 100644 osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/DifficultyPointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/DifficultyPointPiece.cs index 4d5970d7e7..31cc768056 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/DifficultyPointPiece.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/DifficultyPointPiece.cs @@ -41,8 +41,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline }, new Container { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, AutoSizeAxes = Axes.Both, Children = new Drawable[] { diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs new file mode 100644 index 0000000000..67da335f6b --- /dev/null +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.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.Colour; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osuTK.Graphics; + +namespace osu.Game.Screens.Edit.Compose.Components.Timeline +{ + public class SamplePointPiece : CompositeDrawable + { + private readonly Bindable bank; + private readonly BindableNumber volume; + + private OsuSpriteText text; + private Box volumeBox; + + public SamplePointPiece(SampleControlPoint samplePoint) + { + volume = samplePoint.SampleVolumeBindable.GetBoundCopy(); + bank = samplePoint.SampleBankBindable.GetBoundCopy(); + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + Origin = Anchor.TopLeft; + Anchor = Anchor.TopLeft; + + AutoSizeAxes = Axes.X; + RelativeSizeAxes = Axes.Y; + + Color4 colour = colours.BlueDarker; + + InternalChildren = new Drawable[] + { + new Container + { + RelativeSizeAxes = Axes.Y, + Width = 20, + Children = new Drawable[] + { + volumeBox = new Box + { + X = 2, + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Colour = ColourInfo.GradientVertical(colour, Color4.Black), + RelativeSizeAxes = Axes.Both, + }, + new Box + { + Colour = colours.Blue, + Width = 2, + RelativeSizeAxes = Axes.Y, + }, + } + }, + text = new OsuSpriteText + { + X = 2, + Y = -5, + Anchor = Anchor.BottomLeft, + Alpha = 0.9f, + Rotation = -90, + Font = OsuFont.Default.With(weight: FontWeight.SemiBold) + } + }; + + volume.BindValueChanged(volume => volumeBox.Height = volume.NewValue / 100f, true); + bank.BindValueChanged(bank => text.Text = $"{bank.NewValue}", true); + } + } +} diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineControlPointGroup.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineControlPointGroup.cs index 05a7f6e493..1a09a05a6c 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineControlPointGroup.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineControlPointGroup.cs @@ -50,6 +50,10 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline case TimingControlPoint timingPoint: AddInternal(new TimingPointPiece(timingPoint)); break; + + case SampleControlPoint samplePoint: + AddInternal(new SamplePointPiece(samplePoint)); + break; } } }, true); From 589a26a149d11b99c70560b117d79913729d4f59 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 1 Oct 2020 18:59:35 +0900 Subject: [PATCH 1393/1782] Ensure stable display order for control points in the same group --- .../Compose/Components/Timeline/TimelineControlPointGroup.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineControlPointGroup.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineControlPointGroup.cs index 1a09a05a6c..e32616a574 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineControlPointGroup.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineControlPointGroup.cs @@ -44,7 +44,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline switch (point) { case DifficultyControlPoint difficultyPoint: - AddInternal(new DifficultyPointPiece(difficultyPoint)); + AddInternal(new DifficultyPointPiece(difficultyPoint) { Depth = -2 }); break; case TimingControlPoint timingPoint: @@ -52,7 +52,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline break; case SampleControlPoint samplePoint: - AddInternal(new SamplePointPiece(samplePoint)); + AddInternal(new SamplePointPiece(samplePoint) { Depth = -1 }); break; } } From fcccce8b4e2f5466059906b82c4ad1f76ba45df7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 1 Oct 2020 19:03:17 +0900 Subject: [PATCH 1394/1782] Use pink for sample control points to avoid clash with waveform blue --- .../Edit/Compose/Components/Timeline/SamplePointPiece.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs index 67da335f6b..6a6e947343 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs @@ -37,7 +37,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline AutoSizeAxes = Axes.X; RelativeSizeAxes = Axes.Y; - Color4 colour = colours.BlueDarker; + Color4 colour = colours.PinkDarker; InternalChildren = new Drawable[] { @@ -57,7 +57,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline }, new Box { - Colour = colours.Blue, + Colour = colours.Pink, Width = 2, RelativeSizeAxes = Axes.Y, }, From e96e30a19d3bf861ada8cac8d85c59852e63c25f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 1 Oct 2020 19:29:34 +0900 Subject: [PATCH 1395/1782] Move control point colour specifications to common location and use for formatting timing screen table --- osu.Game/Beatmaps/ControlPoints/ControlPoint.cs | 4 ++++ .../ControlPoints/DifficultyControlPoint.cs | 4 ++++ .../ControlPoints/EffectControlPoint.cs | 4 ++++ .../ControlPoints/SampleControlPoint.cs | 4 ++++ .../ControlPoints/TimingControlPoint.cs | 4 ++++ .../Components/Timeline/DifficultyPointPiece.cs | 9 ++++++--- .../Components/Timeline/SamplePointPiece.cs | 8 ++++++-- .../Components/Timeline/TimingPointPiece.cs | 8 +++++++- .../Screens/Edit/Timing/ControlPointTable.cs | 17 +++++++++++++---- osu.Game/Screens/Edit/Timing/RowAttribute.cs | 7 +++++-- 10 files changed, 57 insertions(+), 12 deletions(-) diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs index a1822a1163..c6649f6af1 100644 --- a/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs @@ -2,6 +2,8 @@ // See the LICENCE file in the repository root for full licence text. using System; +using osu.Game.Graphics; +using osuTK.Graphics; namespace osu.Game.Beatmaps.ControlPoints { @@ -18,6 +20,8 @@ namespace osu.Game.Beatmaps.ControlPoints public int CompareTo(ControlPoint other) => Time.CompareTo(other.Time); + public virtual Color4 GetRepresentingColour(OsuColour colours) => colours.Yellow; + /// /// Determines whether this results in a meaningful change when placed alongside another. /// diff --git a/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs index 1d38790f87..283bf76572 100644 --- a/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs @@ -2,6 +2,8 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Bindables; +using osu.Game.Graphics; +using osuTK.Graphics; namespace osu.Game.Beatmaps.ControlPoints { @@ -23,6 +25,8 @@ namespace osu.Game.Beatmaps.ControlPoints MaxValue = 10 }; + public override Color4 GetRepresentingColour(OsuColour colours) => colours.GreenDark; + /// /// The speed multiplier at this control point. /// diff --git a/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs index 9e8e3978be..ea28fca170 100644 --- a/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs @@ -2,6 +2,8 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Bindables; +using osu.Game.Graphics; +using osuTK.Graphics; namespace osu.Game.Beatmaps.ControlPoints { @@ -18,6 +20,8 @@ namespace osu.Game.Beatmaps.ControlPoints /// public readonly BindableBool OmitFirstBarLineBindable = new BindableBool(); + public override Color4 GetRepresentingColour(OsuColour colours) => colours.Purple; + /// /// Whether the first bar line of this control point is ignored. /// diff --git a/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs index c052c04ea0..f57ecfb9e3 100644 --- a/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs @@ -3,6 +3,8 @@ using osu.Framework.Bindables; using osu.Game.Audio; +using osu.Game.Graphics; +using osuTK.Graphics; namespace osu.Game.Beatmaps.ControlPoints { @@ -16,6 +18,8 @@ namespace osu.Game.Beatmaps.ControlPoints SampleVolumeBindable = { Disabled = true } }; + public override Color4 GetRepresentingColour(OsuColour colours) => colours.Pink; + /// /// The default sample bank at this control point. /// diff --git a/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs index 9345299c3a..d9378bca4a 100644 --- a/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs @@ -3,6 +3,8 @@ using osu.Framework.Bindables; using osu.Game.Beatmaps.Timing; +using osu.Game.Graphics; +using osuTK.Graphics; namespace osu.Game.Beatmaps.ControlPoints { @@ -18,6 +20,8 @@ namespace osu.Game.Beatmaps.ControlPoints /// private const double default_beat_length = 60000.0 / 60.0; + public override Color4 GetRepresentingColour(OsuColour colours) => colours.YellowDark; + public static readonly TimingControlPoint DEFAULT = new TimingControlPoint { BeatLengthBindable = diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/DifficultyPointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/DifficultyPointPiece.cs index 31cc768056..510ba8c094 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/DifficultyPointPiece.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/DifficultyPointPiece.cs @@ -15,12 +15,15 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { public class DifficultyPointPiece : CompositeDrawable { + private readonly DifficultyControlPoint difficultyPoint; + private OsuSpriteText speedMultiplierText; private readonly BindableNumber speedMultiplier; - public DifficultyPointPiece(DifficultyControlPoint point) + public DifficultyPointPiece(DifficultyControlPoint difficultyPoint) { - speedMultiplier = point.SpeedMultiplierBindable.GetBoundCopy(); + this.difficultyPoint = difficultyPoint; + speedMultiplier = difficultyPoint.SpeedMultiplierBindable.GetBoundCopy(); } [BackgroundDependencyLoader] @@ -29,7 +32,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline RelativeSizeAxes = Axes.Y; AutoSizeAxes = Axes.X; - Color4 colour = colours.GreenDark; + Color4 colour = difficultyPoint.GetRepresentingColour(colours); InternalChildren = new Drawable[] { diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs index 6a6e947343..ffc0e55940 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs @@ -3,6 +3,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; @@ -16,6 +17,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { public class SamplePointPiece : CompositeDrawable { + private readonly SampleControlPoint samplePoint; + private readonly Bindable bank; private readonly BindableNumber volume; @@ -24,6 +27,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline public SamplePointPiece(SampleControlPoint samplePoint) { + this.samplePoint = samplePoint; volume = samplePoint.SampleVolumeBindable.GetBoundCopy(); bank = samplePoint.SampleBankBindable.GetBoundCopy(); } @@ -37,7 +41,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline AutoSizeAxes = Axes.X; RelativeSizeAxes = Axes.Y; - Color4 colour = colours.PinkDarker; + Color4 colour = samplePoint.GetRepresentingColour(colours); InternalChildren = new Drawable[] { @@ -57,7 +61,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline }, new Box { - Colour = colours.Pink, + Colour = colour.Lighten(0.2f), Width = 2, RelativeSizeAxes = Axes.Y, }, diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimingPointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimingPointPiece.cs index de7cfecbf0..ba94916458 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimingPointPiece.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimingPointPiece.cs @@ -11,16 +11,20 @@ using osu.Framework.Graphics.Shapes; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; +using osuTK.Graphics; namespace osu.Game.Screens.Edit.Compose.Components.Timeline { public class TimingPointPiece : CompositeDrawable { + private readonly TimingControlPoint point; + private readonly BindableNumber beatLength; private OsuSpriteText bpmText; public TimingPointPiece(TimingControlPoint point) { + this.point = point; beatLength = point.BeatLengthBindable.GetBoundCopy(); } @@ -32,12 +36,14 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline AutoSizeAxes = Axes.Both; + Color4 colour = point.GetRepresentingColour(colours); + InternalChildren = new Drawable[] { new Box { Alpha = 0.9f, - Colour = ColourInfo.GradientHorizontal(colours.YellowDark, colours.YellowDark.Opacity(0.5f)), + Colour = ColourInfo.GradientHorizontal(colour, colour.Opacity(0.5f)), RelativeSizeAxes = Axes.Both, }, bpmText = new OsuSpriteText diff --git a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs index 87af4546f1..4121e1f7bb 100644 --- a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs +++ b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs @@ -114,7 +114,14 @@ namespace osu.Game.Screens.Edit.Timing controlPoints = group.ControlPoints.GetBoundCopy(); controlPoints.CollectionChanged += (_, __) => createChildren(); + } + [Resolved] + private OsuColour colours { get; set; } + + [BackgroundDependencyLoader] + private void load() + { createChildren(); } @@ -125,20 +132,22 @@ namespace osu.Game.Screens.Edit.Timing private Drawable createAttribute(ControlPoint controlPoint) { + Color4 colour = controlPoint.GetRepresentingColour(colours); + 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}", colour); case DifficultyControlPoint difficulty: - return new RowAttribute("difficulty", () => $"{difficulty.SpeedMultiplier:n2}x"); + return new RowAttribute("difficulty", () => $"{difficulty.SpeedMultiplier:n2}x", colour); case EffectControlPoint effect: - return new RowAttribute("effect", () => $"{(effect.KiaiMode ? "Kiai " : "")}{(effect.OmitFirstBarLine ? "NoBarLine " : "")}"); + return new RowAttribute("effect", () => $"{(effect.KiaiMode ? "Kiai " : "")}{(effect.OmitFirstBarLine ? "NoBarLine " : "")}", colour); case SampleControlPoint sample: - return new RowAttribute("sample", () => $"{sample.SampleBank} {sample.SampleVolume}%"); + return new RowAttribute("sample", () => $"{sample.SampleBank} {sample.SampleVolume}%", colour); } return null; diff --git a/osu.Game/Screens/Edit/Timing/RowAttribute.cs b/osu.Game/Screens/Edit/Timing/RowAttribute.cs index be8f693683..c45995ee83 100644 --- a/osu.Game/Screens/Edit/Timing/RowAttribute.cs +++ b/osu.Game/Screens/Edit/Timing/RowAttribute.cs @@ -9,6 +9,7 @@ using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; +using osuTK.Graphics; namespace osu.Game.Screens.Edit.Timing { @@ -16,11 +17,13 @@ namespace osu.Game.Screens.Edit.Timing { private readonly string header; private readonly Func content; + private readonly Color4 colour; - public RowAttribute(string header, Func content) + public RowAttribute(string header, Func content, Color4 colour) { this.header = header; this.content = content; + this.colour = colour; } [BackgroundDependencyLoader] @@ -40,7 +43,7 @@ namespace osu.Game.Screens.Edit.Timing { new Box { - Colour = colours.Yellow, + Colour = colour, RelativeSizeAxes = Axes.Both, }, new OsuSpriteText From 5ad2944e26e9714a10006ad2879a0840623b46d5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 1 Oct 2020 19:31:41 +0900 Subject: [PATCH 1396/1782] Fix ticks displaying higher than control point info --- osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs index 3d2e2ebef7..be3bca3242 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs @@ -86,8 +86,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline MidColour = colours.BlueDark, HighColour = colours.BlueDarker, }, - controlPoints = new TimelineControlPointDisplay(), ticks = new TimelineTickDisplay(), + controlPoints = new TimelineControlPointDisplay(), } }, }); From 7e5ecd84bc39dabad82b32e926e3a73ceee4ae46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Max=20H=C3=BCbner?= Date: Thu, 1 Oct 2020 12:41:44 +0200 Subject: [PATCH 1397/1782] Add braces to clear up operator precedence --- osu.Game.Rulesets.Osu/Skinning/LegacyOldStyleSpinner.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacyOldStyleSpinner.cs b/osu.Game.Rulesets.Osu/Skinning/LegacyOldStyleSpinner.cs index 63cd48676e..a5f20378fe 100644 --- a/osu.Game.Rulesets.Osu/Skinning/LegacyOldStyleSpinner.cs +++ b/osu.Game.Rulesets.Osu/Skinning/LegacyOldStyleSpinner.cs @@ -33,7 +33,7 @@ namespace osu.Game.Rulesets.Osu.Skinning [BackgroundDependencyLoader] private void load(ISkinSource source, DrawableHitObject drawableObject) { - spinnerBlink = !source.GetConfig(OsuSkinConfiguration.SpinnerNoBlink)?.Value ?? true; + spinnerBlink = !(source.GetConfig(OsuSkinConfiguration.SpinnerNoBlink)?.Value) ?? true; drawableSpinner = (DrawableSpinner)drawableObject; From 3e6af7ce43975aea05208ef52654038a7984360a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 1 Oct 2020 20:09:09 +0900 Subject: [PATCH 1398/1782] Refactor for readability --- osu.Game.Rulesets.Osu/Skinning/LegacyOldStyleSpinner.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacyOldStyleSpinner.cs b/osu.Game.Rulesets.Osu/Skinning/LegacyOldStyleSpinner.cs index a5f20378fe..c952500bbf 100644 --- a/osu.Game.Rulesets.Osu/Skinning/LegacyOldStyleSpinner.cs +++ b/osu.Game.Rulesets.Osu/Skinning/LegacyOldStyleSpinner.cs @@ -33,7 +33,7 @@ namespace osu.Game.Rulesets.Osu.Skinning [BackgroundDependencyLoader] private void load(ISkinSource source, DrawableHitObject drawableObject) { - spinnerBlink = !(source.GetConfig(OsuSkinConfiguration.SpinnerNoBlink)?.Value) ?? true; + spinnerBlink = source.GetConfig(OsuSkinConfiguration.SpinnerNoBlink)?.Value != true; drawableSpinner = (DrawableSpinner)drawableObject; From ba76089219cd9489cf10b160898cafea7414192c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 1 Oct 2020 20:24:32 +0900 Subject: [PATCH 1399/1782] Fix spinner flashing yellow glow before completion --- .../Objects/Drawables/Pieces/DefaultSpinnerDisc.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/DefaultSpinnerDisc.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/DefaultSpinnerDisc.cs index 2862fe49bd..587bd415ee 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/DefaultSpinnerDisc.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/DefaultSpinnerDisc.cs @@ -93,6 +93,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces drawableSpinner.RotationTracker.Complete.BindValueChanged(complete => updateComplete(complete.NewValue, 200)); drawableSpinner.ApplyCustomUpdateState += updateStateTransforms; + + updateStateTransforms(drawableSpinner, drawableSpinner.State.Value); } protected override void Update() @@ -124,6 +126,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces private void updateStateTransforms(DrawableHitObject drawableHitObject, ArmedState state) { + if (!(drawableHitObject is DrawableSpinner)) + return; + centre.ScaleTo(0); mainContainer.ScaleTo(0); From 6d3f4c8699f713ab5146401254647c9aab421f6b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 1 Oct 2020 20:38:47 +0900 Subject: [PATCH 1400/1782] Fix a few more similar cases --- osu.Game.Rulesets.Osu/Skinning/LegacyNewStyleSpinner.cs | 5 +++++ osu.Game.Rulesets.Osu/Skinning/LegacyOldStyleSpinner.cs | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacyNewStyleSpinner.cs b/osu.Game.Rulesets.Osu/Skinning/LegacyNewStyleSpinner.cs index bcb2af8e3e..1dfc9c0772 100644 --- a/osu.Game.Rulesets.Osu/Skinning/LegacyNewStyleSpinner.cs +++ b/osu.Game.Rulesets.Osu/Skinning/LegacyNewStyleSpinner.cs @@ -72,10 +72,15 @@ namespace osu.Game.Rulesets.Osu.Skinning this.FadeOut(); drawableSpinner.ApplyCustomUpdateState += updateStateTransforms; + + updateStateTransforms(drawableSpinner, drawableSpinner.State.Value); } private void updateStateTransforms(DrawableHitObject drawableHitObject, ArmedState state) { + if (!(drawableHitObject is DrawableSpinner)) + return; + var spinner = (Spinner)drawableSpinner.HitObject; using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimeFadeIn / 2, true)) diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacyOldStyleSpinner.cs b/osu.Game.Rulesets.Osu/Skinning/LegacyOldStyleSpinner.cs index a45d91801d..c498179eef 100644 --- a/osu.Game.Rulesets.Osu/Skinning/LegacyOldStyleSpinner.cs +++ b/osu.Game.Rulesets.Osu/Skinning/LegacyOldStyleSpinner.cs @@ -86,10 +86,15 @@ namespace osu.Game.Rulesets.Osu.Skinning this.FadeOut(); drawableSpinner.ApplyCustomUpdateState += updateStateTransforms; + + updateStateTransforms(drawableSpinner, drawableSpinner.State.Value); } private void updateStateTransforms(DrawableHitObject drawableHitObject, ArmedState state) { + if (!(drawableHitObject is DrawableSpinner)) + return; + var spinner = drawableSpinner.HitObject; using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimeFadeIn / 2, true)) From 62b55c4c9cb57eb436c8a3a4447f6f20c691dade Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 1 Oct 2020 20:50:47 +0900 Subject: [PATCH 1401/1782] Use static method, add xmldoc + link to wiki --- osu.Game/Beatmaps/BeatmapDifficultyManager.cs | 33 +++++++++++-------- osu.Game/Beatmaps/BeatmapInfo.cs | 16 +-------- 2 files changed, 21 insertions(+), 28 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapDifficultyManager.cs b/osu.Game/Beatmaps/BeatmapDifficultyManager.cs index 159a229499..945a60fb62 100644 --- a/osu.Game/Beatmaps/BeatmapDifficultyManager.cs +++ b/osu.Game/Beatmaps/BeatmapDifficultyManager.cs @@ -114,6 +114,25 @@ namespace osu.Game.Beatmaps return computeDifficulty(key, beatmapInfo, rulesetInfo); } + /// + /// Retrieves the that describes a star rating. + /// + /// + /// For more information, see: https://osu.ppy.sh/help/wiki/Difficulties + /// + /// The star rating. + /// The that best describes . + public static DifficultyRating GetDifficultyRating(double starRating) + { + if (starRating < 2.0) return DifficultyRating.Easy; + if (starRating < 2.7) return DifficultyRating.Normal; + if (starRating < 4.0) return DifficultyRating.Hard; + if (starRating < 5.3) return DifficultyRating.Insane; + if (starRating < 6.5) return DifficultyRating.Expert; + + return DifficultyRating.ExpertPlus; + } + private CancellationTokenSource trackedUpdateCancellationSource; private readonly List linkedCancellationSources = new List(); @@ -308,18 +327,6 @@ namespace osu.Game.Beatmaps // Todo: Add more members (BeatmapInfo.DifficultyRating? Attributes? Etc...) } - public DifficultyRating DifficultyRating - { - get - { - if (Stars < 2.0) return DifficultyRating.Easy; - if (Stars < 2.7) return DifficultyRating.Normal; - if (Stars < 4.0) return DifficultyRating.Hard; - if (Stars < 5.3) return DifficultyRating.Insane; - if (Stars < 6.5) return DifficultyRating.Expert; - - return DifficultyRating.ExpertPlus; - } - } + public DifficultyRating DifficultyRating => BeatmapDifficultyManager.GetDifficultyRating(Stars); } } diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index c5be5810e9..acab525821 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -135,21 +135,7 @@ namespace osu.Game.Beatmaps public List Scores { get; set; } [JsonIgnore] - 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 DifficultyRating DifficultyRating => BeatmapDifficultyManager.GetDifficultyRating(StarDifficulty); public string[] SearchableTerms => new[] { From d7f9b8045cc99a153342527217246953c146401f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 1 Oct 2020 21:33:54 +0900 Subject: [PATCH 1402/1782] Safeguard againts multiple ApplyResult() invocations --- 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 f159d28eed..abfe7eb58c 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -469,6 +469,9 @@ namespace osu.Game.Rulesets.Objects.Drawables /// The callback that applies changes to the . protected void ApplyResult(Action application) { + if (Result.HasResult) + throw new InvalidOperationException($"Cannot apply result on a hitobject that already has a result."); + application?.Invoke(Result); if (!Result.HasResult) From 40c153e705f2bcb9cbcfa96615fe853edfd62a01 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 1 Oct 2020 21:39:40 +0900 Subject: [PATCH 1403/1782] Use component instead of drawable --- 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 9ffe813187..45327d4514 100644 --- a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs +++ b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs @@ -122,7 +122,7 @@ namespace osu.Game.Beatmaps.Drawables public object TooltipContent => shouldShowTooltip ? new DifficultyIconTooltipContent(beatmap, difficultyBindable) : null; - private class DifficultyRetriever : Drawable + private class DifficultyRetriever : Component { public readonly Bindable StarDifficulty = new Bindable(); From 042c39ae1b04f3653cb068acb2089f01213a3aed Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 1 Oct 2020 21:48:45 +0900 Subject: [PATCH 1404/1782] Remove redundant string interpolation --- 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 abfe7eb58c..a2d222d0a8 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -470,7 +470,7 @@ namespace osu.Game.Rulesets.Objects.Drawables protected void ApplyResult(Action application) { if (Result.HasResult) - throw new InvalidOperationException($"Cannot apply result on a hitobject that already has a result."); + throw new InvalidOperationException("Cannot apply result on a hitobject that already has a result."); application?.Invoke(Result); From ab33434a8a80f3c50228e52e7e7f643a3f71e40c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 1 Oct 2020 21:54:43 +0900 Subject: [PATCH 1405/1782] Reword xmldocs to better describe nested events --- 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 f159d28eed..24ee3f629d 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -51,12 +51,12 @@ namespace osu.Game.Rulesets.Objects.Drawables public override bool PropagateNonPositionalInputSubTree => HandleUserInput; /// - /// Invoked when a has been applied by this or a nested . + /// Invoked by this or a nested after a has been applied. /// public event Action OnNewResult; /// - /// Invoked when a is being reverted by this or a nested . + /// Invoked by this or a nested prior to a being reverted. /// public event Action OnRevertResult; @@ -236,7 +236,7 @@ namespace osu.Game.Rulesets.Objects.Drawables #region State / Transform Management /// - /// Bind to apply a custom state which can override the default implementation. + /// Invoked by this or a nested to apply a custom state that can override the default implementation. /// public event Action ApplyCustomUpdateState; From c72dbf1ba0e84ff3d19c2eb780d292ad1c4d56cf Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Thu, 1 Oct 2020 17:15:49 +0000 Subject: [PATCH 1406/1782] Bump ppy.osu.Framework.NativeLibs from 2020.213.0 to 2020.923.0 Bumps [ppy.osu.Framework.NativeLibs](https://github.com/ppy/osu-framework) from 2020.213.0 to 2020.923.0. - [Release notes](https://github.com/ppy/osu-framework/releases) - [Commits](https://github.com/ppy/osu-framework/compare/2020.213.0...2020.923.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 48c91f0d53..31f1af135d 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -85,6 +85,6 @@ - + From 9e52f9c8582ec697d8ba3658a035747e04336697 Mon Sep 17 00:00:00 2001 From: Endrik Tombak Date: Thu, 1 Oct 2020 23:23:28 +0300 Subject: [PATCH 1407/1782] Consider cursor size in trail interval --- osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs index 9bcb3abc63..546bb3f233 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.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Batches; using osu.Framework.Graphics.OpenGL.Vertices; @@ -15,6 +16,7 @@ using osu.Framework.Input; using osu.Framework.Input.Events; using osu.Framework.Layout; using osu.Framework.Timing; +using osu.Game.Configuration; using osuTK; using osuTK.Graphics; using osuTK.Graphics.ES30; @@ -28,6 +30,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor private readonly TrailPart[] parts = new TrailPart[max_sprites]; private int currentIndex; private IShader shader; + private Bindable cursorSize; private double timeOffset; private float time; @@ -48,9 +51,10 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor } [BackgroundDependencyLoader] - private void load(ShaderManager shaders) + private void load(ShaderManager shaders, OsuConfigManager config) { shader = shaders.Load(@"CursorTrail", FragmentShaderDescriptor.TEXTURE); + cursorSize = config.GetBindable(OsuSetting.GameplayCursorSize); } protected override void LoadComplete() @@ -147,7 +151,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor float distance = diff.Length; Vector2 direction = diff / distance; - float interval = partSize.X / 2.5f; + float interval = partSize.X / 2.5f / cursorSize.Value; for (float d = interval; d < distance; d += interval) { From abf1afd3f125ac970ab1924c7d956629f5477e10 Mon Sep 17 00:00:00 2001 From: Endrik Tombak Date: Thu, 1 Oct 2020 23:27:57 +0300 Subject: [PATCH 1408/1782] Do not decrease density --- 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 546bb3f233..8a1dc9b8cb 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs @@ -151,7 +151,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor float distance = diff.Length; Vector2 direction = diff / distance; - float interval = partSize.X / 2.5f / cursorSize.Value; + float interval = partSize.X / 2.5f / Math.Max(cursorSize.Value, 1); for (float d = interval; d < distance; d += interval) { From fa1903cd03c4e5f4efa2e796379ee041f1772ac8 Mon Sep 17 00:00:00 2001 From: Endrik Tombak Date: Thu, 1 Oct 2020 23:41:24 +0300 Subject: [PATCH 1409/1782] Get bound copy instead --- 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 8a1dc9b8cb..fb8a850223 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs @@ -54,7 +54,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor private void load(ShaderManager shaders, OsuConfigManager config) { shader = shaders.Load(@"CursorTrail", FragmentShaderDescriptor.TEXTURE); - cursorSize = config.GetBindable(OsuSetting.GameplayCursorSize); + cursorSize = config.GetBindable(OsuSetting.GameplayCursorSize).GetBoundCopy(); } protected override void LoadComplete() From 50722cc754f8cfc20042c11e2a27da2ffb5cdd7b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Oct 2020 14:48:56 +0900 Subject: [PATCH 1410/1782] Update slider test scene sliders to fit better --- .../TestSceneSlider.cs | 74 ++++++++++--------- 1 file changed, 39 insertions(+), 35 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs index 6a689a1f80..c79cae2fe5 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs @@ -164,7 +164,7 @@ namespace osu.Game.Rulesets.Osu.Tests { var slider = new Slider { - StartTime = Time.Current + 1000, + StartTime = Time.Current + time_offset, Position = new Vector2(239, 176), Path = new SliderPath(PathType.PerfectCurve, new[] { @@ -185,22 +185,26 @@ namespace osu.Game.Rulesets.Osu.Tests private Drawable testSlowSpeed() => createSlider(speedMultiplier: 0.5); - private Drawable testShortSlowSpeed(int repeats = 0) => createSlider(distance: 100, repeats: repeats, speedMultiplier: 0.5); + private Drawable testShortSlowSpeed(int repeats = 0) => createSlider(distance: max_length / 4, repeats: repeats, speedMultiplier: 0.5); private Drawable testHighSpeed(int repeats = 0) => createSlider(repeats: repeats, speedMultiplier: 15); - private Drawable testShortHighSpeed(int repeats = 0) => createSlider(distance: 100, repeats: repeats, speedMultiplier: 15); + private Drawable testShortHighSpeed(int repeats = 0) => createSlider(distance: max_length / 4, repeats: repeats, speedMultiplier: 15); - private Drawable createSlider(float circleSize = 2, float distance = 400, int repeats = 0, double speedMultiplier = 2, int stackHeight = 0) + private const double time_offset = 1500; + + private const float max_length = 200; + + private Drawable createSlider(float circleSize = 2, float distance = max_length, int repeats = 0, double speedMultiplier = 2, int stackHeight = 0) { var slider = new Slider { - StartTime = Time.Current + 1000, - Position = new Vector2(-(distance / 2), 0), + StartTime = Time.Current + time_offset, + Position = new Vector2(0, -(distance / 2)), Path = new SliderPath(PathType.PerfectCurve, new[] { Vector2.Zero, - new Vector2(distance, 0), + new Vector2(0, distance), }, distance), RepeatCount = repeats, StackHeight = stackHeight @@ -213,14 +217,14 @@ namespace osu.Game.Rulesets.Osu.Tests { var slider = new Slider { - StartTime = Time.Current + 1000, - Position = new Vector2(-200, 0), + StartTime = Time.Current + time_offset, + Position = new Vector2(-max_length / 2, 0), Path = new SliderPath(PathType.PerfectCurve, new[] { Vector2.Zero, - new Vector2(200, 200), - new Vector2(400, 0) - }, 600), + new Vector2(max_length / 2, max_length / 2), + new Vector2(max_length, 0) + }, max_length * 1.5f), RepeatCount = repeats, }; @@ -233,16 +237,16 @@ namespace osu.Game.Rulesets.Osu.Tests { var slider = new Slider { - StartTime = Time.Current + 1000, - Position = new Vector2(-200, 0), + StartTime = Time.Current + time_offset, + Position = new Vector2(-max_length / 2, 0), Path = new SliderPath(PathType.Linear, new[] { Vector2.Zero, - new Vector2(150, 75), - new Vector2(200, 0), - new Vector2(300, -200), - new Vector2(400, 0), - new Vector2(430, 0) + new Vector2(max_length * 0.375f, max_length * 0.18f), + new Vector2(max_length / 2, 0), + new Vector2(max_length * 0.75f, -max_length / 2), + new Vector2(max_length * 0.95f, 0), + new Vector2(max_length, 0) }), RepeatCount = repeats, }; @@ -256,15 +260,15 @@ namespace osu.Game.Rulesets.Osu.Tests { var slider = new Slider { - StartTime = Time.Current + 1000, - Position = new Vector2(-200, 0), + StartTime = Time.Current + time_offset, + Position = new Vector2(-max_length / 2, 0), Path = new SliderPath(PathType.Bezier, new[] { Vector2.Zero, - new Vector2(150, 75), - new Vector2(200, 100), - new Vector2(300, -200), - new Vector2(430, 0) + new Vector2(max_length * 0.375f, max_length * 0.18f), + new Vector2(max_length / 2, max_length / 4), + new Vector2(max_length * 0.75f, -max_length / 2), + new Vector2(max_length, 0) }), RepeatCount = repeats, }; @@ -278,16 +282,16 @@ namespace osu.Game.Rulesets.Osu.Tests { var slider = new Slider { - StartTime = Time.Current + 1000, + StartTime = Time.Current + time_offset, Position = new Vector2(0, 0), Path = new SliderPath(PathType.Linear, new[] { Vector2.Zero, - new Vector2(-200, 0), + new Vector2(-max_length / 2, 0), new Vector2(0, 0), - new Vector2(0, -200), - new Vector2(-200, -200), - new Vector2(0, -200) + new Vector2(0, -max_length / 2), + new Vector2(-max_length / 2, -max_length / 2), + new Vector2(0, -max_length / 2) }), RepeatCount = repeats, }; @@ -305,14 +309,14 @@ namespace osu.Game.Rulesets.Osu.Tests var slider = new Slider { - StartTime = Time.Current + 1000, - Position = new Vector2(-100, 0), + StartTime = Time.Current + time_offset, + Position = new Vector2(-max_length / 4, 0), Path = new SliderPath(PathType.Catmull, new[] { Vector2.Zero, - new Vector2(50, -50), - new Vector2(150, 50), - new Vector2(200, 0) + new Vector2(max_length * 0.125f, max_length * 0.125f), + new Vector2(max_length * 0.375f, max_length * 0.125f), + new Vector2(max_length / 2, 0) }), RepeatCount = repeats, NodeSamples = repeatSamples From 78bf58f4f8c469f4dce04f7b3db7c31174a04cd3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Oct 2020 13:38:13 +0900 Subject: [PATCH 1411/1782] Add metrics skin elements for sliderendcircle --- .../metrics-skin/sliderendcircle@2x.png | Bin 0 -> 18105 bytes .../metrics-skin/sliderendcircleoverlay@2x.png | Bin 0 -> 45734 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/metrics-skin/sliderendcircle@2x.png create mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/metrics-skin/sliderendcircleoverlay@2x.png diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/metrics-skin/sliderendcircle@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/metrics-skin/sliderendcircle@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..c6c3771593701a825f5cf61c8e05be66bd30699f GIT binary patch literal 18105 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 literal 0 HcmV?d00001 From fce3eacd7de3254ce75619efaa2d15d59d564623 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Oct 2020 13:38:48 +0900 Subject: [PATCH 1412/1782] Move tail circle to display beneath ticks etc. --- 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 9abcef83c4..e77bca1e20 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -52,6 +52,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables InternalChildren = new Drawable[] { Body = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SliderBody), _ => new DefaultSliderBody(), confineMode: ConfineMode.NoScaling), + tailContainer = new Container { RelativeSizeAxes = Axes.Both }, tickContainer = new Container { RelativeSizeAxes = Axes.Both }, repeatContainer = new Container { RelativeSizeAxes = Axes.Both }, Ball = new SliderBall(s, this) @@ -63,7 +64,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables Alpha = 0 }, headContainer = new Container { RelativeSizeAxes = Axes.Both }, - tailContainer = new Container { RelativeSizeAxes = Axes.Both }, }; } From fc7f3173e19aa8d47ebba85490425eb9e434407c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Oct 2020 13:40:24 +0900 Subject: [PATCH 1413/1782] Add the ability to use LegacyMainCirclePiece with no combo number displayed --- .../Skinning/LegacyMainCirclePiece.cs | 38 ++++++++++++------- 1 file changed, 24 insertions(+), 14 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs b/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs index d15a0a3203..f051cbfa3b 100644 --- a/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs +++ b/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs @@ -21,10 +21,12 @@ namespace osu.Game.Rulesets.Osu.Skinning public class LegacyMainCirclePiece : CompositeDrawable { private readonly string priorityLookup; + private readonly bool hasNumber; - public LegacyMainCirclePiece(string priorityLookup = null) + public LegacyMainCirclePiece(string priorityLookup = null, bool hasNumber = true) { this.priorityLookup = priorityLookup; + this.hasNumber = hasNumber; Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2); } @@ -70,7 +72,11 @@ namespace osu.Game.Rulesets.Osu.Skinning } } }, - hitCircleText = new SkinnableSpriteText(new OsuSkinComponent(OsuSkinComponents.HitCircleText), _ => new OsuSpriteText + }; + + if (hasNumber) + { + AddInternal(hitCircleText = new SkinnableSpriteText(new OsuSkinComponent(OsuSkinComponents.HitCircleText), _ => new OsuSpriteText { Font = OsuFont.Numeric.With(size: 40), UseFullGlyphHeight = false, @@ -78,8 +84,8 @@ namespace osu.Game.Rulesets.Osu.Skinning { Anchor = Anchor.Centre, Origin = Anchor.Centre, - }, - }; + }); + } bool overlayAboveNumber = skin.GetConfig(OsuSkinConfiguration.HitCircleOverlayAboveNumber)?.Value ?? true; @@ -107,7 +113,8 @@ namespace osu.Game.Rulesets.Osu.Skinning state.BindValueChanged(updateState, true); accentColour.BindValueChanged(colour => hitCircleSprite.Colour = LegacyColourCompatibility.DisallowZeroAlpha(colour.NewValue), true); - indexInCurrentCombo.BindValueChanged(index => hitCircleText.Text = (index.NewValue + 1).ToString(), true); + if (hasNumber) + indexInCurrentCombo.BindValueChanged(index => hitCircleText.Text = (index.NewValue + 1).ToString(), true); } private void updateState(ValueChangedEvent state) @@ -120,16 +127,19 @@ namespace osu.Game.Rulesets.Osu.Skinning circleSprites.FadeOut(legacy_fade_duration, Easing.Out); circleSprites.ScaleTo(1.4f, legacy_fade_duration, Easing.Out); - var legacyVersion = skin.GetConfig(LegacySetting.Version)?.Value; - - if (legacyVersion >= 2.0m) - // legacy skins of version 2.0 and newer only apply very short fade out to the number piece. - hitCircleText.FadeOut(legacy_fade_duration / 4, Easing.Out); - else + if (hasNumber) { - // old skins scale and fade it normally along other pieces. - hitCircleText.FadeOut(legacy_fade_duration, Easing.Out); - hitCircleText.ScaleTo(1.4f, legacy_fade_duration, Easing.Out); + var legacyVersion = skin.GetConfig(LegacySetting.Version)?.Value; + + if (legacyVersion >= 2.0m) + // legacy skins of version 2.0 and newer only apply very short fade out to the number piece. + hitCircleText.FadeOut(legacy_fade_duration / 4, Easing.Out); + else + { + // old skins scale and fade it normally along other pieces. + hitCircleText.FadeOut(legacy_fade_duration, Easing.Out); + hitCircleText.ScaleTo(1.4f, legacy_fade_duration, Easing.Out); + } } break; From 5d2a8ec7640fff9ff189f9adca1e9e5c381d29c8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Oct 2020 13:41:22 +0900 Subject: [PATCH 1414/1782] Add final sliderendcircle display support --- .../Objects/Drawables/DrawableSliderRepeat.cs | 25 ++++-- .../Objects/Drawables/DrawableSliderTail.cs | 79 ++++++++++++++++--- .../{SliderCircle.cs => SliderEndCircle.cs} | 2 +- osu.Game.Rulesets.Osu/OsuSkinComponents.cs | 1 + .../Skinning/OsuLegacySkinTransformer.cs | 6 ++ 5 files changed, 94 insertions(+), 19 deletions(-) rename osu.Game.Rulesets.Osu/Objects/{SliderCircle.cs => SliderEndCircle.cs} (82%) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs index f65077685f..9d775de7df 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs @@ -6,9 +6,11 @@ using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Utils; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces; +using osu.Game.Skinning; using osuTK; namespace osu.Game.Rulesets.Osu.Objects.Drawables @@ -34,7 +36,18 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables Origin = Anchor.Centre; - InternalChild = scaleContainer = new ReverseArrowPiece(); + InternalChild = scaleContainer = new Container + { + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Children = new Drawable[] + { + // no default for this; only visible in legacy skins. + new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SliderTailHitCircle), _ => Empty()), + arrow = new ReverseArrowPiece(), + } + }; } private readonly IBindable scaleBindable = new BindableFloat(); @@ -85,6 +98,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables private bool hasRotation; + private readonly ReverseArrowPiece arrow; + public void UpdateSnakingPosition(Vector2 start, Vector2 end) { // When the repeat is hit, the arrow should fade out on spot rather than following the slider @@ -114,18 +129,18 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables } float aimRotation = MathUtils.RadiansToDegrees(MathF.Atan2(aimRotationVector.Y - Position.Y, aimRotationVector.X - Position.X)); - while (Math.Abs(aimRotation - Rotation) > 180) - aimRotation += aimRotation < Rotation ? 360 : -360; + while (Math.Abs(aimRotation - arrow.Rotation) > 180) + aimRotation += aimRotation < arrow.Rotation ? 360 : -360; if (!hasRotation) { - Rotation = aimRotation; + arrow.Rotation = aimRotation; hasRotation = true; } else { // If we're already snaking, interpolate to smooth out sharp curves (linear sliders, mainly). - Rotation = Interpolation.ValueAt(Math.Clamp(Clock.ElapsedFrameTime, 0, 100), Rotation, aimRotation, 0, 50, Easing.OutQuint); + arrow.Rotation = Interpolation.ValueAt(Math.Clamp(Clock.ElapsedFrameTime, 0, 100), arrow.Rotation, aimRotation, 0, 50, Easing.OutQuint); } } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs index 0939e2847a..3751ff0975 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs @@ -1,13 +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.Diagnostics; +using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Skinning; using osuTK; namespace osu.Game.Rulesets.Osu.Objects.Drawables { - public class DrawableSliderTail : DrawableOsuHitObject, IRequireTracking + public class DrawableSliderTail : DrawableOsuHitObject, IRequireTracking, ITrackSnaking { private readonly Slider slider; @@ -18,28 +23,73 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables public bool Tracking { get; set; } - private readonly IBindable positionBindable = new Bindable(); - private readonly IBindable pathVersion = new Bindable(); + private readonly IBindable scaleBindable = new BindableFloat(); + + private readonly SkinnableDrawable circlePiece; + + private readonly Container scaleContainer; public DrawableSliderTail(Slider slider, SliderTailCircle hitCircle) : base(hitCircle) { this.slider = slider; - Origin = Anchor.Centre; - RelativeSizeAxes = Axes.Both; - FillMode = FillMode.Fit; + Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2); - AlwaysPresent = true; + InternalChildren = new Drawable[] + { + scaleContainer = new Container + { + RelativeSizeAxes = Axes.Both, + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + Children = new Drawable[] + { + // no default for this; only visible in legacy skins. + circlePiece = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SliderTailHitCircle), _ => Empty()) + } + }, + }; + } - positionBindable.BindTo(hitCircle.PositionBindable); - pathVersion.BindTo(slider.Path.Version); + [BackgroundDependencyLoader] + private void load() + { + scaleBindable.BindValueChanged(scale => scaleContainer.Scale = new Vector2(scale.NewValue), true); + scaleBindable.BindTo(HitObject.ScaleBindable); + } - positionBindable.BindValueChanged(_ => updatePosition()); - pathVersion.BindValueChanged(_ => updatePosition(), true); + protected override void UpdateInitialTransforms() + { + base.UpdateInitialTransforms(); - // TODO: This has no drawable content. Support for skins should be added. + circlePiece.FadeInFromZero(HitObject.TimeFadeIn); + } + + protected override void UpdateStateTransforms(ArmedState state) + { + base.UpdateStateTransforms(state); + + Debug.Assert(HitObject.HitWindows != null); + + switch (state) + { + case ArmedState.Idle: + this.Delay(HitObject.TimePreempt).FadeOut(500); + + Expire(true); + break; + + case ArmedState.Miss: + this.FadeOut(100); + break; + + case ArmedState.Hit: + // todo: temporary / arbitrary + this.Delay(800).FadeOut(); + break; + } } protected override void CheckForResult(bool userTriggered, double timeOffset) @@ -48,6 +98,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables ApplyResult(r => r.Type = Tracking ? r.Judgement.MaxResult : r.Judgement.MinResult); } - private void updatePosition() => Position = HitObject.Position - slider.Position; + public void UpdateSnakingPosition(Vector2 start, Vector2 end) + { + Position = end; + } } } diff --git a/osu.Game.Rulesets.Osu/Objects/SliderCircle.cs b/osu.Game.Rulesets.Osu/Objects/SliderEndCircle.cs similarity index 82% rename from osu.Game.Rulesets.Osu/Objects/SliderCircle.cs rename to osu.Game.Rulesets.Osu/Objects/SliderEndCircle.cs index 151902a752..d9ae520f5c 100644 --- a/osu.Game.Rulesets.Osu/Objects/SliderCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/SliderEndCircle.cs @@ -3,7 +3,7 @@ namespace osu.Game.Rulesets.Osu.Objects { - public class SliderCircle : HitCircle + public class SliderEndCircle : HitCircle { } } diff --git a/osu.Game.Rulesets.Osu/OsuSkinComponents.cs b/osu.Game.Rulesets.Osu/OsuSkinComponents.cs index 5468764692..2883f0c187 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, SliderHeadHitCircle, + SliderTailHitCircle, SliderFollowCircle, SliderBall, SliderBody, diff --git a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs index 851a8d56c9..78bc26eff7 100644 --- a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs @@ -66,6 +66,12 @@ namespace osu.Game.Rulesets.Osu.Skinning return null; + case OsuSkinComponents.SliderTailHitCircle: + if (hasHitCircle.Value) + return new LegacyMainCirclePiece("sliderendcircle", false); + + return null; + case OsuSkinComponents.SliderHeadHitCircle: if (hasHitCircle.Value) return new LegacyMainCirclePiece("sliderstartcircle"); From 2427ae43da2284d31e5c2b26662f6df93c0739ca Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Oct 2020 14:20:55 +0900 Subject: [PATCH 1415/1782] Share fade in logic with repeats --- .../Objects/Drawables/DrawableSliderTail.cs | 14 +++++----- osu.Game.Rulesets.Osu/Objects/Slider.cs | 4 ++- .../Objects/SliderEndCircle.cs | 27 ++++++++++++++++++- osu.Game.Rulesets.Osu/Objects/SliderRepeat.cs | 23 +--------------- .../Objects/SliderTailCircle.cs | 13 +-------- 5 files changed, 37 insertions(+), 44 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs index 3751ff0975..f5bcecccdf 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs @@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { public class DrawableSliderTail : DrawableOsuHitObject, IRequireTracking, ITrackSnaking { - private readonly Slider slider; + private readonly SliderTailCircle tailCircle; ///

/// The judgement text is provided by the . @@ -29,10 +29,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables private readonly Container scaleContainer; - public DrawableSliderTail(Slider slider, SliderTailCircle hitCircle) - : base(hitCircle) + public DrawableSliderTail(Slider slider, SliderTailCircle tailCircle) + : base(tailCircle) { - this.slider = slider; + this.tailCircle = tailCircle; Origin = Anchor.Centre; Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2); @@ -98,9 +98,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables ApplyResult(r => r.Type = Tracking ? r.Judgement.MaxResult : r.Judgement.MinResult); } - public void UpdateSnakingPosition(Vector2 start, Vector2 end) - { - Position = end; - } + public void UpdateSnakingPosition(Vector2 start, Vector2 end) => + Position = tailCircle.RepeatIndex % 2 == 0 ? end : start; } } diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index 51f6a44a87..9cc3f17c55 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -174,8 +174,10 @@ namespace osu.Game.Rulesets.Osu.Objects // we need to use the LegacyLastTick here for compatibility reasons (difficulty). // it is *okay* to use this because the TailCircle is not used for any meaningful purpose in gameplay. // if this is to change, we should revisit this. - AddNested(TailCircle = new SliderTailCircle(this) + AddNested(TailCircle = new SliderTailCircle { + RepeatIndex = e.SpanIndex, + SpanDuration = SpanDuration, StartTime = e.Time, Position = EndPosition, StackHeight = StackHeight diff --git a/osu.Game.Rulesets.Osu/Objects/SliderEndCircle.cs b/osu.Game.Rulesets.Osu/Objects/SliderEndCircle.cs index d9ae520f5c..a34eec0c79 100644 --- a/osu.Game.Rulesets.Osu/Objects/SliderEndCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/SliderEndCircle.cs @@ -1,9 +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 osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Scoring; + namespace osu.Game.Rulesets.Osu.Objects { - public class SliderEndCircle : HitCircle + /// + /// A hitcircle which is at the end of a slider path (either repeat or final tail). + /// + public abstract class SliderEndCircle : HitCircle { + public int RepeatIndex { get; set; } + public double SpanDuration { get; set; } + + protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) + { + base.ApplyDefaultsToSelf(controlPointInfo, difficulty); + + // Out preempt should be one span early to give the user ample warning. + TimePreempt += SpanDuration; + + // We want to show the first RepeatPoint as the TimePreempt dictates but on short (and possibly fast) sliders + // we may need to cut down this time on following RepeatPoints to only show up to two RepeatPoints at any given time. + if (RepeatIndex > 0) + TimePreempt = Math.Min(SpanDuration * 2, TimePreempt); + } + + protected override HitWindows CreateHitWindows() => HitWindows.Empty; } } diff --git a/osu.Game.Rulesets.Osu/Objects/SliderRepeat.cs b/osu.Game.Rulesets.Osu/Objects/SliderRepeat.cs index b6c58a75d1..6bf0ec0355 100644 --- a/osu.Game.Rulesets.Osu/Objects/SliderRepeat.cs +++ b/osu.Game.Rulesets.Osu/Objects/SliderRepeat.cs @@ -1,35 +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.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 { - public class SliderRepeat : OsuHitObject + public class SliderRepeat : SliderEndCircle { - public int RepeatIndex { get; set; } - public double SpanDuration { get; set; } - - protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) - { - base.ApplyDefaultsToSelf(controlPointInfo, difficulty); - - // Out preempt should be one span early to give the user ample warning. - TimePreempt += SpanDuration; - - // We want to show the first RepeatPoint as the TimePreempt dictates but on short (and possibly fast) sliders - // we may need to cut down this time on following RepeatPoints to only show up to two RepeatPoints at any given time. - if (RepeatIndex > 0) - TimePreempt = Math.Min(SpanDuration * 2, TimePreempt); - } - - protected override HitWindows CreateHitWindows() => HitWindows.Empty; - public override Judgement CreateJudgement() => new SliderRepeatJudgement(); public class SliderRepeatJudgement : OsuJudgement diff --git a/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs b/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs index aff3f38e17..2f1bfdfcc0 100644 --- a/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.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.Bindables; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Judgements; @@ -13,18 +12,8 @@ namespace osu.Game.Rulesets.Osu.Objects /// Note that this should not be used for timing correctness. /// See usage in for more information. /// - public class SliderTailCircle : SliderCircle + public class SliderTailCircle : SliderEndCircle { - private readonly IBindable pathVersion = new Bindable(); - - public SliderTailCircle(Slider slider) - { - pathVersion.BindTo(slider.Path.Version); - pathVersion.BindValueChanged(_ => Position = slider.EndPosition); - } - - protected override HitWindows CreateHitWindows() => HitWindows.Empty; - public override Judgement CreateJudgement() => new SliderTailJudgement(); public class SliderTailJudgement : OsuJudgement From 2975ea9210a7e329a2ae35dfb7f0ef57a283fd74 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Oct 2020 14:37:07 +0900 Subject: [PATCH 1416/1782] Adjust repeat/tail fade in to match stable closer --- osu.Game.Rulesets.Osu/Objects/SliderEndCircle.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/SliderEndCircle.cs b/osu.Game.Rulesets.Osu/Objects/SliderEndCircle.cs index a34eec0c79..e0bbac67fc 100644 --- a/osu.Game.Rulesets.Osu/Objects/SliderEndCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/SliderEndCircle.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.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Scoring; @@ -20,13 +19,14 @@ namespace osu.Game.Rulesets.Osu.Objects { base.ApplyDefaultsToSelf(controlPointInfo, difficulty); - // Out preempt should be one span early to give the user ample warning. - TimePreempt += SpanDuration; - - // We want to show the first RepeatPoint as the TimePreempt dictates but on short (and possibly fast) sliders - // we may need to cut down this time on following RepeatPoints to only show up to two RepeatPoints at any given time. if (RepeatIndex > 0) - TimePreempt = Math.Min(SpanDuration * 2, TimePreempt); + { + // Repeat points after the first span should appear behind the still-visible one. + TimeFadeIn = 0; + + // The next end circle should appear exactly after the previous circle (on the same end) is hit. + TimePreempt = SpanDuration * 2; + } } protected override HitWindows CreateHitWindows() => HitWindows.Empty; From ad4cac13acccaa6a30b81470afa0a4a74f5a166c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Oct 2020 15:21:52 +0900 Subject: [PATCH 1417/1782] Add preempt adjustment and fade in first end circle with slider to match stable --- osu.Game.Rulesets.Osu/Objects/Slider.cs | 6 ++---- .../Objects/SliderEndCircle.cs | 20 +++++++++++++++++-- osu.Game.Rulesets.Osu/Objects/SliderRepeat.cs | 5 +++++ .../Objects/SliderTailCircle.cs | 5 +++++ 4 files changed, 30 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index 9cc3f17c55..917382eccf 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -174,10 +174,9 @@ namespace osu.Game.Rulesets.Osu.Objects // we need to use the LegacyLastTick here for compatibility reasons (difficulty). // it is *okay* to use this because the TailCircle is not used for any meaningful purpose in gameplay. // if this is to change, we should revisit this. - AddNested(TailCircle = new SliderTailCircle + AddNested(TailCircle = new SliderTailCircle(this) { RepeatIndex = e.SpanIndex, - SpanDuration = SpanDuration, StartTime = e.Time, Position = EndPosition, StackHeight = StackHeight @@ -185,10 +184,9 @@ namespace osu.Game.Rulesets.Osu.Objects break; case SliderEventType.Repeat: - AddNested(new SliderRepeat + AddNested(new SliderRepeat(this) { RepeatIndex = e.SpanIndex, - SpanDuration = SpanDuration, StartTime = StartTime + (e.SpanIndex + 1) * SpanDuration, Position = Position + Path.PositionAt(e.PathProgress), StackHeight = StackHeight, diff --git a/osu.Game.Rulesets.Osu/Objects/SliderEndCircle.cs b/osu.Game.Rulesets.Osu/Objects/SliderEndCircle.cs index e0bbac67fc..a6aed2c00e 100644 --- a/osu.Game.Rulesets.Osu/Objects/SliderEndCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/SliderEndCircle.cs @@ -8,12 +8,20 @@ using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Osu.Objects { /// - /// A hitcircle which is at the end of a slider path (either repeat or final tail). + /// A hit circle which is at the end of a slider path (either repeat or final tail). /// public abstract class SliderEndCircle : HitCircle { + private readonly Slider slider; + + protected SliderEndCircle(Slider slider) + { + this.slider = slider; + } + public int RepeatIndex { get; set; } - public double SpanDuration { get; set; } + + public double SpanDuration => slider.SpanDuration; protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) { @@ -27,6 +35,14 @@ namespace osu.Game.Rulesets.Osu.Objects // The next end circle should appear exactly after the previous circle (on the same end) is hit. TimePreempt = SpanDuration * 2; } + else + { + // taken from osu-stable + const float first_end_circle_preempt_adjust = 2 / 3f; + + // The first end circle should fade in with the slider. + TimePreempt = (StartTime - slider.StartTime) + slider.TimePreempt * first_end_circle_preempt_adjust; + } } protected override HitWindows CreateHitWindows() => HitWindows.Empty; diff --git a/osu.Game.Rulesets.Osu/Objects/SliderRepeat.cs b/osu.Game.Rulesets.Osu/Objects/SliderRepeat.cs index 6bf0ec0355..cca86361c2 100644 --- a/osu.Game.Rulesets.Osu/Objects/SliderRepeat.cs +++ b/osu.Game.Rulesets.Osu/Objects/SliderRepeat.cs @@ -9,6 +9,11 @@ namespace osu.Game.Rulesets.Osu.Objects { public class SliderRepeat : SliderEndCircle { + public SliderRepeat(Slider slider) + : base(slider) + { + } + public override Judgement CreateJudgement() => new SliderRepeatJudgement(); public class SliderRepeatJudgement : OsuJudgement diff --git a/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs b/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs index 2f1bfdfcc0..5aa2940e10 100644 --- a/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs @@ -14,6 +14,11 @@ namespace osu.Game.Rulesets.Osu.Objects ///
public class SliderTailCircle : SliderEndCircle { + public SliderTailCircle(Slider slider) + : base(slider) + { + } + public override Judgement CreateJudgement() => new SliderTailJudgement(); public class SliderTailJudgement : OsuJudgement From d6fe5482d30fe8f5197d18145a47ec8a2644dcca Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Oct 2020 15:28:08 +0900 Subject: [PATCH 1418/1782] Add failing test showing missing control point removal --- osu.Game.Tests/NonVisual/ControlPointInfoTest.cs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/osu.Game.Tests/NonVisual/ControlPointInfoTest.cs b/osu.Game.Tests/NonVisual/ControlPointInfoTest.cs index 830e4bc603..90a487c0ac 100644 --- a/osu.Game.Tests/NonVisual/ControlPointInfoTest.cs +++ b/osu.Game.Tests/NonVisual/ControlPointInfoTest.cs @@ -139,6 +139,22 @@ namespace osu.Game.Tests.NonVisual Assert.That(cpi.Groups.Count, Is.EqualTo(0)); } + [Test] + public void TestRemoveGroupAlsoRemovedControlPoints() + { + var cpi = new ControlPointInfo(); + + var group = cpi.GroupAt(1000, true); + + group.Add(new SampleControlPoint()); + + Assert.That(cpi.SamplePoints.Count, Is.EqualTo(1)); + + cpi.RemoveGroup(group); + + Assert.That(cpi.SamplePoints.Count, Is.EqualTo(0)); + } + [Test] public void TestAddControlPointToGroup() { From f501c88b46f09b6bbcda0ddcc520151a882bdc64 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Oct 2020 15:25:35 +0900 Subject: [PATCH 1419/1782] Fix individual control points not being removed from group when group is removed --- osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs index e7788b75f3..22314f28c7 100644 --- a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs +++ b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs @@ -158,6 +158,9 @@ namespace osu.Game.Beatmaps.ControlPoints public void RemoveGroup(ControlPointGroup group) { + foreach (var item in group.ControlPoints.ToArray()) + group.Remove(item); + group.ItemAdded -= groupItemAdded; group.ItemRemoved -= groupItemRemoved; From 959c8730f6c673d1f4ac3970ca43426b4ac97e13 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Oct 2020 15:25:48 +0900 Subject: [PATCH 1420/1782] Add settings section from TimingPointGroups on timing screen --- .../Edit/Timing/ControlPointSettings.cs | 1 + osu.Game/Screens/Edit/Timing/GroupSection.cs | 96 +++++++++++++++++++ 2 files changed, 97 insertions(+) create mode 100644 osu.Game/Screens/Edit/Timing/GroupSection.cs diff --git a/osu.Game/Screens/Edit/Timing/ControlPointSettings.cs b/osu.Game/Screens/Edit/Timing/ControlPointSettings.cs index e1182d9fa4..c40061b97c 100644 --- a/osu.Game/Screens/Edit/Timing/ControlPointSettings.cs +++ b/osu.Game/Screens/Edit/Timing/ControlPointSettings.cs @@ -41,6 +41,7 @@ namespace osu.Game.Screens.Edit.Timing private IReadOnlyList createSections() => new Drawable[] { + new GroupSection(), new TimingSection(), new DifficultySection(), new SampleSection(), diff --git a/osu.Game/Screens/Edit/Timing/GroupSection.cs b/osu.Game/Screens/Edit/Timing/GroupSection.cs new file mode 100644 index 0000000000..2c3c393e3c --- /dev/null +++ b/osu.Game/Screens/Edit/Timing/GroupSection.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 System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Graphics.UserInterface; +using osu.Game.Graphics.UserInterfaceV2; +using osuTK; + +namespace osu.Game.Screens.Edit.Timing +{ + internal class GroupSection : CompositeDrawable + { + private LabelledTextBox textBox; + + [Resolved] + protected Bindable SelectedGroup { get; private set; } + + [Resolved] + protected IBindable Beatmap { get; private set; } + + [Resolved] + private EditorClock clock { get; set; } + + [BackgroundDependencyLoader] + private void load() + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + + Padding = new MarginPadding(10); + + InternalChildren = new Drawable[] + { + new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Spacing = new Vector2(10), + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + textBox = new LabelledTextBox + { + Label = "Time" + }, + new TriangleButton + { + Text = "Use current time", + RelativeSizeAxes = Axes.X, + Action = () => changeSelectedGroupTime(clock.CurrentTime) + } + } + }, + }; + + textBox.OnCommit += (sender, isNew) => + { + if (double.TryParse(sender.Text, out var newTime)) + { + changeSelectedGroupTime(newTime); + } + }; + + SelectedGroup.BindValueChanged(group => + { + if (group.NewValue == null) + { + textBox.Text = string.Empty; + textBox.Current.Disabled = true; + return; + } + + textBox.Current.Disabled = false; + textBox.Text = $"{group.NewValue.Time:n0}"; + }, true); + } + + private void changeSelectedGroupTime(in double time) + { + var currentGroupItems = SelectedGroup.Value.ControlPoints.ToArray(); + + Beatmap.Value.Beatmap.ControlPointInfo.RemoveGroup(SelectedGroup.Value); + + foreach (var cp in currentGroupItems) + Beatmap.Value.Beatmap.ControlPointInfo.Add(time, cp); + + SelectedGroup.Value = Beatmap.Value.Beatmap.ControlPointInfo.GroupAt(time); + } + } +} From 2698dc513f31d4d2a0a0f75b1615c50cdc106800 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Oct 2020 15:33:33 +0900 Subject: [PATCH 1421/1782] Add basic textbox error handling --- osu.Game/Screens/Edit/Timing/GroupSection.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/osu.Game/Screens/Edit/Timing/GroupSection.cs b/osu.Game/Screens/Edit/Timing/GroupSection.cs index 2c3c393e3c..ac9c4be97a 100644 --- a/osu.Game/Screens/Edit/Timing/GroupSection.cs +++ b/osu.Game/Screens/Edit/Timing/GroupSection.cs @@ -61,10 +61,17 @@ namespace osu.Game.Screens.Edit.Timing textBox.OnCommit += (sender, isNew) => { + if (!isNew) + return; + if (double.TryParse(sender.Text, out var newTime)) { changeSelectedGroupTime(newTime); } + else + { + SelectedGroup.TriggerChange(); + } }; SelectedGroup.BindValueChanged(group => From 0cb3926e1d090b7c336b16a5512f31f696d18661 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Oct 2020 15:44:32 +0900 Subject: [PATCH 1422/1782] Add event on EditorChangeHandler state change --- .../Editing/EditorChangeHandlerTest.cs | 22 ++++++++++++++++++- osu.Game/Screens/Edit/EditorChangeHandler.cs | 5 +++++ osu.Game/Screens/Edit/IEditorChangeHandler.cs | 6 +++++ 3 files changed, 32 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Editing/EditorChangeHandlerTest.cs b/osu.Game.Tests/Editing/EditorChangeHandlerTest.cs index ff2c9fb1a9..b7a41ffd1c 100644 --- a/osu.Game.Tests/Editing/EditorChangeHandlerTest.cs +++ b/osu.Game.Tests/Editing/EditorChangeHandlerTest.cs @@ -12,6 +12,14 @@ namespace osu.Game.Tests.Editing [TestFixture] public class EditorChangeHandlerTest { + private int stateChangedFired; + + [SetUp] + public void SetUp() + { + stateChangedFired = 0; + } + [Test] public void TestSaveRestoreState() { @@ -23,6 +31,8 @@ namespace osu.Game.Tests.Editing addArbitraryChange(beatmap); handler.SaveState(); + Assert.That(stateChangedFired, Is.EqualTo(1)); + Assert.That(handler.CanUndo.Value, Is.True); Assert.That(handler.CanRedo.Value, Is.False); @@ -30,6 +40,8 @@ namespace osu.Game.Tests.Editing Assert.That(handler.CanUndo.Value, Is.False); Assert.That(handler.CanRedo.Value, Is.True); + + Assert.That(stateChangedFired, Is.EqualTo(2)); } [Test] @@ -45,6 +57,7 @@ namespace osu.Game.Tests.Editing Assert.That(handler.CanUndo.Value, Is.True); Assert.That(handler.CanRedo.Value, Is.False); + Assert.That(stateChangedFired, Is.EqualTo(1)); string hash = handler.CurrentStateHash; @@ -52,6 +65,7 @@ namespace osu.Game.Tests.Editing handler.SaveState(); Assert.That(hash, Is.EqualTo(handler.CurrentStateHash)); + Assert.That(stateChangedFired, Is.EqualTo(1)); handler.RestoreState(-1); @@ -60,6 +74,7 @@ namespace osu.Game.Tests.Editing // we should only be able to restore once even though we saved twice. Assert.That(handler.CanUndo.Value, Is.False); Assert.That(handler.CanRedo.Value, Is.True); + Assert.That(stateChangedFired, Is.EqualTo(2)); } [Test] @@ -71,6 +86,8 @@ namespace osu.Game.Tests.Editing for (int i = 0; i < EditorChangeHandler.MAX_SAVED_STATES; i++) { + Assert.That(stateChangedFired, Is.EqualTo(i)); + addArbitraryChange(beatmap); handler.SaveState(); } @@ -114,7 +131,10 @@ namespace osu.Game.Tests.Editing { var beatmap = new EditorBeatmap(new Beatmap()); - return (new EditorChangeHandler(beatmap), beatmap); + var changeHandler = new EditorChangeHandler(beatmap); + + changeHandler.OnStateChange += () => stateChangedFired++; + return (changeHandler, beatmap); } private void addArbitraryChange(EditorBeatmap beatmap) diff --git a/osu.Game/Screens/Edit/EditorChangeHandler.cs b/osu.Game/Screens/Edit/EditorChangeHandler.cs index 617c436ee0..616d0608c0 100644 --- a/osu.Game/Screens/Edit/EditorChangeHandler.cs +++ b/osu.Game/Screens/Edit/EditorChangeHandler.cs @@ -21,6 +21,8 @@ namespace osu.Game.Screens.Edit public readonly Bindable CanUndo = new Bindable(); public readonly Bindable CanRedo = new Bindable(); + public event Action OnStateChange; + private readonly LegacyEditorBeatmapPatcher patcher; private readonly List savedStates = new List(); @@ -109,6 +111,8 @@ namespace osu.Game.Screens.Edit savedStates.Add(newState); currentState = savedStates.Count - 1; + + OnStateChange?.Invoke(); updateBindables(); } } @@ -136,6 +140,7 @@ namespace osu.Game.Screens.Edit isRestoring = false; + OnStateChange?.Invoke(); updateBindables(); } diff --git a/osu.Game/Screens/Edit/IEditorChangeHandler.cs b/osu.Game/Screens/Edit/IEditorChangeHandler.cs index c1328252d4..a23a956e14 100644 --- a/osu.Game/Screens/Edit/IEditorChangeHandler.cs +++ b/osu.Game/Screens/Edit/IEditorChangeHandler.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using osu.Game.Rulesets.Objects; namespace osu.Game.Screens.Edit @@ -10,6 +11,11 @@ namespace osu.Game.Screens.Edit ///
public interface IEditorChangeHandler { + /// + /// Fired whenever a state change occurs. + /// + public event Action OnStateChange; + /// /// Begins a bulk state change event. should be invoked soon after. /// From 501e02db097eab45553f0376caf420457b6cbb2d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Oct 2020 15:44:37 +0900 Subject: [PATCH 1423/1782] Only regenerate autoplay on editor state change --- osu.Game/Rulesets/Edit/DrawableEditRulesetWrapper.cs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/osu.Game/Rulesets/Edit/DrawableEditRulesetWrapper.cs b/osu.Game/Rulesets/Edit/DrawableEditRulesetWrapper.cs index 1070b8cbd2..d259a89055 100644 --- a/osu.Game/Rulesets/Edit/DrawableEditRulesetWrapper.cs +++ b/osu.Game/Rulesets/Edit/DrawableEditRulesetWrapper.cs @@ -40,17 +40,21 @@ namespace osu.Game.Rulesets.Edit Playfield.DisplayJudgements.Value = false; } + [Resolved] + private IEditorChangeHandler changeHandler { get; set; } + protected override void LoadComplete() { base.LoadComplete(); beatmap.HitObjectAdded += addHitObject; - beatmap.HitObjectUpdated += updateReplay; beatmap.HitObjectRemoved += removeHitObject; + + // for now only regenerate replay on a finalised state change, not HitObjectUpdated. + changeHandler.OnStateChange += updateReplay; } - private void updateReplay(HitObject obj = null) => - drawableRuleset.RegenerateAutoplay(); + private void updateReplay() => drawableRuleset.RegenerateAutoplay(); private void addHitObject(HitObject hitObject) { @@ -69,7 +73,7 @@ namespace osu.Game.Rulesets.Edit drawableRuleset.Playfield.Remove(drawableObject); drawableRuleset.Playfield.PostProcess(); - drawableRuleset.RegenerateAutoplay(); + updateReplay(); } public override bool PropagatePositionalInputSubTree => false; From dde7f706aafae01330c084719124c504b1abc0ef Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Oct 2020 01:48:41 +0900 Subject: [PATCH 1424/1782] Avoid rapid triangle repositioning during editor slider placement --- .../Objects/Drawables/Pieces/CirclePiece.cs | 5 +++-- .../Drawables/Pieces/TrianglesPiece.cs | 3 ++- osu.Game/Graphics/Backgrounds/Triangles.cs | 21 ++++++++++++++----- 3 files changed, 21 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/CirclePiece.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/CirclePiece.cs index aab01f45d4..e95cdc7ee3 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/CirclePiece.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/CirclePiece.cs @@ -6,6 +6,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; +using osu.Game.Rulesets.Objects.Drawables; using osuTK; namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces @@ -25,7 +26,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces } [BackgroundDependencyLoader] - private void load(TextureStore textures) + private void load(TextureStore textures, DrawableHitObject drawableHitObject) { InternalChildren = new Drawable[] { @@ -35,7 +36,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces Origin = Anchor.Centre, Texture = textures.Get(@"Gameplay/osu/disc"), }, - new TrianglesPiece + new TrianglesPiece((int)drawableHitObject.HitObject.StartTime) { RelativeSizeAxes = Axes.Both, Blending = BlendingParameters.Additive, diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/TrianglesPiece.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/TrianglesPiece.cs index 0e29a1dcd8..6cdb0d3df3 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/TrianglesPiece.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/TrianglesPiece.cs @@ -11,7 +11,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces protected override bool CreateNewTriangles => false; protected override float SpawnRatio => 0.5f; - public TrianglesPiece() + public TrianglesPiece(int? seed = null) + : base(seed) { TriangleScale = 1.2f; HideAlphaDiscrepancies = false; diff --git a/osu.Game/Graphics/Backgrounds/Triangles.cs b/osu.Game/Graphics/Backgrounds/Triangles.cs index 27027202ce..5b0fa44444 100644 --- a/osu.Game/Graphics/Backgrounds/Triangles.cs +++ b/osu.Game/Graphics/Backgrounds/Triangles.cs @@ -86,13 +86,24 @@ namespace osu.Game.Graphics.Backgrounds ///
public float Velocity = 1; + private readonly Random stableRandom; + + private float nextRandom() => (float)(stableRandom?.NextDouble() ?? RNG.NextSingle()); + private readonly SortedList parts = new SortedList(Comparer.Default); private IShader shader; private readonly Texture texture; - public Triangles() + /// + /// Construct a new triangle visualisation. + /// + /// An optional seed to stabilise random positions / attributes. Note that this does not guarantee stable playback when seeking in time. + public Triangles(int? seed = null) { + if (seed != null) + stableRandom = new Random(seed.Value); + texture = Texture.WhitePixel; } @@ -175,8 +186,8 @@ namespace osu.Game.Graphics.Backgrounds { TriangleParticle particle = CreateTriangle(); - particle.Position = new Vector2(RNG.NextSingle(), randomY ? RNG.NextSingle() : 1); - particle.ColourShade = RNG.NextSingle(); + particle.Position = new Vector2(nextRandom(), randomY ? nextRandom() : 1); + particle.ColourShade = nextRandom(); particle.Colour = CreateTriangleShade(particle.ColourShade); return particle; @@ -191,8 +202,8 @@ namespace osu.Game.Graphics.Backgrounds const float std_dev = 0.16f; const float mean = 0.5f; - float u1 = 1 - RNG.NextSingle(); //uniform(0,1] random floats - float u2 = 1 - RNG.NextSingle(); + float u1 = 1 - nextRandom(); //uniform(0,1] random floats + float u2 = 1 - nextRandom(); float randStdNormal = (float)(Math.Sqrt(-2.0 * Math.Log(u1)) * Math.Sin(2.0 * Math.PI * u2)); // random normal(0,1) var scale = Math.Max(triangleScale * (mean + std_dev * randStdNormal), 0.1f); // random normal(mean,stdDev^2) From e49ec092c9a35f2aa414102153829aa4ea221402 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Oct 2020 16:08:11 +0900 Subject: [PATCH 1425/1782] Expose ability to register a component as an import handler --- osu.Game/OsuGameBase.cs | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index b1269e9300..11c1f6c5cf 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -232,9 +232,9 @@ namespace osu.Game dependencies.Cache(new SessionStatics()); dependencies.Cache(new OsuColour()); - fileImporters.Add(BeatmapManager); - fileImporters.Add(ScoreManager); - fileImporters.Add(SkinManager); + RegisterImportHandler(BeatmapManager); + RegisterImportHandler(ScoreManager); + RegisterImportHandler(SkinManager); // tracks play so loud our samples can't keep up. // this adds a global reduction of track volume for the time being. @@ -341,7 +341,19 @@ namespace osu.Game protected override Storage CreateStorage(GameHost host, Storage defaultStorage) => new OsuStorage(host, defaultStorage); - private readonly List fileImporters = new List(); + private readonly HashSet fileImporters = new HashSet(); + + /// + /// Register a global handler for file imports. + /// + /// The handler to register. + public void RegisterImportHandler(ICanAcceptFiles handler) => fileImporters.Add(handler); + + /// + /// Unregister a global handler for file imports. + /// + /// The previously registered handler. + public void UnregisterImportHandler(ICanAcceptFiles handler) => fileImporters.Remove(handler); public async Task Import(params string[] paths) { From fc65cb43759477e96d48337513a1e9565b10082d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Oct 2020 16:14:21 +0900 Subject: [PATCH 1426/1782] Ensure precedence is given to newer registered handlers --- osu.Game/OsuGameBase.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 11c1f6c5cf..dfda0d0118 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -341,13 +341,13 @@ namespace osu.Game protected override Storage CreateStorage(GameHost host, Storage defaultStorage) => new OsuStorage(host, defaultStorage); - private readonly HashSet fileImporters = new HashSet(); + private readonly List fileImporters = new List(); /// - /// Register a global handler for file imports. + /// Register a global handler for file imports. Most recently registered will have precedence. /// /// The handler to register. - public void RegisterImportHandler(ICanAcceptFiles handler) => fileImporters.Add(handler); + public void RegisterImportHandler(ICanAcceptFiles handler) => fileImporters.Insert(0, handler); /// /// Unregister a global handler for file imports. From f3c8cd91f4e87c461b2cee362a84fd096f838d05 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Oct 2020 16:14:27 +0900 Subject: [PATCH 1427/1782] Remove unused method --- osu.Game/Screens/Edit/EditorScreen.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/osu.Game/Screens/Edit/EditorScreen.cs b/osu.Game/Screens/Edit/EditorScreen.cs index 52bffc4342..4d62a7d3cd 100644 --- a/osu.Game/Screens/Edit/EditorScreen.cs +++ b/osu.Game/Screens/Edit/EditorScreen.cs @@ -44,10 +44,5 @@ namespace osu.Game.Screens.Edit .Then() .FadeTo(1f, 250, Easing.OutQuint); } - - public void Exit() - { - Expire(); - } } } From 50eca202f48a08bbeb0ac5c8867a81507a4a2881 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Oct 2020 16:17:10 +0900 Subject: [PATCH 1428/1782] User IEnumerable for HandledExtensions --- osu.Game/Beatmaps/BeatmapManager.cs | 2 +- osu.Game/Database/ArchiveModelManager.cs | 2 +- osu.Game/Database/ICanAcceptFiles.cs | 3 ++- osu.Game/OsuGameBase.cs | 2 +- osu.Game/Scoring/ScoreManager.cs | 2 +- osu.Game/Skinning/SkinManager.cs | 2 +- 6 files changed, 7 insertions(+), 6 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index b48ab6112e..4c75069f08 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -57,7 +57,7 @@ namespace osu.Game.Beatmaps /// public readonly WorkingBeatmap DefaultBeatmap; - public override string[] HandledExtensions => new[] { ".osz" }; + public override IEnumerable HandledExtensions => new[] { ".osz" }; protected override string[] HashableFileTypes => new[] { ".osu" }; diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index bbe2604216..3292936f5f 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -70,7 +70,7 @@ namespace osu.Game.Database private readonly Bindable> itemRemoved = new Bindable>(); - public virtual string[] HandledExtensions => new[] { ".zip" }; + public virtual IEnumerable HandledExtensions => new[] { ".zip" }; public virtual bool SupportsImportFromStable => RuntimeInfo.IsDesktop; diff --git a/osu.Game/Database/ICanAcceptFiles.cs b/osu.Game/Database/ICanAcceptFiles.cs index b9f882468d..e4d92d957c 100644 --- a/osu.Game/Database/ICanAcceptFiles.cs +++ b/osu.Game/Database/ICanAcceptFiles.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.Threading.Tasks; namespace osu.Game.Database @@ -19,6 +20,6 @@ namespace osu.Game.Database /// /// An array of accepted file extensions (in the standard format of ".abc"). /// - string[] HandledExtensions { get; } + IEnumerable HandledExtensions { get; } } } diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index dfda0d0118..f61ff43ca9 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -366,7 +366,7 @@ namespace osu.Game } } - public string[] HandledExtensions => fileImporters.SelectMany(i => i.HandledExtensions).ToArray(); + public IEnumerable HandledExtensions => fileImporters.SelectMany(i => i.HandledExtensions); protected override void Dispose(bool isDisposing) { diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index 8e8147ff39..5a6da53839 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -27,7 +27,7 @@ namespace osu.Game.Scoring { public class ScoreManager : DownloadableArchiveModelManager { - public override string[] HandledExtensions => new[] { ".osr" }; + public override IEnumerable HandledExtensions => new[] { ".osr" }; protected override string[] HashableFileTypes => new[] { ".osr" }; diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index ee4b7bc8e7..7af400e807 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -33,7 +33,7 @@ namespace osu.Game.Skinning public readonly Bindable CurrentSkin = new Bindable(new DefaultSkin()); public readonly Bindable CurrentSkinInfo = new Bindable(SkinInfo.Default) { Default = SkinInfo.Default }; - public override string[] HandledExtensions => new[] { ".osk" }; + public override IEnumerable HandledExtensions => new[] { ".osk" }; protected override string[] HashableFileTypes => new[] { ".ini" }; From fe818a020a52896aa082de261d4dee9d2ec6a37e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 2 Oct 2020 16:17:57 +0900 Subject: [PATCH 1429/1782] Fix spinners not transforming correctly --- .../Drawables/Pieces/DefaultSpinnerDisc.cs | 74 +++++++++++-------- .../Skinning/LegacyNewStyleSpinner.cs | 15 ++-- .../Skinning/LegacyOldStyleSpinner.cs | 5 +- 3 files changed, 57 insertions(+), 37 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/DefaultSpinnerDisc.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/DefaultSpinnerDisc.cs index 587bd415ee..e855317544 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/DefaultSpinnerDisc.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/DefaultSpinnerDisc.cs @@ -20,6 +20,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces private Spinner spinner; + private const float initial_scale = 1.3f; private const float idle_alpha = 0.2f; private const float tracking_alpha = 0.4f; @@ -41,7 +42,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces // we are slightly bigger than our parent, to clip the top and bottom of the circle // this should probably be revisited when scaled spinners are a thing. - Scale = new Vector2(1.3f); + Scale = new Vector2(initial_scale); Anchor = Anchor.Centre; Origin = Anchor.Centre; @@ -117,8 +118,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces fill.Alpha = (float)Interpolation.Damp(fill.Alpha, drawableSpinner.RotationTracker.Tracking ? tracking_alpha : idle_alpha, 0.98f, (float)Math.Abs(Clock.ElapsedFrameTime)); } - const float initial_scale = 0.2f; - float targetScale = initial_scale + (1 - initial_scale) * drawableSpinner.Progress; + const float initial_fill_scale = 0.2f; + float targetScale = initial_fill_scale + (1 - initial_fill_scale) * drawableSpinner.Progress; fill.Scale = new Vector2((float)Interpolation.Lerp(fill.Scale.X, targetScale, Math.Clamp(Math.Abs(Time.Elapsed) / 100, 0, 1))); mainContainer.Rotation = drawableSpinner.RotationTracker.Rotation; @@ -129,41 +130,54 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces if (!(drawableHitObject is DrawableSpinner)) return; - centre.ScaleTo(0); - mainContainer.ScaleTo(0); - - using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimePreempt / 2, true)) + using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimePreempt, true)) { - // constant ambient rotation to give the spinner "spinning" character. - this.RotateTo((float)(25 * spinner.Duration / 2000), spinner.TimePreempt + spinner.Duration); - - centre.ScaleTo(0.3f, spinner.TimePreempt / 4, Easing.OutQuint); - mainContainer.ScaleTo(0.2f, spinner.TimePreempt / 4, Easing.OutQuint); + this.ScaleTo(initial_scale); + this.RotateTo(0); using (BeginDelayedSequence(spinner.TimePreempt / 2, true)) { - centre.ScaleTo(0.5f, spinner.TimePreempt / 2, Easing.OutQuint); - mainContainer.ScaleTo(1, spinner.TimePreempt / 2, Easing.OutQuint); + // constant ambient rotation to give the spinner "spinning" character. + this.RotateTo((float)(25 * spinner.Duration / 2000), spinner.TimePreempt + spinner.Duration); + } + + using (BeginDelayedSequence(spinner.TimePreempt + spinner.Duration + drawableHitObject.Result.TimeOffset, true)) + { + switch (state) + { + case ArmedState.Hit: + this.ScaleTo(initial_scale * 1.2f, 320, Easing.Out); + this.RotateTo(mainContainer.Rotation + 180, 320); + break; + + case ArmedState.Miss: + this.ScaleTo(initial_scale * 0.8f, 320, Easing.In); + break; + } + } + } + + using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimePreempt, true)) + { + centre.ScaleTo(0); + mainContainer.ScaleTo(0); + + using (BeginDelayedSequence(spinner.TimePreempt / 2, true)) + { + centre.ScaleTo(0.3f, spinner.TimePreempt / 4, Easing.OutQuint); + mainContainer.ScaleTo(0.2f, spinner.TimePreempt / 4, Easing.OutQuint); + + using (BeginDelayedSequence(spinner.TimePreempt / 2, true)) + { + centre.ScaleTo(0.5f, spinner.TimePreempt / 2, Easing.OutQuint); + mainContainer.ScaleTo(1, spinner.TimePreempt / 2, Easing.OutQuint); + } } } // transforms we have from completing the spinner will be rolled back, so reapply immediately. - updateComplete(state == ArmedState.Hit, 0); - - using (BeginDelayedSequence(spinner.Duration, true)) - { - switch (state) - { - case ArmedState.Hit: - this.ScaleTo(Scale * 1.2f, 320, Easing.Out); - this.RotateTo(mainContainer.Rotation + 180, 320); - break; - - case ArmedState.Miss: - this.ScaleTo(Scale * 0.8f, 320, Easing.In); - break; - } - } + using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimePreempt, true)) + updateComplete(state == ArmedState.Hit, 0); } private void updateComplete(bool complete, double duration) diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacyNewStyleSpinner.cs b/osu.Game.Rulesets.Osu/Skinning/LegacyNewStyleSpinner.cs index 1dfc9c0772..56b5571ce1 100644 --- a/osu.Game.Rulesets.Osu/Skinning/LegacyNewStyleSpinner.cs +++ b/osu.Game.Rulesets.Osu/Skinning/LegacyNewStyleSpinner.cs @@ -70,9 +70,7 @@ namespace osu.Game.Rulesets.Osu.Skinning { base.LoadComplete(); - this.FadeOut(); drawableSpinner.ApplyCustomUpdateState += updateStateTransforms; - updateStateTransforms(drawableSpinner, drawableSpinner.State.Value); } @@ -83,12 +81,19 @@ namespace osu.Game.Rulesets.Osu.Skinning var spinner = (Spinner)drawableSpinner.HitObject; + using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimePreempt, true)) + this.FadeOut(); + using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimeFadeIn / 2, true)) this.FadeInFromZero(spinner.TimeFadeIn / 2); - fixedMiddle.FadeColour(Color4.White); - using (BeginAbsoluteSequence(spinner.StartTime, true)) - fixedMiddle.FadeColour(Color4.Red, spinner.Duration); + using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimePreempt, true)) + { + fixedMiddle.FadeColour(Color4.White); + + using (BeginDelayedSequence(spinner.TimePreempt, true)) + fixedMiddle.FadeColour(Color4.Red, spinner.Duration); + } } protected override void Update() diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacyOldStyleSpinner.cs b/osu.Game.Rulesets.Osu/Skinning/LegacyOldStyleSpinner.cs index eba9abda0b..7b0d7acbbc 100644 --- a/osu.Game.Rulesets.Osu/Skinning/LegacyOldStyleSpinner.cs +++ b/osu.Game.Rulesets.Osu/Skinning/LegacyOldStyleSpinner.cs @@ -88,9 +88,7 @@ namespace osu.Game.Rulesets.Osu.Skinning { base.LoadComplete(); - this.FadeOut(); drawableSpinner.ApplyCustomUpdateState += updateStateTransforms; - updateStateTransforms(drawableSpinner, drawableSpinner.State.Value); } @@ -101,6 +99,9 @@ namespace osu.Game.Rulesets.Osu.Skinning var spinner = drawableSpinner.HitObject; + using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimePreempt, true)) + this.FadeOut(); + using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimeFadeIn / 2, true)) this.FadeInFromZero(spinner.TimeFadeIn / 2); } From b7c276093db90227293a4fc8505e3d3aaa46f5cc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Oct 2020 16:21:50 +0900 Subject: [PATCH 1430/1782] Add fallback case when EditorChangeHandler is not present (for tests) --- .../Rulesets/Edit/DrawableEditRulesetWrapper.cs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/osu.Game/Rulesets/Edit/DrawableEditRulesetWrapper.cs b/osu.Game/Rulesets/Edit/DrawableEditRulesetWrapper.cs index d259a89055..43e5153f24 100644 --- a/osu.Game/Rulesets/Edit/DrawableEditRulesetWrapper.cs +++ b/osu.Game/Rulesets/Edit/DrawableEditRulesetWrapper.cs @@ -40,7 +40,7 @@ namespace osu.Game.Rulesets.Edit Playfield.DisplayJudgements.Value = false; } - [Resolved] + [Resolved(canBeNull: true)] private IEditorChangeHandler changeHandler { get; set; } protected override void LoadComplete() @@ -50,8 +50,15 @@ namespace osu.Game.Rulesets.Edit beatmap.HitObjectAdded += addHitObject; beatmap.HitObjectRemoved += removeHitObject; - // for now only regenerate replay on a finalised state change, not HitObjectUpdated. - changeHandler.OnStateChange += updateReplay; + if (changeHandler != null) + { + // for now only regenerate replay on a finalised state change, not HitObjectUpdated. + changeHandler.OnStateChange += updateReplay; + } + else + { + beatmap.HitObjectUpdated += _ => updateReplay(); + } } private void updateReplay() => drawableRuleset.RegenerateAutoplay(); From b7aba194411ea28ab6de45246edce05e62964ac1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Oct 2020 16:31:11 +0900 Subject: [PATCH 1431/1782] Add audio file drag-drop support at editor setup screen --- osu.Game/Screens/Edit/Setup/SetupScreen.cs | 44 +++++++++++++++++++++- 1 file changed, 42 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Setup/SetupScreen.cs b/osu.Game/Screens/Edit/Setup/SetupScreen.cs index f6eb92e1ec..7bb4e8bbc4 100644 --- a/osu.Game/Screens/Edit/Setup/SetupScreen.cs +++ b/osu.Game/Screens/Edit/Setup/SetupScreen.cs @@ -2,8 +2,10 @@ // 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.Bindables; using osu.Framework.Graphics; @@ -13,6 +15,7 @@ using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; +using osu.Game.Database; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; @@ -23,8 +26,14 @@ using osuTK; namespace osu.Game.Screens.Edit.Setup { - public class SetupScreen : EditorScreen + public class SetupScreen : EditorScreen, ICanAcceptFiles { + public IEnumerable HandledExtensions => ImageExtensions.Concat(AudioExtensions); + + public static string[] ImageExtensions { get; } = { ".jpg", ".jpeg", ".png" }; + + public static string[] AudioExtensions { get; } = { ".mp3", ".ogg" }; + private FillFlowContainer flow; private LabelledTextBox artistTextBox; private LabelledTextBox titleTextBox; @@ -32,6 +41,9 @@ namespace osu.Game.Screens.Edit.Setup private LabelledTextBox difficultyTextBox; private LabelledTextBox audioTrackTextBox; + [Resolved] + private OsuGameBase game { get; set; } + [Resolved] private MusicController music { get; set; } @@ -150,6 +162,12 @@ namespace osu.Game.Screens.Edit.Setup item.OnCommit += onCommit; } + protected override void LoadComplete() + { + base.LoadComplete(); + game.RegisterImportHandler(this); + } + public bool ChangeAudioTrack(string path) { var info = new FileInfo(path); @@ -196,6 +214,28 @@ namespace osu.Game.Screens.Edit.Setup Beatmap.Value.Metadata.AuthorString = creatorTextBox.Current.Value; Beatmap.Value.BeatmapInfo.Version = difficultyTextBox.Current.Value; } + + public Task Import(params string[] paths) + { + var firstFile = new FileInfo(paths.First()); + + if (ImageExtensions.Contains(firstFile.Extension)) + { + // todo: add image drag drop support + } + else if (AudioExtensions.Contains(firstFile.Extension)) + { + audioTrackTextBox.Text = firstFile.FullName; + } + + return Task.CompletedTask; + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + game.UnregisterImportHandler(this); + } } internal class FileChooserLabelledTextBox : LabelledTextBox @@ -230,7 +270,7 @@ namespace osu.Game.Screens.Edit.Setup public void DisplayFileChooser() { - Target.Child = new FileSelector(validFileExtensions: new[] { ".mp3", ".ogg" }) + Target.Child = new FileSelector(validFileExtensions: SetupScreen.AudioExtensions) { RelativeSizeAxes = Axes.X, Height = 400, From 4139301afa172f2edc0eb734fa05dfe0596a30f9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Oct 2020 16:49:47 +0900 Subject: [PATCH 1432/1782] Exit import process after first handler is run --- osu.Game/OsuGameBase.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index f61ff43ca9..611bd783cd 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -362,7 +362,10 @@ namespace osu.Game foreach (var importer in fileImporters) { if (importer.HandledExtensions.Contains(extension)) + { await importer.Import(paths); + continue; + } } } From 2a02f8f3f3ba5dbbf86d807e0ddb29b4a26b4ebc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Oct 2020 16:49:55 +0900 Subject: [PATCH 1433/1782] Add support for background changing --- osu.Game/Screens/Edit/Setup/SetupScreen.cs | 89 ++++++++++++++++------ 1 file changed, 65 insertions(+), 24 deletions(-) diff --git a/osu.Game/Screens/Edit/Setup/SetupScreen.cs b/osu.Game/Screens/Edit/Setup/SetupScreen.cs index 7bb4e8bbc4..bbd0e23210 100644 --- a/osu.Game/Screens/Edit/Setup/SetupScreen.cs +++ b/osu.Game/Screens/Edit/Setup/SetupScreen.cs @@ -40,6 +40,7 @@ namespace osu.Game.Screens.Edit.Setup private LabelledTextBox creatorTextBox; private LabelledTextBox difficultyTextBox; private LabelledTextBox audioTrackTextBox; + private Container backgroundSpriteContainer; [Resolved] private OsuGameBase game { get; set; } @@ -95,19 +96,12 @@ namespace osu.Game.Screens.Edit.Setup Direction = FillDirection.Vertical, Children = new Drawable[] { - new Container + backgroundSpriteContainer = new Container { RelativeSizeAxes = Axes.X, Height = 250, Masking = true, CornerRadius = 10, - Child = new BeatmapBackgroundSprite(Beatmap.Value) - { - RelativeSizeAxes = Axes.Both, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - FillMode = FillMode.Fill, - }, }, new OsuSpriteText { @@ -156,18 +150,81 @@ namespace osu.Game.Screens.Edit.Setup } }; + updateBackgroundSprite(); + audioTrackTextBox.Current.BindValueChanged(audioTrackChanged); foreach (var item in flow.OfType()) item.OnCommit += onCommit; } + Task ICanAcceptFiles.Import(params string[] paths) + { + Schedule(() => + { + var firstFile = new FileInfo(paths.First()); + + if (ImageExtensions.Contains(firstFile.Extension)) + { + ChangeBackgroundImage(firstFile.FullName); + } + else if (AudioExtensions.Contains(firstFile.Extension)) + { + audioTrackTextBox.Text = firstFile.FullName; + } + }); + + return Task.CompletedTask; + } + + private void updateBackgroundSprite() + { + LoadComponentAsync(new BeatmapBackgroundSprite(Beatmap.Value) + { + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + FillMode = FillMode.Fill, + }, background => + { + backgroundSpriteContainer.Child = background; + background.FadeInFromZero(500); + }); + } + protected override void LoadComplete() { base.LoadComplete(); game.RegisterImportHandler(this); } + public bool ChangeBackgroundImage(string path) + { + var info = new FileInfo(path); + + if (!info.Exists) + return false; + + var set = Beatmap.Value.BeatmapSetInfo; + + // remove the previous background for now. + // in the future we probably want to check if this is being used elsewhere (other difficulties?) + var oldFile = set.Files.FirstOrDefault(f => f.Filename == Beatmap.Value.Metadata.BackgroundFile); + + using (var stream = info.OpenRead()) + { + if (oldFile != null) + beatmaps.ReplaceFile(set, oldFile, stream, info.Name); + else + beatmaps.AddFile(set, stream, info.Name); + } + + Beatmap.Value.Metadata.BackgroundFile = info.Name; + updateBackgroundSprite(); + + return true; + } + public bool ChangeAudioTrack(string path) { var info = new FileInfo(path); @@ -215,22 +272,6 @@ namespace osu.Game.Screens.Edit.Setup Beatmap.Value.BeatmapInfo.Version = difficultyTextBox.Current.Value; } - public Task Import(params string[] paths) - { - var firstFile = new FileInfo(paths.First()); - - if (ImageExtensions.Contains(firstFile.Extension)) - { - // todo: add image drag drop support - } - else if (AudioExtensions.Contains(firstFile.Extension)) - { - audioTrackTextBox.Text = firstFile.FullName; - } - - return Task.CompletedTask; - } - protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); From faeb9910e5e98bae54ffc7503e554134ded98a85 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Oct 2020 17:06:55 +0900 Subject: [PATCH 1434/1782] Revert "Exit import process after first handler is run" This reverts commit 4139301afa172f2edc0eb734fa05dfe0596a30f9. --- osu.Game/OsuGameBase.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 611bd783cd..f61ff43ca9 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -362,10 +362,7 @@ namespace osu.Game foreach (var importer in fileImporters) { if (importer.HandledExtensions.Contains(extension)) - { await importer.Import(paths); - continue; - } } } From fc920a8899478ff4e00ff5be84188386799148f6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Oct 2020 17:32:34 +0900 Subject: [PATCH 1435/1782] Add change handler logic --- osu.Game/Screens/Edit/Timing/GroupSection.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/osu.Game/Screens/Edit/Timing/GroupSection.cs b/osu.Game/Screens/Edit/Timing/GroupSection.cs index ac9c4be97a..0cc78315d2 100644 --- a/osu.Game/Screens/Edit/Timing/GroupSection.cs +++ b/osu.Game/Screens/Edit/Timing/GroupSection.cs @@ -27,6 +27,9 @@ namespace osu.Game.Screens.Edit.Timing [Resolved] private EditorClock clock { get; set; } + [Resolved(canBeNull: true)] + private IEditorChangeHandler changeHandler { get; set; } + [BackgroundDependencyLoader] private void load() { @@ -90,6 +93,8 @@ namespace osu.Game.Screens.Edit.Timing private void changeSelectedGroupTime(in double time) { + changeHandler?.BeginChange(); + var currentGroupItems = SelectedGroup.Value.ControlPoints.ToArray(); Beatmap.Value.Beatmap.ControlPointInfo.RemoveGroup(SelectedGroup.Value); @@ -98,6 +103,8 @@ namespace osu.Game.Screens.Edit.Timing Beatmap.Value.Beatmap.ControlPointInfo.Add(time, cp); SelectedGroup.Value = Beatmap.Value.Beatmap.ControlPointInfo.GroupAt(time); + + changeHandler?.EndChange(); } } } From 00eed295272b57ae3570fa9ef8e87274e9b1870f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Oct 2020 17:35:41 +0900 Subject: [PATCH 1436/1782] Don't update time if it hasn't changed --- osu.Game/Screens/Edit/Timing/GroupSection.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Screens/Edit/Timing/GroupSection.cs b/osu.Game/Screens/Edit/Timing/GroupSection.cs index 0cc78315d2..ee19aaface 100644 --- a/osu.Game/Screens/Edit/Timing/GroupSection.cs +++ b/osu.Game/Screens/Edit/Timing/GroupSection.cs @@ -93,6 +93,9 @@ namespace osu.Game.Screens.Edit.Timing private void changeSelectedGroupTime(in double time) { + if (time == SelectedGroup.Value.Time) + return; + changeHandler?.BeginChange(); var currentGroupItems = SelectedGroup.Value.ControlPoints.ToArray(); From 436cc572d3666353d24669846155b6c709f0b4f5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Oct 2020 17:15:28 +0900 Subject: [PATCH 1437/1782] Expose ChangeHandler.SaveState via interface --- osu.Game/Screens/Edit/EditorChangeHandler.cs | 3 --- osu.Game/Screens/Edit/IEditorChangeHandler.cs | 6 ++++++ 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Edit/EditorChangeHandler.cs b/osu.Game/Screens/Edit/EditorChangeHandler.cs index 617c436ee0..66331d54c0 100644 --- a/osu.Game/Screens/Edit/EditorChangeHandler.cs +++ b/osu.Game/Screens/Edit/EditorChangeHandler.cs @@ -79,9 +79,6 @@ namespace osu.Game.Screens.Edit SaveState(); } - /// - /// Saves the current state. - /// public void SaveState() { if (bulkChangesStarted > 0) diff --git a/osu.Game/Screens/Edit/IEditorChangeHandler.cs b/osu.Game/Screens/Edit/IEditorChangeHandler.cs index c1328252d4..f95df76907 100644 --- a/osu.Game/Screens/Edit/IEditorChangeHandler.cs +++ b/osu.Game/Screens/Edit/IEditorChangeHandler.cs @@ -29,5 +29,11 @@ namespace osu.Game.Screens.Edit /// This should be invoked as soon as possible after to cause a state change. /// void EndChange(); + + /// + /// Immediately saves the current state. + /// Note that this will be a no-op if there is a change in progress via . + /// + void SaveState(); } } From c1c5b5da8e703e7fc37b9585c4c63f743d4a7180 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Oct 2020 17:15:58 +0900 Subject: [PATCH 1438/1782] Push state change on control point group addition / removal --- osu.Game/Screens/Edit/Timing/TimingScreen.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Screens/Edit/Timing/TimingScreen.cs b/osu.Game/Screens/Edit/Timing/TimingScreen.cs index 0a0cfe193d..3b3ae949c1 100644 --- a/osu.Game/Screens/Edit/Timing/TimingScreen.cs +++ b/osu.Game/Screens/Edit/Timing/TimingScreen.cs @@ -87,6 +87,9 @@ namespace osu.Game.Screens.Edit.Timing [Resolved] private Bindable selectedGroup { get; set; } + [Resolved(canBeNull: true)] + private IEditorChangeHandler changeHandler { get; set; } + [BackgroundDependencyLoader] private void load(OsuColour colours) { @@ -146,6 +149,7 @@ namespace osu.Game.Screens.Edit.Timing controlGroups.BindCollectionChanged((sender, args) => { table.ControlGroups = controlGroups; + changeHandler.SaveState(); }, true); } From 98fd661b239dbd189cc7fb36cd1a30b8e20083c8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Oct 2020 17:55:47 +0900 Subject: [PATCH 1439/1782] Add change handling for timing section --- osu.Game/Screens/Edit/Timing/Section.cs | 3 +++ osu.Game/Screens/Edit/Timing/TimingSection.cs | 7 +++++++ 2 files changed, 10 insertions(+) diff --git a/osu.Game/Screens/Edit/Timing/Section.cs b/osu.Game/Screens/Edit/Timing/Section.cs index 603fb77f31..7a81eeb1a4 100644 --- a/osu.Game/Screens/Edit/Timing/Section.cs +++ b/osu.Game/Screens/Edit/Timing/Section.cs @@ -32,6 +32,9 @@ namespace osu.Game.Screens.Edit.Timing [Resolved] protected Bindable SelectedGroup { get; private set; } + [Resolved(canBeNull: true)] + protected IEditorChangeHandler ChangeHandler { get; private set; } + [BackgroundDependencyLoader] private void load(OsuColour colours) { diff --git a/osu.Game/Screens/Edit/Timing/TimingSection.cs b/osu.Game/Screens/Edit/Timing/TimingSection.cs index 0202441537..2ab8703cc4 100644 --- a/osu.Game/Screens/Edit/Timing/TimingSection.cs +++ b/osu.Game/Screens/Edit/Timing/TimingSection.cs @@ -37,8 +37,13 @@ namespace osu.Game.Screens.Edit.Timing if (point.NewValue != null) { bpmSlider.Bindable = point.NewValue.BeatLengthBindable; + bpmSlider.Bindable.BindValueChanged(_ => ChangeHandler?.SaveState()); + bpmTextEntry.Bindable = point.NewValue.BeatLengthBindable; + // no need to hook change handler here as it's the same bindable as above + timeSignature.Bindable = point.NewValue.TimeSignatureBindable; + timeSignature.Bindable.BindValueChanged(_ => ChangeHandler?.SaveState()); } } @@ -117,6 +122,8 @@ namespace osu.Game.Screens.Edit.Timing bpmBindable.BindValueChanged(bpm => beatLengthBindable.Value = beatLengthToBpm(bpm.NewValue)); base.Bindable = bpmBindable; + + TransferValueOnCommit = true; } public override Bindable Bindable From 693a4ff474ea957bd1d8bc4276b3d75616904278 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Oct 2020 17:56:30 +0900 Subject: [PATCH 1440/1782] Add change handling for effects section --- osu.Game/Screens/Edit/Timing/EffectSection.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Screens/Edit/Timing/EffectSection.cs b/osu.Game/Screens/Edit/Timing/EffectSection.cs index 71e7f42713..2f143108a9 100644 --- a/osu.Game/Screens/Edit/Timing/EffectSection.cs +++ b/osu.Game/Screens/Edit/Timing/EffectSection.cs @@ -28,7 +28,10 @@ namespace osu.Game.Screens.Edit.Timing if (point.NewValue != null) { kiai.Current = point.NewValue.KiaiModeBindable; + kiai.Current.BindValueChanged(_ => ChangeHandler?.SaveState()); + omitBarLine.Current = point.NewValue.OmitFirstBarLineBindable; + omitBarLine.Current.BindValueChanged(_ => ChangeHandler?.SaveState()); } } From 08faef694bde8b66b7234c231ea58b89a058a951 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Oct 2020 17:58:22 +0900 Subject: [PATCH 1441/1782] Add change handling for difficulty section --- osu.Game/Screens/Edit/Timing/DifficultySection.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Screens/Edit/Timing/DifficultySection.cs b/osu.Game/Screens/Edit/Timing/DifficultySection.cs index 78766d9777..b55d74e3b4 100644 --- a/osu.Game/Screens/Edit/Timing/DifficultySection.cs +++ b/osu.Game/Screens/Edit/Timing/DifficultySection.cs @@ -28,6 +28,7 @@ namespace osu.Game.Screens.Edit.Timing if (point.NewValue != null) { multiplierSlider.Current = point.NewValue.SpeedMultiplierBindable; + multiplierSlider.Current.BindValueChanged(_ => ChangeHandler?.SaveState()); } } From 9fc9009dbe1a6bba52686e41413aae20c4804652 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Oct 2020 17:59:47 +0900 Subject: [PATCH 1442/1782] Add change handling for sample section --- osu.Game/Screens/Edit/Timing/SampleSection.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Screens/Edit/Timing/SampleSection.cs b/osu.Game/Screens/Edit/Timing/SampleSection.cs index de986e28ca..280e19c99a 100644 --- a/osu.Game/Screens/Edit/Timing/SampleSection.cs +++ b/osu.Game/Screens/Edit/Timing/SampleSection.cs @@ -35,7 +35,10 @@ namespace osu.Game.Screens.Edit.Timing if (point.NewValue != null) { bank.Current = point.NewValue.SampleBankBindable; + bank.Current.BindValueChanged(_ => ChangeHandler?.SaveState()); + volume.Current = point.NewValue.SampleVolumeBindable; + volume.Current.BindValueChanged(_ => ChangeHandler?.SaveState()); } } From 519c3ac2bdb6a23e30f48cac57db9e4f62c858e5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Oct 2020 17:59:57 +0900 Subject: [PATCH 1443/1782] Change SliderWithTextBoxInput to transfer on commit --- osu.Game/Screens/Edit/Timing/SliderWithTextBoxInput.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Screens/Edit/Timing/SliderWithTextBoxInput.cs b/osu.Game/Screens/Edit/Timing/SliderWithTextBoxInput.cs index 14023b0c35..d5afc8978d 100644 --- a/osu.Game/Screens/Edit/Timing/SliderWithTextBoxInput.cs +++ b/osu.Game/Screens/Edit/Timing/SliderWithTextBoxInput.cs @@ -38,6 +38,7 @@ namespace osu.Game.Screens.Edit.Timing }, slider = new SettingsSlider { + TransferValueOnCommit = true, RelativeSizeAxes = Axes.X, } } From 66f5187e6a26ea480fb777d9f5abef93ce7a4e13 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Oct 2020 18:20:59 +0900 Subject: [PATCH 1444/1782] Remove redundant access permission --- osu.Game/Screens/Edit/IEditorChangeHandler.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/IEditorChangeHandler.cs b/osu.Game/Screens/Edit/IEditorChangeHandler.cs index a23a956e14..1774ec6c04 100644 --- a/osu.Game/Screens/Edit/IEditorChangeHandler.cs +++ b/osu.Game/Screens/Edit/IEditorChangeHandler.cs @@ -14,7 +14,7 @@ namespace osu.Game.Screens.Edit /// /// Fired whenever a state change occurs. /// - public event Action OnStateChange; + event Action OnStateChange; /// /// Begins a bulk state change event. should be invoked soon after. From 575046e5fdc34bc13797cab78517f337136734c7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Oct 2020 18:21:13 +0900 Subject: [PATCH 1445/1782] Don't update reply on add/remove (will be automatically handled by change handler events) --- osu.Game/Rulesets/Edit/DrawableEditRulesetWrapper.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/osu.Game/Rulesets/Edit/DrawableEditRulesetWrapper.cs b/osu.Game/Rulesets/Edit/DrawableEditRulesetWrapper.cs index 43e5153f24..8ed7885101 100644 --- a/osu.Game/Rulesets/Edit/DrawableEditRulesetWrapper.cs +++ b/osu.Game/Rulesets/Edit/DrawableEditRulesetWrapper.cs @@ -69,8 +69,6 @@ namespace osu.Game.Rulesets.Edit drawableRuleset.Playfield.Add(drawableObject); drawableRuleset.Playfield.PostProcess(); - - updateReplay(); } private void removeHitObject(HitObject hitObject) @@ -79,8 +77,6 @@ namespace osu.Game.Rulesets.Edit drawableRuleset.Playfield.Remove(drawableObject); drawableRuleset.Playfield.PostProcess(); - - updateReplay(); } public override bool PropagatePositionalInputSubTree => false; From 1a0171fb2dc2a4fea5eaa8c57a86cd74932d4ff9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Oct 2020 18:18:14 +0900 Subject: [PATCH 1446/1782] Fix tests specifying steps in their constructors --- osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneHoldNote.cs | 4 +++- osu.Game.Rulesets.Mania.Tests/TestSceneNotes.cs | 4 ++-- osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs | 3 ++- osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs | 3 ++- osu.Game.Rulesets.Taiko.Tests/TestSceneHits.cs | 5 ++--- 5 files changed, 11 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneHoldNote.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneHoldNote.cs index 95e86de884..9c4c2b3d5b 100644 --- a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneHoldNote.cs +++ b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneHoldNote.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System.Linq; +using NUnit.Framework; using osu.Framework.Bindables; using osu.Framework.Testing; using osu.Game.Beatmaps; @@ -13,7 +14,8 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning { public class TestSceneHoldNote : ManiaHitObjectTestScene { - public TestSceneHoldNote() + [Test] + public void TestHoldNote() { AddToggleStep("toggle hitting", v => { diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneNotes.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneNotes.cs index dd5fd93710..76c1b47cca 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneNotes.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneNotes.cs @@ -28,8 +28,8 @@ namespace osu.Game.Rulesets.Mania.Tests [TestFixture] public class TestSceneNotes : OsuTestScene { - [BackgroundDependencyLoader] - private void load() + [Test] + public void TestVariousNotes() { Child = new FillFlowContainer { diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs index 37df0d6e37..596bc06c68 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs @@ -20,7 +20,8 @@ namespace osu.Game.Rulesets.Osu.Tests { private int depthIndex; - public TestSceneHitCircle() + [Test] + public void TestVariousHitCircles() { AddStep("Miss Big Single", () => SetContents(() => testSingle(2))); AddStep("Miss Medium Single", () => SetContents(() => testSingle(5))); diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs index c79cae2fe5..c9e112f76d 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs @@ -27,7 +27,8 @@ namespace osu.Game.Rulesets.Osu.Tests { private int depthIndex; - public TestSceneSlider() + [Test] + public void TestVariousSliders() { AddStep("Big Single", () => SetContents(() => testSimpleBig())); AddStep("Medium Single", () => SetContents(() => testSimpleMedium())); diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneHits.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneHits.cs index 0f605be8f9..e4c0766844 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TestSceneHits.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneHits.cs @@ -3,7 +3,6 @@ using System; using NUnit.Framework; -using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Utils; using osu.Game.Beatmaps; @@ -30,8 +29,8 @@ namespace osu.Game.Rulesets.Taiko.Tests private readonly Random rng = new Random(1337); - [BackgroundDependencyLoader] - private void load() + [Test] + public void TestVariousHits() { AddStep("Hit", () => addHitJudgement(false)); AddStep("Strong hit", () => addStrongHitJudgement(false)); From 5a6c45e2ff43fa7e4c811a46883d577db715faea Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Oct 2020 18:41:28 +0900 Subject: [PATCH 1447/1782] Fix hidden mod support for sliderendcircle --- osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs | 31 +++++++++++++++++++ .../Objects/Drawables/DrawableSliderRepeat.cs | 4 ++- 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs index 08fd13915d..80e40af717 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs @@ -39,6 +39,9 @@ namespace osu.Game.Rulesets.Osu.Mods base.ApplyToDrawableHitObjects(drawables); } + private double lastSliderHeadFadeOutStartTime; + private double lastSliderHeadFadeOutDuration; + protected override void ApplyHiddenState(DrawableHitObject drawable, ArmedState state) { if (!(drawable is DrawableOsuHitObject d)) @@ -54,7 +57,35 @@ namespace osu.Game.Rulesets.Osu.Mods switch (drawable) { + case DrawableSliderTail sliderTail: + // use stored values from head circle to achieve same fade sequence. + fadeOutDuration = lastSliderHeadFadeOutDuration; + fadeOutStartTime = lastSliderHeadFadeOutStartTime; + + using (drawable.BeginAbsoluteSequence(fadeOutStartTime, true)) + sliderTail.FadeOut(fadeOutDuration); + + break; + + case DrawableSliderRepeat sliderRepeat: + // use stored values from head circle to achieve same fade sequence. + fadeOutDuration = lastSliderHeadFadeOutDuration; + fadeOutStartTime = lastSliderHeadFadeOutStartTime; + + using (drawable.BeginAbsoluteSequence(fadeOutStartTime, true)) + // only apply to circle piece – reverse arrow is not affected by hidden. + sliderRepeat.CirclePiece.FadeOut(fadeOutDuration); + + break; + case DrawableHitCircle circle: + + if (circle is DrawableSliderHead) + { + lastSliderHeadFadeOutDuration = fadeOutDuration; + lastSliderHeadFadeOutStartTime = fadeOutStartTime; + } + // we don't want to see the approach circle using (circle.BeginAbsoluteSequence(h.StartTime - h.TimePreempt, true)) circle.ApproachCircle.Hide(); diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs index 9d775de7df..46d47a8c94 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs @@ -24,6 +24,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables private readonly Drawable scaleContainer; + public readonly Drawable CirclePiece; + public override bool DisplayResult => false; public DrawableSliderRepeat(SliderRepeat sliderRepeat, DrawableSlider drawableSlider) @@ -44,7 +46,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables Children = new Drawable[] { // no default for this; only visible in legacy skins. - new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SliderTailHitCircle), _ => Empty()), + CirclePiece = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SliderTailHitCircle), _ => Empty()), arrow = new ReverseArrowPiece(), } }; From ed34985fdde5b8bcf86169a1f66219def3e0ac9f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Oct 2020 18:47:11 +0900 Subject: [PATCH 1448/1782] Add step for mania note construction --- .../TestSceneNotes.cs | 37 ++++++++++++------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneNotes.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneNotes.cs index 76c1b47cca..fd8a01766b 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneNotes.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneNotes.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.Testing; using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; @@ -31,22 +32,30 @@ namespace osu.Game.Rulesets.Mania.Tests [Test] public void TestVariousNotes() { - Child = new FillFlowContainer + DrawableNote note1 = null; + DrawableNote note2 = null; + DrawableHoldNote holdNote1 = null; + DrawableHoldNote holdNote2 = null; + + AddStep("create notes", () => { - Clock = new FramedClock(new ManualClock()), - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(20), - Children = new[] + Child = new FillFlowContainer { - createNoteDisplay(ScrollingDirection.Down, 1, out var note1), - createNoteDisplay(ScrollingDirection.Up, 2, out var note2), - createHoldNoteDisplay(ScrollingDirection.Down, 1, out var holdNote1), - createHoldNoteDisplay(ScrollingDirection.Up, 2, out var holdNote2), - } - }; + Clock = new FramedClock(new ManualClock()), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(20), + Children = new[] + { + createNoteDisplay(ScrollingDirection.Down, 1, out note1), + createNoteDisplay(ScrollingDirection.Up, 2, out note2), + createHoldNoteDisplay(ScrollingDirection.Down, 1, out holdNote1), + createHoldNoteDisplay(ScrollingDirection.Up, 2, out holdNote2), + } + }; + }); AddAssert("note 1 facing downwards", () => verifyAnchors(note1, Anchor.y2)); AddAssert("note 2 facing upwards", () => verifyAnchors(note2, Anchor.y0)); From fcc6cb36e4c1e204e4ea106e6756b2cdb442bb48 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Oct 2020 18:50:47 +0900 Subject: [PATCH 1449/1782] Change text colour to black --- osu.Game/Screens/Edit/Timing/RowAttribute.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Timing/RowAttribute.cs b/osu.Game/Screens/Edit/Timing/RowAttribute.cs index c45995ee83..2757e08026 100644 --- a/osu.Game/Screens/Edit/Timing/RowAttribute.cs +++ b/osu.Game/Screens/Edit/Timing/RowAttribute.cs @@ -53,7 +53,7 @@ namespace osu.Game.Screens.Edit.Timing Origin = Anchor.Centre, Font = OsuFont.Default.With(weight: FontWeight.SemiBold, size: 12), Text = header, - Colour = colours.Gray3 + Colour = colours.Gray0 }, }; } From 0d3a95d8fca626aededdff8bbc4e329b4401818c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Oct 2020 19:54:13 +0900 Subject: [PATCH 1450/1782] Remove unnecessary string interpolation --- .../Edit/Compose/Components/Timeline/SamplePointPiece.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs index 6a6e947343..37c8c8402a 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs @@ -75,7 +75,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline }; volume.BindValueChanged(volume => volumeBox.Height = volume.NewValue / 100f, true); - bank.BindValueChanged(bank => text.Text = $"{bank.NewValue}", true); + bank.BindValueChanged(bank => text.Text = bank.NewValue, true); } } } From a3ecc6c5a4bb55553b9a4ab97f1859e3f665ec0f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Oct 2020 19:56:24 +0900 Subject: [PATCH 1451/1782] Remove redundant array type specification --- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs index 46d47a8c94..2a88f11f69 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs @@ -43,7 +43,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables RelativeSizeAxes = Axes.Both, Anchor = Anchor.Centre, Origin = Anchor.Centre, - Children = new Drawable[] + Children = new[] { // no default for this; only visible in legacy skins. CirclePiece = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SliderTailHitCircle), _ => Empty()), From 75ae9f1b30c47e3802fa7b2170e8f9d4d695cc52 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Oct 2020 19:57:14 +0900 Subject: [PATCH 1452/1782] Remove unused using --- osu.Game.Rulesets.Mania.Tests/TestSceneNotes.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneNotes.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneNotes.cs index fd8a01766b..6b8f5d5d9d 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneNotes.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneNotes.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.Testing; using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; From dab50bff6f28dcddc8c9ed68ed2a3e0be71e9822 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 3 Oct 2020 01:27:42 +0900 Subject: [PATCH 1453/1782] Protect "use current time" button against crash when no timing point is selected --- osu.Game/Screens/Edit/Timing/GroupSection.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/GroupSection.cs b/osu.Game/Screens/Edit/Timing/GroupSection.cs index ee19aaface..c77d48ef0a 100644 --- a/osu.Game/Screens/Edit/Timing/GroupSection.cs +++ b/osu.Game/Screens/Edit/Timing/GroupSection.cs @@ -18,6 +18,8 @@ namespace osu.Game.Screens.Edit.Timing { private LabelledTextBox textBox; + private TriangleButton button; + [Resolved] protected Bindable SelectedGroup { get; private set; } @@ -52,7 +54,7 @@ namespace osu.Game.Screens.Edit.Timing { Label = "Time" }, - new TriangleButton + button = new TriangleButton { Text = "Use current time", RelativeSizeAxes = Axes.X, @@ -82,18 +84,22 @@ namespace osu.Game.Screens.Edit.Timing if (group.NewValue == null) { textBox.Text = string.Empty; + textBox.Current.Disabled = true; + button.Enabled.Value = false; return; } textBox.Current.Disabled = false; + button.Enabled.Value = true; + textBox.Text = $"{group.NewValue.Time:n0}"; }, true); } private void changeSelectedGroupTime(in double time) { - if (time == SelectedGroup.Value.Time) + if (SelectedGroup.Value == null || time == SelectedGroup.Value.Time) return; changeHandler?.BeginChange(); From 16f331cf6d09601c655234cfa245252a90adbe03 Mon Sep 17 00:00:00 2001 From: Endrik Tombak Date: Fri, 2 Oct 2020 19:34:06 +0300 Subject: [PATCH 1454/1782] Move implementation to LegacyCursorTrail --- osu.Game.Rulesets.Osu/Skinning/LegacyCursorTrail.cs | 10 +++++++++- osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs | 10 ++++------ 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacyCursorTrail.cs b/osu.Game.Rulesets.Osu/Skinning/LegacyCursorTrail.cs index 1885c76fcc..eabf797607 100644 --- a/osu.Game.Rulesets.Osu/Skinning/LegacyCursorTrail.cs +++ b/osu.Game.Rulesets.Osu/Skinning/LegacyCursorTrail.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 osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Input.Events; +using osu.Game.Configuration; using osu.Game.Rulesets.Osu.UI.Cursor; using osu.Game.Skinning; @@ -15,6 +18,7 @@ namespace osu.Game.Rulesets.Osu.Skinning private bool disjointTrail; private double lastTrailTime; + private IBindable cursorSize; public LegacyCursorTrail() { @@ -22,7 +26,7 @@ namespace osu.Game.Rulesets.Osu.Skinning } [BackgroundDependencyLoader] - private void load(ISkinSource skin) + private void load(ISkinSource skin, OsuConfigManager config) { Texture = skin.GetTexture("cursortrail"); disjointTrail = skin.GetTexture("cursormiddle") == null; @@ -32,12 +36,16 @@ namespace osu.Game.Rulesets.Osu.Skinning // stable "magic ratio". see OsuPlayfieldAdjustmentContainer for full explanation. Texture.ScaleAdjust *= 1.6f; } + + cursorSize = config.GetBindable(OsuSetting.GameplayCursorSize).GetBoundCopy(); } protected override double FadeDuration => disjointTrail ? 150 : 500; protected override bool InterpolateMovements => !disjointTrail; + protected override float IntervalMultiplier => Math.Max(cursorSize.Value, 1); + protected override bool OnMouseMove(MouseMoveEvent e) { if (!disjointTrail) diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs index fb8a850223..c30615e6e9 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs @@ -5,7 +5,6 @@ using System; using System.Diagnostics; using System.Runtime.InteropServices; using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Batches; using osu.Framework.Graphics.OpenGL.Vertices; @@ -16,7 +15,6 @@ using osu.Framework.Input; using osu.Framework.Input.Events; using osu.Framework.Layout; using osu.Framework.Timing; -using osu.Game.Configuration; using osuTK; using osuTK.Graphics; using osuTK.Graphics.ES30; @@ -30,7 +28,6 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor private readonly TrailPart[] parts = new TrailPart[max_sprites]; private int currentIndex; private IShader shader; - private Bindable cursorSize; private double timeOffset; private float time; @@ -51,10 +48,9 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor } [BackgroundDependencyLoader] - private void load(ShaderManager shaders, OsuConfigManager config) + private void load(ShaderManager shaders) { shader = shaders.Load(@"CursorTrail", FragmentShaderDescriptor.TEXTURE); - cursorSize = config.GetBindable(OsuSetting.GameplayCursorSize).GetBoundCopy(); } protected override void LoadComplete() @@ -123,6 +119,8 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor /// protected virtual bool InterpolateMovements => true; + protected virtual float IntervalMultiplier => 1.0f; + private Vector2? lastPosition; private readonly InputResampler resampler = new InputResampler(); @@ -151,7 +149,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor float distance = diff.Length; Vector2 direction = diff / distance; - float interval = partSize.X / 2.5f / Math.Max(cursorSize.Value, 1); + float interval = partSize.X / 2.5f / IntervalMultiplier; for (float d = interval; d < distance; d += interval) { From 8cd13729eeabb94dccdea6057994007ad4dbeac4 Mon Sep 17 00:00:00 2001 From: Endrik Tombak Date: Fri, 2 Oct 2020 19:34:49 +0300 Subject: [PATCH 1455/1782] Actually multiply by the multiplier --- osu.Game.Rulesets.Osu/Skinning/LegacyCursorTrail.cs | 2 +- osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacyCursorTrail.cs b/osu.Game.Rulesets.Osu/Skinning/LegacyCursorTrail.cs index eabf797607..e6cd7bc59d 100644 --- a/osu.Game.Rulesets.Osu/Skinning/LegacyCursorTrail.cs +++ b/osu.Game.Rulesets.Osu/Skinning/LegacyCursorTrail.cs @@ -44,7 +44,7 @@ namespace osu.Game.Rulesets.Osu.Skinning protected override bool InterpolateMovements => !disjointTrail; - protected override float IntervalMultiplier => Math.Max(cursorSize.Value, 1); + protected override float IntervalMultiplier => 1 / Math.Max(cursorSize.Value, 1); protected override bool OnMouseMove(MouseMoveEvent e) { diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs index c30615e6e9..0b30c28b8d 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs @@ -149,7 +149,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor float distance = diff.Length; Vector2 direction = diff / distance; - float interval = partSize.X / 2.5f / IntervalMultiplier; + float interval = partSize.X / 2.5f * IntervalMultiplier; for (float d = interval; d < distance; d += interval) { From 0163688a174d84305b191caa1a2a42ce48ed3a6f Mon Sep 17 00:00:00 2001 From: Lucas A Date: Fri, 2 Oct 2020 19:24:30 +0200 Subject: [PATCH 1456/1782] Remove IBeatmap from PerformanceCalculator. --- osu.Game.Rulesets.Catch/CatchRuleset.cs | 2 +- .../Difficulty/CatchPerformanceCalculator.cs | 4 ++-- .../Difficulty/ManiaPerformanceCalculator.cs | 4 ++-- osu.Game.Rulesets.Mania/ManiaRuleset.cs | 2 +- .../Difficulty/OsuPerformanceCalculator.cs | 4 ++-- osu.Game.Rulesets.Osu/OsuRuleset.cs | 2 +- .../Difficulty/TaikoPerformanceCalculator.cs | 4 ++-- osu.Game.Rulesets.Taiko/TaikoRuleset.cs | 2 +- osu.Game/Rulesets/Difficulty/PerformanceCalculator.cs | 9 +++------ osu.Game/Rulesets/Ruleset.cs | 2 +- 10 files changed, 16 insertions(+), 19 deletions(-) diff --git a/osu.Game.Rulesets.Catch/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs index ca75a816f1..cb7cac436b 100644 --- a/osu.Game.Rulesets.Catch/CatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs @@ -145,7 +145,7 @@ namespace osu.Game.Rulesets.Catch public override ISkin CreateLegacySkinProvider(ISkinSource source, IBeatmap beatmap) => new CatchLegacySkinTransformer(source); - public override PerformanceCalculator CreatePerformanceCalculator(WorkingBeatmap beatmap, ScoreInfo score) => new CatchPerformanceCalculator(this, beatmap, score); + public override PerformanceCalculator CreatePerformanceCalculator(WorkingBeatmap beatmap, DifficultyAttributes attributes, ScoreInfo score) => new CatchPerformanceCalculator(this, attributes, score); public int LegacyID => 2; diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs index a4b9ca35eb..e671e581cf 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs @@ -25,8 +25,8 @@ namespace osu.Game.Rulesets.Catch.Difficulty private int tinyTicksMissed; private int misses; - public CatchPerformanceCalculator(Ruleset ruleset, WorkingBeatmap beatmap, ScoreInfo score) - : base(ruleset, beatmap, score) + public CatchPerformanceCalculator(Ruleset ruleset, DifficultyAttributes attributes, ScoreInfo score) + : base(ruleset, attributes, score) { } diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs index 91383c5548..086afb3254 100644 --- a/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs @@ -29,8 +29,8 @@ namespace osu.Game.Rulesets.Mania.Difficulty private int countMeh; private int countMiss; - public ManiaPerformanceCalculator(Ruleset ruleset, WorkingBeatmap beatmap, ScoreInfo score) - : base(ruleset, beatmap, score) + public ManiaPerformanceCalculator(Ruleset ruleset, DifficultyAttributes attributes, ScoreInfo score) + : base(ruleset, attributes, score) { } diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index 71ac85dd1b..8bf6b5e064 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -49,7 +49,7 @@ namespace osu.Game.Rulesets.Mania public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new ManiaBeatmapConverter(beatmap, this); - public override PerformanceCalculator CreatePerformanceCalculator(WorkingBeatmap beatmap, ScoreInfo score) => new ManiaPerformanceCalculator(this, beatmap, score); + public override PerformanceCalculator CreatePerformanceCalculator(WorkingBeatmap beatmap, DifficultyAttributes attributes, ScoreInfo score) => new ManiaPerformanceCalculator(this, attributes, score); public const string SHORT_NAME = "mania"; diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index 02577461f0..9e08163329 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -31,8 +31,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty private int countMeh; private int countMiss; - public OsuPerformanceCalculator(Ruleset ruleset, WorkingBeatmap beatmap, ScoreInfo score) - : base(ruleset, beatmap, score) + public OsuPerformanceCalculator(Ruleset ruleset, DifficultyAttributes attributes, ScoreInfo score) + : base(ruleset, attributes, score) { countHitCircles = Beatmap.HitObjects.Count(h => h is HitCircle); diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index 7f4a0dcbbb..9798f15f21 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -171,7 +171,7 @@ namespace osu.Game.Rulesets.Osu public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new OsuDifficultyCalculator(this, beatmap); - public override PerformanceCalculator CreatePerformanceCalculator(WorkingBeatmap beatmap, ScoreInfo score) => new OsuPerformanceCalculator(this, beatmap, score); + public override PerformanceCalculator CreatePerformanceCalculator(WorkingBeatmap beatmap, DifficultyAttributes attributes, ScoreInfo score) => new OsuPerformanceCalculator(this, attributes, score); public override HitObjectComposer CreateHitObjectComposer() => new OsuHitObjectComposer(this); diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs index c04fffa2e7..2505300425 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs @@ -24,8 +24,8 @@ namespace osu.Game.Rulesets.Taiko.Difficulty private int countMeh; private int countMiss; - public TaikoPerformanceCalculator(Ruleset ruleset, WorkingBeatmap beatmap, ScoreInfo score) - : base(ruleset, beatmap, score) + public TaikoPerformanceCalculator(Ruleset ruleset, DifficultyAttributes attributes, ScoreInfo score) + : base(ruleset, attributes, score) { } diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index 9d485e3f20..3bc749b868 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -153,7 +153,7 @@ namespace osu.Game.Rulesets.Taiko public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new TaikoDifficultyCalculator(this, beatmap); - public override PerformanceCalculator CreatePerformanceCalculator(WorkingBeatmap beatmap, ScoreInfo score) => new TaikoPerformanceCalculator(this, beatmap, score); + public override PerformanceCalculator CreatePerformanceCalculator(WorkingBeatmap beatmap, DifficultyAttributes attributes, ScoreInfo score) => new TaikoPerformanceCalculator(this, attributes, score); public int LegacyID => 1; diff --git a/osu.Game/Rulesets/Difficulty/PerformanceCalculator.cs b/osu.Game/Rulesets/Difficulty/PerformanceCalculator.cs index ac3b817840..58427f6945 100644 --- a/osu.Game/Rulesets/Difficulty/PerformanceCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/PerformanceCalculator.cs @@ -1,11 +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 System.Collections.Generic; using System.Linq; using osu.Framework.Audio.Track; using osu.Framework.Extensions.IEnumerableExtensions; -using osu.Game.Beatmaps; using osu.Game.Rulesets.Mods; using osu.Game.Scoring; @@ -16,19 +16,16 @@ namespace osu.Game.Rulesets.Difficulty protected readonly DifficultyAttributes Attributes; protected readonly Ruleset Ruleset; - protected readonly IBeatmap Beatmap; protected readonly ScoreInfo Score; protected double TimeRate { get; private set; } = 1; - protected PerformanceCalculator(Ruleset ruleset, WorkingBeatmap beatmap, ScoreInfo score) + protected PerformanceCalculator(Ruleset ruleset, DifficultyAttributes attributes, ScoreInfo score) { Ruleset = ruleset; Score = score; - Beatmap = beatmap.GetPlayableBeatmap(ruleset.RulesetInfo, score.Mods); - - Attributes = ruleset.CreateDifficultyCalculator(beatmap).Calculate(score.Mods); + Attributes = attributes ?? throw new ArgumentNullException(nameof(attributes)); ApplyMods(score.Mods); } diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index 915544d010..25c5f41b5b 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -158,7 +158,7 @@ namespace osu.Game.Rulesets public abstract DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap); - public virtual PerformanceCalculator CreatePerformanceCalculator(WorkingBeatmap beatmap, ScoreInfo score) => null; + public virtual PerformanceCalculator CreatePerformanceCalculator(WorkingBeatmap beatmap, DifficultyAttributes attributes, ScoreInfo score) => null; public virtual HitObjectComposer CreateHitObjectComposer() => null; From cb2f695fddf3fca6c2fd6654a25984be5b721dcf Mon Sep 17 00:00:00 2001 From: Lucas A Date: Fri, 2 Oct 2020 19:34:41 +0200 Subject: [PATCH 1457/1782] Calculate hit circle count in OsuPerformanceCalculator. --- .../Difficulty/OsuDifficultyAttributes.cs | 1 + .../Difficulty/OsuDifficultyCalculator.cs | 3 +++ .../Difficulty/OsuPerformanceCalculator.cs | 20 ++++++------------- 3 files changed, 10 insertions(+), 14 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs index a9879013f8..50f060cf06 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs @@ -11,5 +11,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty public double SpeedStrain; public double ApproachRate; public double OverallDifficulty; + public int HitCirclesCount; } } diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs index b0d261a1cc..86c7cd2298 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs @@ -47,6 +47,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty // Add the ticks + tail of the slider. 1 is subtracted because the head circle would be counted twice (once for the slider itself in the line above) maxCombo += beatmap.HitObjects.OfType().Sum(s => s.NestedHitObjects.Count - 1); + int hitCirclesCount = beatmap.HitObjects.Count(h => h is HitCircle); + return new OsuDifficultyAttributes { StarRating = starRating, @@ -56,6 +58,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty ApproachRate = preempt > 1200 ? (1800 - preempt) / 120 : (1200 - preempt) / 150 + 5, OverallDifficulty = (80 - hitWindowGreat) / 6, MaxCombo = maxCombo, + HitCirclesCount = hitCirclesCount, Skills = skills }; } diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index 9e08163329..6acd8f9a87 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -19,9 +19,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty { public new OsuDifficultyAttributes Attributes => (OsuDifficultyAttributes)base.Attributes; - private readonly int countHitCircles; - private readonly int beatmapMaxCombo; - private Mod[] mods; private double accuracy; @@ -34,11 +31,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty public OsuPerformanceCalculator(Ruleset ruleset, DifficultyAttributes attributes, ScoreInfo score) : base(ruleset, attributes, score) { - countHitCircles = Beatmap.HitObjects.Count(h => h is HitCircle); - - beatmapMaxCombo = Beatmap.HitObjects.Count; - // Add the ticks + tail of the slider. 1 is subtracted because the "headcircle" would be counted twice (once for the slider itself in the line above) - beatmapMaxCombo += Beatmap.HitObjects.OfType().Sum(s => s.NestedHitObjects.Count - 1); } public override double Calculate(Dictionary categoryRatings = null) @@ -81,7 +73,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty categoryRatings.Add("Accuracy", accuracyValue); categoryRatings.Add("OD", Attributes.OverallDifficulty); categoryRatings.Add("AR", Attributes.ApproachRate); - categoryRatings.Add("Max Combo", beatmapMaxCombo); + categoryRatings.Add("Max Combo", Attributes.MaxCombo); } return totalValue; @@ -106,8 +98,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty aimValue *= Math.Pow(0.97, countMiss); // Combo scaling - if (beatmapMaxCombo > 0) - aimValue *= Math.Min(Math.Pow(scoreMaxCombo, 0.8) / Math.Pow(beatmapMaxCombo, 0.8), 1.0); + if (Attributes.MaxCombo > 0) + aimValue *= Math.Min(Math.Pow(scoreMaxCombo, 0.8) / Math.Pow(Attributes.MaxCombo, 0.8), 1.0); double approachRateFactor = 1.0; @@ -154,8 +146,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty speedValue *= Math.Pow(0.97, countMiss); // Combo scaling - if (beatmapMaxCombo > 0) - speedValue *= Math.Min(Math.Pow(scoreMaxCombo, 0.8) / Math.Pow(beatmapMaxCombo, 0.8), 1.0); + if (Attributes.MaxCombo > 0) + speedValue *= Math.Min(Math.Pow(scoreMaxCombo, 0.8) / Math.Pow(Attributes.MaxCombo, 0.8), 1.0); double approachRateFactor = 1.0; if (Attributes.ApproachRate > 10.33) @@ -178,7 +170,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty { // This percentage only considers HitCircles of any value - in this part of the calculation we focus on hitting the timing hit window double betterAccuracyPercentage; - int amountHitObjectsWithAccuracy = countHitCircles; + int amountHitObjectsWithAccuracy = Attributes.HitCirclesCount; if (amountHitObjectsWithAccuracy > 0) betterAccuracyPercentage = ((countGreat - (totalHits - amountHitObjectsWithAccuracy)) * 6 + countOk * 2 + countMeh) / (double)(amountHitObjectsWithAccuracy * 6); From abd395a03098808ff3926be50d7063394d572342 Mon Sep 17 00:00:00 2001 From: Lucas A Date: Fri, 2 Oct 2020 19:41:24 +0200 Subject: [PATCH 1458/1782] Remove unecessary using references. --- .../Difficulty/CatchPerformanceCalculator.cs | 1 - .../Difficulty/ManiaPerformanceCalculator.cs | 1 - osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs | 2 -- .../Difficulty/TaikoPerformanceCalculator.cs | 1 - 4 files changed, 5 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs index e671e581cf..6a3a16ed33 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; using System.Linq; using osu.Framework.Extensions; -using osu.Game.Beatmaps; using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Scoring; diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs index 086afb3254..00bec18a45 100644 --- a/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; using System.Linq; using osu.Framework.Extensions; -using osu.Game.Beatmaps; using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Scoring; diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index 6acd8f9a87..fed0a12536 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -5,11 +5,9 @@ using System; using System.Collections.Generic; using System.Linq; using osu.Framework.Extensions; -using osu.Game.Beatmaps; using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Mods; -using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs index 2505300425..2d9b95ae88 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; using System.Linq; using osu.Framework.Extensions; -using osu.Game.Beatmaps; using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Scoring; From 2b1ef16f89ef4e7d9b1646ee0fe5d183176d49fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 2 Oct 2020 22:57:49 +0200 Subject: [PATCH 1459/1782] Replace comparison references to HitResult.Miss with IsHit --- osu.Game.Rulesets.Catch/UI/CatchComboDisplay.cs | 2 +- osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs | 2 +- .../TestSceneMissHitWindowJudgements.cs | 4 ++-- osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs | 2 +- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs | 2 +- .../Objects/Drawables/DrawableOsuJudgement.cs | 2 +- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs | 2 +- osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs | 2 +- osu.Game.Rulesets.Taiko/Scoring/TaikoHealthProcessor.cs | 4 ++-- osu.Game.Rulesets.Taiko/Skinning/LegacyTaikoScroller.cs | 2 +- osu.Game.Rulesets.Taiko/UI/DefaultHitExplosion.cs | 2 +- osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs | 2 +- osu.Game/Screens/Play/HUD/StandardHealthDisplay.cs | 2 +- .../Ranking/Statistics/HitEventTimingDistributionGraph.cs | 2 +- osu.Game/Screens/Ranking/Statistics/UnstableRate.cs | 2 +- 15 files changed, 17 insertions(+), 17 deletions(-) diff --git a/osu.Game.Rulesets.Catch/UI/CatchComboDisplay.cs b/osu.Game.Rulesets.Catch/UI/CatchComboDisplay.cs index cc01009dd9..75feb21298 100644 --- a/osu.Game.Rulesets.Catch/UI/CatchComboDisplay.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchComboDisplay.cs @@ -36,7 +36,7 @@ namespace osu.Game.Rulesets.Catch.UI if (!result.Type.AffectsCombo() || !result.HasResult) return; - if (result.Type == HitResult.Miss) + if (!result.IsHit) { updateCombo(0, null); return; diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs index ba6cad978d..f6d539c91b 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs @@ -243,7 +243,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables endHold(); } - if (Tail.Result.Type == HitResult.Miss) + if (Tail.Judged && !Tail.IsHit) HasBroken = true; } diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneMissHitWindowJudgements.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneMissHitWindowJudgements.cs index f3221ffe32..39deba2f57 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneMissHitWindowJudgements.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneMissHitWindowJudgements.cs @@ -40,7 +40,7 @@ namespace osu.Game.Rulesets.Osu.Tests { HitObjects = { new HitCircle { Position = new Vector2(256, 192) } } }, - PassCondition = () => Player.Results.Count > 0 && Player.Results[0].TimeOffset < -hitWindows.WindowFor(HitResult.Meh) && Player.Results[0].Type == HitResult.Miss + PassCondition = () => Player.Results.Count > 0 && Player.Results[0].TimeOffset < -hitWindows.WindowFor(HitResult.Meh) && !Player.Results[0].IsHit }); } @@ -59,7 +59,7 @@ namespace osu.Game.Rulesets.Osu.Tests { Autoplay = false, Beatmap = beatmap, - PassCondition = () => Player.Results.Count > 0 && Player.Results[0].TimeOffset >= hitWindows.WindowFor(HitResult.Meh) && Player.Results[0].Type == HitResult.Miss + PassCondition = () => Player.Results.Count > 0 && Player.Results[0].TimeOffset >= hitWindows.WindowFor(HitResult.Meh) && !Player.Results[0].IsHit }); } diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs index d5c3538c81..844449851f 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs @@ -314,7 +314,7 @@ namespace osu.Game.Rulesets.Osu.Tests private bool assertMaxJudge() => judgementResults.Any() && judgementResults.All(t => t.Type == t.Judgement.MaxResult); - private bool assertHeadMissTailTracked() => judgementResults[^2].Type == HitResult.IgnoreHit && judgementResults.First().Type == HitResult.Miss; + private bool assertHeadMissTailTracked() => judgementResults[^2].Type == HitResult.IgnoreHit && !judgementResults.First().IsHit; private bool assertMidSliderJudgements() => judgementResults[^2].Type == HitResult.IgnoreHit; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs index a438dc8be4..6d6bd7fc97 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs @@ -143,7 +143,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables var circleResult = (OsuHitCircleJudgementResult)r; // Todo: This should also consider misses, but they're a little more interesting to handle, since we don't necessarily know the position at the time of a miss. - if (result != HitResult.Miss) + if (result.IsHit()) { var localMousePosition = ToLocalSpace(inputManager.CurrentState.Mouse.Position); circleResult.CursorPositionAtHit = HitObject.StackedPosition + (localMousePosition - DrawSize / 2); diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs index 012d9f8878..46f6276a85 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs @@ -67,7 +67,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables if (JudgedObject != null) { lightingColour = JudgedObject.AccentColour.GetBoundCopy(); - lightingColour.BindValueChanged(colour => Lighting.Colour = Result.Type == HitResult.Miss ? Color4.Transparent : colour.NewValue, true); + lightingColour.BindValueChanged(colour => Lighting.Colour = Result.IsHit ? colour.NewValue : Color4.Transparent, true); } else { diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index 9abcef83c4..21c7d49961 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -250,7 +250,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { // rather than doing it this way, we should probably attach the sample to the tail circle. // this can only be done after we stop using LegacyLastTick. - if (TailCircle.Result.Type != HitResult.Miss) + if (TailCircle.IsHit) base.PlaySamples(); } diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs index 286feac5ba..677e63c993 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs @@ -107,7 +107,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables if (!(obj is DrawableDrumRollTick)) return; - if (result.Type > HitResult.Miss) + if (result.IsHit) rollingHits++; else rollingHits--; diff --git a/osu.Game.Rulesets.Taiko/Scoring/TaikoHealthProcessor.cs b/osu.Game.Rulesets.Taiko/Scoring/TaikoHealthProcessor.cs index dd3c2289ea..f7a1d130eb 100644 --- a/osu.Game.Rulesets.Taiko/Scoring/TaikoHealthProcessor.cs +++ b/osu.Game.Rulesets.Taiko/Scoring/TaikoHealthProcessor.cs @@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Taiko.Scoring private double hpMultiplier; /// - /// HP multiplier for a . + /// HP multiplier for a that does not satisfy . /// private double hpMissMultiplier; @@ -45,6 +45,6 @@ namespace osu.Game.Rulesets.Taiko.Scoring } protected override double GetHealthIncreaseFor(JudgementResult result) - => base.GetHealthIncreaseFor(result) * (result.Type == HitResult.Miss ? hpMissMultiplier : hpMultiplier); + => base.GetHealthIncreaseFor(result) * (result.IsHit ? hpMultiplier : hpMissMultiplier); } } diff --git a/osu.Game.Rulesets.Taiko/Skinning/LegacyTaikoScroller.cs b/osu.Game.Rulesets.Taiko/Skinning/LegacyTaikoScroller.cs index 928072c491..e029040ef3 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/LegacyTaikoScroller.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/LegacyTaikoScroller.cs @@ -45,7 +45,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning if (r?.Type.AffectsCombo() == false) return; - passing = r == null || r.Type > HitResult.Miss; + passing = r == null || r.IsHit; foreach (var sprite in InternalChildren.OfType()) sprite.Passing = passing; diff --git a/osu.Game.Rulesets.Taiko/UI/DefaultHitExplosion.cs b/osu.Game.Rulesets.Taiko/UI/DefaultHitExplosion.cs index 7b8ab89233..3bd20e4bb4 100644 --- a/osu.Game.Rulesets.Taiko/UI/DefaultHitExplosion.cs +++ b/osu.Game.Rulesets.Taiko/UI/DefaultHitExplosion.cs @@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Taiko.UI Alpha = 0.15f; Masking = true; - if (result == HitResult.Miss) + if (!result.IsHit()) return; bool isRim = (judgedObject.HitObject as Hit)?.Type == HitType.Rim; diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 29c25f20a4..9af7ae12a2 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -511,7 +511,7 @@ namespace osu.Game.Rulesets.Objects.Drawables case HitResult.None: break; - case HitResult.Miss: + case { } result when !result.IsHit(): updateState(ArmedState.Miss); break; diff --git a/osu.Game/Screens/Play/HUD/StandardHealthDisplay.cs b/osu.Game/Screens/Play/HUD/StandardHealthDisplay.cs index 7736541c92..aff5a36c81 100644 --- a/osu.Game/Screens/Play/HUD/StandardHealthDisplay.cs +++ b/osu.Game/Screens/Play/HUD/StandardHealthDisplay.cs @@ -106,7 +106,7 @@ namespace osu.Game.Screens.Play.HUD public void Flash(JudgementResult result) { - if (result.Type == HitResult.Miss) + if (!result.IsHit) return; Scheduler.AddOnce(flash); diff --git a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs index 45fdc3ff33..aa2a83774e 100644 --- a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs +++ b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs @@ -48,7 +48,7 @@ namespace osu.Game.Screens.Ranking.Statistics /// The s to display the timing distribution of. public HitEventTimingDistributionGraph(IReadOnlyList hitEvents) { - this.hitEvents = hitEvents.Where(e => !(e.HitObject.HitWindows is HitWindows.EmptyHitWindows) && e.Result != HitResult.Miss).ToList(); + this.hitEvents = hitEvents.Where(e => !(e.HitObject.HitWindows is HitWindows.EmptyHitWindows) && e.Result.IsHit()).ToList(); } [BackgroundDependencyLoader] diff --git a/osu.Game/Screens/Ranking/Statistics/UnstableRate.cs b/osu.Game/Screens/Ranking/Statistics/UnstableRate.cs index 18a2238784..055db143d1 100644 --- a/osu.Game/Screens/Ranking/Statistics/UnstableRate.cs +++ b/osu.Game/Screens/Ranking/Statistics/UnstableRate.cs @@ -20,7 +20,7 @@ namespace osu.Game.Screens.Ranking.Statistics public UnstableRate(IEnumerable hitEvents) : base("Unstable Rate") { - var timeOffsets = hitEvents.Where(e => !(e.HitObject.HitWindows is HitWindows.EmptyHitWindows) && e.Result != HitResult.Miss) + var timeOffsets = hitEvents.Where(e => !(e.HitObject.HitWindows is HitWindows.EmptyHitWindows) && e.Result.IsHit()) .Select(ev => ev.TimeOffset).ToArray(); Value = 10 * standardDeviation(timeOffsets); } From 1f0620ffd49921a28a4edf9abcc61805f97d3243 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 2 Oct 2020 22:58:10 +0200 Subject: [PATCH 1460/1782] Replace assignment references to HitResult.Miss with Judgement.MinResult --- .../Objects/Drawables/DrawableHoldNoteTail.cs | 2 +- .../Objects/Drawables/DrawableManiaHitObject.cs | 3 +-- osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs | 2 +- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs | 2 +- .../Objects/Drawables/DrawableOsuHitObject.cs | 3 +-- .../Objects/Drawables/DrawableOsuJudgement.cs | 1 - osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs | 1 - osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs | 2 +- osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs | 2 +- osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs | 4 ++-- osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs | 4 +--- osu.Game/Screens/Play/HUD/StandardHealthDisplay.cs | 1 - 12 files changed, 10 insertions(+), 17 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTail.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTail.cs index 31e43d3ee2..c780c0836e 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTail.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTail.cs @@ -40,7 +40,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables if (!userTriggered) { if (!HitObject.HitWindows.CanBeHit(timeOffset)) - ApplyResult(r => r.Type = HitResult.Miss); + ApplyResult(r => r.Type = r.Judgement.MinResult); return; } diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs index 08c41b0d75..27960b3f3a 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs @@ -9,7 +9,6 @@ using osu.Framework.Graphics; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Rulesets.Mania.UI; -using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Mania.Objects.Drawables { @@ -136,7 +135,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables /// /// Causes this to get missed, disregarding all conditions in implementations of . /// - public void MissForcefully() => ApplyResult(r => r.Type = HitResult.Miss); + public void MissForcefully() => ApplyResult(r => r.Type = r.Judgement.MinResult); } public abstract class DrawableManiaHitObject : DrawableManiaHitObject diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs index 973dc06e05..b3402d13e4 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs @@ -48,7 +48,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables if (!userTriggered) { if (!HitObject.HitWindows.CanBeHit(timeOffset)) - ApplyResult(r => r.Type = HitResult.Miss); + ApplyResult(r => r.Type = r.Judgement.MinResult); return; } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs index 6d6bd7fc97..b5ac26c824 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs @@ -125,7 +125,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables if (!userTriggered) { if (!HitObject.HitWindows.CanBeHit(timeOffset)) - ApplyResult(r => r.Type = HitResult.Miss); + ApplyResult(r => r.Type = r.Judgement.MinResult); return; } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs index 2946331bc6..45c664ba3b 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs @@ -8,7 +8,6 @@ using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Osu.Judgements; using osu.Game.Graphics.Containers; using osu.Game.Rulesets.Osu.UI; -using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Osu.Objects.Drawables { @@ -68,7 +67,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables /// /// Causes this to get missed, disregarding all conditions in implementations of . /// - public void MissForcefully() => ApplyResult(r => r.Type = HitResult.Miss); + public void MissForcefully() => ApplyResult(r => r.Type = r.Judgement.MinResult); protected override JudgementResult CreateResult(Judgement judgement) => new OsuJudgementResult(HitObject, judgement); } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs index 46f6276a85..49535e7fff 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs @@ -8,7 +8,6 @@ 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; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index 21c7d49961..280ca33234 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -13,7 +13,6 @@ using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Skinning; using osu.Game.Rulesets.Osu.UI; -using osu.Game.Rulesets.Scoring; using osuTK.Graphics; using osu.Game.Skinning; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index fe7cb278b0..130b4e6e53 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -224,7 +224,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables else if (Progress > .75) r.Type = HitResult.Meh; else if (Time.Current >= Spinner.EndTime) - r.Type = HitResult.Miss; + r.Type = r.Judgement.MinResult; }); } diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs index 677e63c993..8f268dc1c7 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs @@ -132,7 +132,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables ApplyResult(r => r.Type = countHit >= HitObject.RequiredGreatHits ? HitResult.Great : HitResult.Ok); } else - ApplyResult(r => r.Type = HitResult.Miss); + ApplyResult(r => r.Type = r.Judgement.MinResult); } protected override void UpdateStateTransforms(ArmedState state) diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs index 03df28f850..bb42240f25 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs @@ -143,7 +143,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables if (!userTriggered) { if (!HitObject.HitWindows.CanBeHit(timeOffset)) - ApplyResult(r => r.Type = HitResult.Miss); + ApplyResult(r => r.Type = r.Judgement.MinResult); return; } @@ -152,7 +152,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables return; if (!validActionPressed) - ApplyResult(r => r.Type = HitResult.Miss); + ApplyResult(r => r.Type = r.Judgement.MinResult); else ApplyResult(r => r.Type = result); } diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs index 11ff0729e2..8ee4a5db71 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs @@ -211,9 +211,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables tick.TriggerResult(false); } - var hitResult = numHits > HitObject.RequiredHits / 2 ? HitResult.Ok : HitResult.Miss; - - ApplyResult(r => r.Type = hitResult); + ApplyResult(r => r.Type = numHits > HitObject.RequiredHits / 2 ? HitResult.Ok : r.Judgement.MinResult); } } diff --git a/osu.Game/Screens/Play/HUD/StandardHealthDisplay.cs b/osu.Game/Screens/Play/HUD/StandardHealthDisplay.cs index aff5a36c81..fc4a1a5d83 100644 --- a/osu.Game/Screens/Play/HUD/StandardHealthDisplay.cs +++ b/osu.Game/Screens/Play/HUD/StandardHealthDisplay.cs @@ -13,7 +13,6 @@ using osuTK; using osuTK.Graphics; using osu.Framework.Graphics.Shapes; using osu.Framework.Utils; -using osu.Game.Rulesets.Scoring; namespace osu.Game.Screens.Play.HUD { From 7359c422dd377f7b589bbb12ce8662c41060124d Mon Sep 17 00:00:00 2001 From: Shane Woolcock Date: Sat, 3 Oct 2020 12:58:43 +0930 Subject: [PATCH 1461/1782] Hoist icon stream --- osu.Desktop/OsuGameDesktop.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Desktop/OsuGameDesktop.cs b/osu.Desktop/OsuGameDesktop.cs index 659730630a..836b968a67 100644 --- a/osu.Desktop/OsuGameDesktop.cs +++ b/osu.Desktop/OsuGameDesktop.cs @@ -125,12 +125,14 @@ namespace osu.Desktop { base.SetHost(host); + var iconStream = Assembly.GetExecutingAssembly().GetManifestResourceStream(GetType(), "lazer.ico"); + switch (host.Window) { // Legacy osuTK DesktopGameWindow case DesktopGameWindow desktopGameWindow: desktopGameWindow.CursorState |= CursorState.Hidden; - desktopGameWindow.SetIconFromStream(Assembly.GetExecutingAssembly().GetManifestResourceStream(GetType(), "lazer.ico")); + desktopGameWindow.SetIconFromStream(iconStream); desktopGameWindow.Title = Name; desktopGameWindow.FileDrop += (_, e) => fileDrop(e.FileNames); break; @@ -138,7 +140,7 @@ namespace osu.Desktop // SDL2 DesktopWindow case DesktopWindow desktopWindow: desktopWindow.CursorState.Value |= CursorState.Hidden; - desktopWindow.SetIconFromStream(Assembly.GetExecutingAssembly().GetManifestResourceStream(GetType(), "lazer.ico")); + desktopWindow.SetIconFromStream(iconStream); desktopWindow.Title = Name; desktopWindow.DragDrop += f => fileDrop(new[] { f }); break; From 2ddfd799230c0336bb3ffa6b215281032e996ad0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 3 Oct 2020 08:09:10 +0200 Subject: [PATCH 1462/1782] Replace object pattern match with simple conditional --- .../Objects/Drawables/DrawableHitObject.cs | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 9af7ae12a2..66fc61720a 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -506,19 +506,8 @@ namespace osu.Game.Rulesets.Objects.Drawables Result.TimeOffset = Math.Min(HitObject.HitWindows.WindowFor(HitResult.Miss), Time.Current - endTime); - switch (Result.Type) - { - case HitResult.None: - break; - - case { } result when !result.IsHit(): - updateState(ArmedState.Miss); - break; - - default: - updateState(ArmedState.Hit); - break; - } + if (Result.HasResult) + updateState(Result.IsHit ? ArmedState.Hit : ArmedState.Miss); OnNewResult?.Invoke(this, Result); } From feb39920c5d9e3d5353ab71eb5be230767af1273 Mon Sep 17 00:00:00 2001 From: tytydraco Date: Sat, 3 Oct 2020 00:48:49 -0700 Subject: [PATCH 1463/1782] Allow rotation lock on Android to function properly According to Google's documentation, fullSensor will ignore rotation locking preferences, while fullUser will obey them. Signed-off-by: tytydraco --- 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 9839d16030..db73bb7e7f 100644 --- a/osu.Android/OsuGameActivity.cs +++ b/osu.Android/OsuGameActivity.cs @@ -9,7 +9,7 @@ using osu.Framework.Android; namespace osu.Android { - [Activity(Theme = "@android:style/Theme.NoTitleBar", MainLauncher = true, ScreenOrientation = ScreenOrientation.FullSensor, SupportsPictureInPicture = false, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize, HardwareAccelerated = false)] + [Activity(Theme = "@android:style/Theme.NoTitleBar", MainLauncher = true, ScreenOrientation = ScreenOrientation.FullUser, SupportsPictureInPicture = false, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize, HardwareAccelerated = false)] public class OsuGameActivity : AndroidGameActivity { protected override Framework.Game CreateGame() => new OsuGameAndroid(); From 309714081fa99023d06560f19b293acee4783df1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 3 Oct 2020 11:08:51 +0200 Subject: [PATCH 1464/1782] Make new health increase values mania-specific --- .../Judgements/ManiaJudgement.cs | 30 +++++++++++++++++++ osu.Game/Rulesets/Judgements/Judgement.cs | 10 +++---- 2 files changed, 35 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Judgements/ManiaJudgement.cs b/osu.Game.Rulesets.Mania/Judgements/ManiaJudgement.cs index 220dedc4a4..d28b7bdf58 100644 --- a/osu.Game.Rulesets.Mania/Judgements/ManiaJudgement.cs +++ b/osu.Game.Rulesets.Mania/Judgements/ManiaJudgement.cs @@ -2,10 +2,40 @@ // See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Mania.Judgements { public class ManiaJudgement : Judgement { + protected override double HealthIncreaseFor(HitResult result) + { + switch (result) + { + case HitResult.LargeTickHit: + return DEFAULT_MAX_HEALTH_INCREASE * 0.1; + + case HitResult.LargeTickMiss: + return -DEFAULT_MAX_HEALTH_INCREASE * 0.1; + + case HitResult.Meh: + return -DEFAULT_MAX_HEALTH_INCREASE * 0.5; + + case HitResult.Ok: + return -DEFAULT_MAX_HEALTH_INCREASE * 0.3; + + case HitResult.Good: + return DEFAULT_MAX_HEALTH_INCREASE * 0.1; + + case HitResult.Great: + return DEFAULT_MAX_HEALTH_INCREASE * 0.8; + + case HitResult.Perfect: + return DEFAULT_MAX_HEALTH_INCREASE; + + default: + return base.HealthIncreaseFor(result); + } + } } } diff --git a/osu.Game/Rulesets/Judgements/Judgement.cs b/osu.Game/Rulesets/Judgements/Judgement.cs index 4ee0ce437c..5d7444e9b0 100644 --- a/osu.Game/Rulesets/Judgements/Judgement.cs +++ b/osu.Game/Rulesets/Judgements/Judgement.cs @@ -124,19 +124,19 @@ namespace osu.Game.Rulesets.Judgements return -DEFAULT_MAX_HEALTH_INCREASE; case HitResult.Meh: - return -DEFAULT_MAX_HEALTH_INCREASE * 0.5; + return -DEFAULT_MAX_HEALTH_INCREASE * 0.05; case HitResult.Ok: - return -DEFAULT_MAX_HEALTH_INCREASE * 0.3; + return DEFAULT_MAX_HEALTH_INCREASE * 0.5; case HitResult.Good: - return DEFAULT_MAX_HEALTH_INCREASE * 0.1; + return DEFAULT_MAX_HEALTH_INCREASE * 0.75; case HitResult.Great: - return DEFAULT_MAX_HEALTH_INCREASE * 0.8; + return DEFAULT_MAX_HEALTH_INCREASE; case HitResult.Perfect: - return DEFAULT_MAX_HEALTH_INCREASE; + return DEFAULT_MAX_HEALTH_INCREASE * 1.05; case HitResult.SmallBonus: return DEFAULT_MAX_HEALTH_INCREASE * 0.1; From 601675db073b9f12f83a20f8462825ef3d19a725 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 3 Oct 2020 11:10:08 +0200 Subject: [PATCH 1465/1782] Adjust health increase values to match old ones better --- osu.Game/Rulesets/Judgements/Judgement.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game/Rulesets/Judgements/Judgement.cs b/osu.Game/Rulesets/Judgements/Judgement.cs index 5d7444e9b0..89a3a2b855 100644 --- a/osu.Game/Rulesets/Judgements/Judgement.cs +++ b/osu.Game/Rulesets/Judgements/Judgement.cs @@ -109,16 +109,16 @@ namespace osu.Game.Rulesets.Judgements return 0; case HitResult.SmallTickHit: - return DEFAULT_MAX_HEALTH_INCREASE * 0.05; + return DEFAULT_MAX_HEALTH_INCREASE * 0.5; case HitResult.SmallTickMiss: - return -DEFAULT_MAX_HEALTH_INCREASE * 0.05; + return -DEFAULT_MAX_HEALTH_INCREASE * 0.5; case HitResult.LargeTickHit: - return DEFAULT_MAX_HEALTH_INCREASE * 0.1; + return DEFAULT_MAX_HEALTH_INCREASE; case HitResult.LargeTickMiss: - return -DEFAULT_MAX_HEALTH_INCREASE * 0.1; + return -DEFAULT_MAX_HEALTH_INCREASE; case HitResult.Miss: return -DEFAULT_MAX_HEALTH_INCREASE; @@ -139,10 +139,10 @@ namespace osu.Game.Rulesets.Judgements return DEFAULT_MAX_HEALTH_INCREASE * 1.05; case HitResult.SmallBonus: - return DEFAULT_MAX_HEALTH_INCREASE * 0.1; + return DEFAULT_MAX_HEALTH_INCREASE * 0.5; case HitResult.LargeBonus: - return DEFAULT_MAX_HEALTH_INCREASE * 0.2; + return DEFAULT_MAX_HEALTH_INCREASE; } } From db31280671cf969e09000ac135455094dc1c012d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 3 Oct 2020 11:10:28 +0200 Subject: [PATCH 1466/1782] Award health for completed slider tails --- osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs b/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs index aff3f38e17..3afd36669f 100644 --- a/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs @@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Osu.Objects public class SliderTailJudgement : OsuJudgement { - public override HitResult MaxResult => HitResult.IgnoreHit; + public override HitResult MaxResult => HitResult.SmallTickHit; } } } From 682b5fb056ae9ecc50dd863b6490f851d1508a70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 3 Oct 2020 11:41:26 +0200 Subject: [PATCH 1467/1782] Adjust health increase for drum roll tick to match new max result --- .../Judgements/TaikoDrumRollTickJudgement.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/Judgements/TaikoDrumRollTickJudgement.cs b/osu.Game.Rulesets.Taiko/Judgements/TaikoDrumRollTickJudgement.cs index 0551df3211..647ad7853d 100644 --- a/osu.Game.Rulesets.Taiko/Judgements/TaikoDrumRollTickJudgement.cs +++ b/osu.Game.Rulesets.Taiko/Judgements/TaikoDrumRollTickJudgement.cs @@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.Taiko.Judgements { switch (result) { - case HitResult.Great: + case HitResult.SmallTickHit: return 0.15; default: From 7e7f225eee6bd5ea896944d65c509d5b87f1bf95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 3 Oct 2020 12:34:34 +0200 Subject: [PATCH 1468/1782] Adjust slider input test to match new judgement result --- osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs index d5c3538c81..1810ef4353 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs @@ -314,11 +314,11 @@ namespace osu.Game.Rulesets.Osu.Tests private bool assertMaxJudge() => judgementResults.Any() && judgementResults.All(t => t.Type == t.Judgement.MaxResult); - private bool assertHeadMissTailTracked() => judgementResults[^2].Type == HitResult.IgnoreHit && judgementResults.First().Type == HitResult.Miss; + private bool assertHeadMissTailTracked() => judgementResults[^2].Type == HitResult.SmallTickHit && judgementResults.First().Type == HitResult.Miss; - private bool assertMidSliderJudgements() => judgementResults[^2].Type == HitResult.IgnoreHit; + private bool assertMidSliderJudgements() => judgementResults[^2].Type == HitResult.SmallTickHit; - private bool assertMidSliderJudgementFail() => judgementResults[^2].Type == HitResult.IgnoreMiss; + private bool assertMidSliderJudgementFail() => judgementResults[^2].Type == HitResult.SmallTickMiss; private ScoreAccessibleReplayPlayer currentPlayer; From d87e4c524c1c03539881df35fa97ccf800751aae Mon Sep 17 00:00:00 2001 From: Endrik Tombak Date: Sat, 3 Oct 2020 14:21:40 +0300 Subject: [PATCH 1469/1782] Test HitResultExtensions methods --- .../Rulesets/Scoring/ScoreProcessorTest.cs | 100 ++++++++++++++++++ 1 file changed, 100 insertions(+) diff --git a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs index ace57aad1d..38d2b4a47f 100644 --- a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs +++ b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs @@ -53,5 +53,105 @@ namespace osu.Game.Tests.Rulesets.Scoring Assert.IsTrue(Precision.AlmostEquals(expectedScore, scoreProcessor.TotalScore.Value)); } + + [TestCase(HitResult.None, false)] + [TestCase(HitResult.IgnoreMiss, false)] + [TestCase(HitResult.IgnoreHit, false)] + [TestCase(HitResult.Miss, true)] + [TestCase(HitResult.Meh, true)] + [TestCase(HitResult.Ok, true)] + [TestCase(HitResult.Good, true)] + [TestCase(HitResult.Great, true)] + [TestCase(HitResult.Perfect, true)] + [TestCase(HitResult.SmallTickMiss, false)] + [TestCase(HitResult.SmallTickHit, false)] + [TestCase(HitResult.LargeTickMiss, true)] + [TestCase(HitResult.LargeTickHit, true)] + [TestCase(HitResult.SmallBonus, false)] + [TestCase(HitResult.LargeBonus, false)] + public void TestAffectsCombo(HitResult hitResult, bool expectedReturnValue) + { + Assert.IsTrue(hitResult.AffectsCombo() == expectedReturnValue); + } + + [TestCase(HitResult.None, false)] + [TestCase(HitResult.IgnoreMiss, false)] + [TestCase(HitResult.IgnoreHit, false)] + [TestCase(HitResult.Miss, true)] + [TestCase(HitResult.Meh, true)] + [TestCase(HitResult.Ok, true)] + [TestCase(HitResult.Good, true)] + [TestCase(HitResult.Great, true)] + [TestCase(HitResult.Perfect, true)] + [TestCase(HitResult.SmallTickMiss, true)] + [TestCase(HitResult.SmallTickHit, true)] + [TestCase(HitResult.LargeTickMiss, true)] + [TestCase(HitResult.LargeTickHit, true)] + [TestCase(HitResult.SmallBonus, false)] + [TestCase(HitResult.LargeBonus, false)] + public void TestAffectsAccuracy(HitResult hitResult, bool expectedReturnValue) + { + Assert.IsTrue(hitResult.AffectsAccuracy() == expectedReturnValue); + } + + [TestCase(HitResult.None, false)] + [TestCase(HitResult.IgnoreMiss, false)] + [TestCase(HitResult.IgnoreHit, false)] + [TestCase(HitResult.Miss, false)] + [TestCase(HitResult.Meh, false)] + [TestCase(HitResult.Ok, false)] + [TestCase(HitResult.Good, false)] + [TestCase(HitResult.Great, false)] + [TestCase(HitResult.Perfect, false)] + [TestCase(HitResult.SmallTickMiss, false)] + [TestCase(HitResult.SmallTickHit, false)] + [TestCase(HitResult.LargeTickMiss, false)] + [TestCase(HitResult.LargeTickHit, false)] + [TestCase(HitResult.SmallBonus, true)] + [TestCase(HitResult.LargeBonus, true)] + public void TestIsBonus(HitResult hitResult, bool expectedReturnValue) + { + Assert.IsTrue(hitResult.IsBonus() == expectedReturnValue); + } + + [TestCase(HitResult.None, false)] + [TestCase(HitResult.IgnoreMiss, false)] + [TestCase(HitResult.IgnoreHit, true)] + [TestCase(HitResult.Miss, false)] + [TestCase(HitResult.Meh, true)] + [TestCase(HitResult.Ok, true)] + [TestCase(HitResult.Good, true)] + [TestCase(HitResult.Great, true)] + [TestCase(HitResult.Perfect, true)] + [TestCase(HitResult.SmallTickMiss, false)] + [TestCase(HitResult.SmallTickHit, true)] + [TestCase(HitResult.LargeTickMiss, false)] + [TestCase(HitResult.LargeTickHit, true)] + [TestCase(HitResult.SmallBonus, true)] + [TestCase(HitResult.LargeBonus, true)] + public void TestIsHit(HitResult hitResult, bool expectedReturnValue) + { + Assert.IsTrue(hitResult.IsHit() == expectedReturnValue); + } + + [TestCase(HitResult.None, false)] + [TestCase(HitResult.IgnoreMiss, false)] + [TestCase(HitResult.IgnoreHit, false)] + [TestCase(HitResult.Miss, true)] + [TestCase(HitResult.Meh, true)] + [TestCase(HitResult.Ok, true)] + [TestCase(HitResult.Good, true)] + [TestCase(HitResult.Great, true)] + [TestCase(HitResult.Perfect, true)] + [TestCase(HitResult.SmallTickMiss, true)] + [TestCase(HitResult.SmallTickHit, true)] + [TestCase(HitResult.LargeTickMiss, true)] + [TestCase(HitResult.LargeTickHit, true)] + [TestCase(HitResult.SmallBonus, true)] + [TestCase(HitResult.LargeBonus, true)] + public void TestIsScorable(HitResult hitResult, bool expectedReturnValue) + { + Assert.IsTrue(hitResult.IsScorable() == expectedReturnValue); + } } } From d7747ebb2d5ba27bf6e5272e381dbdafc6c921bc Mon Sep 17 00:00:00 2001 From: Lucas A Date: Sat, 3 Oct 2020 16:51:22 +0200 Subject: [PATCH 1470/1782] Remove unused WorkingBeatmap argument. --- osu.Game.Rulesets.Catch/CatchRuleset.cs | 2 +- osu.Game.Rulesets.Mania/ManiaRuleset.cs | 2 +- osu.Game.Rulesets.Osu/OsuRuleset.cs | 2 +- osu.Game.Rulesets.Taiko/TaikoRuleset.cs | 2 +- osu.Game/Rulesets/Ruleset.cs | 10 +++++++++- 5 files changed, 13 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Catch/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs index cb7cac436b..1f27de3352 100644 --- a/osu.Game.Rulesets.Catch/CatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs @@ -145,7 +145,7 @@ namespace osu.Game.Rulesets.Catch public override ISkin CreateLegacySkinProvider(ISkinSource source, IBeatmap beatmap) => new CatchLegacySkinTransformer(source); - public override PerformanceCalculator CreatePerformanceCalculator(WorkingBeatmap beatmap, DifficultyAttributes attributes, ScoreInfo score) => new CatchPerformanceCalculator(this, attributes, score); + public override PerformanceCalculator CreatePerformanceCalculator(DifficultyAttributes attributes, ScoreInfo score) => new CatchPerformanceCalculator(this, attributes, score); public int LegacyID => 2; diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index 8bf6b5e064..ecb09ebe85 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -49,7 +49,7 @@ namespace osu.Game.Rulesets.Mania public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new ManiaBeatmapConverter(beatmap, this); - public override PerformanceCalculator CreatePerformanceCalculator(WorkingBeatmap beatmap, DifficultyAttributes attributes, ScoreInfo score) => new ManiaPerformanceCalculator(this, attributes, score); + public override PerformanceCalculator CreatePerformanceCalculator(DifficultyAttributes attributes, ScoreInfo score) => new ManiaPerformanceCalculator(this, attributes, score); public const string SHORT_NAME = "mania"; diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index 9798f15f21..cc2eebdd36 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -171,7 +171,7 @@ namespace osu.Game.Rulesets.Osu public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new OsuDifficultyCalculator(this, beatmap); - public override PerformanceCalculator CreatePerformanceCalculator(WorkingBeatmap beatmap, DifficultyAttributes attributes, ScoreInfo score) => new OsuPerformanceCalculator(this, attributes, score); + public override PerformanceCalculator CreatePerformanceCalculator(DifficultyAttributes attributes, ScoreInfo score) => new OsuPerformanceCalculator(this, attributes, score); public override HitObjectComposer CreateHitObjectComposer() => new OsuHitObjectComposer(this); diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index 3bc749b868..642eb0ddcc 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -153,7 +153,7 @@ namespace osu.Game.Rulesets.Taiko public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new TaikoDifficultyCalculator(this, beatmap); - public override PerformanceCalculator CreatePerformanceCalculator(WorkingBeatmap beatmap, DifficultyAttributes attributes, ScoreInfo score) => new TaikoPerformanceCalculator(this, attributes, score); + public override PerformanceCalculator CreatePerformanceCalculator(DifficultyAttributes attributes, ScoreInfo score) => new TaikoPerformanceCalculator(this, attributes, score); public int LegacyID => 1; diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index 25c5f41b5b..2ba884efc2 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -158,7 +158,15 @@ namespace osu.Game.Rulesets public abstract DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap); - public virtual PerformanceCalculator CreatePerformanceCalculator(WorkingBeatmap beatmap, DifficultyAttributes attributes, ScoreInfo score) => null; + public virtual PerformanceCalculator CreatePerformanceCalculator(DifficultyAttributes attributes, ScoreInfo score) => null; + + [Obsolete("Use the DifficultyAttributes overload instead.")] + public PerformanceCalculator CreatePerformanceCalculator(WorkingBeatmap beatmap, ScoreInfo score) + { + var difficultyCalculator = CreateDifficultyCalculator(beatmap); + var difficultyAttributes = difficultyCalculator.Calculate(score.Mods); + return CreatePerformanceCalculator(difficultyAttributes, score); + } public virtual HitObjectComposer CreateHitObjectComposer() => null; From 27cc6c50467616f63daa1b1de1e43a9306bc30f1 Mon Sep 17 00:00:00 2001 From: Lucas A Date: Sat, 3 Oct 2020 16:52:33 +0200 Subject: [PATCH 1471/1782] Rename HitCirclesCount -> HitCircleCount. --- osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs | 2 +- osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs | 2 +- osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs index 50f060cf06..fff033357d 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs @@ -11,6 +11,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty public double SpeedStrain; public double ApproachRate; public double OverallDifficulty; - public int HitCirclesCount; + public int HitCircleCount; } } diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs index 86c7cd2298..6027635b75 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs @@ -58,7 +58,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty ApproachRate = preempt > 1200 ? (1800 - preempt) / 120 : (1200 - preempt) / 150 + 5, OverallDifficulty = (80 - hitWindowGreat) / 6, MaxCombo = maxCombo, - HitCirclesCount = hitCirclesCount, + HitCircleCount = hitCirclesCount, Skills = skills }; } diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index fed0a12536..063cde8747 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -168,7 +168,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty { // This percentage only considers HitCircles of any value - in this part of the calculation we focus on hitting the timing hit window double betterAccuracyPercentage; - int amountHitObjectsWithAccuracy = Attributes.HitCirclesCount; + int amountHitObjectsWithAccuracy = Attributes.HitCircleCount; if (amountHitObjectsWithAccuracy > 0) betterAccuracyPercentage = ((countGreat - (totalHits - amountHitObjectsWithAccuracy)) * 6 + countOk * 2 + countMeh) / (double)(amountHitObjectsWithAccuracy * 6); From 5888ecdeb16a6db3d8acd2825f086c0fd323d5a7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 4 Oct 2020 01:08:24 +0900 Subject: [PATCH 1472/1782] Fix spinner crashing on rewind --- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index fe7cb278b0..0f249c8bbf 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -268,7 +268,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables while (wholeSpins != spins) { - var tick = ticks.FirstOrDefault(t => !t.IsHit); + var tick = ticks.FirstOrDefault(t => !t.Result.HasResult); // tick may be null if we've hit the spin limit. if (tick != null) From 26eff0120db42daed55b375cb22db982b8d60d38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 3 Oct 2020 21:11:34 +0200 Subject: [PATCH 1473/1782] Apply same fix for miss-triggering case See 5888ecd - the same fix is applied here, but in the miss case. --- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index 0f249c8bbf..5e1b7bdcae 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -212,7 +212,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables return; // Trigger a miss result for remaining ticks to avoid infinite gameplay. - foreach (var tick in ticks.Where(t => !t.IsHit)) + foreach (var tick in ticks.Where(t => !t.Result.HasResult)) tick.TriggerResult(false); ApplyResult(r => From ad42ce5639d5ae92dd6fc4e9bc067f446da04214 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 4 Oct 2020 14:50:25 +0200 Subject: [PATCH 1474/1782] Add failing test cases --- osu.Game.Tests/NonVisual/GameplayClockTest.cs | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 osu.Game.Tests/NonVisual/GameplayClockTest.cs diff --git a/osu.Game.Tests/NonVisual/GameplayClockTest.cs b/osu.Game.Tests/NonVisual/GameplayClockTest.cs new file mode 100644 index 0000000000..3fd7c364b7 --- /dev/null +++ b/osu.Game.Tests/NonVisual/GameplayClockTest.cs @@ -0,0 +1,39 @@ +// 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.Bindables; +using osu.Framework.Timing; +using osu.Game.Screens.Play; + +namespace osu.Game.Tests.NonVisual +{ + [TestFixture] + public class GameplayClockTest + { + [TestCase(0)] + [TestCase(1)] + public void TestTrueGameplayRateWithZeroAdjustment(double underlyingClockRate) + { + var framedClock = new FramedClock(new ManualClock { Rate = underlyingClockRate }); + var gameplayClock = new TestGameplayClock(framedClock); + + gameplayClock.MutableNonGameplayAdjustments.Add(new BindableDouble()); + + Assert.That(gameplayClock.TrueGameplayRate, Is.EqualTo(0)); + } + + private class TestGameplayClock : GameplayClock + { + public List> MutableNonGameplayAdjustments { get; } = new List>(); + + public override IEnumerable> NonGameplayAdjustments => MutableNonGameplayAdjustments; + + public TestGameplayClock(IFrameBasedClock underlyingClock) + : base(underlyingClock) + { + } + } + } +} From 6f2b991b329cb9b44980995e668ecc5d7e5980a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 4 Oct 2020 14:51:27 +0200 Subject: [PATCH 1475/1782] Ensure true gameplay rate is finite when paused externally --- osu.Game/Screens/Play/GameplayClock.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game/Screens/Play/GameplayClock.cs b/osu.Game/Screens/Play/GameplayClock.cs index 9d04722c12..9f2868573e 100644 --- a/osu.Game/Screens/Play/GameplayClock.cs +++ b/osu.Game/Screens/Play/GameplayClock.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Linq; using osu.Framework.Bindables; using osu.Framework.Timing; +using osu.Framework.Utils; namespace osu.Game.Screens.Play { @@ -47,7 +48,12 @@ namespace osu.Game.Screens.Play double baseRate = Rate; foreach (var adjustment in NonGameplayAdjustments) + { + if (Precision.AlmostEquals(adjustment.Value, 0)) + return 0; + baseRate /= adjustment.Value; + } return baseRate; } From 02e4f3ddafc4678df1965523d29296f058523708 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 4 Oct 2020 23:47:16 +0900 Subject: [PATCH 1476/1782] Fix the editor saving new beatmaps even when the user chooses not to --- osu.Game/Screens/Edit/Editor.cs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index a0692d94e6..956b77b0d4 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -84,6 +84,8 @@ namespace osu.Game.Screens.Edit private DependencyContainer dependencies; + private bool isNewBeatmap; + protected override UserActivity InitialActivity => new UserActivity.Editing(Beatmap.Value.BeatmapInfo); protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) @@ -113,8 +115,6 @@ namespace osu.Game.Screens.Edit // todo: remove caching of this and consume via editorBeatmap? dependencies.Cache(beatDivisor); - bool isNewBeatmap = false; - if (Beatmap.Value is DummyWorkingBeatmap) { isNewBeatmap = true; @@ -287,6 +287,9 @@ namespace osu.Game.Screens.Edit protected void Save() { + // no longer new after first user-triggered save. + isNewBeatmap = false; + // apply any set-level metadata changes. beatmapManager.Update(playableBeatmap.BeatmapInfo.BeatmapSet); @@ -435,7 +438,7 @@ namespace osu.Game.Screens.Edit public override bool OnExiting(IScreen next) { - if (!exitConfirmed && dialogOverlay != null && HasUnsavedChanges && !(dialogOverlay.CurrentDialog is PromptForSaveDialog)) + if (!exitConfirmed && dialogOverlay != null && (isNewBeatmap || HasUnsavedChanges) && !(dialogOverlay.CurrentDialog is PromptForSaveDialog)) { dialogOverlay?.Push(new PromptForSaveDialog(confirmExit, confirmExitWithSave)); return true; @@ -456,6 +459,12 @@ namespace osu.Game.Screens.Edit private void confirmExit() { + if (isNewBeatmap) + { + // confirming exit without save means we should delete the new beatmap completely. + beatmapManager.Delete(playableBeatmap.BeatmapInfo.BeatmapSet); + } + exitConfirmed = true; this.Exit(); } From 1b02c814d6ba33141cc71461ffc876c20e97035b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 4 Oct 2020 23:47:47 +0900 Subject: [PATCH 1477/1782] 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 78ceaa8616..d7817cf4cf 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,6 +52,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 3a839ac1a4..fa2135580d 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -24,7 +24,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 31f1af135d..20a51e5feb 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -80,7 +80,7 @@ - + From 9ca0e48accc80a4778c60b7049e994a7abd4d58e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 4 Oct 2020 23:57:28 +0900 Subject: [PATCH 1478/1782] Change exit logic to be more test-friendly --- osu.Game/Screens/Edit/Editor.cs | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 956b77b0d4..875ab25003 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -438,10 +438,20 @@ namespace osu.Game.Screens.Edit public override bool OnExiting(IScreen next) { - if (!exitConfirmed && dialogOverlay != null && (isNewBeatmap || HasUnsavedChanges) && !(dialogOverlay.CurrentDialog is PromptForSaveDialog)) + if (!exitConfirmed) { - dialogOverlay?.Push(new PromptForSaveDialog(confirmExit, confirmExitWithSave)); - return true; + // if the confirm dialog is already showing (or we can't show it, ie. in tests) exit without save. + if (dialogOverlay == null || dialogOverlay.CurrentDialog is PromptForSaveDialog) + { + confirmExit(); + return true; + } + + if (isNewBeatmap || HasUnsavedChanges) + { + dialogOverlay?.Push(new PromptForSaveDialog(confirmExit, confirmExitWithSave)); + return true; + } } Background.FadeColour(Color4.White, 500); From 432ba7cdf953f1806b914769518d0e6f1cf3c23c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 4 Oct 2020 23:57:35 +0900 Subject: [PATCH 1479/1782] Add test coverage of exit-without-save --- .../Editing/TestSceneEditorBeatmapCreation.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs index 720cf51f2c..13a3195824 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs @@ -5,6 +5,8 @@ using System; using System.IO; using System.Linq; using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Screens; using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Rulesets; @@ -22,6 +24,9 @@ namespace osu.Game.Tests.Visual.Editing protected override bool EditorComponentsReady => Editor.ChildrenOfType().SingleOrDefault()?.IsLoaded == true; + [Resolved] + private BeatmapManager beatmapManager { get; set; } + public override void SetUpSteps() { AddStep("set dummy", () => Beatmap.Value = new DummyWorkingBeatmap(Audio, null)); @@ -38,6 +43,15 @@ namespace osu.Game.Tests.Visual.Editing { AddStep("save beatmap", () => Editor.Save()); AddAssert("new beatmap persisted", () => EditorBeatmap.BeatmapInfo.ID > 0); + AddAssert("new beatmap in database", () => beatmapManager.QueryBeatmapSet(s => s.ID == EditorBeatmap.BeatmapInfo.BeatmapSet.ID)?.DeletePending == false); + } + + [Test] + public void TestExitWithoutSave() + { + AddStep("exit without save", () => Editor.Exit()); + AddUntilStep("wait for exit", () => !Editor.IsCurrentScreen()); + AddAssert("new beatmap not persisted", () => beatmapManager.QueryBeatmapSet(s => s.ID == EditorBeatmap.BeatmapInfo.BeatmapSet.ID)?.DeletePending == true); } [Test] From 5859755886ac3e141e00e72a421bf61d19d6524e Mon Sep 17 00:00:00 2001 From: Shane Woolcock Date: Mon, 5 Oct 2020 11:11:46 +1030 Subject: [PATCH 1480/1782] Use current OverlayActivationMode to determine confine logic --- osu.Game/Input/ConfineMouseTracker.cs | 33 +++++++++++---------------- osu.Game/Screens/Play/Player.cs | 5 ++-- 2 files changed, 15 insertions(+), 23 deletions(-) diff --git a/osu.Game/Input/ConfineMouseTracker.cs b/osu.Game/Input/ConfineMouseTracker.cs index b111488a5b..6565967d1d 100644 --- a/osu.Game/Input/ConfineMouseTracker.cs +++ b/osu.Game/Input/ConfineMouseTracker.cs @@ -7,44 +7,37 @@ using osu.Framework.Configuration; using osu.Framework.Graphics; using osu.Framework.Input; using osu.Game.Configuration; +using osu.Game.Overlays; using osu.Game.Screens.Play; namespace osu.Game.Input { /// - /// Connects with - /// while providing a property for to indicate whether gameplay is currently active. + /// Connects with , + /// while optionally binding an mode, usually that of the current . + /// It is assumed that while overlay activation is , we should also confine the + /// mouse cursor if it has been requested with . /// public class ConfineMouseTracker : Component { private Bindable frameworkConfineMode; private Bindable osuConfineMode; - private bool gameplayActive; - /// - /// Indicates whether osu! is currently considered "in gameplay" for the - /// purposes of . + /// The bindable used to indicate whether gameplay is active. + /// Should be bound to the corresponding bindable of the current . + /// Defaults to to assume that all other screens are considered "not gameplay". /// - public bool GameplayActive - { - get => gameplayActive; - set - { - if (gameplayActive == value) - return; - - gameplayActive = value; - updateConfineMode(); - } - } + public IBindable OverlayActivationMode { get; } = new Bindable(OverlayActivation.All); [BackgroundDependencyLoader] private void load(FrameworkConfigManager frameworkConfigManager, OsuConfigManager osuConfigManager) { frameworkConfineMode = frameworkConfigManager.GetBindable(FrameworkSetting.ConfineMouseMode); osuConfineMode = osuConfigManager.GetBindable(OsuSetting.ConfineMouseMode); - osuConfineMode.BindValueChanged(_ => updateConfineMode(), true); + osuConfineMode.ValueChanged += _ => updateConfineMode(); + + OverlayActivationMode.BindValueChanged(_ => updateConfineMode(), true); } private void updateConfineMode() @@ -60,7 +53,7 @@ namespace osu.Game.Input break; case OsuConfineMouseMode.DuringGameplay: - frameworkConfineMode.Value = GameplayActive ? ConfineMouseMode.Always : ConfineMouseMode.Never; + frameworkConfineMode.Value = OverlayActivationMode.Value == OverlayActivation.Disabled ? ConfineMouseMode.Always : ConfineMouseMode.Never; break; case OsuConfineMouseMode.Always: diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 77f873083a..6d2f61bdf1 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -229,6 +229,8 @@ namespace osu.Game.Screens.Play DrawableRuleset.HasReplayLoaded.BindValueChanged(_ => updatePauseOnFocusLostState(), true); + confineMouseTracker.OverlayActivationMode.BindTo(OverlayActivationMode); + // bind clock into components that require it DrawableRuleset.IsPaused.BindTo(GameplayClockContainer.IsPaused); @@ -365,9 +367,6 @@ namespace osu.Game.Screens.Play OverlayActivationMode.Value = OverlayActivation.UserTriggered; else OverlayActivationMode.Value = OverlayActivation.Disabled; - - if (confineMouseTracker != null) - confineMouseTracker.GameplayActive = !GameplayClockContainer.IsPaused.Value && !DrawableRuleset.HasReplayLoaded.Value && !HasFailed; } private void updatePauseOnFocusLostState() => From a483dfd2d7157131d886d8b7c92a4b08defdbf63 Mon Sep 17 00:00:00 2001 From: Shane Woolcock Date: Mon, 5 Oct 2020 11:54:39 +1030 Subject: [PATCH 1481/1782] Allow confineMouseTracker to be null --- 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 6d2f61bdf1..de67b2d46d 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -229,7 +229,7 @@ namespace osu.Game.Screens.Play DrawableRuleset.HasReplayLoaded.BindValueChanged(_ => updatePauseOnFocusLostState(), true); - confineMouseTracker.OverlayActivationMode.BindTo(OverlayActivationMode); + confineMouseTracker?.OverlayActivationMode.BindTo(OverlayActivationMode); // bind clock into components that require it DrawableRuleset.IsPaused.BindTo(GameplayClockContainer.IsPaused); From e1c4c8f3d5401ce298c4f3392a1464103a931385 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 5 Oct 2020 13:16:45 +0900 Subject: [PATCH 1482/1782] Add failing test coverage of gameplay sample pausing (during seek) --- .../TestSceneGameplaySamplePlayback.cs | 61 +++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySamplePlayback.cs diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySamplePlayback.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySamplePlayback.cs new file mode 100644 index 0000000000..3ab4df20df --- /dev/null +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySamplePlayback.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.Linq; +using NUnit.Framework; +using osu.Framework.Graphics.Audio; +using osu.Framework.Testing; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Game.Rulesets.UI; +using osu.Game.Screens.Play; +using osu.Game.Skinning; + +namespace osu.Game.Tests.Visual.Gameplay +{ + public class TestSceneGameplaySamplePlayback : PlayerTestScene + { + [Test] + public void TestAllSamplesStopDuringSeek() + { + DrawableSlider slider = null; + DrawableSample[] samples = null; + ISamplePlaybackDisabler gameplayClock = null; + + AddStep("get variables", () => + { + gameplayClock = Player.ChildrenOfType().First().GameplayClock; + slider = Player.ChildrenOfType().OrderBy(s => s.HitObject.StartTime).First(); + samples = slider.ChildrenOfType().ToArray(); + }); + + AddUntilStep("wait for slider sliding then seek", () => + { + if (!slider.Tracking.Value) + return false; + + if (!samples.Any(s => s.Playing)) + return false; + + Player.ChildrenOfType().First().Seek(40000); + return true; + }); + + AddAssert("sample playback disabled", () => gameplayClock.SamplePlaybackDisabled.Value); + + // because we are in frame stable context, it's quite likely that not all samples are "played" at this point. + // the important thing is that at least one started, and that sample has since stopped. + AddAssert("no samples are playing", () => Player.ChildrenOfType().All(s => !s.IsPlaying)); + + AddAssert("sample playback still disabled", () => gameplayClock.SamplePlaybackDisabled.Value); + + AddUntilStep("seek finished, sample playback enabled", () => !gameplayClock.SamplePlaybackDisabled.Value); + AddUntilStep("any sample is playing", () => Player.ChildrenOfType().Any(s => s.IsPlaying)); + } + + protected override bool Autoplay => true; + + protected override Ruleset CreatePlayerRuleset() => new OsuRuleset(); + } +} From 2a46f905ff130c465676019d2e9daed638543870 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 5 Oct 2020 12:45:37 +0900 Subject: [PATCH 1483/1782] Remove unnecessary IsSeeking checks from taiko drum implementation --- osu.Game.Rulesets.Taiko/UI/InputDrum.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/UI/InputDrum.cs b/osu.Game.Rulesets.Taiko/UI/InputDrum.cs index 5966b24b34..1ca1be1bdf 100644 --- a/osu.Game.Rulesets.Taiko/UI/InputDrum.cs +++ b/osu.Game.Rulesets.Taiko/UI/InputDrum.cs @@ -163,16 +163,14 @@ namespace osu.Game.Rulesets.Taiko.UI target = centreHit; back = centre; - if (gameplayClock?.IsSeeking != true) - drumSample.Centre?.Play(); + drumSample.Centre?.Play(); } else if (action == RimAction) { target = rimHit; back = rim; - if (gameplayClock?.IsSeeking != true) - drumSample.Rim?.Play(); + drumSample.Rim?.Play(); } if (target != null) From af7d10afe0f532e7d339a0c28675817cd0b11226 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 5 Oct 2020 12:45:57 +0900 Subject: [PATCH 1484/1782] Fix FrameStabilityContainer not re-caching its GameplayClock correctly --- osu.Game/Rulesets/UI/FrameStabilityContainer.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs index 55c4edfbd1..668cbbdc35 100644 --- a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs +++ b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs @@ -35,6 +35,7 @@ namespace osu.Game.Rulesets.UI public GameplayClock GameplayClock => stabilityGameplayClock; [Cached(typeof(GameplayClock))] + [Cached(typeof(ISamplePlaybackDisabler))] private readonly StabilityGameplayClock stabilityGameplayClock; public FrameStabilityContainer(double gameplayStartTime = double.MinValue) From e4710f82ec5a06258970ec01d9073838b8d7e581 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 5 Oct 2020 12:46:15 +0900 Subject: [PATCH 1485/1782] Fix sample disabled status not being updated correctly from seek state --- osu.Game/Rulesets/UI/FrameStabilityContainer.cs | 4 +++- osu.Game/Screens/Play/GameplayClock.cs | 12 ++++++++---- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs index 668cbbdc35..6956d3c31a 100644 --- a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs +++ b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs @@ -228,7 +228,9 @@ namespace osu.Game.Rulesets.UI { } - public override bool IsSeeking => ParentGameplayClock != null && Math.Abs(CurrentTime - ParentGameplayClock.CurrentTime) > 200; + protected override bool ShouldDisableSamplePlayback => + // handle the case where playback is catching up to real-time. + base.ShouldDisableSamplePlayback || (ParentGameplayClock != null && Math.Abs(CurrentTime - ParentGameplayClock.CurrentTime) > 200); } } } diff --git a/osu.Game/Screens/Play/GameplayClock.cs b/osu.Game/Screens/Play/GameplayClock.cs index 9f2868573e..eeea6777c6 100644 --- a/osu.Game/Screens/Play/GameplayClock.cs +++ b/osu.Game/Screens/Play/GameplayClock.cs @@ -28,6 +28,8 @@ namespace osu.Game.Screens.Play ///
public virtual IEnumerable> NonGameplayAdjustments => Enumerable.Empty>(); + private readonly Bindable samplePlaybackDisabled = new Bindable(); + public GameplayClock(IFrameBasedClock underlyingClock) { this.underlyingClock = underlyingClock; @@ -62,13 +64,15 @@ namespace osu.Game.Screens.Play public bool IsRunning => underlyingClock.IsRunning; /// - /// Whether an ongoing seek operation is active. + /// Whether nested samples supporting the interface should be paused. /// - public virtual bool IsSeeking => false; + protected virtual bool ShouldDisableSamplePlayback => IsPaused.Value; public void ProcessFrame() { - // we do not want to process the underlying clock. + // intentionally not updating the underlying clock (handled externally). + + samplePlaybackDisabled.Value = ShouldDisableSamplePlayback; } public double ElapsedFrameTime => underlyingClock.ElapsedFrameTime; @@ -79,6 +83,6 @@ namespace osu.Game.Screens.Play public IClock Source => underlyingClock; - public IBindable SamplePlaybackDisabled => IsPaused; + IBindable ISamplePlaybackDisabler.SamplePlaybackDisabled => samplePlaybackDisabled; } } From ae8bf8cdd4ca70eb5455b4389f4be459783b8c4a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 5 Oct 2020 12:47:00 +0900 Subject: [PATCH 1486/1782] Fix StabilityGameClock not being updated --- osu.Game/Rulesets/UI/FrameStabilityContainer.cs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs index 6956d3c31a..f32f8d177b 100644 --- a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs +++ b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs @@ -208,11 +208,15 @@ namespace osu.Game.Rulesets.UI private void setClock() { - // in case a parent gameplay clock isn't available, just use the parent clock. - parentGameplayClock ??= Clock; - - Clock = GameplayClock; - ProcessCustomClock = false; + if (parentGameplayClock == null) + { + // in case a parent gameplay clock isn't available, just use the parent clock. + parentGameplayClock ??= Clock; + } + else + { + Clock = GameplayClock; + } } public ReplayInputHandler ReplayInputHandler { get; set; } From 758088672cf9b7e58c8c83a5c4aebae143063d56 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 5 Oct 2020 15:07:46 +0900 Subject: [PATCH 1487/1782] Don't stop non-looping samples immediately when pausing --- .../Objects/Drawables/DrawableSlider.cs | 4 +-- .../Objects/Drawables/DrawableSpinner.cs | 4 +-- .../Objects/Drawables/DrawableHitObject.cs | 10 +++++-- osu.Game/Skinning/PausableSkinnableSound.cs | 26 +++++++++---------- 4 files changed, 25 insertions(+), 19 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index 280ca33234..4433aac9b5 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -109,9 +109,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables } } - public override void StopAllSamples() + public override void StopLoopingSamples() { - base.StopAllSamples(); + base.StopLoopingSamples(); slidingSample?.Stop(); } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index 130b4e6e53..dda0c94982 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -124,9 +124,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables } } - public override void StopAllSamples() + public override void StopLoopingSamples() { - base.StopAllSamples(); + base.StopLoopingSamples(); spinningSample?.Stop(); } diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 66fc61720a..7a4970d172 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -18,6 +18,7 @@ using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Scoring; using osu.Game.Skinning; using osu.Game.Configuration; +using osu.Game.Screens.Play; using osuTK.Graphics; namespace osu.Game.Rulesets.Objects.Drawables @@ -387,7 +388,10 @@ namespace osu.Game.Rulesets.Objects.Drawables /// /// Stops playback of all samples. Automatically called when 's lifetime has been exceeded. /// - public virtual void StopAllSamples() => Samples?.Stop(); + public virtual void StopLoopingSamples() + { + if (Samples?.Looping == true) + Samples.Stop(); protected override void Update() { @@ -457,7 +461,9 @@ namespace osu.Game.Rulesets.Objects.Drawables foreach (var nested in NestedHitObjects) nested.OnKilled(); - StopAllSamples(); + // failsafe to ensure looping samples don't get stuck in a playing state. + // this could occur in a non-frame-stable context where DrawableHitObjects get killed before a SkinnableSound has the chance to be stopped. + StopLoopingSamples(); UpdateResult(false); } diff --git a/osu.Game/Skinning/PausableSkinnableSound.cs b/osu.Game/Skinning/PausableSkinnableSound.cs index 9819574b1d..d340f67575 100644 --- a/osu.Game/Skinning/PausableSkinnableSound.cs +++ b/osu.Game/Skinning/PausableSkinnableSound.cs @@ -34,21 +34,21 @@ namespace osu.Game.Skinning samplePlaybackDisabled.BindTo(samplePlaybackDisabler.SamplePlaybackDisabled); samplePlaybackDisabled.BindValueChanged(disabled => { - if (RequestedPlaying) + if (!RequestedPlaying) return; + + // let non-looping samples that have already been started play out to completion (sounds better than abruptly cutting off). + if (!Looping) return; + + if (disabled.NewValue) + base.Stop(); + else { - if (disabled.NewValue) - base.Stop(); - // it's not easy to know if a sample has finished playing (to end). - // to keep things simple only resume playing looping samples. - else if (Looping) + // schedule so we don't start playing a sample which is no longer alive. + Schedule(() => { - // schedule so we don't start playing a sample which is no longer alive. - Schedule(() => - { - if (RequestedPlaying) - base.Play(); - }); - } + if (RequestedPlaying) + base.Play(); + }); } }); } From 9f43dedf59da624a4ea1381cedb982dce40d1b24 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 5 Oct 2020 15:12:34 +0900 Subject: [PATCH 1488/1782] Fix missing line --- 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 7a4970d172..11f84d370a 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -18,7 +18,6 @@ using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Scoring; using osu.Game.Skinning; using osu.Game.Configuration; -using osu.Game.Screens.Play; using osuTK.Graphics; namespace osu.Game.Rulesets.Objects.Drawables @@ -392,6 +391,7 @@ namespace osu.Game.Rulesets.Objects.Drawables { if (Samples?.Looping == true) Samples.Stop(); + } protected override void Update() { From a69b1636be75e094a7323a0961a45a1703a878f1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 5 Oct 2020 15:18:28 +0900 Subject: [PATCH 1489/1782] Update tests --- .../Visual/Editing/TestSceneEditorSamplePlayback.cs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorSamplePlayback.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorSamplePlayback.cs index 039a21fd94..f182023c0e 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorSamplePlayback.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorSamplePlayback.cs @@ -19,12 +19,14 @@ namespace osu.Game.Tests.Visual.Editing public void TestSlidingSampleStopsOnSeek() { DrawableSlider slider = null; - DrawableSample[] samples = null; + DrawableSample[] loopingSamples = null; + DrawableSample[] onceOffSamples = null; AddStep("get first slider", () => { slider = Editor.ChildrenOfType().OrderBy(s => s.HitObject.StartTime).First(); - samples = slider.ChildrenOfType().ToArray(); + onceOffSamples = slider.ChildrenOfType().Where(s => !s.Looping).ToArray(); + loopingSamples = slider.ChildrenOfType().Where(s => s.Looping).ToArray(); }); AddStep("start playback", () => EditorClock.Start()); @@ -34,14 +36,15 @@ namespace osu.Game.Tests.Visual.Editing if (!slider.Tracking.Value) return false; - if (!samples.Any(s => s.Playing)) + if (!loopingSamples.Any(s => s.Playing)) return false; EditorClock.Seek(20000); return true; }); - AddAssert("slider samples are not playing", () => samples.Length == 5 && samples.All(s => s.Played && !s.Playing)); + AddAssert("non-looping samples are playing", () => onceOffSamples.Length == 4 && loopingSamples.All(s => s.Played || s.Playing)); + AddAssert("looping samples are not playing", () => loopingSamples.Length == 1 && loopingSamples.All(s => s.Played && !s.Playing)); } } } From 0605bb9b8d565b843dda3fdbf9702474fc8a5592 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 5 Oct 2020 16:20:29 +0900 Subject: [PATCH 1490/1782] Fix incorrect parent state transfer --- 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 f32f8d177b..70b3d0c7d4 100644 --- a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs +++ b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs @@ -59,13 +59,16 @@ namespace osu.Game.Rulesets.UI private int direction; [BackgroundDependencyLoader(true)] - private void load(GameplayClock clock) + private void load(GameplayClock clock, ISamplePlaybackDisabler sampleDisabler) { if (clock != null) { parentGameplayClock = stabilityGameplayClock.ParentGameplayClock = clock; GameplayClock.IsPaused.BindTo(clock.IsPaused); } + + // this is a bit temporary. should really be done inside of GameplayClock (but requires large structural changes). + stabilityGameplayClock.ParentSampleDisabler = sampleDisabler; } protected override void LoadComplete() @@ -225,6 +228,8 @@ namespace osu.Game.Rulesets.UI { public GameplayClock ParentGameplayClock; + public ISamplePlaybackDisabler ParentSampleDisabler; + public override IEnumerable> NonGameplayAdjustments => ParentGameplayClock?.NonGameplayAdjustments ?? Enumerable.Empty>(); public StabilityGameplayClock(FramedClock underlyingClock) @@ -234,7 +239,9 @@ namespace osu.Game.Rulesets.UI protected override bool ShouldDisableSamplePlayback => // handle the case where playback is catching up to real-time. - base.ShouldDisableSamplePlayback || (ParentGameplayClock != null && Math.Abs(CurrentTime - ParentGameplayClock.CurrentTime) > 200); + base.ShouldDisableSamplePlayback + || ParentSampleDisabler?.SamplePlaybackDisabled.Value == true + || (ParentGameplayClock != null && Math.Abs(CurrentTime - ParentGameplayClock.CurrentTime) > 200); } } } From c622adde7a9374459a3a9a6ed93b7064685eb14d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 5 Oct 2020 16:24:02 +0900 Subject: [PATCH 1491/1782] Rename method back and add xmldoc --- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs | 4 ++-- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs | 4 ++-- osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs | 7 ++++--- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index 4433aac9b5..280ca33234 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -109,9 +109,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables } } - public override void StopLoopingSamples() + public override void StopAllSamples() { - base.StopLoopingSamples(); + base.StopAllSamples(); slidingSample?.Stop(); } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index dda0c94982..130b4e6e53 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -124,9 +124,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables } } - public override void StopLoopingSamples() + public override void StopAllSamples() { - base.StopLoopingSamples(); + base.StopAllSamples(); spinningSample?.Stop(); } diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 11f84d370a..8012b4d95c 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -385,9 +385,10 @@ namespace osu.Game.Rulesets.Objects.Drawables } /// - /// Stops playback of all samples. Automatically called when 's lifetime has been exceeded. + /// Stops playback of all relevant samples. Generally only looping samples should be stopped by this, and the rest let to play out. + /// Automatically called when 's lifetime has been exceeded. /// - public virtual void StopLoopingSamples() + public virtual void StopAllSamples() { if (Samples?.Looping == true) Samples.Stop(); @@ -463,7 +464,7 @@ namespace osu.Game.Rulesets.Objects.Drawables // failsafe to ensure looping samples don't get stuck in a playing state. // this could occur in a non-frame-stable context where DrawableHitObjects get killed before a SkinnableSound has the chance to be stopped. - StopLoopingSamples(); + StopAllSamples(); UpdateResult(false); } From 2b824787c1c4a2620f6bf3ab4e43a3fbee78b807 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 5 Oct 2020 19:28:13 +0900 Subject: [PATCH 1492/1782] Guard against potential nullref --- osu.Game/Screens/Edit/Setup/SetupScreen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Setup/SetupScreen.cs b/osu.Game/Screens/Edit/Setup/SetupScreen.cs index bbd0e23210..3d94737e59 100644 --- a/osu.Game/Screens/Edit/Setup/SetupScreen.cs +++ b/osu.Game/Screens/Edit/Setup/SetupScreen.cs @@ -275,7 +275,7 @@ namespace osu.Game.Screens.Edit.Setup protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); - game.UnregisterImportHandler(this); + game?.UnregisterImportHandler(this); } } From 606a08c6ad38b967a557a2605e0c1184bedb33eb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 5 Oct 2020 20:01:12 +0900 Subject: [PATCH 1493/1782] Temporarily ignore failing gameplay samples test --- .../Visual/Gameplay/TestSceneGameplaySamplePlayback.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySamplePlayback.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySamplePlayback.cs index 3ab4df20df..f0d39a8b18 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySamplePlayback.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySamplePlayback.cs @@ -17,6 +17,7 @@ namespace osu.Game.Tests.Visual.Gameplay public class TestSceneGameplaySamplePlayback : PlayerTestScene { [Test] + [Ignore("temporarily disabled pending investigation")] public void TestAllSamplesStopDuringSeek() { DrawableSlider slider = null; From 6bc0afdafb32c9e5728458fa8e099b39e9f7902a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 5 Oct 2020 20:09:18 +0900 Subject: [PATCH 1494/1782] Fix remaining conflicts --- osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs index 0bb3d25011..be3bca3242 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs @@ -13,7 +13,6 @@ using osu.Framework.Input.Events; using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Rulesets.Edit; -using osu.Game.Screens.Edit.Components.Timelines.Summary.Parts; using osuTK; namespace osu.Game.Screens.Edit.Compose.Components.Timeline @@ -63,8 +62,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline } private WaveformGraph waveform; - private ControlPointPart controlPoints; - private TimelineTickDisplay ticks; private TimelineTickDisplay ticks; From 9eeac759b8e5fd150db97118c15a99fb2496c658 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 5 Oct 2020 21:22:07 +0200 Subject: [PATCH 1495/1782] Re-enable and fix gameplay sample playback test --- .../Visual/Gameplay/TestSceneGameplaySamplePlayback.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySamplePlayback.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySamplePlayback.cs index f0d39a8b18..5bb3851264 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySamplePlayback.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySamplePlayback.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 NUnit.Framework; using osu.Framework.Graphics.Audio; @@ -17,7 +18,6 @@ namespace osu.Game.Tests.Visual.Gameplay public class TestSceneGameplaySamplePlayback : PlayerTestScene { [Test] - [Ignore("temporarily disabled pending investigation")] public void TestAllSamplesStopDuringSeek() { DrawableSlider slider = null; @@ -47,7 +47,8 @@ namespace osu.Game.Tests.Visual.Gameplay // because we are in frame stable context, it's quite likely that not all samples are "played" at this point. // the important thing is that at least one started, and that sample has since stopped. - AddAssert("no samples are playing", () => Player.ChildrenOfType().All(s => !s.IsPlaying)); + AddAssert("all looping samples stopped immediately", () => allStopped(allLoopingSounds)); + AddUntilStep("all samples stopped eventually", () => allStopped(allSounds)); AddAssert("sample playback still disabled", () => gameplayClock.SamplePlaybackDisabled.Value); @@ -55,6 +56,11 @@ namespace osu.Game.Tests.Visual.Gameplay AddUntilStep("any sample is playing", () => Player.ChildrenOfType().Any(s => s.IsPlaying)); } + private IEnumerable allSounds => Player.ChildrenOfType(); + private IEnumerable allLoopingSounds => allSounds.Where(sound => sound.Looping); + + private bool allStopped(IEnumerable sounds) => sounds.All(sound => !sound.IsPlaying); + protected override bool Autoplay => true; protected override Ruleset CreatePlayerRuleset() => new OsuRuleset(); From 46f6e84a3351dd77e4de78f785670788ab92a908 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 6 Oct 2020 12:33:57 +0900 Subject: [PATCH 1496/1782] Fix disclaimer potentially running same code from two different threads --- osu.Game/Screens/Menu/Disclaimer.cs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Menu/Disclaimer.cs b/osu.Game/Screens/Menu/Disclaimer.cs index fcb9aacd76..8368047d5a 100644 --- a/osu.Game/Screens/Menu/Disclaimer.cs +++ b/osu.Game/Screens/Menu/Disclaimer.cs @@ -42,8 +42,11 @@ namespace osu.Game.Screens.Menu ValidForResume = false; } + [Resolved] + private IAPIProvider api { get; set; } + [BackgroundDependencyLoader] - private void load(OsuColour colours, IAPIProvider api) + private void load(OsuColour colours) { InternalChildren = new Drawable[] { @@ -104,7 +107,9 @@ namespace osu.Game.Screens.Menu iconColour = colours.Yellow; - currentUser.BindTo(api.LocalUser); + // manually transfer the user once, but only do the final bind in LoadComplete to avoid thread woes (API scheduler could run while this screen is still loading). + // the manual transfer is here to ensure all text content is loaded ahead of time as this is very early in the game load process and we want to avoid stutters. + currentUser.Value = api.LocalUser.Value; currentUser.BindValueChanged(e => { supportFlow.Children.ForEach(d => d.FadeOut().Expire()); @@ -141,6 +146,8 @@ namespace osu.Game.Screens.Menu base.LoadComplete(); if (nextScreen != null) LoadComponentAsync(nextScreen); + + currentUser.BindTo(api.LocalUser); } public override void OnEntering(IScreen last) From 22b0105d629a70344609d33314a76978053e633d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 6 Oct 2020 13:00:02 +0900 Subject: [PATCH 1497/1782] Show a notification if checking for updates via button and there are none available --- osu.Desktop/Updater/SquirrelUpdateManager.cs | 8 ++++--- .../Sections/General/UpdateSettings.cs | 21 +++++++++++++++++-- osu.Game/Updater/SimpleUpdateManager.cs | 7 ++++++- osu.Game/Updater/UpdateManager.cs | 19 ++++++++++------- 4 files changed, 41 insertions(+), 14 deletions(-) diff --git a/osu.Desktop/Updater/SquirrelUpdateManager.cs b/osu.Desktop/Updater/SquirrelUpdateManager.cs index 05c8e835ac..b9b148b383 100644 --- a/osu.Desktop/Updater/SquirrelUpdateManager.cs +++ b/osu.Desktop/Updater/SquirrelUpdateManager.cs @@ -37,9 +37,9 @@ namespace osu.Desktop.Updater Splat.Locator.CurrentMutable.Register(() => new SquirrelLogger(), typeof(Splat.ILogger)); } - protected override async Task PerformUpdateCheck() => await checkForUpdateAsync(); + protected override async Task PerformUpdateCheck() => await checkForUpdateAsync(); - private async Task checkForUpdateAsync(bool useDeltaPatching = true, UpdateProgressNotification notification = null) + private async Task checkForUpdateAsync(bool useDeltaPatching = true, UpdateProgressNotification notification = null) { // should we schedule a retry on completion of this check? bool scheduleRecheck = true; @@ -51,7 +51,7 @@ namespace osu.Desktop.Updater var info = await updateManager.CheckForUpdate(!useDeltaPatching); if (info.ReleasesToApply.Count == 0) // no updates available. bail and retry later. - return; + return false; if (notification == null) { @@ -103,6 +103,8 @@ namespace osu.Desktop.Updater Scheduler.AddDelayed(async () => await checkForUpdateAsync(), 60000 * 30); } } + + return true; } protected override void Dispose(bool isDisposing) diff --git a/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs b/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs index 9c7d0b0be4..9b7b7392d8 100644 --- a/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs @@ -4,9 +4,11 @@ using System.Threading.Tasks; using osu.Framework; using osu.Framework.Allocation; +using osu.Framework.Graphics.Sprites; using osu.Framework.Platform; using osu.Framework.Screens; using osu.Game.Configuration; +using osu.Game.Overlays.Notifications; using osu.Game.Overlays.Settings.Sections.Maintenance; using osu.Game.Updater; @@ -21,6 +23,9 @@ namespace osu.Game.Overlays.Settings.Sections.General private SettingsButton checkForUpdatesButton; + [Resolved] + private NotificationOverlay notifications { get; set; } + [BackgroundDependencyLoader(true)] private void load(Storage storage, OsuConfigManager config, OsuGame game) { @@ -30,7 +35,7 @@ namespace osu.Game.Overlays.Settings.Sections.General Bindable = config.GetBindable(OsuSetting.ReleaseStream), }); - if (updateManager?.CanCheckForUpdate == true) + //if (updateManager?.CanCheckForUpdate == true) { Add(checkForUpdatesButton = new SettingsButton { @@ -38,7 +43,19 @@ namespace osu.Game.Overlays.Settings.Sections.General Action = () => { checkForUpdatesButton.Enabled.Value = false; - Task.Run(updateManager.CheckForUpdateAsync).ContinueWith(t => Schedule(() => checkForUpdatesButton.Enabled.Value = true)); + Task.Run(updateManager.CheckForUpdateAsync).ContinueWith(t => Schedule(() => + { + if (!t.Result) + { + notifications.Post(new SimpleNotification + { + Text = $"You are running the latest release ({game.Version})", + Icon = FontAwesome.Solid.CheckCircle, + }); + } + + checkForUpdatesButton.Enabled.Value = true; + })); } }); } diff --git a/osu.Game/Updater/SimpleUpdateManager.cs b/osu.Game/Updater/SimpleUpdateManager.cs index ebb9995c66..79b2d46b93 100644 --- a/osu.Game/Updater/SimpleUpdateManager.cs +++ b/osu.Game/Updater/SimpleUpdateManager.cs @@ -30,7 +30,7 @@ namespace osu.Game.Updater version = game.Version; } - protected override async Task PerformUpdateCheck() + protected override async Task PerformUpdateCheck() { try { @@ -53,12 +53,17 @@ namespace osu.Game.Updater return true; } }); + + return true; } } catch { // we shouldn't crash on a web failure. or any failure for the matter. + return true; } + + return false; } private string getBestUrl(GitHubRelease release) diff --git a/osu.Game/Updater/UpdateManager.cs b/osu.Game/Updater/UpdateManager.cs index 61775a26b7..30e28f0e95 100644 --- a/osu.Game/Updater/UpdateManager.cs +++ b/osu.Game/Updater/UpdateManager.cs @@ -57,25 +57,28 @@ namespace osu.Game.Updater private readonly object updateTaskLock = new object(); - private Task updateCheckTask; + private Task updateCheckTask; - public async Task CheckForUpdateAsync() + public async Task CheckForUpdateAsync() { - if (!CanCheckForUpdate) - return; - - Task waitTask; + Task waitTask; lock (updateTaskLock) waitTask = (updateCheckTask ??= PerformUpdateCheck()); - await waitTask; + bool hasUpdates = await waitTask; lock (updateTaskLock) updateCheckTask = null; + + return hasUpdates; } - protected virtual Task PerformUpdateCheck() => Task.CompletedTask; + /// + /// Performs an asynchronous check for application updates. + /// + /// Whether any update is waiting. May return true if an error occured (there is potentially an update available). + protected virtual Task PerformUpdateCheck() => Task.FromResult(false); private class UpdateCompleteNotification : SimpleNotification { From 5e10ac418bb188044f990d0b96c6544f16eff26b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 6 Oct 2020 13:09:42 +0900 Subject: [PATCH 1498/1782] Add update notifications for iOS builds --- osu.Game/Updater/SimpleUpdateManager.cs | 5 +++++ osu.iOS/OsuGameIOS.cs | 2 ++ 2 files changed, 7 insertions(+) diff --git a/osu.Game/Updater/SimpleUpdateManager.cs b/osu.Game/Updater/SimpleUpdateManager.cs index ebb9995c66..48c6722bd9 100644 --- a/osu.Game/Updater/SimpleUpdateManager.cs +++ b/osu.Game/Updater/SimpleUpdateManager.cs @@ -79,6 +79,11 @@ namespace osu.Game.Updater bestAsset = release.Assets?.Find(f => f.Name.EndsWith(".AppImage")); break; + case RuntimeInfo.Platform.iOS: + // iOS releases are available via testflight. this link seems to work well enough for now. + // see https://stackoverflow.com/a/32960501 + return "itms-beta://beta.itunes.apple.com/v1/app/1447765923"; + case RuntimeInfo.Platform.Android: // on our testing device this causes the download to magically disappear. //bestAsset = release.Assets?.Find(f => f.Name.EndsWith(".apk")); diff --git a/osu.iOS/OsuGameIOS.cs b/osu.iOS/OsuGameIOS.cs index 3a16f81530..5125ad81e0 100644 --- a/osu.iOS/OsuGameIOS.cs +++ b/osu.iOS/OsuGameIOS.cs @@ -11,5 +11,7 @@ namespace osu.iOS public class OsuGameIOS : OsuGame { public override Version AssemblyVersion => new Version(NSBundle.MainBundle.InfoDictionary["CFBundleVersion"].ToString()); + + protected override UpdateManager CreateUpdateManager() => new SimpleUpdateManager(); } } From de47392e3d11ddf7e7831c029ac4c5bf353007d2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 6 Oct 2020 13:18:42 +0900 Subject: [PATCH 1499/1782] Display the "restart to update" notification on checking for update after dismissal --- osu.Desktop/Updater/SquirrelUpdateManager.cs | 50 ++++++++++++++------ 1 file changed, 36 insertions(+), 14 deletions(-) diff --git a/osu.Desktop/Updater/SquirrelUpdateManager.cs b/osu.Desktop/Updater/SquirrelUpdateManager.cs index b9b148b383..71f9fafe57 100644 --- a/osu.Desktop/Updater/SquirrelUpdateManager.cs +++ b/osu.Desktop/Updater/SquirrelUpdateManager.cs @@ -29,6 +29,11 @@ namespace osu.Desktop.Updater private static readonly Logger logger = Logger.GetLogger("updater"); + /// + /// Whether an update has been downloaded but not yet applied. + /// + private bool updatePending; + [BackgroundDependencyLoader] private void load(NotificationOverlay notification) { @@ -49,9 +54,19 @@ namespace osu.Desktop.Updater updateManager ??= await UpdateManager.GitHubUpdateManager(@"https://github.com/ppy/osu", @"osulazer", null, null, true); var info = await updateManager.CheckForUpdate(!useDeltaPatching); + if (info.ReleasesToApply.Count == 0) + { + if (updatePending) + { + // the user may have dismissed the completion notice, so show it again. + notificationOverlay.Post(new UpdateCompleteNotification(this)); + return true; + } + // no updates available. bail and retry later. return false; + } if (notification == null) { @@ -72,6 +87,7 @@ namespace osu.Desktop.Updater await updateManager.ApplyReleases(info, p => notification.Progress = p / 100f); notification.State = ProgressNotificationState.Completed; + updatePending = true; } catch (Exception e) { @@ -113,10 +129,27 @@ namespace osu.Desktop.Updater updateManager?.Dispose(); } + private class UpdateCompleteNotification : ProgressCompletionNotification + { + [Resolved] + private OsuGame game { get; set; } + + public UpdateCompleteNotification(SquirrelUpdateManager updateManager) + { + Text = @"Update ready to install. Click to restart!"; + + Activated = () => + { + updateManager.PrepareUpdateAsync() + .ContinueWith(_ => updateManager.Schedule(() => game.GracefullyExit())); + return true; + }; + } + } + private class UpdateProgressNotification : ProgressNotification { private readonly SquirrelUpdateManager updateManager; - private OsuGame game; public UpdateProgressNotification(SquirrelUpdateManager updateManager) { @@ -125,23 +158,12 @@ namespace osu.Desktop.Updater protected override Notification CreateCompletionNotification() { - return new ProgressCompletionNotification - { - Text = @"Update ready to install. Click to restart!", - Activated = () => - { - updateManager.PrepareUpdateAsync() - .ContinueWith(_ => updateManager.Schedule(() => game.GracefullyExit())); - return true; - } - }; + return new UpdateCompleteNotification(updateManager); } [BackgroundDependencyLoader] - private void load(OsuColour colours, OsuGame game) + private void load(OsuColour colours) { - this.game = game; - IconContent.AddRange(new Drawable[] { new Box From 767a2a10bd0f0798eb5b313068c5b43b3f962160 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 6 Oct 2020 13:56:38 +0900 Subject: [PATCH 1500/1782] Fix incorrect sliderendcircle fallback logic Correctly handle the case where a skin has "sliderendcircle.png" but not "sliderendcircleoverlay.png". --- .../Skinning/LegacyMainCirclePiece.cs | 28 +++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs b/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs index f051cbfa3b..d556ecb9bc 100644 --- a/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs +++ b/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs @@ -49,6 +49,25 @@ namespace osu.Game.Rulesets.Osu.Skinning { OsuHitObject osuObject = (OsuHitObject)drawableObject.HitObject; + Texture baseTexture; + Texture overlayTexture; + bool allowFallback = false; + + // attempt lookup using priority specification + baseTexture = getTextureWithFallback(string.Empty); + + // if the base texture was not found without a fallback, switch on fallback mode and re-perform the lookup. + if (baseTexture == null) + { + allowFallback = true; + baseTexture = getTextureWithFallback(string.Empty); + } + + // at this point, any further texture fetches should be correctly using the priority source if the base texture was retrieved using it. + // the flow above handles the case where a sliderendcircle.png is retrieved from the skin, but sliderendcircleoverlay.png doesn't exist. + // expected behaviour in this scenario is not showing the overlay, rather than using hitcircleoverlay.png (potentially from the default/fall-through skin). + overlayTexture = getTextureWithFallback("overlay"); + InternalChildren = new Drawable[] { circleSprites = new Container @@ -60,13 +79,13 @@ namespace osu.Game.Rulesets.Osu.Skinning { hitCircleSprite = new Sprite { - Texture = getTextureWithFallback(string.Empty), + Texture = baseTexture, Anchor = Anchor.Centre, Origin = Anchor.Centre, }, hitCircleOverlay = new Sprite { - Texture = getTextureWithFallback("overlay"), + Texture = overlayTexture, Anchor = Anchor.Centre, Origin = Anchor.Centre, } @@ -101,8 +120,13 @@ namespace osu.Game.Rulesets.Osu.Skinning Texture tex = null; if (!string.IsNullOrEmpty(priorityLookup)) + { tex = skin.GetTexture($"{priorityLookup}{name}"); + if (!allowFallback) + return tex; + } + return tex ?? skin.GetTexture($"hitcircle{name}"); } } From ed982e8dd13f9f4657e1463a7bd19a7aac2f41f4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 6 Oct 2020 14:08:55 +0900 Subject: [PATCH 1501/1782] Make stacked hitcircles more visible when using default skin --- osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/RingPiece.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/RingPiece.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/RingPiece.cs index bcf64b81a6..619fea73bc 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/RingPiece.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/RingPiece.cs @@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces Origin = Anchor.Centre; Masking = true; - BorderThickness = 10; + BorderThickness = 9; // roughly matches slider borders and makes stacked circles distinctly visible from each other. BorderColour = Color4.White; Child = new Box From 048507478ee199fc155be842a5d008a05dcf1869 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 6 Oct 2020 14:12:46 +0900 Subject: [PATCH 1502/1782] Join declaration and specification --- osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs b/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs index d556ecb9bc..382d6e53cc 100644 --- a/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs +++ b/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs @@ -49,12 +49,10 @@ namespace osu.Game.Rulesets.Osu.Skinning { OsuHitObject osuObject = (OsuHitObject)drawableObject.HitObject; - Texture baseTexture; - Texture overlayTexture; bool allowFallback = false; // attempt lookup using priority specification - baseTexture = getTextureWithFallback(string.Empty); + Texture baseTexture = getTextureWithFallback(string.Empty); // if the base texture was not found without a fallback, switch on fallback mode and re-perform the lookup. if (baseTexture == null) @@ -66,7 +64,7 @@ namespace osu.Game.Rulesets.Osu.Skinning // at this point, any further texture fetches should be correctly using the priority source if the base texture was retrieved using it. // the flow above handles the case where a sliderendcircle.png is retrieved from the skin, but sliderendcircleoverlay.png doesn't exist. // expected behaviour in this scenario is not showing the overlay, rather than using hitcircleoverlay.png (potentially from the default/fall-through skin). - overlayTexture = getTextureWithFallback("overlay"); + Texture overlayTexture = getTextureWithFallback("overlay"); InternalChildren = new Drawable[] { From 9d7880afdaebdc2f929c5a04f0ce674cfdab8706 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 6 Oct 2020 17:18:41 +0900 Subject: [PATCH 1503/1782] Make SettingsItem conform to IHasCurrentValue --- .../ManiaSettingsSubsection.cs | 4 ++-- .../UI/OsuSettingsSubsection.cs | 6 ++--- osu.Game.Tournament/Components/DateTextBox.cs | 18 +++++++------- .../Screens/Editors/RoundEditorScreen.cs | 12 +++++----- .../Screens/Editors/SeedingEditorScreen.cs | 10 ++++---- .../Screens/Editors/TeamEditorScreen.cs | 12 +++++----- .../Screens/Gameplay/GameplayScreen.cs | 4 ++-- .../Ladder/Components/LadderEditorSettings.cs | 12 +++++----- .../Screens/TeamIntro/SeedingScreen.cs | 2 +- .../Configuration/SettingSourceAttribute.cs | 12 +++++----- .../Sections/Audio/AudioDevicesSettings.cs | 2 +- .../Sections/Audio/MainMenuSettings.cs | 8 +++---- .../Settings/Sections/Audio/OffsetSettings.cs | 2 +- .../Settings/Sections/Audio/VolumeSettings.cs | 8 +++---- .../Sections/Debug/GeneralSettings.cs | 4 ++-- .../Sections/Gameplay/GeneralSettings.cs | 24 +++++++++---------- .../Sections/Gameplay/ModsSettings.cs | 2 +- .../Sections/Gameplay/SongSelectSettings.cs | 10 ++++---- .../Sections/General/LanguageSettings.cs | 2 +- .../Sections/General/LoginSettings.cs | 4 ++-- .../Sections/General/UpdateSettings.cs | 2 +- .../Sections/Graphics/DetailSettings.cs | 8 +++---- .../Sections/Graphics/LayoutSettings.cs | 20 ++++++++-------- .../Sections/Graphics/RendererSettings.cs | 6 ++--- .../Graphics/UserInterfaceSettings.cs | 6 ++--- .../Settings/Sections/Input/MouseSettings.cs | 12 +++++----- .../Settings/Sections/Online/WebSettings.cs | 4 ++-- .../Overlays/Settings/Sections/SkinSection.cs | 12 +++++----- osu.Game/Overlays/Settings/SettingsItem.cs | 4 ++-- .../Edit/Timing/SliderWithTextBoxInput.cs | 8 +++---- osu.Game/Screens/Edit/Timing/TimingSection.cs | 14 +++++------ .../Play/PlayerSettings/PlaybackSettings.cs | 4 ++-- .../Play/PlayerSettings/VisualSettings.cs | 4 ++-- 33 files changed, 131 insertions(+), 131 deletions(-) diff --git a/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs b/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs index b470405df2..de77af8306 100644 --- a/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs +++ b/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs @@ -29,12 +29,12 @@ namespace osu.Game.Rulesets.Mania new SettingsEnumDropdown { LabelText = "Scrolling direction", - Bindable = config.GetBindable(ManiaRulesetSetting.ScrollDirection) + Current = config.GetBindable(ManiaRulesetSetting.ScrollDirection) }, new SettingsSlider { LabelText = "Scroll speed", - Bindable = config.GetBindable(ManiaRulesetSetting.ScrollTime), + Current = config.GetBindable(ManiaRulesetSetting.ScrollTime), KeyboardStep = 5 }, }; diff --git a/osu.Game.Rulesets.Osu/UI/OsuSettingsSubsection.cs b/osu.Game.Rulesets.Osu/UI/OsuSettingsSubsection.cs index 88adf72551..3870f303b4 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuSettingsSubsection.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuSettingsSubsection.cs @@ -27,17 +27,17 @@ namespace osu.Game.Rulesets.Osu.UI new SettingsCheckbox { LabelText = "Snaking in sliders", - Bindable = config.GetBindable(OsuRulesetSetting.SnakingInSliders) + Current = config.GetBindable(OsuRulesetSetting.SnakingInSliders) }, new SettingsCheckbox { LabelText = "Snaking out sliders", - Bindable = config.GetBindable(OsuRulesetSetting.SnakingOutSliders) + Current = config.GetBindable(OsuRulesetSetting.SnakingOutSliders) }, new SettingsCheckbox { LabelText = "Cursor trail", - Bindable = config.GetBindable(OsuRulesetSetting.ShowCursorTrail) + Current = config.GetBindable(OsuRulesetSetting.ShowCursorTrail) }, }; } diff --git a/osu.Game.Tournament/Components/DateTextBox.cs b/osu.Game.Tournament/Components/DateTextBox.cs index a1b5ac38ea..5782301a65 100644 --- a/osu.Game.Tournament/Components/DateTextBox.cs +++ b/osu.Game.Tournament/Components/DateTextBox.cs @@ -10,34 +10,34 @@ namespace osu.Game.Tournament.Components { public class DateTextBox : SettingsTextBox { - public new Bindable Bindable + public new Bindable Current { - get => bindable; + get => current; set { - bindable = value.GetBoundCopy(); - bindable.BindValueChanged(dto => - base.Bindable.Value = dto.NewValue.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ"), true); + current = value.GetBoundCopy(); + current.BindValueChanged(dto => + base.Current.Value = dto.NewValue.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ"), true); } } // hold a reference to the provided bindable so we don't have to in every settings section. - private Bindable bindable = new Bindable(); + private Bindable current = new Bindable(); public DateTextBox() { - base.Bindable = new Bindable(); + base.Current = new Bindable(); ((OsuTextBox)Control).OnCommit += (sender, newText) => { try { - bindable.Value = DateTimeOffset.Parse(sender.Text); + current.Value = DateTimeOffset.Parse(sender.Text); } catch { // reset textbox content to its last valid state on a parse failure. - bindable.TriggerChange(); + current.TriggerChange(); } }; } diff --git a/osu.Game.Tournament/Screens/Editors/RoundEditorScreen.cs b/osu.Game.Tournament/Screens/Editors/RoundEditorScreen.cs index 8b8078e119..069ddfa4db 100644 --- a/osu.Game.Tournament/Screens/Editors/RoundEditorScreen.cs +++ b/osu.Game.Tournament/Screens/Editors/RoundEditorScreen.cs @@ -63,25 +63,25 @@ namespace osu.Game.Tournament.Screens.Editors { LabelText = "Name", Width = 0.33f, - Bindable = Model.Name + Current = Model.Name }, new SettingsTextBox { LabelText = "Description", Width = 0.33f, - Bindable = Model.Description + Current = Model.Description }, new DateTextBox { LabelText = "Start Time", Width = 0.33f, - Bindable = Model.StartDate + Current = Model.StartDate }, new SettingsSlider { LabelText = "Best of", Width = 0.33f, - Bindable = Model.BestOf + Current = Model.BestOf }, new SettingsButton { @@ -186,14 +186,14 @@ namespace osu.Game.Tournament.Screens.Editors LabelText = "Beatmap ID", RelativeSizeAxes = Axes.None, Width = 200, - Bindable = beatmapId, + Current = beatmapId, }, new SettingsTextBox { LabelText = "Mods", RelativeSizeAxes = Axes.None, Width = 200, - Bindable = mods, + Current = mods, }, drawableContainer = new Container { diff --git a/osu.Game.Tournament/Screens/Editors/SeedingEditorScreen.cs b/osu.Game.Tournament/Screens/Editors/SeedingEditorScreen.cs index 0973a7dc75..7bd8d3f6a0 100644 --- a/osu.Game.Tournament/Screens/Editors/SeedingEditorScreen.cs +++ b/osu.Game.Tournament/Screens/Editors/SeedingEditorScreen.cs @@ -74,13 +74,13 @@ namespace osu.Game.Tournament.Screens.Editors { LabelText = "Mod", Width = 0.33f, - Bindable = Model.Mod + Current = Model.Mod }, new SettingsSlider { LabelText = "Seed", Width = 0.33f, - Bindable = Model.Seed + Current = Model.Seed }, new SettingsButton { @@ -187,21 +187,21 @@ namespace osu.Game.Tournament.Screens.Editors LabelText = "Beatmap ID", RelativeSizeAxes = Axes.None, Width = 200, - Bindable = beatmapId, + Current = beatmapId, }, new SettingsSlider { LabelText = "Seed", RelativeSizeAxes = Axes.None, Width = 200, - Bindable = beatmap.Seed + Current = beatmap.Seed }, new SettingsTextBox { LabelText = "Score", RelativeSizeAxes = Axes.None, Width = 200, - Bindable = score, + Current = score, }, drawableContainer = new Container { diff --git a/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs b/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs index dbfcfe4225..7196f47bd6 100644 --- a/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs +++ b/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs @@ -102,31 +102,31 @@ namespace osu.Game.Tournament.Screens.Editors { LabelText = "Name", Width = 0.2f, - Bindable = Model.FullName + Current = Model.FullName }, new SettingsTextBox { LabelText = "Acronym", Width = 0.2f, - Bindable = Model.Acronym + Current = Model.Acronym }, new SettingsTextBox { LabelText = "Flag", Width = 0.2f, - Bindable = Model.FlagName + Current = Model.FlagName }, new SettingsTextBox { LabelText = "Seed", Width = 0.2f, - Bindable = Model.Seed + Current = Model.Seed }, new SettingsSlider { LabelText = "Last Year Placement", Width = 0.33f, - Bindable = Model.LastYearPlacing + Current = Model.LastYearPlacing }, new SettingsButton { @@ -247,7 +247,7 @@ namespace osu.Game.Tournament.Screens.Editors LabelText = "User ID", RelativeSizeAxes = Axes.None, Width = 200, - Bindable = userId, + Current = userId, }, drawableContainer = new Container { diff --git a/osu.Game.Tournament/Screens/Gameplay/GameplayScreen.cs b/osu.Game.Tournament/Screens/Gameplay/GameplayScreen.cs index e4e3842369..e4ec45c00e 100644 --- a/osu.Game.Tournament/Screens/Gameplay/GameplayScreen.cs +++ b/osu.Game.Tournament/Screens/Gameplay/GameplayScreen.cs @@ -113,13 +113,13 @@ namespace osu.Game.Tournament.Screens.Gameplay new SettingsSlider { LabelText = "Chroma width", - Bindable = LadderInfo.ChromaKeyWidth, + Current = LadderInfo.ChromaKeyWidth, KeyboardStep = 1, }, new SettingsSlider { LabelText = "Players per team", - Bindable = LadderInfo.PlayersPerTeam, + Current = LadderInfo.PlayersPerTeam, KeyboardStep = 1, } } diff --git a/osu.Game.Tournament/Screens/Ladder/Components/LadderEditorSettings.cs b/osu.Game.Tournament/Screens/Ladder/Components/LadderEditorSettings.cs index b60eb814e5..cf4466a2e3 100644 --- a/osu.Game.Tournament/Screens/Ladder/Components/LadderEditorSettings.cs +++ b/osu.Game.Tournament/Screens/Ladder/Components/LadderEditorSettings.cs @@ -51,15 +51,15 @@ namespace osu.Game.Tournament.Screens.Ladder.Components editorInfo.Selected.ValueChanged += selection => { - roundDropdown.Bindable = selection.NewValue?.Round; + roundDropdown.Current = selection.NewValue?.Round; losersCheckbox.Current = selection.NewValue?.Losers; - dateTimeBox.Bindable = selection.NewValue?.Date; + dateTimeBox.Current = selection.NewValue?.Date; - team1Dropdown.Bindable = selection.NewValue?.Team1; - team2Dropdown.Bindable = selection.NewValue?.Team2; + team1Dropdown.Current = selection.NewValue?.Team1; + team2Dropdown.Current = selection.NewValue?.Team2; }; - roundDropdown.Bindable.ValueChanged += round => + roundDropdown.Current.ValueChanged += round => { if (editorInfo.Selected.Value?.Date.Value < round.NewValue?.StartDate.Value) { @@ -88,7 +88,7 @@ namespace osu.Game.Tournament.Screens.Ladder.Components { public SettingsRoundDropdown(BindableList rounds) { - Bindable = new Bindable(); + Current = new Bindable(); foreach (var r in rounds.Prepend(new TournamentRound())) add(r); diff --git a/osu.Game.Tournament/Screens/TeamIntro/SeedingScreen.cs b/osu.Game.Tournament/Screens/TeamIntro/SeedingScreen.cs index eed3cac9f0..b343608e69 100644 --- a/osu.Game.Tournament/Screens/TeamIntro/SeedingScreen.cs +++ b/osu.Game.Tournament/Screens/TeamIntro/SeedingScreen.cs @@ -61,7 +61,7 @@ namespace osu.Game.Tournament.Screens.TeamIntro new SettingsTeamDropdown(LadderInfo.Teams) { LabelText = "Show specific team", - Bindable = currentTeam, + Current = currentTeam, } } } diff --git a/osu.Game/Configuration/SettingSourceAttribute.cs b/osu.Game/Configuration/SettingSourceAttribute.cs index fe487cb1d0..50069be4b2 100644 --- a/osu.Game/Configuration/SettingSourceAttribute.cs +++ b/osu.Game/Configuration/SettingSourceAttribute.cs @@ -57,7 +57,7 @@ namespace osu.Game.Configuration yield return new SettingsSlider { LabelText = attr.Label, - Bindable = bNumber, + Current = bNumber, KeyboardStep = 0.1f, }; @@ -67,7 +67,7 @@ namespace osu.Game.Configuration yield return new SettingsSlider { LabelText = attr.Label, - Bindable = bNumber, + Current = bNumber, KeyboardStep = 0.1f, }; @@ -77,7 +77,7 @@ namespace osu.Game.Configuration yield return new SettingsSlider { LabelText = attr.Label, - Bindable = bNumber + Current = bNumber }; break; @@ -86,7 +86,7 @@ namespace osu.Game.Configuration yield return new SettingsCheckbox { LabelText = attr.Label, - Bindable = bBool + Current = bBool }; break; @@ -95,7 +95,7 @@ namespace osu.Game.Configuration yield return new SettingsTextBox { LabelText = attr.Label, - Bindable = bString + Current = bString }; break; @@ -105,7 +105,7 @@ namespace osu.Game.Configuration var dropdown = (Drawable)Activator.CreateInstance(dropdownType); dropdownType.GetProperty(nameof(SettingsDropdown.LabelText))?.SetValue(dropdown, attr.Label); - dropdownType.GetProperty(nameof(SettingsDropdown.Bindable))?.SetValue(dropdown, bindable); + dropdownType.GetProperty(nameof(SettingsDropdown.Current))?.SetValue(dropdown, bindable); yield return dropdown; diff --git a/osu.Game/Overlays/Settings/Sections/Audio/AudioDevicesSettings.cs b/osu.Game/Overlays/Settings/Sections/Audio/AudioDevicesSettings.cs index 3da64e0de4..bed74542c9 100644 --- a/osu.Game/Overlays/Settings/Sections/Audio/AudioDevicesSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Audio/AudioDevicesSettings.cs @@ -64,7 +64,7 @@ namespace osu.Game.Overlays.Settings.Sections.Audio updateItems(); - dropdown.Bindable = audio.AudioDevice; + dropdown.Current = audio.AudioDevice; audio.OnNewDevice += onDeviceChanged; audio.OnLostDevice += onDeviceChanged; diff --git a/osu.Game/Overlays/Settings/Sections/Audio/MainMenuSettings.cs b/osu.Game/Overlays/Settings/Sections/Audio/MainMenuSettings.cs index a303f93b34..d5de32ed05 100644 --- a/osu.Game/Overlays/Settings/Sections/Audio/MainMenuSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Audio/MainMenuSettings.cs @@ -21,23 +21,23 @@ namespace osu.Game.Overlays.Settings.Sections.Audio new SettingsCheckbox { LabelText = "Interface voices", - Bindable = config.GetBindable(OsuSetting.MenuVoice) + Current = config.GetBindable(OsuSetting.MenuVoice) }, new SettingsCheckbox { LabelText = "osu! music theme", - Bindable = config.GetBindable(OsuSetting.MenuMusic) + Current = config.GetBindable(OsuSetting.MenuMusic) }, new SettingsDropdown { LabelText = "Intro sequence", - Bindable = config.GetBindable(OsuSetting.IntroSequence), + Current = config.GetBindable(OsuSetting.IntroSequence), Items = Enum.GetValues(typeof(IntroSequence)).Cast() }, new SettingsDropdown { LabelText = "Background source", - Bindable = config.GetBindable(OsuSetting.MenuBackgroundSource), + Current = config.GetBindable(OsuSetting.MenuBackgroundSource), Items = Enum.GetValues(typeof(BackgroundSource)).Cast() } }; diff --git a/osu.Game/Overlays/Settings/Sections/Audio/OffsetSettings.cs b/osu.Game/Overlays/Settings/Sections/Audio/OffsetSettings.cs index aaa4302553..c9a81b955b 100644 --- a/osu.Game/Overlays/Settings/Sections/Audio/OffsetSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Audio/OffsetSettings.cs @@ -20,7 +20,7 @@ namespace osu.Game.Overlays.Settings.Sections.Audio new SettingsSlider { LabelText = "Audio offset", - Bindable = config.GetBindable(OsuSetting.AudioOffset), + Current = config.GetBindable(OsuSetting.AudioOffset), KeyboardStep = 1f }, new SettingsButton diff --git a/osu.Game/Overlays/Settings/Sections/Audio/VolumeSettings.cs b/osu.Game/Overlays/Settings/Sections/Audio/VolumeSettings.cs index bda677ecd6..c172a76ab9 100644 --- a/osu.Game/Overlays/Settings/Sections/Audio/VolumeSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Audio/VolumeSettings.cs @@ -20,28 +20,28 @@ namespace osu.Game.Overlays.Settings.Sections.Audio new SettingsSlider { LabelText = "Master", - Bindable = audio.Volume, + Current = audio.Volume, KeyboardStep = 0.01f, DisplayAsPercentage = true }, new SettingsSlider { LabelText = "Master (window inactive)", - Bindable = config.GetBindable(OsuSetting.VolumeInactive), + Current = config.GetBindable(OsuSetting.VolumeInactive), KeyboardStep = 0.01f, DisplayAsPercentage = true }, new SettingsSlider { LabelText = "Effect", - Bindable = audio.VolumeSample, + Current = audio.VolumeSample, KeyboardStep = 0.01f, DisplayAsPercentage = true }, new SettingsSlider { LabelText = "Music", - Bindable = audio.VolumeTrack, + Current = audio.VolumeTrack, KeyboardStep = 0.01f, DisplayAsPercentage = true }, diff --git a/osu.Game/Overlays/Settings/Sections/Debug/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/Debug/GeneralSettings.cs index 9edb18e065..f05b876d8f 100644 --- a/osu.Game/Overlays/Settings/Sections/Debug/GeneralSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Debug/GeneralSettings.cs @@ -19,12 +19,12 @@ namespace osu.Game.Overlays.Settings.Sections.Debug new SettingsCheckbox { LabelText = "Show log overlay", - Bindable = frameworkConfig.GetBindable(FrameworkSetting.ShowLogOverlay) + Current = frameworkConfig.GetBindable(FrameworkSetting.ShowLogOverlay) }, new SettingsCheckbox { LabelText = "Bypass front-to-back render pass", - Bindable = config.GetBindable(DebugSetting.BypassFrontToBackPass) + Current = config.GetBindable(DebugSetting.BypassFrontToBackPass) } }; } diff --git a/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs index 0149e6c3a6..73968761e2 100644 --- a/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs @@ -21,62 +21,62 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay new SettingsSlider { LabelText = "Background dim", - Bindable = config.GetBindable(OsuSetting.DimLevel), + Current = config.GetBindable(OsuSetting.DimLevel), KeyboardStep = 0.01f, DisplayAsPercentage = true }, new SettingsSlider { LabelText = "Background blur", - Bindable = config.GetBindable(OsuSetting.BlurLevel), + Current = config.GetBindable(OsuSetting.BlurLevel), KeyboardStep = 0.01f, DisplayAsPercentage = true }, new SettingsCheckbox { LabelText = "Lighten playfield during breaks", - Bindable = config.GetBindable(OsuSetting.LightenDuringBreaks) + Current = config.GetBindable(OsuSetting.LightenDuringBreaks) }, new SettingsCheckbox { LabelText = "Show score overlay", - Bindable = config.GetBindable(OsuSetting.ShowInterface) + Current = config.GetBindable(OsuSetting.ShowInterface) }, new SettingsCheckbox { LabelText = "Show difficulty graph on progress bar", - Bindable = config.GetBindable(OsuSetting.ShowProgressGraph) + Current = config.GetBindable(OsuSetting.ShowProgressGraph) }, new SettingsCheckbox { LabelText = "Show health display even when you can't fail", - Bindable = config.GetBindable(OsuSetting.ShowHealthDisplayWhenCantFail), + Current = config.GetBindable(OsuSetting.ShowHealthDisplayWhenCantFail), Keywords = new[] { "hp", "bar" } }, new SettingsCheckbox { LabelText = "Fade playfield to red when health is low", - Bindable = config.GetBindable(OsuSetting.FadePlayfieldWhenHealthLow), + Current = config.GetBindable(OsuSetting.FadePlayfieldWhenHealthLow), }, new SettingsCheckbox { LabelText = "Always show key overlay", - Bindable = config.GetBindable(OsuSetting.KeyOverlay) + Current = config.GetBindable(OsuSetting.KeyOverlay) }, new SettingsCheckbox { LabelText = "Positional hitsounds", - Bindable = config.GetBindable(OsuSetting.PositionalHitSounds) + Current = config.GetBindable(OsuSetting.PositionalHitSounds) }, new SettingsEnumDropdown { LabelText = "Score meter type", - Bindable = config.GetBindable(OsuSetting.ScoreMeter) + Current = config.GetBindable(OsuSetting.ScoreMeter) }, new SettingsEnumDropdown { LabelText = "Score display mode", - Bindable = config.GetBindable(OsuSetting.ScoreDisplayMode) + Current = config.GetBindable(OsuSetting.ScoreDisplayMode) } }; @@ -85,7 +85,7 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay Add(new SettingsCheckbox { LabelText = "Disable Windows key during gameplay", - Bindable = config.GetBindable(OsuSetting.GameplayDisableWinKey) + Current = config.GetBindable(OsuSetting.GameplayDisableWinKey) }); } } diff --git a/osu.Game/Overlays/Settings/Sections/Gameplay/ModsSettings.cs b/osu.Game/Overlays/Settings/Sections/Gameplay/ModsSettings.cs index 0babb98066..2b2fb9cef7 100644 --- a/osu.Game/Overlays/Settings/Sections/Gameplay/ModsSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Gameplay/ModsSettings.cs @@ -22,7 +22,7 @@ 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), + Current = config.GetBindable(OsuSetting.IncreaseFirstObjectVisibility), }, }; } diff --git a/osu.Game/Overlays/Settings/Sections/Gameplay/SongSelectSettings.cs b/osu.Game/Overlays/Settings/Sections/Gameplay/SongSelectSettings.cs index 0c42247993..b26876556e 100644 --- a/osu.Game/Overlays/Settings/Sections/Gameplay/SongSelectSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Gameplay/SongSelectSettings.cs @@ -31,31 +31,31 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay new SettingsCheckbox { LabelText = "Right mouse drag to absolute scroll", - Bindable = config.GetBindable(OsuSetting.SongSelectRightMouseScroll), + Current = config.GetBindable(OsuSetting.SongSelectRightMouseScroll), }, new SettingsCheckbox { LabelText = "Show converted beatmaps", - Bindable = config.GetBindable(OsuSetting.ShowConvertedBeatmaps), + Current = config.GetBindable(OsuSetting.ShowConvertedBeatmaps), }, new SettingsSlider { LabelText = "Display beatmaps from", - Bindable = config.GetBindable(OsuSetting.DisplayStarsMinimum), + Current = config.GetBindable(OsuSetting.DisplayStarsMinimum), KeyboardStep = 0.1f, Keywords = new[] { "minimum", "maximum", "star", "difficulty" } }, new SettingsSlider { LabelText = "up to", - Bindable = config.GetBindable(OsuSetting.DisplayStarsMaximum), + Current = config.GetBindable(OsuSetting.DisplayStarsMaximum), KeyboardStep = 0.1f, Keywords = new[] { "minimum", "maximum", "star", "difficulty" } }, new SettingsEnumDropdown { LabelText = "Random selection algorithm", - Bindable = config.GetBindable(OsuSetting.RandomSelectAlgorithm), + Current = config.GetBindable(OsuSetting.RandomSelectAlgorithm), } }; } diff --git a/osu.Game/Overlays/Settings/Sections/General/LanguageSettings.cs b/osu.Game/Overlays/Settings/Sections/General/LanguageSettings.cs index 236bfbecc3..44e42ecbfe 100644 --- a/osu.Game/Overlays/Settings/Sections/General/LanguageSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/General/LanguageSettings.cs @@ -19,7 +19,7 @@ namespace osu.Game.Overlays.Settings.Sections.General new SettingsCheckbox { LabelText = "Prefer metadata in original language", - Bindable = frameworkConfig.GetBindable(FrameworkSetting.ShowUnicode) + Current = frameworkConfig.GetBindable(FrameworkSetting.ShowUnicode) }, }; } diff --git a/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs b/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs index f96e204f62..9e358d0cf5 100644 --- a/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs @@ -240,12 +240,12 @@ namespace osu.Game.Overlays.Settings.Sections.General new SettingsCheckbox { LabelText = "Remember username", - Bindable = config.GetBindable(OsuSetting.SaveUsername), + Current = config.GetBindable(OsuSetting.SaveUsername), }, new SettingsCheckbox { LabelText = "Stay signed in", - Bindable = config.GetBindable(OsuSetting.SavePassword), + Current = config.GetBindable(OsuSetting.SavePassword), }, new Container { diff --git a/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs b/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs index 9c7d0b0be4..a59a6b00b9 100644 --- a/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs @@ -27,7 +27,7 @@ namespace osu.Game.Overlays.Settings.Sections.General Add(new SettingsEnumDropdown { LabelText = "Release stream", - Bindable = config.GetBindable(OsuSetting.ReleaseStream), + Current = config.GetBindable(OsuSetting.ReleaseStream), }); if (updateManager?.CanCheckForUpdate == true) diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/DetailSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/DetailSettings.cs index 3089040f96..30caa45995 100644 --- a/osu.Game/Overlays/Settings/Sections/Graphics/DetailSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Graphics/DetailSettings.cs @@ -19,22 +19,22 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics new SettingsCheckbox { LabelText = "Storyboard / Video", - Bindable = config.GetBindable(OsuSetting.ShowStoryboard) + Current = config.GetBindable(OsuSetting.ShowStoryboard) }, new SettingsCheckbox { LabelText = "Hit Lighting", - Bindable = config.GetBindable(OsuSetting.HitLighting) + Current = config.GetBindable(OsuSetting.HitLighting) }, new SettingsEnumDropdown { LabelText = "Screenshot format", - Bindable = config.GetBindable(OsuSetting.ScreenshotFormat) + Current = config.GetBindable(OsuSetting.ScreenshotFormat) }, new SettingsCheckbox { LabelText = "Show menu cursor in screenshots", - Bindable = config.GetBindable(OsuSetting.ScreenshotCaptureMenuCursor) + Current = config.GetBindable(OsuSetting.ScreenshotCaptureMenuCursor) } }; } diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs index 4312b319c0..14b8dbfac0 100644 --- a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs @@ -62,7 +62,7 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics windowModeDropdown = new SettingsDropdown { LabelText = "Screen mode", - Bindable = config.GetBindable(FrameworkSetting.WindowMode), + Current = config.GetBindable(FrameworkSetting.WindowMode), ItemSource = windowModes, }, resolutionSettingsContainer = new Container @@ -74,14 +74,14 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics { LabelText = "UI Scaling", TransferValueOnCommit = true, - Bindable = osuConfig.GetBindable(OsuSetting.UIScale), + Current = osuConfig.GetBindable(OsuSetting.UIScale), KeyboardStep = 0.01f, Keywords = new[] { "scale", "letterbox" }, }, new SettingsEnumDropdown { LabelText = "Screen Scaling", - Bindable = osuConfig.GetBindable(OsuSetting.Scaling), + Current = osuConfig.GetBindable(OsuSetting.Scaling), Keywords = new[] { "scale", "letterbox" }, }, scalingSettings = new FillFlowContainer> @@ -97,28 +97,28 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics new SettingsSlider { LabelText = "Horizontal position", - Bindable = scalingPositionX, + Current = scalingPositionX, KeyboardStep = 0.01f, DisplayAsPercentage = true }, new SettingsSlider { LabelText = "Vertical position", - Bindable = scalingPositionY, + Current = scalingPositionY, KeyboardStep = 0.01f, DisplayAsPercentage = true }, new SettingsSlider { LabelText = "Horizontal scale", - Bindable = scalingSizeX, + Current = scalingSizeX, KeyboardStep = 0.01f, DisplayAsPercentage = true }, new SettingsSlider { LabelText = "Vertical scale", - Bindable = scalingSizeY, + Current = scalingSizeY, KeyboardStep = 0.01f, DisplayAsPercentage = true }, @@ -126,7 +126,7 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics }, }; - scalingSettings.ForEach(s => bindPreviewEvent(s.Bindable)); + scalingSettings.ForEach(s => bindPreviewEvent(s.Current)); var resolutions = getResolutions(); @@ -137,10 +137,10 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics LabelText = "Resolution", ShowsDefaultIndicator = false, Items = resolutions, - Bindable = sizeFullscreen + Current = sizeFullscreen }; - windowModeDropdown.Bindable.BindValueChanged(mode => + windowModeDropdown.Current.BindValueChanged(mode => { if (mode.NewValue == WindowMode.Fullscreen) { diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/RendererSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/RendererSettings.cs index 69ff9b43e5..8773e6763c 100644 --- a/osu.Game/Overlays/Settings/Sections/Graphics/RendererSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Graphics/RendererSettings.cs @@ -23,17 +23,17 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics new SettingsEnumDropdown { LabelText = "Frame limiter", - Bindable = config.GetBindable(FrameworkSetting.FrameSync) + Current = config.GetBindable(FrameworkSetting.FrameSync) }, new SettingsEnumDropdown { LabelText = "Threading mode", - Bindable = config.GetBindable(FrameworkSetting.ExecutionMode) + Current = config.GetBindable(FrameworkSetting.ExecutionMode) }, new SettingsCheckbox { LabelText = "Show FPS", - Bindable = osuConfig.GetBindable(OsuSetting.ShowFpsDisplay) + Current = osuConfig.GetBindable(OsuSetting.ShowFpsDisplay) }, }; } diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/UserInterfaceSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/UserInterfaceSettings.cs index a8953ac3a2..38c30ddd64 100644 --- a/osu.Game/Overlays/Settings/Sections/Graphics/UserInterfaceSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Graphics/UserInterfaceSettings.cs @@ -20,17 +20,17 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics new SettingsCheckbox { LabelText = "Rotate cursor when dragging", - Bindable = config.GetBindable(OsuSetting.CursorRotation) + Current = config.GetBindable(OsuSetting.CursorRotation) }, new SettingsCheckbox { LabelText = "Parallax", - Bindable = config.GetBindable(OsuSetting.MenuParallax) + Current = config.GetBindable(OsuSetting.MenuParallax) }, new SettingsSlider { LabelText = "Hold-to-confirm activation time", - Bindable = config.GetBindable(OsuSetting.UIHoldActivationDelay), + Current = config.GetBindable(OsuSetting.UIHoldActivationDelay), KeyboardStep = 50 }, }; diff --git a/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs b/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs index d27ab63fb7..5227e328ec 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs @@ -35,32 +35,32 @@ namespace osu.Game.Overlays.Settings.Sections.Input new SettingsCheckbox { LabelText = "Raw input", - Bindable = rawInputToggle + Current = rawInputToggle }, new SensitivitySetting { LabelText = "Cursor sensitivity", - Bindable = sensitivityBindable + Current = sensitivityBindable }, new SettingsCheckbox { LabelText = "Map absolute input to window", - Bindable = config.GetBindable(FrameworkSetting.MapAbsoluteInputToWindow) + Current = config.GetBindable(FrameworkSetting.MapAbsoluteInputToWindow) }, new SettingsEnumDropdown { LabelText = "Confine mouse cursor to window", - Bindable = config.GetBindable(FrameworkSetting.ConfineMouseMode), + Current = config.GetBindable(FrameworkSetting.ConfineMouseMode), }, new SettingsCheckbox { LabelText = "Disable mouse wheel during gameplay", - Bindable = osuConfig.GetBindable(OsuSetting.MouseDisableWheel) + Current = osuConfig.GetBindable(OsuSetting.MouseDisableWheel) }, new SettingsCheckbox { LabelText = "Disable mouse buttons during gameplay", - Bindable = osuConfig.GetBindable(OsuSetting.MouseDisableButtons) + Current = osuConfig.GetBindable(OsuSetting.MouseDisableButtons) }, }; diff --git a/osu.Game/Overlays/Settings/Sections/Online/WebSettings.cs b/osu.Game/Overlays/Settings/Sections/Online/WebSettings.cs index 23513eade8..6461bd7b93 100644 --- a/osu.Game/Overlays/Settings/Sections/Online/WebSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Online/WebSettings.cs @@ -19,13 +19,13 @@ namespace osu.Game.Overlays.Settings.Sections.Online new SettingsCheckbox { LabelText = "Warn about opening external links", - Bindable = config.GetBindable(OsuSetting.ExternalLinkWarning) + Current = config.GetBindable(OsuSetting.ExternalLinkWarning) }, new SettingsCheckbox { LabelText = "Prefer downloads without video", Keywords = new[] { "no-video" }, - Bindable = config.GetBindable(OsuSetting.PreferNoVideo) + Current = config.GetBindable(OsuSetting.PreferNoVideo) }, }; } diff --git a/osu.Game/Overlays/Settings/Sections/SkinSection.cs b/osu.Game/Overlays/Settings/Sections/SkinSection.cs index 596d3a9801..1ade4befdc 100644 --- a/osu.Game/Overlays/Settings/Sections/SkinSection.cs +++ b/osu.Game/Overlays/Settings/Sections/SkinSection.cs @@ -47,29 +47,29 @@ namespace osu.Game.Overlays.Settings.Sections new SettingsSlider { LabelText = "Menu cursor size", - Bindable = config.GetBindable(OsuSetting.MenuCursorSize), + Current = config.GetBindable(OsuSetting.MenuCursorSize), KeyboardStep = 0.01f }, new SettingsSlider { LabelText = "Gameplay cursor size", - Bindable = config.GetBindable(OsuSetting.GameplayCursorSize), + Current = config.GetBindable(OsuSetting.GameplayCursorSize), KeyboardStep = 0.01f }, new SettingsCheckbox { LabelText = "Adjust gameplay cursor size based on current beatmap", - Bindable = config.GetBindable(OsuSetting.AutoCursorSize) + Current = config.GetBindable(OsuSetting.AutoCursorSize) }, new SettingsCheckbox { LabelText = "Beatmap skins", - Bindable = config.GetBindable(OsuSetting.BeatmapSkins) + Current = config.GetBindable(OsuSetting.BeatmapSkins) }, new SettingsCheckbox { LabelText = "Beatmap hitsounds", - Bindable = config.GetBindable(OsuSetting.BeatmapHitsounds) + Current = config.GetBindable(OsuSetting.BeatmapHitsounds) }, }; @@ -81,7 +81,7 @@ namespace osu.Game.Overlays.Settings.Sections config.BindWith(OsuSetting.Skin, configBindable); - skinDropdown.Bindable = dropdownBindable; + skinDropdown.Current = dropdownBindable; skinDropdown.Items = skins.GetAllUsableSkins().ToArray(); // Todo: This should not be necessary when OsuConfigManager is databased diff --git a/osu.Game/Overlays/Settings/SettingsItem.cs b/osu.Game/Overlays/Settings/SettingsItem.cs index c2dd40d2a6..ad6aaafd9d 100644 --- a/osu.Game/Overlays/Settings/SettingsItem.cs +++ b/osu.Game/Overlays/Settings/SettingsItem.cs @@ -21,7 +21,7 @@ using osuTK; namespace osu.Game.Overlays.Settings { - public abstract class SettingsItem : Container, IFilterable, ISettingsItem + public abstract class SettingsItem : Container, IFilterable, ISettingsItem, IHasCurrentValue { protected abstract Drawable CreateControl(); @@ -54,7 +54,7 @@ namespace osu.Game.Overlays.Settings } } - public virtual Bindable Bindable + public virtual Bindable Current { get => controlWithCurrent.Current; set => controlWithCurrent.Current = value; diff --git a/osu.Game/Screens/Edit/Timing/SliderWithTextBoxInput.cs b/osu.Game/Screens/Edit/Timing/SliderWithTextBoxInput.cs index d5afc8978d..f2f9f76143 100644 --- a/osu.Game/Screens/Edit/Timing/SliderWithTextBoxInput.cs +++ b/osu.Game/Screens/Edit/Timing/SliderWithTextBoxInput.cs @@ -11,7 +11,7 @@ using osu.Game.Overlays.Settings; namespace osu.Game.Screens.Edit.Timing { - internal class SliderWithTextBoxInput : CompositeDrawable, IHasCurrentValue + public class SliderWithTextBoxInput : CompositeDrawable, IHasCurrentValue where T : struct, IEquatable, IComparable, IConvertible { private readonly SettingsSlider slider; @@ -51,7 +51,7 @@ namespace osu.Game.Screens.Edit.Timing try { - slider.Bindable.Parse(t.Text); + slider.Current.Parse(t.Text); } catch { @@ -71,8 +71,8 @@ namespace osu.Game.Screens.Edit.Timing public Bindable Current { - get => slider.Bindable; - set => slider.Bindable = value; + get => slider.Current; + set => slider.Current = value; } } } diff --git a/osu.Game/Screens/Edit/Timing/TimingSection.cs b/osu.Game/Screens/Edit/Timing/TimingSection.cs index 2ab8703cc4..1ae2a86885 100644 --- a/osu.Game/Screens/Edit/Timing/TimingSection.cs +++ b/osu.Game/Screens/Edit/Timing/TimingSection.cs @@ -36,14 +36,14 @@ namespace osu.Game.Screens.Edit.Timing { if (point.NewValue != null) { - bpmSlider.Bindable = point.NewValue.BeatLengthBindable; - bpmSlider.Bindable.BindValueChanged(_ => ChangeHandler?.SaveState()); + bpmSlider.Current = point.NewValue.BeatLengthBindable; + bpmSlider.Current.BindValueChanged(_ => ChangeHandler?.SaveState()); bpmTextEntry.Bindable = point.NewValue.BeatLengthBindable; // no need to hook change handler here as it's the same bindable as above - timeSignature.Bindable = point.NewValue.TimeSignatureBindable; - timeSignature.Bindable.BindValueChanged(_ => ChangeHandler?.SaveState()); + timeSignature.Current = point.NewValue.TimeSignatureBindable; + timeSignature.Current.BindValueChanged(_ => ChangeHandler?.SaveState()); } } @@ -121,14 +121,14 @@ namespace osu.Game.Screens.Edit.Timing beatLengthBindable.BindValueChanged(beatLength => updateCurrent(beatLengthToBpm(beatLength.NewValue)), true); bpmBindable.BindValueChanged(bpm => beatLengthBindable.Value = beatLengthToBpm(bpm.NewValue)); - base.Bindable = bpmBindable; + base.Current = bpmBindable; TransferValueOnCommit = true; } - public override Bindable Bindable + public override Bindable Current { - get => base.Bindable; + get => base.Current; set { // incoming will be beat length, not bpm diff --git a/osu.Game/Screens/Play/PlayerSettings/PlaybackSettings.cs b/osu.Game/Screens/Play/PlayerSettings/PlaybackSettings.cs index 24ddc277cd..16e29ac3c8 100644 --- a/osu.Game/Screens/Play/PlayerSettings/PlaybackSettings.cs +++ b/osu.Game/Screens/Play/PlayerSettings/PlaybackSettings.cs @@ -51,14 +51,14 @@ namespace osu.Game.Screens.Play.PlayerSettings } }, }, - rateSlider = new PlayerSliderBar { Bindable = UserPlaybackRate } + rateSlider = new PlayerSliderBar { Current = UserPlaybackRate } }; } protected override void LoadComplete() { base.LoadComplete(); - rateSlider.Bindable.BindValueChanged(multiplier => multiplierText.Text = $"{multiplier.NewValue:0.0}x", true); + rateSlider.Current.BindValueChanged(multiplier => multiplierText.Text = $"{multiplier.NewValue:0.0}x", true); } } } diff --git a/osu.Game/Screens/Play/PlayerSettings/VisualSettings.cs b/osu.Game/Screens/Play/PlayerSettings/VisualSettings.cs index e06cf5c6d5..8f29fe7893 100644 --- a/osu.Game/Screens/Play/PlayerSettings/VisualSettings.cs +++ b/osu.Game/Screens/Play/PlayerSettings/VisualSettings.cs @@ -50,8 +50,8 @@ namespace osu.Game.Screens.Play.PlayerSettings [BackgroundDependencyLoader] private void load(OsuConfigManager config) { - dimSliderBar.Bindable = config.GetBindable(OsuSetting.DimLevel); - blurSliderBar.Bindable = config.GetBindable(OsuSetting.BlurLevel); + dimSliderBar.Current = config.GetBindable(OsuSetting.DimLevel); + blurSliderBar.Current = config.GetBindable(OsuSetting.BlurLevel); showStoryboardToggle.Current = config.GetBindable(OsuSetting.ShowStoryboard); beatmapSkinsToggle.Current = config.GetBindable(OsuSetting.BeatmapSkins); beatmapHitsoundsToggle.Current = config.GetBindable(OsuSetting.BeatmapHitsounds); From 28756d862b630eb5b28eecf8c9373dfca97be24b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 6 Oct 2020 14:22:53 +0900 Subject: [PATCH 1504/1782] Add placeholder text/colour when no beatmap background is specified yet --- osu.Game/Screens/Edit/Setup/SetupScreen.cs | 27 ++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Setup/SetupScreen.cs b/osu.Game/Screens/Edit/Setup/SetupScreen.cs index 3d94737e59..1e9ebec41d 100644 --- a/osu.Game/Screens/Edit/Setup/SetupScreen.cs +++ b/osu.Game/Screens/Edit/Setup/SetupScreen.cs @@ -59,8 +59,11 @@ namespace osu.Game.Screens.Edit.Setup { } + [Resolved] + private OsuColour colours { get; set; } + [BackgroundDependencyLoader] - private void load(OsuColour colours) + private void load() { Container audioTrackFileChooserContainer = new Container { @@ -187,7 +190,27 @@ namespace osu.Game.Screens.Edit.Setup FillMode = FillMode.Fill, }, background => { - backgroundSpriteContainer.Child = background; + if (background.Texture != null) + backgroundSpriteContainer.Child = background; + else + { + backgroundSpriteContainer.Children = new Drawable[] + { + new Box + { + Colour = colours.GreySeafoamDarker, + RelativeSizeAxes = Axes.Both, + }, + new OsuTextFlowContainer(t => t.Font = OsuFont.Default.With(size: 24)) + { + Text = "Drag image here to set beatmap background!", + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AutoSizeAxes = Axes.X, + } + }; + } + background.FadeInFromZero(500); }); } From 87bf3bdc161a601893e25ab7899ab2151952e39f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 6 Oct 2020 16:40:47 +0900 Subject: [PATCH 1505/1782] Add the most basic implementation of LabelledSliderBar feasible --- .../TestSceneLabelledSliderBar.cs | 46 +++++++++++++++++++ .../UserInterfaceV2/LabelledSliderBar.cs | 24 ++++++++++ 2 files changed, 70 insertions(+) create mode 100644 osu.Game.Tests/Visual/UserInterface/TestSceneLabelledSliderBar.cs create mode 100644 osu.Game/Graphics/UserInterfaceV2/LabelledSliderBar.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledSliderBar.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledSliderBar.cs new file mode 100644 index 0000000000..393420e700 --- /dev/null +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledSliderBar.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.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Graphics.UserInterfaceV2; + +namespace osu.Game.Tests.Visual.UserInterface +{ + public class TestSceneLabelledSliderBar : OsuTestScene + { + [TestCase(false)] + [TestCase(true)] + public void TestSliderBar(bool hasDescription) => createSliderBar(hasDescription); + + private void createSliderBar(bool hasDescription = false) + { + AddStep("create component", () => + { + LabelledSliderBar component; + + Child = new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Width = 500, + AutoSizeAxes = Axes.Y, + Child = component = new LabelledSliderBar + { + Current = new BindableDouble(5) + { + MinValue = 0, + MaxValue = 10, + Precision = 1, + } + } + }; + + component.Label = "a sample component"; + component.Description = hasDescription ? "this text describes the component" : string.Empty; + }); + } + } +} diff --git a/osu.Game/Graphics/UserInterfaceV2/LabelledSliderBar.cs b/osu.Game/Graphics/UserInterfaceV2/LabelledSliderBar.cs new file mode 100644 index 0000000000..cba94e314b --- /dev/null +++ b/osu.Game/Graphics/UserInterfaceV2/LabelledSliderBar.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 System; +using osu.Framework.Graphics; +using osu.Game.Overlays.Settings; + +namespace osu.Game.Graphics.UserInterfaceV2 +{ + public class LabelledSliderBar : LabelledComponent, TNumber> + where TNumber : struct, IEquatable, IComparable, IConvertible + { + public LabelledSliderBar() + : base(true) + { + } + + protected override SettingsSlider CreateComponent() => new SettingsSlider + { + TransferValueOnCommit = true, + RelativeSizeAxes = Axes.X, + }; + } +} From 98fe5f78ee956d3765324a1d8482fb1945a63673 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 6 Oct 2020 15:17:15 +0900 Subject: [PATCH 1506/1782] Split setup screen up into sections (and use a SectionContainer) --- .../Editing/TestSceneEditorBeatmapCreation.cs | 2 +- .../Edit/Setup/FileChooserLabelledTextBox.cs | 73 ++++ .../Screens/Edit/Setup/MetadataSection.cs | 71 ++++ .../Screens/Edit/Setup/ResourcesSection.cs | 211 ++++++++++++ osu.Game/Screens/Edit/Setup/SetupScreen.cs | 319 +----------------- osu.Game/Screens/Edit/Setup/SetupSection.cs | 43 +++ 6 files changed, 417 insertions(+), 302 deletions(-) create mode 100644 osu.Game/Screens/Edit/Setup/FileChooserLabelledTextBox.cs create mode 100644 osu.Game/Screens/Edit/Setup/MetadataSection.cs create mode 100644 osu.Game/Screens/Edit/Setup/ResourcesSection.cs create mode 100644 osu.Game/Screens/Edit/Setup/SetupSection.cs diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs index 13a3195824..7584c74c71 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs @@ -69,7 +69,7 @@ namespace osu.Game.Tests.Visual.Editing using (var zip = ZipArchive.Open(temp)) zip.WriteToDirectory(extractedFolder); - bool success = setup.ChangeAudioTrack(Path.Combine(extractedFolder, "03. Renatus - Soleily 192kbps.mp3")); + bool success = setup.ChildrenOfType().First().ChangeAudioTrack(Path.Combine(extractedFolder, "03. Renatus - Soleily 192kbps.mp3")); File.Delete(temp); Directory.Delete(extractedFolder, true); diff --git a/osu.Game/Screens/Edit/Setup/FileChooserLabelledTextBox.cs b/osu.Game/Screens/Edit/Setup/FileChooserLabelledTextBox.cs new file mode 100644 index 0000000000..b802b3405a --- /dev/null +++ b/osu.Game/Screens/Edit/Setup/FileChooserLabelledTextBox.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 System; +using System.IO; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Input.Events; +using osu.Game.Graphics.UserInterface; +using osu.Game.Graphics.UserInterfaceV2; + +namespace osu.Game.Screens.Edit.Setup +{ + /// + /// A labelled textbox which reveals an inline file chooser when clicked. + /// + internal class FileChooserLabelledTextBox : LabelledTextBox + { + public Container Target; + + private readonly IBindable currentFile = new Bindable(); + + public FileChooserLabelledTextBox() + { + currentFile.BindValueChanged(onFileSelected); + } + + private void onFileSelected(ValueChangedEvent file) + { + if (file.NewValue == null) + return; + + Target.Clear(); + Current.Value = file.NewValue.FullName; + } + + protected override OsuTextBox CreateTextBox() => + new FileChooserOsuTextBox + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.X, + CornerRadius = CORNER_RADIUS, + OnFocused = DisplayFileChooser + }; + + public void DisplayFileChooser() + { + Target.Child = new FileSelector(validFileExtensions: ResourcesSection.AudioExtensions) + { + RelativeSizeAxes = Axes.X, + Height = 400, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + CurrentFile = { BindTarget = currentFile } + }; + } + + internal class FileChooserOsuTextBox : OsuTextBox + { + public Action OnFocused; + + protected override void OnFocus(FocusEvent e) + { + OnFocused?.Invoke(); + base.OnFocus(e); + + GetContainingInputManager().TriggerFocusContention(this); + } + } + } +} diff --git a/osu.Game/Screens/Edit/Setup/MetadataSection.cs b/osu.Game/Screens/Edit/Setup/MetadataSection.cs new file mode 100644 index 0000000000..31a2c2ce1a --- /dev/null +++ b/osu.Game/Screens/Edit/Setup/MetadataSection.cs @@ -0,0 +1,71 @@ +// 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.UserInterface; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterfaceV2; + +namespace osu.Game.Screens.Edit.Setup +{ + internal class MetadataSection : SetupSection + { + private LabelledTextBox artistTextBox; + private LabelledTextBox titleTextBox; + private LabelledTextBox creatorTextBox; + private LabelledTextBox difficultyTextBox; + + [BackgroundDependencyLoader] + private void load() + { + Flow.Children = new Drawable[] + { + new OsuSpriteText + { + Text = "Beatmap metadata" + }, + artistTextBox = new LabelledTextBox + { + Label = "Artist", + Current = { Value = Beatmap.Value.Metadata.Artist }, + TabbableContentContainer = this + }, + titleTextBox = new LabelledTextBox + { + Label = "Title", + Current = { Value = Beatmap.Value.Metadata.Title }, + TabbableContentContainer = this + }, + creatorTextBox = new LabelledTextBox + { + Label = "Creator", + Current = { Value = Beatmap.Value.Metadata.AuthorString }, + TabbableContentContainer = this + }, + difficultyTextBox = new LabelledTextBox + { + Label = "Difficulty Name", + Current = { Value = Beatmap.Value.BeatmapInfo.Version }, + TabbableContentContainer = this + }, + }; + + foreach (var item in Flow.OfType()) + item.OnCommit += onCommit; + } + + private void onCommit(TextBox sender, bool newText) + { + if (!newText) return; + + // for now, update these on commit rather than making BeatmapMetadata bindables. + // after switching database engines we can reconsider if switching to bindables is a good direction. + Beatmap.Value.Metadata.Artist = artistTextBox.Current.Value; + Beatmap.Value.Metadata.Title = titleTextBox.Current.Value; + Beatmap.Value.Metadata.AuthorString = creatorTextBox.Current.Value; + Beatmap.Value.BeatmapInfo.Version = difficultyTextBox.Current.Value; + } + } +} diff --git a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs new file mode 100644 index 0000000000..86d7968856 --- /dev/null +++ b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs @@ -0,0 +1,211 @@ +// 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.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.Drawables; +using osu.Game.Database; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterfaceV2; +using osu.Game.Overlays; + +namespace osu.Game.Screens.Edit.Setup +{ + internal class ResourcesSection : SetupSection, ICanAcceptFiles + { + private LabelledTextBox audioTrackTextBox; + private Container backgroundSpriteContainer; + + public IEnumerable HandledExtensions => ImageExtensions.Concat(AudioExtensions); + + public static string[] ImageExtensions { get; } = { ".jpg", ".jpeg", ".png" }; + + public static string[] AudioExtensions { get; } = { ".mp3", ".ogg" }; + + [Resolved] + private OsuGameBase game { get; set; } + + [Resolved] + private MusicController music { get; set; } + + [Resolved] + private BeatmapManager beatmaps { get; set; } + + [Resolved(canBeNull: true)] + private Editor editor { get; set; } + + [BackgroundDependencyLoader] + private void load() + { + Container audioTrackFileChooserContainer = new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + }; + + Flow.Children = new Drawable[] + { + backgroundSpriteContainer = new Container + { + RelativeSizeAxes = Axes.X, + Height = 250, + Masking = true, + CornerRadius = 10, + }, + new OsuSpriteText + { + Text = "Resources" + }, + audioTrackTextBox = new FileChooserLabelledTextBox + { + Label = "Audio Track", + Current = { Value = Beatmap.Value.Metadata.AudioFile ?? "Click to select a track" }, + Target = audioTrackFileChooserContainer, + TabbableContentContainer = this + }, + audioTrackFileChooserContainer, + }; + + updateBackgroundSprite(); + + audioTrackTextBox.Current.BindValueChanged(audioTrackChanged); + } + + Task ICanAcceptFiles.Import(params string[] paths) + { + Schedule(() => + { + var firstFile = new FileInfo(paths.First()); + + if (ImageExtensions.Contains(firstFile.Extension)) + { + ChangeBackgroundImage(firstFile.FullName); + } + else if (AudioExtensions.Contains(firstFile.Extension)) + { + audioTrackTextBox.Text = firstFile.FullName; + } + }); + return Task.CompletedTask; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + game.RegisterImportHandler(this); + } + + public bool ChangeBackgroundImage(string path) + { + var info = new FileInfo(path); + + if (!info.Exists) + return false; + + var set = Beatmap.Value.BeatmapSetInfo; + + // remove the previous background for now. + // in the future we probably want to check if this is being used elsewhere (other difficulties?) + var oldFile = set.Files.FirstOrDefault(f => f.Filename == Beatmap.Value.Metadata.BackgroundFile); + + using (var stream = info.OpenRead()) + { + if (oldFile != null) + beatmaps.ReplaceFile(set, oldFile, stream, info.Name); + else + beatmaps.AddFile(set, stream, info.Name); + } + + Beatmap.Value.Metadata.BackgroundFile = info.Name; + updateBackgroundSprite(); + + return true; + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + game?.UnregisterImportHandler(this); + } + + public bool ChangeAudioTrack(string path) + { + var info = new FileInfo(path); + + if (!info.Exists) + return false; + + var set = Beatmap.Value.BeatmapSetInfo; + + // remove the previous audio track for now. + // in the future we probably want to check if this is being used elsewhere (other difficulties?) + var oldFile = set.Files.FirstOrDefault(f => f.Filename == Beatmap.Value.Metadata.AudioFile); + + using (var stream = info.OpenRead()) + { + if (oldFile != null) + beatmaps.ReplaceFile(set, oldFile, stream, info.Name); + else + beatmaps.AddFile(set, stream, info.Name); + } + + Beatmap.Value.Metadata.AudioFile = info.Name; + + music.ReloadCurrentTrack(); + + editor?.UpdateClockSource(); + return true; + } + + private void audioTrackChanged(ValueChangedEvent filePath) + { + if (!ChangeAudioTrack(filePath.NewValue)) + audioTrackTextBox.Current.Value = filePath.OldValue; + } + + private void updateBackgroundSprite() + { + LoadComponentAsync(new BeatmapBackgroundSprite(Beatmap.Value) + { + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + FillMode = FillMode.Fill, + }, background => + { + if (background.Texture != null) + backgroundSpriteContainer.Child = background; + else + { + backgroundSpriteContainer.Children = new Drawable[] + { + new Box + { + Colour = Colours.GreySeafoamDarker, + RelativeSizeAxes = Axes.Both, + }, + new OsuTextFlowContainer(t => t.Font = OsuFont.Default.With(size: 24)) + { + Text = "Drag image here to set beatmap background!", + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AutoSizeAxes = Axes.X, + } + }; + } + + background.FadeInFromZero(500); + }); + } + } +} diff --git a/osu.Game/Screens/Edit/Setup/SetupScreen.cs b/osu.Game/Screens/Edit/Setup/SetupScreen.cs index 1e9ebec41d..cd4f6733c0 100644 --- a/osu.Game/Screens/Edit/Setup/SetupScreen.cs +++ b/osu.Game/Screens/Edit/Setup/SetupScreen.cs @@ -1,76 +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 System.IO; -using System.Linq; -using System.Threading.Tasks; 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.UserInterface; -using osu.Framework.Input.Events; -using osu.Game.Beatmaps; -using osu.Game.Beatmaps.Drawables; -using osu.Game.Database; using osu.Game.Graphics; using osu.Game.Graphics.Containers; -using osu.Game.Graphics.Sprites; -using osu.Game.Graphics.UserInterface; -using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Overlays; -using osuTK; namespace osu.Game.Screens.Edit.Setup { - public class SetupScreen : EditorScreen, ICanAcceptFiles + public class SetupScreen : EditorScreen { - public IEnumerable HandledExtensions => ImageExtensions.Concat(AudioExtensions); - - public static string[] ImageExtensions { get; } = { ".jpg", ".jpeg", ".png" }; - - public static string[] AudioExtensions { get; } = { ".mp3", ".ogg" }; - - private FillFlowContainer flow; - private LabelledTextBox artistTextBox; - private LabelledTextBox titleTextBox; - private LabelledTextBox creatorTextBox; - private LabelledTextBox difficultyTextBox; - private LabelledTextBox audioTrackTextBox; - private Container backgroundSpriteContainer; - [Resolved] - private OsuGameBase game { get; set; } + private OsuColour colours { get; set; } - [Resolved] - private MusicController music { get; set; } - - [Resolved] - private BeatmapManager beatmaps { get; set; } - - [Resolved(canBeNull: true)] - private Editor editor { get; set; } + [Cached] + protected readonly OverlayColourProvider ColourProvider; public SetupScreen() : base(EditorScreenMode.SongSetup) { + ColourProvider = new OverlayColourProvider(OverlayColourScheme.Green); } - [Resolved] - private OsuColour colours { get; set; } - [BackgroundDependencyLoader] private void load() { - Container audioTrackFileChooserContainer = new Container - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - }; - Child = new Container { RelativeSizeAxes = Axes.Both, @@ -87,273 +44,33 @@ namespace osu.Game.Screens.Edit.Setup Colour = colours.GreySeafoamDark, RelativeSizeAxes = Axes.Both, }, - new OsuScrollContainer + new SectionsContainer { + FixedHeader = new SetupScreenHeader(), RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding(10), - Child = flow = new FillFlowContainer + Children = new SetupSection[] { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Spacing = new Vector2(20), - Direction = FillDirection.Vertical, - Children = new Drawable[] - { - backgroundSpriteContainer = new Container - { - RelativeSizeAxes = Axes.X, - Height = 250, - Masking = true, - CornerRadius = 10, - }, - new OsuSpriteText - { - Text = "Resources" - }, - audioTrackTextBox = new FileChooserLabelledTextBox - { - Label = "Audio Track", - Current = { Value = Beatmap.Value.Metadata.AudioFile ?? "Click to select a track" }, - Target = audioTrackFileChooserContainer, - TabbableContentContainer = this - }, - audioTrackFileChooserContainer, - new OsuSpriteText - { - Text = "Beatmap metadata" - }, - artistTextBox = new LabelledTextBox - { - Label = "Artist", - Current = { Value = Beatmap.Value.Metadata.Artist }, - TabbableContentContainer = this - }, - titleTextBox = new LabelledTextBox - { - Label = "Title", - Current = { Value = Beatmap.Value.Metadata.Title }, - TabbableContentContainer = this - }, - creatorTextBox = new LabelledTextBox - { - Label = "Creator", - Current = { Value = Beatmap.Value.Metadata.AuthorString }, - TabbableContentContainer = this - }, - difficultyTextBox = new LabelledTextBox - { - Label = "Difficulty Name", - Current = { Value = Beatmap.Value.BeatmapInfo.Version }, - TabbableContentContainer = this - }, - } - }, + new ResourcesSection(), + new MetadataSection(), + } }, } } }; - - updateBackgroundSprite(); - - audioTrackTextBox.Current.BindValueChanged(audioTrackChanged); - - foreach (var item in flow.OfType()) - item.OnCommit += onCommit; - } - - Task ICanAcceptFiles.Import(params string[] paths) - { - Schedule(() => - { - var firstFile = new FileInfo(paths.First()); - - if (ImageExtensions.Contains(firstFile.Extension)) - { - ChangeBackgroundImage(firstFile.FullName); - } - else if (AudioExtensions.Contains(firstFile.Extension)) - { - audioTrackTextBox.Text = firstFile.FullName; - } - }); - - return Task.CompletedTask; - } - - private void updateBackgroundSprite() - { - LoadComponentAsync(new BeatmapBackgroundSprite(Beatmap.Value) - { - RelativeSizeAxes = Axes.Both, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - FillMode = FillMode.Fill, - }, background => - { - if (background.Texture != null) - backgroundSpriteContainer.Child = background; - else - { - backgroundSpriteContainer.Children = new Drawable[] - { - new Box - { - Colour = colours.GreySeafoamDarker, - RelativeSizeAxes = Axes.Both, - }, - new OsuTextFlowContainer(t => t.Font = OsuFont.Default.With(size: 24)) - { - Text = "Drag image here to set beatmap background!", - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - AutoSizeAxes = Axes.X, - } - }; - } - - background.FadeInFromZero(500); - }); - } - - protected override void LoadComplete() - { - base.LoadComplete(); - game.RegisterImportHandler(this); - } - - public bool ChangeBackgroundImage(string path) - { - var info = new FileInfo(path); - - if (!info.Exists) - return false; - - var set = Beatmap.Value.BeatmapSetInfo; - - // remove the previous background for now. - // in the future we probably want to check if this is being used elsewhere (other difficulties?) - var oldFile = set.Files.FirstOrDefault(f => f.Filename == Beatmap.Value.Metadata.BackgroundFile); - - using (var stream = info.OpenRead()) - { - if (oldFile != null) - beatmaps.ReplaceFile(set, oldFile, stream, info.Name); - else - beatmaps.AddFile(set, stream, info.Name); - } - - Beatmap.Value.Metadata.BackgroundFile = info.Name; - updateBackgroundSprite(); - - return true; - } - - public bool ChangeAudioTrack(string path) - { - var info = new FileInfo(path); - - if (!info.Exists) - return false; - - var set = Beatmap.Value.BeatmapSetInfo; - - // remove the previous audio track for now. - // in the future we probably want to check if this is being used elsewhere (other difficulties?) - var oldFile = set.Files.FirstOrDefault(f => f.Filename == Beatmap.Value.Metadata.AudioFile); - - using (var stream = info.OpenRead()) - { - if (oldFile != null) - beatmaps.ReplaceFile(set, oldFile, stream, info.Name); - else - beatmaps.AddFile(set, stream, info.Name); - } - - Beatmap.Value.Metadata.AudioFile = info.Name; - - music.ReloadCurrentTrack(); - - editor?.UpdateClockSource(); - return true; - } - - private void audioTrackChanged(ValueChangedEvent filePath) - { - if (!ChangeAudioTrack(filePath.NewValue)) - audioTrackTextBox.Current.Value = filePath.OldValue; - } - - private void onCommit(TextBox sender, bool newText) - { - if (!newText) return; - - // for now, update these on commit rather than making BeatmapMetadata bindables. - // after switching database engines we can reconsider if switching to bindables is a good direction. - Beatmap.Value.Metadata.Artist = artistTextBox.Current.Value; - Beatmap.Value.Metadata.Title = titleTextBox.Current.Value; - Beatmap.Value.Metadata.AuthorString = creatorTextBox.Current.Value; - Beatmap.Value.BeatmapInfo.Version = difficultyTextBox.Current.Value; - } - - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - game?.UnregisterImportHandler(this); } } - internal class FileChooserLabelledTextBox : LabelledTextBox + internal class SetupScreenHeader : OverlayHeader { - public Container Target; + protected override OverlayTitle CreateTitle() => new SetupScreenTitle(); - private readonly IBindable currentFile = new Bindable(); - - public FileChooserLabelledTextBox() + private class SetupScreenTitle : OverlayTitle { - currentFile.BindValueChanged(onFileSelected); - } - - private void onFileSelected(ValueChangedEvent file) - { - if (file.NewValue == null) - return; - - Target.Clear(); - Current.Value = file.NewValue.FullName; - } - - protected override OsuTextBox CreateTextBox() => - new FileChooserOsuTextBox + public SetupScreenTitle() { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.X, - CornerRadius = CORNER_RADIUS, - OnFocused = DisplayFileChooser - }; - - public void DisplayFileChooser() - { - Target.Child = new FileSelector(validFileExtensions: SetupScreen.AudioExtensions) - { - RelativeSizeAxes = Axes.X, - Height = 400, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - CurrentFile = { BindTarget = currentFile } - }; - } - - internal class FileChooserOsuTextBox : OsuTextBox - { - public Action OnFocused; - - protected override void OnFocus(FocusEvent e) - { - OnFocused?.Invoke(); - base.OnFocus(e); - - GetContainingInputManager().TriggerFocusContention(this); + Title = "beatmap setup"; + Description = "change general settings of your beatmap"; + IconTexture = "Icons/Hexacons/social"; } } } diff --git a/osu.Game/Screens/Edit/Setup/SetupSection.cs b/osu.Game/Screens/Edit/Setup/SetupSection.cs new file mode 100644 index 0000000000..54e383a4d8 --- /dev/null +++ b/osu.Game/Screens/Edit/Setup/SetupSection.cs @@ -0,0 +1,43 @@ +// Copyright (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.Beatmaps; +using osu.Game.Graphics; +using osuTK; + +namespace osu.Game.Screens.Edit.Setup +{ + internal class SetupSection : Container + { + protected FillFlowContainer Flow; + + [Resolved] + protected OsuColour Colours { get; private set; } + + [Resolved] + protected IBindable Beatmap { get; private set; } + + public override void Add(Drawable drawable) => throw new InvalidOperationException("Use Flow.Add instead"); + + public SetupSection() + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + + Padding = new MarginPadding(10); + + InternalChild = Flow = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Spacing = new Vector2(20), + Direction = FillDirection.Vertical, + }; + } + } +} From 505dd37a75e98261394327771ffa764099716a73 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 6 Oct 2020 17:18:41 +0900 Subject: [PATCH 1507/1782] Make SettingsItem conform to IHasCurrentValue --- .../ManiaSettingsSubsection.cs | 4 ++-- .../UI/OsuSettingsSubsection.cs | 6 ++--- osu.Game.Tournament/Components/DateTextBox.cs | 18 +++++++------- .../Screens/Editors/RoundEditorScreen.cs | 12 +++++----- .../Screens/Editors/SeedingEditorScreen.cs | 10 ++++---- .../Screens/Editors/TeamEditorScreen.cs | 12 +++++----- .../Screens/Gameplay/GameplayScreen.cs | 4 ++-- .../Ladder/Components/LadderEditorSettings.cs | 12 +++++----- .../Screens/TeamIntro/SeedingScreen.cs | 2 +- .../Configuration/SettingSourceAttribute.cs | 12 +++++----- .../Sections/Audio/AudioDevicesSettings.cs | 2 +- .../Sections/Audio/MainMenuSettings.cs | 8 +++---- .../Settings/Sections/Audio/OffsetSettings.cs | 2 +- .../Settings/Sections/Audio/VolumeSettings.cs | 8 +++---- .../Sections/Debug/GeneralSettings.cs | 4 ++-- .../Sections/Gameplay/GeneralSettings.cs | 24 +++++++++---------- .../Sections/Gameplay/ModsSettings.cs | 2 +- .../Sections/Gameplay/SongSelectSettings.cs | 10 ++++---- .../Sections/General/LanguageSettings.cs | 2 +- .../Sections/General/LoginSettings.cs | 4 ++-- .../Sections/General/UpdateSettings.cs | 2 +- .../Sections/Graphics/DetailSettings.cs | 8 +++---- .../Sections/Graphics/LayoutSettings.cs | 20 ++++++++-------- .../Sections/Graphics/RendererSettings.cs | 6 ++--- .../Graphics/UserInterfaceSettings.cs | 6 ++--- .../Settings/Sections/Input/MouseSettings.cs | 12 +++++----- .../Settings/Sections/Online/WebSettings.cs | 4 ++-- .../Overlays/Settings/Sections/SkinSection.cs | 12 +++++----- osu.Game/Overlays/Settings/SettingsItem.cs | 4 ++-- .../Edit/Timing/SliderWithTextBoxInput.cs | 8 +++---- osu.Game/Screens/Edit/Timing/TimingSection.cs | 14 +++++------ .../Play/PlayerSettings/PlaybackSettings.cs | 4 ++-- .../Play/PlayerSettings/VisualSettings.cs | 4 ++-- 33 files changed, 131 insertions(+), 131 deletions(-) diff --git a/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs b/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs index b470405df2..de77af8306 100644 --- a/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs +++ b/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs @@ -29,12 +29,12 @@ namespace osu.Game.Rulesets.Mania new SettingsEnumDropdown { LabelText = "Scrolling direction", - Bindable = config.GetBindable(ManiaRulesetSetting.ScrollDirection) + Current = config.GetBindable(ManiaRulesetSetting.ScrollDirection) }, new SettingsSlider { LabelText = "Scroll speed", - Bindable = config.GetBindable(ManiaRulesetSetting.ScrollTime), + Current = config.GetBindable(ManiaRulesetSetting.ScrollTime), KeyboardStep = 5 }, }; diff --git a/osu.Game.Rulesets.Osu/UI/OsuSettingsSubsection.cs b/osu.Game.Rulesets.Osu/UI/OsuSettingsSubsection.cs index 88adf72551..3870f303b4 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuSettingsSubsection.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuSettingsSubsection.cs @@ -27,17 +27,17 @@ namespace osu.Game.Rulesets.Osu.UI new SettingsCheckbox { LabelText = "Snaking in sliders", - Bindable = config.GetBindable(OsuRulesetSetting.SnakingInSliders) + Current = config.GetBindable(OsuRulesetSetting.SnakingInSliders) }, new SettingsCheckbox { LabelText = "Snaking out sliders", - Bindable = config.GetBindable(OsuRulesetSetting.SnakingOutSliders) + Current = config.GetBindable(OsuRulesetSetting.SnakingOutSliders) }, new SettingsCheckbox { LabelText = "Cursor trail", - Bindable = config.GetBindable(OsuRulesetSetting.ShowCursorTrail) + Current = config.GetBindable(OsuRulesetSetting.ShowCursorTrail) }, }; } diff --git a/osu.Game.Tournament/Components/DateTextBox.cs b/osu.Game.Tournament/Components/DateTextBox.cs index a1b5ac38ea..5782301a65 100644 --- a/osu.Game.Tournament/Components/DateTextBox.cs +++ b/osu.Game.Tournament/Components/DateTextBox.cs @@ -10,34 +10,34 @@ namespace osu.Game.Tournament.Components { public class DateTextBox : SettingsTextBox { - public new Bindable Bindable + public new Bindable Current { - get => bindable; + get => current; set { - bindable = value.GetBoundCopy(); - bindable.BindValueChanged(dto => - base.Bindable.Value = dto.NewValue.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ"), true); + current = value.GetBoundCopy(); + current.BindValueChanged(dto => + base.Current.Value = dto.NewValue.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ"), true); } } // hold a reference to the provided bindable so we don't have to in every settings section. - private Bindable bindable = new Bindable(); + private Bindable current = new Bindable(); public DateTextBox() { - base.Bindable = new Bindable(); + base.Current = new Bindable(); ((OsuTextBox)Control).OnCommit += (sender, newText) => { try { - bindable.Value = DateTimeOffset.Parse(sender.Text); + current.Value = DateTimeOffset.Parse(sender.Text); } catch { // reset textbox content to its last valid state on a parse failure. - bindable.TriggerChange(); + current.TriggerChange(); } }; } diff --git a/osu.Game.Tournament/Screens/Editors/RoundEditorScreen.cs b/osu.Game.Tournament/Screens/Editors/RoundEditorScreen.cs index 8b8078e119..069ddfa4db 100644 --- a/osu.Game.Tournament/Screens/Editors/RoundEditorScreen.cs +++ b/osu.Game.Tournament/Screens/Editors/RoundEditorScreen.cs @@ -63,25 +63,25 @@ namespace osu.Game.Tournament.Screens.Editors { LabelText = "Name", Width = 0.33f, - Bindable = Model.Name + Current = Model.Name }, new SettingsTextBox { LabelText = "Description", Width = 0.33f, - Bindable = Model.Description + Current = Model.Description }, new DateTextBox { LabelText = "Start Time", Width = 0.33f, - Bindable = Model.StartDate + Current = Model.StartDate }, new SettingsSlider { LabelText = "Best of", Width = 0.33f, - Bindable = Model.BestOf + Current = Model.BestOf }, new SettingsButton { @@ -186,14 +186,14 @@ namespace osu.Game.Tournament.Screens.Editors LabelText = "Beatmap ID", RelativeSizeAxes = Axes.None, Width = 200, - Bindable = beatmapId, + Current = beatmapId, }, new SettingsTextBox { LabelText = "Mods", RelativeSizeAxes = Axes.None, Width = 200, - Bindable = mods, + Current = mods, }, drawableContainer = new Container { diff --git a/osu.Game.Tournament/Screens/Editors/SeedingEditorScreen.cs b/osu.Game.Tournament/Screens/Editors/SeedingEditorScreen.cs index 0973a7dc75..7bd8d3f6a0 100644 --- a/osu.Game.Tournament/Screens/Editors/SeedingEditorScreen.cs +++ b/osu.Game.Tournament/Screens/Editors/SeedingEditorScreen.cs @@ -74,13 +74,13 @@ namespace osu.Game.Tournament.Screens.Editors { LabelText = "Mod", Width = 0.33f, - Bindable = Model.Mod + Current = Model.Mod }, new SettingsSlider { LabelText = "Seed", Width = 0.33f, - Bindable = Model.Seed + Current = Model.Seed }, new SettingsButton { @@ -187,21 +187,21 @@ namespace osu.Game.Tournament.Screens.Editors LabelText = "Beatmap ID", RelativeSizeAxes = Axes.None, Width = 200, - Bindable = beatmapId, + Current = beatmapId, }, new SettingsSlider { LabelText = "Seed", RelativeSizeAxes = Axes.None, Width = 200, - Bindable = beatmap.Seed + Current = beatmap.Seed }, new SettingsTextBox { LabelText = "Score", RelativeSizeAxes = Axes.None, Width = 200, - Bindable = score, + Current = score, }, drawableContainer = new Container { diff --git a/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs b/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs index dbfcfe4225..7196f47bd6 100644 --- a/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs +++ b/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs @@ -102,31 +102,31 @@ namespace osu.Game.Tournament.Screens.Editors { LabelText = "Name", Width = 0.2f, - Bindable = Model.FullName + Current = Model.FullName }, new SettingsTextBox { LabelText = "Acronym", Width = 0.2f, - Bindable = Model.Acronym + Current = Model.Acronym }, new SettingsTextBox { LabelText = "Flag", Width = 0.2f, - Bindable = Model.FlagName + Current = Model.FlagName }, new SettingsTextBox { LabelText = "Seed", Width = 0.2f, - Bindable = Model.Seed + Current = Model.Seed }, new SettingsSlider { LabelText = "Last Year Placement", Width = 0.33f, - Bindable = Model.LastYearPlacing + Current = Model.LastYearPlacing }, new SettingsButton { @@ -247,7 +247,7 @@ namespace osu.Game.Tournament.Screens.Editors LabelText = "User ID", RelativeSizeAxes = Axes.None, Width = 200, - Bindable = userId, + Current = userId, }, drawableContainer = new Container { diff --git a/osu.Game.Tournament/Screens/Gameplay/GameplayScreen.cs b/osu.Game.Tournament/Screens/Gameplay/GameplayScreen.cs index e4e3842369..e4ec45c00e 100644 --- a/osu.Game.Tournament/Screens/Gameplay/GameplayScreen.cs +++ b/osu.Game.Tournament/Screens/Gameplay/GameplayScreen.cs @@ -113,13 +113,13 @@ namespace osu.Game.Tournament.Screens.Gameplay new SettingsSlider { LabelText = "Chroma width", - Bindable = LadderInfo.ChromaKeyWidth, + Current = LadderInfo.ChromaKeyWidth, KeyboardStep = 1, }, new SettingsSlider { LabelText = "Players per team", - Bindable = LadderInfo.PlayersPerTeam, + Current = LadderInfo.PlayersPerTeam, KeyboardStep = 1, } } diff --git a/osu.Game.Tournament/Screens/Ladder/Components/LadderEditorSettings.cs b/osu.Game.Tournament/Screens/Ladder/Components/LadderEditorSettings.cs index b60eb814e5..cf4466a2e3 100644 --- a/osu.Game.Tournament/Screens/Ladder/Components/LadderEditorSettings.cs +++ b/osu.Game.Tournament/Screens/Ladder/Components/LadderEditorSettings.cs @@ -51,15 +51,15 @@ namespace osu.Game.Tournament.Screens.Ladder.Components editorInfo.Selected.ValueChanged += selection => { - roundDropdown.Bindable = selection.NewValue?.Round; + roundDropdown.Current = selection.NewValue?.Round; losersCheckbox.Current = selection.NewValue?.Losers; - dateTimeBox.Bindable = selection.NewValue?.Date; + dateTimeBox.Current = selection.NewValue?.Date; - team1Dropdown.Bindable = selection.NewValue?.Team1; - team2Dropdown.Bindable = selection.NewValue?.Team2; + team1Dropdown.Current = selection.NewValue?.Team1; + team2Dropdown.Current = selection.NewValue?.Team2; }; - roundDropdown.Bindable.ValueChanged += round => + roundDropdown.Current.ValueChanged += round => { if (editorInfo.Selected.Value?.Date.Value < round.NewValue?.StartDate.Value) { @@ -88,7 +88,7 @@ namespace osu.Game.Tournament.Screens.Ladder.Components { public SettingsRoundDropdown(BindableList rounds) { - Bindable = new Bindable(); + Current = new Bindable(); foreach (var r in rounds.Prepend(new TournamentRound())) add(r); diff --git a/osu.Game.Tournament/Screens/TeamIntro/SeedingScreen.cs b/osu.Game.Tournament/Screens/TeamIntro/SeedingScreen.cs index eed3cac9f0..b343608e69 100644 --- a/osu.Game.Tournament/Screens/TeamIntro/SeedingScreen.cs +++ b/osu.Game.Tournament/Screens/TeamIntro/SeedingScreen.cs @@ -61,7 +61,7 @@ namespace osu.Game.Tournament.Screens.TeamIntro new SettingsTeamDropdown(LadderInfo.Teams) { LabelText = "Show specific team", - Bindable = currentTeam, + Current = currentTeam, } } } diff --git a/osu.Game/Configuration/SettingSourceAttribute.cs b/osu.Game/Configuration/SettingSourceAttribute.cs index fe487cb1d0..50069be4b2 100644 --- a/osu.Game/Configuration/SettingSourceAttribute.cs +++ b/osu.Game/Configuration/SettingSourceAttribute.cs @@ -57,7 +57,7 @@ namespace osu.Game.Configuration yield return new SettingsSlider { LabelText = attr.Label, - Bindable = bNumber, + Current = bNumber, KeyboardStep = 0.1f, }; @@ -67,7 +67,7 @@ namespace osu.Game.Configuration yield return new SettingsSlider { LabelText = attr.Label, - Bindable = bNumber, + Current = bNumber, KeyboardStep = 0.1f, }; @@ -77,7 +77,7 @@ namespace osu.Game.Configuration yield return new SettingsSlider { LabelText = attr.Label, - Bindable = bNumber + Current = bNumber }; break; @@ -86,7 +86,7 @@ namespace osu.Game.Configuration yield return new SettingsCheckbox { LabelText = attr.Label, - Bindable = bBool + Current = bBool }; break; @@ -95,7 +95,7 @@ namespace osu.Game.Configuration yield return new SettingsTextBox { LabelText = attr.Label, - Bindable = bString + Current = bString }; break; @@ -105,7 +105,7 @@ namespace osu.Game.Configuration var dropdown = (Drawable)Activator.CreateInstance(dropdownType); dropdownType.GetProperty(nameof(SettingsDropdown.LabelText))?.SetValue(dropdown, attr.Label); - dropdownType.GetProperty(nameof(SettingsDropdown.Bindable))?.SetValue(dropdown, bindable); + dropdownType.GetProperty(nameof(SettingsDropdown.Current))?.SetValue(dropdown, bindable); yield return dropdown; diff --git a/osu.Game/Overlays/Settings/Sections/Audio/AudioDevicesSettings.cs b/osu.Game/Overlays/Settings/Sections/Audio/AudioDevicesSettings.cs index 3da64e0de4..bed74542c9 100644 --- a/osu.Game/Overlays/Settings/Sections/Audio/AudioDevicesSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Audio/AudioDevicesSettings.cs @@ -64,7 +64,7 @@ namespace osu.Game.Overlays.Settings.Sections.Audio updateItems(); - dropdown.Bindable = audio.AudioDevice; + dropdown.Current = audio.AudioDevice; audio.OnNewDevice += onDeviceChanged; audio.OnLostDevice += onDeviceChanged; diff --git a/osu.Game/Overlays/Settings/Sections/Audio/MainMenuSettings.cs b/osu.Game/Overlays/Settings/Sections/Audio/MainMenuSettings.cs index a303f93b34..d5de32ed05 100644 --- a/osu.Game/Overlays/Settings/Sections/Audio/MainMenuSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Audio/MainMenuSettings.cs @@ -21,23 +21,23 @@ namespace osu.Game.Overlays.Settings.Sections.Audio new SettingsCheckbox { LabelText = "Interface voices", - Bindable = config.GetBindable(OsuSetting.MenuVoice) + Current = config.GetBindable(OsuSetting.MenuVoice) }, new SettingsCheckbox { LabelText = "osu! music theme", - Bindable = config.GetBindable(OsuSetting.MenuMusic) + Current = config.GetBindable(OsuSetting.MenuMusic) }, new SettingsDropdown { LabelText = "Intro sequence", - Bindable = config.GetBindable(OsuSetting.IntroSequence), + Current = config.GetBindable(OsuSetting.IntroSequence), Items = Enum.GetValues(typeof(IntroSequence)).Cast() }, new SettingsDropdown { LabelText = "Background source", - Bindable = config.GetBindable(OsuSetting.MenuBackgroundSource), + Current = config.GetBindable(OsuSetting.MenuBackgroundSource), Items = Enum.GetValues(typeof(BackgroundSource)).Cast() } }; diff --git a/osu.Game/Overlays/Settings/Sections/Audio/OffsetSettings.cs b/osu.Game/Overlays/Settings/Sections/Audio/OffsetSettings.cs index aaa4302553..c9a81b955b 100644 --- a/osu.Game/Overlays/Settings/Sections/Audio/OffsetSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Audio/OffsetSettings.cs @@ -20,7 +20,7 @@ namespace osu.Game.Overlays.Settings.Sections.Audio new SettingsSlider { LabelText = "Audio offset", - Bindable = config.GetBindable(OsuSetting.AudioOffset), + Current = config.GetBindable(OsuSetting.AudioOffset), KeyboardStep = 1f }, new SettingsButton diff --git a/osu.Game/Overlays/Settings/Sections/Audio/VolumeSettings.cs b/osu.Game/Overlays/Settings/Sections/Audio/VolumeSettings.cs index bda677ecd6..c172a76ab9 100644 --- a/osu.Game/Overlays/Settings/Sections/Audio/VolumeSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Audio/VolumeSettings.cs @@ -20,28 +20,28 @@ namespace osu.Game.Overlays.Settings.Sections.Audio new SettingsSlider { LabelText = "Master", - Bindable = audio.Volume, + Current = audio.Volume, KeyboardStep = 0.01f, DisplayAsPercentage = true }, new SettingsSlider { LabelText = "Master (window inactive)", - Bindable = config.GetBindable(OsuSetting.VolumeInactive), + Current = config.GetBindable(OsuSetting.VolumeInactive), KeyboardStep = 0.01f, DisplayAsPercentage = true }, new SettingsSlider { LabelText = "Effect", - Bindable = audio.VolumeSample, + Current = audio.VolumeSample, KeyboardStep = 0.01f, DisplayAsPercentage = true }, new SettingsSlider { LabelText = "Music", - Bindable = audio.VolumeTrack, + Current = audio.VolumeTrack, KeyboardStep = 0.01f, DisplayAsPercentage = true }, diff --git a/osu.Game/Overlays/Settings/Sections/Debug/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/Debug/GeneralSettings.cs index 9edb18e065..f05b876d8f 100644 --- a/osu.Game/Overlays/Settings/Sections/Debug/GeneralSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Debug/GeneralSettings.cs @@ -19,12 +19,12 @@ namespace osu.Game.Overlays.Settings.Sections.Debug new SettingsCheckbox { LabelText = "Show log overlay", - Bindable = frameworkConfig.GetBindable(FrameworkSetting.ShowLogOverlay) + Current = frameworkConfig.GetBindable(FrameworkSetting.ShowLogOverlay) }, new SettingsCheckbox { LabelText = "Bypass front-to-back render pass", - Bindable = config.GetBindable(DebugSetting.BypassFrontToBackPass) + Current = config.GetBindable(DebugSetting.BypassFrontToBackPass) } }; } diff --git a/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs index 0149e6c3a6..73968761e2 100644 --- a/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs @@ -21,62 +21,62 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay new SettingsSlider { LabelText = "Background dim", - Bindable = config.GetBindable(OsuSetting.DimLevel), + Current = config.GetBindable(OsuSetting.DimLevel), KeyboardStep = 0.01f, DisplayAsPercentage = true }, new SettingsSlider { LabelText = "Background blur", - Bindable = config.GetBindable(OsuSetting.BlurLevel), + Current = config.GetBindable(OsuSetting.BlurLevel), KeyboardStep = 0.01f, DisplayAsPercentage = true }, new SettingsCheckbox { LabelText = "Lighten playfield during breaks", - Bindable = config.GetBindable(OsuSetting.LightenDuringBreaks) + Current = config.GetBindable(OsuSetting.LightenDuringBreaks) }, new SettingsCheckbox { LabelText = "Show score overlay", - Bindable = config.GetBindable(OsuSetting.ShowInterface) + Current = config.GetBindable(OsuSetting.ShowInterface) }, new SettingsCheckbox { LabelText = "Show difficulty graph on progress bar", - Bindable = config.GetBindable(OsuSetting.ShowProgressGraph) + Current = config.GetBindable(OsuSetting.ShowProgressGraph) }, new SettingsCheckbox { LabelText = "Show health display even when you can't fail", - Bindable = config.GetBindable(OsuSetting.ShowHealthDisplayWhenCantFail), + Current = config.GetBindable(OsuSetting.ShowHealthDisplayWhenCantFail), Keywords = new[] { "hp", "bar" } }, new SettingsCheckbox { LabelText = "Fade playfield to red when health is low", - Bindable = config.GetBindable(OsuSetting.FadePlayfieldWhenHealthLow), + Current = config.GetBindable(OsuSetting.FadePlayfieldWhenHealthLow), }, new SettingsCheckbox { LabelText = "Always show key overlay", - Bindable = config.GetBindable(OsuSetting.KeyOverlay) + Current = config.GetBindable(OsuSetting.KeyOverlay) }, new SettingsCheckbox { LabelText = "Positional hitsounds", - Bindable = config.GetBindable(OsuSetting.PositionalHitSounds) + Current = config.GetBindable(OsuSetting.PositionalHitSounds) }, new SettingsEnumDropdown { LabelText = "Score meter type", - Bindable = config.GetBindable(OsuSetting.ScoreMeter) + Current = config.GetBindable(OsuSetting.ScoreMeter) }, new SettingsEnumDropdown { LabelText = "Score display mode", - Bindable = config.GetBindable(OsuSetting.ScoreDisplayMode) + Current = config.GetBindable(OsuSetting.ScoreDisplayMode) } }; @@ -85,7 +85,7 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay Add(new SettingsCheckbox { LabelText = "Disable Windows key during gameplay", - Bindable = config.GetBindable(OsuSetting.GameplayDisableWinKey) + Current = config.GetBindable(OsuSetting.GameplayDisableWinKey) }); } } diff --git a/osu.Game/Overlays/Settings/Sections/Gameplay/ModsSettings.cs b/osu.Game/Overlays/Settings/Sections/Gameplay/ModsSettings.cs index 0babb98066..2b2fb9cef7 100644 --- a/osu.Game/Overlays/Settings/Sections/Gameplay/ModsSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Gameplay/ModsSettings.cs @@ -22,7 +22,7 @@ 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), + Current = config.GetBindable(OsuSetting.IncreaseFirstObjectVisibility), }, }; } diff --git a/osu.Game/Overlays/Settings/Sections/Gameplay/SongSelectSettings.cs b/osu.Game/Overlays/Settings/Sections/Gameplay/SongSelectSettings.cs index 0c42247993..b26876556e 100644 --- a/osu.Game/Overlays/Settings/Sections/Gameplay/SongSelectSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Gameplay/SongSelectSettings.cs @@ -31,31 +31,31 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay new SettingsCheckbox { LabelText = "Right mouse drag to absolute scroll", - Bindable = config.GetBindable(OsuSetting.SongSelectRightMouseScroll), + Current = config.GetBindable(OsuSetting.SongSelectRightMouseScroll), }, new SettingsCheckbox { LabelText = "Show converted beatmaps", - Bindable = config.GetBindable(OsuSetting.ShowConvertedBeatmaps), + Current = config.GetBindable(OsuSetting.ShowConvertedBeatmaps), }, new SettingsSlider { LabelText = "Display beatmaps from", - Bindable = config.GetBindable(OsuSetting.DisplayStarsMinimum), + Current = config.GetBindable(OsuSetting.DisplayStarsMinimum), KeyboardStep = 0.1f, Keywords = new[] { "minimum", "maximum", "star", "difficulty" } }, new SettingsSlider { LabelText = "up to", - Bindable = config.GetBindable(OsuSetting.DisplayStarsMaximum), + Current = config.GetBindable(OsuSetting.DisplayStarsMaximum), KeyboardStep = 0.1f, Keywords = new[] { "minimum", "maximum", "star", "difficulty" } }, new SettingsEnumDropdown { LabelText = "Random selection algorithm", - Bindable = config.GetBindable(OsuSetting.RandomSelectAlgorithm), + Current = config.GetBindable(OsuSetting.RandomSelectAlgorithm), } }; } diff --git a/osu.Game/Overlays/Settings/Sections/General/LanguageSettings.cs b/osu.Game/Overlays/Settings/Sections/General/LanguageSettings.cs index 236bfbecc3..44e42ecbfe 100644 --- a/osu.Game/Overlays/Settings/Sections/General/LanguageSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/General/LanguageSettings.cs @@ -19,7 +19,7 @@ namespace osu.Game.Overlays.Settings.Sections.General new SettingsCheckbox { LabelText = "Prefer metadata in original language", - Bindable = frameworkConfig.GetBindable(FrameworkSetting.ShowUnicode) + Current = frameworkConfig.GetBindable(FrameworkSetting.ShowUnicode) }, }; } diff --git a/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs b/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs index f96e204f62..9e358d0cf5 100644 --- a/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs @@ -240,12 +240,12 @@ namespace osu.Game.Overlays.Settings.Sections.General new SettingsCheckbox { LabelText = "Remember username", - Bindable = config.GetBindable(OsuSetting.SaveUsername), + Current = config.GetBindable(OsuSetting.SaveUsername), }, new SettingsCheckbox { LabelText = "Stay signed in", - Bindable = config.GetBindable(OsuSetting.SavePassword), + Current = config.GetBindable(OsuSetting.SavePassword), }, new Container { diff --git a/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs b/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs index 9c7d0b0be4..a59a6b00b9 100644 --- a/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs @@ -27,7 +27,7 @@ namespace osu.Game.Overlays.Settings.Sections.General Add(new SettingsEnumDropdown { LabelText = "Release stream", - Bindable = config.GetBindable(OsuSetting.ReleaseStream), + Current = config.GetBindable(OsuSetting.ReleaseStream), }); if (updateManager?.CanCheckForUpdate == true) diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/DetailSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/DetailSettings.cs index 3089040f96..30caa45995 100644 --- a/osu.Game/Overlays/Settings/Sections/Graphics/DetailSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Graphics/DetailSettings.cs @@ -19,22 +19,22 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics new SettingsCheckbox { LabelText = "Storyboard / Video", - Bindable = config.GetBindable(OsuSetting.ShowStoryboard) + Current = config.GetBindable(OsuSetting.ShowStoryboard) }, new SettingsCheckbox { LabelText = "Hit Lighting", - Bindable = config.GetBindable(OsuSetting.HitLighting) + Current = config.GetBindable(OsuSetting.HitLighting) }, new SettingsEnumDropdown { LabelText = "Screenshot format", - Bindable = config.GetBindable(OsuSetting.ScreenshotFormat) + Current = config.GetBindable(OsuSetting.ScreenshotFormat) }, new SettingsCheckbox { LabelText = "Show menu cursor in screenshots", - Bindable = config.GetBindable(OsuSetting.ScreenshotCaptureMenuCursor) + Current = config.GetBindable(OsuSetting.ScreenshotCaptureMenuCursor) } }; } diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs index 4312b319c0..14b8dbfac0 100644 --- a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs @@ -62,7 +62,7 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics windowModeDropdown = new SettingsDropdown { LabelText = "Screen mode", - Bindable = config.GetBindable(FrameworkSetting.WindowMode), + Current = config.GetBindable(FrameworkSetting.WindowMode), ItemSource = windowModes, }, resolutionSettingsContainer = new Container @@ -74,14 +74,14 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics { LabelText = "UI Scaling", TransferValueOnCommit = true, - Bindable = osuConfig.GetBindable(OsuSetting.UIScale), + Current = osuConfig.GetBindable(OsuSetting.UIScale), KeyboardStep = 0.01f, Keywords = new[] { "scale", "letterbox" }, }, new SettingsEnumDropdown { LabelText = "Screen Scaling", - Bindable = osuConfig.GetBindable(OsuSetting.Scaling), + Current = osuConfig.GetBindable(OsuSetting.Scaling), Keywords = new[] { "scale", "letterbox" }, }, scalingSettings = new FillFlowContainer> @@ -97,28 +97,28 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics new SettingsSlider { LabelText = "Horizontal position", - Bindable = scalingPositionX, + Current = scalingPositionX, KeyboardStep = 0.01f, DisplayAsPercentage = true }, new SettingsSlider { LabelText = "Vertical position", - Bindable = scalingPositionY, + Current = scalingPositionY, KeyboardStep = 0.01f, DisplayAsPercentage = true }, new SettingsSlider { LabelText = "Horizontal scale", - Bindable = scalingSizeX, + Current = scalingSizeX, KeyboardStep = 0.01f, DisplayAsPercentage = true }, new SettingsSlider { LabelText = "Vertical scale", - Bindable = scalingSizeY, + Current = scalingSizeY, KeyboardStep = 0.01f, DisplayAsPercentage = true }, @@ -126,7 +126,7 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics }, }; - scalingSettings.ForEach(s => bindPreviewEvent(s.Bindable)); + scalingSettings.ForEach(s => bindPreviewEvent(s.Current)); var resolutions = getResolutions(); @@ -137,10 +137,10 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics LabelText = "Resolution", ShowsDefaultIndicator = false, Items = resolutions, - Bindable = sizeFullscreen + Current = sizeFullscreen }; - windowModeDropdown.Bindable.BindValueChanged(mode => + windowModeDropdown.Current.BindValueChanged(mode => { if (mode.NewValue == WindowMode.Fullscreen) { diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/RendererSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/RendererSettings.cs index 69ff9b43e5..8773e6763c 100644 --- a/osu.Game/Overlays/Settings/Sections/Graphics/RendererSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Graphics/RendererSettings.cs @@ -23,17 +23,17 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics new SettingsEnumDropdown { LabelText = "Frame limiter", - Bindable = config.GetBindable(FrameworkSetting.FrameSync) + Current = config.GetBindable(FrameworkSetting.FrameSync) }, new SettingsEnumDropdown { LabelText = "Threading mode", - Bindable = config.GetBindable(FrameworkSetting.ExecutionMode) + Current = config.GetBindable(FrameworkSetting.ExecutionMode) }, new SettingsCheckbox { LabelText = "Show FPS", - Bindable = osuConfig.GetBindable(OsuSetting.ShowFpsDisplay) + Current = osuConfig.GetBindable(OsuSetting.ShowFpsDisplay) }, }; } diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/UserInterfaceSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/UserInterfaceSettings.cs index a8953ac3a2..38c30ddd64 100644 --- a/osu.Game/Overlays/Settings/Sections/Graphics/UserInterfaceSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Graphics/UserInterfaceSettings.cs @@ -20,17 +20,17 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics new SettingsCheckbox { LabelText = "Rotate cursor when dragging", - Bindable = config.GetBindable(OsuSetting.CursorRotation) + Current = config.GetBindable(OsuSetting.CursorRotation) }, new SettingsCheckbox { LabelText = "Parallax", - Bindable = config.GetBindable(OsuSetting.MenuParallax) + Current = config.GetBindable(OsuSetting.MenuParallax) }, new SettingsSlider { LabelText = "Hold-to-confirm activation time", - Bindable = config.GetBindable(OsuSetting.UIHoldActivationDelay), + Current = config.GetBindable(OsuSetting.UIHoldActivationDelay), KeyboardStep = 50 }, }; diff --git a/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs b/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs index d27ab63fb7..5227e328ec 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs @@ -35,32 +35,32 @@ namespace osu.Game.Overlays.Settings.Sections.Input new SettingsCheckbox { LabelText = "Raw input", - Bindable = rawInputToggle + Current = rawInputToggle }, new SensitivitySetting { LabelText = "Cursor sensitivity", - Bindable = sensitivityBindable + Current = sensitivityBindable }, new SettingsCheckbox { LabelText = "Map absolute input to window", - Bindable = config.GetBindable(FrameworkSetting.MapAbsoluteInputToWindow) + Current = config.GetBindable(FrameworkSetting.MapAbsoluteInputToWindow) }, new SettingsEnumDropdown { LabelText = "Confine mouse cursor to window", - Bindable = config.GetBindable(FrameworkSetting.ConfineMouseMode), + Current = config.GetBindable(FrameworkSetting.ConfineMouseMode), }, new SettingsCheckbox { LabelText = "Disable mouse wheel during gameplay", - Bindable = osuConfig.GetBindable(OsuSetting.MouseDisableWheel) + Current = osuConfig.GetBindable(OsuSetting.MouseDisableWheel) }, new SettingsCheckbox { LabelText = "Disable mouse buttons during gameplay", - Bindable = osuConfig.GetBindable(OsuSetting.MouseDisableButtons) + Current = osuConfig.GetBindable(OsuSetting.MouseDisableButtons) }, }; diff --git a/osu.Game/Overlays/Settings/Sections/Online/WebSettings.cs b/osu.Game/Overlays/Settings/Sections/Online/WebSettings.cs index 23513eade8..6461bd7b93 100644 --- a/osu.Game/Overlays/Settings/Sections/Online/WebSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Online/WebSettings.cs @@ -19,13 +19,13 @@ namespace osu.Game.Overlays.Settings.Sections.Online new SettingsCheckbox { LabelText = "Warn about opening external links", - Bindable = config.GetBindable(OsuSetting.ExternalLinkWarning) + Current = config.GetBindable(OsuSetting.ExternalLinkWarning) }, new SettingsCheckbox { LabelText = "Prefer downloads without video", Keywords = new[] { "no-video" }, - Bindable = config.GetBindable(OsuSetting.PreferNoVideo) + Current = config.GetBindable(OsuSetting.PreferNoVideo) }, }; } diff --git a/osu.Game/Overlays/Settings/Sections/SkinSection.cs b/osu.Game/Overlays/Settings/Sections/SkinSection.cs index 596d3a9801..1ade4befdc 100644 --- a/osu.Game/Overlays/Settings/Sections/SkinSection.cs +++ b/osu.Game/Overlays/Settings/Sections/SkinSection.cs @@ -47,29 +47,29 @@ namespace osu.Game.Overlays.Settings.Sections new SettingsSlider { LabelText = "Menu cursor size", - Bindable = config.GetBindable(OsuSetting.MenuCursorSize), + Current = config.GetBindable(OsuSetting.MenuCursorSize), KeyboardStep = 0.01f }, new SettingsSlider { LabelText = "Gameplay cursor size", - Bindable = config.GetBindable(OsuSetting.GameplayCursorSize), + Current = config.GetBindable(OsuSetting.GameplayCursorSize), KeyboardStep = 0.01f }, new SettingsCheckbox { LabelText = "Adjust gameplay cursor size based on current beatmap", - Bindable = config.GetBindable(OsuSetting.AutoCursorSize) + Current = config.GetBindable(OsuSetting.AutoCursorSize) }, new SettingsCheckbox { LabelText = "Beatmap skins", - Bindable = config.GetBindable(OsuSetting.BeatmapSkins) + Current = config.GetBindable(OsuSetting.BeatmapSkins) }, new SettingsCheckbox { LabelText = "Beatmap hitsounds", - Bindable = config.GetBindable(OsuSetting.BeatmapHitsounds) + Current = config.GetBindable(OsuSetting.BeatmapHitsounds) }, }; @@ -81,7 +81,7 @@ namespace osu.Game.Overlays.Settings.Sections config.BindWith(OsuSetting.Skin, configBindable); - skinDropdown.Bindable = dropdownBindable; + skinDropdown.Current = dropdownBindable; skinDropdown.Items = skins.GetAllUsableSkins().ToArray(); // Todo: This should not be necessary when OsuConfigManager is databased diff --git a/osu.Game/Overlays/Settings/SettingsItem.cs b/osu.Game/Overlays/Settings/SettingsItem.cs index c2dd40d2a6..ad6aaafd9d 100644 --- a/osu.Game/Overlays/Settings/SettingsItem.cs +++ b/osu.Game/Overlays/Settings/SettingsItem.cs @@ -21,7 +21,7 @@ using osuTK; namespace osu.Game.Overlays.Settings { - public abstract class SettingsItem : Container, IFilterable, ISettingsItem + public abstract class SettingsItem : Container, IFilterable, ISettingsItem, IHasCurrentValue { protected abstract Drawable CreateControl(); @@ -54,7 +54,7 @@ namespace osu.Game.Overlays.Settings } } - public virtual Bindable Bindable + public virtual Bindable Current { get => controlWithCurrent.Current; set => controlWithCurrent.Current = value; diff --git a/osu.Game/Screens/Edit/Timing/SliderWithTextBoxInput.cs b/osu.Game/Screens/Edit/Timing/SliderWithTextBoxInput.cs index d5afc8978d..f2f9f76143 100644 --- a/osu.Game/Screens/Edit/Timing/SliderWithTextBoxInput.cs +++ b/osu.Game/Screens/Edit/Timing/SliderWithTextBoxInput.cs @@ -11,7 +11,7 @@ using osu.Game.Overlays.Settings; namespace osu.Game.Screens.Edit.Timing { - internal class SliderWithTextBoxInput : CompositeDrawable, IHasCurrentValue + public class SliderWithTextBoxInput : CompositeDrawable, IHasCurrentValue where T : struct, IEquatable, IComparable, IConvertible { private readonly SettingsSlider slider; @@ -51,7 +51,7 @@ namespace osu.Game.Screens.Edit.Timing try { - slider.Bindable.Parse(t.Text); + slider.Current.Parse(t.Text); } catch { @@ -71,8 +71,8 @@ namespace osu.Game.Screens.Edit.Timing public Bindable Current { - get => slider.Bindable; - set => slider.Bindable = value; + get => slider.Current; + set => slider.Current = value; } } } diff --git a/osu.Game/Screens/Edit/Timing/TimingSection.cs b/osu.Game/Screens/Edit/Timing/TimingSection.cs index 2ab8703cc4..1ae2a86885 100644 --- a/osu.Game/Screens/Edit/Timing/TimingSection.cs +++ b/osu.Game/Screens/Edit/Timing/TimingSection.cs @@ -36,14 +36,14 @@ namespace osu.Game.Screens.Edit.Timing { if (point.NewValue != null) { - bpmSlider.Bindable = point.NewValue.BeatLengthBindable; - bpmSlider.Bindable.BindValueChanged(_ => ChangeHandler?.SaveState()); + bpmSlider.Current = point.NewValue.BeatLengthBindable; + bpmSlider.Current.BindValueChanged(_ => ChangeHandler?.SaveState()); bpmTextEntry.Bindable = point.NewValue.BeatLengthBindable; // no need to hook change handler here as it's the same bindable as above - timeSignature.Bindable = point.NewValue.TimeSignatureBindable; - timeSignature.Bindable.BindValueChanged(_ => ChangeHandler?.SaveState()); + timeSignature.Current = point.NewValue.TimeSignatureBindable; + timeSignature.Current.BindValueChanged(_ => ChangeHandler?.SaveState()); } } @@ -121,14 +121,14 @@ namespace osu.Game.Screens.Edit.Timing beatLengthBindable.BindValueChanged(beatLength => updateCurrent(beatLengthToBpm(beatLength.NewValue)), true); bpmBindable.BindValueChanged(bpm => beatLengthBindable.Value = beatLengthToBpm(bpm.NewValue)); - base.Bindable = bpmBindable; + base.Current = bpmBindable; TransferValueOnCommit = true; } - public override Bindable Bindable + public override Bindable Current { - get => base.Bindable; + get => base.Current; set { // incoming will be beat length, not bpm diff --git a/osu.Game/Screens/Play/PlayerSettings/PlaybackSettings.cs b/osu.Game/Screens/Play/PlayerSettings/PlaybackSettings.cs index 24ddc277cd..16e29ac3c8 100644 --- a/osu.Game/Screens/Play/PlayerSettings/PlaybackSettings.cs +++ b/osu.Game/Screens/Play/PlayerSettings/PlaybackSettings.cs @@ -51,14 +51,14 @@ namespace osu.Game.Screens.Play.PlayerSettings } }, }, - rateSlider = new PlayerSliderBar { Bindable = UserPlaybackRate } + rateSlider = new PlayerSliderBar { Current = UserPlaybackRate } }; } protected override void LoadComplete() { base.LoadComplete(); - rateSlider.Bindable.BindValueChanged(multiplier => multiplierText.Text = $"{multiplier.NewValue:0.0}x", true); + rateSlider.Current.BindValueChanged(multiplier => multiplierText.Text = $"{multiplier.NewValue:0.0}x", true); } } } diff --git a/osu.Game/Screens/Play/PlayerSettings/VisualSettings.cs b/osu.Game/Screens/Play/PlayerSettings/VisualSettings.cs index e06cf5c6d5..8f29fe7893 100644 --- a/osu.Game/Screens/Play/PlayerSettings/VisualSettings.cs +++ b/osu.Game/Screens/Play/PlayerSettings/VisualSettings.cs @@ -50,8 +50,8 @@ namespace osu.Game.Screens.Play.PlayerSettings [BackgroundDependencyLoader] private void load(OsuConfigManager config) { - dimSliderBar.Bindable = config.GetBindable(OsuSetting.DimLevel); - blurSliderBar.Bindable = config.GetBindable(OsuSetting.BlurLevel); + dimSliderBar.Current = config.GetBindable(OsuSetting.DimLevel); + blurSliderBar.Current = config.GetBindable(OsuSetting.BlurLevel); showStoryboardToggle.Current = config.GetBindable(OsuSetting.ShowStoryboard); beatmapSkinsToggle.Current = config.GetBindable(OsuSetting.BeatmapSkins); beatmapHitsoundsToggle.Current = config.GetBindable(OsuSetting.BeatmapHitsounds); From e1a6f47d90b226ed0525d4e113cdac3b59e05afb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 6 Oct 2020 16:40:47 +0900 Subject: [PATCH 1508/1782] Add the most basic implementation of LabelledSliderBar feasible --- .../TestSceneLabelledSliderBar.cs | 46 +++++++++++++++++++ .../UserInterfaceV2/LabelledSliderBar.cs | 24 ++++++++++ 2 files changed, 70 insertions(+) create mode 100644 osu.Game.Tests/Visual/UserInterface/TestSceneLabelledSliderBar.cs create mode 100644 osu.Game/Graphics/UserInterfaceV2/LabelledSliderBar.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledSliderBar.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledSliderBar.cs new file mode 100644 index 0000000000..393420e700 --- /dev/null +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledSliderBar.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.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Graphics.UserInterfaceV2; + +namespace osu.Game.Tests.Visual.UserInterface +{ + public class TestSceneLabelledSliderBar : OsuTestScene + { + [TestCase(false)] + [TestCase(true)] + public void TestSliderBar(bool hasDescription) => createSliderBar(hasDescription); + + private void createSliderBar(bool hasDescription = false) + { + AddStep("create component", () => + { + LabelledSliderBar component; + + Child = new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Width = 500, + AutoSizeAxes = Axes.Y, + Child = component = new LabelledSliderBar + { + Current = new BindableDouble(5) + { + MinValue = 0, + MaxValue = 10, + Precision = 1, + } + } + }; + + component.Label = "a sample component"; + component.Description = hasDescription ? "this text describes the component" : string.Empty; + }); + } + } +} diff --git a/osu.Game/Graphics/UserInterfaceV2/LabelledSliderBar.cs b/osu.Game/Graphics/UserInterfaceV2/LabelledSliderBar.cs new file mode 100644 index 0000000000..cba94e314b --- /dev/null +++ b/osu.Game/Graphics/UserInterfaceV2/LabelledSliderBar.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 System; +using osu.Framework.Graphics; +using osu.Game.Overlays.Settings; + +namespace osu.Game.Graphics.UserInterfaceV2 +{ + public class LabelledSliderBar : LabelledComponent, TNumber> + where TNumber : struct, IEquatable, IComparable, IConvertible + { + public LabelledSliderBar() + : base(true) + { + } + + protected override SettingsSlider CreateComponent() => new SettingsSlider + { + TransferValueOnCommit = true, + RelativeSizeAxes = Axes.X, + }; + } +} From 13b67b93a5ba3ff79a78b3a92a7ae1c60f6d0f7c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 6 Oct 2020 15:51:40 +0900 Subject: [PATCH 1509/1782] Add difficulty section --- .../Screens/Edit/Setup/DifficultySection.cs | 25 +++++++++++++++++++ osu.Game/Screens/Edit/Setup/SetupScreen.cs | 1 + 2 files changed, 26 insertions(+) create mode 100644 osu.Game/Screens/Edit/Setup/DifficultySection.cs diff --git a/osu.Game/Screens/Edit/Setup/DifficultySection.cs b/osu.Game/Screens/Edit/Setup/DifficultySection.cs new file mode 100644 index 0000000000..952ce90273 --- /dev/null +++ b/osu.Game/Screens/Edit/Setup/DifficultySection.cs @@ -0,0 +1,25 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Game.Graphics.Sprites; + +namespace osu.Game.Screens.Edit.Setup +{ + internal class DifficultySection : SetupSection + { + [BackgroundDependencyLoader] + private void load() + { + Flow.Children = new Drawable[] + { + new OsuSpriteText + { + Text = "Difficulty settings" + }, + new LabelledSlider() + }; + } + } +} diff --git a/osu.Game/Screens/Edit/Setup/SetupScreen.cs b/osu.Game/Screens/Edit/Setup/SetupScreen.cs index cd4f6733c0..1c3cbb7206 100644 --- a/osu.Game/Screens/Edit/Setup/SetupScreen.cs +++ b/osu.Game/Screens/Edit/Setup/SetupScreen.cs @@ -52,6 +52,7 @@ namespace osu.Game.Screens.Edit.Setup { new ResourcesSection(), new MetadataSection(), + new DifficultySection(), } }, } From 6d7f12ad4bc68c1573a71b8811e8b0a4adebc09e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 6 Oct 2020 17:01:50 +0900 Subject: [PATCH 1510/1782] Add basic difficulty setting sliders --- .../Screens/Edit/Setup/DifficultySection.cs | 63 ++++++++++++++++++- 1 file changed, 62 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Setup/DifficultySection.cs b/osu.Game/Screens/Edit/Setup/DifficultySection.cs index 952ce90273..0434c1cf1f 100644 --- a/osu.Game/Screens/Edit/Setup/DifficultySection.cs +++ b/osu.Game/Screens/Edit/Setup/DifficultySection.cs @@ -1,14 +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; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Game.Beatmaps; using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterfaceV2; namespace osu.Game.Screens.Edit.Setup { internal class DifficultySection : SetupSection { + private LabelledSliderBar circleSizeSlider; + private LabelledSliderBar healthDrainSlider; + private LabelledSliderBar approachRateSlider; + private LabelledSliderBar overallDifficultySlider; + [BackgroundDependencyLoader] private void load() { @@ -18,8 +27,60 @@ namespace osu.Game.Screens.Edit.Setup { Text = "Difficulty settings" }, - new LabelledSlider() + circleSizeSlider = new LabelledSliderBar + { + Label = "Circle Size", + Current = new BindableFloat(Beatmap.Value.BeatmapInfo.BaseDifficulty.CircleSize) + { + Default = BeatmapDifficulty.DEFAULT_DIFFICULTY, + MinValue = 2, + MaxValue = 7 + } + }, + healthDrainSlider = new LabelledSliderBar + { + Label = "Health Drain", + Current = new BindableFloat(Beatmap.Value.BeatmapInfo.BaseDifficulty.DrainRate) + { + Default = BeatmapDifficulty.DEFAULT_DIFFICULTY, + MinValue = 0, + MaxValue = 10 + } + }, + approachRateSlider = new LabelledSliderBar + { + Label = "Approach Rate", + Current = new BindableFloat(Beatmap.Value.BeatmapInfo.BaseDifficulty.ApproachRate) + { + Default = BeatmapDifficulty.DEFAULT_DIFFICULTY, + MinValue = 0, + MaxValue = 10 + } + }, + overallDifficultySlider = new LabelledSliderBar + { + Label = "Overall Difficulty", + Current = new BindableFloat(Beatmap.Value.BeatmapInfo.BaseDifficulty.OverallDifficulty) + { + Default = BeatmapDifficulty.DEFAULT_DIFFICULTY, + MinValue = 0, + MaxValue = 10 + } + }, }; + + foreach (var item in Flow.OfType>()) + item.Current.ValueChanged += onValueChanged; + } + + private void onValueChanged(ValueChangedEvent args) + { + // for now, update these on commit rather than making BeatmapMetadata bindables. + // after switching database engines we can reconsider if switching to bindables is a good direction. + Beatmap.Value.BeatmapInfo.BaseDifficulty.CircleSize = circleSizeSlider.Current.Value; + Beatmap.Value.BeatmapInfo.BaseDifficulty.DrainRate = healthDrainSlider.Current.Value; + Beatmap.Value.BeatmapInfo.BaseDifficulty.ApproachRate = approachRateSlider.Current.Value; + Beatmap.Value.BeatmapInfo.BaseDifficulty.OverallDifficulty = overallDifficultySlider.Current.Value; } } } From 7a20a34aff82cbf491a21da19c18ae156d346378 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 6 Oct 2020 17:12:19 +0900 Subject: [PATCH 1511/1782] Add support to EditorBeatmap to update all hitobjects --- osu.Game/Screens/Edit/EditorBeatmap.cs | 9 +++++++++ osu.Game/Screens/Edit/Setup/DifficultySection.cs | 5 +++++ 2 files changed, 14 insertions(+) diff --git a/osu.Game/Screens/Edit/EditorBeatmap.cs b/osu.Game/Screens/Edit/EditorBeatmap.cs index 3248c5b8be..d37b7dd2b5 100644 --- a/osu.Game/Screens/Edit/EditorBeatmap.cs +++ b/osu.Game/Screens/Edit/EditorBeatmap.cs @@ -258,5 +258,14 @@ namespace osu.Game.Screens.Edit public double GetBeatLengthAtTime(double referenceTime) => ControlPointInfo.TimingPointAt(referenceTime).BeatLength / BeatDivisor; public int BeatDivisor => beatDivisor?.Value ?? 1; + + /// + /// Update all hit objects with potentially changed difficulty or control point data. + /// + public void UpdateBeatmap() + { + foreach (var h in HitObjects) + pendingUpdates.Add(h); + } } } diff --git a/osu.Game/Screens/Edit/Setup/DifficultySection.cs b/osu.Game/Screens/Edit/Setup/DifficultySection.cs index 0434c1cf1f..ce6f617f37 100644 --- a/osu.Game/Screens/Edit/Setup/DifficultySection.cs +++ b/osu.Game/Screens/Edit/Setup/DifficultySection.cs @@ -13,6 +13,9 @@ namespace osu.Game.Screens.Edit.Setup { internal class DifficultySection : SetupSection { + [Resolved] + private EditorBeatmap editorBeatmap { get; set; } + private LabelledSliderBar circleSizeSlider; private LabelledSliderBar healthDrainSlider; private LabelledSliderBar approachRateSlider; @@ -81,6 +84,8 @@ namespace osu.Game.Screens.Edit.Setup Beatmap.Value.BeatmapInfo.BaseDifficulty.DrainRate = healthDrainSlider.Current.Value; Beatmap.Value.BeatmapInfo.BaseDifficulty.ApproachRate = approachRateSlider.Current.Value; Beatmap.Value.BeatmapInfo.BaseDifficulty.OverallDifficulty = overallDifficultySlider.Current.Value; + + editorBeatmap.UpdateBeatmap(); } } } From 7e8ab1cb9587913ba6c50ec362c871e7b4240e73 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 6 Oct 2020 17:17:03 +0900 Subject: [PATCH 1512/1782] Add description text --- osu.Game/Screens/Edit/Setup/DifficultySection.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Setup/DifficultySection.cs b/osu.Game/Screens/Edit/Setup/DifficultySection.cs index ce6f617f37..4ed8d0164b 100644 --- a/osu.Game/Screens/Edit/Setup/DifficultySection.cs +++ b/osu.Game/Screens/Edit/Setup/DifficultySection.cs @@ -32,7 +32,8 @@ namespace osu.Game.Screens.Edit.Setup }, circleSizeSlider = new LabelledSliderBar { - Label = "Circle Size", + Label = "Object Size", + Description = "The size of all hit objects", Current = new BindableFloat(Beatmap.Value.BeatmapInfo.BaseDifficulty.CircleSize) { Default = BeatmapDifficulty.DEFAULT_DIFFICULTY, @@ -43,6 +44,7 @@ namespace osu.Game.Screens.Edit.Setup healthDrainSlider = new LabelledSliderBar { Label = "Health Drain", + Description = "The rate of passive health drain throughout playable time", Current = new BindableFloat(Beatmap.Value.BeatmapInfo.BaseDifficulty.DrainRate) { Default = BeatmapDifficulty.DEFAULT_DIFFICULTY, @@ -53,6 +55,7 @@ namespace osu.Game.Screens.Edit.Setup approachRateSlider = new LabelledSliderBar { Label = "Approach Rate", + Description = "The speed at which objects are presented to the player", Current = new BindableFloat(Beatmap.Value.BeatmapInfo.BaseDifficulty.ApproachRate) { Default = BeatmapDifficulty.DEFAULT_DIFFICULTY, @@ -63,6 +66,7 @@ namespace osu.Game.Screens.Edit.Setup overallDifficultySlider = new LabelledSliderBar { Label = "Overall Difficulty", + Description = "The harshness of hit windows and difficulty of special objects (ie. spinners)", Current = new BindableFloat(Beatmap.Value.BeatmapInfo.BaseDifficulty.OverallDifficulty) { Default = BeatmapDifficulty.DEFAULT_DIFFICULTY, From 3ce234d552bb5ae708b8915c4bfad7b7b8e7c896 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 6 Oct 2020 17:47:22 +0900 Subject: [PATCH 1513/1782] Seek at 4x normal speed when holding shift This matches osu-stable 1:1. Not sure if it feels better or not but let's stick with what people are used to for the time being. --- 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 875ab25003..4b4266d049 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -589,7 +589,7 @@ namespace osu.Game.Screens.Edit private void seek(UIEvent e, int direction) { - double amount = e.ShiftPressed ? 2 : 1; + double amount = e.ShiftPressed ? 4 : 1; if (direction < 1) clock.SeekBackward(!clock.IsRunning, amount); From b1a64f89d78603ef86157940ec582127698f1707 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 6 Oct 2020 17:49:12 +0900 Subject: [PATCH 1514/1782] Increase backwards seek magnitude when the track is running This matches osu-stable. When the track is running, seeking backwards (against the flow) is harder than seeking forwards. Adding a mutliplier makes it feel much better. Note that this is additive not multiplicative because for larger seeks the (where `amount` > 1) we don't want to jump an insanely huge amount - just offset the seek slightly to account for playing audio. --- osu.Game/Screens/Edit/EditorClock.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/EditorClock.cs b/osu.Game/Screens/Edit/EditorClock.cs index 4b7cd82637..64ed34f5ec 100644 --- a/osu.Game/Screens/Edit/EditorClock.cs +++ b/osu.Game/Screens/Edit/EditorClock.cs @@ -86,7 +86,7 @@ namespace osu.Game.Screens.Edit /// /// Whether to snap to the closest beat after seeking. /// The relative amount (magnitude) which should be seeked. - public void SeekBackward(bool snapped = false, double amount = 1) => seek(-1, snapped, amount); + public void SeekBackward(bool snapped = false, double amount = 1) => seek(-1, snapped, amount + (IsRunning ? 1.5 : 0)); /// /// Seeks forwards by one beat length. From 1877312a917ce017b5fa8e58951a4c373cb7cecb Mon Sep 17 00:00:00 2001 From: Shane Woolcock Date: Tue, 6 Oct 2020 19:52:18 +1030 Subject: [PATCH 1515/1782] Rename DuringGameplay --- osu.Game/Configuration/OsuConfigManager.cs | 2 +- osu.Game/Input/ConfineMouseTracker.cs | 4 ++-- osu.Game/Input/OsuConfineMouseMode.cs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 78179a781a..71cbdb345c 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -70,7 +70,7 @@ namespace osu.Game.Configuration Set(OsuSetting.MouseDisableButtons, false); Set(OsuSetting.MouseDisableWheel, false); - Set(OsuSetting.ConfineMouseMode, OsuConfineMouseMode.DuringGameplay); + Set(OsuSetting.ConfineMouseMode, OsuConfineMouseMode.WhenOverlaysDisabled); // Graphics Set(OsuSetting.ShowFpsDisplay, false); diff --git a/osu.Game/Input/ConfineMouseTracker.cs b/osu.Game/Input/ConfineMouseTracker.cs index 6565967d1d..653622e881 100644 --- a/osu.Game/Input/ConfineMouseTracker.cs +++ b/osu.Game/Input/ConfineMouseTracker.cs @@ -16,7 +16,7 @@ namespace osu.Game.Input /// Connects with , /// while optionally binding an mode, usually that of the current . /// It is assumed that while overlay activation is , we should also confine the - /// mouse cursor if it has been requested with . + /// mouse cursor if it has been requested with . /// public class ConfineMouseTracker : Component { @@ -52,7 +52,7 @@ namespace osu.Game.Input frameworkConfineMode.Value = ConfineMouseMode.Fullscreen; break; - case OsuConfineMouseMode.DuringGameplay: + case OsuConfineMouseMode.WhenOverlaysDisabled: frameworkConfineMode.Value = OverlayActivationMode.Value == OverlayActivation.Disabled ? ConfineMouseMode.Always : ConfineMouseMode.Never; break; diff --git a/osu.Game/Input/OsuConfineMouseMode.cs b/osu.Game/Input/OsuConfineMouseMode.cs index 32b456395c..53e352c8bd 100644 --- a/osu.Game/Input/OsuConfineMouseMode.cs +++ b/osu.Game/Input/OsuConfineMouseMode.cs @@ -27,7 +27,7 @@ namespace osu.Game.Input /// but may otherwise move freely. /// [Description("During Gameplay")] - DuringGameplay, + WhenOverlaysDisabled, /// /// The mouse cursor will always be locked to the window bounds while the game has focus. From 782fc1d60fe1c10768ef393ba3102a77e3237662 Mon Sep 17 00:00:00 2001 From: Shane Woolcock Date: Tue, 6 Oct 2020 20:11:48 +1030 Subject: [PATCH 1516/1782] Use OsuGame.OverlayActivationMode rather than per-Player --- osu.Game/Input/ConfineMouseTracker.cs | 19 ++++++------------- osu.Game/Input/OsuConfineMouseMode.cs | 2 +- osu.Game/Screens/Play/Player.cs | 6 ------ 3 files changed, 7 insertions(+), 20 deletions(-) diff --git a/osu.Game/Input/ConfineMouseTracker.cs b/osu.Game/Input/ConfineMouseTracker.cs index 653622e881..3b54c03bb0 100644 --- a/osu.Game/Input/ConfineMouseTracker.cs +++ b/osu.Game/Input/ConfineMouseTracker.cs @@ -8,13 +8,12 @@ using osu.Framework.Graphics; using osu.Framework.Input; using osu.Game.Configuration; using osu.Game.Overlays; -using osu.Game.Screens.Play; namespace osu.Game.Input { /// /// Connects with , - /// while optionally binding an mode, usually that of the current . + /// while binding . /// It is assumed that while overlay activation is , we should also confine the /// mouse cursor if it has been requested with . /// @@ -22,22 +21,16 @@ namespace osu.Game.Input { private Bindable frameworkConfineMode; private Bindable osuConfineMode; - - /// - /// The bindable used to indicate whether gameplay is active. - /// Should be bound to the corresponding bindable of the current . - /// Defaults to to assume that all other screens are considered "not gameplay". - /// - public IBindable OverlayActivationMode { get; } = new Bindable(OverlayActivation.All); + private IBindable overlayActivationMode; [BackgroundDependencyLoader] - private void load(FrameworkConfigManager frameworkConfigManager, OsuConfigManager osuConfigManager) + private void load(OsuGame game, FrameworkConfigManager frameworkConfigManager, OsuConfigManager osuConfigManager) { frameworkConfineMode = frameworkConfigManager.GetBindable(FrameworkSetting.ConfineMouseMode); osuConfineMode = osuConfigManager.GetBindable(OsuSetting.ConfineMouseMode); osuConfineMode.ValueChanged += _ => updateConfineMode(); - - OverlayActivationMode.BindValueChanged(_ => updateConfineMode(), true); + overlayActivationMode = game.OverlayActivationMode.GetBoundCopy(); + overlayActivationMode.BindValueChanged(_ => updateConfineMode(), true); } private void updateConfineMode() @@ -53,7 +46,7 @@ namespace osu.Game.Input break; case OsuConfineMouseMode.WhenOverlaysDisabled: - frameworkConfineMode.Value = OverlayActivationMode.Value == OverlayActivation.Disabled ? ConfineMouseMode.Always : ConfineMouseMode.Never; + frameworkConfineMode.Value = overlayActivationMode?.Value == OverlayActivation.Disabled ? ConfineMouseMode.Always : ConfineMouseMode.Never; break; case OsuConfineMouseMode.Always: diff --git a/osu.Game/Input/OsuConfineMouseMode.cs b/osu.Game/Input/OsuConfineMouseMode.cs index 53e352c8bd..e8411a3d9f 100644 --- a/osu.Game/Input/OsuConfineMouseMode.cs +++ b/osu.Game/Input/OsuConfineMouseMode.cs @@ -23,7 +23,7 @@ namespace osu.Game.Input Fullscreen, /// - /// The mouse cursor will be locked to the window bounds during gameplay, + /// The mouse cursor will be locked to the window bounds while overlays are disabled, /// but may otherwise move freely. /// [Description("During Gameplay")] diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index de67b2d46d..175722c44e 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -18,7 +18,6 @@ using osu.Framework.Threading; using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Graphics.Containers; -using osu.Game.Input; using osu.Game.IO.Archives; using osu.Game.Online.API; using osu.Game.Overlays; @@ -67,9 +66,6 @@ namespace osu.Game.Screens.Play private Bindable mouseWheelDisabled; - [Resolved(CanBeNull = true)] - private ConfineMouseTracker confineMouseTracker { get; set; } - private readonly Bindable storyboardReplacesBackground = new Bindable(); public int RestartCount; @@ -229,8 +225,6 @@ namespace osu.Game.Screens.Play DrawableRuleset.HasReplayLoaded.BindValueChanged(_ => updatePauseOnFocusLostState(), true); - confineMouseTracker?.OverlayActivationMode.BindTo(OverlayActivationMode); - // bind clock into components that require it DrawableRuleset.IsPaused.BindTo(GameplayClockContainer.IsPaused); From e64cee10b8fa82ca05e2d365c760924a75fd3cfe Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 6 Oct 2020 19:07:31 +0900 Subject: [PATCH 1517/1782] Add obsoleted Bindable property back to SettingsItem for compatibility --- osu.Game/Overlays/Settings/SettingsItem.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/osu.Game/Overlays/Settings/SettingsItem.cs b/osu.Game/Overlays/Settings/SettingsItem.cs index ad6aaafd9d..278479e04f 100644 --- a/osu.Game/Overlays/Settings/SettingsItem.cs +++ b/osu.Game/Overlays/Settings/SettingsItem.cs @@ -54,6 +54,13 @@ namespace osu.Game.Overlays.Settings } } + [Obsolete("Use Current instead")] // Can be removed 20210406 + public Bindable Bindable + { + get => Current; + set => Current = value; + } + public virtual Bindable Current { get => controlWithCurrent.Current; From a2796d2c017bce185aaca88e3b581d56599888b8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 6 Oct 2020 19:20:53 +0900 Subject: [PATCH 1518/1782] Add repeats display to timeline blueprints --- .../Timeline/TimelineHitObjectBlueprint.cs | 35 ++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs index bc2ccfc605..f0757a3dda 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs @@ -199,7 +199,40 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline base.Update(); // no bindable so we perform this every update - Width = (float)(HitObject.GetEndTime() - HitObject.StartTime); + float duration = (float)(HitObject.GetEndTime() - HitObject.StartTime); + + if (Width != duration) + { + Width = duration; + + // kind of haphazard but yeah, no bindables. + if (HitObject is IHasRepeats repeats) + updateRepeats(repeats); + } + } + + private Container repeatsContainer; + + private void updateRepeats(IHasRepeats repeats) + { + repeatsContainer?.Expire(); + + mainComponents.Add(repeatsContainer = new Container + { + RelativeSizeAxes = Axes.Both, + }); + + for (int i = 0; i < repeats.RepeatCount; i++) + { + repeatsContainer.Add(new Circle + { + Size = new Vector2(circle_size / 2), + Anchor = Anchor.CentreLeft, + Origin = Anchor.Centre, + RelativePositionAxes = Axes.X, + X = (float)(i + 1) / (repeats.RepeatCount + 1), + }); + } } protected override bool ShouldBeConsideredForInput(Drawable child) => true; From 06a51297a3f2b20d40a99e31d9ee024dea020261 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 6 Oct 2020 19:26:57 +0900 Subject: [PATCH 1519/1782] Use content instead of exposing the flow container --- osu.Game/Screens/Edit/Setup/MetadataSection.cs | 4 ++-- osu.Game/Screens/Edit/Setup/ResourcesSection.cs | 2 +- osu.Game/Screens/Edit/Setup/SetupSection.cs | 7 +++---- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/Edit/Setup/MetadataSection.cs b/osu.Game/Screens/Edit/Setup/MetadataSection.cs index 31a2c2ce1a..4ddee2acc6 100644 --- a/osu.Game/Screens/Edit/Setup/MetadataSection.cs +++ b/osu.Game/Screens/Edit/Setup/MetadataSection.cs @@ -20,7 +20,7 @@ namespace osu.Game.Screens.Edit.Setup [BackgroundDependencyLoader] private void load() { - Flow.Children = new Drawable[] + Children = new Drawable[] { new OsuSpriteText { @@ -52,7 +52,7 @@ namespace osu.Game.Screens.Edit.Setup }, }; - foreach (var item in Flow.OfType()) + foreach (var item in Children.OfType()) item.OnCommit += onCommit; } diff --git a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs index 86d7968856..17ecfdd52e 100644 --- a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs +++ b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs @@ -53,7 +53,7 @@ namespace osu.Game.Screens.Edit.Setup AutoSizeAxes = Axes.Y, }; - Flow.Children = new Drawable[] + Children = new Drawable[] { backgroundSpriteContainer = new Container { diff --git a/osu.Game/Screens/Edit/Setup/SetupSection.cs b/osu.Game/Screens/Edit/Setup/SetupSection.cs index 54e383a4d8..cdf17d355e 100644 --- a/osu.Game/Screens/Edit/Setup/SetupSection.cs +++ b/osu.Game/Screens/Edit/Setup/SetupSection.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -14,7 +13,7 @@ namespace osu.Game.Screens.Edit.Setup { internal class SetupSection : Container { - protected FillFlowContainer Flow; + private readonly FillFlowContainer flow; [Resolved] protected OsuColour Colours { get; private set; } @@ -22,7 +21,7 @@ namespace osu.Game.Screens.Edit.Setup [Resolved] protected IBindable Beatmap { get; private set; } - public override void Add(Drawable drawable) => throw new InvalidOperationException("Use Flow.Add instead"); + protected override Container Content => flow; public SetupSection() { @@ -31,7 +30,7 @@ namespace osu.Game.Screens.Edit.Setup Padding = new MarginPadding(10); - InternalChild = Flow = new FillFlowContainer + InternalChild = flow = new FillFlowContainer { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, From 461be02e6f9ff563ebe458603f9ecaaf6fd9aa26 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 6 Oct 2020 19:34:21 +0900 Subject: [PATCH 1520/1782] Update with underlying changes --- osu.Game/Screens/Edit/Setup/DifficultySection.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Setup/DifficultySection.cs b/osu.Game/Screens/Edit/Setup/DifficultySection.cs index 4ed8d0164b..f23bc0f3b2 100644 --- a/osu.Game/Screens/Edit/Setup/DifficultySection.cs +++ b/osu.Game/Screens/Edit/Setup/DifficultySection.cs @@ -24,7 +24,7 @@ namespace osu.Game.Screens.Edit.Setup [BackgroundDependencyLoader] private void load() { - Flow.Children = new Drawable[] + Children = new Drawable[] { new OsuSpriteText { @@ -76,7 +76,7 @@ namespace osu.Game.Screens.Edit.Setup }, }; - foreach (var item in Flow.OfType>()) + foreach (var item in Children.OfType>()) item.Current.ValueChanged += onValueChanged; } From e8b34ba4acd0d5fb61e06bb3f1ce4c14bffdded4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 6 Oct 2020 20:57:39 +0900 Subject: [PATCH 1521/1782] Fix incorrectly committed testing change --- osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs b/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs index 9b7b7392d8..c216d1efe9 100644 --- a/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs @@ -35,7 +35,7 @@ namespace osu.Game.Overlays.Settings.Sections.General Bindable = config.GetBindable(OsuSetting.ReleaseStream), }); - //if (updateManager?.CanCheckForUpdate == true) + if (updateManager?.CanCheckForUpdate == true) { Add(checkForUpdatesButton = new SettingsButton { From 478f2dec9624f13c9fdf8503774094a19da2e9da Mon Sep 17 00:00:00 2001 From: Shane Woolcock Date: Tue, 6 Oct 2020 22:39:35 +1030 Subject: [PATCH 1522/1782] Maintain the current gameplay state in OsuGame --- osu.Game/Configuration/OsuConfigManager.cs | 2 +- osu.Game/Input/ConfineMouseTracker.cs | 19 +++++++-------- osu.Game/Input/OsuConfineMouseMode.cs | 4 ++-- osu.Game/OsuGame.cs | 5 ++++ osu.Game/Screens/Play/Player.cs | 28 ++++++++++++---------- 5 files changed, 33 insertions(+), 25 deletions(-) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 71cbdb345c..78179a781a 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -70,7 +70,7 @@ namespace osu.Game.Configuration Set(OsuSetting.MouseDisableButtons, false); Set(OsuSetting.MouseDisableWheel, false); - Set(OsuSetting.ConfineMouseMode, OsuConfineMouseMode.WhenOverlaysDisabled); + Set(OsuSetting.ConfineMouseMode, OsuConfineMouseMode.DuringGameplay); // Graphics Set(OsuSetting.ShowFpsDisplay, false); diff --git a/osu.Game/Input/ConfineMouseTracker.cs b/osu.Game/Input/ConfineMouseTracker.cs index 3b54c03bb0..c1089874ae 100644 --- a/osu.Game/Input/ConfineMouseTracker.cs +++ b/osu.Game/Input/ConfineMouseTracker.cs @@ -7,30 +7,29 @@ using osu.Framework.Configuration; using osu.Framework.Graphics; using osu.Framework.Input; using osu.Game.Configuration; -using osu.Game.Overlays; namespace osu.Game.Input { /// - /// Connects with , - /// while binding . - /// It is assumed that while overlay activation is , we should also confine the - /// mouse cursor if it has been requested with . + /// Connects with . + /// If is true, we should also confine the mouse cursor if it has been + /// requested with . /// public class ConfineMouseTracker : Component { private Bindable frameworkConfineMode; private Bindable osuConfineMode; - private IBindable overlayActivationMode; + private IBindable isGameplay; [BackgroundDependencyLoader] private void load(OsuGame game, FrameworkConfigManager frameworkConfigManager, OsuConfigManager osuConfigManager) { frameworkConfineMode = frameworkConfigManager.GetBindable(FrameworkSetting.ConfineMouseMode); osuConfineMode = osuConfigManager.GetBindable(OsuSetting.ConfineMouseMode); + isGameplay = game.IsGameplay.GetBoundCopy(); + osuConfineMode.ValueChanged += _ => updateConfineMode(); - overlayActivationMode = game.OverlayActivationMode.GetBoundCopy(); - overlayActivationMode.BindValueChanged(_ => updateConfineMode(), true); + isGameplay.BindValueChanged(_ => updateConfineMode(), true); } private void updateConfineMode() @@ -45,8 +44,8 @@ namespace osu.Game.Input frameworkConfineMode.Value = ConfineMouseMode.Fullscreen; break; - case OsuConfineMouseMode.WhenOverlaysDisabled: - frameworkConfineMode.Value = overlayActivationMode?.Value == OverlayActivation.Disabled ? ConfineMouseMode.Always : ConfineMouseMode.Never; + case OsuConfineMouseMode.DuringGameplay: + frameworkConfineMode.Value = isGameplay.Value ? ConfineMouseMode.Always : ConfineMouseMode.Never; break; case OsuConfineMouseMode.Always: diff --git a/osu.Game/Input/OsuConfineMouseMode.cs b/osu.Game/Input/OsuConfineMouseMode.cs index e8411a3d9f..32b456395c 100644 --- a/osu.Game/Input/OsuConfineMouseMode.cs +++ b/osu.Game/Input/OsuConfineMouseMode.cs @@ -23,11 +23,11 @@ namespace osu.Game.Input Fullscreen, /// - /// The mouse cursor will be locked to the window bounds while overlays are disabled, + /// The mouse cursor will be locked to the window bounds during gameplay, /// but may otherwise move freely. /// [Description("During Gameplay")] - WhenOverlaysDisabled, + DuringGameplay, /// /// The mouse cursor will always be locked to the window bounds while the game has focus. diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 06e6e4bfb0..466ff13615 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -97,6 +97,11 @@ namespace osu.Game /// public readonly IBindable OverlayActivationMode = new Bindable(); + /// + /// Whether gameplay is currently active. + /// + public readonly Bindable IsGameplay = new BindableBool(); + protected OsuScreenStack ScreenStack; protected BackButton BackButton; diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 175722c44e..a217d2a396 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -68,6 +68,8 @@ namespace osu.Game.Screens.Play private readonly Bindable storyboardReplacesBackground = new Bindable(); + private readonly Bindable isGameplay = new Bindable(); + public int RestartCount; [Resolved] @@ -156,7 +158,7 @@ namespace osu.Game.Screens.Play } [BackgroundDependencyLoader] - private void load(AudioManager audio, OsuConfigManager config) + private void load(AudioManager audio, OsuConfigManager config, OsuGame game) { Mods.Value = base.Mods.Value.Select(m => m.CreateCopy()).ToArray(); @@ -172,6 +174,8 @@ namespace osu.Game.Screens.Play mouseWheelDisabled = config.GetBindable(OsuSetting.MouseDisableWheel); + isGameplay.BindTo(game.IsGameplay); + DrawableRuleset = ruleset.CreateDrawableRulesetWith(playableBeatmap, Mods.Value); ScoreProcessor = ruleset.CreateScoreProcessor(); @@ -219,9 +223,9 @@ namespace osu.Game.Screens.Play skipOverlay.Hide(); } - DrawableRuleset.IsPaused.BindValueChanged(_ => updateOverlayActivationMode()); - DrawableRuleset.HasReplayLoaded.BindValueChanged(_ => updateOverlayActivationMode()); - breakTracker.IsBreakTime.BindValueChanged(_ => updateOverlayActivationMode()); + DrawableRuleset.IsPaused.BindValueChanged(_ => updateGameplayState()); + DrawableRuleset.HasReplayLoaded.BindValueChanged(_ => updateGameplayState()); + breakTracker.IsBreakTime.BindValueChanged(_ => updateGameplayState()); DrawableRuleset.HasReplayLoaded.BindValueChanged(_ => updatePauseOnFocusLostState(), true); @@ -353,14 +357,11 @@ namespace osu.Game.Screens.Play HUDOverlay.KeyCounter.IsCounting = !isBreakTime.NewValue; } - private void updateOverlayActivationMode() + private void updateGameplayState() { - bool canTriggerOverlays = DrawableRuleset.IsPaused.Value || breakTracker.IsBreakTime.Value; - - if (DrawableRuleset.HasReplayLoaded.Value || canTriggerOverlays) - OverlayActivationMode.Value = OverlayActivation.UserTriggered; - else - OverlayActivationMode.Value = OverlayActivation.Disabled; + bool inGameplay = !DrawableRuleset.HasReplayLoaded.Value && !DrawableRuleset.IsPaused.Value && !breakTracker.IsBreakTime.Value; + OverlayActivationMode.Value = inGameplay ? OverlayActivation.Disabled : OverlayActivation.UserTriggered; + isGameplay.Value = inGameplay; } private void updatePauseOnFocusLostState() => @@ -657,7 +658,7 @@ namespace osu.Game.Screens.Play foreach (var mod in Mods.Value.OfType()) mod.ApplyToTrack(musicController.CurrentTrack); - updateOverlayActivationMode(); + updateGameplayState(); } public override void OnSuspending(IScreen next) @@ -693,6 +694,9 @@ namespace osu.Game.Screens.Play musicController.ResetTrackAdjustments(); + // Ensure we reset the IsGameplay state + isGameplay.Value = false; + fadeOut(); return base.OnExiting(next); } From b2dad67adead750b3b3f377d61449bf53e00d5a2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 6 Oct 2020 21:28:59 +0900 Subject: [PATCH 1523/1782] Fix unresolvable dependency in settings test scene --- osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs b/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs index c216d1efe9..c0a525e012 100644 --- a/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs @@ -23,7 +23,7 @@ namespace osu.Game.Overlays.Settings.Sections.General private SettingsButton checkForUpdatesButton; - [Resolved] + [Resolved(CanBeNull = true)] private NotificationOverlay notifications { get; set; } [BackgroundDependencyLoader(true)] @@ -47,7 +47,7 @@ namespace osu.Game.Overlays.Settings.Sections.General { if (!t.Result) { - notifications.Post(new SimpleNotification + notifications?.Post(new SimpleNotification { Text = $"You are running the latest release ({game.Version})", Icon = FontAwesome.Solid.CheckCircle, From 14c734c24407d7e8250ccf8dcba113065c37d623 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 6 Oct 2020 21:21:09 +0900 Subject: [PATCH 1524/1782] Add a very simple method of applying batch changes to EditorBeatmap --- osu.Game/Screens/Edit/EditorBeatmap.cs | 69 ++++++++++++++++--- .../Edit/LegacyEditorBeatmapPatcher.cs | 21 +++--- 2 files changed, 73 insertions(+), 17 deletions(-) diff --git a/osu.Game/Screens/Edit/EditorBeatmap.cs b/osu.Game/Screens/Edit/EditorBeatmap.cs index 3248c5b8be..549423dfb8 100644 --- a/osu.Game/Screens/Edit/EditorBeatmap.cs +++ b/osu.Game/Screens/Edit/EditorBeatmap.cs @@ -91,6 +91,8 @@ namespace osu.Game.Screens.Edit private readonly HashSet pendingUpdates = new HashSet(); + private bool isBatchApplying; + /// /// Adds a collection of s to this . /// @@ -126,12 +128,17 @@ namespace osu.Game.Screens.Edit mutableHitObjects.Insert(index, hitObject); - // must be run after any change to hitobject ordering - beatmapProcessor?.PreProcess(); - processHitObject(hitObject); - beatmapProcessor?.PostProcess(); + if (isBatchApplying) + batchPendingInserts.Add(hitObject); + else + { + // must be run after any change to hitobject ordering + beatmapProcessor?.PreProcess(); + processHitObject(hitObject); + beatmapProcessor?.PostProcess(); - HitObjectAdded?.Invoke(hitObject); + HitObjectAdded?.Invoke(hitObject); + } } /// @@ -180,12 +187,58 @@ namespace osu.Game.Screens.Edit bindable.UnbindAll(); startTimeBindables.Remove(hitObject); - // must be run after any change to hitobject ordering + if (isBatchApplying) + batchPendingDeletes.Add(hitObject); + else + { + // must be run after any change to hitobject ordering + beatmapProcessor?.PreProcess(); + processHitObject(hitObject); + beatmapProcessor?.PostProcess(); + + HitObjectRemoved?.Invoke(hitObject); + } + } + + private readonly List batchPendingInserts = new List(); + + private readonly List batchPendingDeletes = new List(); + + /// + /// Apply a batch of operations in one go, without performing Pre/Postprocessing each time. + /// + /// The function which will apply the batch changes. + public void ApplyBatchChanges(Action applyFunction) + { + if (isBatchApplying) + throw new InvalidOperationException("Attempting to perform a batch application from within an existing batch"); + + isBatchApplying = true; + + applyFunction(this); + beatmapProcessor?.PreProcess(); - processHitObject(hitObject); beatmapProcessor?.PostProcess(); - HitObjectRemoved?.Invoke(hitObject); + isBatchApplying = false; + + foreach (var h in batchPendingDeletes) + { + processHitObject(h); + HitObjectRemoved?.Invoke(h); + } + + batchPendingDeletes.Clear(); + + foreach (var h in batchPendingInserts) + { + processHitObject(h); + HitObjectAdded?.Invoke(h); + } + + batchPendingInserts.Clear(); + + isBatchApplying = false; } /// diff --git a/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs b/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs index 57b7ce6940..fb7d0dd826 100644 --- a/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs +++ b/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs @@ -68,16 +68,19 @@ namespace osu.Game.Screens.Edit toRemove.Sort(); toAdd.Sort(); - // Apply the changes. - for (int i = toRemove.Count - 1; i >= 0; i--) - editorBeatmap.RemoveAt(toRemove[i]); - - if (toAdd.Count > 0) + editorBeatmap.ApplyBatchChanges(eb => { - IBeatmap newBeatmap = readBeatmap(newState); - foreach (var i in toAdd) - editorBeatmap.Insert(i, newBeatmap.HitObjects[i]); - } + // Apply the changes. + for (int i = toRemove.Count - 1; i >= 0; i--) + eb.RemoveAt(toRemove[i]); + + if (toAdd.Count > 0) + { + IBeatmap newBeatmap = readBeatmap(newState); + foreach (var i in toAdd) + eb.Insert(i, newBeatmap.HitObjects[i]); + } + }); } private string readString(byte[] state) => Encoding.UTF8.GetString(state); From 09f5e9c9eb545bdc8880a6362e1de61f39077fb9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 6 Oct 2020 22:09:48 +0900 Subject: [PATCH 1525/1782] Use batch change application in many places that can benefit from it --- .../Compose/Components/SelectionHandler.cs | 5 +--- osu.Game/Screens/Edit/Editor.cs | 3 +- osu.Game/Screens/Edit/EditorBeatmap.cs | 28 +++++++++++++------ 3 files changed, 21 insertions(+), 15 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index fdf8dbe44e..7808d7a5bc 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -239,10 +239,7 @@ namespace osu.Game.Screens.Edit.Compose.Components private void deleteSelected() { ChangeHandler?.BeginChange(); - - foreach (var h in selectedBlueprints.ToList()) - EditorBeatmap?.Remove(h.HitObject); - + EditorBeatmap?.RemoveRange(selectedBlueprints.Select(b => b.HitObject)); ChangeHandler?.EndChange(); } diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 4b4266d049..3c5cbf30e9 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -484,8 +484,7 @@ namespace osu.Game.Screens.Edit protected void Cut() { Copy(); - foreach (var h in editorBeatmap.SelectedHitObjects.ToArray()) - editorBeatmap.Remove(h); + editorBeatmap.RemoveRange(editorBeatmap.SelectedHitObjects.ToArray()); } protected void Copy() diff --git a/osu.Game/Screens/Edit/EditorBeatmap.cs b/osu.Game/Screens/Edit/EditorBeatmap.cs index 549423dfb8..1357f12055 100644 --- a/osu.Game/Screens/Edit/EditorBeatmap.cs +++ b/osu.Game/Screens/Edit/EditorBeatmap.cs @@ -99,8 +99,11 @@ namespace osu.Game.Screens.Edit /// The s to add. public void AddRange(IEnumerable hitObjects) { - foreach (var h in hitObjects) - Add(h); + ApplyBatchChanges(_ => + { + foreach (var h in hitObjects) + Add(h); + }); } /// @@ -166,6 +169,19 @@ namespace osu.Game.Screens.Edit return true; } + /// + /// Removes a collection of s to this . + /// + /// The s to remove. + public void RemoveRange(IEnumerable hitObjects) + { + ApplyBatchChanges(_ => + { + foreach (var h in hitObjects) + Remove(h); + }); + } + /// /// Finds the index of a in this . /// @@ -237,18 +253,12 @@ namespace osu.Game.Screens.Edit } batchPendingInserts.Clear(); - - isBatchApplying = false; } /// /// Clears all from this . /// - public void Clear() - { - foreach (var h in HitObjects.ToArray()) - Remove(h); - } + public void Clear() => RemoveRange(HitObjects.ToArray()); protected override void Update() { From afe3d3989a08a4e6aa0e8d88cc01d7cff78e40ee Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 6 Oct 2020 19:04:50 +0900 Subject: [PATCH 1526/1782] Force first hitobject to be a NewCombo in BeatmapProcessor preprocessing step --- osu.Game/Beatmaps/BeatmapProcessor.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/osu.Game/Beatmaps/BeatmapProcessor.cs b/osu.Game/Beatmaps/BeatmapProcessor.cs index 250cc49ad4..b7b5adc52e 100644 --- a/osu.Game/Beatmaps/BeatmapProcessor.cs +++ b/osu.Game/Beatmaps/BeatmapProcessor.cs @@ -22,8 +22,18 @@ namespace osu.Game.Beatmaps { IHasComboInformation lastObj = null; + bool isFirst = true; + foreach (var obj in Beatmap.HitObjects.OfType()) { + if (isFirst) + { + obj.NewCombo = true; + + // first hitobject should always be marked as a new combo for sanity. + isFirst = false; + } + if (obj.NewCombo) { obj.IndexInCurrentCombo = 0; From 1f2dd13b49ccaeb572150b159bac8420c22f2dd4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 6 Oct 2020 19:04:58 +0900 Subject: [PATCH 1527/1782] Update tests --- .../Editing/LegacyEditorBeatmapPatcherTest.cs | 45 ++++++++++--------- 1 file changed, 24 insertions(+), 21 deletions(-) diff --git a/osu.Game.Tests/Editing/LegacyEditorBeatmapPatcherTest.cs b/osu.Game.Tests/Editing/LegacyEditorBeatmapPatcherTest.cs index b491157627..afaaafdd26 100644 --- a/osu.Game.Tests/Editing/LegacyEditorBeatmapPatcherTest.cs +++ b/osu.Game.Tests/Editing/LegacyEditorBeatmapPatcherTest.cs @@ -44,7 +44,7 @@ namespace osu.Game.Tests.Editing { HitObjects = { - new HitCircle { StartTime = 1000 } + new HitCircle { StartTime = 1000, NewCombo = true } } }; @@ -56,7 +56,7 @@ namespace osu.Game.Tests.Editing { current.AddRange(new[] { - new HitCircle { StartTime = 1000 }, + new HitCircle { StartTime = 1000, NewCombo = true }, new HitCircle { StartTime = 3000 }, }); @@ -78,7 +78,7 @@ namespace osu.Game.Tests.Editing { current.AddRange(new[] { - new HitCircle { StartTime = 1000 }, + new HitCircle { StartTime = 1000, NewCombo = true }, new HitCircle { StartTime = 2000 }, new HitCircle { StartTime = 3000 }, }); @@ -100,7 +100,7 @@ namespace osu.Game.Tests.Editing { current.AddRange(new[] { - new HitCircle { StartTime = 1000 }, + new HitCircle { StartTime = 1000, NewCombo = true }, new HitCircle { StartTime = 2000 }, new HitCircle { StartTime = 3000 }, }); @@ -109,7 +109,7 @@ namespace osu.Game.Tests.Editing { HitObjects = { - new HitCircle { StartTime = 500 }, + new HitCircle { StartTime = 500, NewCombo = true }, (OsuHitObject)current.HitObjects[1], (OsuHitObject)current.HitObjects[2], } @@ -123,7 +123,7 @@ namespace osu.Game.Tests.Editing { current.AddRange(new[] { - new HitCircle { StartTime = 1000 }, + new HitCircle { StartTime = 1000, NewCombo = true }, new HitCircle { StartTime = 2000 }, new HitCircle { StartTime = 3000 }, }); @@ -146,7 +146,7 @@ namespace osu.Game.Tests.Editing { current.AddRange(new OsuHitObject[] { - new HitCircle { StartTime = 1000 }, + new HitCircle { StartTime = 1000, NewCombo = true }, new Slider { StartTime = 2000, @@ -188,7 +188,7 @@ namespace osu.Game.Tests.Editing { current.AddRange(new[] { - new HitCircle { StartTime = 1000 }, + new HitCircle { StartTime = 1000, NewCombo = true }, new HitCircle { StartTime = 2000 }, new HitCircle { StartTime = 3000 }, }); @@ -197,7 +197,7 @@ namespace osu.Game.Tests.Editing { HitObjects = { - new HitCircle { StartTime = 500 }, + new HitCircle { StartTime = 500, NewCombo = true }, (OsuHitObject)current.HitObjects[0], new HitCircle { StartTime = 1500 }, (OsuHitObject)current.HitObjects[1], @@ -216,7 +216,7 @@ namespace osu.Game.Tests.Editing { current.AddRange(new[] { - new HitCircle { StartTime = 500 }, + new HitCircle { StartTime = 500, NewCombo = true }, new HitCircle { StartTime = 1000 }, new HitCircle { StartTime = 1500 }, new HitCircle { StartTime = 2000 }, @@ -226,6 +226,9 @@ namespace osu.Game.Tests.Editing new HitCircle { StartTime = 3500 }, }); + var patchedFirst = (HitCircle)current.HitObjects[1]; + patchedFirst.NewCombo = true; + var patch = new OsuBeatmap { HitObjects = @@ -244,7 +247,7 @@ namespace osu.Game.Tests.Editing { current.AddRange(new[] { - new HitCircle { StartTime = 500 }, + new HitCircle { StartTime = 500, NewCombo = true }, new HitCircle { StartTime = 1000 }, new HitCircle { StartTime = 1500 }, new HitCircle { StartTime = 2000 }, @@ -277,7 +280,7 @@ namespace osu.Game.Tests.Editing { current.AddRange(new[] { - new HitCircle { StartTime = 500 }, + new HitCircle { StartTime = 500, NewCombo = true }, new HitCircle { StartTime = 1000 }, new HitCircle { StartTime = 1500 }, new HitCircle { StartTime = 2000 }, @@ -291,7 +294,7 @@ namespace osu.Game.Tests.Editing { HitObjects = { - new HitCircle { StartTime = 750 }, + new HitCircle { StartTime = 750, NewCombo = true }, (OsuHitObject)current.HitObjects[1], (OsuHitObject)current.HitObjects[4], (OsuHitObject)current.HitObjects[5], @@ -309,20 +312,20 @@ namespace osu.Game.Tests.Editing { current.AddRange(new[] { - new HitCircle { StartTime = 500, Position = new Vector2(50) }, - new HitCircle { StartTime = 500, Position = new Vector2(100) }, - new HitCircle { StartTime = 500, Position = new Vector2(150) }, - new HitCircle { StartTime = 500, Position = new Vector2(200) }, + new HitCircle { StartTime = 500, Position = new Vector2(50), NewCombo = true }, + new HitCircle { StartTime = 500, Position = new Vector2(100), NewCombo = true }, + new HitCircle { StartTime = 500, Position = new Vector2(150), NewCombo = true }, + new HitCircle { StartTime = 500, Position = new Vector2(200), NewCombo = true }, }); var patch = new OsuBeatmap { HitObjects = { - new HitCircle { StartTime = 500, Position = new Vector2(150) }, - new HitCircle { StartTime = 500, Position = new Vector2(100) }, - new HitCircle { StartTime = 500, Position = new Vector2(50) }, - new HitCircle { StartTime = 500, Position = new Vector2(200) }, + new HitCircle { StartTime = 500, Position = new Vector2(150), NewCombo = true }, + new HitCircle { StartTime = 500, Position = new Vector2(100), NewCombo = true }, + new HitCircle { StartTime = 500, Position = new Vector2(50), NewCombo = true }, + new HitCircle { StartTime = 500, Position = new Vector2(200), NewCombo = true }, } }; From 01636d501a0fc5dbf4a1959c23e9d3eaf577817f Mon Sep 17 00:00:00 2001 From: Endrik Tombak Date: Tue, 6 Oct 2020 18:36:15 +0300 Subject: [PATCH 1528/1782] Add MinResults test and starts of score portion tests --- .../Rulesets/Scoring/ScoreProcessorTest.cs | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs index 38d2b4a47f..52848cb716 100644 --- a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs +++ b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.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 NUnit.Framework; @@ -9,6 +10,7 @@ using osu.Game.Beatmaps; using osu.Game.Rulesets; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Judgements; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Scoring; @@ -54,6 +56,44 @@ namespace osu.Game.Tests.Rulesets.Scoring Assert.IsTrue(Precision.AlmostEquals(expectedScore, scoreProcessor.TotalScore.Value)); } + [TestCase(ScoringMode.Standardised, "osu", typeof(HitCircle), HitResult.Great, 575_000)] + public void TestFourVariousResultsOneMiss(ScoringMode scoringMode, string rulesetName, Type hitObjectType, HitResult hitResult, int expectedScore) + { + IBeatmap fourObjectBeatmap = new TestBeatmap(new OsuRuleset().RulesetInfo) + { + HitObjects = new List(Enumerable.Repeat((HitObject)Activator.CreateInstance(hitObjectType), 4)) + }; + scoreProcessor.Mode.Value = scoringMode; + scoreProcessor.ApplyBeatmap(fourObjectBeatmap); + + for (int i = 0; i < 4; i++) + { + var judgementResult = new JudgementResult(fourObjectBeatmap.HitObjects[i], new Judgement()) + { + Type = i == 2 ? HitResult.Miss : hitResult + }; + scoreProcessor.ApplyResult(judgementResult); + } + + Assert.IsTrue(Precision.AlmostEquals(expectedScore, scoreProcessor.TotalScore.Value)); + } + + [TestCase(HitResult.IgnoreHit, HitResult.IgnoreMiss)] + [TestCase(HitResult.Meh, HitResult.Miss)] + [TestCase(HitResult.Ok, HitResult.Miss)] + [TestCase(HitResult.Good, HitResult.Miss)] + [TestCase(HitResult.Great, HitResult.Miss)] + [TestCase(HitResult.Perfect, HitResult.Miss)] + [TestCase(HitResult.SmallTickHit, HitResult.SmallTickMiss)] + [TestCase(HitResult.LargeTickHit, HitResult.LargeTickMiss)] + [TestCase(HitResult.SmallBonus, HitResult.IgnoreMiss)] + [TestCase(HitResult.LargeBonus, HitResult.IgnoreMiss)] + public void TestMinResults(HitResult hitResult, HitResult expectedMinResult) + { + var result = new JudgementResult(new HitObject(), new TestJudgement(hitResult)); + Assert.IsTrue(result.Judgement.MinResult == expectedMinResult); + } + [TestCase(HitResult.None, false)] [TestCase(HitResult.IgnoreMiss, false)] [TestCase(HitResult.IgnoreHit, false)] @@ -153,5 +193,15 @@ namespace osu.Game.Tests.Rulesets.Scoring { Assert.IsTrue(hitResult.IsScorable() == expectedReturnValue); } + + private class TestJudgement : Judgement + { + public override HitResult MaxResult { get; } + + public TestJudgement(HitResult maxResult) + { + MaxResult = maxResult; + } + } } } From bdc84c529114b14957aeb9959f2fddca675956f2 Mon Sep 17 00:00:00 2001 From: Endrik Tombak Date: Tue, 6 Oct 2020 19:53:24 +0300 Subject: [PATCH 1529/1782] Finish score portion tests for standardised scoring mode --- .../Rulesets/Scoring/ScoreProcessorTest.cs | 38 +++++++++++++++---- 1 file changed, 31 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs index 52848cb716..d38a2a89cc 100644 --- a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs +++ b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.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 NUnit.Framework; @@ -10,7 +9,6 @@ using osu.Game.Beatmaps; using osu.Game.Rulesets; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects; -using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Judgements; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Scoring; @@ -56,12 +54,23 @@ namespace osu.Game.Tests.Rulesets.Scoring Assert.IsTrue(Precision.AlmostEquals(expectedScore, scoreProcessor.TotalScore.Value)); } - [TestCase(ScoringMode.Standardised, "osu", typeof(HitCircle), HitResult.Great, 575_000)] - public void TestFourVariousResultsOneMiss(ScoringMode scoringMode, string rulesetName, Type hitObjectType, HitResult hitResult, int expectedScore) + [TestCase(ScoringMode.Standardised, HitResult.Miss, HitResult.Great, 0)] + [TestCase(ScoringMode.Standardised, HitResult.Meh, HitResult.Great, 387_500)] + [TestCase(ScoringMode.Standardised, HitResult.Ok, HitResult.Great, 425_000)] + [TestCase(ScoringMode.Standardised, HitResult.Great, HitResult.Great, 575_000)] + [TestCase(ScoringMode.Standardised, HitResult.SmallTickMiss, HitResult.SmallTickHit, 0)] + [TestCase(ScoringMode.Standardised, HitResult.SmallTickHit, HitResult.SmallTickHit, 0)] // TODO: idk, this should be 225_000 from accuracy portion + [TestCase(ScoringMode.Standardised, HitResult.LargeTickMiss, HitResult.LargeTickHit, 0)] + [TestCase(ScoringMode.Standardised, HitResult.LargeTickHit, HitResult.LargeTickHit, 575_000)] + [TestCase(ScoringMode.Standardised, HitResult.SmallBonus, HitResult.SmallBonus, 30)] + [TestCase(ScoringMode.Standardised, HitResult.LargeBonus, HitResult.LargeBonus, 150)] + public void TestFourVariousResultsOneMiss(ScoringMode scoringMode, HitResult hitResult, HitResult maxResult, int expectedScore) { - IBeatmap fourObjectBeatmap = new TestBeatmap(new OsuRuleset().RulesetInfo) + var minResult = new JudgementResult(new HitObject(), new TestJudgement(hitResult)).Judgement.MinResult; + + IBeatmap fourObjectBeatmap = new TestBeatmap(new RulesetInfo()) { - HitObjects = new List(Enumerable.Repeat((HitObject)Activator.CreateInstance(hitObjectType), 4)) + HitObjects = new List(Enumerable.Repeat(new TestHitObject(maxResult), 4)) }; scoreProcessor.Mode.Value = scoringMode; scoreProcessor.ApplyBeatmap(fourObjectBeatmap); @@ -70,7 +79,7 @@ namespace osu.Game.Tests.Rulesets.Scoring { var judgementResult = new JudgementResult(fourObjectBeatmap.HitObjects[i], new Judgement()) { - Type = i == 2 ? HitResult.Miss : hitResult + Type = i == 2 ? minResult : hitResult }; scoreProcessor.ApplyResult(judgementResult); } @@ -203,5 +212,20 @@ namespace osu.Game.Tests.Rulesets.Scoring MaxResult = maxResult; } } + + private class TestHitObject : HitObject + { + private readonly HitResult maxResult; + + public override Judgement CreateJudgement() + { + return new TestJudgement(maxResult); + } + + public TestHitObject(HitResult maxResult) + { + this.maxResult = maxResult; + } + } } } From f5a6beb4e55f17407640c32f85cd21d1baf86284 Mon Sep 17 00:00:00 2001 From: Lucas A Date: Tue, 6 Oct 2020 19:01:03 +0200 Subject: [PATCH 1530/1782] Remove obsoletion notice. --- osu.Game/Rulesets/Ruleset.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index 2ba884efc2..ae1407fb8f 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -160,7 +160,6 @@ namespace osu.Game.Rulesets public virtual PerformanceCalculator CreatePerformanceCalculator(DifficultyAttributes attributes, ScoreInfo score) => null; - [Obsolete("Use the DifficultyAttributes overload instead.")] public PerformanceCalculator CreatePerformanceCalculator(WorkingBeatmap beatmap, ScoreInfo score) { var difficultyCalculator = CreateDifficultyCalculator(beatmap); From 879131c6752fcad96f42fe4bb17de0dc75b4095a Mon Sep 17 00:00:00 2001 From: Endrik Tombak Date: Tue, 6 Oct 2020 20:02:33 +0300 Subject: [PATCH 1531/1782] Also test Goods and Perfects --- osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs index d38a2a89cc..e1afb82e81 100644 --- a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs +++ b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs @@ -57,14 +57,16 @@ namespace osu.Game.Tests.Rulesets.Scoring [TestCase(ScoringMode.Standardised, HitResult.Miss, HitResult.Great, 0)] [TestCase(ScoringMode.Standardised, HitResult.Meh, HitResult.Great, 387_500)] [TestCase(ScoringMode.Standardised, HitResult.Ok, HitResult.Great, 425_000)] + [TestCase(ScoringMode.Standardised, HitResult.Good, HitResult.Perfect, 3_350_000 / 7.0)] [TestCase(ScoringMode.Standardised, HitResult.Great, HitResult.Great, 575_000)] + [TestCase(ScoringMode.Standardised, HitResult.Perfect, HitResult.Perfect, 575_000)] [TestCase(ScoringMode.Standardised, HitResult.SmallTickMiss, HitResult.SmallTickHit, 0)] [TestCase(ScoringMode.Standardised, HitResult.SmallTickHit, HitResult.SmallTickHit, 0)] // TODO: idk, this should be 225_000 from accuracy portion [TestCase(ScoringMode.Standardised, HitResult.LargeTickMiss, HitResult.LargeTickHit, 0)] [TestCase(ScoringMode.Standardised, HitResult.LargeTickHit, HitResult.LargeTickHit, 575_000)] [TestCase(ScoringMode.Standardised, HitResult.SmallBonus, HitResult.SmallBonus, 30)] [TestCase(ScoringMode.Standardised, HitResult.LargeBonus, HitResult.LargeBonus, 150)] - public void TestFourVariousResultsOneMiss(ScoringMode scoringMode, HitResult hitResult, HitResult maxResult, int expectedScore) + public void TestFourVariousResultsOneMiss(ScoringMode scoringMode, HitResult hitResult, HitResult maxResult, double expectedScore) { var minResult = new JudgementResult(new HitObject(), new TestJudgement(hitResult)).Judgement.MinResult; From 6684a98a321ff086811225f44b1d3c13b8039ec2 Mon Sep 17 00:00:00 2001 From: Endrik Tombak Date: Tue, 6 Oct 2020 20:24:42 +0300 Subject: [PATCH 1532/1782] Also test Classic scoring --- .../Rulesets/Scoring/ScoreProcessorTest.cs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs index e1afb82e81..dd364a645c 100644 --- a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs +++ b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs @@ -61,11 +61,23 @@ namespace osu.Game.Tests.Rulesets.Scoring [TestCase(ScoringMode.Standardised, HitResult.Great, HitResult.Great, 575_000)] [TestCase(ScoringMode.Standardised, HitResult.Perfect, HitResult.Perfect, 575_000)] [TestCase(ScoringMode.Standardised, HitResult.SmallTickMiss, HitResult.SmallTickHit, 0)] - [TestCase(ScoringMode.Standardised, HitResult.SmallTickHit, HitResult.SmallTickHit, 0)] // TODO: idk, this should be 225_000 from accuracy portion + [TestCase(ScoringMode.Standardised, HitResult.SmallTickHit, HitResult.SmallTickHit, 0)] // TODO: this should probably be 225_000 from accuracy portion [TestCase(ScoringMode.Standardised, HitResult.LargeTickMiss, HitResult.LargeTickHit, 0)] [TestCase(ScoringMode.Standardised, HitResult.LargeTickHit, HitResult.LargeTickHit, 575_000)] [TestCase(ScoringMode.Standardised, HitResult.SmallBonus, HitResult.SmallBonus, 30)] [TestCase(ScoringMode.Standardised, HitResult.LargeBonus, HitResult.LargeBonus, 150)] + [TestCase(ScoringMode.Classic, HitResult.Miss, HitResult.Great, 0)] + [TestCase(ScoringMode.Classic, HitResult.Meh, HitResult.Great, 156)] + [TestCase(ScoringMode.Classic, HitResult.Ok, HitResult.Great, 312)] + [TestCase(ScoringMode.Classic, HitResult.Good, HitResult.Perfect, 3744 / 7.0)] + [TestCase(ScoringMode.Classic, HitResult.Great, HitResult.Great, 936)] + [TestCase(ScoringMode.Classic, HitResult.Perfect, HitResult.Perfect, 936)] + [TestCase(ScoringMode.Classic, HitResult.SmallTickMiss, HitResult.SmallTickHit, 0)] + [TestCase(ScoringMode.Classic, HitResult.SmallTickHit, HitResult.SmallTickHit, 0)] + [TestCase(ScoringMode.Classic, HitResult.LargeTickMiss, HitResult.LargeTickHit, 0)] + [TestCase(ScoringMode.Classic, HitResult.LargeTickHit, HitResult.LargeTickHit, 936)] + [TestCase(ScoringMode.Classic, HitResult.SmallBonus, HitResult.SmallBonus, 30)] + [TestCase(ScoringMode.Classic, HitResult.LargeBonus, HitResult.LargeBonus, 150)] public void TestFourVariousResultsOneMiss(ScoringMode scoringMode, HitResult hitResult, HitResult maxResult, double expectedScore) { var minResult = new JudgementResult(new HitObject(), new TestJudgement(hitResult)).Judgement.MinResult; From a31fe5f5ff9d23532c2b3c681a335236e51a72a7 Mon Sep 17 00:00:00 2001 From: Endrik Tombak Date: Tue, 6 Oct 2020 22:26:18 +0300 Subject: [PATCH 1533/1782] Temporarily remove SmallTickHit tests --- osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs index dd364a645c..2ad9837654 100644 --- a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs +++ b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs @@ -60,8 +60,6 @@ namespace osu.Game.Tests.Rulesets.Scoring [TestCase(ScoringMode.Standardised, HitResult.Good, HitResult.Perfect, 3_350_000 / 7.0)] [TestCase(ScoringMode.Standardised, HitResult.Great, HitResult.Great, 575_000)] [TestCase(ScoringMode.Standardised, HitResult.Perfect, HitResult.Perfect, 575_000)] - [TestCase(ScoringMode.Standardised, HitResult.SmallTickMiss, HitResult.SmallTickHit, 0)] - [TestCase(ScoringMode.Standardised, HitResult.SmallTickHit, HitResult.SmallTickHit, 0)] // TODO: this should probably be 225_000 from accuracy portion [TestCase(ScoringMode.Standardised, HitResult.LargeTickMiss, HitResult.LargeTickHit, 0)] [TestCase(ScoringMode.Standardised, HitResult.LargeTickHit, HitResult.LargeTickHit, 575_000)] [TestCase(ScoringMode.Standardised, HitResult.SmallBonus, HitResult.SmallBonus, 30)] @@ -72,8 +70,6 @@ namespace osu.Game.Tests.Rulesets.Scoring [TestCase(ScoringMode.Classic, HitResult.Good, HitResult.Perfect, 3744 / 7.0)] [TestCase(ScoringMode.Classic, HitResult.Great, HitResult.Great, 936)] [TestCase(ScoringMode.Classic, HitResult.Perfect, HitResult.Perfect, 936)] - [TestCase(ScoringMode.Classic, HitResult.SmallTickMiss, HitResult.SmallTickHit, 0)] - [TestCase(ScoringMode.Classic, HitResult.SmallTickHit, HitResult.SmallTickHit, 0)] [TestCase(ScoringMode.Classic, HitResult.LargeTickMiss, HitResult.LargeTickHit, 0)] [TestCase(ScoringMode.Classic, HitResult.LargeTickHit, HitResult.LargeTickHit, 936)] [TestCase(ScoringMode.Classic, HitResult.SmallBonus, HitResult.SmallBonus, 30)] From 4c9840ccf1f8bf91801f099b1c3d8ac43f9cde2c Mon Sep 17 00:00:00 2001 From: Endrik Tombak Date: Tue, 6 Oct 2020 22:57:55 +0300 Subject: [PATCH 1534/1782] Apply review suggestions --- .../Rulesets/Scoring/ScoreProcessorTest.cs | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs index 2ad9837654..1181d82d09 100644 --- a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs +++ b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs @@ -76,7 +76,7 @@ namespace osu.Game.Tests.Rulesets.Scoring [TestCase(ScoringMode.Classic, HitResult.LargeBonus, HitResult.LargeBonus, 150)] public void TestFourVariousResultsOneMiss(ScoringMode scoringMode, HitResult hitResult, HitResult maxResult, double expectedScore) { - var minResult = new JudgementResult(new HitObject(), new TestJudgement(hitResult)).Judgement.MinResult; + var minResult = new TestJudgement(hitResult).MinResult; IBeatmap fourObjectBeatmap = new TestBeatmap(new RulesetInfo()) { @@ -109,8 +109,7 @@ namespace osu.Game.Tests.Rulesets.Scoring [TestCase(HitResult.LargeBonus, HitResult.IgnoreMiss)] public void TestMinResults(HitResult hitResult, HitResult expectedMinResult) { - var result = new JudgementResult(new HitObject(), new TestJudgement(hitResult)); - Assert.IsTrue(result.Judgement.MinResult == expectedMinResult); + Assert.AreEqual(expectedMinResult, new TestJudgement(hitResult).MinResult); } [TestCase(HitResult.None, false)] @@ -130,7 +129,7 @@ namespace osu.Game.Tests.Rulesets.Scoring [TestCase(HitResult.LargeBonus, false)] public void TestAffectsCombo(HitResult hitResult, bool expectedReturnValue) { - Assert.IsTrue(hitResult.AffectsCombo() == expectedReturnValue); + Assert.AreEqual(expectedReturnValue, hitResult.AffectsCombo()); } [TestCase(HitResult.None, false)] @@ -150,7 +149,7 @@ namespace osu.Game.Tests.Rulesets.Scoring [TestCase(HitResult.LargeBonus, false)] public void TestAffectsAccuracy(HitResult hitResult, bool expectedReturnValue) { - Assert.IsTrue(hitResult.AffectsAccuracy() == expectedReturnValue); + Assert.AreEqual(expectedReturnValue, hitResult.AffectsAccuracy()); } [TestCase(HitResult.None, false)] @@ -170,7 +169,7 @@ namespace osu.Game.Tests.Rulesets.Scoring [TestCase(HitResult.LargeBonus, true)] public void TestIsBonus(HitResult hitResult, bool expectedReturnValue) { - Assert.IsTrue(hitResult.IsBonus() == expectedReturnValue); + Assert.AreEqual(expectedReturnValue, hitResult.IsBonus()); } [TestCase(HitResult.None, false)] @@ -190,7 +189,7 @@ namespace osu.Game.Tests.Rulesets.Scoring [TestCase(HitResult.LargeBonus, true)] public void TestIsHit(HitResult hitResult, bool expectedReturnValue) { - Assert.IsTrue(hitResult.IsHit() == expectedReturnValue); + Assert.AreEqual(expectedReturnValue, hitResult.IsHit()); } [TestCase(HitResult.None, false)] @@ -210,7 +209,7 @@ namespace osu.Game.Tests.Rulesets.Scoring [TestCase(HitResult.LargeBonus, true)] public void TestIsScorable(HitResult hitResult, bool expectedReturnValue) { - Assert.IsTrue(hitResult.IsScorable() == expectedReturnValue); + Assert.AreEqual(expectedReturnValue, hitResult.IsScorable()); } private class TestJudgement : Judgement From 5e314c0662919757871761414682e3389886bfa3 Mon Sep 17 00:00:00 2001 From: Endrik Tombak Date: Tue, 6 Oct 2020 22:58:09 +0300 Subject: [PATCH 1535/1782] Write new test for small ticks --- .../Rulesets/Scoring/ScoreProcessorTest.cs | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs index 1181d82d09..f81ab6c866 100644 --- a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs +++ b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs @@ -97,6 +97,40 @@ namespace osu.Game.Tests.Rulesets.Scoring Assert.IsTrue(Precision.AlmostEquals(expectedScore, scoreProcessor.TotalScore.Value)); } + [TestCase(ScoringMode.Standardised, HitResult.SmallTickHit, 6_850_000 / 7.0)] + [TestCase(ScoringMode.Standardised, HitResult.SmallTickMiss, 6_400_000 / 7.0)] + [TestCase(ScoringMode.Classic, HitResult.SmallTickHit, 1950 / 7.0)] + [TestCase(ScoringMode.Classic, HitResult.SmallTickMiss, 1500 / 7.0)] + public void TestSmallTicksAccuracy(ScoringMode scoringMode, HitResult hitResult, double expectedScore) + { + IEnumerable hitObjects = Enumerable + .Repeat(new TestHitObject(HitResult.SmallTickHit), 4) + .Append(new TestHitObject(HitResult.Ok)); + IBeatmap fiveObjectBeatmap = new TestBeatmap(new RulesetInfo()) + { + HitObjects = hitObjects.ToList() + }; + scoreProcessor.Mode.Value = scoringMode; + scoreProcessor.ApplyBeatmap(fiveObjectBeatmap); + + for (int i = 0; i < 4; i++) + { + var judgementResult = new JudgementResult(fiveObjectBeatmap.HitObjects[i], new Judgement()) + { + Type = i == 2 ? HitResult.SmallTickMiss : hitResult + }; + scoreProcessor.ApplyResult(judgementResult); + } + + var lastJudgementResult = new JudgementResult(fiveObjectBeatmap.HitObjects.Last(), new Judgement()) + { + Type = HitResult.Ok + }; + scoreProcessor.ApplyResult(lastJudgementResult); + + Assert.IsTrue(Precision.AlmostEquals(expectedScore, scoreProcessor.TotalScore.Value)); + } + [TestCase(HitResult.IgnoreHit, HitResult.IgnoreMiss)] [TestCase(HitResult.Meh, HitResult.Miss)] [TestCase(HitResult.Ok, HitResult.Miss)] From 8847b88e653490a62d5319bc066702d9151d100c Mon Sep 17 00:00:00 2001 From: Shane Woolcock Date: Wed, 7 Oct 2020 11:44:41 +1030 Subject: [PATCH 1536/1782] Fix unit tests trying to resolve OsuGame --- osu.Game/Screens/Play/Player.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index a217d2a396..932c5ba1df 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -158,7 +158,7 @@ namespace osu.Game.Screens.Play } [BackgroundDependencyLoader] - private void load(AudioManager audio, OsuConfigManager config, OsuGame game) + private void load(AudioManager audio, OsuConfigManager config, OsuGameBase game) { Mods.Value = base.Mods.Value.Select(m => m.CreateCopy()).ToArray(); @@ -174,7 +174,8 @@ namespace osu.Game.Screens.Play mouseWheelDisabled = config.GetBindable(OsuSetting.MouseDisableWheel); - isGameplay.BindTo(game.IsGameplay); + if (game is OsuGame osuGame) + isGameplay.BindTo(osuGame.IsGameplay); DrawableRuleset = ruleset.CreateDrawableRulesetWith(playableBeatmap, Mods.Value); From c1a8fe01ef6a5253e060fa1db27cbb730882c4a8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 7 Oct 2020 11:09:45 +0900 Subject: [PATCH 1537/1782] Fix postprocess order in batch events --- osu.Game/Screens/Edit/EditorBeatmap.cs | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/osu.Game/Screens/Edit/EditorBeatmap.cs b/osu.Game/Screens/Edit/EditorBeatmap.cs index 1357f12055..be032d3104 100644 --- a/osu.Game/Screens/Edit/EditorBeatmap.cs +++ b/osu.Game/Screens/Edit/EditorBeatmap.cs @@ -234,25 +234,19 @@ namespace osu.Game.Screens.Edit applyFunction(this); beatmapProcessor?.PreProcess(); + + foreach (var h in batchPendingDeletes) processHitObject(h); + foreach (var h in batchPendingInserts) processHitObject(h); + beatmapProcessor?.PostProcess(); - isBatchApplying = false; - - foreach (var h in batchPendingDeletes) - { - processHitObject(h); - HitObjectRemoved?.Invoke(h); - } + foreach (var h in batchPendingDeletes) HitObjectRemoved?.Invoke(h); + foreach (var h in batchPendingInserts) HitObjectAdded?.Invoke(h); batchPendingDeletes.Clear(); - - foreach (var h in batchPendingInserts) - { - processHitObject(h); - HitObjectAdded?.Invoke(h); - } - batchPendingInserts.Clear(); + + isBatchApplying = false; } /// From a8151d5c635c0803dc9fc5812904156ce49b405e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 7 Oct 2020 13:45:41 +0900 Subject: [PATCH 1538/1782] Fix HitWindows getting serialized alongside HitObjects These were being serialized as the base type. On deserialization, due to the HitWindow of objects being non-null, they would not get correctly initialised by the CreateHitWindows() virtual method. - Closes #10403 --- osu.Game/Rulesets/Objects/HitObject.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Rulesets/Objects/HitObject.cs b/osu.Game/Rulesets/Objects/HitObject.cs index 0dfde834ee..826d411822 100644 --- a/osu.Game/Rulesets/Objects/HitObject.cs +++ b/osu.Game/Rulesets/Objects/HitObject.cs @@ -77,6 +77,7 @@ namespace osu.Game.Rulesets.Objects /// /// The hit windows for this . /// + [JsonIgnore] public HitWindows HitWindows { get; set; } private readonly List nestedHitObjects = new List(); From a6d1484ad5af2f5eb921133a283477b235da0d56 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 7 Oct 2020 14:26:01 +0900 Subject: [PATCH 1539/1782] Add arbirary precision specification for now --- osu.Game/Screens/Edit/Setup/DifficultySection.cs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Edit/Setup/DifficultySection.cs b/osu.Game/Screens/Edit/Setup/DifficultySection.cs index f23bc0f3b2..2d8031c3c8 100644 --- a/osu.Game/Screens/Edit/Setup/DifficultySection.cs +++ b/osu.Game/Screens/Edit/Setup/DifficultySection.cs @@ -38,7 +38,8 @@ namespace osu.Game.Screens.Edit.Setup { Default = BeatmapDifficulty.DEFAULT_DIFFICULTY, MinValue = 2, - MaxValue = 7 + MaxValue = 7, + Precision = 0.1f, } }, healthDrainSlider = new LabelledSliderBar @@ -49,7 +50,8 @@ namespace osu.Game.Screens.Edit.Setup { Default = BeatmapDifficulty.DEFAULT_DIFFICULTY, MinValue = 0, - MaxValue = 10 + MaxValue = 10, + Precision = 0.1f, } }, approachRateSlider = new LabelledSliderBar @@ -60,7 +62,8 @@ namespace osu.Game.Screens.Edit.Setup { Default = BeatmapDifficulty.DEFAULT_DIFFICULTY, MinValue = 0, - MaxValue = 10 + MaxValue = 10, + Precision = 0.1f, } }, overallDifficultySlider = new LabelledSliderBar @@ -71,7 +74,8 @@ namespace osu.Game.Screens.Edit.Setup { Default = BeatmapDifficulty.DEFAULT_DIFFICULTY, MinValue = 0, - MaxValue = 10 + MaxValue = 10, + Precision = 0.1f, } }, }; From c8c5998af475a9860041eea732e61a10f949f069 Mon Sep 17 00:00:00 2001 From: Shane Woolcock Date: Wed, 7 Oct 2020 16:02:35 +1030 Subject: [PATCH 1540/1782] Bail if FrameworkSetting.ConfineMouseMode is unavailable --- osu.Game/Input/ConfineMouseTracker.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Input/ConfineMouseTracker.cs b/osu.Game/Input/ConfineMouseTracker.cs index c1089874ae..3d16e44607 100644 --- a/osu.Game/Input/ConfineMouseTracker.cs +++ b/osu.Game/Input/ConfineMouseTracker.cs @@ -34,6 +34,10 @@ namespace osu.Game.Input private void updateConfineMode() { + // confine mode is unavailable on some platforms + if (frameworkConfineMode.Disabled) + return; + switch (osuConfineMode.Value) { case OsuConfineMouseMode.Never: From 7fff762dfc91222fe1823f3b73f90aa588b5129c Mon Sep 17 00:00:00 2001 From: Shane Woolcock Date: Wed, 7 Oct 2020 16:14:49 +1030 Subject: [PATCH 1541/1782] Rename IsGameplay --- osu.Game/Input/ConfineMouseTracker.cs | 10 +++++----- osu.Game/OsuGame.cs | 4 ++-- osu.Game/Screens/Play/Player.cs | 10 +++++----- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/osu.Game/Input/ConfineMouseTracker.cs b/osu.Game/Input/ConfineMouseTracker.cs index 3d16e44607..3dadae6317 100644 --- a/osu.Game/Input/ConfineMouseTracker.cs +++ b/osu.Game/Input/ConfineMouseTracker.cs @@ -12,24 +12,24 @@ namespace osu.Game.Input { /// /// Connects with . - /// If is true, we should also confine the mouse cursor if it has been + /// If is true, we should also confine the mouse cursor if it has been /// requested with . /// public class ConfineMouseTracker : Component { private Bindable frameworkConfineMode; private Bindable osuConfineMode; - private IBindable isGameplay; + private IBindable localUserPlaying; [BackgroundDependencyLoader] private void load(OsuGame game, FrameworkConfigManager frameworkConfigManager, OsuConfigManager osuConfigManager) { frameworkConfineMode = frameworkConfigManager.GetBindable(FrameworkSetting.ConfineMouseMode); osuConfineMode = osuConfigManager.GetBindable(OsuSetting.ConfineMouseMode); - isGameplay = game.IsGameplay.GetBoundCopy(); + localUserPlaying = game.LocalUserPlaying.GetBoundCopy(); osuConfineMode.ValueChanged += _ => updateConfineMode(); - isGameplay.BindValueChanged(_ => updateConfineMode(), true); + localUserPlaying.BindValueChanged(_ => updateConfineMode(), true); } private void updateConfineMode() @@ -49,7 +49,7 @@ namespace osu.Game.Input break; case OsuConfineMouseMode.DuringGameplay: - frameworkConfineMode.Value = isGameplay.Value ? ConfineMouseMode.Always : ConfineMouseMode.Never; + frameworkConfineMode.Value = localUserPlaying.Value ? ConfineMouseMode.Always : ConfineMouseMode.Never; break; case OsuConfineMouseMode.Always: diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 466ff13615..c09ad0eeb9 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -98,9 +98,9 @@ namespace osu.Game public readonly IBindable OverlayActivationMode = new Bindable(); /// - /// Whether gameplay is currently active. + /// Whether the local user is currently interacting with the game in a way that should not be interrupted. /// - public readonly Bindable IsGameplay = new BindableBool(); + public readonly Bindable LocalUserPlaying = new BindableBool(); protected OsuScreenStack ScreenStack; diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 932c5ba1df..e6c15521af 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -68,7 +68,7 @@ namespace osu.Game.Screens.Play private readonly Bindable storyboardReplacesBackground = new Bindable(); - private readonly Bindable isGameplay = new Bindable(); + private readonly Bindable localUserPlaying = new Bindable(); public int RestartCount; @@ -175,7 +175,7 @@ namespace osu.Game.Screens.Play mouseWheelDisabled = config.GetBindable(OsuSetting.MouseDisableWheel); if (game is OsuGame osuGame) - isGameplay.BindTo(osuGame.IsGameplay); + localUserPlaying.BindTo(osuGame.LocalUserPlaying); DrawableRuleset = ruleset.CreateDrawableRulesetWith(playableBeatmap, Mods.Value); @@ -362,7 +362,7 @@ namespace osu.Game.Screens.Play { bool inGameplay = !DrawableRuleset.HasReplayLoaded.Value && !DrawableRuleset.IsPaused.Value && !breakTracker.IsBreakTime.Value; OverlayActivationMode.Value = inGameplay ? OverlayActivation.Disabled : OverlayActivation.UserTriggered; - isGameplay.Value = inGameplay; + localUserPlaying.Value = inGameplay; } private void updatePauseOnFocusLostState() => @@ -695,8 +695,8 @@ namespace osu.Game.Screens.Play musicController.ResetTrackAdjustments(); - // Ensure we reset the IsGameplay state - isGameplay.Value = false; + // Ensure we reset the LocalUserPlaying state + localUserPlaying.Value = false; fadeOut(); return base.OnExiting(next); From 485bd962c7d52d738547af4bcd43af535e2e935a Mon Sep 17 00:00:00 2001 From: Shane Woolcock Date: Wed, 7 Oct 2020 16:15:17 +1030 Subject: [PATCH 1542/1782] Also reset LocalUserPlaying in OnSuspending --- osu.Game/Screens/Play/Player.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index e6c15521af..43dce0786d 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -666,6 +666,9 @@ namespace osu.Game.Screens.Play { screenSuspension?.Expire(); + // Ensure we reset the LocalUserPlaying state + localUserPlaying.Value = false; + fadeOut(); base.OnSuspending(next); } From d1ec3806927a3bb042e47b76be921582ed87575e Mon Sep 17 00:00:00 2001 From: Shane Woolcock Date: Wed, 7 Oct 2020 16:15:32 +1030 Subject: [PATCH 1543/1782] Don't cache ConfineMouseTracker --- osu.Game/OsuGame.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index c09ad0eeb9..d22ac1aec8 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -549,7 +549,6 @@ namespace osu.Game BackButton.Receptor receptor; dependencies.CacheAs(idleTracker = new GameIdleTracker(6000)); - dependencies.Cache(confineMouseTracker = new ConfineMouseTracker()); AddRange(new Drawable[] { @@ -586,7 +585,7 @@ namespace osu.Game leftFloatingOverlayContent = new Container { RelativeSizeAxes = Axes.Both }, topMostOverlayContent = new Container { RelativeSizeAxes = Axes.Both }, idleTracker, - confineMouseTracker + confineMouseTracker = new ConfineMouseTracker() }); ScreenStack.ScreenPushed += screenPushed; From 8b8eb00bd75c4790bec72bdd8a79f5e6ddddf457 Mon Sep 17 00:00:00 2001 From: Shane Woolcock Date: Wed, 7 Oct 2020 16:16:58 +1030 Subject: [PATCH 1544/1782] Permit nulls rather than casting to OsuGame --- osu.Game/Screens/Play/Player.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 43dce0786d..39a6ac4ded 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -157,8 +157,8 @@ namespace osu.Game.Screens.Play DrawableRuleset.SetRecordTarget(recordingReplay = new Replay()); } - [BackgroundDependencyLoader] - private void load(AudioManager audio, OsuConfigManager config, OsuGameBase game) + [BackgroundDependencyLoader(true)] + private void load(AudioManager audio, OsuConfigManager config, OsuGame game) { Mods.Value = base.Mods.Value.Select(m => m.CreateCopy()).ToArray(); @@ -174,8 +174,8 @@ namespace osu.Game.Screens.Play mouseWheelDisabled = config.GetBindable(OsuSetting.MouseDisableWheel); - if (game is OsuGame osuGame) - localUserPlaying.BindTo(osuGame.LocalUserPlaying); + if (game != null) + localUserPlaying.BindTo(game.LocalUserPlaying); DrawableRuleset = ruleset.CreateDrawableRulesetWith(playableBeatmap, Mods.Value); From d6d0bd90a39e48747ce34ba08629373470127829 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 7 Oct 2020 15:34:03 +0900 Subject: [PATCH 1545/1782] Extract tuple into class --- osu.Game/Scoring/HitResultDisplayStatistic.cs | 41 +++++++++++++++++++ osu.Game/Scoring/ScoreInfo.cs | 17 ++++---- 2 files changed, 49 insertions(+), 9 deletions(-) create mode 100644 osu.Game/Scoring/HitResultDisplayStatistic.cs diff --git a/osu.Game/Scoring/HitResultDisplayStatistic.cs b/osu.Game/Scoring/HitResultDisplayStatistic.cs new file mode 100644 index 0000000000..d43d8bf0ba --- /dev/null +++ b/osu.Game/Scoring/HitResultDisplayStatistic.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 osu.Game.Rulesets.Scoring; + +namespace osu.Game.Scoring +{ + /// + /// Compiled result data for a specific in a score. + /// + public class HitResultDisplayStatistic + { + /// + /// The associated result type. + /// + public HitResult Result { get; } + + /// + /// The count of successful hits of this type. + /// + public int Count { get; } + + /// + /// The maximum achievable hits of this type. May be null if undetermined. + /// + public int? MaxCount { get; } + + /// + /// A custom display name for the result type. May be provided by rulesets to give better clarity. + /// + public string DisplayName { get; } + + public HitResultDisplayStatistic(HitResult result, int count, int? maxCount, string displayName) + { + Result = result; + Count = count; + MaxCount = maxCount; + DisplayName = displayName; + } + } +} diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index 0206989231..7cd9578ff1 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -213,22 +213,22 @@ namespace osu.Game.Scoring set => isLegacyScore = value; } - public IEnumerable<(HitResult result, int count, int? maxCount)> GetStatisticsForDisplay() + public IEnumerable GetStatisticsForDisplay() { - foreach (var key in OrderAttributeUtils.GetValuesInOrder()) + foreach (var r in Ruleset.CreateInstance().GetHitResults()) { - if (key.IsBonus()) + if (r.result.IsBonus()) continue; - int value = Statistics.GetOrDefault(key); + int value = Statistics.GetOrDefault(r.result); - switch (key) + switch (r.result) { case HitResult.SmallTickHit: { int total = value + Statistics.GetOrDefault(HitResult.SmallTickMiss); if (total > 0) - yield return (key, value, total); + yield return new HitResultDisplayStatistic(r.result, value, total, r.displayName); break; } @@ -237,7 +237,7 @@ namespace osu.Game.Scoring { int total = value + Statistics.GetOrDefault(HitResult.LargeTickMiss); if (total > 0) - yield return (key, value, total); + yield return new HitResultDisplayStatistic(r.result, value, total, r.displayName); break; } @@ -247,8 +247,7 @@ namespace osu.Game.Scoring break; default: - if (value > 0 || key == HitResult.Miss) - yield return (key, value, null); + yield return new HitResultDisplayStatistic(r.result, value, null, r.displayName); break; } From 3363c3399eaa8a5e0a4efbb2e1742d694639c232 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 7 Oct 2020 15:34:23 +0900 Subject: [PATCH 1546/1782] Allow rulesets to specify valid HitResult types (and display names for them) --- osu.Game/Rulesets/Ruleset.cs | 49 ++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index 915544d010..48d94d2b3f 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -23,8 +23,10 @@ using osu.Game.Scoring; using osu.Game.Skinning; using osu.Game.Users; using JetBrains.Annotations; +using osu.Framework.Extensions; using osu.Framework.Testing; using osu.Game.Screens.Ranking.Statistics; +using osu.Game.Utils; namespace osu.Game.Rulesets { @@ -220,5 +222,52 @@ namespace osu.Game.Rulesets /// The s to display. Each may contain 0 or more . [NotNull] public virtual StatisticRow[] CreateStatisticsForScore(ScoreInfo score, IBeatmap playableBeatmap) => Array.Empty(); + + /// + /// Get all valid s for this ruleset. + /// Generally used for results display purposes, where it can't be determined if zero-count means the user has not achieved any or the type is not used by this ruleset. + /// + /// + /// All valid s along with a display-friendly name. + /// + public IEnumerable<(HitResult result, string displayName)> GetHitResults() + { + var validResults = GetValidHitResults(); + + // enumerate over ordered list to guarantee return order is stable. + foreach (var result in OrderAttributeUtils.GetValuesInOrder()) + { + switch (result) + { + // hard blocked types, should never be displayed even if the ruleset tells us to. + case HitResult.None: + case HitResult.IgnoreHit: + case HitResult.IgnoreMiss: + // display is handled as a completion count with corresponding "hit" type. + case HitResult.LargeTickMiss: + case HitResult.SmallTickMiss: + continue; + } + + if (result == HitResult.Miss || validResults.Contains(result)) + yield return (result, GetDisplayNameForHitResult(result)); + } + } + + /// + /// Get all valid s for this ruleset. + /// Generally used for results display purposes, where it can't be determined if zero-count means the user has not achieved any or the type is not used by this ruleset. + /// + /// + /// is implicitly included. Special types like are ignored even when specified. + /// + protected virtual IEnumerable GetValidHitResults() => OrderAttributeUtils.GetValuesInOrder(); + + /// + /// Get a display friendly name for the specified result type. + /// + /// The result type to get the name for. + /// The display name. + public virtual string GetDisplayNameForHitResult(HitResult result) => result.GetDescription(); } } From 6020ec9ca3e786d95640da84dfc573ef181c452a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 7 Oct 2020 15:34:36 +0900 Subject: [PATCH 1547/1782] Add valid result types for all rulesets --- osu.Game.Rulesets.Catch/CatchRuleset.cs | 29 ++++++++++++++++++++ osu.Game.Rulesets.Mania/ManiaRuleset.cs | 25 ++++++++++++++++++ osu.Game.Rulesets.Osu/OsuRuleset.cs | 35 +++++++++++++++++++++++++ osu.Game.Rulesets.Taiko/TaikoRuleset.cs | 22 ++++++++++++++++ 4 files changed, 111 insertions(+) diff --git a/osu.Game.Rulesets.Catch/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs index ca75a816f1..eb845cdea6 100644 --- a/osu.Game.Rulesets.Catch/CatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs @@ -141,6 +141,35 @@ namespace osu.Game.Rulesets.Catch public override Drawable CreateIcon() => new SpriteIcon { Icon = OsuIcon.RulesetCatch }; + protected override IEnumerable GetValidHitResults() + { + return new[] + { + HitResult.Great, + + HitResult.LargeTickHit, + HitResult.SmallTickHit, + HitResult.LargeBonus, + }; + } + + public override string GetDisplayNameForHitResult(HitResult result) + { + switch (result) + { + case HitResult.LargeTickHit: + return "large droplet"; + + case HitResult.SmallTickHit: + return "small droplet"; + + case HitResult.LargeBonus: + return "bananas"; + } + + return base.GetDisplayNameForHitResult(result); + } + public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new CatchDifficultyCalculator(this, beatmap); public override ISkin CreateLegacySkinProvider(ISkinSource source, IBeatmap beatmap) => new CatchLegacySkinTransformer(source); diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index 71ac85dd1b..d2feeb03af 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -319,6 +319,31 @@ namespace osu.Game.Rulesets.Mania return (PlayfieldType)Enum.GetValues(typeof(PlayfieldType)).Cast().OrderByDescending(i => i).First(v => variant >= v); } + protected override IEnumerable GetValidHitResults() + { + return new[] + { + HitResult.Perfect, + HitResult.Great, + HitResult.Good, + HitResult.Ok, + HitResult.Meh, + + HitResult.LargeTickHit, + }; + } + + public override string GetDisplayNameForHitResult(HitResult result) + { + switch (result) + { + case HitResult.LargeTickHit: + return "hold tick"; + } + + return base.GetDisplayNameForHitResult(result); + } + public override StatisticRow[] CreateStatisticsForScore(ScoreInfo score, IBeatmap playableBeatmap) => new[] { new StatisticRow diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index 7f4a0dcbbb..d946e7a113 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -191,6 +191,41 @@ namespace osu.Game.Rulesets.Osu public override IRulesetConfigManager CreateConfig(SettingsStore settings) => new OsuRulesetConfigManager(settings, RulesetInfo); + protected override IEnumerable GetValidHitResults() + { + return new[] + { + HitResult.Great, + HitResult.Ok, + HitResult.Meh, + + HitResult.LargeTickHit, + HitResult.SmallTickHit, + HitResult.SmallBonus, + HitResult.LargeBonus, + }; + } + + public override string GetDisplayNameForHitResult(HitResult result) + { + switch (result) + { + case HitResult.LargeTickHit: + return "slider tick"; + + case HitResult.SmallTickHit: + return "slider end"; + + case HitResult.SmallBonus: + return "spinner spin"; + + case HitResult.LargeBonus: + return "spinner bonus"; + } + + return base.GetDisplayNameForHitResult(result); + } + public override StatisticRow[] CreateStatisticsForScore(ScoreInfo score, IBeatmap playableBeatmap) { var timedHitEvents = score.HitEvents.Where(e => e.HitObject is HitCircle && !(e.HitObject is SliderTailCircle)).ToList(); diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index 9d485e3f20..f4c94c9248 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -159,6 +159,28 @@ namespace osu.Game.Rulesets.Taiko public override IConvertibleReplayFrame CreateConvertibleReplayFrame() => new TaikoReplayFrame(); + protected override IEnumerable GetValidHitResults() + { + return new[] + { + HitResult.Great, + HitResult.Ok, + + HitResult.SmallTickHit, + }; + } + + public override string GetDisplayNameForHitResult(HitResult result) + { + switch (result) + { + case HitResult.SmallTickHit: + return "drum tick"; + } + + return base.GetDisplayNameForHitResult(result); + } + public override StatisticRow[] CreateStatisticsForScore(ScoreInfo score, IBeatmap playableBeatmap) { var timedHitEvents = score.HitEvents.Where(e => e.HitObject is Hit).ToList(); From e281d724b85feabb494169b6bd007e8e91621e1d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 7 Oct 2020 15:35:04 +0900 Subject: [PATCH 1548/1782] Consume display name logic --- .../Overlays/BeatmapSet/Scores/ScoreTable.cs | 22 +++++++++++-------- .../Scores/TopScoreStatisticsSection.cs | 8 +++---- .../ContractedPanelMiddleContent.cs | 8 +++---- .../Expanded/ExpandedPanelMiddleContent.cs | 4 ++-- .../Expanded/Statistics/HitResultStatistic.cs | 8 +++---- 5 files changed, 25 insertions(+), 25 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs index 968355c377..231d888a4e 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs @@ -60,7 +60,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores /// /// The statistics that appear in the table, in order of appearance. /// - private readonly List statisticResultTypes = new List(); + private readonly List<(HitResult result, string displayName)> statisticResultTypes = new List<(HitResult, string)>(); private bool showPerformancePoints; @@ -101,15 +101,19 @@ namespace osu.Game.Overlays.BeatmapSet.Scores }; // All statistics across all scores, unordered. - var allScoreStatistics = scores.SelectMany(s => s.GetStatisticsForDisplay().Select(stat => stat.result)).ToHashSet(); + var allScoreStatistics = scores.SelectMany(s => s.GetStatisticsForDisplay().Select(stat => stat.Result)).ToHashSet(); + + var ruleset = scores.First().Ruleset.CreateInstance(); foreach (var result in OrderAttributeUtils.GetValuesInOrder()) { if (!allScoreStatistics.Contains(result)) continue; - columns.Add(new TableColumn(result.GetDescription(), Anchor.CentreLeft, new Dimension(GridSizeMode.Distributed, minSize: 35, maxSize: 60))); - statisticResultTypes.Add(result); + string displayName = ruleset.GetDisplayNameForHitResult(result); + + columns.Add(new TableColumn(displayName, Anchor.CentreLeft, new Dimension(GridSizeMode.Distributed, minSize: 35, maxSize: 60))); + statisticResultTypes.Add((result, displayName)); } if (showPerformancePoints) @@ -163,18 +167,18 @@ namespace osu.Game.Overlays.BeatmapSet.Scores } }; - var availableStatistics = score.GetStatisticsForDisplay().ToDictionary(tuple => tuple.result); + var availableStatistics = score.GetStatisticsForDisplay().ToDictionary(tuple => tuple.Result); foreach (var result in statisticResultTypes) { - if (!availableStatistics.TryGetValue(result, out var stat)) - stat = (result, 0, null); + if (!availableStatistics.TryGetValue(result.result, out var stat)) + stat = new HitResultDisplayStatistic(result.result, 0, null, result.displayName); content.Add(new OsuSpriteText { - Text = stat.maxCount == null ? $"{stat.count}" : $"{stat.count}/{stat.maxCount}", + Text = stat.MaxCount == null ? $"{stat.Count}" : $"{stat.Count}/{stat.MaxCount}", Font = OsuFont.GetFont(size: text_size), - Colour = stat.count == 0 ? Color4.Gray : Color4.White + Colour = stat.Count == 0 ? Color4.Gray : Color4.White }); } diff --git a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs index 05789e1fc0..93744dd6a3 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs @@ -5,7 +5,6 @@ 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; @@ -15,7 +14,6 @@ using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; using osu.Game.Scoring; using osuTK; @@ -117,7 +115,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores ppColumn.Alpha = value.Beatmap?.Status == BeatmapSetOnlineStatus.Ranked ? 1 : 0; ppColumn.Text = $@"{value.PP:N0}"; - statisticsColumns.ChildrenEnumerable = value.GetStatisticsForDisplay().Select(s => createStatisticsColumn(s.result, s.count, s.maxCount)); + statisticsColumns.ChildrenEnumerable = value.GetStatisticsForDisplay().Select(createStatisticsColumn); modsColumn.Mods = value.Mods; if (scoreManager != null) @@ -125,9 +123,9 @@ namespace osu.Game.Overlays.BeatmapSet.Scores } } - private TextColumn createStatisticsColumn(HitResult hitResult, int count, int? maxCount) => new TextColumn(hitResult.GetDescription(), smallFont, bottom_columns_min_width) + private TextColumn createStatisticsColumn(HitResultDisplayStatistic stat) => new TextColumn(stat.DisplayName, smallFont, bottom_columns_min_width) { - Text = maxCount == null ? $"{count}" : $"{count}/{maxCount}" + Text = stat.MaxCount == null ? $"{stat.Count}" : $"{stat.Count}/{stat.MaxCount}" }; private class InfoColumn : CompositeDrawable diff --git a/osu.Game/Screens/Ranking/Contracted/ContractedPanelMiddleContent.cs b/osu.Game/Screens/Ranking/Contracted/ContractedPanelMiddleContent.cs index 95ece1a9fb..9481f07342 100644 --- a/osu.Game/Screens/Ranking/Contracted/ContractedPanelMiddleContent.cs +++ b/osu.Game/Screens/Ranking/Contracted/ContractedPanelMiddleContent.cs @@ -3,7 +3,6 @@ using System.Linq; using osu.Framework.Allocation; -using osu.Framework.Extensions; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; @@ -13,7 +12,6 @@ using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Online.Leaderboards; -using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; using osu.Game.Screens.Play.HUD; using osu.Game.Users; @@ -117,7 +115,7 @@ namespace osu.Game.Screens.Ranking.Contracted AutoSizeAxes = Axes.Y, Direction = FillDirection.Vertical, Spacing = new Vector2(0, 5), - ChildrenEnumerable = score.GetStatisticsForDisplay().Select(s => createStatistic(s.result, s.count, s.maxCount)) + ChildrenEnumerable = score.GetStatisticsForDisplay().Select(createStatistic) }, new FillFlowContainer { @@ -199,8 +197,8 @@ namespace osu.Game.Screens.Ranking.Contracted }; } - private Drawable createStatistic(HitResult result, int count, int? maxCount) - => createStatistic(result.GetDescription(), maxCount == null ? $"{count}" : $"{count}/{maxCount}"); + private Drawable createStatistic(HitResultDisplayStatistic result) + => createStatistic(result.DisplayName, result.MaxCount == null ? $"{result.Count}" : $"{result.Count}/{result.MaxCount}"); private Drawable createStatistic(string key, string value) => new Container { diff --git a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs index ebab8c88f6..30b9f47f71 100644 --- a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs +++ b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs @@ -66,8 +66,8 @@ namespace osu.Game.Screens.Ranking.Expanded var bottomStatistics = new List(); - foreach (var (key, value, maxCount) in score.GetStatisticsForDisplay()) - bottomStatistics.Add(new HitResultStatistic(key, value, maxCount)); + foreach (var result in score.GetStatisticsForDisplay()) + bottomStatistics.Add(new HitResultStatistic(result)); statisticDisplays.AddRange(topStatistics); statisticDisplays.AddRange(bottomStatistics); diff --git a/osu.Game/Screens/Ranking/Expanded/Statistics/HitResultStatistic.cs b/osu.Game/Screens/Ranking/Expanded/Statistics/HitResultStatistic.cs index a86033713f..31ef51a031 100644 --- a/osu.Game/Screens/Ranking/Expanded/Statistics/HitResultStatistic.cs +++ b/osu.Game/Screens/Ranking/Expanded/Statistics/HitResultStatistic.cs @@ -2,9 +2,9 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; -using osu.Framework.Extensions; using osu.Game.Graphics; using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; namespace osu.Game.Screens.Ranking.Expanded.Statistics { @@ -12,10 +12,10 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics { private readonly HitResult result; - public HitResultStatistic(HitResult result, int count, int? maxCount = null) - : base(result.GetDescription(), count, maxCount) + public HitResultStatistic(HitResultDisplayStatistic result) + : base(result.DisplayName, result.Count, result.MaxCount) { - this.result = result; + this.result = result.Result; } [BackgroundDependencyLoader] From c0bc6a75b35cb73cf960c1c444125cbea6c1155f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 7 Oct 2020 16:17:08 +0900 Subject: [PATCH 1549/1782] Show auxiliary judgements on next line --- .../Ranking/Expanded/ExpandedPanelMiddleContent.cs | 14 ++++++++++++-- .../Expanded/Statistics/HitResultStatistic.cs | 6 +++--- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs index 30b9f47f71..b2d0c7a874 100644 --- a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs +++ b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs @@ -64,7 +64,7 @@ namespace osu.Game.Screens.Ranking.Expanded new CounterStatistic("pp", (int)(score.PP ?? 0)), }; - var bottomStatistics = new List(); + var bottomStatistics = new List(); foreach (var result in score.GetStatisticsForDisplay()) bottomStatistics.Add(new HitResultStatistic(result)); @@ -198,7 +198,17 @@ namespace osu.Game.Screens.Ranking.Expanded { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Content = new[] { bottomStatistics.Cast().ToArray() }, + Content = new[] { bottomStatistics.Where(s => s.Result <= HitResult.Perfect).ToArray() }, + RowDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize), + } + }, + new GridContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Content = new[] { bottomStatistics.Where(s => s.Result > HitResult.Perfect).ToArray() }, RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize), diff --git a/osu.Game/Screens/Ranking/Expanded/Statistics/HitResultStatistic.cs b/osu.Game/Screens/Ranking/Expanded/Statistics/HitResultStatistic.cs index 31ef51a031..ada8dfabf0 100644 --- a/osu.Game/Screens/Ranking/Expanded/Statistics/HitResultStatistic.cs +++ b/osu.Game/Screens/Ranking/Expanded/Statistics/HitResultStatistic.cs @@ -10,18 +10,18 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics { public class HitResultStatistic : CounterStatistic { - private readonly HitResult result; + public readonly HitResult Result; public HitResultStatistic(HitResultDisplayStatistic result) : base(result.DisplayName, result.Count, result.MaxCount) { - this.result = result.Result; + Result = result.Result; } [BackgroundDependencyLoader] private void load(OsuColour colours) { - HeaderText.Colour = colours.ForHitResult(result); + HeaderText.Colour = colours.ForHitResult(Result); } } } From 6ac70945f2be7f3be6c6daabaf12a5bda8619cf2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 7 Oct 2020 16:17:28 +0900 Subject: [PATCH 1550/1782] Show bonus judgements on expanded panel --- osu.Game/Scoring/ScoreInfo.cs | 3 --- .../Screens/Ranking/Contracted/ContractedPanelMiddleContent.cs | 3 ++- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index 7cd9578ff1..596e98a6bd 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -217,9 +217,6 @@ namespace osu.Game.Scoring { foreach (var r in Ruleset.CreateInstance().GetHitResults()) { - if (r.result.IsBonus()) - continue; - int value = Statistics.GetOrDefault(r.result); switch (r.result) diff --git a/osu.Game/Screens/Ranking/Contracted/ContractedPanelMiddleContent.cs b/osu.Game/Screens/Ranking/Contracted/ContractedPanelMiddleContent.cs index 9481f07342..24f1116d0e 100644 --- a/osu.Game/Screens/Ranking/Contracted/ContractedPanelMiddleContent.cs +++ b/osu.Game/Screens/Ranking/Contracted/ContractedPanelMiddleContent.cs @@ -12,6 +12,7 @@ using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Online.Leaderboards; +using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; using osu.Game.Screens.Play.HUD; using osu.Game.Users; @@ -115,7 +116,7 @@ namespace osu.Game.Screens.Ranking.Contracted AutoSizeAxes = Axes.Y, Direction = FillDirection.Vertical, Spacing = new Vector2(0, 5), - ChildrenEnumerable = score.GetStatisticsForDisplay().Select(createStatistic) + ChildrenEnumerable = score.GetStatisticsForDisplay().Where(s => !s.Result.IsBonus()).Select(createStatistic) }, new FillFlowContainer { From 2e0a9f53c11a4d1e4fe2b63e22a3af3de579904d Mon Sep 17 00:00:00 2001 From: Shane Woolcock Date: Wed, 7 Oct 2020 17:52:39 +1030 Subject: [PATCH 1551/1782] Add test coverage --- .../Visual/Gameplay/TestSceneOverlayActivation.cs | 10 ++++++++++ osu.Game/Screens/Play/Player.cs | 10 +++++----- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneOverlayActivation.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneOverlayActivation.cs index ce04b940e7..41f7582d31 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneOverlayActivation.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneOverlayActivation.cs @@ -3,6 +3,7 @@ using System.Linq; using NUnit.Framework; +using osu.Framework.Bindables; using osu.Game.Overlays; using osu.Game.Rulesets; @@ -23,32 +24,40 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestGameplayOverlayActivation() { + AddAssert("local user playing", () => Player.LocalUserPlaying.Value); AddAssert("activation mode is disabled", () => Player.OverlayActivationMode == OverlayActivation.Disabled); } [Test] public void TestGameplayOverlayActivationPaused() { + AddAssert("local user playing", () => Player.LocalUserPlaying.Value); AddAssert("activation mode is disabled", () => Player.OverlayActivationMode == OverlayActivation.Disabled); AddStep("pause gameplay", () => Player.Pause()); + AddAssert("local user not playing", () => !Player.LocalUserPlaying.Value); AddUntilStep("activation mode is user triggered", () => Player.OverlayActivationMode == OverlayActivation.UserTriggered); } [Test] public void TestGameplayOverlayActivationReplayLoaded() { + AddAssert("local user playing", () => Player.LocalUserPlaying.Value); AddAssert("activation mode is disabled", () => Player.OverlayActivationMode == OverlayActivation.Disabled); AddStep("load a replay", () => Player.DrawableRuleset.HasReplayLoaded.Value = true); + AddAssert("local user not playing", () => !Player.LocalUserPlaying.Value); AddAssert("activation mode is user triggered", () => Player.OverlayActivationMode == OverlayActivation.UserTriggered); } [Test] public void TestGameplayOverlayActivationBreaks() { + AddAssert("local user playing", () => Player.LocalUserPlaying.Value); AddAssert("activation mode is disabled", () => Player.OverlayActivationMode == OverlayActivation.Disabled); AddStep("seek to break", () => Player.GameplayClockContainer.Seek(Beatmap.Value.Beatmap.Breaks.First().StartTime)); + AddAssert("local user not playing", () => !Player.LocalUserPlaying.Value); AddUntilStep("activation mode is user triggered", () => Player.OverlayActivationMode == OverlayActivation.UserTriggered); AddStep("seek to break end", () => Player.GameplayClockContainer.Seek(Beatmap.Value.Beatmap.Breaks.First().EndTime)); + AddAssert("local user playing", () => Player.LocalUserPlaying.Value); AddUntilStep("activation mode is disabled", () => Player.OverlayActivationMode == OverlayActivation.Disabled); } @@ -57,6 +66,7 @@ namespace osu.Game.Tests.Visual.Gameplay protected class OverlayTestPlayer : TestPlayer { public new OverlayActivation OverlayActivationMode => base.OverlayActivationMode.Value; + public new Bindable LocalUserPlaying => base.LocalUserPlaying; } } } diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 39a6ac4ded..8830884a40 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -68,7 +68,7 @@ namespace osu.Game.Screens.Play private readonly Bindable storyboardReplacesBackground = new Bindable(); - private readonly Bindable localUserPlaying = new Bindable(); + protected readonly Bindable LocalUserPlaying = new Bindable(); public int RestartCount; @@ -175,7 +175,7 @@ namespace osu.Game.Screens.Play mouseWheelDisabled = config.GetBindable(OsuSetting.MouseDisableWheel); if (game != null) - localUserPlaying.BindTo(game.LocalUserPlaying); + LocalUserPlaying.BindTo(game.LocalUserPlaying); DrawableRuleset = ruleset.CreateDrawableRulesetWith(playableBeatmap, Mods.Value); @@ -362,7 +362,7 @@ namespace osu.Game.Screens.Play { bool inGameplay = !DrawableRuleset.HasReplayLoaded.Value && !DrawableRuleset.IsPaused.Value && !breakTracker.IsBreakTime.Value; OverlayActivationMode.Value = inGameplay ? OverlayActivation.Disabled : OverlayActivation.UserTriggered; - localUserPlaying.Value = inGameplay; + LocalUserPlaying.Value = inGameplay; } private void updatePauseOnFocusLostState() => @@ -667,7 +667,7 @@ namespace osu.Game.Screens.Play screenSuspension?.Expire(); // Ensure we reset the LocalUserPlaying state - localUserPlaying.Value = false; + LocalUserPlaying.Value = false; fadeOut(); base.OnSuspending(next); @@ -699,7 +699,7 @@ namespace osu.Game.Screens.Play musicController.ResetTrackAdjustments(); // Ensure we reset the LocalUserPlaying state - localUserPlaying.Value = false; + LocalUserPlaying.Value = false; fadeOut(); return base.OnExiting(next); From 67398b5d9551b7e147fa7398b337ee110ee456c9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 7 Oct 2020 16:30:14 +0900 Subject: [PATCH 1552/1782] Move timestamp text out of flow and attach to bottom edge --- .../Expanded/ExpandedPanelMiddleContent.cs | 265 +++++++++--------- 1 file changed, 134 insertions(+), 131 deletions(-) diff --git a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs index b2d0c7a874..5aac449adb 100644 --- a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs +++ b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs @@ -72,157 +72,160 @@ namespace osu.Game.Screens.Ranking.Expanded statisticDisplays.AddRange(topStatistics); statisticDisplays.AddRange(bottomStatistics); - InternalChild = new FillFlowContainer + InternalChildren = new Drawable[] { - RelativeSizeAxes = Axes.Both, - Direction = FillDirection.Vertical, - Spacing = new Vector2(20), - Children = new Drawable[] + new FillFlowContainer { - new FillFlowContainer + RelativeSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Spacing = new Vector2(20), + Children = new Drawable[] { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, - Children = new Drawable[] + new FillFlowContainer { - new OsuSpriteText + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Children = new Drawable[] { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Text = new LocalisedString((metadata.TitleUnicode, metadata.Title)), - Font = OsuFont.Torus.With(size: 20, weight: FontWeight.SemiBold), - MaxWidth = ScorePanel.EXPANDED_WIDTH - padding * 2, - Truncate = true, - }, - new OsuSpriteText - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Text = new LocalisedString((metadata.ArtistUnicode, metadata.Artist)), - Font = OsuFont.Torus.With(size: 14, weight: FontWeight.SemiBold), - MaxWidth = ScorePanel.EXPANDED_WIDTH - padding * 2, - Truncate = true, - }, - new Container - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Margin = new MarginPadding { Top = 40 }, - RelativeSizeAxes = Axes.X, - Height = 230, - Child = new AccuracyCircle(score) + new OsuSpriteText { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - FillMode = FillMode.Fit, - } - }, - scoreCounter = new TotalScoreCounter - { - Margin = new MarginPadding { Top = 0, Bottom = 5 }, - Current = { Value = 0 }, - Alpha = 0, - AlwaysPresent = true - }, - starAndModDisplay = new FillFlowContainer - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - AutoSizeAxes = Axes.Both, - Spacing = new Vector2(5, 0), - Children = new Drawable[] + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Text = new LocalisedString((metadata.TitleUnicode, metadata.Title)), + Font = OsuFont.Torus.With(size: 20, weight: FontWeight.SemiBold), + MaxWidth = ScorePanel.EXPANDED_WIDTH - padding * 2, + Truncate = true, + }, + new OsuSpriteText { - new StarRatingDisplay(beatmap) - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft - }, - } - }, - new FillFlowContainer - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Direction = FillDirection.Vertical, - AutoSizeAxes = Axes.Both, - Children = new Drawable[] + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Text = new LocalisedString((metadata.ArtistUnicode, metadata.Artist)), + Font = OsuFont.Torus.With(size: 14, weight: FontWeight.SemiBold), + MaxWidth = ScorePanel.EXPANDED_WIDTH - padding * 2, + Truncate = true, + }, + new Container { - new OsuSpriteText + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Margin = new MarginPadding { Top = 40 }, + RelativeSizeAxes = Axes.X, + Height = 230, + Child = new AccuracyCircle(score) { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Text = beatmap.Version, - Font = OsuFont.Torus.With(size: 16, weight: FontWeight.SemiBold), - }, - new OsuTextFlowContainer(s => s.Font = OsuFont.Torus.With(size: 12)) + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + FillMode = FillMode.Fit, + } + }, + scoreCounter = new TotalScoreCounter + { + Margin = new MarginPadding { Top = 0, Bottom = 5 }, + Current = { Value = 0 }, + Alpha = 0, + AlwaysPresent = true + }, + starAndModDisplay = new FillFlowContainer + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + AutoSizeAxes = Axes.Both, + Spacing = new Vector2(5, 0), + Children = new Drawable[] { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - }.With(t => - { - if (!string.IsNullOrEmpty(creator)) + new StarRatingDisplay(beatmap) { - t.AddText("mapped by "); - t.AddText(creator, s => s.Font = s.Font.With(weight: FontWeight.SemiBold)); - } - }) - } - }, - } - }, - new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, - Spacing = new Vector2(0, 5), - Children = new Drawable[] + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft + }, + } + }, + new FillFlowContainer + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Direction = FillDirection.Vertical, + AutoSizeAxes = Axes.Both, + Children = new Drawable[] + { + new OsuSpriteText + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Text = beatmap.Version, + Font = OsuFont.Torus.With(size: 16, weight: FontWeight.SemiBold), + }, + new OsuTextFlowContainer(s => s.Font = OsuFont.Torus.With(size: 12)) + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + }.With(t => + { + if (!string.IsNullOrEmpty(creator)) + { + t.AddText("mapped by "); + t.AddText(creator, s => s.Font = s.Font.With(weight: FontWeight.SemiBold)); + } + }) + } + }, + } + }, + new FillFlowContainer { - new GridContainer + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 5), + Children = new Drawable[] { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Content = new[] { topStatistics.Cast().ToArray() }, - RowDimensions = new[] + new GridContainer { - new Dimension(GridSizeMode.AutoSize), - } - }, - new GridContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Content = new[] { bottomStatistics.Where(s => s.Result <= HitResult.Perfect).ToArray() }, - RowDimensions = new[] + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Content = new[] { topStatistics.Cast().ToArray() }, + RowDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize), + } + }, + new GridContainer { - new Dimension(GridSizeMode.AutoSize), - } - }, - new GridContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Content = new[] { bottomStatistics.Where(s => s.Result > HitResult.Perfect).ToArray() }, - RowDimensions = new[] + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Content = new[] { bottomStatistics.Where(s => s.Result <= HitResult.Perfect).ToArray() }, + RowDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize), + } + }, + new GridContainer { - new Dimension(GridSizeMode.AutoSize), + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Content = new[] { bottomStatistics.Where(s => s.Result > HitResult.Perfect).ToArray() }, + RowDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize), + } } } } - }, - new OsuSpriteText - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Font = OsuFont.GetFont(size: 10, weight: FontWeight.SemiBold), - Text = $"Played on {score.Date.ToLocalTime():d MMMM yyyy HH:mm}" } + }, + new OsuSpriteText + { + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + Font = OsuFont.GetFont(size: 10, weight: FontWeight.SemiBold), + Text = $"Played on {score.Date.ToLocalTime():d MMMM yyyy HH:mm}" } }; From f88ba1734bd6cc687d86de8a2a02a87d386dbbb9 Mon Sep 17 00:00:00 2001 From: Shane Woolcock Date: Wed, 7 Oct 2020 18:11:47 +1030 Subject: [PATCH 1553/1782] Remove ConfineMouseTracker field --- osu.Game/OsuGame.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index d22ac1aec8..772f9ff145 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -90,8 +90,6 @@ namespace osu.Game private IdleTracker idleTracker; - private ConfineMouseTracker confineMouseTracker; - /// /// Whether overlays should be able to be opened game-wide. Value is sourced from the current active screen. /// @@ -585,7 +583,7 @@ namespace osu.Game leftFloatingOverlayContent = new Container { RelativeSizeAxes = Axes.Both }, topMostOverlayContent = new Container { RelativeSizeAxes = Axes.Both }, idleTracker, - confineMouseTracker = new ConfineMouseTracker() + new ConfineMouseTracker() }); ScreenStack.ScreenPushed += screenPushed; From 31d347be5cfd24d63d3e39b78ecec916bab843c6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 7 Oct 2020 16:30:26 +0900 Subject: [PATCH 1554/1782] Make extended score panel taller to better fit all information --- osu.Game/Screens/Ranking/ScorePanel.cs | 7 ++++++- osu.Game/Screens/Ranking/ScorePanelTrackingContainer.cs | 1 + 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Ranking/ScorePanel.cs b/osu.Game/Screens/Ranking/ScorePanel.cs index 1904da7094..8c8a547277 100644 --- a/osu.Game/Screens/Ranking/ScorePanel.cs +++ b/osu.Game/Screens/Ranking/ScorePanel.cs @@ -39,7 +39,7 @@ namespace osu.Game.Screens.Ranking /// /// Height of the panel when expanded. /// - private const float expanded_height = 560; + private const float expanded_height = 586; /// /// Height of the top layer when the panel is expanded. @@ -105,11 +105,16 @@ namespace osu.Game.Screens.Ranking [BackgroundDependencyLoader] private void load() { + // ScorePanel doesn't include the top extruding area in its own size. + // Adding a manual offset here allows the expanded version to take on an "acceptable" vertical centre when at 100% UI scale. + const float vertical_fudge = 20; + InternalChild = content = new Container { Anchor = Anchor.Centre, Origin = Anchor.Centre, Size = new Vector2(40), + Y = vertical_fudge, Children = new Drawable[] { topLayerContainer = new Container diff --git a/osu.Game/Screens/Ranking/ScorePanelTrackingContainer.cs b/osu.Game/Screens/Ranking/ScorePanelTrackingContainer.cs index c8010d1c32..67533aaa24 100644 --- a/osu.Game/Screens/Ranking/ScorePanelTrackingContainer.cs +++ b/osu.Game/Screens/Ranking/ScorePanelTrackingContainer.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; namespace osu.Game.Screens.Ranking From f77ad8cf3907327834db7d12c129511f70c5b819 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 7 Oct 2020 17:03:34 +0900 Subject: [PATCH 1555/1782] Remove unused using --- osu.Game/Screens/Ranking/ScorePanelTrackingContainer.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Screens/Ranking/ScorePanelTrackingContainer.cs b/osu.Game/Screens/Ranking/ScorePanelTrackingContainer.cs index 67533aaa24..c8010d1c32 100644 --- a/osu.Game/Screens/Ranking/ScorePanelTrackingContainer.cs +++ b/osu.Game/Screens/Ranking/ScorePanelTrackingContainer.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; namespace osu.Game.Screens.Ranking From f90ac2e76c2bf20c88fafef834b3c9110156f660 Mon Sep 17 00:00:00 2001 From: Shane Woolcock Date: Wed, 7 Oct 2020 18:50:02 +1030 Subject: [PATCH 1556/1782] Ensure we assert after the seek has completed --- osu.Game.Tests/Visual/Gameplay/TestSceneOverlayActivation.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneOverlayActivation.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneOverlayActivation.cs index 41f7582d31..4fa4c00981 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneOverlayActivation.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneOverlayActivation.cs @@ -54,11 +54,11 @@ namespace osu.Game.Tests.Visual.Gameplay AddAssert("local user playing", () => Player.LocalUserPlaying.Value); AddAssert("activation mode is disabled", () => Player.OverlayActivationMode == OverlayActivation.Disabled); AddStep("seek to break", () => Player.GameplayClockContainer.Seek(Beatmap.Value.Beatmap.Breaks.First().StartTime)); - AddAssert("local user not playing", () => !Player.LocalUserPlaying.Value); AddUntilStep("activation mode is user triggered", () => Player.OverlayActivationMode == OverlayActivation.UserTriggered); + AddAssert("local user not playing", () => !Player.LocalUserPlaying.Value); AddStep("seek to break end", () => Player.GameplayClockContainer.Seek(Beatmap.Value.Beatmap.Breaks.First().EndTime)); - AddAssert("local user playing", () => Player.LocalUserPlaying.Value); AddUntilStep("activation mode is disabled", () => Player.OverlayActivationMode == OverlayActivation.Disabled); + AddAssert("local user playing", () => Player.LocalUserPlaying.Value); } protected override TestPlayer CreatePlayer(Ruleset ruleset) => new OverlayTestPlayer(); From 0f6eb9d4cb7750c1890da24f26d5080f42e643ea Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 7 Oct 2020 17:40:54 +0900 Subject: [PATCH 1557/1782] Ensure music playback is stopped when retrying by any means --- 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 175722c44e..90a0eb0027 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -441,6 +441,10 @@ namespace osu.Game.Screens.Play /// public void Restart() { + // at the point of restarting the track should either already be paused or the volume should be zero. + // stopping here is to ensure music doesn't become audible after exiting back to PlayerLoader. + musicController.Stop(); + sampleRestart?.Play(); RestartRequested?.Invoke(); From 04fa0bff9d8c6bbb2d1b656619a103c7e17d4211 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 7 Oct 2020 17:46:57 +0900 Subject: [PATCH 1558/1782] Add CanBeNull spec and xmldoc --- osu.Game/Rulesets/Ruleset.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index ae1407fb8f..fef36ef16a 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -158,8 +158,22 @@ namespace osu.Game.Rulesets public abstract DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap); + /// + /// Optionally creates a to generate performance data from the provided score. + /// + /// Difficulty attributes for the beatmap related to the provided score. + /// The score to be processed. + /// A performance calculator instance for the provided score. + [CanBeNull] public virtual PerformanceCalculator CreatePerformanceCalculator(DifficultyAttributes attributes, ScoreInfo score) => null; + /// + /// Optionally creates a to generate performance data from the provided score. + /// + /// The beatmap to use as a source for generating . + /// The score to be processed. + /// A performance calculator instance for the provided score. + [CanBeNull] public PerformanceCalculator CreatePerformanceCalculator(WorkingBeatmap beatmap, ScoreInfo score) { var difficultyCalculator = CreateDifficultyCalculator(beatmap); From 6487f58e9ad11cc8f28592593e693057e3ecb38e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 7 Oct 2020 17:52:35 +0900 Subject: [PATCH 1559/1782] Fix failing tests --- osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs b/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs index 19294d12fc..528689e67c 100644 --- a/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs +++ b/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs @@ -193,6 +193,7 @@ namespace osu.Game.Tests.Visual.Background AddStep("Transition to Results", () => player.Push(results = new FadeAccessibleResults(new ScoreInfo { + Ruleset = new OsuRuleset().RulesetInfo, User = new User { Username = "osu!" }, Beatmap = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo }))); From 3c3c1ce8855487356f0c6afec1fc6b25d70542c6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 7 Oct 2020 18:18:01 +0900 Subject: [PATCH 1560/1782] Don't force playback of (non-looping) DrawableHitObject samples after skin change --- 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 8012b4d95c..1ef6c8c207 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -273,7 +273,7 @@ namespace osu.Game.Rulesets.Objects.Drawables // apply any custom state overrides ApplyCustomUpdateState?.Invoke(this, newState); - if (newState == ArmedState.Hit) + if (!force && newState == ArmedState.Hit) PlaySamples(); } From 8c528c89108ca7a7ffde30f2a355f214205e3e88 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 7 Oct 2020 18:36:34 +0900 Subject: [PATCH 1561/1782] Fix legacy taiko skins showing double judgements --- .../Skinning/TaikoLegacySkinTransformer.cs | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacySkinTransformer.cs b/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacySkinTransformer.cs index 93c9deec1f..a804ea5f82 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacySkinTransformer.cs @@ -7,6 +7,7 @@ using osu.Framework.Audio.Sample; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Audio; +using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Taiko.UI; using osu.Game.Skinning; @@ -14,13 +15,29 @@ namespace osu.Game.Rulesets.Taiko.Skinning { public class TaikoLegacySkinTransformer : LegacySkinTransformer { + private Lazy hasExplosion; + public TaikoLegacySkinTransformer(ISkinSource source) : base(source) { + Source.SourceChanged += sourceChanged; + sourceChanged(); + } + + private void sourceChanged() + { + hasExplosion = new Lazy(() => Source.GetTexture(getHitName(TaikoSkinComponents.TaikoExplosionGreat)) != null); } public override Drawable GetDrawableComponent(ISkinComponent component) { + if (component is GameplaySkinComponent) + { + // if a taiko skin is providing explosion sprites, hide the judgements completely + if (hasExplosion.Value) + return Drawable.Empty(); + } + if (!(component is TaikoSkinComponent taikoComponent)) return null; @@ -87,10 +104,13 @@ namespace osu.Game.Rulesets.Taiko.Skinning var hitName = getHitName(taikoComponent.Component); var hitSprite = this.GetAnimation(hitName, true, false); - var strongHitSprite = this.GetAnimation($"{hitName}k", true, false); if (hitSprite != null) + { + var strongHitSprite = this.GetAnimation($"{hitName}k", true, false); + return new LegacyHitExplosion(hitSprite, strongHitSprite); + } return null; From 94a6e2856570bcb281d2073cd7eb6f716362b0bb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 7 Oct 2020 18:40:09 +0900 Subject: [PATCH 1562/1782] Add back second removed condition --- osu.Game/Updater/UpdateManager.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Updater/UpdateManager.cs b/osu.Game/Updater/UpdateManager.cs index 30e28f0e95..f772c6d282 100644 --- a/osu.Game/Updater/UpdateManager.cs +++ b/osu.Game/Updater/UpdateManager.cs @@ -61,6 +61,9 @@ namespace osu.Game.Updater public async Task CheckForUpdateAsync() { + if (!CanCheckForUpdate) + return false; + Task waitTask; lock (updateTaskLock) From 74af7cc5036911b47238d9cde41691c0bcf5cf83 Mon Sep 17 00:00:00 2001 From: Endrik Tombak Date: Wed, 7 Oct 2020 17:00:00 +0300 Subject: [PATCH 1563/1782] Rework ScoreProcessor --- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 7a5b707357..ca6a8622f7 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -223,7 +223,7 @@ namespace osu.Game.Rulesets.Scoring case ScoringMode.Classic: // should emulate osu-stable's scoring as closely as we can (https://osu.ppy.sh/help/wiki/Score/ScoreV1) - return getBonusScore(statistics) + (accuracyRatio * maxCombo * 300) * (1 + Math.Max(0, (comboRatio * maxCombo) - 1) * scoreMultiplier / 25); + return getBonusScore(statistics) + (accuracyRatio * Math.Max(1, maxCombo) * 300) * (1 + Math.Max(0, (comboRatio * maxCombo) - 1) * scoreMultiplier / 25); } } @@ -267,12 +267,6 @@ namespace osu.Game.Rulesets.Scoring { maxHighestCombo = HighestCombo.Value; maxBaseScore = baseScore; - - if (maxBaseScore == 0 || maxHighestCombo == 0) - { - Mode.Value = ScoringMode.Classic; - Mode.Disabled = true; - } } baseScore = 0; From 2b6e4e575e2668e102f491a66ff070cac66c5fdd Mon Sep 17 00:00:00 2001 From: Endrik Tombak Date: Wed, 7 Oct 2020 17:04:55 +0300 Subject: [PATCH 1564/1782] Award max combo portion score if max achievable is 0 --- 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 ca6a8622f7..9bfd737f7e 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -197,7 +197,7 @@ namespace osu.Game.Rulesets.Scoring { return GetScore(mode, maxHighestCombo, maxBaseScore > 0 ? baseScore / maxBaseScore : 0, - maxHighestCombo > 0 ? (double)HighestCombo.Value / maxHighestCombo : 0, + maxHighestCombo > 0 ? (double)HighestCombo.Value / maxHighestCombo : 1, scoreResultCounts); } From 6113557acc346a572d710229553b133a9ebfdd91 Mon Sep 17 00:00:00 2001 From: Endrik Tombak Date: Wed, 7 Oct 2020 17:11:48 +0300 Subject: [PATCH 1565/1782] Add back small tick tests --- osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs index f81ab6c866..dd191b03c2 100644 --- a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs +++ b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs @@ -60,16 +60,20 @@ namespace osu.Game.Tests.Rulesets.Scoring [TestCase(ScoringMode.Standardised, HitResult.Good, HitResult.Perfect, 3_350_000 / 7.0)] [TestCase(ScoringMode.Standardised, HitResult.Great, HitResult.Great, 575_000)] [TestCase(ScoringMode.Standardised, HitResult.Perfect, HitResult.Perfect, 575_000)] + [TestCase(ScoringMode.Standardised, HitResult.SmallTickMiss, HitResult.SmallTickHit, 700_000)] + [TestCase(ScoringMode.Standardised, HitResult.SmallTickHit, HitResult.SmallTickHit, 925_000)] [TestCase(ScoringMode.Standardised, HitResult.LargeTickMiss, HitResult.LargeTickHit, 0)] [TestCase(ScoringMode.Standardised, HitResult.LargeTickHit, HitResult.LargeTickHit, 575_000)] - [TestCase(ScoringMode.Standardised, HitResult.SmallBonus, HitResult.SmallBonus, 30)] - [TestCase(ScoringMode.Standardised, HitResult.LargeBonus, HitResult.LargeBonus, 150)] + [TestCase(ScoringMode.Standardised, HitResult.SmallBonus, HitResult.SmallBonus, 700_030)] + [TestCase(ScoringMode.Standardised, HitResult.LargeBonus, HitResult.LargeBonus, 700_150)] [TestCase(ScoringMode.Classic, HitResult.Miss, HitResult.Great, 0)] [TestCase(ScoringMode.Classic, HitResult.Meh, HitResult.Great, 156)] [TestCase(ScoringMode.Classic, HitResult.Ok, HitResult.Great, 312)] [TestCase(ScoringMode.Classic, HitResult.Good, HitResult.Perfect, 3744 / 7.0)] [TestCase(ScoringMode.Classic, HitResult.Great, HitResult.Great, 936)] [TestCase(ScoringMode.Classic, HitResult.Perfect, HitResult.Perfect, 936)] + [TestCase(ScoringMode.Classic, HitResult.SmallTickMiss, HitResult.SmallTickHit, 0)] + [TestCase(ScoringMode.Classic, HitResult.SmallTickHit, HitResult.SmallTickHit, 225)] [TestCase(ScoringMode.Classic, HitResult.LargeTickMiss, HitResult.LargeTickHit, 0)] [TestCase(ScoringMode.Classic, HitResult.LargeTickHit, HitResult.LargeTickHit, 936)] [TestCase(ScoringMode.Classic, HitResult.SmallBonus, HitResult.SmallBonus, 30)] From 7109c3b6cd1777d5aa1b3eafceb547ff6de7742f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 7 Oct 2020 21:06:24 +0200 Subject: [PATCH 1566/1782] Rename variable as suggested --- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 9bfd737f7e..33271d9689 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -67,7 +67,7 @@ namespace osu.Game.Rulesets.Scoring private readonly double accuracyPortion; private readonly double comboPortion; - private int maxHighestCombo; + private int maxAchievableCombo; private double maxBaseScore; private double rollingMaxBaseScore; private double baseScore; @@ -195,9 +195,9 @@ namespace osu.Game.Rulesets.Scoring private double getScore(ScoringMode mode) { - return GetScore(mode, maxHighestCombo, + return GetScore(mode, maxAchievableCombo, maxBaseScore > 0 ? baseScore / maxBaseScore : 0, - maxHighestCombo > 0 ? (double)HighestCombo.Value / maxHighestCombo : 1, + maxAchievableCombo > 0 ? (double)HighestCombo.Value / maxAchievableCombo : 1, scoreResultCounts); } @@ -265,7 +265,7 @@ namespace osu.Game.Rulesets.Scoring if (storeResults) { - maxHighestCombo = HighestCombo.Value; + maxAchievableCombo = HighestCombo.Value; maxBaseScore = baseScore; } From 2d070934d912b9b05d0d587a30e80dee5ff0d838 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 7 Oct 2020 21:12:48 +0200 Subject: [PATCH 1567/1782] Add test coverage for empty beatmaps --- osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs index dd191b03c2..e89562f893 100644 --- a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs +++ b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs @@ -135,6 +135,17 @@ namespace osu.Game.Tests.Rulesets.Scoring Assert.IsTrue(Precision.AlmostEquals(expectedScore, scoreProcessor.TotalScore.Value)); } + [Test] + public void TestEmptyBeatmap( + [Values(ScoringMode.Standardised, ScoringMode.Classic)] + ScoringMode scoringMode) + { + scoreProcessor.Mode.Value = scoringMode; + scoreProcessor.ApplyBeatmap(new TestBeatmap(new RulesetInfo())); + + Assert.IsTrue(Precision.AlmostEquals(0, scoreProcessor.TotalScore.Value)); + } + [TestCase(HitResult.IgnoreHit, HitResult.IgnoreMiss)] [TestCase(HitResult.Meh, HitResult.Miss)] [TestCase(HitResult.Ok, HitResult.Miss)] From b1029a124ca6efd53a24a950d290c33f5efed148 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 7 Oct 2020 22:57:20 +0200 Subject: [PATCH 1568/1782] Move event subscription to LoadComplete Prevents attempting to read from the `colours` field before it is actually injected. --- osu.Game/Screens/Edit/Timing/ControlPointTable.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs index 4121e1f7bb..c8982b819a 100644 --- a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs +++ b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs @@ -113,7 +113,6 @@ namespace osu.Game.Screens.Edit.Timing }; controlPoints = group.ControlPoints.GetBoundCopy(); - controlPoints.CollectionChanged += (_, __) => createChildren(); } [Resolved] @@ -125,6 +124,12 @@ namespace osu.Game.Screens.Edit.Timing createChildren(); } + protected override void LoadComplete() + { + base.LoadComplete(); + controlPoints.CollectionChanged += (_, __) => createChildren(); + } + private void createChildren() { fill.ChildrenEnumerable = controlPoints.Select(createAttribute).Where(c => c != null); From ac44f6f679504554485edb3877f76615f463f1ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 7 Oct 2020 23:10:28 +0200 Subject: [PATCH 1569/1782] Ensure control point group exists after move If the control point group moved was empty, it would not be created due to a lack of ControlPointInfo.Add() calls. --- osu.Game/Screens/Edit/Timing/GroupSection.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Timing/GroupSection.cs b/osu.Game/Screens/Edit/Timing/GroupSection.cs index c77d48ef0a..d76b5e7406 100644 --- a/osu.Game/Screens/Edit/Timing/GroupSection.cs +++ b/osu.Game/Screens/Edit/Timing/GroupSection.cs @@ -111,7 +111,8 @@ namespace osu.Game.Screens.Edit.Timing foreach (var cp in currentGroupItems) Beatmap.Value.Beatmap.ControlPointInfo.Add(time, cp); - SelectedGroup.Value = Beatmap.Value.Beatmap.ControlPointInfo.GroupAt(time); + // the control point might not necessarily exist yet, if currentGroupItems was empty. + SelectedGroup.Value = Beatmap.Value.Beatmap.ControlPointInfo.GroupAt(time, true); changeHandler?.EndChange(); } From d9089ef93c7d6a052a86fa55f27129903d1fa649 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 8 Oct 2020 12:52:52 +0900 Subject: [PATCH 1570/1782] Add missing bonus type for taiko ruleset --- osu.Game.Rulesets.Taiko/TaikoRuleset.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index f4c94c9248..7f8289aa91 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -167,6 +167,8 @@ namespace osu.Game.Rulesets.Taiko HitResult.Ok, HitResult.SmallTickHit, + + HitResult.SmallBonus, }; } @@ -176,6 +178,9 @@ namespace osu.Game.Rulesets.Taiko { case HitResult.SmallTickHit: return "drum tick"; + + case HitResult.SmallBonus: + return "strong bonus"; } return base.GetDisplayNameForHitResult(result); From f70252d07bc4ebea18b4cdf0d1e4bd44477354c4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 8 Oct 2020 12:52:58 +0900 Subject: [PATCH 1571/1782] Match plurality --- 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 eb845cdea6..bda595e840 100644 --- a/osu.Game.Rulesets.Catch/CatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs @@ -164,7 +164,7 @@ namespace osu.Game.Rulesets.Catch return "small droplet"; case HitResult.LargeBonus: - return "bananas"; + return "banana"; } return base.GetDisplayNameForHitResult(result); From ef092de9baaf3bde1e50abf5441fcefd847b6007 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 8 Oct 2020 16:56:55 +0900 Subject: [PATCH 1572/1782] Add missing UpdateHitObject calls and move local to usages (not via bindables) --- .../Compose/Components/BlueprintContainer.cs | 4 ++ .../Timeline/TimelineHitObjectBlueprint.cs | 4 +- osu.Game/Screens/Edit/EditorBeatmap.cs | 42 +++++++------------ 3 files changed, 22 insertions(+), 28 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index 970e16d1c3..addb970e8a 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -436,8 +436,12 @@ namespace osu.Game.Screens.Edit.Compose.Components { // Apply the start time at the newly snapped-to position double offset = result.Time.Value - draggedObject.StartTime; + foreach (HitObject obj in SelectionHandler.SelectedHitObjects) + { obj.StartTime += offset; + Beatmap.UpdateHitObject(obj); + } } return true; diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs index f0757a3dda..6c3bcfae32 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs @@ -392,6 +392,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline return; repeatHitObject.RepeatCount = proposedCount; + beatmap.UpdateHitObject(hitObject); break; case IHasDuration endTimeHitObject: @@ -401,10 +402,9 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline return; endTimeHitObject.Duration = snappedTime - hitObject.StartTime; + beatmap.UpdateHitObject(hitObject); break; } - - beatmap.UpdateHitObject(hitObject); } } diff --git a/osu.Game/Screens/Edit/EditorBeatmap.cs b/osu.Game/Screens/Edit/EditorBeatmap.cs index fb75d91d16..d02841a95f 100644 --- a/osu.Game/Screens/Edit/EditorBeatmap.cs +++ b/osu.Game/Screens/Edit/EditorBeatmap.cs @@ -89,8 +89,6 @@ namespace osu.Game.Screens.Edit private IList mutableHitObjects => (IList)PlayableBeatmap.HitObjects; - private readonly HashSet pendingUpdates = new HashSet(); - private bool isBatchApplying; /// @@ -150,7 +148,16 @@ namespace osu.Game.Screens.Edit /// The to update. public void UpdateHitObject([NotNull] HitObject hitObject) { - pendingUpdates.Add(hitObject); + if (isBatchApplying) + batchPendingUpdates.Add(hitObject); + else + { + beatmapProcessor?.PreProcess(); + processHitObject(hitObject); + beatmapProcessor?.PostProcess(); + + HitObjectUpdated?.Invoke(hitObject); + } } /// @@ -220,6 +227,8 @@ namespace osu.Game.Screens.Edit private readonly List batchPendingDeletes = new List(); + private readonly HashSet batchPendingUpdates = new HashSet(); + /// /// Apply a batch of operations in one go, without performing Pre/Postprocessing each time. /// @@ -237,14 +246,17 @@ namespace osu.Game.Screens.Edit foreach (var h in batchPendingDeletes) processHitObject(h); foreach (var h in batchPendingInserts) processHitObject(h); + foreach (var h in batchPendingUpdates) processHitObject(h); beatmapProcessor?.PostProcess(); foreach (var h in batchPendingDeletes) HitObjectRemoved?.Invoke(h); foreach (var h in batchPendingInserts) HitObjectAdded?.Invoke(h); + foreach (var h in batchPendingUpdates) HitObjectUpdated?.Invoke(h); batchPendingDeletes.Clear(); batchPendingInserts.Clear(); + batchPendingUpdates.Clear(); isBatchApplying = false; } @@ -254,28 +266,6 @@ namespace osu.Game.Screens.Edit /// public void Clear() => RemoveRange(HitObjects.ToArray()); - protected override void Update() - { - base.Update(); - - // debounce updates as they are common and may come from input events, which can run needlessly many times per update frame. - if (pendingUpdates.Count > 0) - { - beatmapProcessor?.PreProcess(); - - foreach (var hitObject in pendingUpdates) - processHitObject(hitObject); - - beatmapProcessor?.PostProcess(); - - // explicitly needs to be fired after PostProcess - foreach (var hitObject in pendingUpdates) - HitObjectUpdated?.Invoke(hitObject); - - pendingUpdates.Clear(); - } - } - private void processHitObject(HitObject hitObject) => hitObject.ApplyDefaults(ControlPointInfo, BeatmapInfo.BaseDifficulty); private void trackStartTime(HitObject hitObject) @@ -322,7 +312,7 @@ namespace osu.Game.Screens.Edit public void UpdateBeatmap() { foreach (var h in HitObjects) - pendingUpdates.Add(h); + batchPendingUpdates.Add(h); } } } From ce04daf053e20ba3a6506f5bd887696d6d6761a7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 8 Oct 2020 16:57:04 +0900 Subject: [PATCH 1573/1782] Split transaction handling code out into base class --- osu.Game/Screens/Edit/EditorChangeHandler.cs | 21 ++------- .../Edit/LegacyEditorBeatmapPatcher.cs | 25 ++++++----- .../Edit/TransactionalCommitComponent.cs | 45 +++++++++++++++++++ 3 files changed, 61 insertions(+), 30 deletions(-) create mode 100644 osu.Game/Screens/Edit/TransactionalCommitComponent.cs diff --git a/osu.Game/Screens/Edit/EditorChangeHandler.cs b/osu.Game/Screens/Edit/EditorChangeHandler.cs index b69e9c4c51..0c80a3e187 100644 --- a/osu.Game/Screens/Edit/EditorChangeHandler.cs +++ b/osu.Game/Screens/Edit/EditorChangeHandler.cs @@ -16,7 +16,7 @@ namespace osu.Game.Screens.Edit /// /// Tracks changes to the . /// - public class EditorChangeHandler : IEditorChangeHandler + public class EditorChangeHandler : TransactionalCommitComponent, IEditorChangeHandler { public readonly Bindable CanUndo = new Bindable(); public readonly Bindable CanRedo = new Bindable(); @@ -41,7 +41,6 @@ namespace osu.Game.Screens.Edit } private readonly EditorBeatmap editorBeatmap; - private int bulkChangesStarted; private bool isRestoring; public const int MAX_SAVED_STATES = 50; @@ -70,22 +69,8 @@ namespace osu.Game.Screens.Edit private void hitObjectUpdated(HitObject obj) => SaveState(); - public void BeginChange() => bulkChangesStarted++; - - public void EndChange() + protected override void UpdateState() { - if (bulkChangesStarted == 0) - throw new InvalidOperationException($"Cannot call {nameof(EndChange)} without a previous call to {nameof(BeginChange)}."); - - if (--bulkChangesStarted == 0) - SaveState(); - } - - public void SaveState() - { - if (bulkChangesStarted > 0) - return; - if (isRestoring) return; @@ -120,7 +105,7 @@ namespace osu.Game.Screens.Edit /// The direction to restore in. If less than 0, an older state will be used. If greater than 0, a newer state will be used. public void RestoreState(int direction) { - if (bulkChangesStarted > 0) + if (TransactionActive) return; if (savedStates.Count == 0) diff --git a/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs b/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs index fb7d0dd826..72d3421755 100644 --- a/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs +++ b/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs @@ -68,19 +68,20 @@ namespace osu.Game.Screens.Edit toRemove.Sort(); toAdd.Sort(); - editorBeatmap.ApplyBatchChanges(eb => - { - // Apply the changes. - for (int i = toRemove.Count - 1; i >= 0; i--) - eb.RemoveAt(toRemove[i]); + editorBeatmap.BeginChange(); - if (toAdd.Count > 0) - { - IBeatmap newBeatmap = readBeatmap(newState); - foreach (var i in toAdd) - eb.Insert(i, newBeatmap.HitObjects[i]); - } - }); + // Apply the changes. + for (int i = toRemove.Count - 1; i >= 0; i--) + editorBeatmap.RemoveAt(toRemove[i]); + + if (toAdd.Count > 0) + { + IBeatmap newBeatmap = readBeatmap(newState); + foreach (var i in toAdd) + editorBeatmap.Insert(i, newBeatmap.HitObjects[i]); + } + + editorBeatmap.EndChange(); } private string readString(byte[] state) => Encoding.UTF8.GetString(state); diff --git a/osu.Game/Screens/Edit/TransactionalCommitComponent.cs b/osu.Game/Screens/Edit/TransactionalCommitComponent.cs new file mode 100644 index 0000000000..87a29a6237 --- /dev/null +++ b/osu.Game/Screens/Edit/TransactionalCommitComponent.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 System; + +namespace osu.Game.Screens.Edit +{ + /// + /// A component that tracks a batch change, only applying after all active changes are completed. + /// + public abstract class TransactionalCommitComponent + { + public bool TransactionActive => bulkChangesStarted > 0; + + private int bulkChangesStarted; + + /// + /// Signal the beginning of a change. + /// + public void BeginChange() => bulkChangesStarted++; + + /// + /// Signal the end of a change. + /// + /// Throws if was not first called. + public void EndChange() + { + if (bulkChangesStarted == 0) + throw new InvalidOperationException($"Cannot call {nameof(EndChange)} without a previous call to {nameof(BeginChange)}."); + + if (--bulkChangesStarted == 0) + UpdateState(); + } + + public void SaveState() + { + if (bulkChangesStarted > 0) + return; + + UpdateState(); + } + + protected abstract void UpdateState(); + } +} From a9bca671d0e3ae2d4a14cf36e5ac541a78bbb63e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 8 Oct 2020 17:17:52 +0900 Subject: [PATCH 1574/1782] Make component and add hooking events --- .../Edit/TransactionalCommitComponent.cs | 32 +++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/TransactionalCommitComponent.cs b/osu.Game/Screens/Edit/TransactionalCommitComponent.cs index 87a29a6237..3d3539ee2f 100644 --- a/osu.Game/Screens/Edit/TransactionalCommitComponent.cs +++ b/osu.Game/Screens/Edit/TransactionalCommitComponent.cs @@ -2,14 +2,30 @@ // See the LICENCE file in the repository root for full licence text. using System; +using osu.Framework.Graphics; namespace osu.Game.Screens.Edit { /// /// A component that tracks a batch change, only applying after all active changes are completed. /// - public abstract class TransactionalCommitComponent + public abstract class TransactionalCommitComponent : Component { + /// + /// Fires whenever a transaction begins. Will not fire on nested transactions. + /// + public event Action TransactionBegan; + + /// + /// Fires when the last transaction completes. + /// + public event Action TransactionEnded; + + /// + /// Fires when is called and results in a non-transactional state save. + /// + public event Action SaveStateTriggered; + public bool TransactionActive => bulkChangesStarted > 0; private int bulkChangesStarted; @@ -17,7 +33,11 @@ namespace osu.Game.Screens.Edit /// /// Signal the beginning of a change. /// - public void BeginChange() => bulkChangesStarted++; + public void BeginChange() + { + if (bulkChangesStarted++ == 0) + TransactionBegan?.Invoke(); + } /// /// Signal the end of a change. @@ -29,14 +49,22 @@ namespace osu.Game.Screens.Edit throw new InvalidOperationException($"Cannot call {nameof(EndChange)} without a previous call to {nameof(BeginChange)}."); if (--bulkChangesStarted == 0) + { UpdateState(); + TransactionEnded?.Invoke(); + } } + /// + /// Force an update of the state with no attached transaction. + /// This is a no-op if a transaction is already active. Should generally be used as a safety measure to ensure granular changes are not left outside a transaction. + /// public void SaveState() { if (bulkChangesStarted > 0) return; + SaveStateTriggered?.Invoke(); UpdateState(); } From 0781fbd44360daccc23d0fe585a98c4b91b5676d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 8 Oct 2020 17:18:20 +0900 Subject: [PATCH 1575/1782] Make EditorBeatmap implement TransactionalCommitComponent --- osu.Game/Screens/Edit/EditorBeatmap.cs | 61 +++++++------------ .../Screens/Edit/Setup/DifficultySection.cs | 2 +- 2 files changed, 24 insertions(+), 39 deletions(-) diff --git a/osu.Game/Screens/Edit/EditorBeatmap.cs b/osu.Game/Screens/Edit/EditorBeatmap.cs index d02841a95f..f776908a0b 100644 --- a/osu.Game/Screens/Edit/EditorBeatmap.cs +++ b/osu.Game/Screens/Edit/EditorBeatmap.cs @@ -8,7 +8,6 @@ using System.Linq; using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; -using osu.Framework.Graphics; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.Timing; @@ -18,7 +17,7 @@ using osu.Game.Skinning; namespace osu.Game.Screens.Edit { - public class EditorBeatmap : Component, IBeatmap, IBeatSnapProvider + public class EditorBeatmap : TransactionalCommitComponent, IBeatmap, IBeatSnapProvider { /// /// Invoked when a is added to this . @@ -89,19 +88,16 @@ namespace osu.Game.Screens.Edit private IList mutableHitObjects => (IList)PlayableBeatmap.HitObjects; - private bool isBatchApplying; - /// /// Adds a collection of s to this . /// /// The s to add. public void AddRange(IEnumerable hitObjects) { - ApplyBatchChanges(_ => - { - foreach (var h in hitObjects) - Add(h); - }); + BeginChange(); + foreach (var h in hitObjects) + Add(h); + EndChange(); } /// @@ -129,7 +125,7 @@ namespace osu.Game.Screens.Edit mutableHitObjects.Insert(index, hitObject); - if (isBatchApplying) + if (TransactionActive) batchPendingInserts.Add(hitObject); else { @@ -148,16 +144,8 @@ namespace osu.Game.Screens.Edit /// The to update. public void UpdateHitObject([NotNull] HitObject hitObject) { - if (isBatchApplying) - batchPendingUpdates.Add(hitObject); - else - { - beatmapProcessor?.PreProcess(); - processHitObject(hitObject); - beatmapProcessor?.PostProcess(); - - HitObjectUpdated?.Invoke(hitObject); - } + // updates are debounced regardless of whether a batch is active. + batchPendingUpdates.Add(hitObject); } /// @@ -182,11 +170,10 @@ namespace osu.Game.Screens.Edit /// The s to remove. public void RemoveRange(IEnumerable hitObjects) { - ApplyBatchChanges(_ => - { - foreach (var h in hitObjects) - Remove(h); - }); + BeginChange(); + foreach (var h in hitObjects) + Remove(h); + EndChange(); } /// @@ -210,7 +197,7 @@ namespace osu.Game.Screens.Edit bindable.UnbindAll(); startTimeBindables.Remove(hitObject); - if (isBatchApplying) + if (TransactionActive) batchPendingDeletes.Add(hitObject); else { @@ -229,18 +216,18 @@ namespace osu.Game.Screens.Edit private readonly HashSet batchPendingUpdates = new HashSet(); - /// - /// Apply a batch of operations in one go, without performing Pre/Postprocessing each time. - /// - /// The function which will apply the batch changes. - public void ApplyBatchChanges(Action applyFunction) + protected override void Update() { - if (isBatchApplying) - throw new InvalidOperationException("Attempting to perform a batch application from within an existing batch"); + base.Update(); - isBatchApplying = true; + if (batchPendingUpdates.Count > 0) + UpdateState(); + } - applyFunction(this); + protected override void UpdateState() + { + if (batchPendingUpdates.Count == 0 && batchPendingDeletes.Count == 0 && batchPendingInserts.Count == 0) + return; beatmapProcessor?.PreProcess(); @@ -257,8 +244,6 @@ namespace osu.Game.Screens.Edit batchPendingDeletes.Clear(); batchPendingInserts.Clear(); batchPendingUpdates.Clear(); - - isBatchApplying = false; } /// @@ -309,7 +294,7 @@ namespace osu.Game.Screens.Edit /// /// Update all hit objects with potentially changed difficulty or control point data. /// - public void UpdateBeatmap() + public void UpdateAllHitObjects() { foreach (var h in HitObjects) batchPendingUpdates.Add(h); diff --git a/osu.Game/Screens/Edit/Setup/DifficultySection.cs b/osu.Game/Screens/Edit/Setup/DifficultySection.cs index 2d8031c3c8..aa1d57db31 100644 --- a/osu.Game/Screens/Edit/Setup/DifficultySection.cs +++ b/osu.Game/Screens/Edit/Setup/DifficultySection.cs @@ -93,7 +93,7 @@ namespace osu.Game.Screens.Edit.Setup Beatmap.Value.BeatmapInfo.BaseDifficulty.ApproachRate = approachRateSlider.Current.Value; Beatmap.Value.BeatmapInfo.BaseDifficulty.OverallDifficulty = overallDifficultySlider.Current.Value; - editorBeatmap.UpdateBeatmap(); + editorBeatmap.UpdateAllHitObjects(); } } } From b2d93f799f8cc269f037baccf6ad7e903cbb5b00 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 8 Oct 2020 17:22:35 +0900 Subject: [PATCH 1576/1782] Hook ChangeHandler to transactional events rather than individual ones --- osu.Game/Screens/Edit/EditorChangeHandler.cs | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/osu.Game/Screens/Edit/EditorChangeHandler.cs b/osu.Game/Screens/Edit/EditorChangeHandler.cs index 0c80a3e187..62187aed24 100644 --- a/osu.Game/Screens/Edit/EditorChangeHandler.cs +++ b/osu.Game/Screens/Edit/EditorChangeHandler.cs @@ -53,9 +53,9 @@ namespace osu.Game.Screens.Edit { this.editorBeatmap = editorBeatmap; - editorBeatmap.HitObjectAdded += hitObjectAdded; - editorBeatmap.HitObjectRemoved += hitObjectRemoved; - editorBeatmap.HitObjectUpdated += hitObjectUpdated; + editorBeatmap.TransactionBegan += BeginChange; + editorBeatmap.TransactionEnded += EndChange; + editorBeatmap.SaveStateTriggered += SaveState; patcher = new LegacyEditorBeatmapPatcher(editorBeatmap); @@ -63,12 +63,6 @@ namespace osu.Game.Screens.Edit SaveState(); } - private void hitObjectAdded(HitObject obj) => SaveState(); - - private void hitObjectRemoved(HitObject obj) => SaveState(); - - private void hitObjectUpdated(HitObject obj) => SaveState(); - protected override void UpdateState() { if (isRestoring) From 7ffab38728014fffdeedbc9906258f693c26167a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 8 Oct 2020 17:43:08 +0900 Subject: [PATCH 1577/1782] Add test coverage of TransactionalCommitComponent --- .../TransactionalCommitComponentTest.cs | 100 ++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 osu.Game.Tests/Editing/TransactionalCommitComponentTest.cs diff --git a/osu.Game.Tests/Editing/TransactionalCommitComponentTest.cs b/osu.Game.Tests/Editing/TransactionalCommitComponentTest.cs new file mode 100644 index 0000000000..4ce9115ec4 --- /dev/null +++ b/osu.Game.Tests/Editing/TransactionalCommitComponentTest.cs @@ -0,0 +1,100 @@ +// Copyright (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.Screens.Edit; + +namespace osu.Game.Tests.Editing +{ + [TestFixture] + public class TransactionalCommitComponentTest + { + private TestHandler handler; + + [SetUp] + public void SetUp() + { + handler = new TestHandler(); + } + + [Test] + public void TestCommitTransaction() + { + Assert.That(handler.StateUpdateCount, Is.EqualTo(0)); + + handler.BeginChange(); + Assert.That(handler.StateUpdateCount, Is.EqualTo(0)); + handler.EndChange(); + + Assert.That(handler.StateUpdateCount, Is.EqualTo(1)); + } + + [Test] + public void TestSaveOutsideOfTransactionTriggersUpdates() + { + Assert.That(handler.StateUpdateCount, Is.EqualTo(0)); + + handler.SaveState(); + Assert.That(handler.StateUpdateCount, Is.EqualTo(1)); + + handler.SaveState(); + Assert.That(handler.StateUpdateCount, Is.EqualTo(2)); + } + + [Test] + public void TestEventsFire() + { + int transactionBegan = 0; + int transactionEnded = 0; + int stateSaved = 0; + + handler.TransactionBegan += () => transactionBegan++; + handler.TransactionEnded += () => transactionEnded++; + handler.SaveStateTriggered += () => stateSaved++; + + handler.BeginChange(); + Assert.That(transactionBegan, Is.EqualTo(1)); + + handler.EndChange(); + Assert.That(transactionEnded, Is.EqualTo(1)); + + Assert.That(stateSaved, Is.EqualTo(0)); + handler.SaveState(); + Assert.That(stateSaved, Is.EqualTo(1)); + } + + [Test] + public void TestSaveDuringTransactionDoesntTriggerUpdate() + { + Assert.That(handler.StateUpdateCount, Is.EqualTo(0)); + + handler.BeginChange(); + + handler.SaveState(); + Assert.That(handler.StateUpdateCount, Is.EqualTo(0)); + + handler.EndChange(); + + Assert.That(handler.StateUpdateCount, Is.EqualTo(1)); + } + + [Test] + public void TestEndWithoutBeginThrows() + { + handler.BeginChange(); + handler.EndChange(); + Assert.That(() => handler.EndChange(), Throws.TypeOf()); + } + + private class TestHandler : TransactionalCommitComponent + { + public int StateUpdateCount { get; private set; } + + protected override void UpdateState() + { + StateUpdateCount++; + } + } + } +} From 38babf3de587e86d83655f2c056d690ca4ab9a44 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 8 Oct 2020 17:43:27 +0900 Subject: [PATCH 1578/1782] Update usages of ChangeHandler to EditorBeatmap where relevant --- .../Edit/TaikoSelectionHandler.cs | 8 ++++---- .../Edit/Compose/Components/SelectionHandler.cs | 14 ++++++-------- osu.Game/Screens/Edit/Editor.cs | 4 ++-- 3 files changed, 12 insertions(+), 14 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs b/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs index d5dd758e10..97998c0f76 100644 --- a/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs +++ b/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs @@ -54,7 +54,7 @@ namespace osu.Game.Rulesets.Taiko.Edit { var hits = SelectedHitObjects.OfType(); - ChangeHandler.BeginChange(); + EditorBeatmap.BeginChange(); foreach (var h in hits) { @@ -65,19 +65,19 @@ namespace osu.Game.Rulesets.Taiko.Edit } } - ChangeHandler.EndChange(); + EditorBeatmap.EndChange(); } public void SetRimState(bool state) { var hits = SelectedHitObjects.OfType(); - ChangeHandler.BeginChange(); + EditorBeatmap.BeginChange(); foreach (var h in hits) h.Type = state ? HitType.Rim : HitType.Centre; - ChangeHandler.EndChange(); + EditorBeatmap.EndChange(); } protected override IEnumerable GetContextMenuItemsForSelection(IEnumerable selection) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index 7808d7a5bc..6a180c439b 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -238,9 +238,7 @@ namespace osu.Game.Screens.Edit.Compose.Components private void deleteSelected() { - ChangeHandler?.BeginChange(); EditorBeatmap?.RemoveRange(selectedBlueprints.Select(b => b.HitObject)); - ChangeHandler?.EndChange(); } #endregion @@ -307,7 +305,7 @@ namespace osu.Game.Screens.Edit.Compose.Components /// The name of the hit sample. public void AddHitSample(string sampleName) { - ChangeHandler?.BeginChange(); + EditorBeatmap?.BeginChange(); foreach (var h in SelectedHitObjects) { @@ -318,7 +316,7 @@ namespace osu.Game.Screens.Edit.Compose.Components h.Samples.Add(new HitSampleInfo { Name = sampleName }); } - ChangeHandler?.EndChange(); + EditorBeatmap?.EndChange(); } /// @@ -328,7 +326,7 @@ namespace osu.Game.Screens.Edit.Compose.Components /// Throws if any selected object doesn't implement public void SetNewCombo(bool state) { - ChangeHandler?.BeginChange(); + EditorBeatmap?.BeginChange(); foreach (var h in SelectedHitObjects) { @@ -340,7 +338,7 @@ namespace osu.Game.Screens.Edit.Compose.Components EditorBeatmap?.UpdateHitObject(h); } - ChangeHandler?.EndChange(); + EditorBeatmap?.EndChange(); } /// @@ -349,12 +347,12 @@ namespace osu.Game.Screens.Edit.Compose.Components /// The name of the hit sample. public void RemoveHitSample(string sampleName) { - ChangeHandler?.BeginChange(); + EditorBeatmap?.BeginChange(); foreach (var h in SelectedHitObjects) h.SamplesBindable.RemoveAll(s => s.Name == sampleName); - ChangeHandler?.EndChange(); + EditorBeatmap?.EndChange(); } #endregion diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 3c5cbf30e9..74f324364a 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -509,14 +509,14 @@ namespace osu.Game.Screens.Edit foreach (var h in objects) h.StartTime += timeOffset; - changeHandler.BeginChange(); + editorBeatmap.BeginChange(); editorBeatmap.SelectedHitObjects.Clear(); editorBeatmap.AddRange(objects); editorBeatmap.SelectedHitObjects.AddRange(objects); - changeHandler.EndChange(); + editorBeatmap.EndChange(); } protected void Undo() => changeHandler.RestoreState(-1); From 1027b608ffb86df7c35afff6a9dd8a34124d4607 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 8 Oct 2020 17:52:49 +0900 Subject: [PATCH 1579/1782] Copy list content before firing events to avoid pollution --- osu.Game/Screens/Edit/EditorBeatmap.cs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Edit/EditorBeatmap.cs b/osu.Game/Screens/Edit/EditorBeatmap.cs index f776908a0b..098d05471f 100644 --- a/osu.Game/Screens/Edit/EditorBeatmap.cs +++ b/osu.Game/Screens/Edit/EditorBeatmap.cs @@ -237,13 +237,19 @@ namespace osu.Game.Screens.Edit beatmapProcessor?.PostProcess(); - foreach (var h in batchPendingDeletes) HitObjectRemoved?.Invoke(h); - foreach (var h in batchPendingInserts) HitObjectAdded?.Invoke(h); - foreach (var h in batchPendingUpdates) HitObjectUpdated?.Invoke(h); - + // callbacks may modify the lists so let's be safe about it + var deletes = batchPendingDeletes.ToArray(); batchPendingDeletes.Clear(); + + var inserts = batchPendingInserts.ToArray(); batchPendingInserts.Clear(); + + var updates = batchPendingUpdates.ToArray(); batchPendingUpdates.Clear(); + + foreach (var h in deletes) HitObjectRemoved?.Invoke(h); + foreach (var h in inserts) HitObjectAdded?.Invoke(h); + foreach (var h in updates) HitObjectUpdated?.Invoke(h); } /// From afed832b19e3e8dfe3d3bf94105e0b84d2073f76 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 8 Oct 2020 18:06:46 +0900 Subject: [PATCH 1580/1782] Tidy up EditorBeatmap slightly --- .../Sliders/SliderSelectionBlueprint.cs | 2 +- .../Edit/TaikoSelectionHandler.cs | 2 +- .../Compose/Components/BlueprintContainer.cs | 4 +-- .../Compose/Components/SelectionHandler.cs | 2 +- .../Timeline/TimelineHitObjectBlueprint.cs | 4 +-- osu.Game/Screens/Edit/EditorBeatmap.cs | 34 +++++++++---------- 6 files changed, 24 insertions(+), 24 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs index 94862eb205..f260c5a8fa 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs @@ -182,7 +182,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders private void updatePath() { HitObject.Path.ExpectedDistance.Value = composer?.GetSnappedDistanceFromDistance(HitObject.StartTime, (float)HitObject.Path.CalculatedDistance) ?? (float)HitObject.Path.CalculatedDistance; - editorBeatmap?.UpdateHitObject(HitObject); + editorBeatmap?.Update(HitObject); } public override MenuItem[] ContextMenuItems => new MenuItem[] diff --git a/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs b/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs index 97998c0f76..ee92936fc2 100644 --- a/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs +++ b/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs @@ -61,7 +61,7 @@ namespace osu.Game.Rulesets.Taiko.Edit if (h.IsStrong != state) { h.IsStrong = state; - EditorBeatmap.UpdateHitObject(h); + EditorBeatmap.Update(h); } } diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index addb970e8a..c7f87ae08e 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -203,7 +203,7 @@ namespace osu.Game.Screens.Edit.Compose.Components { // handle positional change etc. foreach (var obj in selectedHitObjects) - Beatmap.UpdateHitObject(obj); + Beatmap.Update(obj); changeHandler?.EndChange(); isDraggingBlueprint = false; @@ -440,7 +440,7 @@ namespace osu.Game.Screens.Edit.Compose.Components foreach (HitObject obj in SelectionHandler.SelectedHitObjects) { obj.StartTime += offset; - Beatmap.UpdateHitObject(obj); + Beatmap.Update(obj); } } diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index 6a180c439b..e8ab09df85 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -335,7 +335,7 @@ namespace osu.Game.Screens.Edit.Compose.Components if (comboInfo == null || comboInfo.NewCombo == state) continue; comboInfo.NewCombo = state; - EditorBeatmap?.UpdateHitObject(h); + EditorBeatmap?.Update(h); } EditorBeatmap?.EndChange(); diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs index 6c3bcfae32..975433d407 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs @@ -392,7 +392,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline return; repeatHitObject.RepeatCount = proposedCount; - beatmap.UpdateHitObject(hitObject); + beatmap.Update(hitObject); break; case IHasDuration endTimeHitObject: @@ -402,7 +402,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline return; endTimeHitObject.Duration = snappedTime - hitObject.StartTime; - beatmap.UpdateHitObject(hitObject); + beatmap.Update(hitObject); break; } } diff --git a/osu.Game/Screens/Edit/EditorBeatmap.cs b/osu.Game/Screens/Edit/EditorBeatmap.cs index 098d05471f..3278f44e06 100644 --- a/osu.Game/Screens/Edit/EditorBeatmap.cs +++ b/osu.Game/Screens/Edit/EditorBeatmap.cs @@ -88,6 +88,12 @@ namespace osu.Game.Screens.Edit private IList mutableHitObjects => (IList)PlayableBeatmap.HitObjects; + private readonly List batchPendingInserts = new List(); + + private readonly List batchPendingDeletes = new List(); + + private readonly HashSet batchPendingUpdates = new HashSet(); + /// /// Adds a collection of s to this . /// @@ -142,12 +148,21 @@ namespace osu.Game.Screens.Edit /// Updates a , invoking and re-processing the beatmap. /// /// The to update. - public void UpdateHitObject([NotNull] HitObject hitObject) + public void Update([NotNull] HitObject hitObject) { // updates are debounced regardless of whether a batch is active. batchPendingUpdates.Add(hitObject); } + /// + /// Update all hit objects with potentially changed difficulty or control point data. + /// + public void UpdateAllHitObjects() + { + foreach (var h in HitObjects) + batchPendingUpdates.Add(h); + } + /// /// Removes a from this . /// @@ -210,12 +225,6 @@ namespace osu.Game.Screens.Edit } } - private readonly List batchPendingInserts = new List(); - - private readonly List batchPendingDeletes = new List(); - - private readonly HashSet batchPendingUpdates = new HashSet(); - protected override void Update() { base.Update(); @@ -270,7 +279,7 @@ namespace osu.Game.Screens.Edit var insertionIndex = findInsertionIndex(PlayableBeatmap.HitObjects, hitObject.StartTime); mutableHitObjects.Insert(insertionIndex + 1, hitObject); - UpdateHitObject(hitObject); + Update(hitObject); }; } @@ -296,14 +305,5 @@ namespace osu.Game.Screens.Edit public double GetBeatLengthAtTime(double referenceTime) => ControlPointInfo.TimingPointAt(referenceTime).BeatLength / BeatDivisor; public int BeatDivisor => beatDivisor?.Value ?? 1; - - /// - /// Update all hit objects with potentially changed difficulty or control point data. - /// - public void UpdateAllHitObjects() - { - foreach (var h in HitObjects) - batchPendingUpdates.Add(h); - } } } From c9f069d7edd6add1e6625f0e28921acbd9ffa610 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 8 Oct 2020 18:17:57 +0900 Subject: [PATCH 1581/1782] Fix taiko's HitObjectComposer not allowing movement o f selected hitobjects --- osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs b/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs index d5dd758e10..ac14e6131a 100644 --- a/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs +++ b/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs @@ -89,6 +89,8 @@ namespace osu.Game.Rulesets.Taiko.Edit yield return new TernaryStateMenuItem("Strong") { State = { BindTarget = selectionStrongState } }; } + public override bool HandleMovement(MoveSelectionEvent moveEvent) => true; + protected override void UpdateTernaryStates() { base.UpdateTernaryStates(); From 0967db768ffb922e0d6e46fe71ada6bf94731533 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 8 Oct 2020 18:25:40 +0900 Subject: [PATCH 1582/1782] Add xmldoc covering usage restrictions --- osu.Game/OsuGame.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 772f9ff145..e6f6d526cf 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -98,7 +98,11 @@ namespace osu.Game /// /// Whether the local user is currently interacting with the game in a way that should not be interrupted. /// - public readonly Bindable LocalUserPlaying = new BindableBool(); + /// + /// This is exclusively managed by . If other components are mutating this state, a more + /// resilient method should be used to ensure correct state. + /// + public Bindable LocalUserPlaying = new BindableBool(); protected OsuScreenStack ScreenStack; From 43a575484ad1440a799aa41433840b47f5299f1e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 8 Oct 2020 18:25:43 +0900 Subject: [PATCH 1583/1782] Remove pointless comments --- 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 8830884a40..80dd8ae92c 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -666,7 +666,6 @@ namespace osu.Game.Screens.Play { screenSuspension?.Expire(); - // Ensure we reset the LocalUserPlaying state LocalUserPlaying.Value = false; fadeOut(); @@ -698,7 +697,6 @@ namespace osu.Game.Screens.Play musicController.ResetTrackAdjustments(); - // Ensure we reset the LocalUserPlaying state LocalUserPlaying.Value = false; fadeOut(); From dbdb25ccf756cf48ec3ecff87a81aec86f0f0224 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 8 Oct 2020 18:29:19 +0900 Subject: [PATCH 1584/1782] Move reset logic to OsuGame --- osu.Game/OsuGame.cs | 3 +++ osu.Game/Screens/Play/Player.cs | 4 ---- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index e6f6d526cf..d315b213ab 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -957,6 +957,9 @@ namespace osu.Game break; } + // reset on screen change for sanity. + LocalUserPlaying.Value = false; + if (current is IOsuScreen currentOsuScreen) OverlayActivationMode.UnbindFrom(currentOsuScreen.OverlayActivationMode); diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 80dd8ae92c..45f194fc29 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -666,8 +666,6 @@ namespace osu.Game.Screens.Play { screenSuspension?.Expire(); - LocalUserPlaying.Value = false; - fadeOut(); base.OnSuspending(next); } @@ -697,8 +695,6 @@ namespace osu.Game.Screens.Play musicController.ResetTrackAdjustments(); - LocalUserPlaying.Value = false; - fadeOut(); return base.OnExiting(next); } From 3114174e098a898bf09a9946b374fd99fc90ff48 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 8 Oct 2020 18:41:03 +0900 Subject: [PATCH 1585/1782] Add missing non-transactional SaveState calls --- osu.Game/Screens/Edit/EditorBeatmap.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Screens/Edit/EditorBeatmap.cs b/osu.Game/Screens/Edit/EditorBeatmap.cs index 3278f44e06..946c6905db 100644 --- a/osu.Game/Screens/Edit/EditorBeatmap.cs +++ b/osu.Game/Screens/Edit/EditorBeatmap.cs @@ -141,6 +141,7 @@ namespace osu.Game.Screens.Edit beatmapProcessor?.PostProcess(); HitObjectAdded?.Invoke(hitObject); + SaveState(); } } @@ -222,6 +223,7 @@ namespace osu.Game.Screens.Edit beatmapProcessor?.PostProcess(); HitObjectRemoved?.Invoke(hitObject); + SaveState(); } } From 4ccd751604fdc8ceb9561f66f0dd5b5d50d66db6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 8 Oct 2020 18:42:53 +0900 Subject: [PATCH 1586/1782] Further simplify non-transactional change logic --- osu.Game/Screens/Edit/EditorBeatmap.cs | 30 ++++++-------------------- 1 file changed, 6 insertions(+), 24 deletions(-) diff --git a/osu.Game/Screens/Edit/EditorBeatmap.cs b/osu.Game/Screens/Edit/EditorBeatmap.cs index 946c6905db..165d2ba278 100644 --- a/osu.Game/Screens/Edit/EditorBeatmap.cs +++ b/osu.Game/Screens/Edit/EditorBeatmap.cs @@ -131,18 +131,9 @@ namespace osu.Game.Screens.Edit mutableHitObjects.Insert(index, hitObject); - if (TransactionActive) - batchPendingInserts.Add(hitObject); - else - { - // must be run after any change to hitobject ordering - beatmapProcessor?.PreProcess(); - processHitObject(hitObject); - beatmapProcessor?.PostProcess(); - - HitObjectAdded?.Invoke(hitObject); - SaveState(); - } + BeginChange(); + batchPendingInserts.Add(hitObject); + EndChange(); } /// @@ -213,18 +204,9 @@ namespace osu.Game.Screens.Edit bindable.UnbindAll(); startTimeBindables.Remove(hitObject); - if (TransactionActive) - batchPendingDeletes.Add(hitObject); - else - { - // must be run after any change to hitobject ordering - beatmapProcessor?.PreProcess(); - processHitObject(hitObject); - beatmapProcessor?.PostProcess(); - - HitObjectRemoved?.Invoke(hitObject); - SaveState(); - } + BeginChange(); + batchPendingDeletes.Add(hitObject); + EndChange(); } protected override void Update() From 2d0275ba958e9de5fbb8c68635177026a86487e8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Oct 2020 03:07:01 +0900 Subject: [PATCH 1587/1782] Fix first hitobject in osu! hidden mod not getting correct fade applied --- osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs | 15 +++++++++++---- osu.Game/Rulesets/Mods/ModHidden.cs | 20 ++++++++++++++++++++ 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs index 80e40af717..d354a8a726 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs @@ -42,7 +42,11 @@ namespace osu.Game.Rulesets.Osu.Mods private double lastSliderHeadFadeOutStartTime; private double lastSliderHeadFadeOutDuration; - protected override void ApplyHiddenState(DrawableHitObject drawable, ArmedState state) + protected override void ApplyFirstObjectIncreaseVisibilityState(DrawableHitObject drawable, ArmedState state) => applyState(drawable, true); + + protected override void ApplyHiddenState(DrawableHitObject drawable, ArmedState state) => applyState(drawable, false); + + private void applyState(DrawableHitObject drawable, bool increaseVisibility) { if (!(drawable is DrawableOsuHitObject d)) return; @@ -86,9 +90,12 @@ namespace osu.Game.Rulesets.Osu.Mods lastSliderHeadFadeOutStartTime = fadeOutStartTime; } - // we don't want to see the approach circle - using (circle.BeginAbsoluteSequence(h.StartTime - h.TimePreempt, true)) - circle.ApproachCircle.Hide(); + if (!increaseVisibility) + { + // we don't want to see the approach circle + using (circle.BeginAbsoluteSequence(h.StartTime - h.TimePreempt, true)) + circle.ApproachCircle.Hide(); + } // fade out immediately after fade in. using (drawable.BeginAbsoluteSequence(fadeOutStartTime, true)) diff --git a/osu.Game/Rulesets/Mods/ModHidden.cs b/osu.Game/Rulesets/Mods/ModHidden.cs index a1915b974c..d81b439f6a 100644 --- a/osu.Game/Rulesets/Mods/ModHidden.cs +++ b/osu.Game/Rulesets/Mods/ModHidden.cs @@ -38,7 +38,13 @@ namespace osu.Game.Rulesets.Mods public virtual void ApplyToDrawableHitObjects(IEnumerable drawables) { if (IncreaseFirstObjectVisibility.Value) + { + var firstObject = drawables.FirstOrDefault(); + if (firstObject != null) + firstObject.ApplyCustomUpdateState += ApplyFirstObjectIncreaseVisibilityState; + drawables = drawables.SkipWhile(h => !IsFirstHideableObject(h)).Skip(1); + } foreach (var dho in drawables) dho.ApplyCustomUpdateState += ApplyHiddenState; @@ -65,6 +71,20 @@ namespace osu.Game.Rulesets.Mods } } + /// + /// Apply a special visibility state to the first object in a beatmap, if the user chooses to turn on the "increase first object visibility" setting. + /// + /// The hit object to apply the state change to. + /// The state of the hit object. + protected virtual void ApplyFirstObjectIncreaseVisibilityState(DrawableHitObject hitObject, ArmedState state) + { + } + + /// + /// Apply a hidden state to the provided object. + /// + /// The hit object to apply the state change to. + /// The state of the hit object. protected virtual void ApplyHiddenState(DrawableHitObject hitObject, ArmedState state) { } From e7eda19b0723b8edd16d68989434e340c8e9992d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Oct 2020 03:31:01 +0900 Subject: [PATCH 1588/1782] Reset new combo button state after successful placement --- .../Edit/Compose/Components/ComposeBlueprintContainer.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs index 9b3314e2ad..0336c74386 100644 --- a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs @@ -201,7 +201,12 @@ namespace osu.Game.Screens.Edit.Compose.Components protected override void AddBlueprintFor(HitObject hitObject) { refreshTool(); + base.AddBlueprintFor(hitObject); + + // on successful placement, the new combo button should be reset as this is the most common user interaction. + if (Beatmap.SelectedHitObjects.Count == 0) + NewCombo.Value = TernaryState.False; } private void createPlacement() From 5966205037867aefc6c0a4ed4ea5d0ecbe312b46 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Oct 2020 04:31:33 +0900 Subject: [PATCH 1589/1782] Fix ternary button states not updating correctly after a paste operation --- osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index 7808d7a5bc..c5e88ade84 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -37,7 +37,7 @@ namespace osu.Game.Screens.Edit.Compose.Components public int SelectedCount => selectedBlueprints.Count; - public IEnumerable SelectedHitObjects => selectedBlueprints.Select(b => b.HitObject); + public IEnumerable SelectedHitObjects => EditorBeatmap.SelectedHitObjects; private Drawable content; From a5b2c4195efb48684a69a44655a1165c1168563b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Oct 2020 04:41:45 +0900 Subject: [PATCH 1590/1782] Fix incorrect timing distribution display due to lack of rounding --- .../Ranking/TestSceneHitEventTimingDistributionGraph.cs | 6 ++++++ .../Ranking/Statistics/HitEventTimingDistributionGraph.cs | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneHitEventTimingDistributionGraph.cs b/osu.Game.Tests/Visual/Ranking/TestSceneHitEventTimingDistributionGraph.cs index 144f8da2fa..9059fe34af 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneHitEventTimingDistributionGraph.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneHitEventTimingDistributionGraph.cs @@ -23,6 +23,12 @@ namespace osu.Game.Tests.Visual.Ranking createTest(CreateDistributedHitEvents()); } + [Test] + public void TestAroundCentre() + { + createTest(Enumerable.Range(-100, 100).Select(i => new HitEvent(i / 50f, HitResult.Perfect, new HitCircle(), new HitCircle(), null)).ToList()); + } + [Test] public void TestZeroTimeOffset() { diff --git a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs index aa2a83774e..980fc68788 100644 --- a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs +++ b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs @@ -66,7 +66,7 @@ namespace osu.Game.Screens.Ranking.Statistics foreach (var e in hitEvents) { - int binOffset = (int)(e.TimeOffset / binSize); + int binOffset = (int)Math.Round(e.TimeOffset / binSize); bins[timing_distribution_centre_bin_index + binOffset]++; } From ff5a1937f5f17cba7eadd82d3157a9499f6e06ad Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Oct 2020 05:04:03 +0900 Subject: [PATCH 1591/1782] Fix test logic and stabilise rounding direction --- .../Visual/Ranking/TestSceneHitEventTimingDistributionGraph.cs | 2 +- .../Ranking/Statistics/HitEventTimingDistributionGraph.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneHitEventTimingDistributionGraph.cs b/osu.Game.Tests/Visual/Ranking/TestSceneHitEventTimingDistributionGraph.cs index 9059fe34af..4bc843096f 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneHitEventTimingDistributionGraph.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneHitEventTimingDistributionGraph.cs @@ -26,7 +26,7 @@ namespace osu.Game.Tests.Visual.Ranking [Test] public void TestAroundCentre() { - createTest(Enumerable.Range(-100, 100).Select(i => new HitEvent(i / 50f, HitResult.Perfect, new HitCircle(), new HitCircle(), null)).ToList()); + createTest(Enumerable.Range(-150, 300).Select(i => new HitEvent(i / 50f, HitResult.Perfect, new HitCircle(), new HitCircle(), null)).ToList()); } [Test] diff --git a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs index 980fc68788..93885b6e02 100644 --- a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs +++ b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs @@ -66,7 +66,7 @@ namespace osu.Game.Screens.Ranking.Statistics foreach (var e in hitEvents) { - int binOffset = (int)Math.Round(e.TimeOffset / binSize); + int binOffset = (int)Math.Round(e.TimeOffset / binSize, MidpointRounding.AwayFromZero); bins[timing_distribution_centre_bin_index + binOffset]++; } From 85b33fffd028c2a5153c038a13f116c9f19c7e0a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Oct 2020 05:14:44 +0900 Subject: [PATCH 1592/1782] Fix incorrect comments --- .../Screens/Edit/Compose/Components/SelectionHandler.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index 7808d7a5bc..8a80ddd17c 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -141,7 +141,7 @@ namespace osu.Game.Screens.Edit.Compose.Components /// Handles the selected s being rotated. /// /// The delta angle to apply to the selection. - /// Whether any s could be moved. + /// Whether any s could be rotated. public virtual bool HandleRotation(float angle) => false; /// @@ -149,14 +149,14 @@ namespace osu.Game.Screens.Edit.Compose.Components /// /// The delta scale to apply, in playfield local coordinates. /// The point of reference where the scale is originating from. - /// Whether any s could be moved. + /// Whether any s could be scaled. public virtual bool HandleScale(Vector2 scale, Anchor anchor) => false; /// - /// Handled the selected s being flipped. + /// Handles the selected s being flipped. /// /// The direction to flip - /// Whether any s could be moved. + /// Whether any s could be flipped. public virtual bool HandleFlip(Direction direction) => false; public bool OnPressed(PlatformAction action) From eacc7dca9a0d3815e7a4055f50b77c92505be911 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Oct 2020 06:31:59 +0900 Subject: [PATCH 1593/1782] Fix SliderPath not handling Clear correctly --- osu.Game/Rulesets/Objects/SliderPath.cs | 1 + .../Screens/Edit/Compose/Components/SelectionHandler.cs | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/osu.Game/Rulesets/Objects/SliderPath.cs b/osu.Game/Rulesets/Objects/SliderPath.cs index d577e8fdda..3083fcfccb 100644 --- a/osu.Game/Rulesets/Objects/SliderPath.cs +++ b/osu.Game/Rulesets/Objects/SliderPath.cs @@ -57,6 +57,7 @@ namespace osu.Game.Rulesets.Objects c.Changed += invalidate; break; + case NotifyCollectionChangedAction.Reset: case NotifyCollectionChangedAction.Remove: foreach (var c in args.OldItems.Cast()) c.Changed -= invalidate; diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index 8a80ddd17c..3fe0ab4396 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -159,6 +159,12 @@ namespace osu.Game.Screens.Edit.Compose.Components /// Whether any s could be flipped. public virtual bool HandleFlip(Direction direction) => false; + /// + /// Handles the selected s being reversed pattern-wise. + /// + /// Whether any s could be reversed. + public virtual bool HandleReverse() => false; + public bool OnPressed(PlatformAction action) { switch (action.ActionMethod) From 825e10ec8c350bae42e546c86370fada762473d1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Oct 2020 06:32:23 +0900 Subject: [PATCH 1594/1782] Add reverse handler button to selection box --- .../Edit/Compose/Components/SelectionBox.cs | 19 +++++++++++++++++++ .../Compose/Components/SelectionHandler.cs | 1 + 2 files changed, 20 insertions(+) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs index 64191e48e2..b753c45cca 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs @@ -17,10 +17,28 @@ namespace osu.Game.Screens.Edit.Compose.Components public Action OnRotation; public Action OnScale; public Action OnFlip; + public Action OnReverse; public Action OperationStarted; public Action OperationEnded; + private bool canReverse; + + /// + /// Whether pattern reversing support should be enabled. + /// + public bool CanReverse + { + get => canReverse; + set + { + if (canReverse == value) return; + + canReverse = value; + recreate(); + } + } + private bool canRotate; /// @@ -125,6 +143,7 @@ namespace osu.Game.Screens.Edit.Compose.Components if (CanScaleX && CanScaleY) addFullScaleComponents(); if (CanScaleY) addYScaleComponents(); if (CanRotate) addRotationComponents(); + if (CanReverse) addButton(FontAwesome.Solid.Backward, "Reverse pattern", () => OnReverse?.Invoke()); } private void addRotationComponents() diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index 3fe0ab4396..b74095455c 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -103,6 +103,7 @@ namespace osu.Game.Screens.Edit.Compose.Components OnRotation = angle => HandleRotation(angle), OnScale = (amount, anchor) => HandleScale(amount, anchor), OnFlip = direction => HandleFlip(direction), + OnReverse = () => HandleReverse(), }; /// From 2a790c76d5f9c668a842bbc72b0ddf54c076d28a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Oct 2020 06:32:33 +0900 Subject: [PATCH 1595/1782] Add reverse implementation for osu! --- .../Edit/OsuSelectionHandler.cs | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs index 7ae0730e39..762c4a04e7 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs @@ -7,6 +7,7 @@ using System.Linq; using osu.Framework.Graphics; using osu.Framework.Graphics.Primitives; using osu.Framework.Utils; +using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Screens.Edit.Compose.Components; @@ -25,6 +26,7 @@ namespace osu.Game.Rulesets.Osu.Edit SelectionBox.CanRotate = canOperate; SelectionBox.CanScaleX = canOperate; SelectionBox.CanScaleY = canOperate; + SelectionBox.CanReverse = canOperate; } protected override void OnOperationEnded() @@ -41,6 +43,54 @@ namespace osu.Game.Rulesets.Osu.Edit /// private Vector2? referenceOrigin; + public override bool HandleReverse() + { + var hitObjects = selectedMovableObjects; + + double endTime = hitObjects.Max(h => h.GetEndTime()); + double startTime = hitObjects.Min(h => h.StartTime); + + bool moreThanOneObject = hitObjects.Length > 1; + + foreach (var h in hitObjects) + { + if (moreThanOneObject) + h.StartTime = endTime - (h.GetEndTime() - startTime); + + if (h is Slider slider) + { + var points = slider.Path.ControlPoints.ToArray(); + Vector2 endPos = points.Last().Position.Value; + + slider.Path.ControlPoints.Clear(); + + slider.Position += endPos; + + PathType? lastType = null; + + for (var i = 0; i < points.Length; i++) + { + var p = points[i]; + p.Position.Value -= endPos; + + // propagate types forwards to last null type + if (i == points.Length - 1) + p.Type.Value = lastType; + else if (p.Type.Value != null) + { + var newType = p.Type.Value; + p.Type.Value = lastType; + lastType = newType; + } + + slider.Path.ControlPoints.Insert(0, p); + } + } + } + + return true; + } + public override bool HandleFlip(Direction direction) { var hitObjects = selectedMovableObjects; From 6649cb220431e7b3807fc8ae2d98c40e4f9d3811 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Oct 2020 06:41:53 +0900 Subject: [PATCH 1596/1782] Fix incorrect first object logic --- osu.Game/Rulesets/Mods/ModHidden.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Mods/ModHidden.cs b/osu.Game/Rulesets/Mods/ModHidden.cs index d81b439f6a..ad01bf036c 100644 --- a/osu.Game/Rulesets/Mods/ModHidden.cs +++ b/osu.Game/Rulesets/Mods/ModHidden.cs @@ -39,11 +39,13 @@ namespace osu.Game.Rulesets.Mods { if (IncreaseFirstObjectVisibility.Value) { + drawables = drawables.SkipWhile(h => !IsFirstHideableObject(h)); + var firstObject = drawables.FirstOrDefault(); if (firstObject != null) firstObject.ApplyCustomUpdateState += ApplyFirstObjectIncreaseVisibilityState; - drawables = drawables.SkipWhile(h => !IsFirstHideableObject(h)).Skip(1); + drawables = drawables.Skip(1); } foreach (var dho in drawables) From c86b37f60d0f1c036ff4bdd2964ce7532d71bfe0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Oct 2020 13:11:24 +0900 Subject: [PATCH 1597/1782] Add check to ensure MusicController doesn't play a delete pending beatmap's track --- 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 0764f34697..12caf98021 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -150,7 +150,7 @@ namespace osu.Game.Overlays { if (IsUserPaused) return; - if (CurrentTrack.IsDummyDevice) + if (CurrentTrack.IsDummyDevice || beatmap.Value.BeatmapSetInfo.DeletePending) { if (beatmap.Disabled) return; From 68039cff4089bf84e233a3c99ef1f24ffd40836d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Oct 2020 13:11:44 +0900 Subject: [PATCH 1598/1782] Set beatmap to sane default on exiting editor --- osu.Game/Screens/Edit/Editor.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 3c5cbf30e9..d6dbfef2bd 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -469,10 +469,17 @@ namespace osu.Game.Screens.Edit private void confirmExit() { + // stop the track if playing to allow the parent screen to choose a suitable playback mode. + Beatmap.Value.Track.Stop(); + if (isNewBeatmap) { // confirming exit without save means we should delete the new beatmap completely. beatmapManager.Delete(playableBeatmap.BeatmapInfo.BeatmapSet); + + // in theory this shouldn't be required but due to EF core not sharing instance states 100% + // MusicController is unaware of the changed DeletePending state. + Beatmap.SetDefault(); } exitConfirmed = true; From 389ffe7da5b799e1b7e800e07d2ccce894e7cbb8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Oct 2020 13:23:18 +0900 Subject: [PATCH 1599/1782] Hide bonus result types from score table for the time being --- osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs index 231d888a4e..324299ccba 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs @@ -110,6 +110,11 @@ namespace osu.Game.Overlays.BeatmapSet.Scores if (!allScoreStatistics.Contains(result)) continue; + // for the time being ignore bonus result types. + // this is not being sent from the API and will be empty in all cases. + if (result.IsBonus()) + continue; + string displayName = ruleset.GetDisplayNameForHitResult(result); columns.Add(new TableColumn(displayName, Anchor.CentreLeft, new Dimension(GridSizeMode.Distributed, minSize: 35, maxSize: 60))); From 8be19fd82059e79f9141027b9e4e3cda086e32fe Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Oct 2020 13:26:09 +0900 Subject: [PATCH 1600/1782] Increase height of contracted score panel to fit mods again --- osu.Game/Screens/Ranking/ScorePanel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Ranking/ScorePanel.cs b/osu.Game/Screens/Ranking/ScorePanel.cs index 8c8a547277..ee97ee55eb 100644 --- a/osu.Game/Screens/Ranking/ScorePanel.cs +++ b/osu.Game/Screens/Ranking/ScorePanel.cs @@ -29,7 +29,7 @@ namespace osu.Game.Screens.Ranking /// /// Height of the panel when contracted. /// - private const float contracted_height = 355; + private const float contracted_height = 385; /// /// Width of the panel when expanded. From beec0e41930ee69aafe15c7303101cca09f3dcaa Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Oct 2020 14:03:13 +0900 Subject: [PATCH 1601/1782] Hide children of SelectionBlueprint when not selected --- osu.Game/Rulesets/Edit/SelectionBlueprint.cs | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Edit/SelectionBlueprint.cs b/osu.Game/Rulesets/Edit/SelectionBlueprint.cs index 71256093d5..4abdbfc244 100644 --- a/osu.Game/Rulesets/Edit/SelectionBlueprint.cs +++ b/osu.Game/Rulesets/Edit/SelectionBlueprint.cs @@ -89,9 +89,23 @@ namespace osu.Game.Rulesets.Edit } } - protected virtual void OnDeselected() => Hide(); + protected virtual void OnDeselected() + { + // selection blueprints are AlwaysPresent while the related DrawableHitObject is visible + // set the body piece's alpha directly to avoid arbitrarily rendering frame buffers etc. of children. + foreach (var d in InternalChildren) + d.Hide(); - protected virtual void OnSelected() => Show(); + Hide(); + } + + protected virtual void OnSelected() + { + foreach (var d in InternalChildren) + d.Show(); + + Show(); + } // When not selected, input is only required for the blueprint itself to receive IsHovering protected override bool ShouldBeConsideredForInput(Drawable child) => State == SelectionState.Selected; From 34d1439f8ef598cf155b2ef8fa21a003b009d112 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Oct 2020 14:04:26 +0900 Subject: [PATCH 1602/1782] Only update slider selection blueprints paths when visible --- .../Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs | 5 ++++- 1 file changed, 4 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 94862eb205..8fe4b8a9cf 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs @@ -66,13 +66,16 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders pathVersion = HitObject.Path.Version.GetBoundCopy(); pathVersion.BindValueChanged(_ => updatePath()); + + BodyPiece.UpdateFrom(HitObject); } protected override void Update() { base.Update(); - BodyPiece.UpdateFrom(HitObject); + if (IsSelected) + BodyPiece.UpdateFrom(HitObject); } private Vector2 rightClickPosition; From 6b9e94ae93370a686aa62f7efeca2aaa0f9b721b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Oct 2020 14:05:00 +0900 Subject: [PATCH 1603/1782] Avoid retaining slider selection blueprints FBO backing textures after deselection --- .../Sliders/Components/SliderBodyPiece.cs | 2 + .../Sliders/SliderSelectionBlueprint.cs | 39 ++++++++++++++----- 2 files changed, 32 insertions(+), 9 deletions(-) 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 9349ef7a18..5581ce4bfd 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderBodyPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderBodyPiece.cs @@ -49,6 +49,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components OriginPosition = body.PathOffset; } + public void RecyclePath() => body.RecyclePath(); + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => body.ReceivePositionalInputAt(screenSpacePos); } } diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs index 8fe4b8a9cf..67f8088dbb 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs @@ -24,10 +24,12 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders { public class SliderSelectionBlueprint : OsuSelectionBlueprint { - protected readonly SliderBodyPiece BodyPiece; - protected readonly SliderCircleSelectionBlueprint HeadBlueprint; - protected readonly SliderCircleSelectionBlueprint TailBlueprint; - protected readonly PathControlPointVisualiser ControlPointVisualiser; + protected SliderBodyPiece BodyPiece; + protected SliderCircleSelectionBlueprint HeadBlueprint; + protected SliderCircleSelectionBlueprint TailBlueprint; + protected PathControlPointVisualiser ControlPointVisualiser; + + private readonly DrawableSlider slider; [Resolved(CanBeNull = true)] private HitObjectComposer composer { get; set; } @@ -44,17 +46,17 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders public SliderSelectionBlueprint(DrawableSlider slider) : base(slider) { - var sliderObject = (Slider)slider.HitObject; + this.slider = slider; + } + [BackgroundDependencyLoader] + private void load() + { InternalChildren = new Drawable[] { BodyPiece = new SliderBodyPiece(), HeadBlueprint = CreateCircleSelectionBlueprint(slider, SliderPosition.Start), TailBlueprint = CreateCircleSelectionBlueprint(slider, SliderPosition.End), - ControlPointVisualiser = new PathControlPointVisualiser(sliderObject, true) - { - RemoveControlPointsRequested = removeControlPoints - } }; } @@ -78,6 +80,25 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders BodyPiece.UpdateFrom(HitObject); } + protected override void OnSelected() + { + AddInternal(ControlPointVisualiser = new PathControlPointVisualiser((Slider)slider.HitObject, true) + { + RemoveControlPointsRequested = removeControlPoints + }); + + base.OnSelected(); + } + + protected override void OnDeselected() + { + base.OnDeselected(); + + // throw away frame buffers on deselection. + ControlPointVisualiser?.Expire(); + BodyPiece.RecyclePath(); + } + private Vector2 rightClickPosition; protected override bool OnMouseDown(MouseDownEvent e) From 9baf704942288a18b60011c5ab9dbee80ca14633 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Oct 2020 15:38:58 +0900 Subject: [PATCH 1604/1782] Add local pooling to TimelineTickDisplay --- .../Visualisations/PointVisualisation.cs | 13 ++- .../Timeline/TimelineTickDisplay.cs | 102 ++++++++++++------ 2 files changed, 78 insertions(+), 37 deletions(-) diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Visualisations/PointVisualisation.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Visualisations/PointVisualisation.cs index 1ac960039e..ea093e6a4e 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Visualisations/PointVisualisation.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Visualisations/PointVisualisation.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 osuTK; using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; +using osuTK; namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Visualisations { @@ -13,15 +13,20 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Visualisations public class PointVisualisation : Box { public PointVisualisation(double startTime) + : this() + { + X = (float)startTime; + } + + public PointVisualisation() { Origin = Anchor.TopCentre; + RelativePositionAxes = Axes.X; RelativeSizeAxes = Axes.Y; + Width = 1; EdgeSmoothness = new Vector2(1, 0); - - RelativePositionAxes = Axes.X; - X = (float)startTime; } } } diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs index 36ee976bf7..745f3e393b 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.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; @@ -12,7 +13,7 @@ using osu.Game.Screens.Edit.Components.Timelines.Summary.Visualisations; namespace osu.Game.Screens.Edit.Compose.Components.Timeline { - public class TimelineTickDisplay : TimelinePart + public class TimelineTickDisplay : TimelinePart { [Resolved] private EditorBeatmap beatmap { get; set; } @@ -31,15 +32,23 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline RelativeSizeAxes = Axes.Both; } - [BackgroundDependencyLoader] - private void load() - { - beatDivisor.BindValueChanged(_ => createLines(), true); - } + [Resolved(canBeNull: true)] + private Timeline timeline { get; set; } - private void createLines() + protected override void Update() { - Clear(); + base.Update(); + + int drawableIndex = 0; + + double minVisibleTime = double.MinValue; + double maxVisibleTime = double.MaxValue; + + if (timeline != null) + { + minVisibleTime = ToLocalSpace(timeline.ScreenSpaceDrawQuad.TopLeft).X / DrawWidth * Content.RelativeChildSize.X; + maxVisibleTime = ToLocalSpace(timeline.ScreenSpaceDrawQuad.TopRight).X / DrawWidth * Content.RelativeChildSize.X; + } for (var i = 0; i < beatmap.ControlPointInfo.TimingPoints.Count; i++) { @@ -50,41 +59,68 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline for (double t = point.Time; t < until; t += point.BeatLength / beatDivisor.Value) { - var indexInBeat = beat % beatDivisor.Value; - - if (indexInBeat == 0) + if (t >= minVisibleTime && t <= maxVisibleTime) { - Add(new PointVisualisation(t) - { - Colour = BindableBeatDivisor.GetColourFor(1, colours), - Origin = Anchor.TopCentre, - }); - } - else - { - var divisor = BindableBeatDivisor.GetDivisorForBeatIndex(beat, beatDivisor.Value); - var colour = BindableBeatDivisor.GetColourFor(divisor, colours); - var height = 0.1f - (float)divisor / BindableBeatDivisor.VALID_DIVISORS.Last() * 0.08f; + var indexInBeat = beat % beatDivisor.Value; - Add(new PointVisualisation(t) + if (indexInBeat == 0) { - Colour = colour, - Height = height, - Origin = Anchor.TopCentre, - }); + var downbeatPoint = getNextUsablePoint(); + downbeatPoint.X = (float)t; - Add(new PointVisualisation(t) + downbeatPoint.Colour = BindableBeatDivisor.GetColourFor(1, colours); + downbeatPoint.Anchor = Anchor.TopLeft; + downbeatPoint.Origin = Anchor.TopCentre; + downbeatPoint.Height = 1; + } + else { - Colour = colour, - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomCentre, - Height = height, - }); + var divisor = BindableBeatDivisor.GetDivisorForBeatIndex(beat, beatDivisor.Value); + var colour = BindableBeatDivisor.GetColourFor(divisor, colours); + var height = 0.1f - (float)divisor / BindableBeatDivisor.VALID_DIVISORS.Last() * 0.08f; + + var topPoint = getNextUsablePoint(); + topPoint.X = (float)t; + topPoint.Colour = colour; + topPoint.Height = height; + topPoint.Anchor = Anchor.TopLeft; + topPoint.Origin = Anchor.TopCentre; + + var bottomPoint = getNextUsablePoint(); + bottomPoint.X = (float)t; + bottomPoint.Colour = colour; + bottomPoint.Anchor = Anchor.BottomLeft; + bottomPoint.Origin = Anchor.BottomCentre; + bottomPoint.Height = height; + } } beat++; } } + + int usedDrawables = drawableIndex; + + // save a few drawables beyond the currently used for edge cases. + while (drawableIndex < Math.Min(usedDrawables + 16, Count)) + Children[drawableIndex++].Hide(); + + // expire any excess + while (drawableIndex < Count) + Children[drawableIndex++].Expire(); + + Drawable getNextUsablePoint() + { + PointVisualisation point; + if (drawableIndex >= Count) + Add(point = new PointVisualisation()); + else + point = Children[drawableIndex++]; + + point.Show(); + + return point; + } } } } From 017a8ce496f74e19bf726ac9ab7a5fdf139aa679 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Oct 2020 15:57:31 +0900 Subject: [PATCH 1605/1782] Only recalculate when display actually changes --- .../Timeline/TimelineTickDisplay.cs | 38 +++++++++++++++---- 1 file changed, 30 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs index 745f3e393b..76428d6fbc 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs @@ -5,6 +5,7 @@ using System; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Caching; using osu.Framework.Graphics; using osu.Game.Beatmaps; using osu.Game.Graphics; @@ -32,6 +33,16 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline RelativeSizeAxes = Axes.Both; } + private readonly Cached tickCache = new Cached(); + + [BackgroundDependencyLoader] + private void load() + { + beatDivisor.BindValueChanged(_ => tickCache.Invalidate()); + } + + private (float min, float max) visibleRange = (float.MinValue, float.MaxValue); + [Resolved(canBeNull: true)] private Timeline timeline { get; set; } @@ -39,17 +50,26 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { base.Update(); - int drawableIndex = 0; - - double minVisibleTime = double.MinValue; - double maxVisibleTime = double.MaxValue; - if (timeline != null) { - minVisibleTime = ToLocalSpace(timeline.ScreenSpaceDrawQuad.TopLeft).X / DrawWidth * Content.RelativeChildSize.X; - maxVisibleTime = ToLocalSpace(timeline.ScreenSpaceDrawQuad.TopRight).X / DrawWidth * Content.RelativeChildSize.X; + var newRange = ( + ToLocalSpace(timeline.ScreenSpaceDrawQuad.TopLeft).X / DrawWidth * Content.RelativeChildSize.X, + ToLocalSpace(timeline.ScreenSpaceDrawQuad.TopRight).X / DrawWidth * Content.RelativeChildSize.X); + + if (visibleRange != newRange) + tickCache.Invalidate(); + + visibleRange = newRange; } + if (!tickCache.IsValid) + createTicks(); + } + + private void createTicks() + { + int drawableIndex = 0; + for (var i = 0; i < beatmap.ControlPointInfo.TimingPoints.Count; i++) { var point = beatmap.ControlPointInfo.TimingPoints[i]; @@ -59,7 +79,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline for (double t = point.Time; t < until; t += point.BeatLength / beatDivisor.Value) { - if (t >= minVisibleTime && t <= maxVisibleTime) + if (t >= visibleRange.min && t <= visibleRange.max) { var indexInBeat = beat % beatDivisor.Value; @@ -109,6 +129,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline while (drawableIndex < Count) Children[drawableIndex++].Expire(); + tickCache.Validate(); + Drawable getNextUsablePoint() { PointVisualisation point; From 955836916b340c3400efac07e4e385fbb6b4b744 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Oct 2020 16:45:11 +0900 Subject: [PATCH 1606/1782] Fix timeline tick display test making two instances of the component --- osu.Game.Tests/Visual/Editing/TestSceneTimelineTickDisplay.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneTimelineTickDisplay.cs b/osu.Game.Tests/Visual/Editing/TestSceneTimelineTickDisplay.cs index e33040acdc..20e58c3d2a 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneTimelineTickDisplay.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneTimelineTickDisplay.cs @@ -5,7 +5,6 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Game.Screens.Edit.Compose.Components; -using osu.Game.Screens.Edit.Compose.Components.Timeline; using osuTK; namespace osu.Game.Tests.Visual.Editing @@ -13,7 +12,7 @@ namespace osu.Game.Tests.Visual.Editing [TestFixture] public class TestSceneTimelineTickDisplay : TimelineTestScene { - public override Drawable CreateTestComponent() => new TimelineTickDisplay(); + public override Drawable CreateTestComponent() => Empty(); // tick display is implicitly inside the timeline. [BackgroundDependencyLoader] private void load() From ceb1494c33285a0219f58111e3e10b1183509c55 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Oct 2020 16:46:54 +0900 Subject: [PATCH 1607/1782] Only run regeneration when passing a new min/max tick boundary --- .../Timeline/TimelineTickDisplay.cs | 83 ++++++++++++------- 1 file changed, 52 insertions(+), 31 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs index 76428d6fbc..c6e435b6ae 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs @@ -41,8 +41,21 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline beatDivisor.BindValueChanged(_ => tickCache.Invalidate()); } + /// + /// The visible time/position range of the timeline. + /// private (float min, float max) visibleRange = (float.MinValue, float.MaxValue); + /// + /// The next time/position value to the left of the display when tick regeneration needs to be run. + /// + private float? nextMinTick; + + /// + /// The next time/position value to the right of the display when tick regeneration needs to be run. + /// + private float? nextMaxTick; + [Resolved(canBeNull: true)] private Timeline timeline { get; set; } @@ -57,9 +70,13 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline ToLocalSpace(timeline.ScreenSpaceDrawQuad.TopRight).X / DrawWidth * Content.RelativeChildSize.X); if (visibleRange != newRange) - tickCache.Invalidate(); + { + visibleRange = newRange; - visibleRange = newRange; + // actual regeneration only needs to occur if we've passed one of the known next min/max tick boundaries. + if (nextMinTick == null || nextMaxTick == null || (visibleRange.min < nextMinTick || visibleRange.max > nextMaxTick)) + tickCache.Invalidate(); + } } if (!tickCache.IsValid) @@ -69,6 +86,10 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private void createTicks() { int drawableIndex = 0; + int highestDivisor = BindableBeatDivisor.VALID_DIVISORS.Last(); + + nextMinTick = null; + nextMaxTick = null; for (var i = 0; i < beatmap.ControlPointInfo.TimingPoints.Count; i++) { @@ -79,40 +100,39 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline for (double t = point.Time; t < until; t += point.BeatLength / beatDivisor.Value) { - if (t >= visibleRange.min && t <= visibleRange.max) + float xPos = (float)t; + + if (t < visibleRange.min) + nextMinTick = xPos; + else if (t > visibleRange.max) + nextMaxTick ??= xPos; + else { + // if this is the first beat in the beatmap, there is no next min tick + if (beat == 0 && i == 0) + nextMinTick = float.MinValue; + var indexInBeat = beat % beatDivisor.Value; - if (indexInBeat == 0) - { - var downbeatPoint = getNextUsablePoint(); - downbeatPoint.X = (float)t; + var divisor = BindableBeatDivisor.GetDivisorForBeatIndex(beat, beatDivisor.Value); + var colour = BindableBeatDivisor.GetColourFor(divisor, colours); - downbeatPoint.Colour = BindableBeatDivisor.GetColourFor(1, colours); - downbeatPoint.Anchor = Anchor.TopLeft; - downbeatPoint.Origin = Anchor.TopCentre; - downbeatPoint.Height = 1; - } - else - { - var divisor = BindableBeatDivisor.GetDivisorForBeatIndex(beat, beatDivisor.Value); - var colour = BindableBeatDivisor.GetColourFor(divisor, colours); - var height = 0.1f - (float)divisor / BindableBeatDivisor.VALID_DIVISORS.Last() * 0.08f; + // even though "bar lines" take up the full vertical space, we render them in two pieces because it allows for less anchor/origin churn. + var height = indexInBeat == 0 ? 0.5f : 0.1f - (float)divisor / highestDivisor * 0.08f; - var topPoint = getNextUsablePoint(); - topPoint.X = (float)t; - topPoint.Colour = colour; - topPoint.Height = height; - topPoint.Anchor = Anchor.TopLeft; - topPoint.Origin = Anchor.TopCentre; + var topPoint = getNextUsablePoint(); + topPoint.X = xPos; + topPoint.Colour = colour; + topPoint.Height = height; + topPoint.Anchor = Anchor.TopLeft; + topPoint.Origin = Anchor.TopCentre; - var bottomPoint = getNextUsablePoint(); - bottomPoint.X = (float)t; - bottomPoint.Colour = colour; - bottomPoint.Anchor = Anchor.BottomLeft; - bottomPoint.Origin = Anchor.BottomCentre; - bottomPoint.Height = height; - } + var bottomPoint = getNextUsablePoint(); + bottomPoint.X = xPos; + bottomPoint.Colour = colour; + bottomPoint.Anchor = Anchor.BottomLeft; + bottomPoint.Origin = Anchor.BottomCentre; + bottomPoint.Height = height; } beat++; @@ -137,8 +157,9 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline if (drawableIndex >= Count) Add(point = new PointVisualisation()); else - point = Children[drawableIndex++]; + point = Children[drawableIndex]; + drawableIndex++; point.Show(); return point; From 5d888f687ae809c1086ebce68033978475a16c56 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Oct 2020 16:49:51 +0900 Subject: [PATCH 1608/1782] Account for the width of points so they don't suddenly appear at timeline edges --- .../Timelines/Summary/Visualisations/PointVisualisation.cs | 6 ++++-- .../Edit/Compose/Components/Timeline/TimelineTickDisplay.cs | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Visualisations/PointVisualisation.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Visualisations/PointVisualisation.cs index ea093e6a4e..b0ecffdd24 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Visualisations/PointVisualisation.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Visualisations/PointVisualisation.cs @@ -12,6 +12,8 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Visualisations /// public class PointVisualisation : Box { + public const float WIDTH = 1; + public PointVisualisation(double startTime) : this() { @@ -25,8 +27,8 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Visualisations RelativePositionAxes = Axes.X; RelativeSizeAxes = Axes.Y; - Width = 1; - EdgeSmoothness = new Vector2(1, 0); + Width = WIDTH; + EdgeSmoothness = new Vector2(WIDTH, 0); } } } diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs index c6e435b6ae..ce73a2b50b 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs @@ -66,8 +66,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline if (timeline != null) { var newRange = ( - ToLocalSpace(timeline.ScreenSpaceDrawQuad.TopLeft).X / DrawWidth * Content.RelativeChildSize.X, - ToLocalSpace(timeline.ScreenSpaceDrawQuad.TopRight).X / DrawWidth * Content.RelativeChildSize.X); + (ToLocalSpace(timeline.ScreenSpaceDrawQuad.TopLeft).X - PointVisualisation.WIDTH * 2) / DrawWidth * Content.RelativeChildSize.X, + (ToLocalSpace(timeline.ScreenSpaceDrawQuad.TopRight).X + PointVisualisation.WIDTH * 2) / DrawWidth * Content.RelativeChildSize.X); if (visibleRange != newRange) { From a0af2eb6c880fc727d15fcfdb662914a89d32d37 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Oct 2020 16:54:43 +0900 Subject: [PATCH 1609/1782] Private protect setters --- .../Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs | 8 ++++---- 1 file changed, 4 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 67f8088dbb..9cfb02ab20 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs @@ -24,10 +24,10 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders { public class SliderSelectionBlueprint : OsuSelectionBlueprint { - protected SliderBodyPiece BodyPiece; - protected SliderCircleSelectionBlueprint HeadBlueprint; - protected SliderCircleSelectionBlueprint TailBlueprint; - protected PathControlPointVisualiser ControlPointVisualiser; + protected SliderBodyPiece BodyPiece { get; private set; } + protected SliderCircleSelectionBlueprint HeadBlueprint { get; private set; } + protected SliderCircleSelectionBlueprint TailBlueprint { get; private set; } + protected PathControlPointVisualiser ControlPointVisualiser { get; private set; } private readonly DrawableSlider slider; From 144726e3c691aa2c0259cdae6e03f7d05759c8e5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Oct 2020 17:12:01 +0900 Subject: [PATCH 1610/1782] Better guard against taiko swells becoming strong --- .../Beatmaps/TaikoBeatmapConverter.cs | 2 +- osu.Game.Rulesets.Taiko/Objects/Swell.cs | 2 ++ osu.Game.Rulesets.Taiko/Objects/TaikoHitObject.cs | 14 +++++++++++++- 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs index ed7b8589ba..607eaf5dbd 100644 --- a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs +++ b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs @@ -65,7 +65,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps converted.HitObjects = converted.HitObjects.GroupBy(t => t.StartTime).Select(x => { TaikoHitObject first = x.First(); - if (x.Skip(1).Any() && !(first is Swell)) + if (x.Skip(1).Any() && first.CanBeStrong) first.IsStrong = true; return first; }).ToList(); diff --git a/osu.Game.Rulesets.Taiko/Objects/Swell.cs b/osu.Game.Rulesets.Taiko/Objects/Swell.cs index eeae6e79f8..bf8b7bc178 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Swell.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Swell.cs @@ -17,6 +17,8 @@ namespace osu.Game.Rulesets.Taiko.Objects set => Duration = value - StartTime; } + public override bool CanBeStrong => false; + public double Duration { get; set; } /// diff --git a/osu.Game.Rulesets.Taiko/Objects/TaikoHitObject.cs b/osu.Game.Rulesets.Taiko/Objects/TaikoHitObject.cs index 2922010001..d2c37d965c 100644 --- a/osu.Game.Rulesets.Taiko/Objects/TaikoHitObject.cs +++ b/osu.Game.Rulesets.Taiko/Objects/TaikoHitObject.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.Threading; using osu.Framework.Bindables; using osu.Game.Rulesets.Judgements; @@ -30,6 +31,11 @@ namespace osu.Game.Rulesets.Taiko.Objects public readonly Bindable IsStrongBindable = new BindableBool(); + /// + /// Whether this can be made a "strong" (large) hit. + /// + public virtual bool CanBeStrong => true; + /// /// Whether this HitObject is a "strong" type. /// Strong hit objects give more points for hitting the hit object with both keys. @@ -37,7 +43,13 @@ namespace osu.Game.Rulesets.Taiko.Objects public bool IsStrong { get => IsStrongBindable.Value; - set => IsStrongBindable.Value = value; + set + { + if (value && !CanBeStrong) + throw new InvalidOperationException($"Object of type {GetType()} cannot be strong"); + + IsStrongBindable.Value = value; + } } protected override void CreateNestedHitObjects(CancellationToken cancellationToken) From cb96a40dd6eb389668ff2cfec630f47ec027029a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Oct 2020 17:12:10 +0900 Subject: [PATCH 1611/1782] Fix bindable propagation potentially making swells strong --- .../Objects/Drawables/DrawableTaikoHitObject.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs index 9cd23383c4..d8d75a7614 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs @@ -158,7 +158,8 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables { base.LoadSamples(); - isStrong.Value = getStrongSamples().Any(); + if (HitObject.CanBeStrong) + isStrong.Value = getStrongSamples().Any(); } private void updateSamplesFromStrong() From 21c6242f9064b8a8ac51af371640df29eb876718 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Oct 2020 18:35:44 +0900 Subject: [PATCH 1612/1782] Fix bar lines ("down beat" as people call it) showing up too often in timeline --- .../Edit/Compose/Components/Timeline/TimelineTickDisplay.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs index ce73a2b50b..724256af8b 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs @@ -112,13 +112,13 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline if (beat == 0 && i == 0) nextMinTick = float.MinValue; - var indexInBeat = beat % beatDivisor.Value; + var indexInBar = beat % ((int)point.TimeSignature * beatDivisor.Value); var divisor = BindableBeatDivisor.GetDivisorForBeatIndex(beat, beatDivisor.Value); var colour = BindableBeatDivisor.GetColourFor(divisor, colours); // even though "bar lines" take up the full vertical space, we render them in two pieces because it allows for less anchor/origin churn. - var height = indexInBeat == 0 ? 0.5f : 0.1f - (float)divisor / highestDivisor * 0.08f; + var height = indexInBar == 0 ? 0.5f : 0.1f - (float)divisor / highestDivisor * 0.08f; var topPoint = getNextUsablePoint(); topPoint.X = xPos; From febfe9cdd049e6bef918074c6406411f1db6884d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Oct 2020 18:43:16 +0900 Subject: [PATCH 1613/1782] Don't fade the approach circle on increased visibility --- osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs index d354a8a726..f69cacd432 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs @@ -90,7 +90,14 @@ namespace osu.Game.Rulesets.Osu.Mods lastSliderHeadFadeOutStartTime = fadeOutStartTime; } - if (!increaseVisibility) + Drawable fadeTarget = circle; + + if (increaseVisibility) + { + // only fade the circle piece (not the approach circle) for the increased visibility object. + fadeTarget = circle.CirclePiece; + } + else { // we don't want to see the approach circle using (circle.BeginAbsoluteSequence(h.StartTime - h.TimePreempt, true)) @@ -99,8 +106,7 @@ namespace osu.Game.Rulesets.Osu.Mods // fade out immediately after fade in. using (drawable.BeginAbsoluteSequence(fadeOutStartTime, true)) - circle.FadeOut(fadeOutDuration); - + fadeTarget.FadeOut(fadeOutDuration); break; case DrawableSlider slider: From edaf6db5c6890a8475dd31b4dbfab45d3ddd9fa6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Oct 2020 18:44:23 +0900 Subject: [PATCH 1614/1782] Reference EditorBeatmap directly for selected objects --- .../Edit/Compose/Components/SelectionHandler.cs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index c5e88ade84..8902e8119d 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -37,8 +37,6 @@ namespace osu.Game.Screens.Edit.Compose.Components public int SelectedCount => selectedBlueprints.Count; - public IEnumerable SelectedHitObjects => EditorBeatmap.SelectedHitObjects; - private Drawable content; private OsuSpriteText selectionDetailsText; @@ -309,7 +307,7 @@ namespace osu.Game.Screens.Edit.Compose.Components { ChangeHandler?.BeginChange(); - foreach (var h in SelectedHitObjects) + foreach (var h in EditorBeatmap.SelectedHitObjects) { // Make sure there isn't already an existing sample if (h.Samples.Any(s => s.Name == sampleName)) @@ -330,7 +328,7 @@ namespace osu.Game.Screens.Edit.Compose.Components { ChangeHandler?.BeginChange(); - foreach (var h in SelectedHitObjects) + foreach (var h in EditorBeatmap.SelectedHitObjects) { var comboInfo = h as IHasComboInformation; @@ -351,7 +349,7 @@ namespace osu.Game.Screens.Edit.Compose.Components { ChangeHandler?.BeginChange(); - foreach (var h in SelectedHitObjects) + foreach (var h in EditorBeatmap.SelectedHitObjects) h.SamplesBindable.RemoveAll(s => s.Name == sampleName); ChangeHandler?.EndChange(); @@ -425,11 +423,11 @@ namespace osu.Game.Screens.Edit.Compose.Components /// protected virtual void UpdateTernaryStates() { - SelectionNewComboState.Value = GetStateFromSelection(SelectedHitObjects.OfType(), h => h.NewCombo); + SelectionNewComboState.Value = GetStateFromSelection(EditorBeatmap.SelectedHitObjects.OfType(), h => h.NewCombo); foreach (var (sampleName, bindable) in SelectionSampleStates) { - bindable.Value = GetStateFromSelection(SelectedHitObjects, h => h.Samples.Any(s => s.Name == sampleName)); + bindable.Value = GetStateFromSelection(EditorBeatmap.SelectedHitObjects, h => h.Samples.Any(s => s.Name == sampleName)); } } From 3838f405dd6ac1d00ddfad5c86c7d1619982c517 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Oct 2020 18:50:05 +0900 Subject: [PATCH 1615/1782] Fix missed usages --- .../Edit/ManiaSelectionHandler.cs | 4 ++-- osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs | 12 ++++++------ .../Edit/TaikoSelectionHandler.cs | 8 ++++---- .../Edit/Compose/Components/BlueprintContainer.cs | 2 +- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs b/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs index 65f40d7d0a..50629f41a9 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs @@ -45,7 +45,7 @@ namespace osu.Game.Rulesets.Mania.Edit int minColumn = int.MaxValue; int maxColumn = int.MinValue; - foreach (var obj in SelectedHitObjects.OfType()) + foreach (var obj in EditorBeatmap.SelectedHitObjects.OfType()) { if (obj.Column < minColumn) minColumn = obj.Column; @@ -55,7 +55,7 @@ namespace osu.Game.Rulesets.Mania.Edit columnDelta = Math.Clamp(columnDelta, -minColumn, maniaPlayfield.TotalColumns - 1 - maxColumn); - foreach (var obj in SelectedHitObjects.OfType()) + foreach (var obj in EditorBeatmap.SelectedHitObjects.OfType()) obj.Column += columnDelta; } } diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs index 7ae0730e39..68c4869a45 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs @@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Osu.Edit { base.OnSelectionChanged(); - bool canOperate = SelectedHitObjects.Count() > 1 || SelectedHitObjects.Any(s => s is Slider); + bool canOperate = EditorBeatmap.SelectedHitObjects.Count > 1 || EditorBeatmap.SelectedHitObjects.Any(s => s is Slider); SelectionBox.CanRotate = canOperate; SelectionBox.CanScaleX = canOperate; @@ -182,7 +182,7 @@ namespace osu.Game.Rulesets.Osu.Edit /// The points to calculate a quad for. private Quad getSurroundingQuad(IEnumerable points) { - if (!SelectedHitObjects.Any()) + if (!EditorBeatmap.SelectedHitObjects.Any()) return new Quad(); Vector2 minPosition = new Vector2(float.MaxValue, float.MaxValue); @@ -203,10 +203,10 @@ namespace osu.Game.Rulesets.Osu.Edit /// /// All osu! hitobjects which can be moved/rotated/scaled. /// - private OsuHitObject[] selectedMovableObjects => SelectedHitObjects - .OfType() - .Where(h => !(h is Spinner)) - .ToArray(); + private OsuHitObject[] selectedMovableObjects => EditorBeatmap.SelectedHitObjects + .OfType() + .Where(h => !(h is Spinner)) + .ToArray(); /// /// Rotate a point around an arbitrary origin. diff --git a/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs b/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs index d5dd758e10..6e940be54d 100644 --- a/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs +++ b/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs @@ -52,7 +52,7 @@ namespace osu.Game.Rulesets.Taiko.Edit public void SetStrongState(bool state) { - var hits = SelectedHitObjects.OfType(); + var hits = EditorBeatmap.SelectedHitObjects.OfType(); ChangeHandler.BeginChange(); @@ -70,7 +70,7 @@ namespace osu.Game.Rulesets.Taiko.Edit public void SetRimState(bool state) { - var hits = SelectedHitObjects.OfType(); + var hits = EditorBeatmap.SelectedHitObjects.OfType(); ChangeHandler.BeginChange(); @@ -93,8 +93,8 @@ namespace osu.Game.Rulesets.Taiko.Edit { base.UpdateTernaryStates(); - selectionRimState.Value = GetStateFromSelection(SelectedHitObjects.OfType(), h => h.Type == HitType.Rim); - selectionStrongState.Value = GetStateFromSelection(SelectedHitObjects.OfType(), h => h.IsStrong); + selectionRimState.Value = GetStateFromSelection(EditorBeatmap.SelectedHitObjects.OfType(), h => h.Type == HitType.Rim); + selectionStrongState.Value = GetStateFromSelection(EditorBeatmap.SelectedHitObjects.OfType(), h => h.IsStrong); } } } diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index 970e16d1c3..1eff716b3d 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -436,7 +436,7 @@ namespace osu.Game.Screens.Edit.Compose.Components { // Apply the start time at the newly snapped-to position double offset = result.Time.Value - draggedObject.StartTime; - foreach (HitObject obj in SelectionHandler.SelectedHitObjects) + foreach (HitObject obj in Beatmap.SelectedHitObjects) obj.StartTime += offset; } From 021777145fd279b0a2b80c9ba7e8cc9992b7729b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Oct 2020 18:51:52 +0900 Subject: [PATCH 1616/1782] 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 d7817cf4cf..3df894fbcc 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,6 +52,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index fa2135580d..8b10f0a7f7 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -24,7 +24,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 20a51e5feb..88abbca73d 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -80,7 +80,7 @@ - + From e618b62ccd2367a67b06b414bf47d487f9d71fca Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Oct 2020 19:02:53 +0900 Subject: [PATCH 1617/1782] Update waveform tests --- osu.Game.Tests/Visual/Editing/TestSceneWaveform.cs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneWaveform.cs b/osu.Game.Tests/Visual/Editing/TestSceneWaveform.cs index 0c1296b82c..c3a5a0e944 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneWaveform.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneWaveform.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; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; @@ -76,7 +77,7 @@ namespace osu.Game.Tests.Visual.Editing }; }); - AddUntilStep("wait for load", () => graph.ResampledWaveform != null); + AddUntilStep("wait for load", () => graph.Loaded.IsSet); } [Test] @@ -98,12 +99,18 @@ namespace osu.Game.Tests.Visual.Editing }; }); - AddUntilStep("wait for load", () => graph.ResampledWaveform != null); + AddUntilStep("wait for load", () => graph.Loaded.IsSet); } public class TestWaveformGraph : WaveformGraph { - public new Waveform ResampledWaveform => base.ResampledWaveform; + public readonly ManualResetEventSlim Loaded = new ManualResetEventSlim(); + + protected override void OnWaveformRegenerated(Waveform waveform) + { + base.OnWaveformRegenerated(waveform); + Loaded.Set(); + } } } } From 573336cb47d2549a670a90c8ce1200411f89e376 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 9 Oct 2020 20:12:17 +0900 Subject: [PATCH 1618/1782] Ensure stable sorting order in beatmap conversion tests --- .../ManiaBeatmapConversionTest.cs | 18 +++++++++++++++++- .../Tests/Beatmaps/BeatmapConversionTest.cs | 13 +++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs b/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs index 0c57267970..3d4bc4748b 100644 --- a/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs +++ b/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs @@ -83,11 +83,17 @@ namespace osu.Game.Rulesets.Mania.Tests RandomZ = snapshot.RandomZ; } + public override void PostProcess() + { + base.PostProcess(); + Objects.Sort(); + } + public bool Equals(ManiaConvertMapping other) => other != null && RandomW == other.RandomW && RandomX == other.RandomX && RandomY == other.RandomY && RandomZ == other.RandomZ; public override bool Equals(ConvertMapping other) => base.Equals(other) && Equals(other as ManiaConvertMapping); } - public struct ConvertValue : IEquatable + public struct ConvertValue : IEquatable, IComparable { /// /// A sane value to account for osu!stable using ints everwhere. @@ -102,5 +108,15 @@ namespace osu.Game.Rulesets.Mania.Tests => Precision.AlmostEquals(StartTime, other.StartTime, conversion_lenience) && Precision.AlmostEquals(EndTime, other.EndTime, conversion_lenience) && Column == other.Column; + + public int CompareTo(ConvertValue other) + { + var result = StartTime.CompareTo(other.StartTime); + + if (result != 0) + return result; + + return Column.CompareTo(other.Column); + } } } diff --git a/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs b/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs index e492069c5e..fcf20a2eb2 100644 --- a/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs +++ b/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs @@ -34,6 +34,12 @@ namespace osu.Game.Tests.Beatmaps var ourResult = convert(name, mods.Select(m => (Mod)Activator.CreateInstance(m)).ToArray()); var expectedResult = read(name); + foreach (var m in ourResult.Mappings) + m.PostProcess(); + + foreach (var m in expectedResult.Mappings) + m.PostProcess(); + Assert.Multiple(() => { int mappingCounter = 0; @@ -239,6 +245,13 @@ namespace osu.Game.Tests.Beatmaps set => Objects = value; } + /// + /// Invoked after this is populated to post-process the contained data. + /// + public virtual void PostProcess() + { + } + public virtual bool Equals(ConvertMapping other) => StartTime == other?.StartTime; } } From 696e3d53afdc3754c7d8a2565d022520664d1c4c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 9 Oct 2020 20:50:09 +0900 Subject: [PATCH 1619/1782] Fix slider samples being overwritten by the last node --- osu.Game.Rulesets.Catch/Objects/JuiceStream.cs | 6 ++++-- osu.Game.Rulesets.Osu/Objects/Slider.cs | 11 ++++++----- .../Rulesets/Objects/Legacy/ConvertHitObjectParser.cs | 3 --- osu.Game/Rulesets/Objects/Types/IHasRepeats.cs | 10 ++++++++++ 4 files changed, 20 insertions(+), 10 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs index 6b8b70ed54..e209d012fa 100644 --- a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs +++ b/osu.Game.Rulesets.Catch/Objects/JuiceStream.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 System.Threading; @@ -56,6 +57,7 @@ namespace osu.Game.Rulesets.Catch.Objects Volume = s.Volume }).ToList(); + int nodeIndex = 0; SliderEventDescriptor? lastEvent = null; foreach (var e in SliderEventGenerator.Generate(StartTime, SpanDuration, Velocity, TickDistance, Path.Distance, this.SpanCount(), LegacyLastTickOffset, cancellationToken)) @@ -105,7 +107,7 @@ namespace osu.Game.Rulesets.Catch.Objects case SliderEventType.Repeat: AddNested(new Fruit { - Samples = Samples, + Samples = this.GetNodeSamples(nodeIndex++), StartTime = e.Time, X = X + Path.PositionAt(e.PathProgress).X, }); @@ -119,7 +121,7 @@ namespace osu.Game.Rulesets.Catch.Objects public double Duration { get => this.SpanCount() * Path.Distance / Velocity; - set => throw new System.NotSupportedException($"Adjust via {nameof(RepeatCount)} instead"); // can be implemented if/when needed. + set => throw new NotSupportedException($"Adjust via {nameof(RepeatCount)} instead"); // can be implemented if/when needed. } public double EndTime => StartTime + Duration; diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index 917382eccf..755ce0866a 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -137,6 +137,10 @@ namespace osu.Game.Rulesets.Osu.Objects Velocity = scoringDistance / timingPoint.BeatLength; TickDistance = scoringDistance / difficulty.SliderTickRate * TickDistanceMultiplier; + + // The samples should be attached to the slider tail, however this can only be done after LegacyLastTick is removed otherwise they would play earlier than they're intended to. + // For now, the samples are attached to and played by the slider itself at the correct end time. + Samples = this.GetNodeSamples(repeatCount + 1); } protected override void CreateNestedHitObjects(CancellationToken cancellationToken) @@ -230,15 +234,12 @@ namespace osu.Game.Rulesets.Osu.Objects tick.Samples = sampleList; foreach (var repeat in NestedHitObjects.OfType()) - repeat.Samples = getNodeSamples(repeat.RepeatIndex + 1); + repeat.Samples = this.GetNodeSamples(repeat.RepeatIndex + 1); if (HeadCircle != null) - HeadCircle.Samples = getNodeSamples(0); + HeadCircle.Samples = this.GetNodeSamples(0); } - private IList getNodeSamples(int nodeIndex) => - nodeIndex < NodeSamples.Count ? NodeSamples[nodeIndex] : Samples; - public override Judgement CreateJudgement() => new OsuIgnoreJudgement(); protected override HitWindows CreateHitWindows() => HitWindows.Empty; diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs index 9afc0ecaf4..f6adeced96 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs @@ -184,9 +184,6 @@ namespace osu.Game.Rulesets.Objects.Legacy nodeSamples.Add(convertSoundType(nodeSoundTypes[i], nodeBankInfos[i])); 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[^1]; } else if (type.HasFlag(LegacyHitObjectType.Spinner)) { diff --git a/osu.Game/Rulesets/Objects/Types/IHasRepeats.cs b/osu.Game/Rulesets/Objects/Types/IHasRepeats.cs index 7a3fb16196..674e2aee88 100644 --- a/osu.Game/Rulesets/Objects/Types/IHasRepeats.cs +++ b/osu.Game/Rulesets/Objects/Types/IHasRepeats.cs @@ -35,5 +35,15 @@ namespace osu.Game.Rulesets.Objects.Types /// /// The object that has repeats. public static int SpanCount(this IHasRepeats obj) => obj.RepeatCount + 1; + + /// + /// Retrieves the samples at a particular node in a object. + /// + /// The . + /// The node to attempt to retrieve the samples at. + /// The samples at the given node index, or 's default samples if the given node doesn't exist. + public static IList GetNodeSamples(this T obj, int nodeIndex) + where T : HitObject, IHasRepeats + => nodeIndex < obj.NodeSamples.Count ? obj.NodeSamples[nodeIndex] : obj.Samples; } } From d536a1f75e71a8075334b941aff91b9ab6c737c7 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 9 Oct 2020 21:04:56 +0900 Subject: [PATCH 1620/1782] Fix breaks being culled too early --- osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs | 7 +------ osu.Game/Rulesets/Mods/ModFlashlight.cs | 3 +++ 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index b30ec0ca2c..6dadbbd2da 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -307,12 +307,7 @@ namespace osu.Game.Beatmaps.Formats double start = getOffsetTime(Parsing.ParseDouble(split[1])); double end = Math.Max(start, getOffsetTime(Parsing.ParseDouble(split[2]))); - var breakEvent = new BreakPeriod(start, end); - - if (!breakEvent.HasEffect) - return; - - beatmap.Breaks.Add(breakEvent); + beatmap.Breaks.Add(new BreakPeriod(start, end)); break; } } diff --git a/osu.Game/Rulesets/Mods/ModFlashlight.cs b/osu.Game/Rulesets/Mods/ModFlashlight.cs index 6e94a84e7d..08f2ccb75c 100644 --- a/osu.Game/Rulesets/Mods/ModFlashlight.cs +++ b/osu.Game/Rulesets/Mods/ModFlashlight.cs @@ -107,6 +107,9 @@ namespace osu.Game.Rulesets.Mods { foreach (var breakPeriod in Breaks) { + if (!breakPeriod.HasEffect) + continue; + if (breakPeriod.Duration < FLASHLIGHT_FADE_DURATION * 2) continue; this.Delay(breakPeriod.StartTime + FLASHLIGHT_FADE_DURATION).FadeOutFromOne(FLASHLIGHT_FADE_DURATION); From 4d0e4f4adeb0fb1c5c7ac0312b054dd4312d21fb Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 9 Oct 2020 21:11:12 +0900 Subject: [PATCH 1621/1782] Fix incorrect initial density --- osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs index 524ea27efa..c0fbd47899 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs @@ -116,7 +116,8 @@ namespace osu.Game.Rulesets.Mania.Beatmaps prevNoteTimes.RemoveAt(0); prevNoteTimes.Add(newNoteTime); - density = (prevNoteTimes[^1] - prevNoteTimes[0]) / prevNoteTimes.Count; + if (prevNoteTimes.Count >= 2) + density = (prevNoteTimes[^1] - prevNoteTimes[0]) / prevNoteTimes.Count; } private double lastTime; From 9d09503ace3762d213bc15a664c5f02c7d9c984c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 9 Oct 2020 21:12:38 +0900 Subject: [PATCH 1622/1782] Fix spinner conversion not considering stacking + forced initial column --- .../Beatmaps/ManiaBeatmapConverter.cs | 2 +- .../Legacy/EndTimeObjectPatternGenerator.cs | 26 ++++++++++++++----- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs index c0fbd47899..b17ab3f375 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs @@ -181,7 +181,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps case IHasDuration endTimeData: { - conversion = new EndTimeObjectPatternGenerator(Random, original, beatmap, originalBeatmap); + conversion = new EndTimeObjectPatternGenerator(Random, original, beatmap, lastPattern, originalBeatmap); recordNote(endTimeData.EndTime, new Vector2(256, 192)); computeDensity(endTimeData.EndTime); diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs index d5286a3779..f816a70ab3 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs @@ -14,12 +14,17 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy { internal class EndTimeObjectPatternGenerator : PatternGenerator { - private readonly double endTime; + private readonly int endTime; + private readonly PatternType convertType; - public EndTimeObjectPatternGenerator(FastRandom random, HitObject hitObject, ManiaBeatmap beatmap, IBeatmap originalBeatmap) - : base(random, hitObject, beatmap, new Pattern(), originalBeatmap) + public EndTimeObjectPatternGenerator(FastRandom random, HitObject hitObject, ManiaBeatmap beatmap, Pattern previousPattern, IBeatmap originalBeatmap) + : base(random, hitObject, beatmap, previousPattern, originalBeatmap) { - endTime = (HitObject as IHasDuration)?.EndTime ?? 0; + endTime = (int)((HitObject as IHasDuration)?.EndTime ?? 0); + + convertType = PreviousPattern.ColumnWithObjects == TotalColumns + ? PatternType.None + : PatternType.ForceNotStack; } public override IEnumerable Generate() @@ -40,18 +45,25 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy break; case 8: - addToPattern(pattern, FindAvailableColumn(GetRandomColumn(), PreviousPattern), generateHold); + addToPattern(pattern, getRandomColumn(), generateHold); break; default: - if (TotalColumns > 0) - addToPattern(pattern, GetRandomColumn(), generateHold); + addToPattern(pattern, getRandomColumn(0), generateHold); break; } return pattern; } + private int getRandomColumn(int? lowerBound = null) + { + if ((convertType & PatternType.ForceNotStack) > 0) + return FindAvailableColumn(GetRandomColumn(lowerBound), lowerBound, patterns: PreviousPattern); + + return FindAvailableColumn(GetRandomColumn(lowerBound), lowerBound); + } + /// /// Constructs and adds a note to a pattern. /// From 5f19081db69efe63a0f96a52771d3cefbffb661e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 9 Oct 2020 21:20:00 +0900 Subject: [PATCH 1623/1782] Fix incorrect probability calculation for hitobject conversion --- .../Legacy/HitObjectPatternGenerator.cs | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs index 84f950997d..bc4ab55767 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs @@ -397,7 +397,11 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy case 4: centreProbability = 0; - p2 = Math.Min(p2 * 2, 0.2); + + // Stable requires rngValue > x, which is an inverse-probability. Lazer uses true probability (1 - x). + // But multiplying this value by 2 (stable) is not the same operation as dividing it by 2 (lazer), + // so it needs to be converted to from a probability and then back after the multiplication. + p2 = 1 - Math.Max((1 - p2) * 2, 0.8); p3 = 0; break; @@ -408,11 +412,20 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy case 6: centreProbability = 0; - p2 = Math.Min(p2 * 2, 0.5); - p3 = Math.Min(p3 * 2, 0.15); + + // Stable requires rngValue > x, which is an inverse-probability. Lazer uses true probability (1 - x). + // But multiplying this value by 2 (stable) is not the same operation as dividing it by 2 (lazer), + // so it needs to be converted to from a probability and then back after the multiplication. + p2 = 1 - Math.Max((1 - p2) * 2, 0.5); + p3 = 1 - Math.Max((1 - p3) * 2, 0.85); break; } + // The stable values were allowed to exceed 1, which indicate <0% probability. + // These values needs to be clamped otherwise GetRandomNoteCount() will throw an exception. + p2 = Math.Clamp(p2, 0, 1); + p3 = Math.Clamp(p3, 0, 1); + double centreVal = Random.NextDouble(); int noteCount = GetRandomNoteCount(p2, p3); From 08f3481b592c74a2151632661759c9e0380a2d65 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 9 Oct 2020 21:22:13 +0900 Subject: [PATCH 1624/1782] Use integer calculations to replicate stable's slider conversion --- .../Legacy/DistanceObjectPatternGenerator.cs | 101 ++++++++++-------- 1 file changed, 55 insertions(+), 46 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs index fe146c5324..415201951b 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs @@ -3,8 +3,8 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; -using osu.Framework.Utils; using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Rulesets.Mania.MathUtils; @@ -12,6 +12,7 @@ using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Beatmaps.Formats; namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy { @@ -25,8 +26,9 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy /// private const float osu_base_scoring_distance = 100; - public readonly double EndTime; - public readonly double SegmentDuration; + public readonly int StartTime; + public readonly int EndTime; + public readonly int SegmentDuration; public readonly int SpanCount; private PatternType convertType; @@ -41,20 +43,25 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy var distanceData = hitObject as IHasDistance; var repeatsData = hitObject as IHasRepeats; - SpanCount = repeatsData?.SpanCount() ?? 1; + Debug.Assert(distanceData != null); TimingControlPoint timingPoint = beatmap.ControlPointInfo.TimingPointAt(hitObject.StartTime); DifficultyControlPoint difficultyPoint = beatmap.ControlPointInfo.DifficultyPointAt(hitObject.StartTime); - // The true distance, accounting for any repeats - double distance = (distanceData?.Distance ?? 0) * SpanCount; - // The velocity of the osu! hit object - calculated as the velocity of a slider - double osuVelocity = osu_base_scoring_distance * beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier * difficultyPoint.SpeedMultiplier / timingPoint.BeatLength; - // The duration of the osu! hit object - double osuDuration = distance / osuVelocity; + double beatLength; +#pragma warning disable 618 + if (difficultyPoint is LegacyBeatmapDecoder.LegacyDifficultyControlPoint legacyDifficultyPoint) +#pragma warning restore 618 + beatLength = timingPoint.BeatLength * legacyDifficultyPoint.BpmMultiplier; + else + beatLength = timingPoint.BeatLength / difficultyPoint.SpeedMultiplier; - EndTime = hitObject.StartTime + osuDuration; - SegmentDuration = (EndTime - HitObject.StartTime) / SpanCount; + SpanCount = repeatsData?.SpanCount() ?? 1; + + StartTime = (int)Math.Round(hitObject.StartTime); + EndTime = (int)Math.Floor(StartTime + distanceData.Distance * beatLength * SpanCount * 0.01 / beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier); + + SegmentDuration = (EndTime - StartTime) / SpanCount; } public override IEnumerable Generate() @@ -76,7 +83,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy foreach (var obj in originalPattern.HitObjects) { - if (!Precision.AlmostEquals(EndTime, obj.GetEndTime())) + if (EndTime != (int)Math.Round(obj.GetEndTime())) intermediatePattern.Add(obj); else endTimePattern.Add(obj); @@ -91,35 +98,35 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy if (TotalColumns == 1) { var pattern = new Pattern(); - addToPattern(pattern, 0, HitObject.StartTime, EndTime); + addToPattern(pattern, 0, StartTime, EndTime); return pattern; } if (SpanCount > 1) { if (SegmentDuration <= 90) - return generateRandomHoldNotes(HitObject.StartTime, 1); + return generateRandomHoldNotes(StartTime, 1); if (SegmentDuration <= 120) { convertType |= PatternType.ForceNotStack; - return generateRandomNotes(HitObject.StartTime, SpanCount + 1); + return generateRandomNotes(StartTime, SpanCount + 1); } if (SegmentDuration <= 160) - return generateStair(HitObject.StartTime); + return generateStair(StartTime); if (SegmentDuration <= 200 && ConversionDifficulty > 3) - return generateRandomMultipleNotes(HitObject.StartTime); + return generateRandomMultipleNotes(StartTime); - double duration = EndTime - HitObject.StartTime; + double duration = EndTime - StartTime; if (duration >= 4000) - return generateNRandomNotes(HitObject.StartTime, 0.23, 0, 0); + return generateNRandomNotes(StartTime, 0.23, 0, 0); if (SegmentDuration > 400 && SpanCount < TotalColumns - 1 - RandomStart) - return generateTiledHoldNotes(HitObject.StartTime); + return generateTiledHoldNotes(StartTime); - return generateHoldAndNormalNotes(HitObject.StartTime); + return generateHoldAndNormalNotes(StartTime); } if (SegmentDuration <= 110) @@ -128,37 +135,37 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy convertType |= PatternType.ForceNotStack; else convertType &= ~PatternType.ForceNotStack; - return generateRandomNotes(HitObject.StartTime, SegmentDuration < 80 ? 1 : 2); + return generateRandomNotes(StartTime, SegmentDuration < 80 ? 1 : 2); } if (ConversionDifficulty > 6.5) { if (convertType.HasFlag(PatternType.LowProbability)) - return generateNRandomNotes(HitObject.StartTime, 0.78, 0.3, 0); + return generateNRandomNotes(StartTime, 0.78, 0.3, 0); - return generateNRandomNotes(HitObject.StartTime, 0.85, 0.36, 0.03); + return generateNRandomNotes(StartTime, 0.85, 0.36, 0.03); } if (ConversionDifficulty > 4) { if (convertType.HasFlag(PatternType.LowProbability)) - return generateNRandomNotes(HitObject.StartTime, 0.43, 0.08, 0); + return generateNRandomNotes(StartTime, 0.43, 0.08, 0); - return generateNRandomNotes(HitObject.StartTime, 0.56, 0.18, 0); + return generateNRandomNotes(StartTime, 0.56, 0.18, 0); } if (ConversionDifficulty > 2.5) { if (convertType.HasFlag(PatternType.LowProbability)) - return generateNRandomNotes(HitObject.StartTime, 0.3, 0, 0); + return generateNRandomNotes(StartTime, 0.3, 0, 0); - return generateNRandomNotes(HitObject.StartTime, 0.37, 0.08, 0); + return generateNRandomNotes(StartTime, 0.37, 0.08, 0); } if (convertType.HasFlag(PatternType.LowProbability)) - return generateNRandomNotes(HitObject.StartTime, 0.17, 0, 0); + return generateNRandomNotes(StartTime, 0.17, 0, 0); - return generateNRandomNotes(HitObject.StartTime, 0.27, 0, 0); + return generateNRandomNotes(StartTime, 0.27, 0, 0); } /// @@ -167,7 +174,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy /// Start time of each hold note. /// Number of hold notes. /// The containing the hit objects. - private Pattern generateRandomHoldNotes(double startTime, int noteCount) + private Pattern generateRandomHoldNotes(int startTime, int noteCount) { // - - - - // ■ - ■ ■ @@ -202,7 +209,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy /// The start time. /// The number of notes. /// The containing the hit objects. - private Pattern generateRandomNotes(double startTime, int noteCount) + private Pattern generateRandomNotes(int startTime, int noteCount) { // - - - - // x - - - @@ -234,7 +241,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy /// /// The start time. /// The containing the hit objects. - private Pattern generateStair(double startTime) + private Pattern generateStair(int startTime) { // - - - - // x - - - @@ -286,7 +293,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy /// /// The start time. /// The containing the hit objects. - private Pattern generateRandomMultipleNotes(double startTime) + private Pattern generateRandomMultipleNotes(int startTime) { // - - - - // x - - - @@ -329,7 +336,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy /// The probability required for 3 hold notes to be generated. /// The probability required for 4 hold notes to be generated. /// The containing the hit objects. - private Pattern generateNRandomNotes(double startTime, double p2, double p3, double p4) + private Pattern generateNRandomNotes(int startTime, double p2, double p3, double p4) { // - - - - // ■ - ■ ■ @@ -366,7 +373,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy 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); + canGenerateTwoNotes &= HitObject.Samples.Any(isDoubleSample) || sampleInfoListAt(StartTime).Any(isDoubleSample); if (canGenerateTwoNotes) p2 = 1; @@ -379,7 +386,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy /// /// The first hold note start time. /// The containing the hit objects. - private Pattern generateTiledHoldNotes(double startTime) + private Pattern generateTiledHoldNotes(int startTime) { // - - - - // ■ ■ ■ ■ @@ -394,6 +401,9 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy int columnRepeat = Math.Min(SpanCount, TotalColumns); + // Due to integer rounding, this is not guaranteed to be the same as EndTime (the class-level variable). + int endTime = startTime + SegmentDuration * SpanCount; + int nextColumn = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true); if (convertType.HasFlag(PatternType.ForceNotStack) && PreviousPattern.ColumnWithObjects < TotalColumns) nextColumn = FindAvailableColumn(nextColumn, PreviousPattern); @@ -401,7 +411,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy for (int i = 0; i < columnRepeat; i++) { nextColumn = FindAvailableColumn(nextColumn, pattern); - addToPattern(pattern, nextColumn, startTime, EndTime); + addToPattern(pattern, nextColumn, startTime, endTime); startTime += SegmentDuration; } @@ -413,7 +423,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy /// /// The start time of notes. /// The containing the hit objects. - private Pattern generateHoldAndNormalNotes(double startTime) + private Pattern generateHoldAndNormalNotes(int startTime) { // - - - - // ■ x x - @@ -448,7 +458,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy for (int i = 0; i <= SpanCount; i++) { - if (!(ignoreHead && startTime == HitObject.StartTime)) + if (!(ignoreHead && startTime == StartTime)) { for (int j = 0; j < noteCount; j++) { @@ -471,19 +481,18 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy /// /// The time to retrieve the sample info list from. /// - private IList sampleInfoListAt(double time) => nodeSamplesAt(time)?.First() ?? HitObject.Samples; + private IList sampleInfoListAt(int time) => nodeSamplesAt(time)?.First() ?? HitObject.Samples; /// /// Retrieves the list of node samples that occur at time greater than or equal to . /// /// The time to retrieve node samples at. - private List> nodeSamplesAt(double time) + private List> nodeSamplesAt(int time) { if (!(HitObject is IHasPathWithRepeats curveData)) return null; - // mathematically speaking this should be a whole number always, but floating-point arithmetic is not so kind - var index = (int)Math.Round(SegmentDuration == 0 ? 0 : (time - HitObject.StartTime) / SegmentDuration, MidpointRounding.AwayFromZero); + var index = SegmentDuration == 0 ? 0 : (time - StartTime) / SegmentDuration; // avoid slicing the list & creating copies, if at all possible. return index == 0 ? curveData.NodeSamples : curveData.NodeSamples.Skip(index).ToList(); @@ -496,7 +505,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy /// The column to add the note to. /// The start time of the note. /// The end time of the note (set to for a non-hold note). - private void addToPattern(Pattern pattern, int column, double startTime, double endTime) + private void addToPattern(Pattern pattern, int column, int startTime, int endTime) { ManiaHitObject newObject; From 485a951281f62bb3ff7afad7b369e4442b61ab24 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 9 Oct 2020 21:42:43 +0900 Subject: [PATCH 1625/1782] Expose current strain and retrieval of peak strain --- osu.Game/Rulesets/Difficulty/Skills/Skill.cs | 25 ++++++++++++++------ 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/osu.Game/Rulesets/Difficulty/Skills/Skill.cs b/osu.Game/Rulesets/Difficulty/Skills/Skill.cs index 227f2f4018..1063a24b27 100644 --- a/osu.Game/Rulesets/Difficulty/Skills/Skill.cs +++ b/osu.Game/Rulesets/Difficulty/Skills/Skill.cs @@ -41,7 +41,11 @@ namespace osu.Game.Rulesets.Difficulty.Skills /// protected readonly LimitedCapacityStack Previous = new LimitedCapacityStack(2); // Contained objects not used yet - private double currentStrain = 1; // We keep track of the strain level at all times throughout the beatmap. + /// + /// The current strain level. + /// + protected double CurrentStrain { get; private set; } = 1; + private double currentSectionPeak = 1; // We also keep track of the peak strain level in the current section. private readonly List strainPeaks = new List(); @@ -51,10 +55,10 @@ namespace osu.Game.Rulesets.Difficulty.Skills /// public void Process(DifficultyHitObject current) { - currentStrain *= strainDecay(current.DeltaTime); - currentStrain += StrainValueOf(current) * SkillMultiplier; + CurrentStrain *= strainDecay(current.DeltaTime); + CurrentStrain += StrainValueOf(current) * SkillMultiplier; - currentSectionPeak = Math.Max(currentStrain, currentSectionPeak); + currentSectionPeak = Math.Max(CurrentStrain, currentSectionPeak); Previous.Push(current); } @@ -71,15 +75,22 @@ namespace osu.Game.Rulesets.Difficulty.Skills /// /// Sets the initial strain level for a new section. /// - /// The beginning of the new section in milliseconds. - public void StartNewSectionFrom(double offset) + /// The beginning of the new section in milliseconds. + public void StartNewSectionFrom(double time) { // The maximum strain of the new section is not zero by default, strain decays as usual regardless of section boundaries. // This means we need to capture the strain level at the beginning of the new section, and use that as the initial peak level. if (Previous.Count > 0) - currentSectionPeak = currentStrain * strainDecay(offset - Previous[0].BaseObject.StartTime); + currentSectionPeak = GetPeakStrain(time); } + /// + /// Retrieves the peak strain at a point in time. + /// + /// The time to retrieve the peak strain at. + /// The peak strain. + protected virtual double GetPeakStrain(double time) => CurrentStrain * strainDecay(time - Previous[0].BaseObject.StartTime); + /// /// Returns the calculated difficulty value representing all processed s. /// From 8f37d2290a4321bc569bc73f30cdbdb7755f1ed7 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 9 Oct 2020 21:43:46 +0900 Subject: [PATCH 1626/1782] Expose sorting of hitobjects --- osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs index 1902de5bda..e80a4e4b1c 100644 --- a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs @@ -69,7 +69,7 @@ namespace osu.Game.Rulesets.Difficulty if (!beatmap.HitObjects.Any()) return CreateDifficultyAttributes(beatmap, mods, skills, clockRate); - var difficultyHitObjects = CreateDifficultyHitObjects(beatmap, clockRate).OrderBy(h => h.BaseObject.StartTime).ToList(); + var difficultyHitObjects = SortObjects(CreateDifficultyHitObjects(beatmap, clockRate)).ToList(); double sectionLength = SectionLength * clockRate; @@ -100,6 +100,14 @@ namespace osu.Game.Rulesets.Difficulty return CreateDifficultyAttributes(beatmap, mods, skills, clockRate); } + /// + /// Sorts a given set of s. + /// + /// The s to sort. + /// The sorted s. + protected virtual IEnumerable SortObjects(IEnumerable input) + => input.OrderBy(h => h.BaseObject.StartTime); + /// /// Creates all combinations which adjust the difficulty. /// From b0f8a7794a58a573a839e237d103bc4d50ac3eaa Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Oct 2020 21:44:10 +0900 Subject: [PATCH 1627/1782] Make SelectionHandler require EditorBeatmap presence --- .../Compose/Components/SelectionHandler.cs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index 6e144fd5ff..4caceedc5a 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -43,7 +43,7 @@ namespace osu.Game.Screens.Edit.Compose.Components protected SelectionBox SelectionBox { get; private set; } - [Resolved(CanBeNull = true)] + [Resolved] protected EditorBeatmap EditorBeatmap { get; private set; } [Resolved(CanBeNull = true)] @@ -243,7 +243,7 @@ namespace osu.Game.Screens.Edit.Compose.Components private void deleteSelected() { - EditorBeatmap?.RemoveRange(selectedBlueprints.Select(b => b.HitObject)); + EditorBeatmap.RemoveRange(selectedBlueprints.Select(b => b.HitObject)); } #endregion @@ -310,7 +310,7 @@ namespace osu.Game.Screens.Edit.Compose.Components /// The name of the hit sample. public void AddHitSample(string sampleName) { - EditorBeatmap?.BeginChange(); + EditorBeatmap.BeginChange(); foreach (var h in EditorBeatmap.SelectedHitObjects) { @@ -321,7 +321,7 @@ namespace osu.Game.Screens.Edit.Compose.Components h.Samples.Add(new HitSampleInfo { Name = sampleName }); } - EditorBeatmap?.EndChange(); + EditorBeatmap.EndChange(); } /// @@ -331,7 +331,7 @@ namespace osu.Game.Screens.Edit.Compose.Components /// Throws if any selected object doesn't implement public void SetNewCombo(bool state) { - EditorBeatmap?.BeginChange(); + EditorBeatmap.BeginChange(); foreach (var h in EditorBeatmap.SelectedHitObjects) { @@ -340,10 +340,10 @@ namespace osu.Game.Screens.Edit.Compose.Components if (comboInfo == null || comboInfo.NewCombo == state) continue; comboInfo.NewCombo = state; - EditorBeatmap?.Update(h); + EditorBeatmap.Update(h); } - EditorBeatmap?.EndChange(); + EditorBeatmap.EndChange(); } /// @@ -352,12 +352,12 @@ namespace osu.Game.Screens.Edit.Compose.Components /// The name of the hit sample. public void RemoveHitSample(string sampleName) { - EditorBeatmap?.BeginChange(); + EditorBeatmap.BeginChange(); foreach (var h in EditorBeatmap.SelectedHitObjects) h.SamplesBindable.RemoveAll(s => s.Name == sampleName); - EditorBeatmap?.EndChange(); + EditorBeatmap.EndChange(); } #endregion From 5017c92fe84973d58f3fa6c4c90af0b0858c2591 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 9 Oct 2020 21:47:34 +0900 Subject: [PATCH 1628/1782] Combine mania skills --- .../Difficulty/ManiaDifficultyCalculator.cs | 47 +---------- .../Difficulty/Skills/Individual.cs | 47 ----------- .../Difficulty/Skills/Overall.cs | 56 ------------- .../Difficulty/Skills/Strain.cs | 80 +++++++++++++++++++ 4 files changed, 84 insertions(+), 146 deletions(-) delete mode 100644 osu.Game.Rulesets.Mania/Difficulty/Skills/Individual.cs delete mode 100644 osu.Game.Rulesets.Mania/Difficulty/Skills/Overall.cs create mode 100644 osu.Game.Rulesets.Mania/Difficulty/Skills/Strain.cs diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs index b08c520c54..7dd1755742 100644 --- a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs @@ -40,7 +40,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty return new ManiaDifficultyAttributes { - StarRating = difficultyValue(skills) * star_scaling_factor, + StarRating = skills[0].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.WindowFor(HitResult.Great)) / clockRate, @@ -49,55 +49,16 @@ namespace osu.Game.Rulesets.Mania.Difficulty }; } - private double difficultyValue(Skill[] skills) - { - // Preprocess the strains to find the maximum overall + individual (aggregate) strain from each section - var overall = skills.OfType().Single(); - var aggregatePeaks = new List(Enumerable.Repeat(0.0, overall.StrainPeaks.Count)); - - foreach (var individual in skills.OfType()) - { - for (int i = 0; i < individual.StrainPeaks.Count; i++) - { - double aggregate = individual.StrainPeaks[i] + overall.StrainPeaks[i]; - - if (aggregate > aggregatePeaks[i]) - aggregatePeaks[i] = aggregate; - } - } - - aggregatePeaks.Sort((a, b) => b.CompareTo(a)); // Sort from highest to lowest strain. - - double difficulty = 0; - double weight = 1; - - // Difficulty is the weighted sum of the highest strains from every section. - foreach (double strain in aggregatePeaks) - { - difficulty += strain * weight; - weight *= 0.9; - } - - return difficulty; - } - protected override IEnumerable CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate) { for (int i = 1; i < beatmap.HitObjects.Count; i++) yield return new ManiaDifficultyHitObject(beatmap.HitObjects[i], beatmap.HitObjects[i - 1], clockRate); } - protected override Skill[] CreateSkills(IBeatmap beatmap) + protected override Skill[] CreateSkills(IBeatmap beatmap) => new Skill[] { - int columnCount = ((ManiaBeatmap)beatmap).TotalColumns; - - var skills = new List { new Overall(columnCount) }; - - for (int i = 0; i < columnCount; i++) - skills.Add(new Individual(i, columnCount)); - - return skills.ToArray(); - } + new Strain(((ManiaBeatmap)beatmap).TotalColumns) + }; protected override Mod[] DifficultyAdjustmentMods { diff --git a/osu.Game.Rulesets.Mania/Difficulty/Skills/Individual.cs b/osu.Game.Rulesets.Mania/Difficulty/Skills/Individual.cs deleted file mode 100644 index 4f7ab87fad..0000000000 --- a/osu.Game.Rulesets.Mania/Difficulty/Skills/Individual.cs +++ /dev/null @@ -1,47 +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.Linq; -using osu.Game.Rulesets.Difficulty.Preprocessing; -using osu.Game.Rulesets.Difficulty.Skills; -using osu.Game.Rulesets.Mania.Difficulty.Preprocessing; -using osu.Game.Rulesets.Objects; - -namespace osu.Game.Rulesets.Mania.Difficulty.Skills -{ - public class Individual : Skill - { - protected override double SkillMultiplier => 1; - protected override double StrainDecayBase => 0.125; - - private readonly double[] holdEndTimes; - - private readonly int column; - - public Individual(int column, int columnCount) - { - this.column = column; - - holdEndTimes = new double[columnCount]; - } - - protected override double StrainValueOf(DifficultyHitObject current) - { - var maniaCurrent = (ManiaDifficultyHitObject)current; - var endTime = maniaCurrent.BaseObject.GetEndTime(); - - try - { - if (maniaCurrent.BaseObject.Column != column) - return 0; - - // We give a slight bonus if something is held meanwhile - return holdEndTimes.Any(t => t > endTime) ? 2.5 : 2; - } - finally - { - holdEndTimes[maniaCurrent.BaseObject.Column] = endTime; - } - } - } -} diff --git a/osu.Game.Rulesets.Mania/Difficulty/Skills/Overall.cs b/osu.Game.Rulesets.Mania/Difficulty/Skills/Overall.cs deleted file mode 100644 index bbbb93fd8b..0000000000 --- a/osu.Game.Rulesets.Mania/Difficulty/Skills/Overall.cs +++ /dev/null @@ -1,56 +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.Difficulty.Preprocessing; -using osu.Game.Rulesets.Difficulty.Skills; -using osu.Game.Rulesets.Mania.Difficulty.Preprocessing; -using osu.Game.Rulesets.Objects; - -namespace osu.Game.Rulesets.Mania.Difficulty.Skills -{ - public class Overall : Skill - { - protected override double SkillMultiplier => 1; - protected override double StrainDecayBase => 0.3; - - private readonly double[] holdEndTimes; - - private readonly int columnCount; - - public Overall(int columnCount) - { - this.columnCount = columnCount; - - holdEndTimes = new double[columnCount]; - } - - protected override double StrainValueOf(DifficultyHitObject current) - { - var maniaCurrent = (ManiaDifficultyHitObject)current; - 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 - - for (int i = 0; i < columnCount; i++) - { - // If there is at least one other overlapping end or note, then we get an addition, buuuuuut... - if (current.BaseObject.StartTime < holdEndTimes[i] && endTime > holdEndTimes[i]) - holdAddition = 1.0; - - // ... this addition only is valid if there is _no_ other note with the same ending. - // Releasing multiple notes at the same time is just as easy as releasing one - if (endTime == holdEndTimes[i]) - holdAddition = 0; - - // We give a slight bonus if something is held meanwhile - if (holdEndTimes[i] > endTime) - holdFactor = 1.25; - } - - holdEndTimes[maniaCurrent.BaseObject.Column] = endTime; - - return (1 + holdAddition) * holdFactor; - } - } -} diff --git a/osu.Game.Rulesets.Mania/Difficulty/Skills/Strain.cs b/osu.Game.Rulesets.Mania/Difficulty/Skills/Strain.cs new file mode 100644 index 0000000000..7ebc1ff752 --- /dev/null +++ b/osu.Game.Rulesets.Mania/Difficulty/Skills/Strain.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 System; +using osu.Framework.Utils; +using osu.Game.Rulesets.Difficulty.Preprocessing; +using osu.Game.Rulesets.Difficulty.Skills; +using osu.Game.Rulesets.Mania.Difficulty.Preprocessing; +using osu.Game.Rulesets.Objects; + +namespace osu.Game.Rulesets.Mania.Difficulty.Skills +{ + public class Strain : Skill + { + private const double individual_decay_base = 0.125; + private const double overall_decay_base = 0.30; + + protected override double SkillMultiplier => 1; + protected override double StrainDecayBase => 1; + + private readonly double[] holdEndTimes; + private readonly double[] individualStrains; + + private double individualStrain; + private double overallStrain; + + public Strain(int totalColumns) + { + holdEndTimes = new double[totalColumns]; + individualStrains = new double[totalColumns]; + overallStrain = 1; + } + + protected override double StrainValueOf(DifficultyHitObject current) + { + var maniaCurrent = (ManiaDifficultyHitObject)current; + var endTime = maniaCurrent.BaseObject.GetEndTime(); + var column = maniaCurrent.BaseObject.Column; + + double holdFactor = 1.0; // Factor to all additional strains 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 + + // Fill up the holdEndTimes array + for (int i = 0; i < holdEndTimes.Length; ++i) + { + // If there is at least one other overlapping end or note, then we get an addition, buuuuuut... + if (Precision.DefinitelyBigger(holdEndTimes[i], maniaCurrent.BaseObject.StartTime, 1) && Precision.DefinitelyBigger(endTime, holdEndTimes[i], 1)) + holdAddition = 1.0; + + // ... this addition only is valid if there is _no_ other note with the same ending. Releasing multiple notes at the same time is just as easy as releasing 1 + if (Precision.AlmostEquals(endTime, holdEndTimes[i], 1)) + holdAddition = 0; + + // We give a slight bonus to everything if something is held meanwhile + if (Precision.DefinitelyBigger(holdEndTimes[i], endTime, 1)) + holdFactor = 1.25; + + // Decay individual strains + individualStrains[i] = applyDecay(individualStrains[i], current.DeltaTime, individual_decay_base); + } + + holdEndTimes[column] = endTime; + + // Increase individual strain in own column + individualStrains[column] += 2.0 * holdFactor; + individualStrain = individualStrains[column]; + + overallStrain = applyDecay(overallStrain, current.DeltaTime, overall_decay_base) + (1 + holdAddition) * holdFactor; + + return individualStrain + overallStrain - CurrentStrain; + } + + protected override double GetPeakStrain(double offset) + => applyDecay(individualStrain, offset - Previous[0].BaseObject.StartTime, individual_decay_base) + + applyDecay(overallStrain, offset - Previous[0].BaseObject.StartTime, overall_decay_base); + + private double applyDecay(double value, double deltaTime, double decayBase) + => value * Math.Pow(decayBase, deltaTime / 1000); + } +} From 306d876d22042d3e6d61b41e3bb534b254e7c276 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 9 Oct 2020 21:50:11 +0900 Subject: [PATCH 1629/1782] Replicate stable's unstable sort --- .../Difficulty/ManiaDifficultyCalculator.cs | 14 +- .../MathUtils/LegacySortHelper.cs | 164 ++++++++++++++++++ 2 files changed, 176 insertions(+), 2 deletions(-) create mode 100644 osu.Game.Rulesets.Mania/MathUtils/LegacySortHelper.cs diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs index 7dd1755742..a3694f354b 100644 --- a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.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; @@ -10,10 +11,12 @@ using osu.Game.Rulesets.Difficulty.Skills; 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.MathUtils; 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 @@ -51,10 +54,17 @@ namespace osu.Game.Rulesets.Mania.Difficulty protected override IEnumerable CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate) { - for (int i = 1; i < beatmap.HitObjects.Count; i++) - yield return new ManiaDifficultyHitObject(beatmap.HitObjects[i], beatmap.HitObjects[i - 1], clockRate); + var sortedObjects = beatmap.HitObjects.ToArray(); + + LegacySortHelper.Sort(sortedObjects, Comparer.Create((a, b) => (int)Math.Round(a.StartTime) - (int)Math.Round(b.StartTime))); + + for (int i = 1; i < sortedObjects.Length; i++) + yield return new ManiaDifficultyHitObject(sortedObjects[i], sortedObjects[i - 1], clockRate); } + // Sorting is done in CreateDifficultyHitObjects, since the full list of hitobjects is required. + protected override IEnumerable SortObjects(IEnumerable input) => input; + protected override Skill[] CreateSkills(IBeatmap beatmap) => new Skill[] { new Strain(((ManiaBeatmap)beatmap).TotalColumns) diff --git a/osu.Game.Rulesets.Mania/MathUtils/LegacySortHelper.cs b/osu.Game.Rulesets.Mania/MathUtils/LegacySortHelper.cs new file mode 100644 index 0000000000..421cc0ae04 --- /dev/null +++ b/osu.Game.Rulesets.Mania/MathUtils/LegacySortHelper.cs @@ -0,0 +1,164 @@ +// Copyright (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.Contracts; + +namespace osu.Game.Rulesets.Mania.MathUtils +{ + /// + /// Provides access to .NET4.0 unstable sorting methods. + /// + /// + /// Source: https://referencesource.microsoft.com/#mscorlib/system/collections/generic/arraysorthelper.cs + /// + internal static class LegacySortHelper + { + private const int quick_sort_depth_threshold = 32; + + public static void Sort(T[] keys, IComparer comparer) + { + if (keys == null) + throw new ArgumentNullException(nameof(keys)); + + if (keys.Length == 0) + return; + + comparer ??= Comparer.Default; + depthLimitedQuickSort(keys, 0, keys.Length - 1, comparer, quick_sort_depth_threshold); + } + + private static void depthLimitedQuickSort(T[] keys, int left, int right, IComparer comparer, int depthLimit) + { + do + { + if (depthLimit == 0) + { + heapsort(keys, left, right, comparer); + return; + } + + int i = left; + int j = right; + + // pre-sort the low, middle (pivot), and high values in place. + // this improves performance in the face of already sorted data, or + // data that is made up of multiple sorted runs appended together. + int middle = i + ((j - i) >> 1); + swapIfGreater(keys, comparer, i, middle); // swap the low with the mid point + swapIfGreater(keys, comparer, i, j); // swap the low with the high + swapIfGreater(keys, comparer, middle, j); // swap the middle with the high + + T x = keys[middle]; + + do + { + while (comparer.Compare(keys[i], x) < 0) i++; + while (comparer.Compare(x, keys[j]) < 0) j--; + Contract.Assert(i >= left && j <= right, "(i>=left && j<=right) Sort failed - Is your IComparer bogus?"); + if (i > j) break; + + if (i < j) + { + T key = keys[i]; + keys[i] = keys[j]; + keys[j] = key; + } + + i++; + j--; + } while (i <= j); + + // The next iteration of the while loop is to "recursively" sort the larger half of the array and the + // following calls recrusively sort the smaller half. So we subtrack one from depthLimit here so + // both sorts see the new value. + depthLimit--; + + if (j - left <= right - i) + { + if (left < j) depthLimitedQuickSort(keys, left, j, comparer, depthLimit); + left = i; + } + else + { + if (i < right) depthLimitedQuickSort(keys, i, right, comparer, depthLimit); + right = j; + } + } while (left < right); + } + + private static void heapsort(T[] keys, int lo, int hi, IComparer comparer) + { + Contract.Requires(keys != null); + Contract.Requires(comparer != null); + Contract.Requires(lo >= 0); + Contract.Requires(hi > lo); + Contract.Requires(hi < keys.Length); + + int n = hi - lo + 1; + + for (int i = n / 2; i >= 1; i = i - 1) + { + downHeap(keys, i, n, lo, comparer); + } + + for (int i = n; i > 1; i = i - 1) + { + swap(keys, lo, lo + i - 1); + downHeap(keys, 1, i - 1, lo, comparer); + } + } + + private static void downHeap(T[] keys, int i, int n, int lo, IComparer comparer) + { + Contract.Requires(keys != null); + Contract.Requires(comparer != null); + Contract.Requires(lo >= 0); + Contract.Requires(lo < keys.Length); + + T d = keys[lo + i - 1]; + + while (i <= n / 2) + { + var child = 2 * i; + + if (child < n && comparer.Compare(keys[lo + child - 1], keys[lo + child]) < 0) + { + child++; + } + + if (!(comparer.Compare(d, keys[lo + child - 1]) < 0)) + break; + + keys[lo + i - 1] = keys[lo + child - 1]; + i = child; + } + + keys[lo + i - 1] = d; + } + + private static void swap(T[] a, int i, int j) + { + if (i != j) + { + T t = a[i]; + a[i] = a[j]; + a[j] = t; + } + } + + private static void swapIfGreater(T[] keys, IComparer comparer, int a, int b) + { + if (a != b) + { + if (comparer.Compare(keys[a], keys[b]) > 0) + { + T key = keys[a]; + keys[a] = keys[b]; + keys[b] = key; + } + } + } + } +} From 65d8530a11008333c8fc3b75c9fbee2a74c3d96b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 9 Oct 2020 22:47:32 +0900 Subject: [PATCH 1630/1782] Fix tests --- .../Testing/Beatmaps/convert-samples-expected-conversion.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/convert-samples-expected-conversion.json b/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/convert-samples-expected-conversion.json index fec1360b26..d49ffa01c5 100644 --- a/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/convert-samples-expected-conversion.json +++ b/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/convert-samples-expected-conversion.json @@ -10,7 +10,7 @@ ["soft-hitnormal"], ["drum-hitnormal"] ], - "Samples": ["drum-hitnormal"] + "Samples": ["-hitnormal"] }, { "StartTime": 1875.0, "EndTime": 2750.0, @@ -19,7 +19,7 @@ ["soft-hitnormal"], ["drum-hitnormal"] ], - "Samples": ["drum-hitnormal"] + "Samples": ["-hitnormal"] }] }, { "StartTime": 3750.0, From 6e8011a7ee51a0f1a9a6abf528f227595abb4e6e Mon Sep 17 00:00:00 2001 From: Endrik Tombak Date: Fri, 9 Oct 2020 17:28:59 +0300 Subject: [PATCH 1631/1782] Write xmldoc for TestFourVariousResultsOneMiss --- .../Rulesets/Scoring/ScoreProcessorTest.cs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs index dd191b03c2..f7144beda7 100644 --- a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs +++ b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs @@ -54,6 +54,21 @@ namespace osu.Game.Tests.Rulesets.Scoring Assert.IsTrue(Precision.AlmostEquals(expectedScore, scoreProcessor.TotalScore.Value)); } + /// + /// Test to see that all contribute to score portions in correct amounts. + /// + /// Scoring mode to test + /// HitResult that will be applied to HitObjects + /// HitResult used for accuracy calcualtion + /// Expected score after 3/4 hitobjects have been hit + /// + /// This test intentionally misses the 3rd hitobject to achieve lower than 75% accuracy and 50% max combo + /// expectedScore is calcualted using this algorithm for standardised scoring: 1_000_000 * ((75% * score_per_hitobject / max_per_hitobject * 30%) + (50% * 70%)) + /// "75% * score_per_hitobject / max_per_hitobject" is the accuracy we would get for hitting 3/4 hitobjects that award "score_per_hitobject / max_per_hitobject" accuracy each hit + /// + /// expectedScore is calculated using this algorithm for classic scoring: score_per_hitobject / max_per_hitobject * 936 + /// "936" is simplified from "75% * 4 * 300 * (1 + 1/25)" + /// [TestCase(ScoringMode.Standardised, HitResult.Miss, HitResult.Great, 0)] [TestCase(ScoringMode.Standardised, HitResult.Meh, HitResult.Great, 387_500)] [TestCase(ScoringMode.Standardised, HitResult.Ok, HitResult.Great, 425_000)] From a279c38af49a57ea7a6b668d37b774c5863335f2 Mon Sep 17 00:00:00 2001 From: Endrik Tombak Date: Fri, 9 Oct 2020 17:33:13 +0300 Subject: [PATCH 1632/1782] Convert all expectedScore values to int --- .../Rulesets/Scoring/ScoreProcessorTest.cs | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs index f7144beda7..4f4503faea 100644 --- a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs +++ b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs @@ -60,7 +60,7 @@ namespace osu.Game.Tests.Rulesets.Scoring /// Scoring mode to test /// HitResult that will be applied to HitObjects /// HitResult used for accuracy calcualtion - /// Expected score after 3/4 hitobjects have been hit + /// Expected score after 3/4 hitobjects have been hit rounded to nearest integer /// /// This test intentionally misses the 3rd hitobject to achieve lower than 75% accuracy and 50% max combo /// expectedScore is calcualted using this algorithm for standardised scoring: 1_000_000 * ((75% * score_per_hitobject / max_per_hitobject * 30%) + (50% * 70%)) @@ -72,7 +72,7 @@ namespace osu.Game.Tests.Rulesets.Scoring [TestCase(ScoringMode.Standardised, HitResult.Miss, HitResult.Great, 0)] [TestCase(ScoringMode.Standardised, HitResult.Meh, HitResult.Great, 387_500)] [TestCase(ScoringMode.Standardised, HitResult.Ok, HitResult.Great, 425_000)] - [TestCase(ScoringMode.Standardised, HitResult.Good, HitResult.Perfect, 3_350_000 / 7.0)] + [TestCase(ScoringMode.Standardised, HitResult.Good, HitResult.Perfect, 478_571)] [TestCase(ScoringMode.Standardised, HitResult.Great, HitResult.Great, 575_000)] [TestCase(ScoringMode.Standardised, HitResult.Perfect, HitResult.Perfect, 575_000)] [TestCase(ScoringMode.Standardised, HitResult.SmallTickMiss, HitResult.SmallTickHit, 700_000)] @@ -84,7 +84,7 @@ namespace osu.Game.Tests.Rulesets.Scoring [TestCase(ScoringMode.Classic, HitResult.Miss, HitResult.Great, 0)] [TestCase(ScoringMode.Classic, HitResult.Meh, HitResult.Great, 156)] [TestCase(ScoringMode.Classic, HitResult.Ok, HitResult.Great, 312)] - [TestCase(ScoringMode.Classic, HitResult.Good, HitResult.Perfect, 3744 / 7.0)] + [TestCase(ScoringMode.Classic, HitResult.Good, HitResult.Perfect, 535)] [TestCase(ScoringMode.Classic, HitResult.Great, HitResult.Great, 936)] [TestCase(ScoringMode.Classic, HitResult.Perfect, HitResult.Perfect, 936)] [TestCase(ScoringMode.Classic, HitResult.SmallTickMiss, HitResult.SmallTickHit, 0)] @@ -93,7 +93,7 @@ namespace osu.Game.Tests.Rulesets.Scoring [TestCase(ScoringMode.Classic, HitResult.LargeTickHit, HitResult.LargeTickHit, 936)] [TestCase(ScoringMode.Classic, HitResult.SmallBonus, HitResult.SmallBonus, 30)] [TestCase(ScoringMode.Classic, HitResult.LargeBonus, HitResult.LargeBonus, 150)] - public void TestFourVariousResultsOneMiss(ScoringMode scoringMode, HitResult hitResult, HitResult maxResult, double expectedScore) + public void TestFourVariousResultsOneMiss(ScoringMode scoringMode, HitResult hitResult, HitResult maxResult, int expectedScore) { var minResult = new TestJudgement(hitResult).MinResult; @@ -113,14 +113,14 @@ namespace osu.Game.Tests.Rulesets.Scoring scoreProcessor.ApplyResult(judgementResult); } - Assert.IsTrue(Precision.AlmostEquals(expectedScore, scoreProcessor.TotalScore.Value)); + Assert.IsTrue(Precision.AlmostEquals(expectedScore, scoreProcessor.TotalScore.Value, 0.5)); } - [TestCase(ScoringMode.Standardised, HitResult.SmallTickHit, 6_850_000 / 7.0)] - [TestCase(ScoringMode.Standardised, HitResult.SmallTickMiss, 6_400_000 / 7.0)] - [TestCase(ScoringMode.Classic, HitResult.SmallTickHit, 1950 / 7.0)] - [TestCase(ScoringMode.Classic, HitResult.SmallTickMiss, 1500 / 7.0)] - public void TestSmallTicksAccuracy(ScoringMode scoringMode, HitResult hitResult, double expectedScore) + [TestCase(ScoringMode.Standardised, HitResult.SmallTickHit, 978_571)] + [TestCase(ScoringMode.Standardised, HitResult.SmallTickMiss, 914_286)] + [TestCase(ScoringMode.Classic, HitResult.SmallTickHit, 279)] + [TestCase(ScoringMode.Classic, HitResult.SmallTickMiss, 214)] + public void TestSmallTicksAccuracy(ScoringMode scoringMode, HitResult hitResult, int expectedScore) { IEnumerable hitObjects = Enumerable .Repeat(new TestHitObject(HitResult.SmallTickHit), 4) @@ -147,7 +147,7 @@ namespace osu.Game.Tests.Rulesets.Scoring }; scoreProcessor.ApplyResult(lastJudgementResult); - Assert.IsTrue(Precision.AlmostEquals(expectedScore, scoreProcessor.TotalScore.Value)); + Assert.IsTrue(Precision.AlmostEquals(expectedScore, scoreProcessor.TotalScore.Value, 0.5)); } [TestCase(HitResult.IgnoreHit, HitResult.IgnoreMiss)] From 20f1eb2b33765d477fdabf04a7f3e287fe2b9170 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 10 Oct 2020 13:11:36 +0900 Subject: [PATCH 1633/1782] Fix windows key blocking applying when window is inactive / when watching a replay Closes #10467. --- osu.Desktop/Windows/GameplayWinKeyBlocker.cs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/osu.Desktop/Windows/GameplayWinKeyBlocker.cs b/osu.Desktop/Windows/GameplayWinKeyBlocker.cs index 86174ceb90..07af009b81 100644 --- a/osu.Desktop/Windows/GameplayWinKeyBlocker.cs +++ b/osu.Desktop/Windows/GameplayWinKeyBlocker.cs @@ -5,24 +5,24 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Platform; +using osu.Game; using osu.Game.Configuration; namespace osu.Desktop.Windows { public class GameplayWinKeyBlocker : Component { - private Bindable allowScreenSuspension; private Bindable disableWinKey; + private Bindable localUserPlaying; - private GameHost host; + [Resolved] + private GameHost host { get; set; } - [BackgroundDependencyLoader] - private void load(GameHost host, OsuConfigManager config) + [BackgroundDependencyLoader(true)] + private void load(OsuGame game, OsuConfigManager config) { - this.host = host; - - allowScreenSuspension = host.AllowScreenSuspension.GetBoundCopy(); - allowScreenSuspension.BindValueChanged(_ => updateBlocking()); + localUserPlaying = game.LocalUserPlaying.GetBoundCopy(); + localUserPlaying.BindValueChanged(_ => updateBlocking()); disableWinKey = config.GetBindable(OsuSetting.GameplayDisableWinKey); disableWinKey.BindValueChanged(_ => updateBlocking(), true); @@ -30,7 +30,7 @@ namespace osu.Desktop.Windows private void updateBlocking() { - bool shouldDisable = disableWinKey.Value && !allowScreenSuspension.Value; + bool shouldDisable = disableWinKey.Value && localUserPlaying.Value; if (shouldDisable) host.InputThread.Scheduler.Add(WindowsKey.Disable); From 09e350d14d069def409a82a1b56128bdd79fb17d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 10 Oct 2020 13:28:24 +0900 Subject: [PATCH 1634/1782] Remove canBNull specification --- osu.Desktop/Windows/GameplayWinKeyBlocker.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Desktop/Windows/GameplayWinKeyBlocker.cs b/osu.Desktop/Windows/GameplayWinKeyBlocker.cs index 07af009b81..efc3f21149 100644 --- a/osu.Desktop/Windows/GameplayWinKeyBlocker.cs +++ b/osu.Desktop/Windows/GameplayWinKeyBlocker.cs @@ -18,7 +18,7 @@ namespace osu.Desktop.Windows [Resolved] private GameHost host { get; set; } - [BackgroundDependencyLoader(true)] + [BackgroundDependencyLoader] private void load(OsuGame game, OsuConfigManager config) { localUserPlaying = game.LocalUserPlaying.GetBoundCopy(); From df9c4bf0a554dd9df255a84546b05aac2b605e98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 10 Oct 2020 13:01:52 +0200 Subject: [PATCH 1635/1782] Improve test xmldoc slightly --- .../Rulesets/Scoring/ScoreProcessorTest.cs | 27 +++++++++++-------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs index 40e6589ac4..b83b97a539 100644 --- a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs +++ b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs @@ -55,19 +55,24 @@ namespace osu.Game.Tests.Rulesets.Scoring } /// - /// Test to see that all contribute to score portions in correct amounts. + /// Test to see that all s contribute to score portions in correct amounts. /// - /// Scoring mode to test - /// HitResult that will be applied to HitObjects - /// HitResult used for accuracy calcualtion - /// Expected score after 3/4 hitobjects have been hit rounded to nearest integer + /// Scoring mode to test. + /// The that will be applied to selected hit objects. + /// The maximum achievable. + /// Expected score after all objects have been judged, rounded to the nearest integer. /// - /// This test intentionally misses the 3rd hitobject to achieve lower than 75% accuracy and 50% max combo - /// expectedScore is calcualted using this algorithm for standardised scoring: 1_000_000 * ((75% * score_per_hitobject / max_per_hitobject * 30%) + (50% * 70%)) - /// "75% * score_per_hitobject / max_per_hitobject" is the accuracy we would get for hitting 3/4 hitobjects that award "score_per_hitobject / max_per_hitobject" accuracy each hit - /// - /// expectedScore is calculated using this algorithm for classic scoring: score_per_hitobject / max_per_hitobject * 936 - /// "936" is simplified from "75% * 4 * 300 * (1 + 1/25)" + /// This test intentionally misses the 3rd hitobject to achieve lower than 75% accuracy and exactly 50% max combo. + /// + /// For standardised scoring, is calculated using the following formula: + /// 1_000_000 * ((3 * / 4 * ) * 30% + 50% * 70%) + /// + /// + /// For classic scoring, is calculated using the following formula: + /// / * 936 + /// where 936 is simplified from: + /// 75% * 4 * 300 * (1 + 1/25) + /// /// [TestCase(ScoringMode.Standardised, HitResult.Miss, HitResult.Great, 0)] [TestCase(ScoringMode.Standardised, HitResult.Meh, HitResult.Great, 387_500)] From 73c238fae3d395f65f4089c2c68179ea828c4a77 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 10 Oct 2020 21:34:01 +0900 Subject: [PATCH 1636/1782] Add the ability to search for local beatmaps via online IDs Closes #10470. --- osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs | 8 ++++++++ osu.Game/Screens/Select/FilterCriteria.cs | 10 ++++++++++ 2 files changed, 18 insertions(+) diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs index 3892e02a8f..83e3c84f39 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs @@ -58,6 +58,14 @@ namespace osu.Game.Screens.Select.Carousel foreach (var criteriaTerm in criteria.SearchTerms) match &= terms.Any(term => term.IndexOf(criteriaTerm, StringComparison.InvariantCultureIgnoreCase) >= 0); + + // if a match wasn't found via text matching of terms, do a second catch-all check matching against online IDs. + // this should be done after text matching so we can prioritise matching numbers in metadata. + if (!match && criteria.SearchNumber.HasValue) + { + match = (Beatmap.OnlineBeatmapID == criteria.SearchNumber.Value) || + (Beatmap.BeatmapSet?.OnlineBeatmapSetID == criteria.SearchNumber.Value); + } } if (match) diff --git a/osu.Game/Screens/Select/FilterCriteria.cs b/osu.Game/Screens/Select/FilterCriteria.cs index 66f164bca8..f34f8f6505 100644 --- a/osu.Game/Screens/Select/FilterCriteria.cs +++ b/osu.Game/Screens/Select/FilterCriteria.cs @@ -43,6 +43,11 @@ namespace osu.Game.Screens.Select private string searchText; + /// + /// as a number (if it can be parsed as one). + /// + public int? SearchNumber { get; private set; } + public string SearchText { get => searchText; @@ -50,6 +55,11 @@ namespace osu.Game.Screens.Select { searchText = value; SearchTerms = searchText.Split(new[] { ',', ' ', '!' }, StringSplitOptions.RemoveEmptyEntries).ToArray(); + + SearchNumber = null; + + if (SearchTerms.Length == 1 && int.TryParse(SearchTerms[0], out int parsed)) + SearchNumber = parsed; } } From 7bbdd6ab25917834bf04ac46a57424f53b97efa3 Mon Sep 17 00:00:00 2001 From: unknown Date: Sat, 10 Oct 2020 21:07:17 +0800 Subject: [PATCH 1637/1782] expose break time bindable --- osu.Game/Screens/Play/Player.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 74714e7e59..6d910e39ed 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -89,6 +89,8 @@ namespace osu.Game.Screens.Play public BreakOverlay BreakOverlay; + public IBindable IsBreakTime => breakTracker?.IsBreakTime; + private BreakTracker breakTracker; private SkipOverlay skipOverlay; From a7c43e17c2a5a72ae358334f49ae46da5fbcb4a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 10 Oct 2020 15:41:48 +0200 Subject: [PATCH 1638/1782] Add test coverage --- .../NonVisual/Filtering/FilterMatchingTest.cs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs b/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs index 30686cb947..24a0a662ba 100644 --- a/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs +++ b/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs @@ -197,5 +197,22 @@ namespace osu.Game.Tests.NonVisual.Filtering carouselItem.Filter(criteria); Assert.AreEqual(filtered, carouselItem.Filtered.Value); } + + [TestCase("202010", true)] + [TestCase("20201010", false)] + [TestCase("153", true)] + [TestCase("1535", false)] + public void TestCriteriaMatchingBeatmapIDs(string query, bool filtered) + { + var beatmap = getExampleBeatmap(); + beatmap.OnlineBeatmapID = 20201010; + beatmap.BeatmapSet = new BeatmapSetInfo { OnlineBeatmapSetID = 1535 }; + + var criteria = new FilterCriteria { SearchText = query }; + var carouselItem = new CarouselBeatmap(beatmap); + carouselItem.Filter(criteria); + + Assert.AreEqual(filtered, carouselItem.Filtered.Value); + } } } From f41879ee7c84509bbab4018e7112e08a9a7bfd1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 10 Oct 2020 17:54:37 +0200 Subject: [PATCH 1639/1782] Show current hit circle placement in timeline --- .../Blueprints/HitCircles/HitCirclePlacementBlueprint.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/HitCirclePlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/HitCirclePlacementBlueprint.cs index 3dbbdcc5d0..e14d6647d2 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/HitCirclePlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/HitCirclePlacementBlueprint.cs @@ -21,6 +21,12 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles InternalChild = circlePiece = new HitCirclePiece(); } + protected override void LoadComplete() + { + base.LoadComplete(); + BeginPlacement(); + } + protected override void Update() { base.Update(); From 75b26d0cdecd5335c4009009036e0b2c160bde63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 10 Oct 2020 18:08:19 +0200 Subject: [PATCH 1640/1782] Add failing test cases --- .../Beatmaps/BeatmapDifficultyManagerTest.cs | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/osu.Game.Tests/Beatmaps/BeatmapDifficultyManagerTest.cs b/osu.Game.Tests/Beatmaps/BeatmapDifficultyManagerTest.cs index 0f6d956b3c..7c1ddd757f 100644 --- a/osu.Game.Tests/Beatmaps/BeatmapDifficultyManagerTest.cs +++ b/osu.Game.Tests/Beatmaps/BeatmapDifficultyManagerTest.cs @@ -28,5 +28,28 @@ namespace osu.Game.Tests.Beatmaps Assert.That(key1, Is.EqualTo(key2)); } + + [TestCase(1.3, DifficultyRating.Easy)] + [TestCase(1.993, DifficultyRating.Easy)] + [TestCase(1.998, DifficultyRating.Normal)] + [TestCase(2.4, DifficultyRating.Normal)] + [TestCase(2.693, DifficultyRating.Normal)] + [TestCase(2.698, DifficultyRating.Hard)] + [TestCase(3.5, DifficultyRating.Hard)] + [TestCase(3.993, DifficultyRating.Hard)] + [TestCase(3.997, DifficultyRating.Insane)] + [TestCase(5.0, DifficultyRating.Insane)] + [TestCase(5.292, DifficultyRating.Insane)] + [TestCase(5.297, DifficultyRating.Expert)] + [TestCase(6.2, DifficultyRating.Expert)] + [TestCase(6.493, DifficultyRating.Expert)] + [TestCase(6.498, DifficultyRating.ExpertPlus)] + [TestCase(8.3, DifficultyRating.ExpertPlus)] + public void TestDifficultyRatingMapping(double starRating, DifficultyRating expectedBracket) + { + var actualBracket = BeatmapDifficultyManager.GetDifficultyRating(starRating); + + Assert.AreEqual(expectedBracket, actualBracket); + } } } From 8af78656e456b70f2f0366c8c3f4741147104037 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 10 Oct 2020 18:15:52 +0200 Subject: [PATCH 1641/1782] Add precision tolerance to difficulty rating range checks --- osu.Game/Beatmaps/BeatmapDifficultyManager.cs | 22 ++++++++++++++----- 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapDifficultyManager.cs b/osu.Game/Beatmaps/BeatmapDifficultyManager.cs index 945a60fb62..8c12ca6f6e 100644 --- a/osu.Game/Beatmaps/BeatmapDifficultyManager.cs +++ b/osu.Game/Beatmaps/BeatmapDifficultyManager.cs @@ -14,6 +14,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics.Containers; using osu.Framework.Lists; using osu.Framework.Threading; +using osu.Framework.Utils; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; @@ -124,13 +125,22 @@ namespace osu.Game.Beatmaps /// The that best describes . public static DifficultyRating GetDifficultyRating(double starRating) { - if (starRating < 2.0) return DifficultyRating.Easy; - if (starRating < 2.7) return DifficultyRating.Normal; - if (starRating < 4.0) return DifficultyRating.Hard; - if (starRating < 5.3) return DifficultyRating.Insane; - if (starRating < 6.5) return DifficultyRating.Expert; + if (Precision.AlmostBigger(starRating, 6.5, 0.005)) + return DifficultyRating.ExpertPlus; - return DifficultyRating.ExpertPlus; + if (Precision.AlmostBigger(starRating, 5.3, 0.005)) + return DifficultyRating.Expert; + + if (Precision.AlmostBigger(starRating, 4.0, 0.005)) + return DifficultyRating.Insane; + + if (Precision.AlmostBigger(starRating, 2.7, 0.005)) + return DifficultyRating.Hard; + + if (Precision.AlmostBigger(starRating, 2.0, 0.005)) + return DifficultyRating.Normal; + + return DifficultyRating.Easy; } private CancellationTokenSource trackedUpdateCancellationSource; From 6a52c98a428b5c3c032c5908d863ce4bcfc9ca89 Mon Sep 17 00:00:00 2001 From: unknown Date: Sun, 11 Oct 2020 06:15:20 +0800 Subject: [PATCH 1642/1782] make IsBreakTime its own bindable and bind it to BreakTracker on load --- osu.Game/Screens/Play/Player.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 6d910e39ed..4f13843503 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -89,7 +89,10 @@ namespace osu.Game.Screens.Play public BreakOverlay BreakOverlay; - public IBindable IsBreakTime => breakTracker?.IsBreakTime; + /// + /// Whether the gameplay is currently in a break. + /// + public readonly BindableBool IsBreakTime = new BindableBool(); private BreakTracker breakTracker; @@ -259,6 +262,7 @@ namespace osu.Game.Screens.Play mod.ApplyToHealthProcessor(HealthProcessor); breakTracker.IsBreakTime.BindValueChanged(onBreakTimeChanged, true); + IsBreakTime.BindTo((BindableBool)breakTracker.IsBreakTime); } private Drawable createUnderlayComponents() => From 8faa86b048e982eaa75e70abc49308a53000306e Mon Sep 17 00:00:00 2001 From: Joehu Date: Sat, 10 Oct 2020 18:30:13 -0700 Subject: [PATCH 1643/1782] Add ability to toggle extended statistics using space or enter --- osu.Game/Screens/Ranking/ResultsScreen.cs | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index c48cd238c0..026ce01857 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -10,9 +10,11 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Input.Bindings; using osu.Framework.Screens; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; +using osu.Game.Input.Bindings; using osu.Game.Online.API; using osu.Game.Scoring; using osu.Game.Screens.Backgrounds; @@ -22,7 +24,7 @@ using osuTK; namespace osu.Game.Screens.Ranking { - public abstract class ResultsScreen : OsuScreen + public abstract class ResultsScreen : OsuScreen, IKeyBindingHandler { protected const float BACKGROUND_BLUR = 20; private static readonly float screen_height = 768 - TwoLayerButton.SIZE_EXTENDED.Y; @@ -314,6 +316,22 @@ namespace osu.Game.Screens.Ranking } } + public bool OnPressed(GlobalAction action) + { + switch (action) + { + case GlobalAction.Select: + statisticsPanel.ToggleVisibility(); + return true; + } + + return false; + } + + public void OnReleased(GlobalAction action) + { + } + private class VerticalScrollContainer : OsuScrollContainer { protected override Container Content => content; From 5fcdee6fd8e2fe9631b0564878aa2b63179dc6c8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 11 Oct 2020 21:46:55 +0900 Subject: [PATCH 1644/1782] Remove cast and expose as IBindable --- 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 4f13843503..4dfa609a2e 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -92,7 +92,7 @@ namespace osu.Game.Screens.Play /// /// Whether the gameplay is currently in a break. /// - public readonly BindableBool IsBreakTime = new BindableBool(); + public readonly IBindable IsBreakTime = new BindableBool(); private BreakTracker breakTracker; @@ -261,8 +261,8 @@ namespace osu.Game.Screens.Play foreach (var mod in Mods.Value.OfType()) mod.ApplyToHealthProcessor(HealthProcessor); + IsBreakTime.BindTo(breakTracker.IsBreakTime); breakTracker.IsBreakTime.BindValueChanged(onBreakTimeChanged, true); - IsBreakTime.BindTo((BindableBool)breakTracker.IsBreakTime); } private Drawable createUnderlayComponents() => From de6fe34361424c9ea1fa15697b90667e0d95b1e1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 11 Oct 2020 21:51:48 +0900 Subject: [PATCH 1645/1782] Bind to local bindable and combine dual bindings --- 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 4dfa609a2e..a2a53b4b75 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -231,7 +231,6 @@ namespace osu.Game.Screens.Play DrawableRuleset.IsPaused.BindValueChanged(_ => updateGameplayState()); DrawableRuleset.HasReplayLoaded.BindValueChanged(_ => updateGameplayState()); - breakTracker.IsBreakTime.BindValueChanged(_ => updateGameplayState()); DrawableRuleset.HasReplayLoaded.BindValueChanged(_ => updatePauseOnFocusLostState(), true); @@ -262,7 +261,7 @@ namespace osu.Game.Screens.Play mod.ApplyToHealthProcessor(HealthProcessor); IsBreakTime.BindTo(breakTracker.IsBreakTime); - breakTracker.IsBreakTime.BindValueChanged(onBreakTimeChanged, true); + IsBreakTime.BindValueChanged(onBreakTimeChanged, true); } private Drawable createUnderlayComponents() => @@ -360,6 +359,7 @@ namespace osu.Game.Screens.Play private void onBreakTimeChanged(ValueChangedEvent isBreakTime) { + updateGameplayState(); updatePauseOnFocusLostState(); HUDOverlay.KeyCounter.IsCounting = !isBreakTime.NewValue; } From e5548a12161afb7bf77c67f3cb68bcd662b61998 Mon Sep 17 00:00:00 2001 From: Leon Gebler Date: Mon, 12 Oct 2020 00:16:18 +0200 Subject: [PATCH 1646/1782] Move ModSettingsContainer class inside ModSelectOverlay --- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 4eb4fc6501..3b158ee98d 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -284,7 +284,7 @@ namespace osu.Game.Overlays.Mods }, }, }, - ModSettingsContainer = new Container + ModSettingsContainer = new CModSettingsContainer { RelativeSizeAxes = Axes.Both, Anchor = Anchor.BottomRight, @@ -495,5 +495,19 @@ namespace osu.Game.Overlays.Mods } #endregion + + protected class CModSettingsContainer : Container + { + protected override bool OnMouseDown(MouseDownEvent e) + { + return true; + } + + protected override bool OnHover(HoverEvent e) + { + return true; + } + } + } } From ac4290dfb65c19374e66e6c47c7d132c4440dd03 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 12 Oct 2020 15:27:33 +0900 Subject: [PATCH 1647/1782] Add comment about stable calculation --- .../Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs index 415201951b..30d33de06e 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs @@ -57,8 +57,9 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy beatLength = timingPoint.BeatLength / difficultyPoint.SpeedMultiplier; SpanCount = repeatsData?.SpanCount() ?? 1; - StartTime = (int)Math.Round(hitObject.StartTime); + + // This matches stable's calculation. EndTime = (int)Math.Floor(StartTime + distanceData.Distance * beatLength * SpanCount * 0.01 / beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier); SegmentDuration = (EndTime - StartTime) / SpanCount; From 379971578dbc7cdc80c352ae35a46d0025602784 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 12 Oct 2020 15:28:16 +0900 Subject: [PATCH 1648/1782] Remove culling notice from HasEffect --- osu.Game/Beatmaps/Timing/BreakPeriod.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/Timing/BreakPeriod.cs b/osu.Game/Beatmaps/Timing/BreakPeriod.cs index bb8ae4a66a..4c90b16745 100644 --- a/osu.Game/Beatmaps/Timing/BreakPeriod.cs +++ b/osu.Game/Beatmaps/Timing/BreakPeriod.cs @@ -28,7 +28,7 @@ namespace osu.Game.Beatmaps.Timing public double Duration => EndTime - StartTime; /// - /// Whether the break has any effect. Breaks that are too short are culled before they are added to the beatmap. + /// Whether the break has any effect. /// public bool HasEffect => Duration >= MIN_BREAK_DURATION; From ccf7e2c49a0818c51c669fc620dcfa13edbe083a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 12 Oct 2020 16:31:42 +0900 Subject: [PATCH 1649/1782] Fallback to default ruleset star rating if conversion fails --- osu.Game/Beatmaps/BeatmapDifficultyManager.cs | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/osu.Game/Beatmaps/BeatmapDifficultyManager.cs b/osu.Game/Beatmaps/BeatmapDifficultyManager.cs index 8c12ca6f6e..c1f4c07833 100644 --- a/osu.Game/Beatmaps/BeatmapDifficultyManager.cs +++ b/osu.Game/Beatmaps/BeatmapDifficultyManager.cs @@ -13,10 +13,12 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics.Containers; using osu.Framework.Lists; +using osu.Framework.Logging; using osu.Framework.Threading; using osu.Framework.Utils; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.UI; namespace osu.Game.Beatmaps { @@ -238,6 +240,24 @@ namespace osu.Game.Beatmaps return difficultyCache[key] = new StarDifficulty(attributes.StarRating, attributes.MaxCombo); } + catch (BeatmapInvalidForRulesetException e) + { + // Conversion has failed for the given ruleset, so return the difficulty in the beatmap's default ruleset. + + // Ensure the beatmap's default ruleset isn't the one already being converted to. + // This shouldn't happen as it means something went seriously wrong, but if it does an endless loop should be avoided. + if (rulesetInfo.Equals(beatmapInfo.Ruleset)) + { + Logger.Error(e, $"Failed to convert {beatmapInfo.OnlineBeatmapID} to the beatmap's default ruleset ({beatmapInfo.Ruleset})."); + return difficultyCache[key] = new StarDifficulty(); + } + + // Check the cache first because this is now a different ruleset than the one previously guarded against. + if (tryGetExisting(beatmapInfo, beatmapInfo.Ruleset, Array.Empty(), out var existingDefault, out var existingDefaultKey)) + return existingDefault; + + return computeDifficulty(existingDefaultKey, beatmapInfo, beatmapInfo.Ruleset); + } catch { return difficultyCache[key] = new StarDifficulty(); From e70d2614747fafb47b6d0393117c890d0de176e3 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 12 Oct 2020 18:03:41 +0900 Subject: [PATCH 1650/1782] Add failing test --- .../Formats/LegacyBeatmapDecoderTest.cs | 39 +++++++++++++++++++ .../Resources/multi-segment-slider.osu | 8 ++++ 2 files changed, 47 insertions(+) create mode 100644 osu.Game.Tests/Resources/multi-segment-slider.osu diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index dab923d75b..74055ca3ce 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -651,5 +651,44 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.IsInstanceOf(decoder); } } + + [Test] + public void TestMultiSegmentSliders() + { + var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false }; + + using (var resStream = TestResources.OpenResource("multi-segment-slider.osu")) + using (var stream = new LineBufferedReader(resStream)) + { + var decoded = decoder.Decode(stream); + + // Multi-segment + var first = ((IHasPath)decoded.HitObjects[0]).Path; + + Assert.That(first.ControlPoints[0].Position.Value, Is.EqualTo(Vector2.Zero)); + Assert.That(first.ControlPoints[0].Type.Value, Is.EqualTo(PathType.PerfectCurve)); + Assert.That(first.ControlPoints[1].Position.Value, Is.EqualTo(new Vector2(161, -244))); + Assert.That(first.ControlPoints[1].Type.Value, Is.EqualTo(null)); + + Assert.That(first.ControlPoints[2].Position.Value, Is.EqualTo(new Vector2(376, -3))); + Assert.That(first.ControlPoints[2].Type.Value, Is.EqualTo(PathType.Bezier)); + Assert.That(first.ControlPoints[3].Position.Value, Is.EqualTo(new Vector2(68, 15))); + Assert.That(first.ControlPoints[3].Type.Value, Is.EqualTo(null)); + Assert.That(first.ControlPoints[4].Position.Value, Is.EqualTo(new Vector2(259, -132))); + Assert.That(first.ControlPoints[4].Type.Value, Is.EqualTo(null)); + Assert.That(first.ControlPoints[5].Position.Value, Is.EqualTo(new Vector2(92, -107))); + Assert.That(first.ControlPoints[5].Type.Value, Is.EqualTo(null)); + + // Single-segment + var second = ((IHasPath)decoded.HitObjects[1]).Path; + + Assert.That(second.ControlPoints[0].Position.Value, Is.EqualTo(Vector2.Zero)); + Assert.That(second.ControlPoints[0].Type.Value, Is.EqualTo(PathType.PerfectCurve)); + Assert.That(second.ControlPoints[1].Position.Value, Is.EqualTo(new Vector2(161, -244))); + Assert.That(second.ControlPoints[1].Type.Value, Is.EqualTo(null)); + Assert.That(second.ControlPoints[2].Position.Value, Is.EqualTo(new Vector2(376, -3))); + Assert.That(second.ControlPoints[2].Type.Value, Is.EqualTo(null)); + } + } } } diff --git a/osu.Game.Tests/Resources/multi-segment-slider.osu b/osu.Game.Tests/Resources/multi-segment-slider.osu new file mode 100644 index 0000000000..03cddba5e5 --- /dev/null +++ b/osu.Game.Tests/Resources/multi-segment-slider.osu @@ -0,0 +1,8 @@ +osu file format v128 + +[HitObjects] +// Multi-segment +63,301,1000,6,0,P|224:57|B|439:298|131:316|322:169|155:194,1,1040,0|0,0:0|0:0,0:0:0:0: + +// Single-segment +63,301,2000,6,0,P|224:57|439:298,1,1040,0|0,0:0|0:0,0:0:0:0: \ No newline at end of file From 48c0ae40effb05113829dc5c6dffc8ac985ada28 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 12 Oct 2020 18:04:28 +0900 Subject: [PATCH 1651/1782] Fix multi-segment sliders not parsing correctly --- .../Objects/Legacy/ConvertHitObjectParser.cs | 157 ++++++++++++------ 1 file changed, 102 insertions(+), 55 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs index f6adeced96..c5ea26bc20 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs @@ -70,53 +70,33 @@ namespace osu.Game.Rulesets.Objects.Legacy } else if (type.HasFlag(LegacyHitObjectType.Slider)) { - PathType pathType = PathType.Catmull; double? length = null; string[] pointSplit = split[5].Split('|'); - int pointCount = 1; + var controlPoints = new List>(); + int startIndex = 0; + int endIndex = 0; + bool first = true; - foreach (var t in pointSplit) + while (++endIndex < pointSplit.Length) { - if (t.Length > 1) - pointCount++; - } - - var points = new Vector2[pointCount]; - - int pointIndex = 1; - - foreach (string t in pointSplit) - { - if (t.Length == 1) - { - 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; - } - + // Keep incrementing endIndex while it's not the start of a new segment (indicated by having a type descriptor of length 1). + if (pointSplit[endIndex].Length > 1) 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; + // Multi-segmented sliders DON'T contain the end point as part of the current segment as it's assumed to be the start of the next segment. + // The start of the next segment is the index after the type descriptor. + string endPoint = endIndex < pointSplit.Length - 1 ? pointSplit[endIndex + 1] : null; + + controlPoints.Add(convertControlPoints(pointSplit.AsSpan().Slice(startIndex, endIndex - startIndex), endPoint, first, pos)); + startIndex = endIndex; + first = false; } + if (endIndex > startIndex) + controlPoints.Add(convertControlPoints(pointSplit.AsSpan().Slice(startIndex, endIndex - startIndex), null, first, pos)); + int repeatCount = Parsing.ParseInt(split[6]); if (repeatCount > 9000) @@ -183,7 +163,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, convertControlPoints(points, pathType), length, repeatCount, nodeSamples); + result = CreateSlider(pos, combo, comboOffset, mergeControlPoints(controlPoints), length, repeatCount, nodeSamples); } else if (type.HasFlag(LegacyHitObjectType.Spinner)) { @@ -252,8 +232,56 @@ namespace osu.Game.Rulesets.Objects.Legacy bankInfo.Filename = split.Length > 4 ? split[4] : null; } - private PathControlPoint[] convertControlPoints(Vector2[] vertices, PathType type) + private PathType convertPathType(string input) { + switch (input[0]) + { + default: + case 'C': + return PathType.Catmull; + + case 'B': + return PathType.Bezier; + + case 'L': + return PathType.Linear; + + case 'P': + return PathType.PerfectCurve; + } + } + + /// + /// Converts a given point list into a set of s. + /// + /// The point list. + /// Any extra endpoint to consider as part of the points. This will NOT be returned. + /// Whether this is the first point list in the set. If true the returned set will contain a zero point. + /// The positional offset to apply to the control points. + /// The set of points contained by , prepended by an extra zero point if is true. + private Memory convertControlPoints(ReadOnlySpan pointSpan, string endPoint, bool first, Vector2 offset) + { + PathType type = convertPathType(pointSpan[0]); + + int readOffset = first ? 1 : 0; // First control point is zero for the first segment. + int readablePoints = pointSpan.Length - 1; // Total points readable from the base point span. + int endPointLength = endPoint != null ? 1 : 0; // Extra length if an endpoint is given that lies outside the base point span. + + var vertices = new PathControlPoint[readOffset + readablePoints + endPointLength]; + + // Fill any non-read points. + for (int i = 0; i < readOffset; i++) + vertices[i] = new PathControlPoint(); + + // Parse into control points. + for (int i = 1; i < pointSpan.Length; i++) + readPoint(pointSpan[i], offset, out vertices[readOffset + i - 1]); + + // If an endpoint is given, add it now. + if (endPoint != null) + readPoint(endPoint, offset, out vertices[^1]); + + // Edge-case rules. if (type == PathType.PerfectCurve) { if (vertices.Length != 3) @@ -265,29 +293,48 @@ namespace osu.Game.Rulesets.Objects.Legacy } } - var points = new List(vertices.Length) - { - new PathControlPoint - { - Position = { Value = vertices[0] }, - Type = { Value = type } - } - }; + // Set a definite type for the first control point. + vertices[0].Type.Value = type; + // A path can have multiple segments of the same type if there are two sequential control points with the same position. for (int i = 1; i < vertices.Length; i++) { if (vertices[i] == vertices[i - 1]) - { - points[^1].Type.Value = type; - continue; - } - - points.Add(new PathControlPoint { Position = { Value = vertices[i] } }); + vertices[i].Type.Value = type; } - return points.ToArray(); + return vertices.AsMemory().Slice(0, vertices.Length - endPointLength); - 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)); + static void readPoint(string value, Vector2 startPos, out PathControlPoint point) + { + string[] vertexSplit = value.Split(':'); + + Vector2 pos = new Vector2((int)Parsing.ParseDouble(vertexSplit[0], Parsing.MAX_COORDINATE_VALUE), (int)Parsing.ParseDouble(vertexSplit[1], Parsing.MAX_COORDINATE_VALUE)) - startPos; + point = new PathControlPoint { Position = { Value = pos } }; + } + + static bool isLinear(PathControlPoint[] p) => Precision.AlmostEquals(0, (p[1].Position.Value.Y - p[0].Position.Value.Y) * (p[2].Position.Value.X - p[0].Position.Value.X) + - (p[1].Position.Value.X - p[0].Position.Value.X) * (p[2].Position.Value.Y - p[0].Position.Value.Y)); + } + + private PathControlPoint[] mergeControlPoints(List> controlPointList) + { + int totalCount = 0; + + foreach (var arr in controlPointList) + totalCount += arr.Length; + + var mergedArray = new PathControlPoint[totalCount]; + var mergedArrayMemory = mergedArray.AsMemory(); + int copyIndex = 0; + + foreach (var arr in controlPointList) + { + arr.CopyTo(mergedArrayMemory.Slice(copyIndex)); + copyIndex += arr.Length; + } + + return mergedArray; } /// From 36a8f61d264372e26b9da448c441519fd4a3be31 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 12 Oct 2020 18:58:07 +0900 Subject: [PATCH 1652/1782] Add failing test for implicit segments --- .../Formats/LegacyBeatmapDecoderTest.cs | 20 +++++++++++++++++++ .../Resources/multi-segment-slider.osu | 5 ++++- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index 74055ca3ce..58fd6b0448 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -688,6 +688,26 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.That(second.ControlPoints[1].Type.Value, Is.EqualTo(null)); Assert.That(second.ControlPoints[2].Position.Value, Is.EqualTo(new Vector2(376, -3))); Assert.That(second.ControlPoints[2].Type.Value, Is.EqualTo(null)); + + // Implicit multi-segment + var third = ((IHasPath)decoded.HitObjects[2]).Path; + + Assert.That(third.ControlPoints[0].Position.Value, Is.EqualTo(Vector2.Zero)); + Assert.That(third.ControlPoints[0].Type.Value, Is.EqualTo(PathType.Bezier)); + Assert.That(third.ControlPoints[1].Position.Value, Is.EqualTo(new Vector2(0, 192))); + Assert.That(third.ControlPoints[1].Type.Value, Is.EqualTo(null)); + Assert.That(third.ControlPoints[2].Position.Value, Is.EqualTo(new Vector2(224, 192))); + Assert.That(third.ControlPoints[2].Type.Value, Is.EqualTo(null)); + + Assert.That(third.ControlPoints[3].Position.Value, Is.EqualTo(new Vector2(224, 0))); + Assert.That(third.ControlPoints[3].Type.Value, Is.EqualTo(PathType.Bezier)); + Assert.That(third.ControlPoints[4].Position.Value, Is.EqualTo(new Vector2(224, -192))); + Assert.That(third.ControlPoints[4].Type.Value, Is.EqualTo(null)); + Assert.That(third.ControlPoints[5].Position.Value, Is.EqualTo(new Vector2(480, -192))); + Assert.That(third.ControlPoints[5].Type.Value, Is.EqualTo(null)); + Assert.That(third.ControlPoints[6].Position.Value, Is.EqualTo(new Vector2(480, 0))); + Assert.That(third.ControlPoints[6].Type.Value, Is.EqualTo(null)); + } } } diff --git a/osu.Game.Tests/Resources/multi-segment-slider.osu b/osu.Game.Tests/Resources/multi-segment-slider.osu index 03cddba5e5..6eabe640e4 100644 --- a/osu.Game.Tests/Resources/multi-segment-slider.osu +++ b/osu.Game.Tests/Resources/multi-segment-slider.osu @@ -5,4 +5,7 @@ osu file format v128 63,301,1000,6,0,P|224:57|B|439:298|131:316|322:169|155:194,1,1040,0|0,0:0|0:0,0:0:0:0: // Single-segment -63,301,2000,6,0,P|224:57|439:298,1,1040,0|0,0:0|0:0,0:0:0:0: \ No newline at end of file +63,301,2000,6,0,P|224:57|439:298,1,1040,0|0,0:0|0:0,0:0:0:0: + +// Implicit multi-segment +32,192,3000,6,0,B|32:384|256:384|256:192|256:192|256:0|512:0|512:192,1,800 From eb4ef157ca3d1d9059bcc7f8a41690a3fa32773b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 12 Oct 2020 19:16:37 +0900 Subject: [PATCH 1653/1782] Fix implicit segments not being constructed correctly --- .../Objects/Legacy/ConvertHitObjectParser.cs | 121 ++++++++++++------ 1 file changed, 80 insertions(+), 41 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs index c5ea26bc20..22447180ec 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs @@ -72,31 +72,6 @@ namespace osu.Game.Rulesets.Objects.Legacy { double? length = null; - string[] pointSplit = split[5].Split('|'); - - var controlPoints = new List>(); - int startIndex = 0; - int endIndex = 0; - bool first = true; - - while (++endIndex < pointSplit.Length) - { - // Keep incrementing endIndex while it's not the start of a new segment (indicated by having a type descriptor of length 1). - if (pointSplit[endIndex].Length > 1) - continue; - - // Multi-segmented sliders DON'T contain the end point as part of the current segment as it's assumed to be the start of the next segment. - // The start of the next segment is the index after the type descriptor. - string endPoint = endIndex < pointSplit.Length - 1 ? pointSplit[endIndex + 1] : null; - - controlPoints.Add(convertControlPoints(pointSplit.AsSpan().Slice(startIndex, endIndex - startIndex), endPoint, first, pos)); - startIndex = endIndex; - first = false; - } - - if (endIndex > startIndex) - controlPoints.Add(convertControlPoints(pointSplit.AsSpan().Slice(startIndex, endIndex - startIndex), null, first, pos)); - int repeatCount = Parsing.ParseInt(split[6]); if (repeatCount > 9000) @@ -163,7 +138,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, mergeControlPoints(controlPoints), length, repeatCount, nodeSamples); + result = CreateSlider(pos, combo, comboOffset, convertPathString(split[5], pos), length, repeatCount, nodeSamples); } else if (type.HasFlag(LegacyHitObjectType.Spinner)) { @@ -252,19 +227,71 @@ namespace osu.Game.Rulesets.Objects.Legacy } /// - /// Converts a given point list into a set of s. + /// Converts a given point string into a set of path control points. /// - /// The point list. - /// Any extra endpoint to consider as part of the points. This will NOT be returned. - /// Whether this is the first point list in the set. If true the returned set will contain a zero point. + /// + /// A point string takes the form: X|1:1|2:2|2:2|3:3|Y|1:1|2:2. + /// This has three segments: + /// + /// + /// X: { (1,1), (2,2) } (implicit segment) + /// + /// + /// X: { (2,2), (3,3) } (implicit segment) + /// + /// + /// Y: { (3,3), (1,1), (2, 2) } (explicit segment) + /// + /// + /// + /// The point string. /// The positional offset to apply to the control points. - /// The set of points contained by , prepended by an extra zero point if is true. - private Memory convertControlPoints(ReadOnlySpan pointSpan, string endPoint, bool first, Vector2 offset) + /// All control points in the resultant path. + private PathControlPoint[] convertPathString(string pointString, Vector2 offset) { - PathType type = convertPathType(pointSpan[0]); + // This code takes on the responsibility of handling explicit segments of the path ("X" & "Y" from above). Implicit segments are handled by calls to convertPoints(). + string[] pointSplit = pointString.Split('|'); + + var controlPoints = new List>(); + int startIndex = 0; + int endIndex = 0; + bool first = true; + + while (++endIndex < pointSplit.Length) + { + // Keep incrementing endIndex while it's not the start of a new segment (indicated by having a type descriptor of length 1). + if (pointSplit[endIndex].Length > 1) + continue; + + // Multi-segmented sliders DON'T contain the end point as part of the current segment as it's assumed to be the start of the next segment. + // The start of the next segment is the index after the type descriptor. + string endPoint = endIndex < pointSplit.Length - 1 ? pointSplit[endIndex + 1] : null; + + controlPoints.AddRange(convertPoints(pointSplit.AsMemory().Slice(startIndex, endIndex - startIndex), endPoint, first, offset)); + startIndex = endIndex; + first = false; + } + + if (endIndex > startIndex) + controlPoints.AddRange(convertPoints(pointSplit.AsMemory().Slice(startIndex, endIndex - startIndex), null, first, offset)); + + return mergePointsLists(controlPoints); + } + + /// + /// Converts a given point list into a set of path segments. + /// + /// The point list. + /// Any extra endpoint to consider as part of the points. This will NOT be returned. + /// Whether this is the first segment in the set. If true the first of the returned segments will contain a zero point. + /// The positional offset to apply to the control points. + /// The set of points contained by as one or more segments of the path, prepended by an extra zero point if is true. + private IEnumerable> convertPoints(ReadOnlyMemory points, string endPoint, bool first, Vector2 offset) + { + PathType type = convertPathType(points.Span[0]); int readOffset = first ? 1 : 0; // First control point is zero for the first segment. - int readablePoints = pointSpan.Length - 1; // Total points readable from the base point span. + int readablePoints = points.Length - 1; // Total points readable from the base point span. int endPointLength = endPoint != null ? 1 : 0; // Extra length if an endpoint is given that lies outside the base point span. var vertices = new PathControlPoint[readOffset + readablePoints + endPointLength]; @@ -274,8 +301,8 @@ namespace osu.Game.Rulesets.Objects.Legacy vertices[i] = new PathControlPoint(); // Parse into control points. - for (int i = 1; i < pointSpan.Length; i++) - readPoint(pointSpan[i], offset, out vertices[readOffset + i - 1]); + for (int i = 1; i < points.Length; i++) + readPoint(points.Span[i], offset, out vertices[readOffset + i - 1]); // If an endpoint is given, add it now. if (endPoint != null) @@ -297,13 +324,25 @@ namespace osu.Game.Rulesets.Objects.Legacy vertices[0].Type.Value = type; // A path can have multiple segments of the same type if there are two sequential control points with the same position. - for (int i = 1; i < vertices.Length; i++) + // To handle such cases, this code may return multiple path segments with the final control point in each segment having a non-null type. + int startIndex = 0; + int endIndex = 0; + + while (++endIndex < vertices.Length - endPointLength) { - if (vertices[i] == vertices[i - 1]) - vertices[i].Type.Value = type; + if (vertices[endIndex].Position.Value != vertices[endIndex - 1].Position.Value) + continue; + + // Force a type on the last point, and return the current control point set as a segment. + vertices[endIndex - 1].Type.Value = type; + yield return vertices.AsMemory().Slice(startIndex, endIndex - startIndex); + + // Skip the current control point - as it's the same as the one that's just been returned. + startIndex = endIndex + 1; } - return vertices.AsMemory().Slice(0, vertices.Length - endPointLength); + if (endIndex > startIndex) + yield return vertices.AsMemory().Slice(startIndex, endIndex - startIndex); static void readPoint(string value, Vector2 startPos, out PathControlPoint point) { @@ -317,7 +356,7 @@ namespace osu.Game.Rulesets.Objects.Legacy - (p[1].Position.Value.X - p[0].Position.Value.X) * (p[2].Position.Value.Y - p[0].Position.Value.Y)); } - private PathControlPoint[] mergeControlPoints(List> controlPointList) + private PathControlPoint[] mergePointsLists(List> controlPointList) { int totalCount = 0; From 372761a46ff3fdd8693f4646d43542dfe21e4af3 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 12 Oct 2020 19:22:34 +0900 Subject: [PATCH 1654/1782] More/better commenting --- .../Objects/Legacy/ConvertHitObjectParser.cs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs index 22447180ec..7dcbc52cea 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs @@ -304,11 +304,11 @@ namespace osu.Game.Rulesets.Objects.Legacy for (int i = 1; i < points.Length; i++) readPoint(points.Span[i], offset, out vertices[readOffset + i - 1]); - // If an endpoint is given, add it now. + // If an endpoint is given, add it to the end. if (endPoint != null) readPoint(endPoint, offset, out vertices[^1]); - // Edge-case rules. + // Edge-case rules (to match stable). if (type == PathType.PerfectCurve) { if (vertices.Length != 3) @@ -320,11 +320,15 @@ namespace osu.Game.Rulesets.Objects.Legacy } } - // Set a definite type for the first control point. + // The first control point must have a definite type. vertices[0].Type.Value = type; - // A path can have multiple segments of the same type if there are two sequential control points with the same position. + // A path can have multiple implicit segments of the same type if there are two sequential control points with the same position. // To handle such cases, this code may return multiple path segments with the final control point in each segment having a non-null type. + // For the point string X|1:1|2:2|2:2|3:3, this code returns the segments: + // X: { (1,1), (2, 2) } + // X: { (3, 3) } + // Note: (2, 2) is not returned in the second segments, as it is implicit in the path. int startIndex = 0; int endIndex = 0; From 58194b4a31169f1cb65b4a6a742078e2040a2d40 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 12 Oct 2020 19:36:35 +0900 Subject: [PATCH 1655/1782] Fix incorrect blank lines --- osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index 58fd6b0448..b6e1af57fd 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -707,7 +707,6 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.That(third.ControlPoints[5].Type.Value, Is.EqualTo(null)); Assert.That(third.ControlPoints[6].Position.Value, Is.EqualTo(new Vector2(480, 0))); Assert.That(third.ControlPoints[6].Type.Value, Is.EqualTo(null)); - } } } From 8768891b12a34c0473f460dea604ddb3d1747d2d Mon Sep 17 00:00:00 2001 From: Leon Gebler Date: Mon, 12 Oct 2020 14:41:05 +0200 Subject: [PATCH 1656/1782] Add testing for clicking mods through customisation menu --- .../UserInterface/TestSceneModSettings.cs | 42 ++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs index c5ce3751ef..d4c8b850d3 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs @@ -18,10 +18,11 @@ using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.UI; +using osuTK.Input; namespace osu.Game.Tests.Visual.UserInterface { - public class TestSceneModSettings : OsuTestScene + public class TestSceneModSettings : OsuManualInputManagerTestScene { private TestModSelectOverlay modSelect; @@ -29,6 +30,8 @@ namespace osu.Game.Tests.Visual.UserInterface private readonly Mod testCustomisableAutoOpenMod = new TestModCustomisable2(); + private readonly Mod testCustomisableMenuCoveredMod = new TestModCustomisable1(); + [SetUp] public void SetUp() => Schedule(() => { @@ -95,6 +98,30 @@ namespace osu.Game.Tests.Visual.UserInterface AddAssert("copy has original value", () => Precision.AlmostEquals(1.5, copy.SpeedChange.Value)); } + [Test] + public void TestCustomisationMenuNoClickthrough() + { + + createModSelect(); + openModSelect(); + + AddStep("change mod settings menu width to full screen", () => modSelect.SetModSettingsWidth(1.0f)); + AddStep("select cm2", () => modSelect.SelectMod(testCustomisableAutoOpenMod)); + AddAssert("Customisation opened", () => modSelect.ModSettingsContainer.Alpha == 1); + AddStep("hover over mod behind settings menu", () => InputManager.MoveMouseTo(modSelect.GetModButton(testCustomisableMenuCoveredMod))); + AddAssert("Mod is not considered hovered over", () => !modSelect.GetModButton(testCustomisableMenuCoveredMod).IsHovered); + AddStep("left click mod", () => InputManager.Click(MouseButton.Left)); + AddAssert("only cm2 is active", () => SelectedMods.Value.Count == 1); + AddStep("right click mod", () => InputManager.Click(MouseButton.Right)); + AddAssert("only cm2 is active", () => SelectedMods.Value.Count == 1); + } + + + + private void clickPosition(int x, int y) { + //Move cursor to coordinates + //Click coordinates + } private void createModSelect() { AddStep("create mod select", () => @@ -124,6 +151,19 @@ 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); + + public ModButton GetModButton(Mod mod) + { + return ModSectionsContainer.Children.Single(s => s.ModType == mod.Type). + ButtonsContainer.OfType().Single(b => b.Mods.Any(m => m.GetType() == mod.GetType())); + } + + public float SetModSettingsWidth(float NewWidth) + { + float oldWidth = ModSettingsContainer.Width; + ModSettingsContainer.Width = NewWidth; + return oldWidth; + } } public class TestRulesetInfo : RulesetInfo From 7df9282727c6997ccccc0df7ee55e930ffb674fe Mon Sep 17 00:00:00 2001 From: Leon Gebler Date: Mon, 12 Oct 2020 15:58:34 +0200 Subject: [PATCH 1657/1782] CodeAnalysis fixes --- .../UserInterface/TestSceneModSettings.cs | 22 ++++++------------- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 1 - 2 files changed, 7 insertions(+), 16 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs index d4c8b850d3..a31e244ca5 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs @@ -101,7 +101,6 @@ namespace osu.Game.Tests.Visual.UserInterface [Test] public void TestCustomisationMenuNoClickthrough() { - createModSelect(); openModSelect(); @@ -116,12 +115,6 @@ namespace osu.Game.Tests.Visual.UserInterface AddAssert("only cm2 is active", () => SelectedMods.Value.Count == 1); } - - - private void clickPosition(int x, int y) { - //Move cursor to coordinates - //Click coordinates - } private void createModSelect() { AddStep("create mod select", () => @@ -148,20 +141,19 @@ namespace osu.Game.Tests.Visual.UserInterface 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); - public ModButton GetModButton(Mod mod) { - return ModSectionsContainer.Children.Single(s => s.ModType == mod.Type). - ButtonsContainer.OfType().Single(b => b.Mods.Any(m => m.GetType() == mod.GetType())); + return ModSectionsContainer.Children.Single(s => s.ModType == mod.Type) + .ButtonsContainer.OfType().Single(b => b.Mods.Any(m => m.GetType() == mod.GetType())); } - public float SetModSettingsWidth(float NewWidth) + public void SelectMod(Mod mod) => + GetModButton(mod).SelectNext(1); + + public float SetModSettingsWidth(float newWidth) { float oldWidth = ModSettingsContainer.Width; - ModSettingsContainer.Width = NewWidth; + ModSettingsContainer.Width = newWidth; return oldWidth; } } diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 3b158ee98d..37541358b8 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -508,6 +508,5 @@ namespace osu.Game.Overlays.Mods return true; } } - } } From 3224aa7a695828835a8aa00556614c50ecf53e8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 12 Oct 2020 17:31:46 +0200 Subject: [PATCH 1658/1782] Clarify test math even further --- .../Rulesets/Scoring/ScoreProcessorTest.cs | 58 +++++++++---------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs index b83b97a539..30b47d9bd7 100644 --- a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs +++ b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs @@ -65,7 +65,7 @@ namespace osu.Game.Tests.Rulesets.Scoring /// This test intentionally misses the 3rd hitobject to achieve lower than 75% accuracy and exactly 50% max combo. /// /// For standardised scoring, is calculated using the following formula: - /// 1_000_000 * ((3 * / 4 * ) * 30% + 50% * 70%) + /// 1_000_000 * (((3 * ) / (4 * )) * 30% + 50% * 70%) /// /// /// For classic scoring, is calculated using the following formula: @@ -74,30 +74,30 @@ namespace osu.Game.Tests.Rulesets.Scoring /// 75% * 4 * 300 * (1 + 1/25) /// /// - [TestCase(ScoringMode.Standardised, HitResult.Miss, HitResult.Great, 0)] - [TestCase(ScoringMode.Standardised, HitResult.Meh, HitResult.Great, 387_500)] - [TestCase(ScoringMode.Standardised, HitResult.Ok, HitResult.Great, 425_000)] - [TestCase(ScoringMode.Standardised, HitResult.Good, HitResult.Perfect, 478_571)] - [TestCase(ScoringMode.Standardised, HitResult.Great, HitResult.Great, 575_000)] - [TestCase(ScoringMode.Standardised, HitResult.Perfect, HitResult.Perfect, 575_000)] - [TestCase(ScoringMode.Standardised, HitResult.SmallTickMiss, HitResult.SmallTickHit, 700_000)] - [TestCase(ScoringMode.Standardised, HitResult.SmallTickHit, HitResult.SmallTickHit, 925_000)] - [TestCase(ScoringMode.Standardised, HitResult.LargeTickMiss, HitResult.LargeTickHit, 0)] - [TestCase(ScoringMode.Standardised, HitResult.LargeTickHit, HitResult.LargeTickHit, 575_000)] - [TestCase(ScoringMode.Standardised, HitResult.SmallBonus, HitResult.SmallBonus, 700_030)] - [TestCase(ScoringMode.Standardised, HitResult.LargeBonus, HitResult.LargeBonus, 700_150)] - [TestCase(ScoringMode.Classic, HitResult.Miss, HitResult.Great, 0)] - [TestCase(ScoringMode.Classic, HitResult.Meh, HitResult.Great, 156)] - [TestCase(ScoringMode.Classic, HitResult.Ok, HitResult.Great, 312)] - [TestCase(ScoringMode.Classic, HitResult.Good, HitResult.Perfect, 535)] - [TestCase(ScoringMode.Classic, HitResult.Great, HitResult.Great, 936)] - [TestCase(ScoringMode.Classic, HitResult.Perfect, HitResult.Perfect, 936)] - [TestCase(ScoringMode.Classic, HitResult.SmallTickMiss, HitResult.SmallTickHit, 0)] - [TestCase(ScoringMode.Classic, HitResult.SmallTickHit, HitResult.SmallTickHit, 225)] - [TestCase(ScoringMode.Classic, HitResult.LargeTickMiss, HitResult.LargeTickHit, 0)] - [TestCase(ScoringMode.Classic, HitResult.LargeTickHit, HitResult.LargeTickHit, 936)] - [TestCase(ScoringMode.Classic, HitResult.SmallBonus, HitResult.SmallBonus, 30)] - [TestCase(ScoringMode.Classic, HitResult.LargeBonus, HitResult.LargeBonus, 150)] + [TestCase(ScoringMode.Standardised, HitResult.Miss, HitResult.Great, 0)] // (3 * 0) / (4 * 300) * 300_000 + (0 / 4) * 700_000 + [TestCase(ScoringMode.Standardised, HitResult.Meh, HitResult.Great, 387_500)] // (3 * 50) / (4 * 300) * 300_000 + (2 / 4) * 700_000 + [TestCase(ScoringMode.Standardised, HitResult.Ok, HitResult.Great, 425_000)] // (3 * 100) / (4 * 300) * 300_000 + (2 / 4) * 700_000 + [TestCase(ScoringMode.Standardised, HitResult.Good, HitResult.Perfect, 478_571)] // (3 * 200) / (4 * 350) * 300_000 + (2 / 4) * 700_000 + [TestCase(ScoringMode.Standardised, HitResult.Great, HitResult.Great, 575_000)] // (3 * 300) / (4 * 300) * 300_000 + (2 / 4) * 700_000 + [TestCase(ScoringMode.Standardised, HitResult.Perfect, HitResult.Perfect, 575_000)] // (3 * 350) / (4 * 350) * 300_000 + (2 / 4) * 700_000 + [TestCase(ScoringMode.Standardised, HitResult.SmallTickMiss, HitResult.SmallTickHit, 700_000)] // (3 * 0) / (4 * 10) * 300_000 + 700_000 (max combo 0) + [TestCase(ScoringMode.Standardised, HitResult.SmallTickHit, HitResult.SmallTickHit, 925_000)] // (3 * 10) / (4 * 10) * 300_000 + 700_000 (max combo 0) + [TestCase(ScoringMode.Standardised, HitResult.LargeTickMiss, HitResult.LargeTickHit, 0)] // (3 * 0) / (4 * 30) * 300_000 + (0 / 4) * 700_000 + [TestCase(ScoringMode.Standardised, HitResult.LargeTickHit, HitResult.LargeTickHit, 575_000)] // (3 * 30) / (4 * 30) * 300_000 + (0 / 4) * 700_000 + [TestCase(ScoringMode.Standardised, HitResult.SmallBonus, HitResult.SmallBonus, 700_030)] // 0 * 300_000 + 700_000 (max combo 0) + 3 * 10 (bonus points) + [TestCase(ScoringMode.Standardised, HitResult.LargeBonus, HitResult.LargeBonus, 700_150)] // 0 * 300_000 + 700_000 (max combo 0) + 3 * 50 (bonus points) + [TestCase(ScoringMode.Classic, HitResult.Miss, HitResult.Great, 0)] // (0 * 4 * 300) * (1 + 0 / 25) + [TestCase(ScoringMode.Classic, HitResult.Meh, HitResult.Great, 156)] // (((3 * 50) / (4 * 300)) * 4 * 300) * (1 + 1 / 25) + [TestCase(ScoringMode.Classic, HitResult.Ok, HitResult.Great, 312)] // (((3 * 100) / (4 * 300)) * 4 * 300) * (1 + 1 / 25) + [TestCase(ScoringMode.Classic, HitResult.Good, HitResult.Perfect, 535)] // (((3 * 200) / (4 * 350)) * 4 * 300) * (1 + 1 / 25) + [TestCase(ScoringMode.Classic, HitResult.Great, HitResult.Great, 936)] // (((3 * 300) / (4 * 300)) * 4 * 300) * (1 + 1 / 25) + [TestCase(ScoringMode.Classic, HitResult.Perfect, HitResult.Perfect, 936)] // (((3 * 350) / (4 * 350)) * 4 * 300) * (1 + 1 / 25) + [TestCase(ScoringMode.Classic, HitResult.SmallTickMiss, HitResult.SmallTickHit, 0)] // (0 * 1 * 300) * (1 + 0 / 25) + [TestCase(ScoringMode.Classic, HitResult.SmallTickHit, HitResult.SmallTickHit, 225)] // (((3 * 10) / (4 * 10)) * 1 * 300) * (1 + 0 / 25) + [TestCase(ScoringMode.Classic, HitResult.LargeTickMiss, HitResult.LargeTickHit, 0)] // (0 * 4 * 300) * (1 + 0 / 25) + [TestCase(ScoringMode.Classic, HitResult.LargeTickHit, HitResult.LargeTickHit, 936)] // (((3 * 50) / (4 * 50)) * 4 * 300) * (1 + 1 / 25) + [TestCase(ScoringMode.Classic, HitResult.SmallBonus, HitResult.SmallBonus, 30)] // (0 * 1 * 300) * (1 + 0 / 25) + 3 * 10 (bonus points) + [TestCase(ScoringMode.Classic, HitResult.LargeBonus, HitResult.LargeBonus, 150)] // (0 * 1 * 300) * (1 + 0 / 25) * 3 * 50 (bonus points) public void TestFourVariousResultsOneMiss(ScoringMode scoringMode, HitResult hitResult, HitResult maxResult, int expectedScore) { var minResult = new TestJudgement(hitResult).MinResult; @@ -121,10 +121,10 @@ namespace osu.Game.Tests.Rulesets.Scoring Assert.IsTrue(Precision.AlmostEquals(expectedScore, scoreProcessor.TotalScore.Value, 0.5)); } - [TestCase(ScoringMode.Standardised, HitResult.SmallTickHit, 978_571)] - [TestCase(ScoringMode.Standardised, HitResult.SmallTickMiss, 914_286)] - [TestCase(ScoringMode.Classic, HitResult.SmallTickHit, 279)] - [TestCase(ScoringMode.Classic, HitResult.SmallTickMiss, 214)] + [TestCase(ScoringMode.Standardised, HitResult.SmallTickHit, 978_571)] // (3 * 10 + 100) / (4 * 10 + 100) * 300_000 + (1 / 1) * 700_000 + [TestCase(ScoringMode.Standardised, HitResult.SmallTickMiss, 914_286)] // (3 * 0 + 100) / (4 * 10 + 100) * 300_000 + (1 / 1) * 700_000 + [TestCase(ScoringMode.Classic, HitResult.SmallTickHit, 279)] // (((3 * 10 + 100) / (4 * 10 + 100)) * 1 * 300) * (1 + 0 / 25) + [TestCase(ScoringMode.Classic, HitResult.SmallTickMiss, 214)] // (((3 * 0 + 100) / (4 * 10 + 100)) * 1 * 300) * (1 + 0 / 25) public void TestSmallTicksAccuracy(ScoringMode scoringMode, HitResult hitResult, int expectedScore) { IEnumerable hitObjects = Enumerable From 82a28d4655277a6a061b5d09ce0b33afc1dd4317 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 12 Oct 2020 17:34:20 +0200 Subject: [PATCH 1659/1782] Fix some inaccuracies --- osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs index 30b47d9bd7..0ca5101292 100644 --- a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs +++ b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs @@ -62,10 +62,10 @@ namespace osu.Game.Tests.Rulesets.Scoring /// The maximum achievable. /// Expected score after all objects have been judged, rounded to the nearest integer. /// - /// This test intentionally misses the 3rd hitobject to achieve lower than 75% accuracy and exactly 50% max combo. + /// This test intentionally misses the 3rd hitobject to achieve lower than 75% accuracy and 50% max combo. /// /// For standardised scoring, is calculated using the following formula: - /// 1_000_000 * (((3 * ) / (4 * )) * 30% + 50% * 70%) + /// 1_000_000 * (((3 * ) / (4 * )) * 30% + (bestCombo / maxCombo) * 70%) /// /// /// For classic scoring, is calculated using the following formula: From 25d9b1ecd08ba1b467c18b3b2ce3c4f762230697 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 12 Oct 2020 17:36:07 +0200 Subject: [PATCH 1660/1782] Clarify purpose and construction of extra test --- osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs index 0ca5101292..9f16312121 100644 --- a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs +++ b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs @@ -121,6 +121,12 @@ namespace osu.Game.Tests.Rulesets.Scoring Assert.IsTrue(Precision.AlmostEquals(expectedScore, scoreProcessor.TotalScore.Value, 0.5)); } + /// + /// This test uses a beatmap with four small ticks and one object with the of . + /// Its goal is to ensure that with the of , + /// small ticks contribute to the accuracy portion, but not the combo portion. + /// In contrast, does not have separate combo and accuracy portion (they are multiplied by each other). + /// [TestCase(ScoringMode.Standardised, HitResult.SmallTickHit, 978_571)] // (3 * 10 + 100) / (4 * 10 + 100) * 300_000 + (1 / 1) * 700_000 [TestCase(ScoringMode.Standardised, HitResult.SmallTickMiss, 914_286)] // (3 * 0 + 100) / (4 * 10 + 100) * 300_000 + (1 / 1) * 700_000 [TestCase(ScoringMode.Classic, HitResult.SmallTickHit, 279)] // (((3 * 10 + 100) / (4 * 10 + 100)) * 1 * 300) * (1 + 0 / 25) From 1a85123b890c8630459561c80095bba22e1df7de Mon Sep 17 00:00:00 2001 From: Leon Gebler Date: Mon, 12 Oct 2020 21:24:42 +0200 Subject: [PATCH 1661/1782] rename container class to be more descriptive --- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 37541358b8..f74f40b9b4 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -284,7 +284,7 @@ namespace osu.Game.Overlays.Mods }, }, }, - ModSettingsContainer = new CModSettingsContainer + ModSettingsContainer = new MouseInputAbsorbingContainer { RelativeSizeAxes = Axes.Both, Anchor = Anchor.BottomRight, @@ -496,17 +496,11 @@ namespace osu.Game.Overlays.Mods #endregion - protected class CModSettingsContainer : Container + protected class MouseInputAbsorbingContainer : Container { - protected override bool OnMouseDown(MouseDownEvent e) - { - return true; - } + protected override bool OnMouseDown(MouseDownEvent e) => true; - protected override bool OnHover(HoverEvent e) - { - return true; - } + protected override bool OnHover(HoverEvent e) => true; } } } From 663b8069746a5b79c1acc0b276a31be0e8710825 Mon Sep 17 00:00:00 2001 From: Leon Gebler Date: Tue, 13 Oct 2020 17:45:40 +0200 Subject: [PATCH 1662/1782] move ModSettingsContainer to seperate component --- .../Overlays/Mods/CModSettingsContainer.cs | 71 +++++++++++++++++++ osu.Game/Overlays/Mods/ModSelectOverlay.cs | 56 +-------------- 2 files changed, 74 insertions(+), 53 deletions(-) create mode 100644 osu.Game/Overlays/Mods/CModSettingsContainer.cs diff --git a/osu.Game/Overlays/Mods/CModSettingsContainer.cs b/osu.Game/Overlays/Mods/CModSettingsContainer.cs new file mode 100644 index 0000000000..a5f33e46c4 --- /dev/null +++ b/osu.Game/Overlays/Mods/CModSettingsContainer.cs @@ -0,0 +1,71 @@ +// 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.Bindables; +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.Containers; +using osu.Game.Rulesets.Mods; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Overlays.Mods +{ + public class CModSettingsContainer : Container + { + private readonly FillFlowContainer modSettingsContent; + + public CModSettingsContainer() + { + 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), + } + } + }; + } + + ///Bool indicating whether any settings are listed + public bool UpdateModSettings(ValueChangedEvent> mods) + { + modSettingsContent.Clear(); + + foreach (var mod in mods.NewValue) + { + var settings = mod.CreateSettingsControls().ToList(); + if (settings.Count > 0) + modSettingsContent.Add(new ModControlSection(mod, settings)); + } + + bool hasSettings = modSettingsContent.Count > 0; + + if (!hasSettings) + Hide(); + + return hasSettings; + } + + protected override bool OnMouseDown(MouseDownEvent e) => true; + protected override bool OnHover(HoverEvent e) => true; + } +} diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index f74f40b9b4..b9a37094d7 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -13,7 +13,6 @@ 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; @@ -45,9 +44,7 @@ namespace osu.Game.Overlays.Mods protected readonly FillFlowContainer ModSectionsContainer; - protected readonly FillFlowContainer ModSettingsContent; - - protected readonly Container ModSettingsContainer; + protected readonly CModSettingsContainer ModSettingsContainer; public readonly Bindable> SelectedMods = new Bindable>(Array.Empty()); @@ -284,7 +281,7 @@ namespace osu.Game.Overlays.Mods }, }, }, - ModSettingsContainer = new MouseInputAbsorbingContainer + ModSettingsContainer = new CModSettingsContainer { RelativeSizeAxes = Axes.Both, Anchor = Anchor.BottomRight, @@ -292,27 +289,6 @@ namespace osu.Game.Overlays.Mods 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), - } - } - } } }; } @@ -424,7 +400,7 @@ namespace osu.Game.Overlays.Mods updateMods(); - updateModSettings(mods); + CustomiseButton.Enabled.Value = ModSettingsContainer.UpdateModSettings(mods); } private void updateMods() @@ -445,25 +421,6 @@ namespace osu.Game.Overlays.Mods MultiplierLabel.FadeColour(Color4.White, 200); } - private void updateModSettings(ValueChangedEvent> selectedMods) - { - ModSettingsContent.Clear(); - - foreach (var mod in selectedMods.NewValue) - { - var settings = mod.CreateSettingsControls().ToList(); - if (settings.Count > 0) - ModSettingsContent.Add(new ModControlSection(mod, settings)); - } - - bool hasSettings = ModSettingsContent.Count > 0; - - CustomiseButton.Enabled.Value = hasSettings; - - if (!hasSettings) - ModSettingsContainer.Hide(); - } - private void modButtonPressed(Mod selectedMod) { if (selectedMod != null) @@ -495,12 +452,5 @@ namespace osu.Game.Overlays.Mods } #endregion - - protected class MouseInputAbsorbingContainer : Container - { - protected override bool OnMouseDown(MouseDownEvent e) => true; - - protected override bool OnHover(HoverEvent e) => true; - } } } From 28d3295f9f25df1cf60f77dd42353ef736390d6f Mon Sep 17 00:00:00 2001 From: Leon Gebler Date: Tue, 13 Oct 2020 19:20:15 +0200 Subject: [PATCH 1663/1782] Test Class Fixes --- .../Visual/UserInterface/TestSceneModSettings.cs | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs index a31e244ca5..6a46ff2666 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs @@ -9,6 +9,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Utils; +using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Graphics.UserInterface; @@ -30,8 +31,6 @@ namespace osu.Game.Tests.Visual.UserInterface private readonly Mod testCustomisableAutoOpenMod = new TestModCustomisable2(); - private readonly Mod testCustomisableMenuCoveredMod = new TestModCustomisable1(); - [SetUp] public void SetUp() => Schedule(() => { @@ -107,8 +106,8 @@ namespace osu.Game.Tests.Visual.UserInterface AddStep("change mod settings menu width to full screen", () => modSelect.SetModSettingsWidth(1.0f)); AddStep("select cm2", () => modSelect.SelectMod(testCustomisableAutoOpenMod)); AddAssert("Customisation opened", () => modSelect.ModSettingsContainer.Alpha == 1); - AddStep("hover over mod behind settings menu", () => InputManager.MoveMouseTo(modSelect.GetModButton(testCustomisableMenuCoveredMod))); - AddAssert("Mod is not considered hovered over", () => !modSelect.GetModButton(testCustomisableMenuCoveredMod).IsHovered); + AddStep("hover over mod behind settings menu", () => InputManager.MoveMouseTo(modSelect.GetModButton(testCustomisableMod))); + AddAssert("Mod is not considered hovered over", () => !modSelect.GetModButton(testCustomisableMod).IsHovered); AddStep("left click mod", () => InputManager.Click(MouseButton.Left)); AddAssert("only cm2 is active", () => SelectedMods.Value.Count == 1); AddStep("right click mod", () => InputManager.Click(MouseButton.Right)); @@ -143,19 +142,14 @@ namespace osu.Game.Tests.Visual.UserInterface public ModButton GetModButton(Mod mod) { - return ModSectionsContainer.Children.Single(s => s.ModType == mod.Type) - .ButtonsContainer.OfType().Single(b => b.Mods.Any(m => m.GetType() == mod.GetType())); + return ModSectionsContainer.ChildrenOfType().Single(b => b.Mods.Any(m => m.GetType() == mod.GetType())); } public void SelectMod(Mod mod) => GetModButton(mod).SelectNext(1); - public float SetModSettingsWidth(float newWidth) - { - float oldWidth = ModSettingsContainer.Width; + public void SetModSettingsWidth(float newWidth) => ModSettingsContainer.Width = newWidth; - return oldWidth; - } } public class TestRulesetInfo : RulesetInfo From 3fd913b13f5a4b317aa5edd45b5cf00de6d45b21 Mon Sep 17 00:00:00 2001 From: Leon Gebler Date: Tue, 13 Oct 2020 19:25:42 +0200 Subject: [PATCH 1664/1782] rename customisation container class --- ...{CModSettingsContainer.cs => ModCustomisationContainer.cs} | 4 ++-- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) rename osu.Game/Overlays/Mods/{CModSettingsContainer.cs => ModCustomisationContainer.cs} (95%) diff --git a/osu.Game/Overlays/Mods/CModSettingsContainer.cs b/osu.Game/Overlays/Mods/ModCustomisationContainer.cs similarity index 95% rename from osu.Game/Overlays/Mods/CModSettingsContainer.cs rename to osu.Game/Overlays/Mods/ModCustomisationContainer.cs index a5f33e46c4..487d92882a 100644 --- a/osu.Game/Overlays/Mods/CModSettingsContainer.cs +++ b/osu.Game/Overlays/Mods/ModCustomisationContainer.cs @@ -16,11 +16,11 @@ using osuTK.Graphics; namespace osu.Game.Overlays.Mods { - public class CModSettingsContainer : Container + public class ModCustomisationContainer : Container { private readonly FillFlowContainer modSettingsContent; - public CModSettingsContainer() + public ModCustomisationContainer() { Children = new Drawable[] { diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index b9a37094d7..b1ffd26bb9 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -44,7 +44,7 @@ namespace osu.Game.Overlays.Mods protected readonly FillFlowContainer ModSectionsContainer; - protected readonly CModSettingsContainer ModSettingsContainer; + protected readonly ModCustomisationContainer ModSettingsContainer; public readonly Bindable> SelectedMods = new Bindable>(Array.Empty()); @@ -281,7 +281,7 @@ namespace osu.Game.Overlays.Mods }, }, }, - ModSettingsContainer = new CModSettingsContainer + ModSettingsContainer = new ModCustomisationContainer { RelativeSizeAxes = Axes.Both, Anchor = Anchor.BottomRight, From 24eff8c66d8aab85a6509f9ea095c13cd5e8a09e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 14 Oct 2020 15:13:49 +0900 Subject: [PATCH 1665/1782] Rename container to match "settings" term used everywhere --- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 4 ++-- .../{ModCustomisationContainer.cs => ModSettingsContainer.cs} | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) rename osu.Game/Overlays/Mods/{ModCustomisationContainer.cs => ModSettingsContainer.cs} (95%) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index b1ffd26bb9..2d8b4dba7c 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -44,7 +44,7 @@ namespace osu.Game.Overlays.Mods protected readonly FillFlowContainer ModSectionsContainer; - protected readonly ModCustomisationContainer ModSettingsContainer; + protected readonly ModSettingsContainer ModSettingsContainer; public readonly Bindable> SelectedMods = new Bindable>(Array.Empty()); @@ -281,7 +281,7 @@ namespace osu.Game.Overlays.Mods }, }, }, - ModSettingsContainer = new ModCustomisationContainer + ModSettingsContainer = new ModSettingsContainer { RelativeSizeAxes = Axes.Both, Anchor = Anchor.BottomRight, diff --git a/osu.Game/Overlays/Mods/ModCustomisationContainer.cs b/osu.Game/Overlays/Mods/ModSettingsContainer.cs similarity index 95% rename from osu.Game/Overlays/Mods/ModCustomisationContainer.cs rename to osu.Game/Overlays/Mods/ModSettingsContainer.cs index 487d92882a..0521bc35b8 100644 --- a/osu.Game/Overlays/Mods/ModCustomisationContainer.cs +++ b/osu.Game/Overlays/Mods/ModSettingsContainer.cs @@ -16,11 +16,11 @@ using osuTK.Graphics; namespace osu.Game.Overlays.Mods { - public class ModCustomisationContainer : Container + public class ModSettingsContainer : Container { private readonly FillFlowContainer modSettingsContent; - public ModCustomisationContainer() + public ModSettingsContainer() { Children = new Drawable[] { From 3e326a9234cb99d743f8ae80de42d85b4a43428c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 14 Oct 2020 15:21:28 +0900 Subject: [PATCH 1666/1782] Use bindable flow for event propagation --- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 5 +++-- .../Overlays/Mods/ModSettingsContainer.cs | 19 ++++++++++++++++--- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 2d8b4dba7c..31adf47456 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -289,8 +289,11 @@ namespace osu.Game.Overlays.Mods Width = 0.25f, Alpha = 0, X = -100, + SelectedMods = { BindTarget = SelectedMods }, } }; + + ((IBindable)CustomiseButton.Enabled).BindTo(ModSettingsContainer.HasSettingsForSelection); } [BackgroundDependencyLoader(true)] @@ -399,8 +402,6 @@ namespace osu.Game.Overlays.Mods section.SelectTypes(mods.NewValue.Select(m => m.GetType()).ToList()); updateMods(); - - CustomiseButton.Enabled.Value = ModSettingsContainer.UpdateModSettings(mods); } private void updateMods() diff --git a/osu.Game/Overlays/Mods/ModSettingsContainer.cs b/osu.Game/Overlays/Mods/ModSettingsContainer.cs index 0521bc35b8..b185b56ecd 100644 --- a/osu.Game/Overlays/Mods/ModSettingsContainer.cs +++ b/osu.Game/Overlays/Mods/ModSettingsContainer.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; @@ -18,6 +19,12 @@ namespace osu.Game.Overlays.Mods { public class ModSettingsContainer : Container { + public readonly IBindable> SelectedMods = new Bindable>(Array.Empty()); + + public IBindable HasSettingsForSelection => hasSettingsForSelection; + + private readonly Bindable hasSettingsForSelection = new Bindable(); + private readonly FillFlowContainer modSettingsContent; public ModSettingsContainer() @@ -45,8 +52,14 @@ namespace osu.Game.Overlays.Mods }; } - ///Bool indicating whether any settings are listed - public bool UpdateModSettings(ValueChangedEvent> mods) + protected override void LoadComplete() + { + base.LoadComplete(); + + SelectedMods.BindValueChanged(modsChanged, true); + } + + private void modsChanged(ValueChangedEvent> mods) { modSettingsContent.Clear(); @@ -62,7 +75,7 @@ namespace osu.Game.Overlays.Mods if (!hasSettings) Hide(); - return hasSettings; + hasSettingsForSelection.Value = hasSettings; } protected override bool OnMouseDown(MouseDownEvent e) => true; From 4eccb03d71de9dd3ddb8e60c5a854bb92df1515e Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 14 Oct 2020 17:08:14 +0900 Subject: [PATCH 1667/1782] Add copyright notice Co-authored-by: Dean Herbert --- osu.Game.Rulesets.Mania/MathUtils/LegacySortHelper.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Rulesets.Mania/MathUtils/LegacySortHelper.cs b/osu.Game.Rulesets.Mania/MathUtils/LegacySortHelper.cs index 421cc0ae04..0f4829028f 100644 --- a/osu.Game.Rulesets.Mania/MathUtils/LegacySortHelper.cs +++ b/osu.Game.Rulesets.Mania/MathUtils/LegacySortHelper.cs @@ -12,6 +12,7 @@ namespace osu.Game.Rulesets.Mania.MathUtils /// /// /// Source: https://referencesource.microsoft.com/#mscorlib/system/collections/generic/arraysorthelper.cs + /// Copyright (c) Microsoft Corporation. All rights reserved. /// internal static class LegacySortHelper { From f9bdb664ee7b8fd1c2a041f6d91b92fedca32d3b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 14 Oct 2020 17:09:01 +0900 Subject: [PATCH 1668/1782] Update diffcalc test --- osu.Game.Rulesets.Mania.Tests/ManiaDifficultyCalculatorTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaDifficultyCalculatorTest.cs b/osu.Game.Rulesets.Mania.Tests/ManiaDifficultyCalculatorTest.cs index 2c36e81190..a25551f854 100644 --- a/osu.Game.Rulesets.Mania.Tests/ManiaDifficultyCalculatorTest.cs +++ b/osu.Game.Rulesets.Mania.Tests/ManiaDifficultyCalculatorTest.cs @@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.Mania.Tests { protected override string ResourceAssembly => "osu.Game.Rulesets.Mania"; - [TestCase(2.3683365342338796d, "diffcalc-test")] + [TestCase(2.3449735700206298d, "diffcalc-test")] public void Test(double expected, string name) => base.Test(expected, name); From 3e6ed6c9ffe327c9767b9fb054e396bf1a295b9d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 14 Oct 2020 17:53:28 +0900 Subject: [PATCH 1669/1782] Add support for dual stages (keycoop) and score multiplier --- .../Beatmaps/ManiaBeatmap.cs | 9 ++++- .../Beatmaps/ManiaBeatmapConverter.cs | 6 +++- .../Difficulty/ManiaDifficultyAttributes.cs | 1 + .../Difficulty/ManiaDifficultyCalculator.cs | 33 +++++++++++++++++++ 4 files changed, 47 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmap.cs b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmap.cs index d1d5adea75..93a9ce3dbd 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmap.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmap.cs @@ -21,13 +21,20 @@ namespace osu.Game.Rulesets.Mania.Beatmaps /// public int TotalColumns => Stages.Sum(g => g.Columns); + /// + /// The total number of columns that were present in this before any user adjustments. + /// + public readonly int OriginalTotalColumns; + /// /// Creates a new . /// /// The initial stages. - public ManiaBeatmap(StageDefinition defaultStage) + /// The total number of columns present before any user adjustments. Defaults to the total columns in . + public ManiaBeatmap(StageDefinition defaultStage, int? originalTotalColumns = null) { Stages.Add(defaultStage); + OriginalTotalColumns = originalTotalColumns ?? defaultStage.Columns; } public override IEnumerable GetStatistics() diff --git a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs index b17ab3f375..757329c525 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs @@ -28,6 +28,8 @@ namespace osu.Game.Rulesets.Mania.Beatmaps public bool Dual; public readonly bool IsForCurrentRuleset; + private int originalTargetColumns; + // Internal for testing purposes internal FastRandom Random { get; private set; } @@ -65,6 +67,8 @@ namespace osu.Game.Rulesets.Mania.Beatmaps else TargetColumns = Math.Max(4, Math.Min((int)roundedOverallDifficulty + 1, 7)); } + + originalTargetColumns = TargetColumns; } public override bool CanConvert() => Beatmap.HitObjects.All(h => h is IHasXPosition); @@ -81,7 +85,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps protected override Beatmap CreateBeatmap() { - beatmap = new ManiaBeatmap(new StageDefinition { Columns = TargetColumns }); + beatmap = new ManiaBeatmap(new StageDefinition { Columns = TargetColumns }, originalTargetColumns); if (Dual) beatmap.Stages.Add(new StageDefinition { Columns = TargetColumns }); diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyAttributes.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyAttributes.cs index 3ff665d2c8..0b58d1efc6 100644 --- a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyAttributes.cs +++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyAttributes.cs @@ -8,5 +8,6 @@ namespace osu.Game.Rulesets.Mania.Difficulty public class ManiaDifficultyAttributes : DifficultyAttributes { public double GreatHitWindow; + public double ScoreMultiplier; } } diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs index a3694f354b..356621acda 100644 --- a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs @@ -47,6 +47,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty 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.WindowFor(HitResult.Great)) / clockRate, + ScoreMultiplier = getScoreMultiplier(beatmap, mods), MaxCombo = beatmap.HitObjects.Sum(h => h is HoldNote ? 2 : 1), Skills = skills }; @@ -93,12 +94,44 @@ namespace osu.Game.Rulesets.Mania.Difficulty new ManiaModKey3(), new ManiaModKey4(), new ManiaModKey5(), + new MultiMod(new ManiaModKey5(), new ManiaModDualStages()), new ManiaModKey6(), + new MultiMod(new ManiaModKey6(), new ManiaModDualStages()), new ManiaModKey7(), + new MultiMod(new ManiaModKey7(), new ManiaModDualStages()), new ManiaModKey8(), + new MultiMod(new ManiaModKey8(), new ManiaModDualStages()), new ManiaModKey9(), + new MultiMod(new ManiaModKey9(), new ManiaModDualStages()), }).ToArray(); } } + + private double getScoreMultiplier(IBeatmap beatmap, Mod[] mods) + { + double scoreMultiplier = 1; + + foreach (var m in mods) + { + switch (m) + { + case ManiaModNoFail _: + case ManiaModEasy _: + case ManiaModHalfTime _: + scoreMultiplier *= 0.5; + break; + } + } + + var maniaBeatmap = (ManiaBeatmap)beatmap; + int diff = maniaBeatmap.TotalColumns - maniaBeatmap.OriginalTotalColumns; + + if (diff > 0) + scoreMultiplier *= 0.9; + else if (diff < 0) + scoreMultiplier *= 0.9 + 0.04 * diff; + + return scoreMultiplier; + } } } From f04aec538fa6286743147eb2e6f7c83c1b6c4a6b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 14 Oct 2020 18:12:19 +0900 Subject: [PATCH 1670/1782] Fix MultiMod throwing exceptions when creating copies --- .../UserInterface/TestSceneModSettings.cs | 18 ++++++++++++++++++ osu.Game/Rulesets/Mods/MultiMod.cs | 4 +++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs index c5ce3751ef..0d43be3f65 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs @@ -95,6 +95,24 @@ namespace osu.Game.Tests.Visual.UserInterface AddAssert("copy has original value", () => Precision.AlmostEquals(1.5, copy.SpeedChange.Value)); } + [Test] + public void TestMultiModSettingsUnboundWhenCopied() + { + MultiMod original = null; + MultiMod copy = null; + + AddStep("create mods", () => + { + original = new MultiMod(new OsuModDoubleTime()); + copy = (MultiMod)original.CreateCopy(); + }); + + AddStep("change property", () => ((OsuModDoubleTime)original.Mods[0]).SpeedChange.Value = 2); + + AddAssert("original has new value", () => Precision.AlmostEquals(2.0, ((OsuModDoubleTime)original.Mods[0]).SpeedChange.Value)); + AddAssert("copy has original value", () => Precision.AlmostEquals(1.5, ((OsuModDoubleTime)copy.Mods[0]).SpeedChange.Value)); + } + private void createModSelect() { AddStep("create mod select", () => diff --git a/osu.Game/Rulesets/Mods/MultiMod.cs b/osu.Game/Rulesets/Mods/MultiMod.cs index f7d574d3c7..2107009dbb 100644 --- a/osu.Game/Rulesets/Mods/MultiMod.cs +++ b/osu.Game/Rulesets/Mods/MultiMod.cs @@ -6,7 +6,7 @@ using System.Linq; namespace osu.Game.Rulesets.Mods { - public class MultiMod : Mod + public sealed class MultiMod : Mod { public override string Name => string.Empty; public override string Acronym => string.Empty; @@ -20,6 +20,8 @@ namespace osu.Game.Rulesets.Mods Mods = mods; } + public override Mod CreateCopy() => new MultiMod(Mods.Select(m => m.CreateCopy()).ToArray()); + public override Type[] IncompatibleMods => Mods.SelectMany(m => m.IncompatibleMods).ToArray(); } } From da8565c0fa653c7b8dee30ec71d8a51d0cdf97f1 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 14 Oct 2020 18:28:19 +0900 Subject: [PATCH 1671/1782] Add 10K mod to incompatibility list --- osu.Game.Rulesets.Mania/Mods/ManiaKeyMod.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaKeyMod.cs b/osu.Game.Rulesets.Mania/Mods/ManiaKeyMod.cs index 13fdd74113..8fd5950dfb 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaKeyMod.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaKeyMod.cs @@ -39,6 +39,7 @@ namespace osu.Game.Rulesets.Mania.Mods typeof(ManiaModKey7), typeof(ManiaModKey8), typeof(ManiaModKey9), + typeof(ManiaModKey10), }.Except(new[] { GetType() }).ToArray(); } } From ace9fbc8d392c6acae2b7a076b6134d5415d5e68 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 14 Oct 2020 18:15:29 +0900 Subject: [PATCH 1672/1782] Confine available area for HUD components to excluse the song progress area --- osu.Game/Screens/Play/HUDOverlay.cs | 53 +++++++++++++++++++++-------- 1 file changed, 38 insertions(+), 15 deletions(-) diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 26aefa138b..f20127bc63 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -80,26 +80,49 @@ namespace osu.Game.Screens.Play visibilityContainer = new Container { RelativeSizeAxes = Axes.Both, - Children = new Drawable[] + Child = new GridContainer { - HealthDisplay = CreateHealthDisplay(), - topScoreContainer = new Container + RelativeSizeAxes = Axes.Both, + Content = new[] { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - AutoSizeAxes = Axes.Both, - Children = new Drawable[] + new Drawable[] { - AccuracyCounter = CreateAccuracyCounter(), - ScoreCounter = CreateScoreCounter(), - ComboCounter = CreateComboCounter(), + new Container + { + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + HealthDisplay = CreateHealthDisplay(), + topScoreContainer = new Container + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + AutoSizeAxes = Axes.Both, + Children = new Drawable[] + { + AccuracyCounter = CreateAccuracyCounter(), + ScoreCounter = CreateScoreCounter(), + ComboCounter = CreateComboCounter(), + }, + }, + ComboCounter = CreateComboCounter(), + ModDisplay = CreateModsContainer(), + HitErrorDisplay = CreateHitErrorDisplayOverlay(), + PlayerSettingsOverlay = CreatePlayerSettingsOverlay(), + } + }, }, + new Drawable[] + { + Progress = CreateProgress(), + } }, - Progress = CreateProgress(), - ModDisplay = CreateModsContainer(), - HitErrorDisplay = CreateHitErrorDisplayOverlay(), - PlayerSettingsOverlay = CreatePlayerSettingsOverlay(), - } + RowDimensions = new[] + { + new Dimension(GridSizeMode.Distributed), + new Dimension(GridSizeMode.AutoSize) + } + }, }, new FillFlowContainer { From 0cf3e909042f301788d2abd682cc23dfc9013e6a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 14 Oct 2020 18:15:58 +0900 Subject: [PATCH 1673/1782] Update SongProgress height based on its dynamic height during resize --- osu.Game/Screens/Play/SongProgress.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/SongProgress.cs b/osu.Game/Screens/Play/SongProgress.cs index aa745f5ba2..acf4640aa4 100644 --- a/osu.Game/Screens/Play/SongProgress.cs +++ b/osu.Game/Screens/Play/SongProgress.cs @@ -70,7 +70,6 @@ namespace osu.Game.Screens.Play public SongProgress() { Masking = true; - Height = bottom_bar_height + graph_height + handle_size.Y + info_height; Children = new Drawable[] { @@ -148,6 +147,8 @@ namespace osu.Game.Screens.Play bar.CurrentTime = gameplayTime; graph.Progress = (int)(graph.ColumnCount * progress); + + Height = bottom_bar_height + graph_height + handle_size.Y + info_height - graph.Y; } private void updateBarVisibility() From a7f8e26e3572f97b9bd085c202dc214e88bcac5b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 14 Oct 2020 18:51:53 +0900 Subject: [PATCH 1674/1782] Adjust bottom-right elements positions based on song progress display --- osu.Game/Screens/Play/HUDOverlay.cs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index f20127bc63..9d7b3f55be 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -63,6 +63,8 @@ namespace osu.Game.Screens.Play private readonly Container topScoreContainer; + private FillFlowContainer bottomRightElements; + private IEnumerable hideTargets => new Drawable[] { visibilityContainer, KeyCounter }; public HUDOverlay(ScoreProcessor scoreProcessor, HealthProcessor healthProcessor, DrawableRuleset drawableRuleset, IReadOnlyList mods) @@ -119,16 +121,16 @@ namespace osu.Game.Screens.Play }, RowDimensions = new[] { - new Dimension(GridSizeMode.Distributed), + new Dimension(), new Dimension(GridSizeMode.AutoSize) } }, }, - new FillFlowContainer + bottomRightElements = new FillFlowContainer { Anchor = Anchor.BottomRight, Origin = Anchor.BottomRight, - Position = -new Vector2(5, TwoLayerButton.SIZE_RETRACTED.Y), + X = -5, AutoSizeAxes = Axes.Both, LayoutDuration = fade_duration / 2, LayoutEasing = fade_easing, @@ -209,6 +211,12 @@ namespace osu.Game.Screens.Play replayLoaded.BindValueChanged(replayLoadedValueChanged, true); } + protected override void Update() + { + base.Update(); + bottomRightElements.Y = -Progress.Height; + } + private void replayLoadedValueChanged(ValueChangedEvent e) { PlayerSettingsOverlay.ReplayLoaded = e.NewValue; From d7a52e97fffd13fd780f6d9a8f283c68f3c637c3 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 14 Oct 2020 19:03:11 +0900 Subject: [PATCH 1675/1782] Fix multimod difficulty combinations not generating correctly --- ...DifficultyAdjustmentModCombinationsTest.cs | 39 +++++++++++++++++++ .../Difficulty/DifficultyCalculator.cs | 31 +++++++++++++-- 2 files changed, 66 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/NonVisual/DifficultyAdjustmentModCombinationsTest.cs b/osu.Game.Tests/NonVisual/DifficultyAdjustmentModCombinationsTest.cs index 760a033aff..de0397dc84 100644 --- a/osu.Game.Tests/NonVisual/DifficultyAdjustmentModCombinationsTest.cs +++ b/osu.Game.Tests/NonVisual/DifficultyAdjustmentModCombinationsTest.cs @@ -94,6 +94,38 @@ namespace osu.Game.Tests.NonVisual Assert.IsTrue(combinations[2] is ModIncompatibleWithAofA); } + [Test] + public void TestMultiMod1() + { + var combinations = new TestLegacyDifficultyCalculator(new ModA(), new MultiMod(new ModB(), new ModC())).CreateDifficultyAdjustmentModCombinations(); + + Assert.AreEqual(4, combinations.Length); + Assert.IsTrue(combinations[0] is ModNoMod); + Assert.IsTrue(combinations[1] is ModA); + Assert.IsTrue(combinations[2] is MultiMod); + Assert.IsTrue(combinations[3] is MultiMod); + + Assert.IsTrue(((MultiMod)combinations[2]).Mods[0] is ModA); + Assert.IsTrue(((MultiMod)combinations[2]).Mods[1] is ModB); + Assert.IsTrue(((MultiMod)combinations[2]).Mods[2] is ModC); + Assert.IsTrue(((MultiMod)combinations[3]).Mods[0] is ModB); + Assert.IsTrue(((MultiMod)combinations[3]).Mods[1] is ModC); + } + + [Test] + public void TestMultiMod2() + { + var combinations = new TestLegacyDifficultyCalculator(new ModA(), new MultiMod(new ModB(), new ModIncompatibleWithA())).CreateDifficultyAdjustmentModCombinations(); + + Assert.AreEqual(3, combinations.Length); + Assert.IsTrue(combinations[0] is ModNoMod); + Assert.IsTrue(combinations[1] is ModA); + Assert.IsTrue(combinations[2] is MultiMod); + + Assert.IsTrue(((MultiMod)combinations[2]).Mods[0] is ModB); + Assert.IsTrue(((MultiMod)combinations[2]).Mods[1] is ModIncompatibleWithA); + } + private class ModA : Mod { public override string Name => nameof(ModA); @@ -112,6 +144,13 @@ namespace osu.Game.Tests.NonVisual public override Type[] IncompatibleMods => new[] { typeof(ModIncompatibleWithAAndB) }; } + private class ModC : Mod + { + public override string Name => nameof(ModC); + public override string Acronym => nameof(ModC); + public override double ScoreMultiplier => 1; + } + private class ModIncompatibleWithA : Mod { public override string Name => $"Incompatible With {nameof(ModA)}"; diff --git a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs index 1902de5bda..9989c750ee 100644 --- a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs @@ -107,7 +107,7 @@ namespace osu.Game.Rulesets.Difficulty { return createDifficultyAdjustmentModCombinations(Array.Empty(), DifficultyAdjustmentMods).ToArray(); - IEnumerable createDifficultyAdjustmentModCombinations(IEnumerable currentSet, Mod[] adjustmentSet, int currentSetCount = 0, int adjustmentSetStart = 0) + static IEnumerable createDifficultyAdjustmentModCombinations(IEnumerable currentSet, Mod[] adjustmentSet, int currentSetCount = 0, int adjustmentSetStart = 0) { switch (currentSetCount) { @@ -133,13 +133,36 @@ namespace osu.Game.Rulesets.Difficulty for (int i = adjustmentSetStart; i < adjustmentSet.Length; i++) { var adjustmentMod = adjustmentSet[i]; - if (currentSet.Any(c => c.IncompatibleMods.Any(m => m.IsInstanceOfType(adjustmentMod)))) - continue; - foreach (var combo in createDifficultyAdjustmentModCombinations(currentSet.Append(adjustmentMod), adjustmentSet, currentSetCount + 1, i + 1)) + if (currentSet.Any(c => c.IncompatibleMods.Any(m => m.IsInstanceOfType(adjustmentMod)) + || adjustmentMod.IncompatibleMods.Any(m => m.IsInstanceOfType(c)))) + { + continue; + } + + // Append the new mod. + int newSetCount = currentSetCount; + var newSet = append(currentSet, adjustmentMod, ref newSetCount); + + foreach (var combo in createDifficultyAdjustmentModCombinations(newSet, adjustmentSet, newSetCount, i + 1)) yield return combo; } } + + // Appends a mod to an existing enumerable, returning the result. Recurses for MultiMod. + static IEnumerable append(IEnumerable existing, Mod mod, ref int count) + { + if (mod is MultiMod multi) + { + foreach (var nested in multi.Mods) + existing = append(existing, nested, ref count); + + return existing; + } + + count++; + return existing.Append(mod); + } } /// From 98acf1e31dc86ea3a0872a4eea5f0043ea2ca4b1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 14 Oct 2020 19:16:25 +0900 Subject: [PATCH 1676/1782] Make field read only --- 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 9d7b3f55be..14ceadac81 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -63,7 +63,7 @@ namespace osu.Game.Screens.Play private readonly Container topScoreContainer; - private FillFlowContainer bottomRightElements; + private readonly FillFlowContainer bottomRightElements; private IEnumerable hideTargets => new Drawable[] { visibilityContainer, KeyCounter }; From 60603d2918b81eeecf6efb721913a825079e7664 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 14 Oct 2020 16:45:40 +0900 Subject: [PATCH 1677/1782] Add skin components and interfaces --- osu.Game/Screens/Play/HUD/IComboCounter.cs | 19 +++++++++++++++++++ osu.Game/Skinning/HUDSkinComponent.cs | 22 ++++++++++++++++++++++ osu.Game/Skinning/HUDSkinComponents.cs | 10 ++++++++++ 3 files changed, 51 insertions(+) create mode 100644 osu.Game/Screens/Play/HUD/IComboCounter.cs create mode 100644 osu.Game/Skinning/HUDSkinComponent.cs create mode 100644 osu.Game/Skinning/HUDSkinComponents.cs diff --git a/osu.Game/Screens/Play/HUD/IComboCounter.cs b/osu.Game/Screens/Play/HUD/IComboCounter.cs new file mode 100644 index 0000000000..ff235bf04e --- /dev/null +++ b/osu.Game/Screens/Play/HUD/IComboCounter.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.Bindables; +using osu.Framework.Graphics; + +namespace osu.Game.Screens.Play.HUD +{ + /// + /// An interface providing a set of methods to update a combo counter. + /// + public interface IComboCounter : IDrawable + { + /// + /// The current combo to be displayed. + /// + Bindable Current { get; } + } +} diff --git a/osu.Game/Skinning/HUDSkinComponent.cs b/osu.Game/Skinning/HUDSkinComponent.cs new file mode 100644 index 0000000000..041beb68f2 --- /dev/null +++ b/osu.Game/Skinning/HUDSkinComponent.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 System.Linq; + +namespace osu.Game.Skinning +{ + public class HUDSkinComponent : ISkinComponent + { + public readonly HUDSkinComponents Component; + + public HUDSkinComponent(HUDSkinComponents component) + { + Component = component; + } + + protected virtual string ComponentName => Component.ToString(); + + public string LookupName => + string.Join("/", new[] { "HUD", ComponentName }.Where(s => !string.IsNullOrEmpty(s))); + } +} diff --git a/osu.Game/Skinning/HUDSkinComponents.cs b/osu.Game/Skinning/HUDSkinComponents.cs new file mode 100644 index 0000000000..6f3e2cbaf5 --- /dev/null +++ b/osu.Game/Skinning/HUDSkinComponents.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 HUDSkinComponents + { + ComboCounter + } +} From 375146b4898e61e3378e9834a1a024b5c4804529 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 14 Oct 2020 16:45:48 +0900 Subject: [PATCH 1678/1782] Make HUDOverlay test scene skinnable --- .../Visual/Gameplay/TestSceneHUDOverlay.cs | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs index c192a7b0e0..e2b831b144 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs @@ -9,13 +9,15 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Testing; using osu.Game.Configuration; +using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu; using osu.Game.Screens.Play; using osuTK.Input; namespace osu.Game.Tests.Visual.Gameplay { - public class TestSceneHUDOverlay : OsuManualInputManagerTestScene + public class TestSceneHUDOverlay : SkinnableTestScene { private HUDOverlay hudOverlay; @@ -107,13 +109,20 @@ namespace osu.Game.Tests.Visual.Gameplay { AddStep("create overlay", () => { - Child = hudOverlay = new HUDOverlay(null, null, null, Array.Empty()); + SetContents(() => + { + hudOverlay = new HUDOverlay(null, null, null, Array.Empty()); - // Add any key just to display the key counter visually. - hudOverlay.KeyCounter.Add(new KeyCounterKeyboard(Key.Space)); + // Add any key just to display the key counter visually. + hudOverlay.KeyCounter.Add(new KeyCounterKeyboard(Key.Space)); - action?.Invoke(hudOverlay); + action?.Invoke(hudOverlay); + + return hudOverlay; + }); }); } + + protected override Ruleset CreateRulesetForSkinProvider() => new OsuRuleset(); } } From f5623ee21e2a229b4de574b961037ba487195bf1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 14 Oct 2020 16:46:13 +0900 Subject: [PATCH 1679/1782] Setup skinnable combo counter component with default implementation --- .../UserInterface/SimpleComboCounter.cs | 3 +- .../Screens/Play/HUD/SkinnableComboCounter.cs | 58 +++++++++++++++++++ osu.Game/Screens/Play/HUDOverlay.cs | 10 +--- 3 files changed, 62 insertions(+), 9 deletions(-) create mode 100644 osu.Game/Screens/Play/HUD/SkinnableComboCounter.cs diff --git a/osu.Game/Graphics/UserInterface/SimpleComboCounter.cs b/osu.Game/Graphics/UserInterface/SimpleComboCounter.cs index c9790aed46..59e31eff55 100644 --- a/osu.Game/Graphics/UserInterface/SimpleComboCounter.cs +++ b/osu.Game/Graphics/UserInterface/SimpleComboCounter.cs @@ -5,13 +5,14 @@ using System; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Game.Graphics.Sprites; +using osu.Game.Screens.Play.HUD; namespace osu.Game.Graphics.UserInterface { /// /// Used as an accuracy counter. Represented visually as a percentage. /// - public class SimpleComboCounter : RollingCounter + public class SimpleComboCounter : RollingCounter, IComboCounter { protected override double RollingDuration => 750; diff --git a/osu.Game/Screens/Play/HUD/SkinnableComboCounter.cs b/osu.Game/Screens/Play/HUD/SkinnableComboCounter.cs new file mode 100644 index 0000000000..a67953c790 --- /dev/null +++ b/osu.Game/Screens/Play/HUD/SkinnableComboCounter.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.Bindables; +using osu.Framework.Graphics; +using osu.Game.Graphics.UserInterface; +using osu.Game.Skinning; +using osuTK.Graphics; + +namespace osu.Game.Screens.Play.HUD +{ + public class SkinnableComboCounter : SkinnableDrawable, IComboCounter + { + public SkinnableComboCounter() + : base(new HUDSkinComponent(HUDSkinComponents.ComboCounter), createDefault) + { + } + + private IComboCounter skinnedCounter; + + protected override void SkinChanged(ISkinSource skin, bool allowFallback) + { + // todo: unnecessary? + if (skinnedCounter != null) + { + Current.UnbindFrom(skinnedCounter.Current); + } + + base.SkinChanged(skin, allowFallback); + + // temporary layout code, will eventually be replaced by the skin layout system. + if (Drawable is SimpleComboCounter) + { + Drawable.BypassAutoSizeAxes = Axes.X; + Drawable.Anchor = Anchor.TopRight; + Drawable.Origin = Anchor.TopLeft; + Drawable.Margin = new MarginPadding { Top = 5, Left = 20 }; + } + else + { + Drawable.BypassAutoSizeAxes = Axes.X; + Drawable.Anchor = Anchor.BottomLeft; + Drawable.Origin = Anchor.BottomLeft; + Drawable.Margin = new MarginPadding { Top = 5, Left = 20 }; + } + + skinnedCounter = (IComboCounter)Drawable; + + Current.BindTo(skinnedCounter.Current); + } + + private static Drawable createDefault(ISkinComponent skinComponent) => new SimpleComboCounter(); + + public Bindable Current { get; } = new Bindable(); + + public void UpdateCombo(int combo, Color4? hitObjectColour = null) => Current.Value = combo; + } +} diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 14ceadac81..ee5b4e3f34 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -28,7 +28,7 @@ namespace osu.Game.Screens.Play private const Easing fade_easing = Easing.Out; public readonly KeyCounterDisplay KeyCounter; - public readonly RollingCounter ComboCounter; + public readonly SkinnableComboCounter ComboCounter; public readonly ScoreCounter ScoreCounter; public readonly RollingCounter AccuracyCounter; public readonly HealthDisplay HealthDisplay; @@ -275,13 +275,7 @@ namespace osu.Game.Screens.Play Origin = Anchor.TopCentre, }; - protected virtual RollingCounter CreateComboCounter() => new SimpleComboCounter - { - BypassAutoSizeAxes = Axes.X, - Anchor = Anchor.TopRight, - Origin = Anchor.TopLeft, - Margin = new MarginPadding { Top = 5, Left = 20 }, - }; + protected virtual SkinnableComboCounter CreateComboCounter() => new SkinnableComboCounter(); protected virtual HealthDisplay CreateHealthDisplay() => new StandardHealthDisplay { From 899bac6ca535a33b8434a79d941134c874547c68 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 14 Oct 2020 17:02:12 +0900 Subject: [PATCH 1680/1782] Rename catch combo counter for clarity --- .../Skinning/CatchLegacySkinTransformer.cs | 2 +- .../{LegacyComboCounter.cs => LegacyCatchComboCounter.cs} | 4 ++-- .../{SimpleComboCounter.cs => DefaultComboCounter.cs} | 0 .../HUD/{StandardComboCounter.cs => LegacyComboCounter.cs} | 0 4 files changed, 3 insertions(+), 3 deletions(-) rename osu.Game.Rulesets.Catch/Skinning/{LegacyComboCounter.cs => LegacyCatchComboCounter.cs} (96%) rename osu.Game/Graphics/UserInterface/{SimpleComboCounter.cs => DefaultComboCounter.cs} (100%) rename osu.Game/Screens/Play/HUD/{StandardComboCounter.cs => LegacyComboCounter.cs} (100%) diff --git a/osu.Game.Rulesets.Catch/Skinning/CatchLegacySkinTransformer.cs b/osu.Game.Rulesets.Catch/Skinning/CatchLegacySkinTransformer.cs index 47224bd195..916b4c5192 100644 --- a/osu.Game.Rulesets.Catch/Skinning/CatchLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Catch/Skinning/CatchLegacySkinTransformer.cs @@ -59,7 +59,7 @@ namespace osu.Game.Rulesets.Catch.Skinning // For simplicity, let's use legacy combo font texture existence as a way to identify legacy skins from default. if (this.HasFont(comboFont)) - return new LegacyComboCounter(Source); + return new LegacyCatchComboCounter(Source); break; } diff --git a/osu.Game.Rulesets.Catch/Skinning/LegacyComboCounter.cs b/osu.Game.Rulesets.Catch/Skinning/LegacyCatchComboCounter.cs similarity index 96% rename from osu.Game.Rulesets.Catch/Skinning/LegacyComboCounter.cs rename to osu.Game.Rulesets.Catch/Skinning/LegacyCatchComboCounter.cs index c8abc9e832..34608b07ff 100644 --- a/osu.Game.Rulesets.Catch/Skinning/LegacyComboCounter.cs +++ b/osu.Game.Rulesets.Catch/Skinning/LegacyCatchComboCounter.cs @@ -14,13 +14,13 @@ namespace osu.Game.Rulesets.Catch.Skinning /// /// A combo counter implementation that visually behaves almost similar to stable's osu!catch combo counter. /// - public class LegacyComboCounter : CompositeDrawable, ICatchComboCounter + public class LegacyCatchComboCounter : CompositeDrawable, ICatchComboCounter { private readonly LegacyRollingCounter counter; private readonly LegacyRollingCounter explosion; - public LegacyComboCounter(ISkin skin) + public LegacyCatchComboCounter(ISkin skin) { var fontName = skin.GetConfig(LegacySetting.ComboPrefix)?.Value ?? "score"; var fontOverlap = skin.GetConfig(LegacySetting.ComboOverlap)?.Value ?? -2f; diff --git a/osu.Game/Graphics/UserInterface/SimpleComboCounter.cs b/osu.Game/Graphics/UserInterface/DefaultComboCounter.cs similarity index 100% rename from osu.Game/Graphics/UserInterface/SimpleComboCounter.cs rename to osu.Game/Graphics/UserInterface/DefaultComboCounter.cs diff --git a/osu.Game/Screens/Play/HUD/StandardComboCounter.cs b/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs similarity index 100% rename from osu.Game/Screens/Play/HUD/StandardComboCounter.cs rename to osu.Game/Screens/Play/HUD/LegacyComboCounter.cs From 6a6718ebab2963afefacb3f05628ffdb7d48367c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 14 Oct 2020 17:20:10 +0900 Subject: [PATCH 1681/1782] Allow bypassing origin/anchor setting of skinnable components It makes little sense to set these when using RelativeSizeAxes.Both --- .../Screens/Play/HUD/SkinnableComboCounter.cs | 1 + osu.Game/Skinning/SkinnableDrawable.cs | 15 +++++++++++++-- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/SkinnableComboCounter.cs b/osu.Game/Screens/Play/HUD/SkinnableComboCounter.cs index a67953c790..36f615e9d0 100644 --- a/osu.Game/Screens/Play/HUD/SkinnableComboCounter.cs +++ b/osu.Game/Screens/Play/HUD/SkinnableComboCounter.cs @@ -14,6 +14,7 @@ namespace osu.Game.Screens.Play.HUD public SkinnableComboCounter() : base(new HUDSkinComponent(HUDSkinComponents.ComboCounter), createDefault) { + CentreComponent = false; } private IComboCounter skinnedCounter; diff --git a/osu.Game/Skinning/SkinnableDrawable.cs b/osu.Game/Skinning/SkinnableDrawable.cs index d9a5036649..5a48bc4baf 100644 --- a/osu.Game/Skinning/SkinnableDrawable.cs +++ b/osu.Game/Skinning/SkinnableDrawable.cs @@ -19,6 +19,12 @@ namespace osu.Game.Skinning /// public Drawable Drawable { get; private set; } + /// + /// Whether the drawable component should be centered in available space. + /// Defaults to true. + /// + public bool CentreComponent { get; set; } = true; + public new Axes AutoSizeAxes { get => base.AutoSizeAxes; @@ -84,8 +90,13 @@ namespace osu.Game.Skinning if (Drawable != null) { scaling.Invalidate(); - Drawable.Origin = Anchor.Centre; - Drawable.Anchor = Anchor.Centre; + + if (CentreComponent) + { + Drawable.Origin = Anchor.Centre; + Drawable.Anchor = Anchor.Centre; + } + InternalChild = Drawable; } else From 6eb3176776808d3737923a76171293f642ffa95a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 14 Oct 2020 17:20:44 +0900 Subject: [PATCH 1682/1782] Add combo incrementing tests to hud overlay test suite --- .../Visual/Gameplay/TestSceneHUDOverlay.cs | 32 ++++++++++++++++--- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs index e2b831b144..c02075bea9 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs @@ -2,9 +2,11 @@ // 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.Allocation; +using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Testing; @@ -21,6 +23,8 @@ namespace osu.Game.Tests.Visual.Gameplay { private HUDOverlay hudOverlay; + private IEnumerable hudOverlays => CreatedDrawables.OfType(); + // best way to check without exposing. private Drawable hideTarget => hudOverlay.KeyCounter; private FillFlowContainer keyCounterFlow => hudOverlay.KeyCounter.ChildrenOfType>().First(); @@ -28,6 +32,24 @@ namespace osu.Game.Tests.Visual.Gameplay [Resolved] private OsuConfigManager config { get; set; } + [Test] + public void TestComboCounterIncrementing() + { + createNew(); + + AddRepeatStep("increase combo", () => + { + foreach (var hud in hudOverlays) + hud.ComboCounter.Current.Value++; + }, 10); + + AddStep("reset combo", () => + { + foreach (var hud in hudOverlays) + hud.ComboCounter.Current.Value = 0; + }); + } + [Test] public void TestShownByDefault() { @@ -55,7 +77,7 @@ namespace osu.Game.Tests.Visual.Gameplay { createNew(); - AddStep("set showhud false", () => hudOverlay.ShowHud.Value = false); + AddStep("set showhud false", () => hudOverlays.ForEach(h => h.ShowHud.Value = false)); AddUntilStep("hidetarget is hidden", () => !hideTarget.IsPresent); AddAssert("pause button is still visible", () => hudOverlay.HoldToQuit.IsPresent); @@ -91,14 +113,14 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("set keycounter visible false", () => { config.Set(OsuSetting.KeyOverlay, false); - hudOverlay.KeyCounter.AlwaysVisible.Value = false; + hudOverlays.ForEach(h => h.KeyCounter.AlwaysVisible.Value = false); }); - AddStep("set showhud false", () => hudOverlay.ShowHud.Value = false); + AddStep("set showhud false", () => hudOverlays.ForEach(h => h.ShowHud.Value = false)); AddUntilStep("hidetarget is hidden", () => !hideTarget.IsPresent); AddAssert("key counters hidden", () => !keyCounterFlow.IsPresent); - AddStep("set showhud true", () => hudOverlay.ShowHud.Value = true); + AddStep("set showhud true", () => hudOverlays.ForEach(h => h.ShowHud.Value = true)); AddUntilStep("hidetarget is visible", () => hideTarget.IsPresent); AddAssert("key counters still hidden", () => !keyCounterFlow.IsPresent); @@ -116,6 +138,8 @@ namespace osu.Game.Tests.Visual.Gameplay // Add any key just to display the key counter visually. hudOverlay.KeyCounter.Add(new KeyCounterKeyboard(Key.Space)); + hudOverlay.ComboCounter.Current.Value = 1; + action?.Invoke(hudOverlay); return hudOverlay; From 2fce064e32d5a57b6c26a56b79a3d029246ddaae Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 14 Oct 2020 17:21:56 +0900 Subject: [PATCH 1683/1782] Add basic legacy combo counter and updating positioning logic --- .../Visual/Gameplay/TestSceneScoreCounter.cs | 2 +- osu.Game/Screens/Play/HUD/ComboCounter.cs | 4 +-- .../Play/HUD}/DefaultComboCounter.cs | 34 +++++++++++++++---- .../Screens/Play/HUD/LegacyComboCounter.cs | 10 +++++- .../Screens/Play/HUD/SkinnableComboCounter.cs | 31 ++--------------- osu.Game/Screens/Play/HUDOverlay.cs | 2 +- osu.Game/Skinning/LegacySkin.cs | 12 +++++++ 7 files changed, 54 insertions(+), 41 deletions(-) rename osu.Game/{Graphics/UserInterface => Screens/Play/HUD}/DefaultComboCounter.cs (54%) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneScoreCounter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneScoreCounter.cs index 09b4f9b761..43b3dd501d 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneScoreCounter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneScoreCounter.cs @@ -24,7 +24,7 @@ namespace osu.Game.Tests.Visual.Gameplay }; Add(score); - ComboCounter comboCounter = new StandardComboCounter + ComboCounter comboCounter = new LegacyComboCounter { Origin = Anchor.BottomLeft, Anchor = Anchor.BottomLeft, diff --git a/osu.Game/Screens/Play/HUD/ComboCounter.cs b/osu.Game/Screens/Play/HUD/ComboCounter.cs index ea50a4a578..d15a8d25ec 100644 --- a/osu.Game/Screens/Play/HUD/ComboCounter.cs +++ b/osu.Game/Screens/Play/HUD/ComboCounter.cs @@ -9,9 +9,9 @@ using osu.Game.Graphics.Sprites; namespace osu.Game.Screens.Play.HUD { - public abstract class ComboCounter : Container + public abstract class ComboCounter : Container, IComboCounter { - public BindableInt Current = new BindableInt + public Bindable Current { get; } = new BindableInt { MinValue = 0, }; diff --git a/osu.Game/Graphics/UserInterface/DefaultComboCounter.cs b/osu.Game/Screens/Play/HUD/DefaultComboCounter.cs similarity index 54% rename from osu.Game/Graphics/UserInterface/DefaultComboCounter.cs rename to osu.Game/Screens/Play/HUD/DefaultComboCounter.cs index 59e31eff55..1e23319c28 100644 --- a/osu.Game/Graphics/UserInterface/DefaultComboCounter.cs +++ b/osu.Game/Screens/Play/HUD/DefaultComboCounter.cs @@ -4,26 +4,46 @@ using System; using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Game.Graphics; using osu.Game.Graphics.Sprites; -using osu.Game.Screens.Play.HUD; +using osu.Game.Graphics.UserInterface; +using osuTK; -namespace osu.Game.Graphics.UserInterface +namespace osu.Game.Screens.Play.HUD { - /// - /// Used as an accuracy counter. Represented visually as a percentage. - /// - public class SimpleComboCounter : RollingCounter, IComboCounter + public class DefaultComboCounter : RollingCounter, IComboCounter { + private readonly Vector2 offset = new Vector2(20, 5); + protected override double RollingDuration => 750; - public SimpleComboCounter() + [Resolved(canBeNull: true)] + private HUDOverlay hud { get; set; } + + public DefaultComboCounter() { Current.Value = DisplayedCount = 0; + + Anchor = Anchor.TopCentre; + Origin = Anchor.TopLeft; + + Position = offset; } [BackgroundDependencyLoader] private void load(OsuColour colours) => Colour = colours.BlueLighter; + protected override void Update() + { + base.Update(); + + if (hud != null) + { + // for now align with the score counter. eventually this will be user customisable. + Position += ToLocalSpace(hud.ScoreCounter.ScreenSpaceDrawQuad.TopRight) + offset; + } + } + protected override string FormatCount(int count) { return $@"{count}x"; diff --git a/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs b/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs index 7301300b8d..8a94d19609 100644 --- a/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs +++ b/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs @@ -9,7 +9,7 @@ namespace osu.Game.Screens.Play.HUD /// /// Uses the 'x' symbol and has a pop-out effect while rolling over. /// - public class StandardComboCounter : ComboCounter + public class LegacyComboCounter : ComboCounter { protected uint ScheduledPopOutCurrentId; @@ -18,6 +18,14 @@ namespace osu.Game.Screens.Play.HUD public new Vector2 PopOutScale = new Vector2(1.6f); + public LegacyComboCounter() + { + Anchor = Anchor.BottomLeft; + Origin = Anchor.BottomLeft; + + Margin = new MarginPadding { Top = 5, Left = 20 }; + } + protected override void LoadComplete() { base.LoadComplete(); diff --git a/osu.Game/Screens/Play/HUD/SkinnableComboCounter.cs b/osu.Game/Screens/Play/HUD/SkinnableComboCounter.cs index 36f615e9d0..9f8ad758e4 100644 --- a/osu.Game/Screens/Play/HUD/SkinnableComboCounter.cs +++ b/osu.Game/Screens/Play/HUD/SkinnableComboCounter.cs @@ -3,9 +3,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Game.Graphics.UserInterface; using osu.Game.Skinning; -using osuTK.Graphics; namespace osu.Game.Screens.Play.HUD { @@ -21,39 +19,14 @@ namespace osu.Game.Screens.Play.HUD protected override void SkinChanged(ISkinSource skin, bool allowFallback) { - // todo: unnecessary? - if (skinnedCounter != null) - { - Current.UnbindFrom(skinnedCounter.Current); - } - base.SkinChanged(skin, allowFallback); - // temporary layout code, will eventually be replaced by the skin layout system. - if (Drawable is SimpleComboCounter) - { - Drawable.BypassAutoSizeAxes = Axes.X; - Drawable.Anchor = Anchor.TopRight; - Drawable.Origin = Anchor.TopLeft; - Drawable.Margin = new MarginPadding { Top = 5, Left = 20 }; - } - else - { - Drawable.BypassAutoSizeAxes = Axes.X; - Drawable.Anchor = Anchor.BottomLeft; - Drawable.Origin = Anchor.BottomLeft; - Drawable.Margin = new MarginPadding { Top = 5, Left = 20 }; - } - skinnedCounter = (IComboCounter)Drawable; - - Current.BindTo(skinnedCounter.Current); + skinnedCounter.Current.BindTo(Current); } - private static Drawable createDefault(ISkinComponent skinComponent) => new SimpleComboCounter(); + private static Drawable createDefault(ISkinComponent skinComponent) => new DefaultComboCounter(); public Bindable Current { get; } = new Bindable(); - - public void UpdateCombo(int combo, Color4? hitObjectColour = null) => Current.Value = combo; } } diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index ee5b4e3f34..a3547bbc68 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -22,6 +22,7 @@ using osuTK.Input; namespace osu.Game.Screens.Play { + [Cached] public class HUDOverlay : Container { private const float fade_duration = 400; @@ -104,7 +105,6 @@ namespace osu.Game.Screens.Play { AccuracyCounter = CreateAccuracyCounter(), ScoreCounter = CreateScoreCounter(), - ComboCounter = CreateComboCounter(), }, }, ComboCounter = CreateComboCounter(), diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index e38913b13a..b8b9349cc0 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -18,6 +18,7 @@ using osu.Game.Audio; using osu.Game.Beatmaps.Formats; using osu.Game.IO; using osu.Game.Rulesets.Scoring; +using osu.Game.Screens.Play.HUD; using osuTK.Graphics; namespace osu.Game.Skinning @@ -327,6 +328,17 @@ namespace osu.Game.Skinning { switch (component) { + case HUDSkinComponent hudComponent: + { + switch (hudComponent.Component) + { + case HUDSkinComponents.ComboCounter: + return new LegacyComboCounter(); + } + + return null; + } + case GameplaySkinComponent resultComponent: switch (resultComponent.Component) { From fbbea48c8c45f5dfc1360c6cf749dc35123751a7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 14 Oct 2020 17:51:03 +0900 Subject: [PATCH 1684/1782] Add score text skinnability --- osu.Game/Screens/Play/HUD/ComboCounter.cs | 43 ++++++++----------- .../Screens/Play/HUD/LegacyComboCounter.cs | 27 ++++++++++-- osu.Game/Skinning/HUDSkinComponents.cs | 3 +- osu.Game/Skinning/LegacySkin.cs | 9 ++++ 4 files changed, 52 insertions(+), 30 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/ComboCounter.cs b/osu.Game/Screens/Play/HUD/ComboCounter.cs index d15a8d25ec..5bffa18032 100644 --- a/osu.Game/Screens/Play/HUD/ComboCounter.cs +++ b/osu.Game/Screens/Play/HUD/ComboCounter.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.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -18,7 +19,7 @@ namespace osu.Game.Screens.Play.HUD public bool IsRolling { get; protected set; } - protected SpriteText PopOutCount; + protected Drawable PopOutCount; protected virtual double PopOutDuration => 150; protected virtual float PopOutScale => 2.0f; @@ -37,7 +38,7 @@ namespace osu.Game.Screens.Play.HUD /// protected Easing RollingEasing => Easing.None; - protected SpriteText DisplayedCountSpriteText; + protected Drawable DisplayedCountSpriteText; private int previousValue; @@ -47,30 +48,34 @@ namespace osu.Game.Screens.Play.HUD protected ComboCounter() { AutoSizeAxes = Axes.Both; + } + [BackgroundDependencyLoader] + private void load() + { Children = new Drawable[] { - DisplayedCountSpriteText = new OsuSpriteText + DisplayedCountSpriteText = CreateSpriteText().With(s => { - Alpha = 0, - }, - PopOutCount = new OsuSpriteText + s.Alpha = 0; + }), + PopOutCount = CreateSpriteText().With(s => { - Alpha = 0, - Margin = new MarginPadding(0.05f), - } + s.Alpha = 0; + s.Margin = new MarginPadding(0.05f); + }) }; - TextSize = 80; - Current.ValueChanged += combo => updateCount(combo.NewValue == 0); } + protected virtual Drawable CreateSpriteText() => new OsuSpriteText(); + protected override void LoadComplete() { base.LoadComplete(); - DisplayedCountSpriteText.Text = FormatCount(Current.Value); + ((IHasText)DisplayedCountSpriteText).Text = FormatCount(Current.Value); DisplayedCountSpriteText.Anchor = Anchor; DisplayedCountSpriteText.Origin = Origin; @@ -94,20 +99,6 @@ namespace osu.Game.Screens.Play.HUD } } - private float textSize; - - public float TextSize - { - get => textSize; - set - { - textSize = value; - - DisplayedCountSpriteText.Font = DisplayedCountSpriteText.Font.With(size: TextSize); - PopOutCount.Font = PopOutCount.Font.With(size: TextSize); - } - } - /// /// Increments the combo by an amount. /// diff --git a/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs b/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs index 8a94d19609..54a4338885 100644 --- a/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs +++ b/osu.Game/Screens/Play/HUD/LegacyComboCounter.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 osu.Framework.Allocation; using osuTK; using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Skinning; namespace osu.Game.Screens.Play.HUD { @@ -26,6 +31,22 @@ namespace osu.Game.Screens.Play.HUD Margin = new MarginPadding { Top = 5, Left = 20 }; } + [Resolved] + private ISkinSource skin { get; set; } + + protected override Drawable CreateSpriteText() + { + return skin?.GetDrawableComponent(new HUDSkinComponent(HUDSkinComponents.ScoreText)) ?? new OsuSpriteText(); + + /* + new OsuSpriteText + { + Font = OsuFont.Numeric.With(size: 40), + UseFullGlyphHeight = false, + }); + */ + } + protected override void LoadComplete() { base.LoadComplete(); @@ -41,7 +62,7 @@ namespace osu.Game.Screens.Play.HUD protected virtual void TransformPopOut(int newValue) { - PopOutCount.Text = FormatCount(newValue); + ((IHasText)PopOutCount).Text = FormatCount(newValue); PopOutCount.ScaleTo(PopOutScale); PopOutCount.FadeTo(PopOutInitialAlpha); @@ -60,13 +81,13 @@ namespace osu.Game.Screens.Play.HUD protected virtual void TransformNoPopOut(int newValue) { - DisplayedCountSpriteText.Text = FormatCount(newValue); + ((IHasText)DisplayedCountSpriteText).Text = FormatCount(newValue); DisplayedCountSpriteText.ScaleTo(1); } protected virtual void TransformPopOutSmall(int newValue) { - DisplayedCountSpriteText.Text = FormatCount(newValue); + ((IHasText)DisplayedCountSpriteText).Text = FormatCount(newValue); DisplayedCountSpriteText.ScaleTo(PopOutSmallScale); DisplayedCountSpriteText.ScaleTo(1, PopOutDuration, PopOutEasing); } diff --git a/osu.Game/Skinning/HUDSkinComponents.cs b/osu.Game/Skinning/HUDSkinComponents.cs index 6f3e2cbaf5..7577ba066c 100644 --- a/osu.Game/Skinning/HUDSkinComponents.cs +++ b/osu.Game/Skinning/HUDSkinComponents.cs @@ -5,6 +5,7 @@ namespace osu.Game.Skinning { public enum HUDSkinComponents { - ComboCounter + ComboCounter, + ScoreText } } diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index b8b9349cc0..fddea40b04 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -19,6 +19,7 @@ using osu.Game.Beatmaps.Formats; using osu.Game.IO; using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Play.HUD; +using osuTK; using osuTK.Graphics; namespace osu.Game.Skinning @@ -334,6 +335,14 @@ namespace osu.Game.Skinning { case HUDSkinComponents.ComboCounter: return new LegacyComboCounter(); + + case HUDSkinComponents.ScoreText: + const string font = "score"; + + if (!this.HasFont(font)) + return null; + + return new LegacySpriteText(this, font); } return null; From 9bb8a43bcee61d11f26598fff2228af1f07a4d17 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 14 Oct 2020 18:15:06 +0900 Subject: [PATCH 1685/1782] Combine LegacyComboCounter and ComboCounter classes --- .../Visual/Gameplay/TestSceneScoreCounter.cs | 2 +- osu.Game/Screens/Play/HUD/ComboCounter.cs | 191 ------------------ .../Screens/Play/HUD/LegacyComboCounter.cs | 176 ++++++++++++++-- osu.Game/Skinning/LegacySkin.cs | 1 - 4 files changed, 155 insertions(+), 215 deletions(-) delete mode 100644 osu.Game/Screens/Play/HUD/ComboCounter.cs diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneScoreCounter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneScoreCounter.cs index 43b3dd501d..29e4b1f0cb 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneScoreCounter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneScoreCounter.cs @@ -24,7 +24,7 @@ namespace osu.Game.Tests.Visual.Gameplay }; Add(score); - ComboCounter comboCounter = new LegacyComboCounter + LegacyComboCounter comboCounter = new LegacyComboCounter { Origin = Anchor.BottomLeft, Anchor = Anchor.BottomLeft, diff --git a/osu.Game/Screens/Play/HUD/ComboCounter.cs b/osu.Game/Screens/Play/HUD/ComboCounter.cs deleted file mode 100644 index 5bffa18032..0000000000 --- a/osu.Game/Screens/Play/HUD/ComboCounter.cs +++ /dev/null @@ -1,191 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using osu.Framework.Allocation; -using osu.Framework.Bindables; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Sprites; -using osu.Game.Graphics.Sprites; - -namespace osu.Game.Screens.Play.HUD -{ - public abstract class ComboCounter : Container, IComboCounter - { - public Bindable Current { get; } = new BindableInt - { - MinValue = 0, - }; - - public bool IsRolling { get; protected set; } - - protected Drawable PopOutCount; - - protected virtual double PopOutDuration => 150; - protected virtual float PopOutScale => 2.0f; - protected virtual Easing PopOutEasing => Easing.None; - protected virtual float PopOutInitialAlpha => 0.75f; - - protected virtual double FadeOutDuration => 100; - - /// - /// Duration in milliseconds for the counter roll-up animation for each element. - /// - protected virtual double RollingDuration => 20; - - /// - /// Easing for the counter rollover animation. - /// - protected Easing RollingEasing => Easing.None; - - protected Drawable DisplayedCountSpriteText; - - private int previousValue; - - /// - /// Base of all combo counters. - /// - protected ComboCounter() - { - AutoSizeAxes = Axes.Both; - } - - [BackgroundDependencyLoader] - private void load() - { - Children = new Drawable[] - { - DisplayedCountSpriteText = CreateSpriteText().With(s => - { - s.Alpha = 0; - }), - PopOutCount = CreateSpriteText().With(s => - { - s.Alpha = 0; - s.Margin = new MarginPadding(0.05f); - }) - }; - - Current.ValueChanged += combo => updateCount(combo.NewValue == 0); - } - - protected virtual Drawable CreateSpriteText() => new OsuSpriteText(); - - protected override void LoadComplete() - { - base.LoadComplete(); - - ((IHasText)DisplayedCountSpriteText).Text = FormatCount(Current.Value); - DisplayedCountSpriteText.Anchor = Anchor; - DisplayedCountSpriteText.Origin = Origin; - - StopRolling(); - } - - private int displayedCount; - - /// - /// Value shown at the current moment. - /// - public virtual int DisplayedCount - { - get => displayedCount; - protected set - { - if (displayedCount.Equals(value)) - return; - - updateDisplayedCount(displayedCount, value, IsRolling); - } - } - - /// - /// Increments the combo by an amount. - /// - /// - public void Increment(int amount = 1) - { - Current.Value += amount; - } - - /// - /// Stops rollover animation, forcing the displayed count to be the actual count. - /// - public void StopRolling() - { - updateCount(false); - } - - protected virtual string FormatCount(int count) - { - return count.ToString(); - } - - protected virtual void OnCountRolling(int currentValue, int newValue) - { - transformRoll(currentValue, newValue); - } - - protected virtual void OnCountIncrement(int currentValue, int newValue) - { - DisplayedCount = newValue; - } - - protected virtual void OnCountChange(int currentValue, int newValue) - { - DisplayedCount = newValue; - } - - private double getProportionalDuration(int currentValue, int newValue) - { - double difference = currentValue > newValue ? currentValue - newValue : newValue - currentValue; - return difference * RollingDuration; - } - - private void updateDisplayedCount(int currentValue, int newValue, bool rolling) - { - displayedCount = newValue; - if (rolling) - OnDisplayedCountRolling(currentValue, newValue); - else if (currentValue + 1 == newValue) - OnDisplayedCountIncrement(newValue); - else - OnDisplayedCountChange(newValue); - } - - private void updateCount(bool rolling) - { - int prev = previousValue; - previousValue = Current.Value; - - if (!IsLoaded) - return; - - if (!rolling) - { - FinishTransforms(false, nameof(DisplayedCount)); - IsRolling = false; - DisplayedCount = prev; - - if (prev + 1 == Current.Value) - OnCountIncrement(prev, Current.Value); - else - OnCountChange(prev, Current.Value); - } - else - { - OnCountRolling(displayedCount, Current.Value); - IsRolling = true; - } - } - - private void transformRoll(int currentValue, int newValue) - { - this.TransformTo(nameof(DisplayedCount), newValue, getProportionalDuration(currentValue, newValue), RollingEasing); - } - - protected abstract void OnDisplayedCountRolling(int currentValue, int newValue); - protected abstract void OnDisplayedCountIncrement(int newValue); - protected abstract void OnDisplayedCountChange(int newValue); - } -} diff --git a/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs b/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs index 54a4338885..b62cd1c309 100644 --- a/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs +++ b/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs @@ -2,62 +2,124 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; -using osuTK; +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.Skinning; +using osuTK; namespace osu.Game.Screens.Play.HUD { /// /// Uses the 'x' symbol and has a pop-out effect while rolling over. /// - public class LegacyComboCounter : ComboCounter + public class LegacyComboCounter : CompositeDrawable, IComboCounter { protected uint ScheduledPopOutCurrentId; protected virtual float PopOutSmallScale => 1.1f; protected virtual bool CanPopOutWhileRolling => false; - public new Vector2 PopOutScale = new Vector2(1.6f); + protected Drawable PopOutCount; + protected Drawable DisplayedCountSpriteText; + private int previousValue; + private int displayedCount; public LegacyComboCounter() { + AutoSizeAxes = Axes.Both; + Anchor = Anchor.BottomLeft; Origin = Anchor.BottomLeft; - Margin = new MarginPadding { Top = 5, Left = 20 }; + Margin = new MarginPadding { Bottom = 20, Left = 20 }; + + Scale = new Vector2(1.6f); } [Resolved] private ISkinSource skin { get; set; } - protected override Drawable CreateSpriteText() + public Bindable Current { get; } = new BindableInt { - return skin?.GetDrawableComponent(new HUDSkinComponent(HUDSkinComponents.ScoreText)) ?? new OsuSpriteText(); + MinValue = 0, + }; - /* - new OsuSpriteText + public bool IsRolling { get; protected set; } + protected virtual double PopOutDuration => 150; + protected virtual float PopOutScale => 1.6f; + protected virtual Easing PopOutEasing => Easing.None; + protected virtual float PopOutInitialAlpha => 0.75f; + protected virtual double FadeOutDuration => 100; + + /// + /// Duration in milliseconds for the counter roll-up animation for each element. + /// + protected virtual double RollingDuration => 20; + + /// + /// Easing for the counter rollover animation. + /// + protected Easing RollingEasing => Easing.None; + + /// + /// Value shown at the current moment. + /// + public virtual int DisplayedCount + { + get => displayedCount; + protected set + { + if (displayedCount.Equals(value)) + return; + + updateDisplayedCount(displayedCount, value, IsRolling); + } + } + + protected Drawable CreateSpriteText() + { + return skin?.GetDrawableComponent(new HUDSkinComponent(HUDSkinComponents.ScoreText)) ?? new OsuSpriteText { Font = OsuFont.Numeric.With(size: 40), UseFullGlyphHeight = false, - }); - */ + }; + } + + [BackgroundDependencyLoader] + private void load() + { + InternalChildren = new Drawable[] + { + DisplayedCountSpriteText = CreateSpriteText().With(s => + { + s.Alpha = 0; + }), + PopOutCount = CreateSpriteText().With(s => + { + s.Alpha = 0; + s.Margin = new MarginPadding(0.05f); + }) + }; + + Current.ValueChanged += combo => updateCount(combo.NewValue == 0); } protected override void LoadComplete() { base.LoadComplete(); + ((IHasText)DisplayedCountSpriteText).Text = FormatCount(Current.Value); + + DisplayedCountSpriteText.Anchor = Anchor; + DisplayedCountSpriteText.Origin = Origin; PopOutCount.Origin = Origin; PopOutCount.Anchor = Anchor; - } - protected override string FormatCount(int count) - { - return $@"{count}x"; + StopRolling(); } protected virtual void TransformPopOut(int newValue) @@ -101,7 +163,7 @@ namespace osu.Game.Screens.Play.HUD DisplayedCount++; } - protected override void OnCountRolling(int currentValue, int newValue) + protected void OnCountRolling(int currentValue, int newValue) { ScheduledPopOutCurrentId++; @@ -109,10 +171,10 @@ namespace osu.Game.Screens.Play.HUD if (currentValue == 0 && newValue == 0) DisplayedCountSpriteText.FadeOut(FadeOutDuration); - base.OnCountRolling(currentValue, newValue); + transformRoll(currentValue, newValue); } - protected override void OnCountIncrement(int currentValue, int newValue) + protected void OnCountIncrement(int currentValue, int newValue) { ScheduledPopOutCurrentId++; @@ -130,17 +192,17 @@ namespace osu.Game.Screens.Play.HUD }, PopOutDuration); } - protected override void OnCountChange(int currentValue, int newValue) + protected void OnCountChange(int currentValue, int newValue) { ScheduledPopOutCurrentId++; if (newValue == 0) DisplayedCountSpriteText.FadeOut(); - base.OnCountChange(currentValue, newValue); + DisplayedCount = newValue; } - protected override void OnDisplayedCountRolling(int currentValue, int newValue) + protected void OnDisplayedCountRolling(int currentValue, int newValue) { if (newValue == 0) DisplayedCountSpriteText.FadeOut(FadeOutDuration); @@ -153,18 +215,88 @@ namespace osu.Game.Screens.Play.HUD TransformNoPopOut(newValue); } - protected override void OnDisplayedCountChange(int newValue) + protected void OnDisplayedCountChange(int newValue) { DisplayedCountSpriteText.FadeTo(newValue == 0 ? 0 : 1); TransformNoPopOut(newValue); } - protected override void OnDisplayedCountIncrement(int newValue) + protected void OnDisplayedCountIncrement(int newValue) { DisplayedCountSpriteText.Show(); TransformPopOutSmall(newValue); } + + /// + /// Increments the combo by an amount. + /// + /// + public void Increment(int amount = 1) + { + Current.Value += amount; + } + + /// + /// Stops rollover animation, forcing the displayed count to be the actual count. + /// + public void StopRolling() + { + updateCount(false); + } + + protected string FormatCount(int count) + { + return $@"{count}x"; + } + + private double getProportionalDuration(int currentValue, int newValue) + { + double difference = currentValue > newValue ? currentValue - newValue : newValue - currentValue; + return difference * RollingDuration; + } + + private void updateDisplayedCount(int currentValue, int newValue, bool rolling) + { + displayedCount = newValue; + if (rolling) + OnDisplayedCountRolling(currentValue, newValue); + else if (currentValue + 1 == newValue) + OnDisplayedCountIncrement(newValue); + else + OnDisplayedCountChange(newValue); + } + + private void updateCount(bool rolling) + { + int prev = previousValue; + previousValue = Current.Value; + + if (!IsLoaded) + return; + + if (!rolling) + { + FinishTransforms(false, nameof(DisplayedCount)); + IsRolling = false; + DisplayedCount = prev; + + if (prev + 1 == Current.Value) + OnCountIncrement(prev, Current.Value); + else + OnCountChange(prev, Current.Value); + } + else + { + OnCountRolling(displayedCount, Current.Value); + IsRolling = true; + } + } + + private void transformRoll(int currentValue, int newValue) + { + this.TransformTo(nameof(DisplayedCount), newValue, getProportionalDuration(currentValue, newValue), RollingEasing); + } } } diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index fddea40b04..a4d47dd2f1 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -19,7 +19,6 @@ using osu.Game.Beatmaps.Formats; using osu.Game.IO; using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Play.HUD; -using osuTK; using osuTK.Graphics; namespace osu.Game.Skinning From 7f5ea57bd483bd528accc4b26f6e0f1f808bf660 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 14 Oct 2020 18:34:51 +0900 Subject: [PATCH 1686/1782] Clean-up pass (best effort) on LegacyComboCounter --- .../Visual/Gameplay/TestSceneScoreCounter.cs | 2 +- .../Screens/Play/HUD/LegacyComboCounter.cs | 359 ++++++++---------- 2 files changed, 158 insertions(+), 203 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneScoreCounter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneScoreCounter.cs index 29e4b1f0cb..ab010bee9f 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneScoreCounter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneScoreCounter.cs @@ -51,7 +51,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep(@"Hit! :D", delegate { score.Current.Value += 300 + (ulong)(300.0 * (comboCounter.Current.Value > 0 ? comboCounter.Current.Value - 1 : 0) / 25.0); - comboCounter.Increment(); + comboCounter.Current.Value++; numerator++; denominator++; accuracyCounter.SetFraction(numerator, denominator); diff --git a/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs b/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs index b62cd1c309..c96a20405c 100644 --- a/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs +++ b/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs @@ -18,16 +18,34 @@ namespace osu.Game.Screens.Play.HUD /// public class LegacyComboCounter : CompositeDrawable, IComboCounter { - protected uint ScheduledPopOutCurrentId; + public Bindable Current { get; } = new BindableInt { MinValue = 0, }; - protected virtual float PopOutSmallScale => 1.1f; - protected virtual bool CanPopOutWhileRolling => false; + private uint scheduledPopOutCurrentId; + + private const double pop_out_duration = 150; + + private const Easing pop_out_easing = Easing.None; + + private const double fade_out_duration = 100; + + /// + /// Duration in milliseconds for the counter roll-up animation for each element. + /// + private const double rolling_duration = 20; + + private Drawable popOutCount; + + private Drawable displayedCountSpriteText; - protected Drawable PopOutCount; - protected Drawable DisplayedCountSpriteText; private int previousValue; + private int displayedCount; + private bool isRolling; + + [Resolved] + private ISkinSource skin { get; set; } + public LegacyComboCounter() { AutoSizeAxes = Axes.Both; @@ -40,65 +58,38 @@ namespace osu.Game.Screens.Play.HUD Scale = new Vector2(1.6f); } - [Resolved] - private ISkinSource skin { get; set; } - - public Bindable Current { get; } = new BindableInt - { - MinValue = 0, - }; - - public bool IsRolling { get; protected set; } - protected virtual double PopOutDuration => 150; - protected virtual float PopOutScale => 1.6f; - protected virtual Easing PopOutEasing => Easing.None; - protected virtual float PopOutInitialAlpha => 0.75f; - protected virtual double FadeOutDuration => 100; - - /// - /// Duration in milliseconds for the counter roll-up animation for each element. - /// - protected virtual double RollingDuration => 20; - - /// - /// Easing for the counter rollover animation. - /// - protected Easing RollingEasing => Easing.None; - /// /// Value shown at the current moment. /// public virtual int DisplayedCount { get => displayedCount; - protected set + private set { if (displayedCount.Equals(value)) return; - updateDisplayedCount(displayedCount, value, IsRolling); - } - } + if (isRolling) + onDisplayedCountRolling(displayedCount, value); + else if (displayedCount + 1 == value) + onDisplayedCountIncrement(value); + else + onDisplayedCountChange(value); - protected Drawable CreateSpriteText() - { - return skin?.GetDrawableComponent(new HUDSkinComponent(HUDSkinComponents.ScoreText)) ?? new OsuSpriteText - { - Font = OsuFont.Numeric.With(size: 40), - UseFullGlyphHeight = false, - }; + displayedCount = value; + } } [BackgroundDependencyLoader] private void load() { - InternalChildren = new Drawable[] + InternalChildren = new[] { - DisplayedCountSpriteText = CreateSpriteText().With(s => + displayedCountSpriteText = createSpriteText().With(s => { s.Alpha = 0; }), - PopOutCount = CreateSpriteText().With(s => + popOutCount = createSpriteText().With(s => { s.Alpha = 0; s.Margin = new MarginPadding(0.05f); @@ -112,162 +103,16 @@ namespace osu.Game.Screens.Play.HUD { base.LoadComplete(); - ((IHasText)DisplayedCountSpriteText).Text = FormatCount(Current.Value); + ((IHasText)displayedCountSpriteText).Text = formatCount(Current.Value); - DisplayedCountSpriteText.Anchor = Anchor; - DisplayedCountSpriteText.Origin = Origin; - PopOutCount.Origin = Origin; - PopOutCount.Anchor = Anchor; + displayedCountSpriteText.Anchor = Anchor; + displayedCountSpriteText.Origin = Origin; + popOutCount.Origin = Origin; + popOutCount.Anchor = Anchor; - StopRolling(); - } - - protected virtual void TransformPopOut(int newValue) - { - ((IHasText)PopOutCount).Text = FormatCount(newValue); - - PopOutCount.ScaleTo(PopOutScale); - PopOutCount.FadeTo(PopOutInitialAlpha); - PopOutCount.MoveTo(Vector2.Zero); - - PopOutCount.ScaleTo(1, PopOutDuration, PopOutEasing); - PopOutCount.FadeOut(PopOutDuration, PopOutEasing); - PopOutCount.MoveTo(DisplayedCountSpriteText.Position, PopOutDuration, PopOutEasing); - } - - protected virtual void TransformPopOutRolling(int newValue) - { - TransformPopOut(newValue); - TransformPopOutSmall(newValue); - } - - protected virtual void TransformNoPopOut(int newValue) - { - ((IHasText)DisplayedCountSpriteText).Text = FormatCount(newValue); - DisplayedCountSpriteText.ScaleTo(1); - } - - protected virtual void TransformPopOutSmall(int newValue) - { - ((IHasText)DisplayedCountSpriteText).Text = FormatCount(newValue); - DisplayedCountSpriteText.ScaleTo(PopOutSmallScale); - DisplayedCountSpriteText.ScaleTo(1, PopOutDuration, PopOutEasing); - } - - protected virtual void ScheduledPopOutSmall(uint id) - { - // Too late; scheduled task invalidated - if (id != ScheduledPopOutCurrentId) - return; - - DisplayedCount++; - } - - protected void OnCountRolling(int currentValue, int newValue) - { - ScheduledPopOutCurrentId++; - - // Hides displayed count if was increasing from 0 to 1 but didn't finish - if (currentValue == 0 && newValue == 0) - DisplayedCountSpriteText.FadeOut(FadeOutDuration); - - transformRoll(currentValue, newValue); - } - - protected void OnCountIncrement(int currentValue, int newValue) - { - ScheduledPopOutCurrentId++; - - if (DisplayedCount < currentValue) - DisplayedCount++; - - DisplayedCountSpriteText.Show(); - - TransformPopOut(newValue); - - uint newTaskId = ScheduledPopOutCurrentId; - Scheduler.AddDelayed(delegate - { - ScheduledPopOutSmall(newTaskId); - }, PopOutDuration); - } - - protected void OnCountChange(int currentValue, int newValue) - { - ScheduledPopOutCurrentId++; - - if (newValue == 0) - DisplayedCountSpriteText.FadeOut(); - - DisplayedCount = newValue; - } - - protected void OnDisplayedCountRolling(int currentValue, int newValue) - { - if (newValue == 0) - DisplayedCountSpriteText.FadeOut(FadeOutDuration); - else - DisplayedCountSpriteText.Show(); - - if (CanPopOutWhileRolling) - TransformPopOutRolling(newValue); - else - TransformNoPopOut(newValue); - } - - protected void OnDisplayedCountChange(int newValue) - { - DisplayedCountSpriteText.FadeTo(newValue == 0 ? 0 : 1); - - TransformNoPopOut(newValue); - } - - protected void OnDisplayedCountIncrement(int newValue) - { - DisplayedCountSpriteText.Show(); - - TransformPopOutSmall(newValue); - } - - /// - /// Increments the combo by an amount. - /// - /// - public void Increment(int amount = 1) - { - Current.Value += amount; - } - - /// - /// Stops rollover animation, forcing the displayed count to be the actual count. - /// - public void StopRolling() - { updateCount(false); } - protected string FormatCount(int count) - { - return $@"{count}x"; - } - - private double getProportionalDuration(int currentValue, int newValue) - { - double difference = currentValue > newValue ? currentValue - newValue : newValue - currentValue; - return difference * RollingDuration; - } - - private void updateDisplayedCount(int currentValue, int newValue, bool rolling) - { - displayedCount = newValue; - if (rolling) - OnDisplayedCountRolling(currentValue, newValue); - else if (currentValue + 1 == newValue) - OnDisplayedCountIncrement(newValue); - else - OnDisplayedCountChange(newValue); - } - private void updateCount(bool rolling) { int prev = previousValue; @@ -279,24 +124,134 @@ namespace osu.Game.Screens.Play.HUD if (!rolling) { FinishTransforms(false, nameof(DisplayedCount)); - IsRolling = false; + isRolling = false; DisplayedCount = prev; if (prev + 1 == Current.Value) - OnCountIncrement(prev, Current.Value); + onCountIncrement(prev, Current.Value); else - OnCountChange(prev, Current.Value); + onCountChange(prev, Current.Value); } else { - OnCountRolling(displayedCount, Current.Value); - IsRolling = true; + onCountRolling(displayedCount, Current.Value); + isRolling = true; } } - private void transformRoll(int currentValue, int newValue) + private void transformPopOut(int newValue) { - this.TransformTo(nameof(DisplayedCount), newValue, getProportionalDuration(currentValue, newValue), RollingEasing); + ((IHasText)popOutCount).Text = formatCount(newValue); + + popOutCount.ScaleTo(1.6f); + popOutCount.FadeTo(0.75f); + popOutCount.MoveTo(Vector2.Zero); + + popOutCount.ScaleTo(1, pop_out_duration, pop_out_easing); + popOutCount.FadeOut(pop_out_duration, pop_out_easing); + popOutCount.MoveTo(displayedCountSpriteText.Position, pop_out_duration, pop_out_easing); } + + private void transformNoPopOut(int newValue) + { + ((IHasText)displayedCountSpriteText).Text = formatCount(newValue); + + displayedCountSpriteText.ScaleTo(1); + } + + private void transformPopOutSmall(int newValue) + { + ((IHasText)displayedCountSpriteText).Text = formatCount(newValue); + displayedCountSpriteText.ScaleTo(1.1f); + displayedCountSpriteText.ScaleTo(1, pop_out_duration, pop_out_easing); + } + + private void scheduledPopOutSmall(uint id) + { + // Too late; scheduled task invalidated + if (id != scheduledPopOutCurrentId) + return; + + DisplayedCount++; + } + + private void onCountIncrement(int currentValue, int newValue) + { + scheduledPopOutCurrentId++; + + if (DisplayedCount < currentValue) + DisplayedCount++; + + displayedCountSpriteText.Show(); + + transformPopOut(newValue); + + uint newTaskId = scheduledPopOutCurrentId; + + Scheduler.AddDelayed(delegate + { + scheduledPopOutSmall(newTaskId); + }, pop_out_duration); + } + + private void onCountRolling(int currentValue, int newValue) + { + scheduledPopOutCurrentId++; + + // Hides displayed count if was increasing from 0 to 1 but didn't finish + if (currentValue == 0 && newValue == 0) + displayedCountSpriteText.FadeOut(fade_out_duration); + + transformRoll(currentValue, newValue); + } + + private void onCountChange(int currentValue, int newValue) + { + scheduledPopOutCurrentId++; + + if (newValue == 0) + displayedCountSpriteText.FadeOut(); + + DisplayedCount = newValue; + } + + private void onDisplayedCountRolling(int currentValue, int newValue) + { + if (newValue == 0) + displayedCountSpriteText.FadeOut(fade_out_duration); + else + displayedCountSpriteText.Show(); + + transformNoPopOut(newValue); + } + + private void onDisplayedCountChange(int newValue) + { + displayedCountSpriteText.FadeTo(newValue == 0 ? 0 : 1); + transformNoPopOut(newValue); + } + + private void onDisplayedCountIncrement(int newValue) + { + displayedCountSpriteText.Show(); + transformPopOutSmall(newValue); + } + + private void transformRoll(int currentValue, int newValue) => + this.TransformTo(nameof(DisplayedCount), newValue, getProportionalDuration(currentValue, newValue), Easing.None); + + private string formatCount(int count) => $@"{count}x"; + + private double getProportionalDuration(int currentValue, int newValue) + { + double difference = currentValue > newValue ? currentValue - newValue : newValue - currentValue; + return difference * rolling_duration; + } + + private Drawable createSpriteText() => skin?.GetDrawableComponent(new HUDSkinComponent(HUDSkinComponents.ScoreText)) ?? new OsuSpriteText + { + Font = OsuFont.Numeric.With(size: 40), + UseFullGlyphHeight = false, + }; } } From ac4f56403df80e1cfda6c9fb9d94879d63aa0930 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 14 Oct 2020 19:15:52 +0900 Subject: [PATCH 1687/1782] Adjust size/position --- osu.Game/Screens/Play/HUD/LegacyComboCounter.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs b/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs index c96a20405c..55ce68fcc8 100644 --- a/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs +++ b/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs @@ -53,9 +53,9 @@ namespace osu.Game.Screens.Play.HUD Anchor = Anchor.BottomLeft; Origin = Anchor.BottomLeft; - Margin = new MarginPadding { Bottom = 20, Left = 20 }; + Margin = new MarginPadding { Bottom = 10, Left = 10 }; - Scale = new Vector2(1.6f); + Scale = new Vector2(1.2f); } /// From 7d2eeb979532fc643eb6df12fed2691a2c417d66 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 14 Oct 2020 19:18:04 +0900 Subject: [PATCH 1688/1782] Fix test names --- .../NonVisual/DifficultyAdjustmentModCombinationsTest.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/NonVisual/DifficultyAdjustmentModCombinationsTest.cs b/osu.Game.Tests/NonVisual/DifficultyAdjustmentModCombinationsTest.cs index de0397dc84..917f245f4f 100644 --- a/osu.Game.Tests/NonVisual/DifficultyAdjustmentModCombinationsTest.cs +++ b/osu.Game.Tests/NonVisual/DifficultyAdjustmentModCombinationsTest.cs @@ -95,7 +95,7 @@ namespace osu.Game.Tests.NonVisual } [Test] - public void TestMultiMod1() + public void TestMultiModFlattening() { var combinations = new TestLegacyDifficultyCalculator(new ModA(), new MultiMod(new ModB(), new ModC())).CreateDifficultyAdjustmentModCombinations(); @@ -113,7 +113,7 @@ namespace osu.Game.Tests.NonVisual } [Test] - public void TestMultiMod2() + public void TestIncompatibleThroughMultiMod() { var combinations = new TestLegacyDifficultyCalculator(new ModA(), new MultiMod(new ModB(), new ModIncompatibleWithA())).CreateDifficultyAdjustmentModCombinations(); From e9ebeedbe2edd7b1d4e62f9d01d8940b4cf5255a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 14 Oct 2020 19:31:31 +0900 Subject: [PATCH 1689/1782] Refactor generation --- .../Difficulty/DifficultyCalculator.cs | 39 ++++++++++--------- 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs index 9989c750ee..70f248e072 100644 --- a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs @@ -105,10 +105,11 @@ namespace osu.Game.Rulesets.Difficulty /// public Mod[] CreateDifficultyAdjustmentModCombinations() { - return createDifficultyAdjustmentModCombinations(Array.Empty(), DifficultyAdjustmentMods).ToArray(); + return createDifficultyAdjustmentModCombinations(DifficultyAdjustmentMods, Array.Empty()).ToArray(); - static IEnumerable createDifficultyAdjustmentModCombinations(IEnumerable currentSet, Mod[] adjustmentSet, int currentSetCount = 0, int adjustmentSetStart = 0) + static IEnumerable createDifficultyAdjustmentModCombinations(ReadOnlyMemory remainingMods, IEnumerable currentSet, int currentSetCount = 0) { + // Return the current set. switch (currentSetCount) { case 0: @@ -128,11 +129,10 @@ namespace osu.Game.Rulesets.Difficulty break; } - // Apply mods in the adjustment set recursively. Using the entire adjustment set would result in duplicate multi-mod mod - // combinations in further recursions, so a moving subset is used to eliminate this effect - for (int i = adjustmentSetStart; i < adjustmentSet.Length; i++) + // Apply the rest of the remaining mods recursively. + for (int i = 0; i < remainingMods.Length; i++) { - var adjustmentMod = adjustmentSet[i]; + var adjustmentMod = remainingMods.Span[i]; if (currentSet.Any(c => c.IncompatibleMods.Any(m => m.IsInstanceOfType(adjustmentMod)) || adjustmentMod.IncompatibleMods.Any(m => m.IsInstanceOfType(c)))) @@ -141,27 +141,30 @@ namespace osu.Game.Rulesets.Difficulty } // Append the new mod. - int newSetCount = currentSetCount; - var newSet = append(currentSet, adjustmentMod, ref newSetCount); + var (newSet, newSetCount) = flatten(adjustmentMod); - foreach (var combo in createDifficultyAdjustmentModCombinations(newSet, adjustmentSet, newSetCount, i + 1)) + foreach (var combo in createDifficultyAdjustmentModCombinations(remainingMods.Slice(i + 1), currentSet.Concat(newSet), currentSetCount + newSetCount)) yield return combo; } } - // Appends a mod to an existing enumerable, returning the result. Recurses for MultiMod. - static IEnumerable append(IEnumerable existing, Mod mod, ref int count) + // Flattens a mod hierarchy (through MultiMod) as an IEnumerable + static (IEnumerable set, int count) flatten(Mod mod) { - if (mod is MultiMod multi) - { - foreach (var nested in multi.Mods) - existing = append(existing, nested, ref count); + if (!(mod is MultiMod multi)) + return (mod.Yield(), 1); - return existing; + IEnumerable set = Enumerable.Empty(); + int count = 0; + + foreach (var nested in multi.Mods) + { + var (nestedSet, nestedCount) = flatten(nested); + set = set.Concat(nestedSet); + count += nestedCount; } - count++; - return existing.Append(mod); + return (set, count); } } From e3eaba7b2ca46685780041b788dd2d3a229bbd57 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 14 Oct 2020 19:39:48 +0900 Subject: [PATCH 1690/1782] Move ISampleDisabler implementation to Player and FrameStabilityContainer --- .../Gameplay/TestSceneGameplaySamplePlayback.cs | 2 +- osu.Game/Rulesets/UI/FrameStabilityContainer.cs | 12 +++++++++--- osu.Game/Screens/Play/GameplayClock.cs | 10 ++-------- osu.Game/Screens/Play/GameplayClockContainer.cs | 1 - osu.Game/Screens/Play/Player.cs | 13 +++++++++++-- 5 files changed, 23 insertions(+), 15 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySamplePlayback.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySamplePlayback.cs index 5bb3851264..6e505b16c2 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySamplePlayback.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySamplePlayback.cs @@ -26,7 +26,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("get variables", () => { - gameplayClock = Player.ChildrenOfType().First().GameplayClock; + gameplayClock = Player.ChildrenOfType().First(); slider = Player.ChildrenOfType().OrderBy(s => s.HitObject.StartTime).First(); samples = slider.ChildrenOfType().ToArray(); }); diff --git a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs index 70b3d0c7d4..e4a3a2fe3d 100644 --- a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs +++ b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs @@ -18,8 +18,11 @@ namespace osu.Game.Rulesets.UI /// A container which consumes a parent gameplay clock and standardises frame counts for children. /// Will ensure a minimum of 50 frames per clock second is maintained, regardless of any system lag or seeks. /// - public class FrameStabilityContainer : Container, IHasReplayHandler + [Cached(typeof(ISamplePlaybackDisabler))] + public class FrameStabilityContainer : Container, IHasReplayHandler, ISamplePlaybackDisabler { + private readonly Bindable samplePlaybackDisabled = new Bindable(); + private readonly double gameplayStartTime; /// @@ -35,7 +38,6 @@ namespace osu.Game.Rulesets.UI public GameplayClock GameplayClock => stabilityGameplayClock; [Cached(typeof(GameplayClock))] - [Cached(typeof(ISamplePlaybackDisabler))] private readonly StabilityGameplayClock stabilityGameplayClock; public FrameStabilityContainer(double gameplayStartTime = double.MinValue) @@ -102,6 +104,8 @@ namespace osu.Game.Rulesets.UI requireMoreUpdateLoops = true; validState = !GameplayClock.IsPaused.Value; + samplePlaybackDisabled.Value = stabilityGameplayClock.ShouldDisableSamplePlayback; + int loops = 0; while (validState && requireMoreUpdateLoops && loops++ < MaxCatchUpFrames) @@ -224,6 +228,8 @@ namespace osu.Game.Rulesets.UI public ReplayInputHandler ReplayInputHandler { get; set; } + IBindable ISamplePlaybackDisabler.SamplePlaybackDisabled => samplePlaybackDisabled; + private class StabilityGameplayClock : GameplayClock { public GameplayClock ParentGameplayClock; @@ -237,7 +243,7 @@ namespace osu.Game.Rulesets.UI { } - protected override bool ShouldDisableSamplePlayback => + public override bool ShouldDisableSamplePlayback => // handle the case where playback is catching up to real-time. base.ShouldDisableSamplePlayback || ParentSampleDisabler?.SamplePlaybackDisabled.Value == true diff --git a/osu.Game/Screens/Play/GameplayClock.cs b/osu.Game/Screens/Play/GameplayClock.cs index eeea6777c6..4d0872e5bb 100644 --- a/osu.Game/Screens/Play/GameplayClock.cs +++ b/osu.Game/Screens/Play/GameplayClock.cs @@ -17,7 +17,7 @@ namespace osu.Game.Screens.Play /// , as this should only be done once to ensure accuracy. /// /// - public class GameplayClock : IFrameBasedClock, ISamplePlaybackDisabler + public class GameplayClock : IFrameBasedClock { private readonly IFrameBasedClock underlyingClock; @@ -28,8 +28,6 @@ namespace osu.Game.Screens.Play /// public virtual IEnumerable> NonGameplayAdjustments => Enumerable.Empty>(); - private readonly Bindable samplePlaybackDisabled = new Bindable(); - public GameplayClock(IFrameBasedClock underlyingClock) { this.underlyingClock = underlyingClock; @@ -66,13 +64,11 @@ namespace osu.Game.Screens.Play /// /// Whether nested samples supporting the interface should be paused. /// - protected virtual bool ShouldDisableSamplePlayback => IsPaused.Value; + public virtual bool ShouldDisableSamplePlayback => IsPaused.Value; public void ProcessFrame() { // intentionally not updating the underlying clock (handled externally). - - samplePlaybackDisabled.Value = ShouldDisableSamplePlayback; } public double ElapsedFrameTime => underlyingClock.ElapsedFrameTime; @@ -82,7 +78,5 @@ namespace osu.Game.Screens.Play public FrameTimeInfo TimeInfo => underlyingClock.TimeInfo; public IClock Source => underlyingClock; - - IBindable ISamplePlaybackDisabler.SamplePlaybackDisabled => samplePlaybackDisabled; } } diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index 9f8e55f577..6679e56871 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -54,7 +54,6 @@ namespace osu.Game.Screens.Play public GameplayClock GameplayClock => localGameplayClock; [Cached(typeof(GameplayClock))] - [Cached(typeof(ISamplePlaybackDisabler))] private readonly LocalGameplayClock localGameplayClock; private Bindable userAudioOffset; diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index a2a53b4b75..56b212291a 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -35,7 +35,8 @@ using osu.Game.Users; namespace osu.Game.Screens.Play { [Cached] - public class Player : ScreenWithBeatmapBackground + [Cached(typeof(ISamplePlaybackDisabler))] + public class Player : ScreenWithBeatmapBackground, ISamplePlaybackDisabler { /// /// The delay upon completion of the beatmap before displaying the results screen. @@ -55,6 +56,8 @@ namespace osu.Game.Screens.Play // We are managing our own adjustments (see OnEntering/OnExiting). public override bool AllowRateAdjustments => false; + private readonly Bindable samplePlaybackDisabled = new Bindable(); + /// /// Whether gameplay should pause when the game window focus is lost. /// @@ -229,7 +232,11 @@ namespace osu.Game.Screens.Play skipOverlay.Hide(); } - DrawableRuleset.IsPaused.BindValueChanged(_ => updateGameplayState()); + DrawableRuleset.IsPaused.BindValueChanged(paused => + { + updateGameplayState(); + samplePlaybackDisabled.Value = paused.NewValue; + }); DrawableRuleset.HasReplayLoaded.BindValueChanged(_ => updateGameplayState()); DrawableRuleset.HasReplayLoaded.BindValueChanged(_ => updatePauseOnFocusLostState(), true); @@ -752,5 +759,7 @@ namespace osu.Game.Screens.Play } #endregion + + IBindable ISamplePlaybackDisabler.SamplePlaybackDisabled => samplePlaybackDisabled; } } From c4fdd35223e85e383b26b82eec0e8c1212eb11f9 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 14 Oct 2020 19:53:37 +0900 Subject: [PATCH 1691/1782] Fix same-type incompatibility through multimod --- .../DifficultyAdjustmentModCombinationsTest.cs | 14 ++++++++++++++ .../Difficulty/DifficultyCalculator.cs | 18 ++++++++++-------- 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/osu.Game.Tests/NonVisual/DifficultyAdjustmentModCombinationsTest.cs b/osu.Game.Tests/NonVisual/DifficultyAdjustmentModCombinationsTest.cs index 917f245f4f..5c7adb3f49 100644 --- a/osu.Game.Tests/NonVisual/DifficultyAdjustmentModCombinationsTest.cs +++ b/osu.Game.Tests/NonVisual/DifficultyAdjustmentModCombinationsTest.cs @@ -126,6 +126,20 @@ namespace osu.Game.Tests.NonVisual Assert.IsTrue(((MultiMod)combinations[2]).Mods[1] is ModIncompatibleWithA); } + [Test] + public void TestIncompatibleWithSameInstanceViaMultiMod() + { + var combinations = new TestLegacyDifficultyCalculator(new ModA(), new MultiMod(new ModA(), new ModB())).CreateDifficultyAdjustmentModCombinations(); + + Assert.AreEqual(3, combinations.Length); + Assert.IsTrue(combinations[0] is ModNoMod); + Assert.IsTrue(combinations[1] is ModA); + Assert.IsTrue(combinations[2] is MultiMod); + + Assert.IsTrue(((MultiMod)combinations[2]).Mods[0] is ModA); + Assert.IsTrue(((MultiMod)combinations[2]).Mods[1] is ModB); + } + private class ModA : Mod { public override string Name => nameof(ModA); diff --git a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs index 70f248e072..55b3d6607c 100644 --- a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Reflection; using osu.Framework.Audio.Track; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Game.Beatmaps; @@ -11,6 +12,7 @@ using osu.Game.Rulesets.Difficulty.Preprocessing; using osu.Game.Rulesets.Difficulty.Skills; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; +using Sentry; namespace osu.Game.Rulesets.Difficulty { @@ -132,18 +134,18 @@ namespace osu.Game.Rulesets.Difficulty // Apply the rest of the remaining mods recursively. for (int i = 0; i < remainingMods.Length; i++) { - var adjustmentMod = remainingMods.Span[i]; + var (nextSet, nextCount) = flatten(remainingMods.Span[i]); - if (currentSet.Any(c => c.IncompatibleMods.Any(m => m.IsInstanceOfType(adjustmentMod)) - || adjustmentMod.IncompatibleMods.Any(m => m.IsInstanceOfType(c)))) - { + // Check if any mods in the next set are incompatible with any of the current set. + if (currentSet.SelectMany(m => m.IncompatibleMods).Any(c => nextSet.Any(c.IsInstanceOfType))) continue; - } - // Append the new mod. - var (newSet, newSetCount) = flatten(adjustmentMod); + // Check if any mods in the next set are the same type as the current set. Mods of the exact same type are not incompatible with themselves. + if (currentSet.Any(c => nextSet.Any(n => c.GetType() == n.GetType()))) + continue; - foreach (var combo in createDifficultyAdjustmentModCombinations(remainingMods.Slice(i + 1), currentSet.Concat(newSet), currentSetCount + newSetCount)) + // If all's good, attach the next set to the current set and recurse further. + foreach (var combo in createDifficultyAdjustmentModCombinations(remainingMods.Slice(i + 1), currentSet.Concat(nextSet), currentSetCount + nextCount)) yield return combo; } } From ed57b1363fdef33c350d1c65404739aca92750bd Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 14 Oct 2020 20:08:46 +0900 Subject: [PATCH 1692/1782] Remove unused usings --- osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs index 55b3d6607c..7616c48150 100644 --- a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Reflection; using osu.Framework.Audio.Track; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Game.Beatmaps; @@ -12,7 +11,6 @@ using osu.Game.Rulesets.Difficulty.Preprocessing; using osu.Game.Rulesets.Difficulty.Skills; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; -using Sentry; namespace osu.Game.Rulesets.Difficulty { From 1a2dc8374052f770fbd936de980901ba69909aeb Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 14 Oct 2020 20:40:17 +0900 Subject: [PATCH 1693/1782] Make field readonly --- osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs index 757329c525..7a0e3b2b76 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs @@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps public bool Dual; public readonly bool IsForCurrentRuleset; - private int originalTargetColumns; + private readonly int originalTargetColumns; // Internal for testing purposes internal FastRandom Random { get; private set; } From 26dffbfd3bed7b5eafc28d8885b276bfb778e9a6 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 14 Oct 2020 20:40:29 +0900 Subject: [PATCH 1694/1782] Replicate hit window calculation --- .../Difficulty/ManiaDifficultyCalculator.cs | 33 ++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs index 356621acda..ade830764d 100644 --- a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs @@ -26,11 +26,13 @@ namespace osu.Game.Rulesets.Mania.Difficulty private const double star_scaling_factor = 0.018; private readonly bool isForCurrentRuleset; + private readonly double originalOverallDifficulty; public ManiaDifficultyCalculator(Ruleset ruleset, WorkingBeatmap beatmap) : base(ruleset, beatmap) { isForCurrentRuleset = beatmap.BeatmapInfo.Ruleset.Equals(ruleset.RulesetInfo); + originalOverallDifficulty = beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty; } protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate) @@ -46,7 +48,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty StarRating = skills[0].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.WindowFor(HitResult.Great)) / clockRate, + GreatHitWindow = (int)Math.Ceiling(getHitWindow300(mods) / clockRate), ScoreMultiplier = getScoreMultiplier(beatmap, mods), MaxCombo = beatmap.HitObjects.Sum(h => h is HoldNote ? 2 : 1), Skills = skills @@ -107,6 +109,35 @@ namespace osu.Game.Rulesets.Mania.Difficulty } } + private int getHitWindow300(Mod[] mods) + { + if (isForCurrentRuleset) + { + double od = Math.Min(10.0, Math.Max(0, 10.0 - originalOverallDifficulty)); + return applyModAdjustments(34 + 3 * od, mods); + } + + if (Math.Round(originalOverallDifficulty) > 4) + return applyModAdjustments(34, mods); + + return applyModAdjustments(47, mods); + + static int applyModAdjustments(double value, Mod[] mods) + { + if (mods.Any(m => m is ManiaModHardRock)) + value /= 1.4; + else if (mods.Any(m => m is ManiaModEasy)) + value *= 1.4; + + if (mods.Any(m => m is ManiaModDoubleTime)) + value *= 1.5; + else if (mods.Any(m => m is ManiaModHalfTime)) + value *= 0.75; + + return (int)value; + } + } + private double getScoreMultiplier(IBeatmap beatmap, Mod[] mods) { double scoreMultiplier = 1; From b63303a2a813aef2b4a574ab5657157f52e1e2ee Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 14 Oct 2020 21:40:49 +0900 Subject: [PATCH 1695/1782] Fix tests --- .../Gameplay/TestSceneSkinnableSound.cs | 54 ++++++++++--------- 1 file changed, 30 insertions(+), 24 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs index 8f2011e5dd..18eeb0a0e7 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs @@ -13,7 +13,6 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.OpenGL.Textures; using osu.Framework.Graphics.Textures; using osu.Framework.Testing; -using osu.Framework.Timing; using osu.Game.Audio; using osu.Game.Screens.Play; using osu.Game.Skinning; @@ -22,27 +21,24 @@ namespace osu.Game.Tests.Visual.Gameplay { public class TestSceneSkinnableSound : OsuTestScene { - [Cached(typeof(ISamplePlaybackDisabler))] - private GameplayClock gameplayClock = new GameplayClock(new FramedClock()); - private TestSkinSourceContainer skinSource; private PausableSkinnableSound skinnableSound; [SetUp] - public void SetUp() => Schedule(() => + public void SetUpSteps() { - gameplayClock.IsPaused.Value = false; - - Children = new Drawable[] + AddStep("setup heirarchy", () => { - skinSource = new TestSkinSourceContainer + Children = new Drawable[] { - Clock = gameplayClock, - RelativeSizeAxes = Axes.Both, - Child = skinnableSound = new PausableSkinnableSound(new SampleInfo("normal-sliderslide")) - }, - }; - }); + skinSource = new TestSkinSourceContainer + { + RelativeSizeAxes = Axes.Both, + Child = skinnableSound = new PausableSkinnableSound(new SampleInfo("normal-sliderslide")) + }, + }; + }); + } [Test] public void TestStoppedSoundDoesntResumeAfterPause() @@ -62,8 +58,9 @@ namespace osu.Game.Tests.Visual.Gameplay AddUntilStep("wait for sample to stop playing", () => !sample.Playing); - AddStep("pause gameplay clock", () => gameplayClock.IsPaused.Value = true); - AddStep("resume gameplay clock", () => gameplayClock.IsPaused.Value = false); + AddStep("pause gameplay clock", () => skinSource.SamplePlaybackDisabled.Value = true); + + AddStep("resume gameplay clock", () => skinSource.SamplePlaybackDisabled.Value = false); AddWaitStep("wait a bit", 5); AddAssert("sample not playing", () => !sample.Playing); @@ -82,8 +79,11 @@ namespace osu.Game.Tests.Visual.Gameplay AddUntilStep("wait for sample to start playing", () => sample.Playing); - AddStep("pause gameplay clock", () => gameplayClock.IsPaused.Value = true); + AddStep("pause gameplay clock", () => skinSource.SamplePlaybackDisabled.Value = true); AddUntilStep("wait for sample to stop playing", () => !sample.Playing); + + AddStep("resume gameplay clock", () => skinSource.SamplePlaybackDisabled.Value = false); + AddUntilStep("wait for sample to start playing", () => sample.Playing); } [Test] @@ -98,10 +98,11 @@ namespace osu.Game.Tests.Visual.Gameplay AddAssert("sample playing", () => sample.Playing); - AddStep("pause gameplay clock", () => gameplayClock.IsPaused.Value = true); - AddUntilStep("wait for sample to stop playing", () => !sample.Playing); + AddStep("pause gameplay clock", () => skinSource.SamplePlaybackDisabled.Value = true); - AddStep("resume gameplay clock", () => gameplayClock.IsPaused.Value = false); + AddUntilStep("sample not playing", () => !sample.Playing); + + AddStep("resume gameplay clock", () => skinSource.SamplePlaybackDisabled.Value = false); AddAssert("sample not playing", () => !sample.Playing); AddAssert("sample not playing", () => !sample.Playing); @@ -120,7 +121,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddAssert("sample playing", () => sample.Playing); - AddStep("pause gameplay clock", () => gameplayClock.IsPaused.Value = true); + AddStep("pause gameplay clock", () => skinSource.SamplePlaybackDisabled.Value = true); AddUntilStep("wait for sample to stop playing", () => !sample.Playing); AddStep("trigger skin change", () => skinSource.TriggerSourceChanged()); @@ -133,20 +134,25 @@ namespace osu.Game.Tests.Visual.Gameplay }); AddAssert("new sample stopped", () => !sample.Playing); - AddStep("resume gameplay clock", () => gameplayClock.IsPaused.Value = false); + AddStep("resume gameplay clock", () => skinSource.SamplePlaybackDisabled.Value = false); AddWaitStep("wait a bit", 5); AddAssert("new sample not played", () => !sample.Playing); } [Cached(typeof(ISkinSource))] - private class TestSkinSourceContainer : Container, ISkinSource + [Cached(typeof(ISamplePlaybackDisabler))] + private class TestSkinSourceContainer : Container, ISkinSource, ISamplePlaybackDisabler { [Resolved] private ISkinSource source { get; set; } public event Action SourceChanged; + public Bindable SamplePlaybackDisabled { get; } = new Bindable(); + + IBindable ISamplePlaybackDisabler.SamplePlaybackDisabled => SamplePlaybackDisabled; + public Drawable GetDrawableComponent(ISkinComponent component) => source?.GetDrawableComponent(component); public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => source?.GetTexture(componentName, wrapModeS, wrapModeT); public SampleChannel GetSample(ISampleInfo sampleInfo) => source?.GetSample(sampleInfo); From e0210f5c4ce2c8ab00a74b35f9103da01824f148 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 14 Oct 2020 23:52:58 +0900 Subject: [PATCH 1696/1782] Ignore failed casts to make tests happy --- osu.Game/Screens/Play/HUD/SkinnableComboCounter.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/SkinnableComboCounter.cs b/osu.Game/Screens/Play/HUD/SkinnableComboCounter.cs index 9f8ad758e4..f7b6e419ea 100644 --- a/osu.Game/Screens/Play/HUD/SkinnableComboCounter.cs +++ b/osu.Game/Screens/Play/HUD/SkinnableComboCounter.cs @@ -21,8 +21,8 @@ namespace osu.Game.Screens.Play.HUD { base.SkinChanged(skin, allowFallback); - skinnedCounter = (IComboCounter)Drawable; - skinnedCounter.Current.BindTo(Current); + skinnedCounter = Drawable as IComboCounter; + skinnedCounter?.Current.BindTo(Current); } private static Drawable createDefault(ISkinComponent skinComponent) => new DefaultComboCounter(); From 2ca6c4e377fd861e989649e3be5295826866022d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 14 Oct 2020 23:24:16 +0200 Subject: [PATCH 1697/1782] Adjust test step names --- .../Visual/Gameplay/TestSceneSkinnableSound.cs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs index 18eeb0a0e7..864e88d023 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs @@ -27,7 +27,7 @@ namespace osu.Game.Tests.Visual.Gameplay [SetUp] public void SetUpSteps() { - AddStep("setup heirarchy", () => + AddStep("setup hierarchy", () => { Children = new Drawable[] { @@ -58,9 +58,9 @@ namespace osu.Game.Tests.Visual.Gameplay AddUntilStep("wait for sample to stop playing", () => !sample.Playing); - AddStep("pause gameplay clock", () => skinSource.SamplePlaybackDisabled.Value = true); + AddStep("disable sample playback", () => skinSource.SamplePlaybackDisabled.Value = true); - AddStep("resume gameplay clock", () => skinSource.SamplePlaybackDisabled.Value = false); + AddStep("enable sample playback", () => skinSource.SamplePlaybackDisabled.Value = false); AddWaitStep("wait a bit", 5); AddAssert("sample not playing", () => !sample.Playing); @@ -79,10 +79,10 @@ namespace osu.Game.Tests.Visual.Gameplay AddUntilStep("wait for sample to start playing", () => sample.Playing); - AddStep("pause gameplay clock", () => skinSource.SamplePlaybackDisabled.Value = true); + AddStep("disable sample playback", () => skinSource.SamplePlaybackDisabled.Value = true); AddUntilStep("wait for sample to stop playing", () => !sample.Playing); - AddStep("resume gameplay clock", () => skinSource.SamplePlaybackDisabled.Value = false); + AddStep("enable sample playback", () => skinSource.SamplePlaybackDisabled.Value = false); AddUntilStep("wait for sample to start playing", () => sample.Playing); } @@ -98,11 +98,11 @@ namespace osu.Game.Tests.Visual.Gameplay AddAssert("sample playing", () => sample.Playing); - AddStep("pause gameplay clock", () => skinSource.SamplePlaybackDisabled.Value = true); + AddStep("disable sample playback", () => skinSource.SamplePlaybackDisabled.Value = true); AddUntilStep("sample not playing", () => !sample.Playing); - AddStep("resume gameplay clock", () => skinSource.SamplePlaybackDisabled.Value = false); + AddStep("enable sample playback", () => skinSource.SamplePlaybackDisabled.Value = false); AddAssert("sample not playing", () => !sample.Playing); AddAssert("sample not playing", () => !sample.Playing); @@ -121,7 +121,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddAssert("sample playing", () => sample.Playing); - AddStep("pause gameplay clock", () => skinSource.SamplePlaybackDisabled.Value = true); + AddStep("disable sample playback", () => skinSource.SamplePlaybackDisabled.Value = true); AddUntilStep("wait for sample to stop playing", () => !sample.Playing); AddStep("trigger skin change", () => skinSource.TriggerSourceChanged()); @@ -134,7 +134,7 @@ namespace osu.Game.Tests.Visual.Gameplay }); AddAssert("new sample stopped", () => !sample.Playing); - AddStep("resume gameplay clock", () => skinSource.SamplePlaybackDisabled.Value = false); + AddStep("enable sample playback", () => skinSource.SamplePlaybackDisabled.Value = false); AddWaitStep("wait a bit", 5); AddAssert("new sample not played", () => !sample.Playing); From b06f59ffdcf99454bb4447b7e4efb936ebb0f399 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 15 Oct 2020 15:35:33 +0900 Subject: [PATCH 1698/1782] Split out test for combo counter specifically --- .../Visual/Gameplay/TestSceneComboCounter.cs | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 osu.Game.Tests/Visual/Gameplay/TestSceneComboCounter.cs diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneComboCounter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneComboCounter.cs new file mode 100644 index 0000000000..d0c2fb5064 --- /dev/null +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneComboCounter.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.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using osu.Framework.Testing; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Osu; +using osu.Game.Screens.Play.HUD; + +namespace osu.Game.Tests.Visual.Gameplay +{ + public class TestSceneComboCounter : SkinnableTestScene + { + private IEnumerable comboCounters => CreatedDrawables.OfType(); + + protected override Ruleset CreateRulesetForSkinProvider() => new OsuRuleset(); + + [SetUpSteps] + public void SetUpSteps() + { + AddStep("Create combo counters", () => SetContents(() => + { + var comboCounter = new SkinnableComboCounter(); + comboCounter.Current.Value = 1; + return comboCounter; + })); + } + + [Test] + public void TestComboCounterIncrementing() + { + AddRepeatStep("increase combo", () => + { + foreach (var counter in comboCounters) + counter.Current.Value++; + }, 10); + + AddStep("reset combo", () => + { + foreach (var counter in comboCounters) + counter.Current.Value = 0; + }); + } + } +} From d5f2aab52e4ba4f155118fe3450dc7e57a3979a5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 15 Oct 2020 15:37:40 +0900 Subject: [PATCH 1699/1782] Tidy up SkinnableComboCounter class slightly --- osu.Game/Screens/Play/HUD/SkinnableComboCounter.cs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/SkinnableComboCounter.cs b/osu.Game/Screens/Play/HUD/SkinnableComboCounter.cs index f7b6e419ea..c04c50141a 100644 --- a/osu.Game/Screens/Play/HUD/SkinnableComboCounter.cs +++ b/osu.Game/Screens/Play/HUD/SkinnableComboCounter.cs @@ -2,15 +2,16 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Bindables; -using osu.Framework.Graphics; using osu.Game.Skinning; namespace osu.Game.Screens.Play.HUD { public class SkinnableComboCounter : SkinnableDrawable, IComboCounter { + public Bindable Current { get; } = new Bindable(); + public SkinnableComboCounter() - : base(new HUDSkinComponent(HUDSkinComponents.ComboCounter), createDefault) + : base(new HUDSkinComponent(HUDSkinComponents.ComboCounter), skinComponent => new DefaultComboCounter()) { CentreComponent = false; } @@ -24,9 +25,5 @@ namespace osu.Game.Screens.Play.HUD skinnedCounter = Drawable as IComboCounter; skinnedCounter?.Current.BindTo(Current); } - - private static Drawable createDefault(ISkinComponent skinComponent) => new DefaultComboCounter(); - - public Bindable Current { get; } = new Bindable(); } } From 219cbec6bdaae9f8b2c987a1be923b55d6d9a596 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 15 Oct 2020 16:31:21 +0900 Subject: [PATCH 1700/1782] Split out DefaultScoreCounter and make ScoreCounter abstract --- .../Visual/Gameplay/TestSceneScoreCounter.cs | 2 +- .../Graphics/UserInterface/ScoreCounter.cs | 16 +++++++----- .../Screens/Play/HUD/DefaultScoreCounter.cs | 26 +++++++++++++++++++ 3 files changed, 36 insertions(+), 8 deletions(-) create mode 100644 osu.Game/Screens/Play/HUD/DefaultScoreCounter.cs diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneScoreCounter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneScoreCounter.cs index ab010bee9f..ba165a70f4 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneScoreCounter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneScoreCounter.cs @@ -16,7 +16,7 @@ namespace osu.Game.Tests.Visual.Gameplay { int numerator = 0, denominator = 0; - ScoreCounter score = new ScoreCounter(7) + ScoreCounter score = new DefaultScoreCounter() { Origin = Anchor.TopRight, Anchor = Anchor.TopRight, diff --git a/osu.Game/Graphics/UserInterface/ScoreCounter.cs b/osu.Game/Graphics/UserInterface/ScoreCounter.cs index 73bbe5f03e..17e5ceedb9 100644 --- a/osu.Game/Graphics/UserInterface/ScoreCounter.cs +++ b/osu.Game/Graphics/UserInterface/ScoreCounter.cs @@ -1,18 +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.Graphics; using osu.Game.Graphics.Sprites; +using osu.Game.Screens.Play.HUD; namespace osu.Game.Graphics.UserInterface { - public class ScoreCounter : RollingCounter + public abstract class ScoreCounter : RollingCounter, IScoreCounter { protected override double RollingDuration => 1000; protected override Easing RollingEasing => Easing.Out; - public bool UseCommaSeparator; + /// + /// Whether comma separators should be displayed. + /// + public bool UseCommaSeparator { get; } /// /// How many leading zeroes the counter has. @@ -23,14 +26,13 @@ namespace osu.Game.Graphics.UserInterface /// Displays score. /// /// How many leading zeroes the counter will have. - public ScoreCounter(uint leading = 0) + /// Whether comma separators should be displayed. + protected ScoreCounter(uint leading = 0, bool useCommaSeparator = false) { + UseCommaSeparator = useCommaSeparator; LeadingZeroes = leading; } - [BackgroundDependencyLoader] - private void load(OsuColour colours) => Colour = colours.BlueLighter; - protected override double GetProportionalDuration(double currentValue, double newValue) { return currentValue > newValue ? currentValue - newValue : newValue - currentValue; diff --git a/osu.Game/Screens/Play/HUD/DefaultScoreCounter.cs b/osu.Game/Screens/Play/HUD/DefaultScoreCounter.cs new file mode 100644 index 0000000000..a461b6a067 --- /dev/null +++ b/osu.Game/Screens/Play/HUD/DefaultScoreCounter.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.Game.Graphics; +using osu.Game.Graphics.UserInterface; + +namespace osu.Game.Screens.Play.HUD +{ + public class DefaultScoreCounter : ScoreCounter + { + public DefaultScoreCounter() + : base(6) + { + Anchor = Anchor.TopCentre; + Origin = Anchor.TopCentre; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + Colour = colours.BlueLighter; + } + } +} From e1da64398e279d0eb6fe53bce2a9e2936403558f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 15 Oct 2020 16:32:20 +0900 Subject: [PATCH 1701/1782] Add and consume skinnable score counter --- .../Visual/Gameplay/TestSceneScoreCounter.cs | 2 +- .../TestSceneSkinnableScoreCounter.cs | 47 +++++++++++++++++++ osu.Game/Screens/Play/HUD/IScoreCounter.cs | 19 ++++++++ .../Screens/Play/HUD/SkinnableScoreCounter.cs | 29 ++++++++++++ osu.Game/Screens/Play/HUDOverlay.cs | 8 +--- osu.Game/Skinning/HUDSkinComponents.cs | 3 +- osu.Game/Skinning/LegacyScoreCounter.cs | 42 +++++++++++++++++ osu.Game/Skinning/LegacySkin.cs | 3 ++ 8 files changed, 145 insertions(+), 8 deletions(-) create mode 100644 osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableScoreCounter.cs create mode 100644 osu.Game/Screens/Play/HUD/IScoreCounter.cs create mode 100644 osu.Game/Screens/Play/HUD/SkinnableScoreCounter.cs create mode 100644 osu.Game/Skinning/LegacyScoreCounter.cs diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneScoreCounter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneScoreCounter.cs index ba165a70f4..34c657bf7f 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneScoreCounter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneScoreCounter.cs @@ -16,7 +16,7 @@ namespace osu.Game.Tests.Visual.Gameplay { int numerator = 0, denominator = 0; - ScoreCounter score = new DefaultScoreCounter() + ScoreCounter score = new DefaultScoreCounter { Origin = Anchor.TopRight, Anchor = Anchor.TopRight, diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableScoreCounter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableScoreCounter.cs new file mode 100644 index 0000000000..2d5003d1da --- /dev/null +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableScoreCounter.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.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using osu.Framework.Testing; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Osu; +using osu.Game.Screens.Play.HUD; + +namespace osu.Game.Tests.Visual.Gameplay +{ + public class TestSceneSkinnableScoreCounter : SkinnableTestScene + { + private IEnumerable scoreCounters => CreatedDrawables.OfType(); + + protected override Ruleset CreateRulesetForSkinProvider() => new OsuRuleset(); + + [SetUpSteps] + public void SetUpSteps() + { + AddStep("Create combo counters", () => SetContents(() => + { + var comboCounter = new SkinnableScoreCounter(); + comboCounter.Current.Value = 1; + return comboCounter; + })); + } + + [Test] + public void TestScoreCounterIncrementing() + { + AddStep(@"Reset all", delegate + { + foreach (var s in scoreCounters) + s.Current.Value = 0; + }); + + AddStep(@"Hit! :D", delegate + { + foreach (var s in scoreCounters) + s.Current.Value += 300; + }); + } + } +} diff --git a/osu.Game/Screens/Play/HUD/IScoreCounter.cs b/osu.Game/Screens/Play/HUD/IScoreCounter.cs new file mode 100644 index 0000000000..2d39a64cfe --- /dev/null +++ b/osu.Game/Screens/Play/HUD/IScoreCounter.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.Bindables; +using osu.Framework.Graphics; + +namespace osu.Game.Screens.Play.HUD +{ + /// + /// An interface providing a set of methods to update a score counter. + /// + public interface IScoreCounter : IDrawable + { + /// + /// The current score to be displayed. + /// + Bindable Current { get; } + } +} diff --git a/osu.Game/Screens/Play/HUD/SkinnableScoreCounter.cs b/osu.Game/Screens/Play/HUD/SkinnableScoreCounter.cs new file mode 100644 index 0000000000..a442ad0d9a --- /dev/null +++ b/osu.Game/Screens/Play/HUD/SkinnableScoreCounter.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.Framework.Bindables; +using osu.Game.Skinning; + +namespace osu.Game.Screens.Play.HUD +{ + public class SkinnableScoreCounter : SkinnableDrawable, IScoreCounter + { + public Bindable Current { get; } = new Bindable(); + + public SkinnableScoreCounter() + : base(new HUDSkinComponent(HUDSkinComponents.ScoreCounter), _ => new DefaultScoreCounter()) + { + CentreComponent = false; + } + + private IScoreCounter skinnedCounter; + + protected override void SkinChanged(ISkinSource skin, bool allowFallback) + { + base.SkinChanged(skin, allowFallback); + + skinnedCounter = Drawable as IScoreCounter; + skinnedCounter?.Current.BindTo(Current); + } + } +} diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index a3547bbc68..56b8d60bd4 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -30,7 +30,7 @@ namespace osu.Game.Screens.Play public readonly KeyCounterDisplay KeyCounter; public readonly SkinnableComboCounter ComboCounter; - public readonly ScoreCounter ScoreCounter; + public readonly SkinnableScoreCounter ScoreCounter; public readonly RollingCounter AccuracyCounter; public readonly HealthDisplay HealthDisplay; public readonly SongProgress Progress; @@ -269,11 +269,7 @@ namespace osu.Game.Screens.Play Margin = new MarginPadding { Top = 5, Right = 20 }, }; - protected virtual ScoreCounter CreateScoreCounter() => new ScoreCounter(6) - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - }; + protected virtual SkinnableScoreCounter CreateScoreCounter() => new SkinnableScoreCounter(); protected virtual SkinnableComboCounter CreateComboCounter() => new SkinnableComboCounter(); diff --git a/osu.Game/Skinning/HUDSkinComponents.cs b/osu.Game/Skinning/HUDSkinComponents.cs index 7577ba066c..7863161971 100644 --- a/osu.Game/Skinning/HUDSkinComponents.cs +++ b/osu.Game/Skinning/HUDSkinComponents.cs @@ -6,6 +6,7 @@ namespace osu.Game.Skinning public enum HUDSkinComponents { ComboCounter, - ScoreText + ScoreText, + ScoreCounter } } diff --git a/osu.Game/Skinning/LegacyScoreCounter.cs b/osu.Game/Skinning/LegacyScoreCounter.cs new file mode 100644 index 0000000000..0e1d4fba7f --- /dev/null +++ b/osu.Game/Skinning/LegacyScoreCounter.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.Bindables; +using osu.Framework.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; + +namespace osu.Game.Skinning +{ + public class LegacyScoreCounter : ScoreCounter + { + private readonly ISkin skin; + + protected override double RollingDuration => 1000; + protected override Easing RollingEasing => Easing.Out; + + public new Bindable Current { get; } = new Bindable(); + + public LegacyScoreCounter(ISkin skin) + : base(6) + { + Anchor = Anchor.TopRight; + Origin = Anchor.TopRight; + + this.skin = skin; + + // base class uses int for display, but externally we bind to ScoreProcesssor as a double for now. + Current.BindValueChanged(v => base.Current.Value = (int)v.NewValue); + + Margin = new MarginPadding { Bottom = 10, Left = 10 }; + Scale = new Vector2(1.2f); + } + + protected sealed override OsuSpriteText CreateSpriteText() => + new LegacySpriteText(skin, "score" /*, true*/) + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + }; + } +} diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index a4d47dd2f1..8f4539ca6d 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -335,6 +335,9 @@ namespace osu.Game.Skinning case HUDSkinComponents.ComboCounter: return new LegacyComboCounter(); + case HUDSkinComponents.ScoreCounter: + return new LegacyScoreCounter(this); + case HUDSkinComponents.ScoreText: const string font = "score"; From 950c47287ca4ce9c4b71f3dc346ba75918341c6a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 15 Oct 2020 16:56:05 +0900 Subject: [PATCH 1702/1782] Fix positioning of score display in HUD overlay --- .../Screens/Play/HUD/DefaultScoreCounter.cs | 12 ++++++ osu.Game/Screens/Play/HUDOverlay.cs | 41 ++++--------------- osu.Game/Skinning/LegacyScoreCounter.cs | 3 +- 3 files changed, 22 insertions(+), 34 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/DefaultScoreCounter.cs b/osu.Game/Screens/Play/HUD/DefaultScoreCounter.cs index a461b6a067..af78ce4be2 100644 --- a/osu.Game/Screens/Play/HUD/DefaultScoreCounter.cs +++ b/osu.Game/Screens/Play/HUD/DefaultScoreCounter.cs @@ -5,11 +5,14 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; +using osuTK; namespace osu.Game.Screens.Play.HUD { public class DefaultScoreCounter : ScoreCounter { + private readonly Vector2 offset = new Vector2(20, 5); + public DefaultScoreCounter() : base(6) { @@ -17,10 +20,19 @@ namespace osu.Game.Screens.Play.HUD Origin = Anchor.TopCentre; } + [Resolved(canBeNull: true)] + private HUDOverlay hud { get; set; } + [BackgroundDependencyLoader] private void load(OsuColour colours) { Colour = colours.BlueLighter; + + // todo: check if default once health display is skinnable + hud?.ShowHealthbar.BindValueChanged(healthBar => + { + this.MoveToY(healthBar.NewValue ? 30 : 0, HUDOverlay.FADE_DURATION, HUDOverlay.FADE_EASING); + }, true); } } } diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 56b8d60bd4..a507eaaa8d 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -25,8 +25,9 @@ namespace osu.Game.Screens.Play [Cached] public class HUDOverlay : Container { - private const float fade_duration = 400; - private const Easing fade_easing = Easing.Out; + public const float FADE_DURATION = 400; + + public const Easing FADE_EASING = Easing.Out; public readonly KeyCounterDisplay KeyCounter; public readonly SkinnableComboCounter ComboCounter; @@ -62,8 +63,6 @@ namespace osu.Game.Screens.Play public Action RequestSeek; - private readonly Container topScoreContainer; - private readonly FillFlowContainer bottomRightElements; private IEnumerable hideTargets => new Drawable[] { visibilityContainer, KeyCounter }; @@ -96,17 +95,8 @@ namespace osu.Game.Screens.Play Children = new Drawable[] { HealthDisplay = CreateHealthDisplay(), - topScoreContainer = new Container - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - AutoSizeAxes = Axes.Both, - Children = new Drawable[] - { - AccuracyCounter = CreateAccuracyCounter(), - ScoreCounter = CreateScoreCounter(), - }, - }, + AccuracyCounter = CreateAccuracyCounter(), + ScoreCounter = CreateScoreCounter(), ComboCounter = CreateComboCounter(), ModDisplay = CreateModsContainer(), HitErrorDisplay = CreateHitErrorDisplayOverlay(), @@ -132,8 +122,8 @@ namespace osu.Game.Screens.Play Origin = Anchor.BottomRight, X = -5, AutoSizeAxes = Axes.Both, - LayoutDuration = fade_duration / 2, - LayoutEasing = fade_easing, + LayoutDuration = FADE_DURATION / 2, + LayoutEasing = FADE_EASING, Direction = FillDirection.Vertical, Children = new Drawable[] { @@ -186,21 +176,8 @@ namespace osu.Game.Screens.Play { base.LoadComplete(); - ShowHud.BindValueChanged(visible => hideTargets.ForEach(d => d.FadeTo(visible.NewValue ? 1 : 0, fade_duration, fade_easing))); - - ShowHealthbar.BindValueChanged(healthBar => - { - if (healthBar.NewValue) - { - HealthDisplay.FadeIn(fade_duration, fade_easing); - topScoreContainer.MoveToY(30, fade_duration, fade_easing); - } - else - { - HealthDisplay.FadeOut(fade_duration, fade_easing); - topScoreContainer.MoveToY(0, fade_duration, fade_easing); - } - }, true); + ShowHealthbar.BindValueChanged(healthBar => HealthDisplay.FadeTo(healthBar.NewValue ? 1 : 0, FADE_DURATION, FADE_EASING), true); + ShowHud.BindValueChanged(visible => hideTargets.ForEach(d => d.FadeTo(visible.NewValue ? 1 : 0, FADE_DURATION, FADE_EASING))); configShowHud.BindValueChanged(visible => { diff --git a/osu.Game/Skinning/LegacyScoreCounter.cs b/osu.Game/Skinning/LegacyScoreCounter.cs index 0e1d4fba7f..f94bef6652 100644 --- a/osu.Game/Skinning/LegacyScoreCounter.cs +++ b/osu.Game/Skinning/LegacyScoreCounter.cs @@ -28,8 +28,7 @@ namespace osu.Game.Skinning // base class uses int for display, but externally we bind to ScoreProcesssor as a double for now. Current.BindValueChanged(v => base.Current.Value = (int)v.NewValue); - Margin = new MarginPadding { Bottom = 10, Left = 10 }; - Scale = new Vector2(1.2f); + Margin = new MarginPadding(10); } protected sealed override OsuSpriteText CreateSpriteText() => From b210147c2e4424f5c935ae28558c10b80a9aa61c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 15 Oct 2020 16:55:47 +0900 Subject: [PATCH 1703/1782] Update combo counter to read from default score display's position correctly --- osu.Game/Screens/Play/HUD/DefaultComboCounter.cs | 4 ++-- osu.Game/Screens/Play/HUD/LegacyComboCounter.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/DefaultComboCounter.cs b/osu.Game/Screens/Play/HUD/DefaultComboCounter.cs index 1e23319c28..5ffaf0d388 100644 --- a/osu.Game/Screens/Play/HUD/DefaultComboCounter.cs +++ b/osu.Game/Screens/Play/HUD/DefaultComboCounter.cs @@ -37,10 +37,10 @@ namespace osu.Game.Screens.Play.HUD { base.Update(); - if (hud != null) + if (hud?.ScoreCounter.Drawable is DefaultScoreCounter score) { // for now align with the score counter. eventually this will be user customisable. - Position += ToLocalSpace(hud.ScoreCounter.ScreenSpaceDrawQuad.TopRight) + offset; + Position += ToLocalSpace(score.ScreenSpaceDrawQuad.TopRight) + offset; } } diff --git a/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs b/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs index 55ce68fcc8..66f4c5edb8 100644 --- a/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs +++ b/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs @@ -53,7 +53,7 @@ namespace osu.Game.Screens.Play.HUD Anchor = Anchor.BottomLeft; Origin = Anchor.BottomLeft; - Margin = new MarginPadding { Bottom = 10, Left = 10 }; + Margin = new MarginPadding(10); Scale = new Vector2(1.2f); } From 74c031cfbb385d4f081f0f110b66a33e6c7376f6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 15 Oct 2020 17:10:35 +0900 Subject: [PATCH 1704/1782] Fix ModOverlay not including "UNRANKED" text in size --- osu.Game/Screens/Play/HUD/ModDisplay.cs | 33 +++++++++++++++---------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/ModDisplay.cs b/osu.Game/Screens/Play/HUD/ModDisplay.cs index 99c31241f1..68d019bf71 100644 --- a/osu.Game/Screens/Play/HUD/ModDisplay.cs +++ b/osu.Game/Screens/Play/HUD/ModDisplay.cs @@ -48,22 +48,29 @@ namespace osu.Game.Screens.Play.HUD { AutoSizeAxes = Axes.Both; - Children = new Drawable[] + Child = new FillFlowContainer { - iconsContainer = new ReverseChildIDFillFlowContainer + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Children = new Drawable[] { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, + iconsContainer = new ReverseChildIDFillFlowContainer + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + }, + unrankedText = new OsuSpriteText + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Text = @"/ UNRANKED /", + Font = OsuFont.Numeric.With(size: 12) + } }, - unrankedText = new OsuSpriteText - { - Anchor = Anchor.BottomCentre, - Origin = Anchor.TopCentre, - Text = @"/ UNRANKED /", - Font = OsuFont.Numeric.With(size: 12) - } }; Current.ValueChanged += mods => From d8d085ede94aedf9051ab85ab035235bab38d215 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 15 Oct 2020 17:11:02 +0900 Subject: [PATCH 1705/1782] Align top-right elements with lowest point in score display --- .../Screens/Play/HUD/PlayerSettingsOverlay.cs | 10 ++++---- osu.Game/Screens/Play/HUDOverlay.cs | 24 +++++++++++++++---- 2 files changed, 23 insertions(+), 11 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/PlayerSettingsOverlay.cs b/osu.Game/Screens/Play/HUD/PlayerSettingsOverlay.cs index fc80983834..ffcbb06fb3 100644 --- a/osu.Game/Screens/Play/HUD/PlayerSettingsOverlay.cs +++ b/osu.Game/Screens/Play/HUD/PlayerSettingsOverlay.cs @@ -20,14 +20,13 @@ namespace osu.Game.Screens.Play.HUD public readonly VisualSettings VisualSettings; - //public readonly CollectionSettings CollectionSettings; - - //public readonly DiscussionSettings DiscussionSettings; - public PlayerSettingsOverlay() { AlwaysPresent = true; - RelativeSizeAxes = Axes.Both; + + Anchor = Anchor.TopRight; + Origin = Anchor.TopRight; + AutoSizeAxes = Axes.Both; Child = new FillFlowContainer { @@ -36,7 +35,6 @@ namespace osu.Game.Screens.Play.HUD AutoSizeAxes = Axes.Both, Direction = FillDirection.Vertical, Spacing = new Vector2(0, 20), - Margin = new MarginPadding { Top = 100, Right = 10 }, Children = new PlayerSettingsGroup[] { //CollectionSettings = new CollectionSettings(), diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index a507eaaa8d..639da7a3b6 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -64,6 +64,7 @@ namespace osu.Game.Screens.Play public Action RequestSeek; private readonly FillFlowContainer bottomRightElements; + private readonly FillFlowContainer topRightElements; private IEnumerable hideTargets => new Drawable[] { visibilityContainer, KeyCounter }; @@ -98,9 +99,7 @@ namespace osu.Game.Screens.Play AccuracyCounter = CreateAccuracyCounter(), ScoreCounter = CreateScoreCounter(), ComboCounter = CreateComboCounter(), - ModDisplay = CreateModsContainer(), HitErrorDisplay = CreateHitErrorDisplayOverlay(), - PlayerSettingsOverlay = CreatePlayerSettingsOverlay(), } }, }, @@ -116,11 +115,26 @@ namespace osu.Game.Screens.Play } }, }, + topRightElements = new FillFlowContainer + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + Margin = new MarginPadding(10), + Spacing = new Vector2(10), + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + ModDisplay = CreateModsContainer(), + PlayerSettingsOverlay = CreatePlayerSettingsOverlay(), + } + }, bottomRightElements = new FillFlowContainer { Anchor = Anchor.BottomRight, Origin = Anchor.BottomRight, - X = -5, + Margin = new MarginPadding(10), + Spacing = new Vector2(10), AutoSizeAxes = Axes.Both, LayoutDuration = FADE_DURATION / 2, LayoutEasing = FADE_EASING, @@ -191,6 +205,8 @@ namespace osu.Game.Screens.Play protected override void Update() { base.Update(); + + topRightElements.Y = ToLocalSpace(ScoreCounter.Drawable.ScreenSpaceDrawQuad.BottomRight).Y; bottomRightElements.Y = -Progress.Height; } @@ -266,7 +282,6 @@ namespace osu.Game.Screens.Play { Anchor = Anchor.BottomRight, Origin = Anchor.BottomRight, - Margin = new MarginPadding(10), }; protected virtual SongProgress CreateProgress() => new SongProgress @@ -287,7 +302,6 @@ namespace osu.Game.Screens.Play Anchor = Anchor.TopRight, Origin = Anchor.TopRight, AutoSizeAxes = Axes.Both, - Margin = new MarginPadding { Top = 20, Right = 20 }, }; protected virtual HitErrorDisplay CreateHitErrorDisplayOverlay() => new HitErrorDisplay(scoreProcessor, drawableRuleset?.FirstAvailableHitWindows); From 5b5ba7df936f159df034467c0786e6ff4d161838 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 15 Oct 2020 17:22:34 +0900 Subject: [PATCH 1706/1782] Remove unused offset --- osu.Game/Screens/Play/HUD/DefaultScoreCounter.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/DefaultScoreCounter.cs b/osu.Game/Screens/Play/HUD/DefaultScoreCounter.cs index af78ce4be2..1dcfe2e067 100644 --- a/osu.Game/Screens/Play/HUD/DefaultScoreCounter.cs +++ b/osu.Game/Screens/Play/HUD/DefaultScoreCounter.cs @@ -5,14 +5,11 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; -using osuTK; namespace osu.Game.Screens.Play.HUD { public class DefaultScoreCounter : ScoreCounter { - private readonly Vector2 offset = new Vector2(20, 5); - public DefaultScoreCounter() : base(6) { From 9f51327e4b409382f6750c4c8687a66ee59a6d7a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 15 Oct 2020 17:29:40 +0900 Subject: [PATCH 1707/1782] Fix completely incorrect default positioning logic --- osu.Game/Screens/Play/HUD/DefaultComboCounter.cs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/DefaultComboCounter.cs b/osu.Game/Screens/Play/HUD/DefaultComboCounter.cs index 1e23319c28..d6a4d30af6 100644 --- a/osu.Game/Screens/Play/HUD/DefaultComboCounter.cs +++ b/osu.Game/Screens/Play/HUD/DefaultComboCounter.cs @@ -23,11 +23,6 @@ namespace osu.Game.Screens.Play.HUD public DefaultComboCounter() { Current.Value = DisplayedCount = 0; - - Anchor = Anchor.TopCentre; - Origin = Anchor.TopLeft; - - Position = offset; } [BackgroundDependencyLoader] @@ -40,7 +35,7 @@ namespace osu.Game.Screens.Play.HUD if (hud != null) { // for now align with the score counter. eventually this will be user customisable. - Position += ToLocalSpace(hud.ScoreCounter.ScreenSpaceDrawQuad.TopRight) + offset; + Position = Parent.ToLocalSpace(hud.ScoreCounter.ScreenSpaceDrawQuad.TopRight) + offset; } } From 37e9f331ad78fb9ff10701888169e5ea47ddea7a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 15 Oct 2020 17:48:50 +0900 Subject: [PATCH 1708/1782] Simplify score font lookup --- osu.Game/Screens/Play/HUD/LegacyComboCounter.cs | 8 +------- osu.Game/Skinning/HUDSkinComponents.cs | 1 - osu.Game/Skinning/LegacySkin.cs | 15 +++++++-------- osu.Game/Skinning/LegacySpriteText.cs | 2 +- 4 files changed, 9 insertions(+), 17 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs b/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs index 55ce68fcc8..5d96a48117 100644 --- a/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs +++ b/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs @@ -6,8 +6,6 @@ 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.Skinning; using osuTK; @@ -248,10 +246,6 @@ namespace osu.Game.Screens.Play.HUD return difference * rolling_duration; } - private Drawable createSpriteText() => skin?.GetDrawableComponent(new HUDSkinComponent(HUDSkinComponents.ScoreText)) ?? new OsuSpriteText - { - Font = OsuFont.Numeric.With(size: 40), - UseFullGlyphHeight = false, - }; + private Drawable createSpriteText() => new LegacySpriteText(skin); } } diff --git a/osu.Game/Skinning/HUDSkinComponents.cs b/osu.Game/Skinning/HUDSkinComponents.cs index 7577ba066c..06b22dc693 100644 --- a/osu.Game/Skinning/HUDSkinComponents.cs +++ b/osu.Game/Skinning/HUDSkinComponents.cs @@ -6,6 +6,5 @@ namespace osu.Game.Skinning public enum HUDSkinComponents { ComboCounter, - ScoreText } } diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index a4d47dd2f1..ea5a6e4e20 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -324,24 +324,23 @@ namespace osu.Game.Skinning return null; } + private const string score_font = "score"; + + private bool hasScoreFont => this.HasFont(score_font); + public override Drawable GetDrawableComponent(ISkinComponent component) { switch (component) { case HUDSkinComponent hudComponent: { + if (!hasScoreFont) + return null; + switch (hudComponent.Component) { case HUDSkinComponents.ComboCounter: return new LegacyComboCounter(); - - case HUDSkinComponents.ScoreText: - const string font = "score"; - - if (!this.HasFont(font)) - return null; - - return new LegacySpriteText(this, font); } return null; diff --git a/osu.Game/Skinning/LegacySpriteText.cs b/osu.Game/Skinning/LegacySpriteText.cs index 773a9dc5c6..858bbcd6a8 100644 --- a/osu.Game/Skinning/LegacySpriteText.cs +++ b/osu.Game/Skinning/LegacySpriteText.cs @@ -12,7 +12,7 @@ namespace osu.Game.Skinning { private readonly LegacyGlyphStore glyphStore; - public LegacySpriteText(ISkin skin, string font) + public LegacySpriteText(ISkin skin, string font = "score") { Shadow = false; UseFullGlyphHeight = false; From 254eba90080f04398ade62a52c85b67345068954 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 15 Oct 2020 17:48:50 +0900 Subject: [PATCH 1709/1782] Add and consume skinnable accuracy counter --- .../UserInterface/PercentageCounter.cs | 4 -- .../Play/HUD/DefaultAccuracyCounter.cs | 41 ++++++++++++++++ osu.Game/Screens/Play/HUD/IAccuracyCounter.cs | 19 ++++++++ .../Play/HUD/SkinnableAccuracyCounter.cs | 29 ++++++++++++ osu.Game/Screens/Play/HUDOverlay.cs | 11 +---- osu.Game/Skinning/HUDSkinComponents.cs | 3 +- osu.Game/Skinning/LegacyAccuracyCounter.cs | 47 +++++++++++++++++++ osu.Game/Skinning/LegacySkin.cs | 3 ++ 8 files changed, 143 insertions(+), 14 deletions(-) create mode 100644 osu.Game/Screens/Play/HUD/DefaultAccuracyCounter.cs create mode 100644 osu.Game/Screens/Play/HUD/IAccuracyCounter.cs create mode 100644 osu.Game/Screens/Play/HUD/SkinnableAccuracyCounter.cs create mode 100644 osu.Game/Skinning/LegacyAccuracyCounter.cs diff --git a/osu.Game/Graphics/UserInterface/PercentageCounter.cs b/osu.Game/Graphics/UserInterface/PercentageCounter.cs index 1ccf7798e5..2d53ec066b 100644 --- a/osu.Game/Graphics/UserInterface/PercentageCounter.cs +++ b/osu.Game/Graphics/UserInterface/PercentageCounter.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Utils; @@ -28,9 +27,6 @@ namespace osu.Game.Graphics.UserInterface Current.Value = DisplayedCount = 1.0f; } - [BackgroundDependencyLoader] - private void load(OsuColour colours) => Colour = colours.BlueLighter; - protected override string FormatCount(double count) => count.FormatAccuracy(); protected override double GetProportionalDuration(double currentValue, double newValue) diff --git a/osu.Game/Screens/Play/HUD/DefaultAccuracyCounter.cs b/osu.Game/Screens/Play/HUD/DefaultAccuracyCounter.cs new file mode 100644 index 0000000000..b286b380e0 --- /dev/null +++ b/osu.Game/Screens/Play/HUD/DefaultAccuracyCounter.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 osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Game.Graphics; +using osu.Game.Graphics.UserInterface; +using osuTK; + +namespace osu.Game.Screens.Play.HUD +{ + public class DefaultAccuracyCounter : PercentageCounter, IAccuracyCounter + { + private readonly Vector2 offset = new Vector2(-20, 5); + + public DefaultAccuracyCounter() + { + Origin = Anchor.TopRight; + } + + [Resolved(canBeNull: true)] + private HUDOverlay hud { get; set; } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + Colour = colours.BlueLighter; + } + + protected override void Update() + { + base.Update(); + + if (hud?.ScoreCounter.Drawable is DefaultScoreCounter score) + { + // for now align with the score counter. eventually this will be user customisable. + Position = Parent.ToLocalSpace(score.ScreenSpaceDrawQuad.TopLeft) + offset; + } + } + } +} diff --git a/osu.Game/Screens/Play/HUD/IAccuracyCounter.cs b/osu.Game/Screens/Play/HUD/IAccuracyCounter.cs new file mode 100644 index 0000000000..0199250a08 --- /dev/null +++ b/osu.Game/Screens/Play/HUD/IAccuracyCounter.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.Bindables; +using osu.Framework.Graphics; + +namespace osu.Game.Screens.Play.HUD +{ + /// + /// An interface providing a set of methods to update a accuracy counter. + /// + public interface IAccuracyCounter : IDrawable + { + /// + /// The current accuracy to be displayed. + /// + Bindable Current { get; } + } +} diff --git a/osu.Game/Screens/Play/HUD/SkinnableAccuracyCounter.cs b/osu.Game/Screens/Play/HUD/SkinnableAccuracyCounter.cs new file mode 100644 index 0000000000..76c9c30813 --- /dev/null +++ b/osu.Game/Screens/Play/HUD/SkinnableAccuracyCounter.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.Framework.Bindables; +using osu.Game.Skinning; + +namespace osu.Game.Screens.Play.HUD +{ + public class SkinnableAccuracyCounter : SkinnableDrawable, IAccuracyCounter + { + public Bindable Current { get; } = new Bindable(); + + public SkinnableAccuracyCounter() + : base(new HUDSkinComponent(HUDSkinComponents.AccuracyCounter), _ => new DefaultAccuracyCounter()) + { + CentreComponent = false; + } + + private IAccuracyCounter skinnedCounter; + + protected override void SkinChanged(ISkinSource skin, bool allowFallback) + { + base.SkinChanged(skin, allowFallback); + + skinnedCounter = Drawable as IAccuracyCounter; + skinnedCounter?.Current.BindTo(Current); + } + } +} diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 639da7a3b6..bb35bd3d69 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -10,7 +10,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Events; using osu.Game.Configuration; -using osu.Game.Graphics.UserInterface; using osu.Game.Overlays; using osu.Game.Overlays.Notifications; using osu.Game.Rulesets.Mods; @@ -32,7 +31,7 @@ namespace osu.Game.Screens.Play public readonly KeyCounterDisplay KeyCounter; public readonly SkinnableComboCounter ComboCounter; public readonly SkinnableScoreCounter ScoreCounter; - public readonly RollingCounter AccuracyCounter; + public readonly SkinnableAccuracyCounter AccuracyCounter; public readonly HealthDisplay HealthDisplay; public readonly SongProgress Progress; public readonly ModDisplay ModDisplay; @@ -254,13 +253,7 @@ namespace osu.Game.Screens.Play return base.OnKeyDown(e); } - protected virtual RollingCounter CreateAccuracyCounter() => new PercentageCounter - { - BypassAutoSizeAxes = Axes.X, - Anchor = Anchor.TopLeft, - Origin = Anchor.TopRight, - Margin = new MarginPadding { Top = 5, Right = 20 }, - }; + protected virtual SkinnableAccuracyCounter CreateAccuracyCounter() => new SkinnableAccuracyCounter(); protected virtual SkinnableScoreCounter CreateScoreCounter() => new SkinnableScoreCounter(); diff --git a/osu.Game/Skinning/HUDSkinComponents.cs b/osu.Game/Skinning/HUDSkinComponents.cs index d810fc31d4..d690a23dee 100644 --- a/osu.Game/Skinning/HUDSkinComponents.cs +++ b/osu.Game/Skinning/HUDSkinComponents.cs @@ -6,6 +6,7 @@ namespace osu.Game.Skinning public enum HUDSkinComponents { ComboCounter, - ScoreCounter + ScoreCounter, + AccuracyCounter } } diff --git a/osu.Game/Skinning/LegacyAccuracyCounter.cs b/osu.Game/Skinning/LegacyAccuracyCounter.cs new file mode 100644 index 0000000000..0f3ac19ce6 --- /dev/null +++ b/osu.Game/Skinning/LegacyAccuracyCounter.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 osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osu.Game.Screens.Play; +using osu.Game.Screens.Play.HUD; +using osuTK; + +namespace osu.Game.Skinning +{ + public class LegacyAccuracyCounter : PercentageCounter, IAccuracyCounter + { + private readonly ISkin skin; + + public LegacyAccuracyCounter(ISkin skin) + { + Origin = Anchor.TopRight; + Scale = new Vector2(0.75f); + + this.skin = skin; + } + + [Resolved(canBeNull: true)] + private HUDOverlay hud { get; set; } + + protected sealed override OsuSpriteText CreateSpriteText() => + new LegacySpriteText(skin, "score" /*, true*/) + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + }; + + protected override void Update() + { + base.Update(); + + if (hud?.ScoreCounter.Drawable is LegacyScoreCounter score) + { + // for now align with the score counter. eventually this will be user customisable. + Position = Parent.ToLocalSpace(score.ScreenSpaceDrawQuad.BottomRight); + } + } + } +} diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index c8460ad797..e1cd095ba8 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -344,6 +344,9 @@ namespace osu.Game.Skinning case HUDSkinComponents.ScoreCounter: return new LegacyScoreCounter(this); + + case HUDSkinComponents.AccuracyCounter: + return new LegacyAccuracyCounter(this); } return null; From 4f6dd1586939eef71e4578131e4e5f55155421ad Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 15 Oct 2020 17:56:37 +0900 Subject: [PATCH 1710/1782] Add legacy font lookup support for comma/percent --- osu.Game/Skinning/LegacySpriteText.cs | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/osu.Game/Skinning/LegacySpriteText.cs b/osu.Game/Skinning/LegacySpriteText.cs index 858bbcd6a8..8394657b1c 100644 --- a/osu.Game/Skinning/LegacySpriteText.cs +++ b/osu.Game/Skinning/LegacySpriteText.cs @@ -34,7 +34,9 @@ namespace osu.Game.Skinning public ITexturedCharacterGlyph Get(string fontName, char character) { - var texture = skin.GetTexture($"{fontName}-{character}"); + var lookup = getLookupName(character); + + var texture = skin.GetTexture($"{fontName}-{lookup}"); if (texture == null) return null; @@ -42,6 +44,24 @@ namespace osu.Game.Skinning return new TexturedCharacterGlyph(new CharacterGlyph(character, 0, 0, texture.Width, null), texture, 1f / texture.ScaleAdjust); } + private static string getLookupName(char character) + { + switch (character) + { + case ',': + return "comma"; + + case '.': + return "dot"; + + case '%': + return "percent"; + + default: + return character.ToString(); + } + } + public Task GetAsync(string fontName, char character) => Task.Run(() => Get(fontName, character)); } } From b31a3fbabbe83ec779f789b9286c0f1bb025c1cf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 15 Oct 2020 18:11:30 +0900 Subject: [PATCH 1711/1782] Add test --- .../TestSceneSkinnableAccuracyCounter.cs | 49 +++++++++++++++++++ .../Play/HUD/DefaultAccuracyCounter.cs | 2 + osu.Game/Skinning/LegacyAccuracyCounter.cs | 4 +- 3 files changed, 54 insertions(+), 1 deletion(-) create mode 100644 osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableAccuracyCounter.cs diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableAccuracyCounter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableAccuracyCounter.cs new file mode 100644 index 0000000000..709929dcb0 --- /dev/null +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableAccuracyCounter.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 System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using osu.Framework.Testing; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Osu; +using osu.Game.Screens.Play.HUD; + +namespace osu.Game.Tests.Visual.Gameplay +{ + public class TestSceneSkinnableAccuracyCounter : SkinnableTestScene + { + private IEnumerable accuracyCounters => CreatedDrawables.OfType(); + + protected override Ruleset CreateRulesetForSkinProvider() => new OsuRuleset(); + + [SetUpSteps] + public void SetUpSteps() + { + AddStep("Create combo counters", () => SetContents(() => + { + var accuracyCounter = new SkinnableAccuracyCounter(); + + accuracyCounter.Current.Value = 1; + + return accuracyCounter; + })); + } + + [Test] + public void TestChangingAccuracy() + { + AddStep(@"Reset all", delegate + { + foreach (var s in accuracyCounters) + s.Current.Value = 1; + }); + + AddStep(@"Hit! :D", delegate + { + foreach (var s in accuracyCounters) + s.Current.Value -= 0.023f; + }); + } + } +} diff --git a/osu.Game/Screens/Play/HUD/DefaultAccuracyCounter.cs b/osu.Game/Screens/Play/HUD/DefaultAccuracyCounter.cs index b286b380e0..d5d8ec570a 100644 --- a/osu.Game/Screens/Play/HUD/DefaultAccuracyCounter.cs +++ b/osu.Game/Screens/Play/HUD/DefaultAccuracyCounter.cs @@ -16,6 +16,7 @@ namespace osu.Game.Screens.Play.HUD public DefaultAccuracyCounter() { Origin = Anchor.TopRight; + Anchor = Anchor.TopRight; } [Resolved(canBeNull: true)] @@ -34,6 +35,7 @@ namespace osu.Game.Screens.Play.HUD if (hud?.ScoreCounter.Drawable is DefaultScoreCounter score) { // for now align with the score counter. eventually this will be user customisable. + Anchor = Anchor.TopLeft; Position = Parent.ToLocalSpace(score.ScreenSpaceDrawQuad.TopLeft) + offset; } } diff --git a/osu.Game/Skinning/LegacyAccuracyCounter.cs b/osu.Game/Skinning/LegacyAccuracyCounter.cs index 0f3ac19ce6..815580e85f 100644 --- a/osu.Game/Skinning/LegacyAccuracyCounter.cs +++ b/osu.Game/Skinning/LegacyAccuracyCounter.cs @@ -17,7 +17,9 @@ namespace osu.Game.Skinning public LegacyAccuracyCounter(ISkin skin) { + Anchor = Anchor.TopRight; Origin = Anchor.TopRight; + Scale = new Vector2(0.75f); this.skin = skin; @@ -40,7 +42,7 @@ namespace osu.Game.Skinning if (hud?.ScoreCounter.Drawable is LegacyScoreCounter score) { // for now align with the score counter. eventually this will be user customisable. - Position = Parent.ToLocalSpace(score.ScreenSpaceDrawQuad.BottomRight); + Y = Parent.ToLocalSpace(score.ScreenSpaceDrawQuad.BottomRight).Y; } } } From ca74cf824c6fd01b21b78e862cae58bd6eca534b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 15 Oct 2020 18:24:28 +0900 Subject: [PATCH 1712/1782] Add padding --- osu.Game/Skinning/LegacyAccuracyCounter.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Skinning/LegacyAccuracyCounter.cs b/osu.Game/Skinning/LegacyAccuracyCounter.cs index 815580e85f..9354b2b3bc 100644 --- a/osu.Game/Skinning/LegacyAccuracyCounter.cs +++ b/osu.Game/Skinning/LegacyAccuracyCounter.cs @@ -21,6 +21,7 @@ namespace osu.Game.Skinning Origin = Anchor.TopRight; Scale = new Vector2(0.75f); + Margin = new MarginPadding(10); this.skin = skin; } From 6983978c989c5063d8d7fb77cbdc5bed95ede85b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 15 Oct 2020 18:30:44 +0900 Subject: [PATCH 1713/1782] Correct top-right element offset by finding the lower top anchor element --- osu.Game/Screens/Play/HUDOverlay.cs | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index bb35bd3d69..7553f332cd 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -16,6 +16,7 @@ using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; using osu.Game.Screens.Play.HUD; +using osu.Game.Skinning; using osuTK; using osuTK.Input; @@ -65,6 +66,8 @@ namespace osu.Game.Screens.Play private readonly FillFlowContainer bottomRightElements; private readonly FillFlowContainer topRightElements; + private Container mainUIElements; + private IEnumerable hideTargets => new Drawable[] { visibilityContainer, KeyCounter }; public HUDOverlay(ScoreProcessor scoreProcessor, HealthProcessor healthProcessor, DrawableRuleset drawableRuleset, IReadOnlyList mods) @@ -89,7 +92,7 @@ namespace osu.Game.Screens.Play { new Drawable[] { - new Container + mainUIElements = new Container { RelativeSizeAxes = Axes.Both, Children = new Drawable[] @@ -205,7 +208,17 @@ namespace osu.Game.Screens.Play { base.Update(); - topRightElements.Y = ToLocalSpace(ScoreCounter.Drawable.ScreenSpaceDrawQuad.BottomRight).Y; + float topRightOffset = 0; + + // fetch the bottom-most position of any main ui element that is anchored to the top of the screen. + // consider this kind of temporary. + foreach (var d in mainUIElements) + { + if (d is SkinnableDrawable sd && (sd.Drawable.Anchor & Anchor.y0) > 0) + topRightOffset = Math.Max(sd.Drawable.ScreenSpaceDrawQuad.BottomRight.Y, topRightOffset); + } + + topRightElements.Y = ToLocalSpace(new Vector2(0, topRightOffset)).Y; bottomRightElements.Y = -Progress.Height; } From d76365ed1b26252b3c54aef204bbd3312526ad4b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 15 Oct 2020 18:38:41 +0900 Subject: [PATCH 1714/1782] Make container readonly --- 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 7553f332cd..fa914c0ebc 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -66,7 +66,7 @@ namespace osu.Game.Screens.Play private readonly FillFlowContainer bottomRightElements; private readonly FillFlowContainer topRightElements; - private Container mainUIElements; + private readonly Container mainUIElements; private IEnumerable hideTargets => new Drawable[] { visibilityContainer, KeyCounter }; From 70806deba1195c41e5c59414482b6613d6965b33 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 15 Oct 2020 19:11:40 +0900 Subject: [PATCH 1715/1782] Add support for bottom-anchored hit error display --- .../Visual/Gameplay/TestSceneHitErrorMeter.cs | 19 ++++++++ osu.Game/Configuration/ScoreMeterType.cs | 10 ++++- osu.Game/Screens/Play/HUD/HitErrorDisplay.cs | 43 +++++++++++++------ .../HUD/HitErrorMeters/BarHitErrorMeter.cs | 8 +++- 4 files changed, 62 insertions(+), 18 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs index 377f305d63..1021ac3760 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs @@ -22,8 +22,10 @@ namespace osu.Game.Tests.Visual.Gameplay { private BarHitErrorMeter barMeter; private BarHitErrorMeter barMeter2; + private BarHitErrorMeter barMeter3; private ColourHitErrorMeter colourMeter; private ColourHitErrorMeter colourMeter2; + private ColourHitErrorMeter colourMeter3; private HitWindows hitWindows; public TestSceneHitErrorMeter() @@ -115,6 +117,13 @@ namespace osu.Game.Tests.Visual.Gameplay Origin = Anchor.CentreLeft, }); + Add(barMeter3 = new BarHitErrorMeter(hitWindows, true) + { + Anchor = Anchor.BottomCentre, + Origin = Anchor.CentreLeft, + Rotation = 270, + }); + Add(colourMeter = new ColourHitErrorMeter(hitWindows) { Anchor = Anchor.CentreRight, @@ -128,6 +137,14 @@ namespace osu.Game.Tests.Visual.Gameplay Origin = Anchor.CentreLeft, Margin = new MarginPadding { Left = 50 } }); + + Add(colourMeter3 = new ColourHitErrorMeter(hitWindows) + { + Anchor = Anchor.BottomCentre, + Origin = Anchor.CentreLeft, + Rotation = 270, + Margin = new MarginPadding { Left = 50 } + }); } private void newJudgement(double offset = 0) @@ -140,8 +157,10 @@ namespace osu.Game.Tests.Visual.Gameplay barMeter.OnNewJudgement(judgement); barMeter2.OnNewJudgement(judgement); + barMeter3.OnNewJudgement(judgement); colourMeter.OnNewJudgement(judgement); colourMeter2.OnNewJudgement(judgement); + colourMeter3.OnNewJudgement(judgement); } } } diff --git a/osu.Game/Configuration/ScoreMeterType.cs b/osu.Game/Configuration/ScoreMeterType.cs index 156c4b1377..b9499c758e 100644 --- a/osu.Game/Configuration/ScoreMeterType.cs +++ b/osu.Game/Configuration/ScoreMeterType.cs @@ -16,7 +16,10 @@ namespace osu.Game.Configuration [Description("Hit Error (right)")] HitErrorRight, - [Description("Hit Error (both)")] + [Description("Hit Error (bottom)")] + HitErrorBottom, + + [Description("Hit Error (left+right)")] HitErrorBoth, [Description("Colour (left)")] @@ -25,7 +28,10 @@ namespace osu.Game.Configuration [Description("Colour (right)")] ColourRight, - [Description("Colour (both)")] + [Description("Colour (left+right)")] ColourBoth, + + [Description("Colour (bottom)")] + ColourBottom, } } diff --git a/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs b/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs index 4d28f00f39..37d10a5320 100644 --- a/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs +++ b/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs @@ -66,54 +66,69 @@ namespace osu.Game.Screens.Play.HUD switch (type.NewValue) { case ScoreMeterType.HitErrorBoth: - createBar(false); - createBar(true); + createBar(Anchor.CentreLeft); + createBar(Anchor.CentreRight); break; case ScoreMeterType.HitErrorLeft: - createBar(false); + createBar(Anchor.CentreLeft); break; case ScoreMeterType.HitErrorRight: - createBar(true); + createBar(Anchor.CentreRight); + break; + + case ScoreMeterType.HitErrorBottom: + createBar(Anchor.BottomCentre); break; case ScoreMeterType.ColourBoth: - createColour(false); - createColour(true); + createColour(Anchor.CentreLeft); + createColour(Anchor.CentreRight); break; case ScoreMeterType.ColourLeft: - createColour(false); + createColour(Anchor.CentreLeft); break; case ScoreMeterType.ColourRight: - createColour(true); + createColour(Anchor.CentreRight); + break; + + case ScoreMeterType.ColourBottom: + createColour(Anchor.BottomCentre); break; } } - private void createBar(bool rightAligned) + private void createBar(Anchor anchor) { + bool rightAligned = (anchor & Anchor.x2) > 0; + bool bottomAligned = (anchor & Anchor.y2) > 0; + var display = new BarHitErrorMeter(hitWindows, rightAligned) { Margin = new MarginPadding(margin), - Anchor = rightAligned ? Anchor.CentreRight : Anchor.CentreLeft, - Origin = rightAligned ? Anchor.CentreRight : Anchor.CentreLeft, + Anchor = anchor, + Origin = bottomAligned ? Anchor.CentreLeft : anchor, Alpha = 0, + Rotation = bottomAligned ? 270 : 0 }; completeDisplayLoading(display); } - private void createColour(bool rightAligned) + private void createColour(Anchor anchor) { + bool bottomAligned = (anchor & Anchor.y2) > 0; + var display = new ColourHitErrorMeter(hitWindows) { Margin = new MarginPadding(margin), - Anchor = rightAligned ? Anchor.CentreRight : Anchor.CentreLeft, - Origin = rightAligned ? Anchor.CentreRight : Anchor.CentreLeft, + Anchor = anchor, + Origin = bottomAligned ? Anchor.CentreLeft : anchor, Alpha = 0, + Rotation = bottomAligned ? 270 : 0 }; completeDisplayLoading(display); diff --git a/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs b/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs index f99c84fc01..89f135de7f 100644 --- a/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs +++ b/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs @@ -99,7 +99,9 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters Size = new Vector2(10), Icon = FontAwesome.Solid.ShippingFast, Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, + Origin = Anchor.Centre, + // undo any layout rotation to display the icon the correct orientation + Rotation = -Rotation, }, new SpriteIcon { @@ -107,7 +109,9 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters Size = new Vector2(10), Icon = FontAwesome.Solid.Bicycle, Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, + Origin = Anchor.Centre, + // undo any layout rotation to display the icon the correct orientation + Rotation = -Rotation, } } }, From e8235757512030f48f8ce423ba57f46b7c153702 Mon Sep 17 00:00:00 2001 From: Lucas A Date: Wed, 14 Oct 2020 13:43:56 +0200 Subject: [PATCH 1716/1782] Lock screen rotation while in gameplay. --- osu.Android/GameplayScreenRotationLocker.cs | 31 +++++++++++++++++++++ osu.Android/OsuGameActivity.cs | 4 +++ osu.Android/OsuGameAndroid.cs | 6 ++++ osu.Android/osu.Android.csproj | 3 +- 4 files changed, 43 insertions(+), 1 deletion(-) create mode 100644 osu.Android/GameplayScreenRotationLocker.cs diff --git a/osu.Android/GameplayScreenRotationLocker.cs b/osu.Android/GameplayScreenRotationLocker.cs new file mode 100644 index 0000000000..d1f4caba52 --- /dev/null +++ b/osu.Android/GameplayScreenRotationLocker.cs @@ -0,0 +1,31 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using Android.Content.PM; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Game; + +namespace osu.Android +{ + public class GameplayScreenRotationLocker : Component + { + private Bindable localUserPlaying; + + [BackgroundDependencyLoader] + private void load(OsuGame game) + { + localUserPlaying = game.LocalUserPlaying.GetBoundCopy(); + localUserPlaying.BindValueChanged(_ => updateLock()); + } + + private void updateLock() + { + OsuGameActivity.Activity.RunOnUiThread(() => + { + OsuGameActivity.Activity.RequestedOrientation = localUserPlaying.Value ? ScreenOrientation.Locked : ScreenOrientation.FullUser; + }); + } + } +} diff --git a/osu.Android/OsuGameActivity.cs b/osu.Android/OsuGameActivity.cs index db73bb7e7f..c2b28f3de4 100644 --- a/osu.Android/OsuGameActivity.cs +++ b/osu.Android/OsuGameActivity.cs @@ -12,10 +12,14 @@ namespace osu.Android [Activity(Theme = "@android:style/Theme.NoTitleBar", MainLauncher = true, ScreenOrientation = ScreenOrientation.FullUser, SupportsPictureInPicture = false, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize, HardwareAccelerated = false)] public class OsuGameActivity : AndroidGameActivity { + internal static Activity Activity; + protected override Framework.Game CreateGame() => new OsuGameAndroid(); protected override void OnCreate(Bundle savedInstanceState) { + Activity = this; + // 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 consistent current directory on all devices the full path of the app data directory is set as the current directory. diff --git a/osu.Android/OsuGameAndroid.cs b/osu.Android/OsuGameAndroid.cs index 7542a2b997..887a8395e3 100644 --- a/osu.Android/OsuGameAndroid.cs +++ b/osu.Android/OsuGameAndroid.cs @@ -55,6 +55,12 @@ namespace osu.Android } } + protected override void LoadComplete() + { + base.LoadComplete(); + LoadComponentAsync(new GameplayScreenRotationLocker(), Add); + } + protected override UpdateManager CreateUpdateManager() => new SimpleUpdateManager(); } } diff --git a/osu.Android/osu.Android.csproj b/osu.Android/osu.Android.csproj index 0598a50530..a2638e95c8 100644 --- a/osu.Android/osu.Android.csproj +++ b/osu.Android/osu.Android.csproj @@ -21,6 +21,7 @@ r8 + @@ -53,4 +54,4 @@ - + \ No newline at end of file From 703f58bb2f0cae677c237a8c206458ce7df34180 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 16 Oct 2020 11:54:11 +0900 Subject: [PATCH 1717/1782] Remove last.fm support Has been broken for ages, and their service isn't really something people use these days. --- osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs | 1 - osu.Game/Users/User.cs | 3 --- 2 files changed, 4 deletions(-) diff --git a/osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs index c27b5f4b4a..946831d13b 100644 --- a/osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs +++ b/osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs @@ -135,7 +135,6 @@ namespace osu.Game.Overlays.Profile.Header anyInfoAdded |= tryAddInfo(FontAwesome.Brands.Twitter, "@" + user.Twitter, $@"https://twitter.com/{user.Twitter}"); anyInfoAdded |= tryAddInfo(FontAwesome.Brands.Discord, user.Discord); anyInfoAdded |= tryAddInfo(FontAwesome.Brands.Skype, user.Skype, @"skype:" + user.Skype + @"?chat"); - anyInfoAdded |= tryAddInfo(FontAwesome.Brands.Lastfm, user.Lastfm, $@"https://last.fm/users/{user.Lastfm}"); anyInfoAdded |= tryAddInfo(FontAwesome.Solid.Link, websiteWithoutProtocol, user.Website); // If no information was added to the bottomLinkContainer, hide it to avoid unwanted padding diff --git a/osu.Game/Users/User.cs b/osu.Game/Users/User.cs index f8bb8f4c6a..89786e3bd8 100644 --- a/osu.Game/Users/User.cs +++ b/osu.Game/Users/User.cs @@ -111,9 +111,6 @@ namespace osu.Game.Users [JsonProperty(@"twitter")] public string Twitter; - [JsonProperty(@"lastfm")] - public string Lastfm; - [JsonProperty(@"skype")] public string Skype; From 39a74536f24d8bafd6bf787311024db647fd2757 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 16 Oct 2020 12:48:31 +0900 Subject: [PATCH 1718/1782] Update inspections --- .idea/.idea.osu.Desktop/.idea/modules.xml | 2 +- osu.sln.DotSettings | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.idea/.idea.osu.Desktop/.idea/modules.xml b/.idea/.idea.osu.Desktop/.idea/modules.xml index fe63f5faf3..680312ad27 100644 --- a/.idea/.idea.osu.Desktop/.idea/modules.xml +++ b/.idea/.idea.osu.Desktop/.idea/modules.xml @@ -2,7 +2,7 @@ - + \ No newline at end of file diff --git a/osu.sln.DotSettings b/osu.sln.DotSettings index 64f3d41acb..3ef419c572 100644 --- a/osu.sln.DotSettings +++ b/osu.sln.DotSettings @@ -199,7 +199,9 @@ WARNING WARNING WARNING + WARNING HINT + WARNING WARNING DO_NOT_SHOW DO_NOT_SHOW @@ -773,6 +775,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" /> + True True True True From cc41845f56bc1a65fa10e01a1584334b6fd7c063 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 16 Oct 2020 12:49:31 +0900 Subject: [PATCH 1719/1782] Add missing string function ordinal specifications --- .../Screens/Drawings/DrawingsScreen.cs | 2 +- osu.Game/Beatmaps/Formats/LegacyDecoder.cs | 2 +- osu.Game/OsuGame.cs | 4 ++-- osu.Game/Rulesets/RulesetStore.cs | 2 +- osu.Game/Skinning/LegacyManiaSkinDecoder.cs | 13 +++++++------ osu.Game/Utils/SentryLogger.cs | 2 +- 6 files changed, 13 insertions(+), 12 deletions(-) diff --git a/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs b/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs index e10154b722..4c3adeae76 100644 --- a/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs +++ b/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs @@ -234,7 +234,7 @@ namespace osu.Game.Tournament.Screens.Drawings if (string.IsNullOrEmpty(line)) continue; - if (line.ToUpperInvariant().StartsWith("GROUP")) + if (line.ToUpperInvariant().StartsWith("GROUP", StringComparison.Ordinal)) continue; // ReSharper disable once AccessToModifiedClosure diff --git a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs index c15240a4f6..7b377e481f 100644 --- a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs @@ -92,7 +92,7 @@ namespace osu.Game.Beatmaps.Formats { var pair = SplitKeyVal(line); - bool isCombo = pair.Key.StartsWith(@"Combo"); + bool isCombo = pair.Key.StartsWith(@"Combo", StringComparison.Ordinal); string[] split = pair.Value.Split(','); diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index d315b213ab..56cced9c04 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -181,7 +181,7 @@ namespace osu.Game if (args?.Length > 0) { - var paths = args.Where(a => !a.StartsWith(@"-")).ToArray(); + var paths = args.Where(a => !a.StartsWith(@"-", StringComparison.Ordinal)).ToArray(); if (paths.Length > 0) Task.Run(() => Import(paths)); } @@ -289,7 +289,7 @@ namespace osu.Game public void OpenUrlExternally(string url) => waitForReady(() => externalLinkOpener, _ => { - if (url.StartsWith("/")) + if (url.StartsWith("/", StringComparison.Ordinal)) url = $"{API.Endpoint}{url}"; externalLinkOpener.OpenUrlExternally(url); diff --git a/osu.Game/Rulesets/RulesetStore.cs b/osu.Game/Rulesets/RulesetStore.cs index 5d93f5186b..c12d418771 100644 --- a/osu.Game/Rulesets/RulesetStore.cs +++ b/osu.Game/Rulesets/RulesetStore.cs @@ -100,7 +100,7 @@ namespace osu.Game.Rulesets { // todo: StartsWith can be changed to Equals on 2020-11-08 // This is to give users enough time to have their database use new abbreviated info). - if (context.RulesetInfo.FirstOrDefault(ri => ri.InstantiationInfo.StartsWith(r.RulesetInfo.InstantiationInfo)) == null) + if (context.RulesetInfo.FirstOrDefault(ri => ri.InstantiationInfo.StartsWith(r.RulesetInfo.InstantiationInfo, StringComparison.Ordinal)) == null) context.RulesetInfo.Add(r.RulesetInfo); } diff --git a/osu.Game/Skinning/LegacyManiaSkinDecoder.cs b/osu.Game/Skinning/LegacyManiaSkinDecoder.cs index a9d88e77ad..3dbec23194 100644 --- a/osu.Game/Skinning/LegacyManiaSkinDecoder.cs +++ b/osu.Game/Skinning/LegacyManiaSkinDecoder.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; @@ -115,16 +116,16 @@ namespace osu.Game.Skinning currentConfig.MinimumColumnWidth = minWidth; break; - case string _ when pair.Key.StartsWith("Colour"): + case string _ when pair.Key.StartsWith("Colour", StringComparison.Ordinal): HandleColours(currentConfig, line); break; // Custom sprite paths - case string _ when pair.Key.StartsWith("NoteImage"): - case string _ when pair.Key.StartsWith("KeyImage"): - case string _ when pair.Key.StartsWith("Hit"): - case string _ when pair.Key.StartsWith("Stage"): - case string _ when pair.Key.StartsWith("Lighting"): + case string _ when pair.Key.StartsWith("NoteImage", StringComparison.Ordinal): + case string _ when pair.Key.StartsWith("KeyImage", StringComparison.Ordinal): + case string _ when pair.Key.StartsWith("Hit", StringComparison.Ordinal): + case string _ when pair.Key.StartsWith("Stage", StringComparison.Ordinal): + case string _ when pair.Key.StartsWith("Lighting", StringComparison.Ordinal): currentConfig.ImageLookups[pair.Key] = pair.Value; break; } diff --git a/osu.Game/Utils/SentryLogger.cs b/osu.Game/Utils/SentryLogger.cs index 981251784e..e8e41cdbbe 100644 --- a/osu.Game/Utils/SentryLogger.cs +++ b/osu.Game/Utils/SentryLogger.cs @@ -45,7 +45,7 @@ namespace osu.Game.Utils // 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)) + lastException.Message == exception.Message && exception.StackTrace.StartsWith(lastException.StackTrace, StringComparison.Ordinal)) return; lastException = exception; From 88f74921fb9de5f01ddfb7be72cb145d9ca14a2d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 16 Oct 2020 12:49:39 +0900 Subject: [PATCH 1720/1782] Update with new r# inspections --- osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs | 2 +- .../Edit/Compose/Components/ComposeBlueprintContainer.cs | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs index c02075bea9..603b5d4956 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs @@ -69,7 +69,7 @@ namespace osu.Game.Tests.Visual.Gameplay createNew(h => h.OnLoadComplete += _ => initialAlpha = hideTarget.Alpha); AddUntilStep("wait for load", () => hudOverlay.IsAlive); - AddAssert("initial alpha was less than 1", () => initialAlpha != null && initialAlpha < 1); + AddAssert("initial alpha was less than 1", () => initialAlpha < 1); } [Test] diff --git a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs index 0336c74386..1527d20f54 100644 --- a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs @@ -79,9 +79,7 @@ namespace osu.Game.Screens.Edit.Compose.Components private void updatePlacementNewCombo() { - if (currentPlacement == null) return; - - if (currentPlacement.HitObject is IHasComboInformation c) + if (currentPlacement?.HitObject is IHasComboInformation c) c.NewCombo = NewCombo.Value == TernaryState.True; } From 88ffcb923408c0e9485f2e28bf38efa98409901a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 16 Oct 2020 12:58:34 +0900 Subject: [PATCH 1721/1782] Update EndsWith usages --- .../Beatmaps/Formats/LegacyBeatmapEncoderTest.cs | 2 +- .../Visual/SongSelect/TestSceneBeatmapCarousel.cs | 2 +- osu.Game.Tests/WaveformTestBeatmap.cs | 5 +++-- osu.Game/Beatmaps/BeatmapManager.cs | 4 ++-- osu.Game/Beatmaps/BeatmapSetInfo.cs | 2 +- osu.Game/Database/ArchiveModelManager.cs | 4 ++-- osu.Game/Screens/Select/FilterQueryParser.cs | 8 ++++---- osu.Game/Skinning/LegacySkin.cs | 4 ++-- osu.Game/Updater/SimpleUpdateManager.cs | 7 ++++--- 9 files changed, 20 insertions(+), 18 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs index 8b22309033..0784109158 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs @@ -30,7 +30,7 @@ namespace osu.Game.Tests.Beatmaps.Formats { private static readonly DllResourceStore beatmaps_resource_store = TestResources.GetStore(); - private static IEnumerable allBeatmaps = beatmaps_resource_store.GetAvailableResources().Where(res => res.EndsWith(".osu")); + private static IEnumerable allBeatmaps = beatmaps_resource_store.GetAvailableResources().Where(res => res.EndsWith(".osu", StringComparison.Ordinal)); [TestCaseSource(nameof(allBeatmaps))] public void TestEncodeDecodeStability(string name) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs index 3aff390a47..8669235a7a 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs @@ -394,7 +394,7 @@ namespace osu.Game.Tests.Visual.SongSelect 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}!")); + AddAssert($"Check #{set_count} is at bottom", () => carousel.BeatmapSets.Last().Metadata.Title.EndsWith($"#{set_count}!", StringComparison.Ordinal)); } [Test] diff --git a/osu.Game.Tests/WaveformTestBeatmap.cs b/osu.Game.Tests/WaveformTestBeatmap.cs index 7dc5ce1d7f..f9613d9e25 100644 --- a/osu.Game.Tests/WaveformTestBeatmap.cs +++ b/osu.Game.Tests/WaveformTestBeatmap.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.IO; using System.Linq; using osu.Framework.Audio; @@ -59,7 +60,7 @@ namespace osu.Game.Tests get { using (var reader = getZipReader()) - return reader.Filenames.First(f => f.EndsWith(".mp3")); + return reader.Filenames.First(f => f.EndsWith(".mp3", StringComparison.Ordinal)); } } @@ -73,7 +74,7 @@ namespace osu.Game.Tests protected override Beatmap CreateBeatmap() { using (var reader = getZipReader()) - using (var beatmapStream = reader.GetStream(reader.Filenames.First(f => f.EndsWith(".osu")))) + using (var beatmapStream = reader.GetStream(reader.Filenames.First(f => f.EndsWith(".osu", StringComparison.Ordinal)))) 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 4c75069f08..370e82b468 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -389,7 +389,7 @@ namespace osu.Game.Beatmaps protected override BeatmapSetInfo CreateModel(ArchiveReader reader) { // let's make sure there are actually .osu files to import. - string mapName = reader.Filenames.FirstOrDefault(f => f.EndsWith(".osu")); + string mapName = reader.Filenames.FirstOrDefault(f => f.EndsWith(".osu", StringComparison.OrdinalIgnoreCase)); if (string.IsNullOrEmpty(mapName)) { @@ -417,7 +417,7 @@ namespace osu.Game.Beatmaps { var beatmapInfos = new List(); - foreach (var file in files.Where(f => f.Filename.EndsWith(".osu"))) + foreach (var file in files.Where(f => f.Filename.EndsWith(".osu", StringComparison.OrdinalIgnoreCase))) { using (var raw = Files.Store.GetStream(file.FileInfo.StoragePath)) using (var ms = new MemoryStream()) // we need a memory stream so we can seek diff --git a/osu.Game/Beatmaps/BeatmapSetInfo.cs b/osu.Game/Beatmaps/BeatmapSetInfo.cs index b76d780860..7bc1c8c7b9 100644 --- a/osu.Game/Beatmaps/BeatmapSetInfo.cs +++ b/osu.Game/Beatmaps/BeatmapSetInfo.cs @@ -57,7 +57,7 @@ namespace osu.Game.Beatmaps public string Hash { get; set; } - public string StoryboardFile => Files?.Find(f => f.Filename.EndsWith(".osb"))?.Filename; + public string StoryboardFile => Files?.Find(f => f.Filename.EndsWith(".osb", StringComparison.OrdinalIgnoreCase))?.Filename; public List Files { get; set; } diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index 3292936f5f..b947056ebd 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -279,7 +279,7 @@ namespace osu.Game.Database // for now, concatenate all .osu files in the set to create a unique hash. MemoryStream hashable = new MemoryStream(); - foreach (TFileModel file in item.Files.Where(f => HashableFileTypes.Any(f.Filename.EndsWith)).OrderBy(f => f.Filename)) + foreach (TFileModel file in item.Files.Where(f => HashableFileTypes.Any(ext => f.Filename.EndsWith(ext, StringComparison.OrdinalIgnoreCase))).OrderBy(f => f.Filename)) { using (Stream s = Files.Store.GetStream(file.FileInfo.StoragePath)) s.CopyTo(hashable); @@ -593,7 +593,7 @@ namespace osu.Game.Database var fileInfos = new List(); string prefix = reader.Filenames.GetCommonPrefix(); - if (!(prefix.EndsWith("/") || prefix.EndsWith("\\"))) + if (!(prefix.EndsWith("/", StringComparison.Ordinal) || prefix.EndsWith("\\", StringComparison.Ordinal))) prefix = string.Empty; // import files to manager diff --git a/osu.Game/Screens/Select/FilterQueryParser.cs b/osu.Game/Screens/Select/FilterQueryParser.cs index 39fa4f777d..fa2beb2652 100644 --- a/osu.Game/Screens/Select/FilterQueryParser.cs +++ b/osu.Game/Screens/Select/FilterQueryParser.cs @@ -79,10 +79,10 @@ namespace osu.Game.Screens.Select } private static int getLengthScale(string value) => - value.EndsWith("ms") ? 1 : - value.EndsWith("s") ? 1000 : - value.EndsWith("m") ? 60000 : - value.EndsWith("h") ? 3600000 : 1000; + value.EndsWith("ms", StringComparison.Ordinal) ? 1 : + value.EndsWith("s", StringComparison.Ordinal) ? 1000 : + value.EndsWith("m", StringComparison.Ordinal) ? 60000 : + value.EndsWith("h", StringComparison.Ordinal) ? 3600000 : 1000; private static bool parseFloatWithPoint(string value, out float result) => float.TryParse(value, NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out result); diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index e1cd095ba8..069a887f63 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -422,7 +422,7 @@ namespace osu.Game.Skinning // Fall back to using the last piece for components coming from lazer (e.g. "Gameplay/osu/approachcircle" -> "approachcircle"). string lastPiece = componentName.Split('/').Last(); - yield return componentName.StartsWith("Gameplay/taiko/") ? "taiko-" + lastPiece : lastPiece; + yield return componentName.StartsWith("Gameplay/taiko/", StringComparison.Ordinal) ? "taiko-" + lastPiece : lastPiece; } private IEnumerable getLegacyLookupNames(HitSampleInfo hitSample) @@ -433,7 +433,7 @@ namespace osu.Game.Skinning // for compatibility with stable, exclude the lookup names with the custom sample bank suffix, if they are not valid for use in this skin. // using .EndsWith() is intentional as it ensures parity in all edge cases // (see LegacyTaikoSampleInfo for an example of one - prioritising the taiko prefix should still apply, but the sample bank should not). - lookupNames = hitSample.LookupNames.Where(name => !name.EndsWith(hitSample.Suffix)); + lookupNames = hitSample.LookupNames.Where(name => !name.EndsWith(hitSample.Suffix, StringComparison.Ordinal)); // also for compatibility, try falling back to non-bank samples (so-called "universal" samples) as the last resort. // going forward specifying banks shall always be required, even for elements that wouldn't require it on stable, diff --git a/osu.Game/Updater/SimpleUpdateManager.cs b/osu.Game/Updater/SimpleUpdateManager.cs index b5fcb56c06..4ebf2a7368 100644 --- a/osu.Game/Updater/SimpleUpdateManager.cs +++ b/osu.Game/Updater/SimpleUpdateManager.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.Threading.Tasks; using Newtonsoft.Json; @@ -73,15 +74,15 @@ namespace osu.Game.Updater switch (RuntimeInfo.OS) { case RuntimeInfo.Platform.Windows: - bestAsset = release.Assets?.Find(f => f.Name.EndsWith(".exe")); + bestAsset = release.Assets?.Find(f => f.Name.EndsWith(".exe", StringComparison.Ordinal)); break; case RuntimeInfo.Platform.MacOsx: - bestAsset = release.Assets?.Find(f => f.Name.EndsWith(".app.zip")); + bestAsset = release.Assets?.Find(f => f.Name.EndsWith(".app.zip", StringComparison.Ordinal)); break; case RuntimeInfo.Platform.Linux: - bestAsset = release.Assets?.Find(f => f.Name.EndsWith(".AppImage")); + bestAsset = release.Assets?.Find(f => f.Name.EndsWith(".AppImage", StringComparison.Ordinal)); break; case RuntimeInfo.Platform.iOS: From aea31d1582e2c6120088e864479c2d6131d3e978 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 16 Oct 2020 13:07:00 +0900 Subject: [PATCH 1722/1782] Fix editor not seeking by full beat when track is playing This is expected behaviour as my osu-stable, and I still stand behind the reasoning behind it. Closes #10519. --- osu.Game/Screens/Edit/Editor.cs | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 7444369e84..c3560dff38 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -597,10 +597,20 @@ namespace osu.Game.Screens.Edit { double amount = e.ShiftPressed ? 4 : 1; + bool trackPlaying = clock.IsRunning; + + if (trackPlaying) + { + // generally users are not looking to perform tiny seeks when the track is playing, + // so seeks should always be by one full beat, bypassing the beatDivisor. + // this multiplication undoes the division that will be applied in the underlying seek operation. + amount *= beatDivisor.Value; + } + if (direction < 1) - clock.SeekBackward(!clock.IsRunning, amount); + clock.SeekBackward(!trackPlaying, amount); else - clock.SeekForward(!clock.IsRunning, amount); + clock.SeekForward(!trackPlaying, amount); } private void exportBeatmap() From 085d8d0ecbfa233c8ad6864ecf03885a3ba9cc7a Mon Sep 17 00:00:00 2001 From: Morilli <35152647+Morilli@users.noreply.github.com> Date: Fri, 16 Oct 2020 06:16:20 +0200 Subject: [PATCH 1723/1782] Add support for ScorePrefix and ScoreOverlap values in legacy skins --- osu.Game/Skinning/LegacyAccuracyCounter.cs | 8 +++++++- osu.Game/Skinning/LegacyScoreCounter.cs | 11 +++++++++-- osu.Game/Skinning/LegacySkinConfiguration.cs | 2 ++ 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/osu.Game/Skinning/LegacyAccuracyCounter.cs b/osu.Game/Skinning/LegacyAccuracyCounter.cs index 9354b2b3bc..0a64545aee 100644 --- a/osu.Game/Skinning/LegacyAccuracyCounter.cs +++ b/osu.Game/Skinning/LegacyAccuracyCounter.cs @@ -15,6 +15,9 @@ namespace osu.Game.Skinning { private readonly ISkin skin; + private readonly string scorePrefix; + private readonly int scoreOverlap; + public LegacyAccuracyCounter(ISkin skin) { Anchor = Anchor.TopRight; @@ -24,16 +27,19 @@ namespace osu.Game.Skinning Margin = new MarginPadding(10); this.skin = skin; + scorePrefix = skin.GetConfig(LegacySkinConfiguration.LegacySetting.ScorePrefix)?.Value ?? "score"; + scoreOverlap = skin.GetConfig(LegacySkinConfiguration.LegacySetting.ScoreOverlap)?.Value ?? -2; } [Resolved(canBeNull: true)] private HUDOverlay hud { get; set; } protected sealed override OsuSpriteText CreateSpriteText() => - new LegacySpriteText(skin, "score" /*, true*/) + new LegacySpriteText(skin, scorePrefix) { Anchor = Anchor.TopRight, Origin = Anchor.TopRight, + Spacing = new Vector2(-scoreOverlap, 0) }; protected override void Update() diff --git a/osu.Game/Skinning/LegacyScoreCounter.cs b/osu.Game/Skinning/LegacyScoreCounter.cs index f94bef6652..e931497564 100644 --- a/osu.Game/Skinning/LegacyScoreCounter.cs +++ b/osu.Game/Skinning/LegacyScoreCounter.cs @@ -5,6 +5,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; +using osuTK; namespace osu.Game.Skinning { @@ -12,6 +13,9 @@ namespace osu.Game.Skinning { private readonly ISkin skin; + private readonly string scorePrefix; + private readonly int scoreOverlap; + protected override double RollingDuration => 1000; protected override Easing RollingEasing => Easing.Out; @@ -24,18 +28,21 @@ namespace osu.Game.Skinning Origin = Anchor.TopRight; this.skin = skin; + scorePrefix = skin.GetConfig(LegacySkinConfiguration.LegacySetting.ScorePrefix)?.Value ?? "score"; + scoreOverlap = skin.GetConfig(LegacySkinConfiguration.LegacySetting.ScoreOverlap)?.Value ?? -2; - // base class uses int for display, but externally we bind to ScoreProcesssor as a double for now. + // base class uses int for display, but externally we bind to ScoreProcessor as a double for now. Current.BindValueChanged(v => base.Current.Value = (int)v.NewValue); Margin = new MarginPadding(10); } protected sealed override OsuSpriteText CreateSpriteText() => - new LegacySpriteText(skin, "score" /*, true*/) + new LegacySpriteText(skin, scorePrefix) { Anchor = Anchor.TopRight, Origin = Anchor.TopRight, + Spacing = new Vector2(-scoreOverlap, 0) }; } } diff --git a/osu.Game/Skinning/LegacySkinConfiguration.cs b/osu.Game/Skinning/LegacySkinConfiguration.cs index 828804b9cb..84a834ec22 100644 --- a/osu.Game/Skinning/LegacySkinConfiguration.cs +++ b/osu.Game/Skinning/LegacySkinConfiguration.cs @@ -17,6 +17,8 @@ namespace osu.Game.Skinning Version, ComboPrefix, ComboOverlap, + ScorePrefix, + ScoreOverlap, AnimationFramerate, LayeredHitSounds } From 83482ca15c53d95b47bb0324cad7ec45ad298170 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 16 Oct 2020 13:21:47 +0900 Subject: [PATCH 1724/1782] Fix one more missed occurrence --- osu.Game/Scoring/ScoreManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index 5a6da53839..cce6153953 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -57,7 +57,7 @@ namespace osu.Game.Scoring if (archive == null) return null; - using (var stream = archive.GetStream(archive.Filenames.First(f => f.EndsWith(".osr")))) + using (var stream = archive.GetStream(archive.Filenames.First(f => f.EndsWith(".osr", StringComparison.OrdinalIgnoreCase)))) { try { From df1db8611c73c0f2d87154e3895d6f8b0f156705 Mon Sep 17 00:00:00 2001 From: Morilli <35152647+Morilli@users.noreply.github.com> Date: Fri, 16 Oct 2020 08:36:20 +0200 Subject: [PATCH 1725/1782] move skin-specific config retrieval to GetDrawableComponent --- osu.Game/Skinning/HUDSkinComponents.cs | 4 +++- osu.Game/Skinning/LegacyAccuracyCounter.cs | 13 +------------ osu.Game/Skinning/LegacyScoreCounter.cs | 14 +------------- osu.Game/Skinning/LegacySkin.cs | 10 ++++++++++ 4 files changed, 15 insertions(+), 26 deletions(-) diff --git a/osu.Game/Skinning/HUDSkinComponents.cs b/osu.Game/Skinning/HUDSkinComponents.cs index d690a23dee..6ec575e106 100644 --- a/osu.Game/Skinning/HUDSkinComponents.cs +++ b/osu.Game/Skinning/HUDSkinComponents.cs @@ -7,6 +7,8 @@ namespace osu.Game.Skinning { ComboCounter, ScoreCounter, - AccuracyCounter + ScoreText, + AccuracyCounter, + AccuracyText } } diff --git a/osu.Game/Skinning/LegacyAccuracyCounter.cs b/osu.Game/Skinning/LegacyAccuracyCounter.cs index 0a64545aee..6c194a06d3 100644 --- a/osu.Game/Skinning/LegacyAccuracyCounter.cs +++ b/osu.Game/Skinning/LegacyAccuracyCounter.cs @@ -15,9 +15,6 @@ namespace osu.Game.Skinning { private readonly ISkin skin; - private readonly string scorePrefix; - private readonly int scoreOverlap; - public LegacyAccuracyCounter(ISkin skin) { Anchor = Anchor.TopRight; @@ -27,20 +24,12 @@ namespace osu.Game.Skinning Margin = new MarginPadding(10); this.skin = skin; - scorePrefix = skin.GetConfig(LegacySkinConfiguration.LegacySetting.ScorePrefix)?.Value ?? "score"; - scoreOverlap = skin.GetConfig(LegacySkinConfiguration.LegacySetting.ScoreOverlap)?.Value ?? -2; } [Resolved(canBeNull: true)] private HUDOverlay hud { get; set; } - protected sealed override OsuSpriteText CreateSpriteText() => - new LegacySpriteText(skin, scorePrefix) - { - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - Spacing = new Vector2(-scoreOverlap, 0) - }; + protected sealed override OsuSpriteText CreateSpriteText() => skin?.GetDrawableComponent(new HUDSkinComponent(HUDSkinComponents.AccuracyText)) as OsuSpriteText ?? new OsuSpriteText(); protected override void Update() { diff --git a/osu.Game/Skinning/LegacyScoreCounter.cs b/osu.Game/Skinning/LegacyScoreCounter.cs index e931497564..41bf35722b 100644 --- a/osu.Game/Skinning/LegacyScoreCounter.cs +++ b/osu.Game/Skinning/LegacyScoreCounter.cs @@ -5,7 +5,6 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; -using osuTK; namespace osu.Game.Skinning { @@ -13,9 +12,6 @@ namespace osu.Game.Skinning { private readonly ISkin skin; - private readonly string scorePrefix; - private readonly int scoreOverlap; - protected override double RollingDuration => 1000; protected override Easing RollingEasing => Easing.Out; @@ -28,8 +24,6 @@ namespace osu.Game.Skinning Origin = Anchor.TopRight; this.skin = skin; - scorePrefix = skin.GetConfig(LegacySkinConfiguration.LegacySetting.ScorePrefix)?.Value ?? "score"; - scoreOverlap = skin.GetConfig(LegacySkinConfiguration.LegacySetting.ScoreOverlap)?.Value ?? -2; // base class uses int for display, but externally we bind to ScoreProcessor as a double for now. Current.BindValueChanged(v => base.Current.Value = (int)v.NewValue); @@ -37,12 +31,6 @@ namespace osu.Game.Skinning Margin = new MarginPadding(10); } - protected sealed override OsuSpriteText CreateSpriteText() => - new LegacySpriteText(skin, scorePrefix) - { - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - Spacing = new Vector2(-scoreOverlap, 0) - }; + protected sealed override OsuSpriteText CreateSpriteText() => skin?.GetDrawableComponent(new HUDSkinComponent(HUDSkinComponents.ScoreText)) as OsuSpriteText ?? new OsuSpriteText(); } } diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index e1cd095ba8..f5265f2d6e 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -19,6 +19,7 @@ using osu.Game.Beatmaps.Formats; using osu.Game.IO; using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Play.HUD; +using osuTK; using osuTK.Graphics; namespace osu.Game.Skinning @@ -347,6 +348,15 @@ namespace osu.Game.Skinning case HUDSkinComponents.AccuracyCounter: return new LegacyAccuracyCounter(this); + + case HUDSkinComponents.ScoreText: + case HUDSkinComponents.AccuracyText: + string scorePrefix = GetConfig(LegacySkinConfiguration.LegacySetting.ScorePrefix)?.Value ?? "score"; + int scoreOverlap = GetConfig(LegacySkinConfiguration.LegacySetting.ScoreOverlap)?.Value ?? -2; + return new LegacySpriteText(this, scorePrefix) + { + Spacing = new Vector2(-scoreOverlap, 0) + }; } return null; From c0a1f2158cdfbc5539a8dd8e9d462c49e1b17c95 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 16 Oct 2020 13:42:50 +0900 Subject: [PATCH 1726/1782] Add basic component structure for skinnable health displays --- .../TestSceneSkinnableHealthDisplay.cs | 62 +++++++++++++++++++ ...althDisplay.cs => DefaultHealthDisplay.cs} | 10 ++- osu.Game/Screens/Play/HUD/HealthDisplay.cs | 9 ++- osu.Game/Screens/Play/HUD/IHealthDisplay.cs | 26 ++++++++ osu.Game/Screens/Play/HUDOverlay.cs | 11 +--- .../Screens/Play/SkinnableHealthDisplay.cs | 47 ++++++++++++++ osu.Game/Skinning/HUDSkinComponents.cs | 3 +- 7 files changed, 154 insertions(+), 14 deletions(-) create mode 100644 osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHealthDisplay.cs rename osu.Game/Screens/Play/HUD/{StandardHealthDisplay.cs => DefaultHealthDisplay.cs} (92%) create mode 100644 osu.Game/Screens/Play/HUD/IHealthDisplay.cs create mode 100644 osu.Game/Screens/Play/SkinnableHealthDisplay.cs diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHealthDisplay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHealthDisplay.cs new file mode 100644 index 0000000000..181fc8ce98 --- /dev/null +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHealthDisplay.cs @@ -0,0 +1,62 @@ +// 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.Testing; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Osu.Judgements; +using osu.Game.Screens.Play; +using osuTK; + +namespace osu.Game.Tests.Visual.Gameplay +{ + public class TestSceneSkinnableHealthDisplay : SkinnableTestScene + { + private IEnumerable healthDisplays => CreatedDrawables.OfType(); + + protected override Ruleset CreateRulesetForSkinProvider() => new OsuRuleset(); + + [SetUpSteps] + public void SetUpSteps() + { + AddStep("Create health displays", () => + { + SetContents(() => new SkinnableHealthDisplay()); + }); + AddStep(@"Reset all", delegate + { + foreach (var s in healthDisplays) + s.Current.Value = 1; + }); + } + + [Test] + public void TestHealthDisplayIncrementing() + { + AddRepeatStep(@"decrease hp", delegate + { + foreach (var healthDisplay in healthDisplays) + healthDisplay.Current.Value -= 0.08f; + }, 10); + + AddRepeatStep(@"increase hp without flash", delegate + { + foreach (var healthDisplay in healthDisplays) + healthDisplay.Current.Value += 0.1f; + }, 3); + + AddRepeatStep(@"increase hp with flash", delegate + { + foreach (var healthDisplay in healthDisplays) + { + healthDisplay.Current.Value += 0.1f; + healthDisplay.Flash(new JudgementResult(null, new OsuJudgement())); + } + }, 3); + } + } +} diff --git a/osu.Game/Screens/Play/HUD/StandardHealthDisplay.cs b/osu.Game/Screens/Play/HUD/DefaultHealthDisplay.cs similarity index 92% rename from osu.Game/Screens/Play/HUD/StandardHealthDisplay.cs rename to osu.Game/Screens/Play/HUD/DefaultHealthDisplay.cs index fc4a1a5d83..ae78d19c2d 100644 --- a/osu.Game/Screens/Play/HUD/StandardHealthDisplay.cs +++ b/osu.Game/Screens/Play/HUD/DefaultHealthDisplay.cs @@ -16,7 +16,7 @@ using osu.Framework.Utils; namespace osu.Game.Screens.Play.HUD { - public class StandardHealthDisplay : HealthDisplay, IHasAccentColour + public class DefaultHealthDisplay : HealthDisplay, IHasAccentColour { /// /// The base opacity of the glow. @@ -71,8 +71,12 @@ namespace osu.Game.Screens.Play.HUD } } - public StandardHealthDisplay() + public DefaultHealthDisplay() { + Size = new Vector2(1, 5); + RelativeSizeAxes = Axes.X; + Margin = new MarginPadding { Top = 20 }; + Children = new Drawable[] { new Box @@ -103,7 +107,7 @@ namespace osu.Game.Screens.Play.HUD GlowColour = colours.BlueDarker; } - public void Flash(JudgementResult result) + public override void Flash(JudgementResult result) { if (!result.IsHit) return; diff --git a/osu.Game/Screens/Play/HUD/HealthDisplay.cs b/osu.Game/Screens/Play/HUD/HealthDisplay.cs index edc9dedf24..5c43e00192 100644 --- a/osu.Game/Screens/Play/HUD/HealthDisplay.cs +++ b/osu.Game/Screens/Play/HUD/HealthDisplay.cs @@ -3,6 +3,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics.Containers; +using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; @@ -12,14 +13,18 @@ namespace osu.Game.Screens.Play.HUD /// A container for components displaying the current player health. /// Gets bound automatically to the when inserted to hierarchy. /// - public abstract class HealthDisplay : Container + public abstract class HealthDisplay : Container, IHealthDisplay { - public readonly BindableDouble Current = new BindableDouble(1) + public Bindable Current { get; } = new BindableDouble(1) { MinValue = 0, MaxValue = 1 }; + public virtual void Flash(JudgementResult result) + { + } + /// /// Bind the tracked fields of to this health display. /// diff --git a/osu.Game/Screens/Play/HUD/IHealthDisplay.cs b/osu.Game/Screens/Play/HUD/IHealthDisplay.cs new file mode 100644 index 0000000000..b1a64bd844 --- /dev/null +++ b/osu.Game/Screens/Play/HUD/IHealthDisplay.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.Framework.Graphics; +using osu.Game.Rulesets.Judgements; + +namespace osu.Game.Screens.Play.HUD +{ + /// + /// An interface providing a set of methods to update a health display. + /// + public interface IHealthDisplay : IDrawable + { + /// + /// The current health to be displayed. + /// + Bindable Current { get; } + + /// + /// Flash the display for a specified result type. + /// + /// The result type. + void Flash(JudgementResult result); + } +} diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index fa914c0ebc..0d92611e0e 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -33,7 +33,7 @@ namespace osu.Game.Screens.Play public readonly SkinnableComboCounter ComboCounter; public readonly SkinnableScoreCounter ScoreCounter; public readonly SkinnableAccuracyCounter AccuracyCounter; - public readonly HealthDisplay HealthDisplay; + public readonly SkinnableHealthDisplay HealthDisplay; public readonly SongProgress Progress; public readonly ModDisplay ModDisplay; public readonly HitErrorDisplay HitErrorDisplay; @@ -272,12 +272,7 @@ namespace osu.Game.Screens.Play protected virtual SkinnableComboCounter CreateComboCounter() => new SkinnableComboCounter(); - protected virtual HealthDisplay CreateHealthDisplay() => new StandardHealthDisplay - { - Size = new Vector2(1, 5), - RelativeSizeAxes = Axes.X, - Margin = new MarginPadding { Top = 20 } - }; + protected virtual SkinnableHealthDisplay CreateHealthDisplay() => new SkinnableHealthDisplay(); protected virtual FailingLayer CreateFailingLayer() => new FailingLayer { @@ -320,7 +315,7 @@ namespace osu.Game.Screens.Play AccuracyCounter?.Current.BindTo(processor.Accuracy); ComboCounter?.Current.BindTo(processor.Combo); - if (HealthDisplay is StandardHealthDisplay shd) + if (HealthDisplay.Drawable is IHealthDisplay shd) processor.NewJudgement += shd.Flash; } diff --git a/osu.Game/Screens/Play/SkinnableHealthDisplay.cs b/osu.Game/Screens/Play/SkinnableHealthDisplay.cs new file mode 100644 index 0000000000..5b77343278 --- /dev/null +++ b/osu.Game/Screens/Play/SkinnableHealthDisplay.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.Bindables; +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Scoring; +using osu.Game.Screens.Play.HUD; +using osu.Game.Skinning; + +namespace osu.Game.Screens.Play +{ + public class SkinnableHealthDisplay : SkinnableDrawable, IHealthDisplay + { + public Bindable Current { get; } = new Bindable(); + + public void Flash(JudgementResult result) => skinnedCounter?.Flash(result); + + private HealthProcessor processor; + + public void BindHealthProcessor(HealthProcessor processor) + { + if (this.processor != null) + throw new InvalidOperationException("Can't bind to a processor more than once"); + + this.processor = processor; + + Current.BindTo(processor.Health); + } + + public SkinnableHealthDisplay() + : base(new HUDSkinComponent(HUDSkinComponents.HealthDisplay), _ => new DefaultHealthDisplay()) + { + CentreComponent = false; + } + + private IHealthDisplay skinnedCounter; + + protected override void SkinChanged(ISkinSource skin, bool allowFallback) + { + base.SkinChanged(skin, allowFallback); + + skinnedCounter = Drawable as IHealthDisplay; + skinnedCounter?.Current.BindTo(Current); + } + } +} diff --git a/osu.Game/Skinning/HUDSkinComponents.cs b/osu.Game/Skinning/HUDSkinComponents.cs index d690a23dee..8772704cef 100644 --- a/osu.Game/Skinning/HUDSkinComponents.cs +++ b/osu.Game/Skinning/HUDSkinComponents.cs @@ -7,6 +7,7 @@ namespace osu.Game.Skinning { ComboCounter, ScoreCounter, - AccuracyCounter + AccuracyCounter, + HealthDisplay } } From e89c5c3b3cadfa20f529b76028cf8d9bba5d3fa0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 16 Oct 2020 14:39:02 +0900 Subject: [PATCH 1727/1782] Add dynamic compile exceptions to fix skin test scenes --- osu.Game/Beatmaps/BeatmapManager.cs | 2 ++ osu.Game/Beatmaps/BeatmapManager_BeatmapOnlineLookupQueue.cs | 2 ++ osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs | 2 ++ osu.Game/Skinning/SkinManager.cs | 2 ++ 4 files changed, 8 insertions(+) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 4c75069f08..f3586ec0ec 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -19,6 +19,7 @@ using osu.Framework.Graphics.Textures; using osu.Framework.Lists; using osu.Framework.Logging; using osu.Framework.Platform; +using osu.Framework.Testing; using osu.Game.Beatmaps.Formats; using osu.Game.Database; using osu.Game.IO; @@ -36,6 +37,7 @@ namespace osu.Game.Beatmaps /// /// Handles the storage and retrieval of Beatmaps/WorkingBeatmaps. /// + [ExcludeFromDynamicCompile] public partial class BeatmapManager : DownloadableArchiveModelManager, IDisposable { /// diff --git a/osu.Game/Beatmaps/BeatmapManager_BeatmapOnlineLookupQueue.cs b/osu.Game/Beatmaps/BeatmapManager_BeatmapOnlineLookupQueue.cs index 16207c7d2a..cb4884aa51 100644 --- a/osu.Game/Beatmaps/BeatmapManager_BeatmapOnlineLookupQueue.cs +++ b/osu.Game/Beatmaps/BeatmapManager_BeatmapOnlineLookupQueue.cs @@ -13,6 +13,7 @@ using osu.Framework.Development; using osu.Framework.IO.Network; using osu.Framework.Logging; using osu.Framework.Platform; +using osu.Framework.Testing; using osu.Framework.Threading; using osu.Game.Online.API; using osu.Game.Online.API.Requests; @@ -23,6 +24,7 @@ namespace osu.Game.Beatmaps { public partial class BeatmapManager { + [ExcludeFromDynamicCompile] private class BeatmapOnlineLookupQueue : IDisposable { private readonly IAPIProvider api; diff --git a/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs b/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs index 362c99ea3f..f5c0d97c1f 100644 --- a/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs @@ -8,6 +8,7 @@ using osu.Framework.Audio.Track; using osu.Framework.Graphics.Textures; using osu.Framework.IO.Stores; using osu.Framework.Logging; +using osu.Framework.Testing; using osu.Game.Beatmaps.Formats; using osu.Game.IO; using osu.Game.Skinning; @@ -17,6 +18,7 @@ namespace osu.Game.Beatmaps { public partial class BeatmapManager { + [ExcludeFromDynamicCompile] private class BeatmapManagerWorkingBeatmap : WorkingBeatmap { private readonly IResourceStore store; diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index 7af400e807..37a2309e01 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -18,12 +18,14 @@ using osu.Framework.Graphics.OpenGL.Textures; using osu.Framework.Graphics.Textures; using osu.Framework.IO.Stores; using osu.Framework.Platform; +using osu.Framework.Testing; using osu.Game.Audio; using osu.Game.Database; using osu.Game.IO.Archives; namespace osu.Game.Skinning { + [ExcludeFromDynamicCompile] public class SkinManager : ArchiveModelManager, ISkinSource { private readonly AudioManager audio; From 5be9e30cd0a8005eba7c1592c16016cbcb126e92 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 16 Oct 2020 14:39:45 +0900 Subject: [PATCH 1728/1782] Add legacy implementation --- osu.Game/Skinning/LegacyHealthDisplay.cs | 101 +++++++++++++++++++++++ osu.Game/Skinning/LegacySkin.cs | 3 + 2 files changed, 104 insertions(+) create mode 100644 osu.Game/Skinning/LegacyHealthDisplay.cs diff --git a/osu.Game/Skinning/LegacyHealthDisplay.cs b/osu.Game/Skinning/LegacyHealthDisplay.cs new file mode 100644 index 0000000000..26617ea422 --- /dev/null +++ b/osu.Game/Skinning/LegacyHealthDisplay.cs @@ -0,0 +1,101 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Game.Rulesets.Judgements; +using osu.Game.Screens.Play.HUD; +using osuTK; + +namespace osu.Game.Skinning +{ + public class LegacyHealthDisplay : CompositeDrawable, IHealthDisplay + { + private readonly Skin skin; + private Sprite fill; + private Marker marker; + public Bindable Current { get; } = new BindableDouble { MinValue = 0, MaxValue = 1 }; + + public LegacyHealthDisplay(Skin skin) + { + this.skin = skin; + } + + [BackgroundDependencyLoader] + private void load() + { + AutoSizeAxes = Axes.Both; + + InternalChildren = new Drawable[] + { + new Sprite + { + Texture = skin.GetTexture("scorebar-bg") + }, + fill = new Sprite + { + Texture = skin.GetTexture("scorebar-colour"), + Position = new Vector2(7.5f, 7.8f) * 1.6f + }, + marker = new Marker(skin) + { + Current = { BindTarget = Current }, + } + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + Current.BindValueChanged(updateHp, true); + } + + private void updateHp(ValueChangedEvent hp) + { + if (fill.Texture != null) + fill.ResizeWidthTo((float)(fill.Texture.DisplayWidth * hp.NewValue), 500, Easing.OutQuint); + } + + protected override void Update() + { + base.Update(); + + marker.Position = fill.Position + new Vector2(fill.DrawWidth, fill.DrawHeight / 2); + } + + public void Flash(JudgementResult result) + { + marker.ScaleTo(1.4f).Then().ScaleTo(1, 200, Easing.Out); + } + + private class Marker : CompositeDrawable + { + public Bindable Current { get; } = new Bindable(); + + public Marker(Skin skin) + { + Origin = Anchor.Centre; + + if (skin.GetTexture("scorebar-ki") != null) + { + // TODO: old style (marker changes as health decreases) + } + else + { + InternalChildren = new Drawable[] + { + new Sprite + { + Texture = skin.GetTexture("scorebar-marker"), + Origin = Anchor.Centre, + } + }; + } + } + } + } +} diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index e1cd095ba8..f02d70fc2a 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -347,6 +347,9 @@ namespace osu.Game.Skinning case HUDSkinComponents.AccuracyCounter: return new LegacyAccuracyCounter(this); + + case HUDSkinComponents.HealthDisplay: + return new LegacyHealthDisplay(this); } return null; From a810f56ec87f0aeeb1d454bceb72eb6a30cc083d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 16 Oct 2020 14:49:05 +0900 Subject: [PATCH 1729/1782] Move "flash on hit only" logic to binding --- osu.Game/Screens/Play/HUD/DefaultHealthDisplay.cs | 8 +------- osu.Game/Screens/Play/HUDOverlay.cs | 10 ++++++++-- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/DefaultHealthDisplay.cs b/osu.Game/Screens/Play/HUD/DefaultHealthDisplay.cs index ae78d19c2d..b550b469e9 100644 --- a/osu.Game/Screens/Play/HUD/DefaultHealthDisplay.cs +++ b/osu.Game/Screens/Play/HUD/DefaultHealthDisplay.cs @@ -107,13 +107,7 @@ namespace osu.Game.Screens.Play.HUD GlowColour = colours.BlueDarker; } - public override void Flash(JudgementResult result) - { - if (!result.IsHit) - return; - - Scheduler.AddOnce(flash); - } + public override void Flash(JudgementResult result) => Scheduler.AddOnce(flash); private void flash() { diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 0d92611e0e..ac74dc22d3 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -315,8 +315,14 @@ namespace osu.Game.Screens.Play AccuracyCounter?.Current.BindTo(processor.Accuracy); ComboCounter?.Current.BindTo(processor.Combo); - if (HealthDisplay.Drawable is IHealthDisplay shd) - processor.NewJudgement += shd.Flash; + if (HealthDisplay is IHealthDisplay shd) + { + processor.NewJudgement += judgement => + { + if (judgement.IsHit) + shd.Flash(judgement); + }; + } } protected virtual void BindHealthProcessor(HealthProcessor processor) From f28bcabae72a49841b59f7428455004944a9ace6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 16 Oct 2020 14:54:46 +0900 Subject: [PATCH 1730/1782] Avoid transforms per hp change --- osu.Game/Skinning/LegacyHealthDisplay.cs | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/osu.Game/Skinning/LegacyHealthDisplay.cs b/osu.Game/Skinning/LegacyHealthDisplay.cs index 26617ea422..2fac11d7a4 100644 --- a/osu.Game/Skinning/LegacyHealthDisplay.cs +++ b/osu.Game/Skinning/LegacyHealthDisplay.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.Framework.Graphics.Sprites; +using osu.Framework.Utils; using osu.Game.Rulesets.Judgements; using osu.Game.Screens.Play.HUD; using osuTK; @@ -17,6 +19,9 @@ namespace osu.Game.Skinning private readonly Skin skin; private Sprite fill; private Marker marker; + + private float maxFillWidth; + public Bindable Current { get; } = new BindableDouble { MinValue = 0, MaxValue = 1 }; public LegacyHealthDisplay(Skin skin) @@ -45,25 +50,18 @@ namespace osu.Game.Skinning Current = { BindTarget = Current }, } }; - } - protected override void LoadComplete() - { - base.LoadComplete(); - - Current.BindValueChanged(updateHp, true); - } - - private void updateHp(ValueChangedEvent hp) - { - if (fill.Texture != null) - fill.ResizeWidthTo((float)(fill.Texture.DisplayWidth * hp.NewValue), 500, Easing.OutQuint); + maxFillWidth = fill.Width; } protected override void Update() { base.Update(); + fill.Width = Interpolation.ValueAt( + Math.Clamp(Clock.ElapsedFrameTime, 0, 200), + fill.Width, (float)Current.Value * maxFillWidth, 0, 200, Easing.OutQuint); + marker.Position = fill.Position + new Vector2(fill.DrawWidth, fill.DrawHeight / 2); } From 6d3a106a868774862b1fd4187cece7729723c30e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 16 Oct 2020 15:10:39 +0900 Subject: [PATCH 1731/1782] Simplify texture lookups --- osu.Game/Skinning/LegacyHealthDisplay.cs | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/osu.Game/Skinning/LegacyHealthDisplay.cs b/osu.Game/Skinning/LegacyHealthDisplay.cs index 2fac11d7a4..3691dbc731 100644 --- a/osu.Game/Skinning/LegacyHealthDisplay.cs +++ b/osu.Game/Skinning/LegacyHealthDisplay.cs @@ -7,6 +7,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Textures; using osu.Framework.Utils; using osu.Game.Rulesets.Judgements; using osu.Game.Screens.Play.HUD; @@ -22,6 +23,8 @@ namespace osu.Game.Skinning private float maxFillWidth; + private Texture isNewStyle; + public Bindable Current { get; } = new BindableDouble { MinValue = 0, MaxValue = 1 }; public LegacyHealthDisplay(Skin skin) @@ -34,15 +37,17 @@ namespace osu.Game.Skinning { AutoSizeAxes = Axes.Both; + isNewStyle = getTexture(skin, "marker"); + InternalChildren = new Drawable[] { new Sprite { - Texture = skin.GetTexture("scorebar-bg") + Texture = getTexture(skin, "bg") }, fill = new Sprite { - Texture = skin.GetTexture("scorebar-colour"), + Texture = getTexture(skin, "colour"), Position = new Vector2(7.5f, 7.8f) * 1.6f }, marker = new Marker(skin) @@ -78,7 +83,7 @@ namespace osu.Game.Skinning { Origin = Anchor.Centre; - if (skin.GetTexture("scorebar-ki") != null) + if (getTexture(skin, "ki") != null) { // TODO: old style (marker changes as health decreases) } @@ -88,12 +93,14 @@ namespace osu.Game.Skinning { new Sprite { - Texture = skin.GetTexture("scorebar-marker"), + Texture = getTexture(skin, "marker"), Origin = Anchor.Centre, } }; } } } + + private static Texture getTexture(Skin skin, string name) => skin.GetTexture($"scorebar-{name}"); } } From bdebf2f1a4f8127393115d99c8c172ce27fe85a8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 16 Oct 2020 16:17:36 +0900 Subject: [PATCH 1732/1782] Fix skinnable test scene still not working with dynamic compilation --- osu.Game/Tests/Visual/SkinnableTestScene.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Tests/Visual/SkinnableTestScene.cs b/osu.Game/Tests/Visual/SkinnableTestScene.cs index a856789d96..fe4f735325 100644 --- a/osu.Game/Tests/Visual/SkinnableTestScene.cs +++ b/osu.Game/Tests/Visual/SkinnableTestScene.cs @@ -35,12 +35,12 @@ namespace osu.Game.Tests.Visual } [BackgroundDependencyLoader] - private void load(AudioManager audio, SkinManager skinManager) + private void load(AudioManager audio, SkinManager skinManager, OsuGameBase game) { var dllStore = new DllResourceStore(DynamicCompilationOriginal.GetType().Assembly); metricsSkin = new TestLegacySkin(new SkinInfo { Name = "metrics-skin" }, new NamespacedResourceStore(dllStore, "Resources/metrics_skin"), audio, true); - defaultSkin = skinManager.GetSkin(DefaultLegacySkin.Info); + defaultSkin = new DefaultLegacySkin(new NamespacedResourceStore(game.Resources, "Skins/Legacy"), audio); specialSkin = new TestLegacySkin(new SkinInfo { Name = "special-skin" }, new NamespacedResourceStore(dllStore, "Resources/special_skin"), audio, true); oldSkin = new TestLegacySkin(new SkinInfo { Name = "old-skin" }, new NamespacedResourceStore(dllStore, "Resources/old_skin"), audio, true); } From f0b15813e206bc8074102905cd52c6a986e414f3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 16 Oct 2020 16:18:29 +0900 Subject: [PATCH 1733/1782] Add support for both legacy styles --- .../Screens/Play/SkinnableHealthDisplay.cs | 6 +- osu.Game/Skinning/LegacyHealthDisplay.cs | 140 ++++++++++++------ 2 files changed, 103 insertions(+), 43 deletions(-) diff --git a/osu.Game/Screens/Play/SkinnableHealthDisplay.cs b/osu.Game/Screens/Play/SkinnableHealthDisplay.cs index 5b77343278..d35d15d665 100644 --- a/osu.Game/Screens/Play/SkinnableHealthDisplay.cs +++ b/osu.Game/Screens/Play/SkinnableHealthDisplay.cs @@ -12,7 +12,11 @@ namespace osu.Game.Screens.Play { public class SkinnableHealthDisplay : SkinnableDrawable, IHealthDisplay { - public Bindable Current { get; } = new Bindable(); + public Bindable Current { get; } = new BindableDouble(1) + { + MinValue = 0, + MaxValue = 1 + }; public void Flash(JudgementResult result) => skinnedCounter?.Flash(result); diff --git a/osu.Game/Skinning/LegacyHealthDisplay.cs b/osu.Game/Skinning/LegacyHealthDisplay.cs index 3691dbc731..7d9a1dfc15 100644 --- a/osu.Game/Skinning/LegacyHealthDisplay.cs +++ b/osu.Game/Skinning/LegacyHealthDisplay.cs @@ -18,14 +18,18 @@ namespace osu.Game.Skinning public class LegacyHealthDisplay : CompositeDrawable, IHealthDisplay { private readonly Skin skin; - private Sprite fill; - private Marker marker; + private Drawable fill; + private LegacyMarker marker; private float maxFillWidth; - private Texture isNewStyle; + private bool isNewStyle; - public Bindable Current { get; } = new BindableDouble { MinValue = 0, MaxValue = 1 }; + public Bindable Current { get; } = new BindableDouble(1) + { + MinValue = 0, + MaxValue = 1 + }; public LegacyHealthDisplay(Skin skin) { @@ -37,25 +41,29 @@ namespace osu.Game.Skinning { AutoSizeAxes = Axes.Both; - isNewStyle = getTexture(skin, "marker"); + isNewStyle = getTexture(skin, "marker") != null; - InternalChildren = new Drawable[] + // background implementation is the same for both versions. + AddInternal(new Sprite { Texture = getTexture(skin, "bg") }); + + if (isNewStyle) { - new Sprite + AddRangeInternal(new[] { - Texture = getTexture(skin, "bg") - }, - fill = new Sprite + fill = new LegacyNewStyleFill(skin), + marker = new LegacyNewStyleMarker(skin), + }); + } + else + { + AddRangeInternal(new[] { - Texture = getTexture(skin, "colour"), - Position = new Vector2(7.5f, 7.8f) * 1.6f - }, - marker = new Marker(skin) - { - Current = { BindTarget = Current }, - } - }; + fill = new LegacyOldStyleFill(skin), + marker = new LegacyOldStyleMarker(skin), + }); + } + marker.Current.BindTo(Current); maxFillWidth = fill.Width; } @@ -70,37 +78,85 @@ namespace osu.Game.Skinning marker.Position = fill.Position + new Vector2(fill.DrawWidth, fill.DrawHeight / 2); } - public void Flash(JudgementResult result) - { - marker.ScaleTo(1.4f).Then().ScaleTo(1, 200, Easing.Out); - } + public void Flash(JudgementResult result) => marker.Flash(result); - private class Marker : CompositeDrawable - { - public Bindable Current { get; } = new Bindable(); + private static Texture getTexture(Skin skin, string name) => skin.GetTexture($"scorebar-{name}"); - public Marker(Skin skin) + public class LegacyOldStyleMarker : LegacyMarker + { + public LegacyOldStyleMarker(Skin skin) { - Origin = Anchor.Centre; - - if (getTexture(skin, "ki") != null) + InternalChildren = new Drawable[] { - // TODO: old style (marker changes as health decreases) - } - else - { - InternalChildren = new Drawable[] + new Sprite { - new Sprite - { - Texture = getTexture(skin, "marker"), - Origin = Anchor.Centre, - } - }; - } + Texture = getTexture(skin, "ki"), + Origin = Anchor.Centre, + } + }; } } - private static Texture getTexture(Skin skin, string name) => skin.GetTexture($"scorebar-{name}"); + public class LegacyNewStyleMarker : LegacyMarker + { + public LegacyNewStyleMarker(Skin skin) + { + InternalChildren = new Drawable[] + { + new Sprite + { + Texture = getTexture(skin, "marker"), + Origin = Anchor.Centre, + } + }; + } + } + + public class LegacyMarker : CompositeDrawable, IHealthDisplay + { + public Bindable Current { get; } = new Bindable(); + + public LegacyMarker() + { + Origin = Anchor.Centre; + } + + public void Flash(JudgementResult result) + { + this.ScaleTo(1.4f).Then().ScaleTo(1, 200, Easing.Out); + } + } + + internal class LegacyOldStyleFill : CompositeDrawable + { + public LegacyOldStyleFill(Skin skin) + { + // required for sizing correctly.. + var firstFrame = getTexture(skin, "colour-0"); + + if (firstFrame == null) + { + InternalChild = new Sprite { Texture = getTexture(skin, "colour") }; + Size = InternalChild.Size; + } + else + { + InternalChild = skin.GetAnimation("scorebar-colour", true, true, startAtCurrentTime: false, applyConfigFrameRate: true) ?? Drawable.Empty(); + Size = new Vector2(firstFrame.DisplayWidth, firstFrame.DisplayHeight); + } + + Position = new Vector2(3, 10) * 1.6f; + Masking = true; + } + } + + internal class LegacyNewStyleFill : Sprite + { + public LegacyNewStyleFill(Skin skin) + { + Texture = getTexture(skin, "colour"); + Position = new Vector2(7.5f, 7.8f) * 1.6f; + } + } } } From 9837286aea6ed1ce625197737d587749e29977f0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 16 Oct 2020 16:18:57 +0900 Subject: [PATCH 1734/1782] Add test resources --- osu.Game.Tests/Resources/old-skin/score-0.png | Bin 0 -> 3092 bytes osu.Game.Tests/Resources/old-skin/score-1.png | Bin 0 -> 1237 bytes osu.Game.Tests/Resources/old-skin/score-2.png | Bin 0 -> 3134 bytes osu.Game.Tests/Resources/old-skin/score-3.png | Bin 0 -> 3712 bytes osu.Game.Tests/Resources/old-skin/score-4.png | Bin 0 -> 2395 bytes osu.Game.Tests/Resources/old-skin/score-5.png | Bin 0 -> 3067 bytes osu.Game.Tests/Resources/old-skin/score-6.png | Bin 0 -> 3337 bytes osu.Game.Tests/Resources/old-skin/score-7.png | Bin 0 -> 1910 bytes osu.Game.Tests/Resources/old-skin/score-8.png | Bin 0 -> 3652 bytes osu.Game.Tests/Resources/old-skin/score-9.png | Bin 0 -> 3561 bytes .../Resources/old-skin/score-comma.png | Bin 0 -> 865 bytes osu.Game.Tests/Resources/old-skin/score-dot.png | Bin 0 -> 771 bytes .../Resources/old-skin/score-percent.png | Bin 0 -> 4904 bytes osu.Game.Tests/Resources/old-skin/score-x.png | Bin 0 -> 2536 bytes .../Resources/old-skin/scorebar-bg.png | Bin 0 -> 7087 bytes .../Resources/old-skin/scorebar-colour-0.png | Bin 0 -> 465 bytes .../Resources/old-skin/scorebar-colour-1.png | Bin 0 -> 475 bytes .../Resources/old-skin/scorebar-colour-2.png | Bin 0 -> 466 bytes .../Resources/old-skin/scorebar-colour-3.png | Bin 0 -> 464 bytes .../Resources/old-skin/scorebar-ki.png | Bin 0 -> 8579 bytes .../Resources/old-skin/scorebar-kidanger.png | Bin 0 -> 7361 bytes .../Resources/old-skin/scorebar-kidanger2.png | Bin 0 -> 9360 bytes osu.Game.Tests/Resources/old-skin/skin.ini | 2 ++ 23 files changed, 2 insertions(+) create mode 100644 osu.Game.Tests/Resources/old-skin/score-0.png create mode 100644 osu.Game.Tests/Resources/old-skin/score-1.png create mode 100644 osu.Game.Tests/Resources/old-skin/score-2.png create mode 100644 osu.Game.Tests/Resources/old-skin/score-3.png create mode 100644 osu.Game.Tests/Resources/old-skin/score-4.png create mode 100644 osu.Game.Tests/Resources/old-skin/score-5.png create mode 100644 osu.Game.Tests/Resources/old-skin/score-6.png create mode 100644 osu.Game.Tests/Resources/old-skin/score-7.png create mode 100644 osu.Game.Tests/Resources/old-skin/score-8.png create mode 100644 osu.Game.Tests/Resources/old-skin/score-9.png create mode 100644 osu.Game.Tests/Resources/old-skin/score-comma.png create mode 100644 osu.Game.Tests/Resources/old-skin/score-dot.png create mode 100644 osu.Game.Tests/Resources/old-skin/score-percent.png create mode 100644 osu.Game.Tests/Resources/old-skin/score-x.png create mode 100644 osu.Game.Tests/Resources/old-skin/scorebar-bg.png create mode 100644 osu.Game.Tests/Resources/old-skin/scorebar-colour-0.png create mode 100644 osu.Game.Tests/Resources/old-skin/scorebar-colour-1.png create mode 100644 osu.Game.Tests/Resources/old-skin/scorebar-colour-2.png create mode 100644 osu.Game.Tests/Resources/old-skin/scorebar-colour-3.png create mode 100644 osu.Game.Tests/Resources/old-skin/scorebar-ki.png create mode 100644 osu.Game.Tests/Resources/old-skin/scorebar-kidanger.png create mode 100644 osu.Game.Tests/Resources/old-skin/scorebar-kidanger2.png create mode 100644 osu.Game.Tests/Resources/old-skin/skin.ini diff --git a/osu.Game.Tests/Resources/old-skin/score-0.png b/osu.Game.Tests/Resources/old-skin/score-0.png new file mode 100644 index 0000000000000000000000000000000000000000..8304617d8c94a8400b50a90f364941bb02983065 GIT binary patch literal 3092 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 Y0OO;-v8NaCZ2$lO07*qoM6N<$f&jDokN^Mx literal 0 HcmV?d00001 diff --git a/osu.Game.Tests/Resources/old-skin/score-3.png b/osu.Game.Tests/Resources/old-skin/score-3.png new file mode 100644 index 0000000000000000000000000000000000000000..82bec3babebd59263dc486e5900a3a01ea4aaed7 GIT binary patch literal 3712 zcmV-`4uA29P)ifhvOHz zhC{oSeoa&R8~uM7%-=W#Zb1^@5;(PUqk@Qru>6f`==sgR{r1})oj7UIB!^~XGSCZ1 z6Y$?Dn&A%3t6@P9=mY}V-=KisM`261#=CoHtZryJ-~qCM9H6)SKR-{^yliU5(m+4Z z0ki|H8rtOZ0PYi__hESCe>PZ*O%g9=0et~4Pzc;4|L!|&+Oz@DXfy>$+;vTd!x3q1 zZS@~Ha-=~s)CaTxO&XfzbAIo~JuR=>dEDItW0Pgs>CFOQATSsh^1uTRl;3mDJ%zX5 zetS|%N{ZohIFJ^ksYpDr0Wa-=gOBg05ePQK3A*dr2&ScZ3Yb{c^| zz^JXQHMVZu+Oc87hSNun9zBLr&I0Fv^J09HgiEJxbRrMCBqkZ7MZgGP+=>+|Wwr}5F`u_XxpW3%?-#>Bczk$=T zvi0KafaHR`hv8jiY>s%g3@Cr~)mMMNXwjm~?Cfmo=D)nREf8*(9>2j(qJ7zNu;P;U<@!H_!B%)+tAQpVp+|e9x%p)F=ks^n|bx> zRkOXl-3*07*5|PYLdH60=gytc(W6JxkX69%#96swlfy0~obqM1u)vT}qee}B>7|#v zJ@qVOwWFiM07DoD4jeGB)CP<(ii(O1B&dPdH!?Fb4Y&KJ+cO844-+R&bgWyquKeML zAC4e76%G0&mc#M9OE#DUEC&Ai`RAWU{C+0$(Z`&PsSQGaoL;M9;2uuL-8Q1PO-u1`{gYLio{>f9PPIWR_y7Q{f=QBR} zUNOmR&hyL^D&o5vi#Pcq1#xPjEe0kgE z&6~dxL+Qbj5)O4**}Wp1s{XbeJ9f;7r^k6u@_a!-f$}bC*6p+j2eLzcadB};d3m{$ zKe9V`BhZ!c-h1zbKmPdR9r%dc$%=JGIv~`qfk+oq1yFRl-dKu_07x6yO9E98k*Qv z;gBi1!F$(TcV#kLqzDf7x1xsvq*7A{;uTx)ES zW-N8|FqHSvFTVJqhddiEKj>YS^>^QW_kw6;B{hTYn4qS2g1A{PyQe3OQB`f*w(Wc2 z*GuyBerYV@d6veO0=FVbZ-J~#=v{^aC@%{2#)Q70gC zf56xvm?fb+<#(ulCMxVjN?MV&W8}fFk(W_X^?Z^V^jq73A4Iiz5>&0 z`{nyatx*h#XVWx8`-z8#p*W0r>#es+?zrO)OY6onmN83SNBh;he*OBtOCe&DS}XSn zs)37TFtb}a)OLwwzhq{r*7t>E6*}bmu&_%nC6*}mf#IO>ThURELhqN%-i^6n#|UhX zptFrLXU-T;J@r)kjvYJpN^5jlB&AR8d3_U#55h27O=<$#=t<4ZVnfNuWIXfCGwh;kw06Hqk~t7NpSY%!$rsA#?glJL z5)O=8FS>}|y!P5_cjo8kTORGM?We)?>?^OlVzvCjR8CDCsee1V9i)g@xo!<(^npWu zZ)pU0^;R&5<<%Yy^$;Q~|9kfAF_7TKJMX-6GsIMlghI1~Cu5i4YIYSRt(S9YQ=TkI zqFg~Ak)7#6FUn9hsZ)R@D_5>O!LIj44|dRjK)}3w`Lg-;+iwRD^ILGe6~J$WWpczW zYb4Y!6j>EFNq zo#^F4&CSg!!PS)0${klsW$Gin+yJRi1B9k>CAm^G8Z@=ANQNNtL+ls8Q>hm(UUb0- zCo3aC?aZ>yxF+l#M0FU2g@vvGI8da16dG+6!-F4u@PT#1WKH@N$)IoG;K753^y}Bp zi&b+#PWlcWJgAh}X-!TBB{bA;i{*jyWGOumJG~LBg{Yo+EX!1B>FMcl*@INa9ubuI z17yH@e6(LUnca8DAxqy^ZdQurc<}MZAD@hDcPTxr_!3R#X^8eev+EV(n}t!*q)}9{ z?h)aWE`Rwz13-0U%a$$c|HKnd7_55nn1uKG@WT)NsE%v#zq^H-S+0Ysk5kjhk!#nk zom*O3>f)4#o*-DI$g5CUeNLP>5dKBT$ z)!>(NIX-BptE+2;*vU+EFB*;fNs`~WRS|J&TT{lNLx-+XH6=0_+3%5r%Fv$N_RuP4 zMB6QL6ciM=de|+;Zlzfv5f#rc20NIc@uo3MLd2gMWo4;pjC2_B=FCbbXG^Z+&YnFx zt-Fqyi4Lj@X&XCHWpG5unF-W_G8v%UvpX2u6%`c@S=zp0H1DhmkYUp%sm_D6cQdGR z*rSg=YQ!5M=p8dc@4Vt*RXCiQoMh6}@7=riN4D{HBP1^4h!G=<^78VbVnDvo8EGwv zF)K*csO~1=cJ_3)tXj3InA;7BLW0I3JR?Gzt(u(e@~~g%=<51Wup&?6c2KK|ykm&l7o`jvcv9?65&uoXpDVjpW0`0iBfRm(8+Ew$*D|kDU32Hcg$qq3FJ0mtF5wJd8D$;>NSIpycAF@a zgbbZovu1^)RC6}G5!eKLjehR*qD70s5JD!m8|Eri*R5Me0-%Oi1Wc41NLBLE z`j0kU%urheTuLNI1xob{G?6aGZfxymoKsT5aAWuG-NuCr7pxKvCNaj3A8$?38CLOT zbh>A$>d@1L!64gEz&I2?tc61dTevO(FJ4wln8}lh@`wM7*yAX30ioAIS};vh$ja#h6KBp6FpF&L}jOGnOu0S}<_n zz=X|ZPY)&+^#l^vfXJzZI{QjO%Wl9JiM!VfquttEtdBNl7>W=nTfTgG`TY6wGr06z z?i<;@<S}*mWBi6G$ z+0AzeH>(iI7Fp?)IRxQ_@i$GJII$QiE(h8<1(h)6zwGBQeLi3G(4M#+{6m!UPTE^Q(jkV@Jr zBHX9FFw3m)>r*ojc9)c9Jh!%i25>Duj4xuq#V{S}%HZR8b{b9(g7 z&yBuSrEgoQMrO?GUeTZX4x8iG_GX~d e_>~?15nuodi5A$|t>!iW0000OpYTi^Qr^^X#Z#l#=& zu!%q5p`ZNa-ygVgZe?E4U*w1K89+mXRMioZ}SqFB0l^X7k^IC0{g zqM{;^nVBhUHk%>n^73*g#cBfmZ#wk?G_fapvg$l(!92~OD50pK_>i)@{?w^cRV5`Q z!YIR23ZN(=BO^m(WihMblyc*V*^`NfZH|ByJS8r$B8n=C_m?bL(s=UZ$)tb{j(pUF zYDR`_^Wd=)U&h5&TToE&@!`XVKb!+#fonM=Jx^_>K?|;S7ey&(v3T3IZ7Z9aniQwg zY5H2s>u5A8AcHY8jm2`NK??_HQ4Ctt(fL(vZEe{umn+UyvqvNn5j{OULh6WSfLqSA zCU5zWw?+B+`OD9pJNI^BVWF_wr`Hf?F&qwS*)#+_GBTopj+kuY=0QvH=7zj2q^Li1 z=+Gw%7cPt|fgaHK__(-v^QH&}gUK!*wL%fw9Odn8R*Q`rH#WAmwmP!2vuALX0UtYd zOq@P_TKIgvS*dNsESbtQcx;Cx6{0=AS5;N@$-#pMONkycng@ftckiA!e*Cx?8ykyL zBw0;bai%s4JkEo>ahqO34dkPmni}D7IHr9#6bgxF&z^~0yLP3`Ppc+rkqdcyhvjX@ zjvYW_wEf14GsMW&8<%L8bGz!_3BovSg~sV{{1e!yfKd_Cnv?# zt5+j8ZrtdCkH{fKDS+C%kxc95t(N$^?C8;>HEe|RU5^!_ySrNu&+pUQui%Z#2=wF| z0S$T6x?Y*q^>**x-LP!gGDCS|z1_EOUmwwxC%MVp`WDdHY+UuE(@EaNt1it5>fc(y4DK2GJ;7$(Bv? z&a_V6G+KPHWy_YP_3PK$_43BFn3$LlXU?4QK6&!wOFH#`iXX91bz;4jma&JLfEMwt zS6^S>Kx$E#mzOuA>#ZcG}mop>e>9z(qGV8kPGjB6#Wt{LOQ?^mb^rMdOmYExrwm1 z_~6KqBXveyPbwH9uD1Zh4EE_&Mj4x8~z7Tdc*Tf9G0L_SNY8=;Ps)gtV;!{-uuRX4aiY&9l)zZ@G zBO_X9T&71Y$iYRSGzIY24%|Rg%3MBNVI^pnaA1Svyaj5GrpM! ze0NnVSFT*%($b>x7|U=GN00dS?b}-MR^nnV@Hs50g!ZNE4Bz8~=v)6(L6P=zr^V+c zkpRBo0gTDGUVhfAkqUEPxqbWg8n@eRICqO9rR=x0wF!^MBkte7zlc7!fX$id ze?aul=Z%ext=FzyTUuILnh>-`5nlhXNiwrnqs{9WbfT#fKS96W9}i7s_*`FKAM<$+ zz3qVsdrtJ2q;pQbQvTYtYpdCE&IVY^HWw*J$~2e0lW$7@G{`16K(QaR!KAjxj_Y(~ zWu;TUK>eK@+9Xw#_G}ysI+zZlo~lM-Fk*;jdbGE<*JEM|^u`WtoW|I&HxrU2mD4_a z>(;H#*4EZ4E`&t#WZ6HW2$^=;r2Tv0=jo!9{L#FxczYuXC5>8}rjm4p9t~ zxd>lz#!d;~)#+Xc6ReUfr?w=#%(4*bAeHb--CJ??6qwlY(TcI2lA|8-SN^nw-iTnV(#=W#*%!tE;P;$7e>M znG%#?wg68ky0K&%1jtFWaj(uMgjMwT06vf*Z6nNOYeQ?{uW_9P#M|oG8WG*h2(*}O z?zd>&+U{)#79j%PF=dzn_)ni^C&l!A?73*G0~GNjoo?FgUKE=LKEop|CXB3UOvTBR z=`?Z9A%5M&^h3?kq+JK#wd=pa<-C2`iT-@afq;ZDfQH|l)dC_b-lF)_ED zfO&xSL#GGY+uOUSxqMbqQliQkrlp(r&Ye3wkh&MT5yb4enyO#cNa~@Y!4#p(R$~BE zj&7@f4$q;_Z18`A+>H~Sz;Bi506MG!s(8e&zpxQ_60ap~9+jq3;JNRCwC#T6<^|`xTzuoqgt=XiRRh z_ond~O^gpp;_DiXTiZb(Gp zz3LXYWOE+88!KA!EDv$}J0V#lo$=Z^T9CQ)WzWKOrA~yg;fPSEl5$59> z2pg4^l~EE7CPV~tfdU{8$YJ7c3l|U;(WQ%N2_S!Nz6l~20s=rM&<=F-F@wJoBTCjA z1r(Q-md>uKs+yy!>ZpN%0iUL6P78mI61CZE2B7!!^bFM3*N5<4Bhbm|H4q4dA3l88 z#Y6`r!tyH-qVKc;uYs5BZn6PlewoTv9Dn@y@t=(yI~F`uA~GaL*L7XN4JmS}laJG@ z*l545t}gmYU$fio8m`rkn+;S~S6}@E8@qyVgNRI&ESghMQ8C@`_p3QMIqxma5a~5* z)(G-8=AicX_xr|=AOC&ue8S5wTp*T5RSu*Kuh*OJa5&y?;cR|qyt19iF}k&4$_En=FA!8!i5V&xb@bpTTS?=htr5I zTS3adQTNKs%=Au~G9?S=agQ4}E8`I`yQb{gwJV5Zeh-H}WeWtPrHV(?(b?IlUcY{w;=q05#tkoP3WKdj z>F9HSZu~7D)2uA2B1NdQJn=SKa@@57k`J+a2YAXS@8Odg3FM?*(iKig_}F+gLQ)Cs z;$*MK_0dF2Pd?e$*hr)rfO_B=6AeixwX&!jutKSc@@?baL|BaU@@vG3sEvu*k_A#I z6r#Ir1zH&NK5Uj{<_%KPCS@7BN%ty}0MR9>Hz?|rB;wdyS;SrUvGN&6Mv^wZs5TO< zh;1l@;Tp0E(i8aQh;Hgv=?qLd!Nnbd%|cYi6#P)Eo{Xq%IMo9nk(~}?1EV=04Abi9 zl2E3gh~Q>~lGm(!aHtj?N+TE5kdGff_Tg=G}`IFTUUvcxy^)wBNxu zLWC)UXvr+9mXwsJiApGHvvTCf5#_*v1GJlt@1_fZA>W{ALm*i4sO+}3wknq|U+%kq z|9)F-ZSB81Iyye%n%=|(qlK*99NCyxm=3JYhD&zO#Ar<~Gg(U7WhYf~buL50t7dTZE;Ewg6v$1TvSrJbhV$po{|Sd~agb3q2pKHvGWmob0uzbw{Q2`M%F4=$kd9K2 zvfSLt>aSnF?n9b+SyxwA&&s}8BH1IUavRUAve@sY0<$5yixw|l9PQ(+v@TS?zF@(E z)7bqx@Q}wl-QcOoKH9|>_FTPswIBGDYr2E26l6kgnOvKs#s$fK4o(}Z_cZ>Tj-??b z70#PCPqiXSD~JHIPn$Na1a9;%{;iu=M^jCyNn^hVasuw-V?id>$`DozSUA5f^OJ@J z1rQ#o`}gk;unTms8nPw1xPw=WF7EUD{Tb$bYBi*=c#aTDBbfyS1>>NEXb_+voy0&T z56eNSi88`?mUijVBsyL*CrFYZNF3zA|MIK?N!GjQ{dLoH+E65U=RuB4Nwg$+d3h9( z-EMZN|o)|vu7bGgA9n4*)A9((mHuaWx0_hrcRwY z7H>abpC*TQGAT1jC;NU5KNBdfsHiAgvSdlLtR|YoKoWLK=`JD@rJx8QU^?(6BOX~Q zt21ZLqzn6jG4ilghzE&eim8Z{?%cUEpWG1Hcta9J(}rU28oZ{J>7RaK=V@=vNvckbNLpivvS z8|aY=NR()hKluFY*|T>W8yllPvKncVMOLp~UBW%n1SV6+#PWEVzr~+V2P&2?UtYCi z#}21ejhC%S0tYS5E9}1F>K9<5v8+NCm<0R)*bhHyXl`yM7ck5le8_n6lox9sD)01Q^R1!yyw@`5-OXB48V2-e2eru^e9_OZ)I+1KQKrxN##9Zszgh6GV0k z2x^ZWJu5Oi1(pN9 zf@MEBbm&lTTU%T7#3QlMO`{|mWOW24iS9qJU(RFgN>$G(ND87Vj|6q37oj=Z*vizy zgrU0I(T^|Mx^?RZ)J{-upQv6Zdq@EVrPS2aM9|pXMk@LTxeV}}`+Heh4%Pu|QBhTd z>`UdUQpmnL0j^NCZr!?>Xu@*fm(c=1xs{}uRT3vFQ0=B_Lj);5*VfiXAfBJ2L%7S+ z>d#okH_MN0%#x8FgAXEV5)(eIKBGB5`Z>{mxOC~#QbcLq4O%Cj!>ZFTcu8+%hbY%nNvFx3~^%D;%VEsM5UR#L;CxMk4dF7gcA2jPdLP^RLnwq zP1C$?saxnB>S$g310vDPeE@`Y>G^qpm&(I%OKRL9sWD9=8#es~L;f4akZ1bCoj1uN zN;Bj^uKZ^f^WQXvJ@==;9I3&WEzS&oM7Ai=|NoP0gtz|+FaQZ$3c`e8^P>O&002ov JPDHLkV1kmK?iT<6 literal 0 HcmV?d00001 diff --git a/osu.Game.Tests/Resources/old-skin/score-6.png b/osu.Game.Tests/Resources/old-skin/score-6.png new file mode 100644 index 0000000000000000000000000000000000000000..b4cf81f26e5cab5a068ce282ee22b15b92d0df12 GIT binary patch literal 3337 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 TZ*tsI00000NkvXXu0mjfuoP_o literal 0 HcmV?d00001 diff --git a/osu.Game.Tests/Resources/old-skin/score-7.png b/osu.Game.Tests/Resources/old-skin/score-7.png new file mode 100644 index 0000000000000000000000000000000000000000..a23f5379b223d61079e055162fdd93f107f0ec02 GIT binary patch literal 1910 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 literal 0 HcmV?d00001 diff --git a/osu.Game.Tests/Resources/old-skin/score-comma.png b/osu.Game.Tests/Resources/old-skin/score-comma.png new file mode 100644 index 0000000000000000000000000000000000000000..f68d32957ff8dc2e6e26e2b739eab85385548faf GIT binary patch literal 865 zcmV-n1D^beP) z8ION{U-N9C`MxinYv|$`M+}Q$F)W6~uoxD@Vpt6OzhDOaIXgR>o2*u=8V;)D@TF?C z+T17S{{H?ym;%w+TADXmhT?$-O-NWvN0;UR_GXqT)+8VcT7_OfX+F1jnFIZS?x8!# zh8}URz_+nygyLs1rfHeIXpZ(*xcNFpGu`( zwcG8nVHo$9mzUrA{eGSAow*&jOrP%XnP4jnsY0KQkB@6tS64o6^aq1MaO`wCezV#1 zPfkt<_8IyJm7o-H)Y71MrP!@^ySuwPg+f7WY;1^ZHX9uC`MfBVO5*6~=mXZgWiM7_ zg2J#BUQj3&i!V*nT&N{Y(}Zo?M9#738`=bqh+giU0>f>loV9|82+ zaU80#CVuw0IXW_vYCm)NDP(>JPj7d--Su=j9k?q&C<*&31~y-*}fwjTm!p*P&Jvpnyz z)m?^=o3|X6i423GymcH7s?X++lz!!LDcrGp4_y83T(4ux)LkG8*W#nj518@;Wz|pB rcm*5_XP0^3eKYf&7eE$5NN3S}(it^R$P@B}JRwiW6Y_*SAy4SP5!!0C zTE9FJKtC>>NlHB>x7U?fC3eMrUE}-6&6|p1Lzp5^Zgp4;-?Tka0Ir& z1rTBaEJ%Zoz;`eQ3$UbTh&3rgX9|7}x(Kd<2wyLO^E@RI`3er0F^e8jrdymkWwHaJ zU>EG6-At#`*8_n7WhgqG&Jb^YGairM;r=~O+gUez%_x%?%@8~chr`cux!mVouO}vx ziI~l1Vmh6Q;czJ0?Y5Q8W=ZG?xDRe~_LPX7{ta*sJS`LoBN8x8Qyg!)u8YB7AXw}% zxJ%jddP>*89q=fXO1TEF6vaC~s48xE{ zqtTROY?u4ELr$I4>-A5|~39QXWMIpY}VCkbsmq$cd@;G3M5vml{BBvrEa%t<@5R1rBdky#=iw0 zIjg12+aCmBcDQpy_7jQ3UL+DBYmj&PwOZ{J`d!cgAHWx$%}VBs{OkvExyH8F_z-XN zhAjBd4tYuwo)#^QA>$xAWkltmAR9EuCtu}5>6DRg%ppIcnq=Aa2%R;=oZ@xF169W7 zDr`CzH^QVBIE8JK`;M1drwJD)wp9Li|3LU5zyS0^RhH-3lmq|(002ovPDHLkV1mea BUy%R+ literal 0 HcmV?d00001 diff --git a/osu.Game.Tests/Resources/old-skin/score-percent.png b/osu.Game.Tests/Resources/old-skin/score-percent.png new file mode 100644 index 0000000000000000000000000000000000000000..fc750abc7e80287192efb65633e58b6b19e47125 GIT binary patch literal 4904 zcmV+@6W8pCP)>HPEA?bv`x0yR5sa^IhC4IP-&%2W!qCT)6~GN6jP8z zltl$(hYNRk=iGM=@9+a<{p0;+&Rp*D`+dJ>|D1Ea=v3#IPV=h$PLh+zA^z^}r3W$! zGSIW_KsG`4u0GIsjfJ#}={1b#=re=(4i|0#YNprjY=)m@0_q3k3FJZV@v;Kd0W|n>QZAbRG^nNiO?^UR0E#l_jgTq9~z?!8NzghdU+poJL2g+hsf zr%s(3>2NrVMMXvR`T6-ZbeqY?$H!~YqD2vtCQb6+wr$(ljEsy-_`CopALtxOAAjEj zYuGe?=SEiIO=||zdXeGb;n7b$^;GQbx8L61U@)iw0|q>G>#eu0fbXu5AobnyNm!iC z4z)E9C>-cIe}DhP6)RT!WB&a4{vIA4N~hB)tJSLZ?Ahb^^2;xA!^W3iepw$D7N$%l zlbSejV#3_Hb7Ph-U;alyK|vN-05e)gekv`XA88NQ97OAmNJ~pgUb=LtH!!Mj-IkUX zjSNns|XW)x3H0^tau1n;SBX zn?MHf>esKInlWRBY5Vr=6Ysg_p1_kQPX@rpK|sYoRTMCIU1YF7xdbv8mYSNH^vENR z1SBLRC|uKMG%Balsp{(L)P@ZkPK)5?=uYJ3;9;i4!hqsIgHtwc+&E|Y^yx<2I5HzI zWE2-FuvbA>_Bc1})?TD*91RaRCO`cV;31^JRgM9}_V?m^S0O^aH#Y*}zzT%68g zvABfB&uwmQR^WS%l$4au;q4xv<76FG@VTw4nq($@$l0SFeDJ|3@X&S)+U<7L(9obR zT)3dX9M#ydW6_M1o10sk-?_WHtEi|bwPVK)pC5nxal+1>J4ff`miEGX(4U%`nq05AY1g8_2nq&;fsEnr zQ2Y3bpW%0`#RCTps3}vXd=GDT0Uf4*hJnW_qjhJRG*S3QMnje?S(4!E>)VDwyc!!D zReE|lGMG=%(|Yvi(Sbkw@IxZ_uK~=i)i+zSJD4+W8cANrELsIJhMFoVDN)~j_nrFn z*I(7<&70NW!Gm4DBQ0Vo$^*JUDGBp%OII0GBFb^v7<|huw|JS&<~BdV4OLZDsWofX zoQJoVoN_3L1ikalJI!a#oSE?ItFNli(9kx*vDs`cM(}=S3_n8+6&4oS_U+r(yldAk z=P$qff>B+qUcFkOfKk)7lu8+O)|5 zzIY7Y{t0x3g2*6X-n4b=R=?S^XGaeiGNcWYcpoHjmlc3kTPiCnFJFQSHeYt-%9SQs z8pH|t~*}3o*C3~41znaK}Kr%Bk?P!W^q8gaph!G?3&%c4t3bKg) zB0(YZEr4n)vu4e5`7Q>ZnwlEa;sv3_ik@mw1|Aps04W-@riwlVzGD+uw3;xglR`O~ zT@z^B4rI^<9(bgc@+4Y7E_lKK39#2FM#W>fwhU z_C*NCxcKLve{T8x_ur5Al@^tzbaF6UlNv!k0}NWu0R-qnZ@lrwfvgb;1OTcvLDDaw&AO2 zbN~JK>m)wK`w_8h*|H^%Hrz~t8Vs=QI!OG%oH9_0APDrulN3><7Hz5zE@l!V*v0(( z{8PJk?`}fL@`V}#O${Q(VzAx>N+gPZ;DHB*vDry54^h&(fB*iY6g-^NoC>?^uDcRP zjT)unNQ^>-#9v2REYxVx(N|11(?ikG^Im-M#RE_)I@vd|ℜ1r5i{-0$U=)+!&zI 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 literal 0 HcmV?d00001 diff --git a/osu.Game.Tests/Resources/old-skin/scorebar-bg.png b/osu.Game.Tests/Resources/old-skin/scorebar-bg.png new file mode 100644 index 0000000000000000000000000000000000000000..1e94f464ca497c1eb0645586af13f489c28bf6f3 GIT binary patch literal 7087 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 Hu0mjf)F0A( literal 0 HcmV?d00001 diff --git a/osu.Game.Tests/Resources/old-skin/scorebar-colour-1.png b/osu.Game.Tests/Resources/old-skin/scorebar-colour-1.png new file mode 100644 index 0000000000000000000000000000000000000000..7669474d8bcce7afd6a0bc8c247b2c0ccef46c22 GIT binary patch literal 475 zcmV<10VMv3P)p000dL0ssI2y8t*G0000PbVXQnQ*UN; zcVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBUzeMv+?RCwC#+uKfqKoo`HwP^v37s}Mv z^v!%U1!Ihfi4kX}CPfX0X%(^kzi_c2n-*@mIwiGMK`l9bAd|@>+2kxH3;8PBW#@C; z{Cu^Jc@@viL-4zIex{JtVGGC4)cTyOZsjiG@m%+Q=2AMB3N?PF(pxDjRlkO;GHmsW zDi?wks-NXH;lwsq^CWj2U&p`iW6Y~g&f;}G#QE*L)Y`aRc23sGTTLhT%?uwWlgXpm zbdw3dY%FoWz8$vl;lR0=*8l*=7DjA@#JG(ZmZjD|Aj#jH%Dj8Vb z0@~6~q?G|y$p8**XzN)n_m!^o7qL;+0F?}&zc{Cs002ovPDHLkV1lE5)RO=J literal 0 HcmV?d00001 diff --git a/osu.Game.Tests/Resources/old-skin/scorebar-colour-2.png b/osu.Game.Tests/Resources/old-skin/scorebar-colour-2.png new file mode 100644 index 0000000000000000000000000000000000000000..70fdb4b14637abdf6237c4bf883fac33542d91f1 GIT binary patch literal 466 zcmV;@0WJQCP)p000dL0ssI2y8t*G0000PbVXQnQ*UN; zcVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBUzbV)=(RCwC#o83->Fc^m45BKBF0C$_a zGcQd`G{%^iSZSw`Wa%hu62#{T2XC9NWt{Nvv0z?bIH@L+$vN5NEM^PGL-C!9SaTCX zJA5o+j>AyD4&h@JYqruV9Br;{rFG10%`RlWm220Q(nhwE^D0YOyQH0eS6zqTH)`Hg z?PZ&|_Eq&>KJL@JgsgaK_uR=(F&}?t6ZeTU?k78^yG}mNTPLS#&d-y{}3GHw0Ev%NR~I1fq&n1`AM`PHtbK|Bh8k^vBQXJ~o4HM|P2SiJ60GQj*AXdAu| zD+ACa1Hh$*wnV#}lCGsiY{(h_B?CZOoVk+hwQ{%8GWND>UJwAFWPn9@I%QDH(4Zwu zpDAwy41kgW;6n9f343z8qHO?_4DbgPo7n{Gh`%H^(FVYC}$xlkqqJN(O*oGR(I!04(ILP<;w80AoP?p8+Di_5c6?07*qo IM6N<$f~e2NAOHXW literal 0 HcmV?d00001 diff --git a/osu.Game.Tests/Resources/old-skin/scorebar-colour-3.png b/osu.Game.Tests/Resources/old-skin/scorebar-colour-3.png new file mode 100644 index 0000000000000000000000000000000000000000..18ac6976c95a162914d6cdd371abf71aca48c72a GIT binary patch literal 464 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 JPDHLkV1iG9rlSA= literal 0 HcmV?d00001 diff --git a/osu.Game.Tests/Resources/old-skin/scorebar-kidanger.png b/osu.Game.Tests/Resources/old-skin/scorebar-kidanger.png new file mode 100644 index 0000000000000000000000000000000000000000..ac5a2c5893b635520d3f8e3804ff87101664d591 GIT binary patch literal 7361 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 literal 0 HcmV?d00001 diff --git a/osu.Game.Tests/Resources/old-skin/scorebar-kidanger2.png b/osu.Game.Tests/Resources/old-skin/scorebar-kidanger2.png new file mode 100644 index 0000000000000000000000000000000000000000..507be0463f0732a07aa7df87cd57c1f273c7d55b GIT binary patch literal 9360 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 KMNUMnLSTX Date: Fri, 16 Oct 2020 16:29:10 +0900 Subject: [PATCH 1735/1782] Add support for old marker style danger textures --- osu.Game/Skinning/LegacyHealthDisplay.cs | 27 +++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/osu.Game/Skinning/LegacyHealthDisplay.cs b/osu.Game/Skinning/LegacyHealthDisplay.cs index 7d9a1dfc15..0da2de4f09 100644 --- a/osu.Game/Skinning/LegacyHealthDisplay.cs +++ b/osu.Game/Skinning/LegacyHealthDisplay.cs @@ -84,17 +84,42 @@ namespace osu.Game.Skinning public class LegacyOldStyleMarker : LegacyMarker { + private readonly Sprite sprite; + + private readonly Texture normalTexture; + private readonly Texture dangerTexture; + private readonly Texture superDangerTexture; + public LegacyOldStyleMarker(Skin skin) { + normalTexture = getTexture(skin, "ki"); + dangerTexture = getTexture(skin, "kidanger"); + superDangerTexture = getTexture(skin, "kidanger2"); + InternalChildren = new Drawable[] { - new Sprite + sprite = new Sprite { Texture = getTexture(skin, "ki"), Origin = Anchor.Centre, } }; } + + protected override void LoadComplete() + { + base.LoadComplete(); + + Current.BindValueChanged(hp => + { + if (hp.NewValue < 0.2f) + sprite.Texture = superDangerTexture; + else if (hp.NewValue < 0.5f) + sprite.Texture = dangerTexture; + else + sprite.Texture = normalTexture; + }); + } } public class LegacyNewStyleMarker : LegacyMarker From 8104bd0f74b0874d6cc5f38f6aec0479aee4587b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 16 Oct 2020 16:45:28 +0900 Subject: [PATCH 1736/1782] Add fill colour changes --- osu.Game/Skinning/LegacyHealthDisplay.cs | 76 +++++++++++++++++++----- 1 file changed, 60 insertions(+), 16 deletions(-) diff --git a/osu.Game/Skinning/LegacyHealthDisplay.cs b/osu.Game/Skinning/LegacyHealthDisplay.cs index 0da2de4f09..f44dd2b864 100644 --- a/osu.Game/Skinning/LegacyHealthDisplay.cs +++ b/osu.Game/Skinning/LegacyHealthDisplay.cs @@ -12,14 +12,15 @@ using osu.Framework.Utils; using osu.Game.Rulesets.Judgements; using osu.Game.Screens.Play.HUD; using osuTK; +using osuTK.Graphics; namespace osu.Game.Skinning { public class LegacyHealthDisplay : CompositeDrawable, IHealthDisplay { private readonly Skin skin; - private Drawable fill; - private LegacyMarker marker; + private LegacyHealthPiece fill; + private LegacyHealthPiece marker; private float maxFillWidth; @@ -63,7 +64,9 @@ namespace osu.Game.Skinning }); } + fill.Current.BindTo(Current); marker.Current.BindTo(Current); + maxFillWidth = fill.Width; } @@ -82,7 +85,18 @@ namespace osu.Game.Skinning private static Texture getTexture(Skin skin, string name) => skin.GetTexture($"scorebar-{name}"); - public class LegacyOldStyleMarker : LegacyMarker + private static Color4 getFillColour(double hp) + { + if (hp < 0.2) + return Interpolation.ValueAt(0.2 - hp, Color4.Black, Color4.Red, 0, 0.2); + + if (hp < 0.5) + return Interpolation.ValueAt(0.5 - hp, Color4.White, Color4.Black, 0, 0.5); + + return Color4.White; + } + + public class LegacyOldStyleMarker : LegacyHealthPiece { private readonly Sprite sprite; @@ -92,6 +106,8 @@ namespace osu.Game.Skinning public LegacyOldStyleMarker(Skin skin) { + Origin = Anchor.Centre; + normalTexture = getTexture(skin, "ki"); dangerTexture = getTexture(skin, "kidanger"); superDangerTexture = getTexture(skin, "kidanger2"); @@ -120,39 +136,46 @@ namespace osu.Game.Skinning sprite.Texture = normalTexture; }); } + + public override void Flash(JudgementResult result) + { + this.ScaleTo(1.4f).Then().ScaleTo(1, 200, Easing.Out); + } } - public class LegacyNewStyleMarker : LegacyMarker + public class LegacyNewStyleMarker : LegacyHealthPiece { + private readonly Sprite sprite; + public LegacyNewStyleMarker(Skin skin) { + Origin = Anchor.Centre; + InternalChildren = new Drawable[] { - new Sprite + sprite = new Sprite { Texture = getTexture(skin, "marker"), Origin = Anchor.Centre, } }; } - } - public class LegacyMarker : CompositeDrawable, IHealthDisplay - { - public Bindable Current { get; } = new Bindable(); - - public LegacyMarker() + protected override void Update() { - Origin = Anchor.Centre; + base.Update(); + + sprite.Colour = getFillColour(Current.Value); + sprite.Blending = Current.Value < 0.5f ? BlendingParameters.Inherit : BlendingParameters.Additive; } - public void Flash(JudgementResult result) + public override void Flash(JudgementResult result) { this.ScaleTo(1.4f).Then().ScaleTo(1, 200, Easing.Out); } } - internal class LegacyOldStyleFill : CompositeDrawable + internal class LegacyOldStyleFill : LegacyHealthPiece { public LegacyOldStyleFill(Skin skin) { @@ -175,12 +198,33 @@ namespace osu.Game.Skinning } } - internal class LegacyNewStyleFill : Sprite + internal class LegacyNewStyleFill : LegacyHealthPiece { public LegacyNewStyleFill(Skin skin) { - Texture = getTexture(skin, "colour"); + InternalChild = new Sprite + { + Texture = getTexture(skin, "colour"), + }; + + Size = InternalChild.Size; Position = new Vector2(7.5f, 7.8f) * 1.6f; + Masking = true; + } + + protected override void Update() + { + base.Update(); + this.Colour = getFillColour(Current.Value); + } + } + + public class LegacyHealthPiece : CompositeDrawable, IHealthDisplay + { + public Bindable Current { get; } = new Bindable(); + + public virtual void Flash(JudgementResult result) + { } } } From 9572260e6da84c1a4b762a5a16376e4b4fcaafa0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 16 Oct 2020 17:09:00 +0900 Subject: [PATCH 1737/1782] Add bulge and explode support --- osu.Game/Skinning/LegacyHealthDisplay.cs | 114 ++++++++++++++--------- 1 file changed, 72 insertions(+), 42 deletions(-) diff --git a/osu.Game/Skinning/LegacyHealthDisplay.cs b/osu.Game/Skinning/LegacyHealthDisplay.cs index f44dd2b864..fece590f03 100644 --- a/osu.Game/Skinning/LegacyHealthDisplay.cs +++ b/osu.Game/Skinning/LegacyHealthDisplay.cs @@ -96,32 +96,25 @@ namespace osu.Game.Skinning return Color4.White; } - public class LegacyOldStyleMarker : LegacyHealthPiece + public class LegacyOldStyleMarker : LegacyMarker { - private readonly Sprite sprite; - private readonly Texture normalTexture; private readonly Texture dangerTexture; private readonly Texture superDangerTexture; public LegacyOldStyleMarker(Skin skin) { - Origin = Anchor.Centre; - normalTexture = getTexture(skin, "ki"); dangerTexture = getTexture(skin, "kidanger"); superDangerTexture = getTexture(skin, "kidanger2"); - - InternalChildren = new Drawable[] - { - sprite = new Sprite - { - Texture = getTexture(skin, "ki"), - Origin = Anchor.Centre, - } - }; } + public override Sprite CreateSprite() => new Sprite + { + Texture = normalTexture, + Origin = Anchor.Centre, + }; + protected override void LoadComplete() { base.LoadComplete(); @@ -129,49 +122,36 @@ namespace osu.Game.Skinning Current.BindValueChanged(hp => { if (hp.NewValue < 0.2f) - sprite.Texture = superDangerTexture; + Main.Texture = superDangerTexture; else if (hp.NewValue < 0.5f) - sprite.Texture = dangerTexture; + Main.Texture = dangerTexture; else - sprite.Texture = normalTexture; + Main.Texture = normalTexture; }); } - - public override void Flash(JudgementResult result) - { - this.ScaleTo(1.4f).Then().ScaleTo(1, 200, Easing.Out); - } } - public class LegacyNewStyleMarker : LegacyHealthPiece + public class LegacyNewStyleMarker : LegacyMarker { - private readonly Sprite sprite; + private readonly Skin skin; public LegacyNewStyleMarker(Skin skin) { - Origin = Anchor.Centre; - - InternalChildren = new Drawable[] - { - sprite = new Sprite - { - Texture = getTexture(skin, "marker"), - Origin = Anchor.Centre, - } - }; + this.skin = skin; } + public override Sprite CreateSprite() => new Sprite + { + Texture = getTexture(skin, "marker"), + Origin = Anchor.Centre, + }; + protected override void Update() { base.Update(); - sprite.Colour = getFillColour(Current.Value); - sprite.Blending = Current.Value < 0.5f ? BlendingParameters.Inherit : BlendingParameters.Additive; - } - - public override void Flash(JudgementResult result) - { - this.ScaleTo(1.4f).Then().ScaleTo(1, 200, Easing.Out); + Main.Colour = getFillColour(Current.Value); + Main.Blending = Current.Value < 0.5f ? BlendingParameters.Inherit : BlendingParameters.Additive; } } @@ -215,10 +195,60 @@ namespace osu.Game.Skinning protected override void Update() { base.Update(); - this.Colour = getFillColour(Current.Value); + Colour = getFillColour(Current.Value); } } + public abstract class LegacyMarker : LegacyHealthPiece + { + protected Sprite Main; + + private Sprite explode; + + protected LegacyMarker() + { + Origin = Anchor.Centre; + } + + [BackgroundDependencyLoader] + private void load() + { + InternalChildren = new Drawable[] + { + Main = CreateSprite(), + explode = CreateSprite().With(s => + { + s.Alpha = 0; + s.Blending = BlendingParameters.Additive; + }), + }; + } + + public abstract Sprite CreateSprite(); + + protected override void LoadComplete() + { + base.LoadComplete(); + + Current.BindValueChanged(val => + { + if (val.NewValue > val.OldValue) + bulgeMain(); + }); + } + + public override void Flash(JudgementResult result) + { + bulgeMain(); + + explode.FadeOutFromOne(120); + explode.ScaleTo(1).Then().ScaleTo(Current.Value > 0.5f ? 2 : 1.6f, 120); + } + + private void bulgeMain() => + Main.ScaleTo(1.4f).Then().ScaleTo(1, 200, Easing.Out); + } + public class LegacyHealthPiece : CompositeDrawable, IHealthDisplay { public Bindable Current { get; } = new Bindable(); From 77bf050a80733bba4b3a5a6434dc66dad933555e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 16 Oct 2020 17:24:43 +0900 Subject: [PATCH 1738/1782] Ignore IgnoreHits for flashiness --- 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 ac74dc22d3..c3de249bf8 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -319,7 +319,7 @@ namespace osu.Game.Screens.Play { processor.NewJudgement += judgement => { - if (judgement.IsHit) + if (judgement.IsHit && judgement.Type != HitResult.IgnoreHit) shd.Flash(judgement); }; } From a1892aa0a7472605ea389bc48db9a465d484f4ca Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 16 Oct 2020 17:24:56 +0900 Subject: [PATCH 1739/1782] Only additive flash explosions over the epic cutoff --- osu.Game/Skinning/LegacyHealthDisplay.cs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/osu.Game/Skinning/LegacyHealthDisplay.cs b/osu.Game/Skinning/LegacyHealthDisplay.cs index fece590f03..489e23ab7a 100644 --- a/osu.Game/Skinning/LegacyHealthDisplay.cs +++ b/osu.Game/Skinning/LegacyHealthDisplay.cs @@ -18,6 +18,8 @@ namespace osu.Game.Skinning { public class LegacyHealthDisplay : CompositeDrawable, IHealthDisplay { + private const double epic_cutoff = 0.5; + private readonly Skin skin; private LegacyHealthPiece fill; private LegacyHealthPiece marker; @@ -90,7 +92,7 @@ namespace osu.Game.Skinning if (hp < 0.2) return Interpolation.ValueAt(0.2 - hp, Color4.Black, Color4.Red, 0, 0.2); - if (hp < 0.5) + if (hp < epic_cutoff) return Interpolation.ValueAt(0.5 - hp, Color4.White, Color4.Black, 0, 0.5); return Color4.White; @@ -123,7 +125,7 @@ namespace osu.Game.Skinning { if (hp.NewValue < 0.2f) Main.Texture = superDangerTexture; - else if (hp.NewValue < 0.5f) + else if (hp.NewValue < epic_cutoff) Main.Texture = dangerTexture; else Main.Texture = normalTexture; @@ -151,7 +153,7 @@ namespace osu.Game.Skinning base.Update(); Main.Colour = getFillColour(Current.Value); - Main.Blending = Current.Value < 0.5f ? BlendingParameters.Inherit : BlendingParameters.Additive; + Main.Blending = Current.Value < epic_cutoff ? BlendingParameters.Inherit : BlendingParameters.Additive; } } @@ -241,8 +243,11 @@ namespace osu.Game.Skinning { bulgeMain(); + bool isEpic = Current.Value >= epic_cutoff; + + explode.Blending = isEpic ? BlendingParameters.Additive : BlendingParameters.Inherit; + explode.ScaleTo(1).Then().ScaleTo(isEpic ? 2 : 1.6f, 120); explode.FadeOutFromOne(120); - explode.ScaleTo(1).Then().ScaleTo(Current.Value > 0.5f ? 2 : 1.6f, 120); } private void bulgeMain() => From de60374c88a5521cfeeb6d5d0d942b0cd1a719c1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 16 Oct 2020 17:26:14 +0900 Subject: [PATCH 1740/1782] Remove unused using --- .../Visual/Gameplay/TestSceneSkinnableHealthDisplay.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHealthDisplay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHealthDisplay.cs index 181fc8ce98..e1b0820662 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHealthDisplay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHealthDisplay.cs @@ -10,7 +10,6 @@ using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Judgements; using osu.Game.Screens.Play; -using osuTK; namespace osu.Game.Tests.Visual.Gameplay { From 05f1017c282317d848265d15564ed8e48c7582f2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 16 Oct 2020 17:35:21 +0900 Subject: [PATCH 1741/1782] Fix lookup check not being updated to use prefix --- osu.Game/Skinning/LegacySkin.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index f5265f2d6e..06539d0f63 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -325,9 +325,9 @@ namespace osu.Game.Skinning return null; } - private const string score_font = "score"; + private string scorePrefix => GetConfig(LegacySkinConfiguration.LegacySetting.ScorePrefix)?.Value ?? "score"; - private bool hasScoreFont => this.HasFont(score_font); + private bool hasScoreFont => this.HasFont(scorePrefix); public override Drawable GetDrawableComponent(ISkinComponent component) { @@ -351,7 +351,6 @@ namespace osu.Game.Skinning case HUDSkinComponents.ScoreText: case HUDSkinComponents.AccuracyText: - string scorePrefix = GetConfig(LegacySkinConfiguration.LegacySetting.ScorePrefix)?.Value ?? "score"; int scoreOverlap = GetConfig(LegacySkinConfiguration.LegacySetting.ScoreOverlap)?.Value ?? -2; return new LegacySpriteText(this, scorePrefix) { From e9c4b67cf4688154c1b044b20335a772103e996f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 16 Oct 2020 17:35:35 +0900 Subject: [PATCH 1742/1782] Inline variable --- osu.Game/Skinning/LegacySkin.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 06539d0f63..22ddd45851 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -351,10 +351,9 @@ namespace osu.Game.Skinning case HUDSkinComponents.ScoreText: case HUDSkinComponents.AccuracyText: - int scoreOverlap = GetConfig(LegacySkinConfiguration.LegacySetting.ScoreOverlap)?.Value ?? -2; return new LegacySpriteText(this, scorePrefix) { - Spacing = new Vector2(-scoreOverlap, 0) + Spacing = new Vector2(-(GetConfig(LegacySkinConfiguration.LegacySetting.ScoreOverlap)?.Value ?? -2), 0) }; } From 3ce6d1fea103cf3c3d96df2f77684ffa6964cd4f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 16 Oct 2020 17:36:15 +0900 Subject: [PATCH 1743/1782] Remove unnecessary AccuracyText enum All elements use "score" regardless. --- osu.Game/Skinning/HUDSkinComponents.cs | 1 - osu.Game/Skinning/LegacyAccuracyCounter.cs | 2 +- osu.Game/Skinning/LegacySkin.cs | 1 - 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game/Skinning/HUDSkinComponents.cs b/osu.Game/Skinning/HUDSkinComponents.cs index 6ec575e106..cb35425981 100644 --- a/osu.Game/Skinning/HUDSkinComponents.cs +++ b/osu.Game/Skinning/HUDSkinComponents.cs @@ -9,6 +9,5 @@ namespace osu.Game.Skinning ScoreCounter, ScoreText, AccuracyCounter, - AccuracyText } } diff --git a/osu.Game/Skinning/LegacyAccuracyCounter.cs b/osu.Game/Skinning/LegacyAccuracyCounter.cs index 6c194a06d3..27d5aa4dbd 100644 --- a/osu.Game/Skinning/LegacyAccuracyCounter.cs +++ b/osu.Game/Skinning/LegacyAccuracyCounter.cs @@ -29,7 +29,7 @@ namespace osu.Game.Skinning [Resolved(canBeNull: true)] private HUDOverlay hud { get; set; } - protected sealed override OsuSpriteText CreateSpriteText() => skin?.GetDrawableComponent(new HUDSkinComponent(HUDSkinComponents.AccuracyText)) as OsuSpriteText ?? new OsuSpriteText(); + protected sealed override OsuSpriteText CreateSpriteText() => skin?.GetDrawableComponent(new HUDSkinComponent(HUDSkinComponents.ScoreText)) as OsuSpriteText ?? new OsuSpriteText(); protected override void Update() { diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 22ddd45851..cd9809a22b 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -350,7 +350,6 @@ namespace osu.Game.Skinning return new LegacyAccuracyCounter(this); case HUDSkinComponents.ScoreText: - case HUDSkinComponents.AccuracyText: return new LegacySpriteText(this, scorePrefix) { Spacing = new Vector2(-(GetConfig(LegacySkinConfiguration.LegacySetting.ScoreOverlap)?.Value ?? -2), 0) From 24b0a1b84b75b4ea10b92e54aaa6066b08e4452a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 16 Oct 2020 17:38:21 +0900 Subject: [PATCH 1744/1782] Switch to direct casts (we can be sure LegacySpriteText is present at this point) --- osu.Game/Skinning/LegacyAccuracyCounter.cs | 2 +- osu.Game/Skinning/LegacyScoreCounter.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Skinning/LegacyAccuracyCounter.cs b/osu.Game/Skinning/LegacyAccuracyCounter.cs index 27d5aa4dbd..a4a432ece2 100644 --- a/osu.Game/Skinning/LegacyAccuracyCounter.cs +++ b/osu.Game/Skinning/LegacyAccuracyCounter.cs @@ -29,7 +29,7 @@ namespace osu.Game.Skinning [Resolved(canBeNull: true)] private HUDOverlay hud { get; set; } - protected sealed override OsuSpriteText CreateSpriteText() => skin?.GetDrawableComponent(new HUDSkinComponent(HUDSkinComponents.ScoreText)) as OsuSpriteText ?? new OsuSpriteText(); + protected sealed override OsuSpriteText CreateSpriteText() => (OsuSpriteText)skin?.GetDrawableComponent(new HUDSkinComponent(HUDSkinComponents.ScoreText)); protected override void Update() { diff --git a/osu.Game/Skinning/LegacyScoreCounter.cs b/osu.Game/Skinning/LegacyScoreCounter.cs index 41bf35722b..39c90211f2 100644 --- a/osu.Game/Skinning/LegacyScoreCounter.cs +++ b/osu.Game/Skinning/LegacyScoreCounter.cs @@ -31,6 +31,6 @@ namespace osu.Game.Skinning Margin = new MarginPadding(10); } - protected sealed override OsuSpriteText CreateSpriteText() => skin?.GetDrawableComponent(new HUDSkinComponent(HUDSkinComponents.ScoreText)) as OsuSpriteText ?? new OsuSpriteText(); + protected sealed override OsuSpriteText CreateSpriteText() => (OsuSpriteText)skin.GetDrawableComponent(new HUDSkinComponent(HUDSkinComponents.ScoreText)); } } From a774de2270c722de1d00c4cdef37a7d9f38c2aeb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 16 Oct 2020 17:40:15 +0900 Subject: [PATCH 1745/1782] Also add support in LegacyComboCounter --- osu.Game/Screens/Play/HUD/LegacyComboCounter.cs | 3 ++- osu.Game/Skinning/HUDSkinComponents.cs | 1 + osu.Game/Skinning/LegacySkin.cs | 8 ++++++++ 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs b/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs index cc9398bc35..4784bca7dd 100644 --- a/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs +++ b/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs @@ -6,6 +6,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; +using osu.Game.Graphics.Sprites; using osu.Game.Skinning; using osuTK; @@ -246,6 +247,6 @@ namespace osu.Game.Screens.Play.HUD return difference * rolling_duration; } - private Drawable createSpriteText() => new LegacySpriteText(skin); + private OsuSpriteText createSpriteText() => (OsuSpriteText)skin.GetDrawableComponent(new HUDSkinComponent(HUDSkinComponents.ComboText)); } } diff --git a/osu.Game/Skinning/HUDSkinComponents.cs b/osu.Game/Skinning/HUDSkinComponents.cs index cb35425981..c5dead7858 100644 --- a/osu.Game/Skinning/HUDSkinComponents.cs +++ b/osu.Game/Skinning/HUDSkinComponents.cs @@ -8,6 +8,7 @@ namespace osu.Game.Skinning ComboCounter, ScoreCounter, ScoreText, + ComboText, AccuracyCounter, } } diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index cd9809a22b..db7307b3fe 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -327,6 +327,8 @@ namespace osu.Game.Skinning private string scorePrefix => GetConfig(LegacySkinConfiguration.LegacySetting.ScorePrefix)?.Value ?? "score"; + private string comboPrefix => GetConfig(LegacySkinConfiguration.LegacySetting.ComboPrefix)?.Value ?? "score"; + private bool hasScoreFont => this.HasFont(scorePrefix); public override Drawable GetDrawableComponent(ISkinComponent component) @@ -349,6 +351,12 @@ namespace osu.Game.Skinning case HUDSkinComponents.AccuracyCounter: return new LegacyAccuracyCounter(this); + case HUDSkinComponents.ComboText: + return new LegacySpriteText(this, comboPrefix) + { + Spacing = new Vector2(-(GetConfig(LegacySkinConfiguration.LegacySetting.ComboOverlap)?.Value ?? -2), 0) + }; + case HUDSkinComponents.ScoreText: return new LegacySpriteText(this, scorePrefix) { From 8a3bce3cc3efe0fbfd07bda9ad9fb3ec6c6b528c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 16 Oct 2020 18:19:09 +0900 Subject: [PATCH 1746/1782] Fix osu!catch showing two combo counters for legacy skins --- .../Skinning/CatchLegacySkinTransformer.cs | 19 ++++++++++++++++--- osu.Game/Screens/Play/Player.cs | 6 +++++- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Skinning/CatchLegacySkinTransformer.cs b/osu.Game.Rulesets.Catch/Skinning/CatchLegacySkinTransformer.cs index 916b4c5192..22db147e32 100644 --- a/osu.Game.Rulesets.Catch/Skinning/CatchLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Catch/Skinning/CatchLegacySkinTransformer.cs @@ -13,6 +13,11 @@ namespace osu.Game.Rulesets.Catch.Skinning { public class CatchLegacySkinTransformer : LegacySkinTransformer { + /// + /// For simplicity, let's use legacy combo font texture existence as a way to identify legacy skins from default. + /// + private bool providesComboCounter => this.HasFont(GetConfig(LegacySetting.ComboPrefix)?.Value ?? "score"); + public CatchLegacySkinTransformer(ISkinSource source) : base(source) { @@ -20,6 +25,16 @@ namespace osu.Game.Rulesets.Catch.Skinning public override Drawable GetDrawableComponent(ISkinComponent component) { + if (component is HUDSkinComponent hudComponent) + { + switch (hudComponent.Component) + { + case HUDSkinComponents.ComboCounter: + // catch may provide its own combo counter; hide the default. + return providesComboCounter ? Drawable.Empty() : null; + } + } + if (!(component is CatchSkinComponent catchSkinComponent)) return null; @@ -55,10 +70,8 @@ namespace osu.Game.Rulesets.Catch.Skinning this.GetAnimation("fruit-ryuuta", true, true, true); case CatchSkinComponents.CatchComboCounter: - var comboFont = GetConfig(LegacySetting.ComboPrefix)?.Value ?? "score"; - // For simplicity, let's use legacy combo font texture existence as a way to identify legacy skins from default. - if (this.HasFont(comboFont)) + if (providesComboCounter) return new LegacyCatchComboCounter(Source); break; diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 56b212291a..df0a52a0e8 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -221,8 +221,12 @@ namespace osu.Game.Screens.Play createGameplayComponents(Beatmap.Value, playableBeatmap) }); + // also give the HUD a ruleset container to allow rulesets to potentially override HUD elements (used to disable combo counters etc.) + // we may want to limit this in the future to disallow rulesets from outright replacing elements the user expects to be there. + var hudRulesetContainer = new SkinProvidingContainer(ruleset.CreateLegacySkinProvider(beatmapSkinProvider, playableBeatmap)); + // add the overlay components as a separate step as they proxy some elements from the above underlay/gameplay components. - GameplayClockContainer.Add(createOverlayComponents(Beatmap.Value)); + GameplayClockContainer.Add(hudRulesetContainer.WithChild(createOverlayComponents(Beatmap.Value))); if (!DrawableRuleset.AllowGameplayOverlays) { From 0437f7e7e982fef41611ab428c949744432a4a11 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 16 Oct 2020 18:22:18 +0900 Subject: [PATCH 1747/1782] Delete outdated test scene Has been replaced by the four new skinnable tests for each component. --- .../Visual/Gameplay/TestSceneScoreCounter.cs | 68 ------------------- 1 file changed, 68 deletions(-) delete mode 100644 osu.Game.Tests/Visual/Gameplay/TestSceneScoreCounter.cs diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneScoreCounter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneScoreCounter.cs deleted file mode 100644 index 34c657bf7f..0000000000 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneScoreCounter.cs +++ /dev/null @@ -1,68 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using NUnit.Framework; -using osu.Framework.Graphics; -using osu.Game.Graphics.UserInterface; -using osu.Game.Screens.Play.HUD; -using osuTK; - -namespace osu.Game.Tests.Visual.Gameplay -{ - [TestFixture] - public class TestSceneScoreCounter : OsuTestScene - { - public TestSceneScoreCounter() - { - int numerator = 0, denominator = 0; - - ScoreCounter score = new DefaultScoreCounter - { - Origin = Anchor.TopRight, - Anchor = Anchor.TopRight, - Margin = new MarginPadding(20), - }; - Add(score); - - LegacyComboCounter comboCounter = new LegacyComboCounter - { - Origin = Anchor.BottomLeft, - Anchor = Anchor.BottomLeft, - Margin = new MarginPadding(10), - }; - Add(comboCounter); - - PercentageCounter accuracyCounter = new PercentageCounter - { - Origin = Anchor.TopRight, - Anchor = Anchor.TopRight, - Position = new Vector2(-20, 60), - }; - Add(accuracyCounter); - - AddStep(@"Reset all", delegate - { - score.Current.Value = 0; - comboCounter.Current.Value = 0; - numerator = denominator = 0; - accuracyCounter.SetFraction(0, 0); - }); - - AddStep(@"Hit! :D", delegate - { - score.Current.Value += 300 + (ulong)(300.0 * (comboCounter.Current.Value > 0 ? comboCounter.Current.Value - 1 : 0) / 25.0); - comboCounter.Current.Value++; - numerator++; - denominator++; - accuracyCounter.SetFraction(numerator, denominator); - }); - - AddStep(@"miss...", delegate - { - comboCounter.Current.Value = 0; - denominator++; - accuracyCounter.SetFraction(numerator, denominator); - }); - } - } -} From cc1128314354b6393d5622f27dd032ac58e56968 Mon Sep 17 00:00:00 2001 From: Berkan Diler Date: Fri, 16 Oct 2020 11:27:02 +0200 Subject: [PATCH 1748/1782] Use string.Starts-/EndsWith char overloads --- osu.Game/Database/ArchiveModelManager.cs | 2 +- osu.Game/OsuGame.cs | 4 ++-- osu.Game/Screens/Select/FilterQueryParser.cs | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index b947056ebd..8bdc804311 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -593,7 +593,7 @@ namespace osu.Game.Database var fileInfos = new List(); string prefix = reader.Filenames.GetCommonPrefix(); - if (!(prefix.EndsWith("/", StringComparison.Ordinal) || prefix.EndsWith("\\", StringComparison.Ordinal))) + if (!(prefix.EndsWith('/') || prefix.EndsWith('\\'))) prefix = string.Empty; // import files to manager diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 56cced9c04..a0ddab702e 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -181,7 +181,7 @@ namespace osu.Game if (args?.Length > 0) { - var paths = args.Where(a => !a.StartsWith(@"-", StringComparison.Ordinal)).ToArray(); + var paths = args.Where(a => !a.StartsWith('-')).ToArray(); if (paths.Length > 0) Task.Run(() => Import(paths)); } @@ -289,7 +289,7 @@ namespace osu.Game public void OpenUrlExternally(string url) => waitForReady(() => externalLinkOpener, _ => { - if (url.StartsWith("/", StringComparison.Ordinal)) + if (url.StartsWith('/')) url = $"{API.Endpoint}{url}"; externalLinkOpener.OpenUrlExternally(url); diff --git a/osu.Game/Screens/Select/FilterQueryParser.cs b/osu.Game/Screens/Select/FilterQueryParser.cs index fa2beb2652..4b6b3be45c 100644 --- a/osu.Game/Screens/Select/FilterQueryParser.cs +++ b/osu.Game/Screens/Select/FilterQueryParser.cs @@ -80,9 +80,9 @@ namespace osu.Game.Screens.Select private static int getLengthScale(string value) => value.EndsWith("ms", StringComparison.Ordinal) ? 1 : - value.EndsWith("s", StringComparison.Ordinal) ? 1000 : - value.EndsWith("m", StringComparison.Ordinal) ? 60000 : - value.EndsWith("h", StringComparison.Ordinal) ? 3600000 : 1000; + 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 cbaad4eb56bf69a733b15c46f0332ec8b78f82cc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 16 Oct 2020 18:34:14 +0900 Subject: [PATCH 1749/1782] Adjust accuracy display to match stable --- osu.Game/Skinning/LegacyAccuracyCounter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Skinning/LegacyAccuracyCounter.cs b/osu.Game/Skinning/LegacyAccuracyCounter.cs index 9354b2b3bc..0d3adeb3ea 100644 --- a/osu.Game/Skinning/LegacyAccuracyCounter.cs +++ b/osu.Game/Skinning/LegacyAccuracyCounter.cs @@ -20,7 +20,7 @@ namespace osu.Game.Skinning Anchor = Anchor.TopRight; Origin = Anchor.TopRight; - Scale = new Vector2(0.75f); + Scale = new Vector2(0.6f); Margin = new MarginPadding(10); this.skin = skin; From 2ba8bc45fd1f68b54df68435eb0d88c3d28fff1c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 16 Oct 2020 18:37:24 +0900 Subject: [PATCH 1750/1782] Also add slight adjustment to score display --- osu.Game/Skinning/LegacyScoreCounter.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Skinning/LegacyScoreCounter.cs b/osu.Game/Skinning/LegacyScoreCounter.cs index f94bef6652..93b50e0ac1 100644 --- a/osu.Game/Skinning/LegacyScoreCounter.cs +++ b/osu.Game/Skinning/LegacyScoreCounter.cs @@ -5,6 +5,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; +using osuTK; namespace osu.Game.Skinning { @@ -28,6 +29,7 @@ namespace osu.Game.Skinning // base class uses int for display, but externally we bind to ScoreProcesssor as a double for now. Current.BindValueChanged(v => base.Current.Value = (int)v.NewValue); + Scale = new Vector2(0.96f); Margin = new MarginPadding(10); } From fe3a23750c6bbda7427e765aa93007b1ee13a6b7 Mon Sep 17 00:00:00 2001 From: Berkan Diler Date: Fri, 16 Oct 2020 11:52:29 +0200 Subject: [PATCH 1751/1782] Use char overloads for string methods --- osu.Game/Beatmaps/BeatmapInfo.cs | 2 +- osu.Game/Online/Chat/ChannelManager.cs | 2 +- osu.Game/Online/Chat/MessageFormatter.cs | 2 +- osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs | 2 +- osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs | 2 +- osu.Game/Skinning/GameplaySkinComponent.cs | 2 +- osu.Game/Skinning/HUDSkinComponent.cs | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index acab525821..8d1f0e59bf 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -98,7 +98,7 @@ namespace osu.Game.Beatmaps [JsonIgnore] public string StoredBookmarks { - get => string.Join(",", Bookmarks); + get => string.Join(',', Bookmarks); set { if (string.IsNullOrEmpty(value)) diff --git a/osu.Game/Online/Chat/ChannelManager.cs b/osu.Game/Online/Chat/ChannelManager.cs index f7ed57f207..16f46581c5 100644 --- a/osu.Game/Online/Chat/ChannelManager.cs +++ b/osu.Game/Online/Chat/ChannelManager.cs @@ -196,7 +196,7 @@ namespace osu.Game.Online.Chat if (target == null) return; - var parameters = text.Split(new[] { ' ' }, 2); + var parameters = text.Split(' ', 2); string command = parameters[0]; string content = parameters.Length == 2 ? parameters[1] : string.Empty; diff --git a/osu.Game/Online/Chat/MessageFormatter.cs b/osu.Game/Online/Chat/MessageFormatter.cs index 648e4a762b..d2a117876d 100644 --- a/osu.Game/Online/Chat/MessageFormatter.cs +++ b/osu.Game/Online/Chat/MessageFormatter.cs @@ -111,7 +111,7 @@ namespace osu.Game.Online.Chat public static LinkDetails GetLinkDetails(string url) { - var args = url.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries); + var args = url.Split('/', StringSplitOptions.RemoveEmptyEntries); args[0] = args[0].TrimEnd(':'); switch (args[0]) diff --git a/osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs index 946831d13b..ebee377a51 100644 --- a/osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs +++ b/osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs @@ -148,7 +148,7 @@ namespace osu.Game.Overlays.Profile.Header if (string.IsNullOrEmpty(content)) return false; // newlines could be contained in API returned user content. - content = content.Replace("\n", " "); + content = content.Replace('\n', ' '); bottomLinkContainer.AddIcon(icon, text => { diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs index 7dcbc52cea..44b22033dc 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs @@ -159,7 +159,7 @@ namespace osu.Game.Rulesets.Objects.Legacy { string[] ss = split[5].Split(':'); endTime = Math.Max(startTime, Parsing.ParseDouble(ss[0])); - readCustomSampleBanks(string.Join(":", ss.Skip(1)), bankInfo); + readCustomSampleBanks(string.Join(':', ss.Skip(1)), bankInfo); } result = CreateHold(pos, combo, comboOffset, endTime + Offset - startTime); diff --git a/osu.Game/Skinning/GameplaySkinComponent.cs b/osu.Game/Skinning/GameplaySkinComponent.cs index 2aa380fa90..80f6efc07a 100644 --- a/osu.Game/Skinning/GameplaySkinComponent.cs +++ b/osu.Game/Skinning/GameplaySkinComponent.cs @@ -18,6 +18,6 @@ namespace osu.Game.Skinning protected virtual string ComponentName => Component.ToString(); public string LookupName => - string.Join("/", new[] { "Gameplay", RulesetPrefix, ComponentName }.Where(s => !string.IsNullOrEmpty(s))); + string.Join('/', new[] { "Gameplay", RulesetPrefix, ComponentName }.Where(s => !string.IsNullOrEmpty(s))); } } diff --git a/osu.Game/Skinning/HUDSkinComponent.cs b/osu.Game/Skinning/HUDSkinComponent.cs index 041beb68f2..cc053421b7 100644 --- a/osu.Game/Skinning/HUDSkinComponent.cs +++ b/osu.Game/Skinning/HUDSkinComponent.cs @@ -17,6 +17,6 @@ namespace osu.Game.Skinning protected virtual string ComponentName => Component.ToString(); public string LookupName => - string.Join("/", new[] { "HUD", ComponentName }.Where(s => !string.IsNullOrEmpty(s))); + string.Join('/', new[] { "HUD", ComponentName }.Where(s => !string.IsNullOrEmpty(s))); } } From 2586990301e0da95f5ffd272f942b86859bb595a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 16 Oct 2020 23:19:34 +0900 Subject: [PATCH 1752/1782] 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 3df894fbcc..1d2cf22b28 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -51,7 +51,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 8b10f0a7f7..133855c6c4 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 88abbca73d..73faa8541e 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -71,7 +71,7 @@ - + From 81cc5e1c42e787f906fda4c6c881474c74f33aac Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 16 Oct 2020 23:31:01 +0900 Subject: [PATCH 1753/1782] Silence EF warning due to ordinal being unsupported --- osu.Game/Rulesets/RulesetStore.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/RulesetStore.cs b/osu.Game/Rulesets/RulesetStore.cs index c12d418771..c4639375da 100644 --- a/osu.Game/Rulesets/RulesetStore.cs +++ b/osu.Game/Rulesets/RulesetStore.cs @@ -100,7 +100,8 @@ namespace osu.Game.Rulesets { // todo: StartsWith can be changed to Equals on 2020-11-08 // This is to give users enough time to have their database use new abbreviated info). - if (context.RulesetInfo.FirstOrDefault(ri => ri.InstantiationInfo.StartsWith(r.RulesetInfo.InstantiationInfo, StringComparison.Ordinal)) == null) + // ReSharper disable once StringStartsWithIsCultureSpecific (silences EF warning of ordinal being unsupported) + if (context.RulesetInfo.FirstOrDefault(ri => ri.InstantiationInfo.StartsWith(r.RulesetInfo.InstantiationInfo)) == null) context.RulesetInfo.Add(r.RulesetInfo); } From 6385d5f3692b95bbdcea9819292ce221e2795999 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 16 Oct 2020 23:40:44 +0900 Subject: [PATCH 1754/1782] Replace with local tolist --- osu.Game/Rulesets/RulesetStore.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/RulesetStore.cs b/osu.Game/Rulesets/RulesetStore.cs index c4639375da..d422bca087 100644 --- a/osu.Game/Rulesets/RulesetStore.cs +++ b/osu.Game/Rulesets/RulesetStore.cs @@ -96,12 +96,13 @@ namespace osu.Game.Rulesets context.SaveChanges(); // add any other modes + var existingRulesets = context.RulesetInfo.ToList(); + foreach (var r in instances.Where(r => !(r is ILegacyRuleset))) { // todo: StartsWith can be changed to Equals on 2020-11-08 // This is to give users enough time to have their database use new abbreviated info). - // ReSharper disable once StringStartsWithIsCultureSpecific (silences EF warning of ordinal being unsupported) - if (context.RulesetInfo.FirstOrDefault(ri => ri.InstantiationInfo.StartsWith(r.RulesetInfo.InstantiationInfo)) == null) + if (existingRulesets.FirstOrDefault(ri => ri.InstantiationInfo.StartsWith(r.RulesetInfo.InstantiationInfo, StringComparison.Ordinal)) == null) context.RulesetInfo.Add(r.RulesetInfo); } From bba9a0b2fe5be16ae37338cddb307121de41aaf1 Mon Sep 17 00:00:00 2001 From: unknown Date: Sat, 17 Oct 2020 00:25:16 +0800 Subject: [PATCH 1755/1782] set sprite text anchor and origin to top right --- osu.Game/Skinning/LegacyScoreCounter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Skinning/LegacyScoreCounter.cs b/osu.Game/Skinning/LegacyScoreCounter.cs index e54c4e8eb4..fc7863fc4e 100644 --- a/osu.Game/Skinning/LegacyScoreCounter.cs +++ b/osu.Game/Skinning/LegacyScoreCounter.cs @@ -33,6 +33,6 @@ namespace osu.Game.Skinning Margin = new MarginPadding(10); } - protected sealed override OsuSpriteText CreateSpriteText() => (OsuSpriteText)skin.GetDrawableComponent(new HUDSkinComponent(HUDSkinComponents.ScoreText)); + protected sealed override OsuSpriteText CreateSpriteText() => ((OsuSpriteText)skin.GetDrawableComponent(new HUDSkinComponent(HUDSkinComponents.ScoreText))).With(s => s.Anchor = s.Origin = Anchor.TopRight); } } From b60dfc55b6625bea0f803c2ddd9d3c389d9ca7cb Mon Sep 17 00:00:00 2001 From: Lucas A Date: Fri, 16 Oct 2020 19:21:51 +0200 Subject: [PATCH 1756/1782] Apply review suggestions. --- osu.Android/GameplayScreenRotationLocker.cs | 6 +++--- osu.Android/OsuGameActivity.cs | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Android/GameplayScreenRotationLocker.cs b/osu.Android/GameplayScreenRotationLocker.cs index d1f4caba52..07cca8c2f1 100644 --- a/osu.Android/GameplayScreenRotationLocker.cs +++ b/osu.Android/GameplayScreenRotationLocker.cs @@ -17,14 +17,14 @@ namespace osu.Android private void load(OsuGame game) { localUserPlaying = game.LocalUserPlaying.GetBoundCopy(); - localUserPlaying.BindValueChanged(_ => updateLock()); + localUserPlaying.BindValueChanged(userPlaying => updateLock(userPlaying)); } - private void updateLock() + private void updateLock(ValueChangedEvent userPlaying) { OsuGameActivity.Activity.RunOnUiThread(() => { - OsuGameActivity.Activity.RequestedOrientation = localUserPlaying.Value ? ScreenOrientation.Locked : ScreenOrientation.FullUser; + OsuGameActivity.Activity.RequestedOrientation = userPlaying.NewValue ? ScreenOrientation.Locked : ScreenOrientation.FullUser; }); } } diff --git a/osu.Android/OsuGameActivity.cs b/osu.Android/OsuGameActivity.cs index c2b28f3de4..d4d2b83502 100644 --- a/osu.Android/OsuGameActivity.cs +++ b/osu.Android/OsuGameActivity.cs @@ -12,7 +12,7 @@ namespace osu.Android [Activity(Theme = "@android:style/Theme.NoTitleBar", MainLauncher = true, ScreenOrientation = ScreenOrientation.FullUser, SupportsPictureInPicture = false, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize, HardwareAccelerated = false)] public class OsuGameActivity : AndroidGameActivity { - internal static Activity Activity; + internal static Activity Activity { get; private set; } protected override Framework.Game CreateGame() => new OsuGameAndroid(); From e4463254d7feab088262dfb84826f4ce5a04ba43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 17 Oct 2020 15:29:30 +0200 Subject: [PATCH 1757/1782] Add test coverage for score counter alignment --- .../Visual/Gameplay/TestSceneSkinnableScoreCounter.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableScoreCounter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableScoreCounter.cs index 2d5003d1da..fc63340f20 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableScoreCounter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableScoreCounter.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Linq; using NUnit.Framework; +using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Testing; using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; @@ -43,5 +44,11 @@ namespace osu.Game.Tests.Visual.Gameplay s.Current.Value += 300; }); } + + [Test] + public void TestVeryLargeScore() + { + AddStep("set large score", () => scoreCounters.ForEach(counter => counter.Current.Value = 1_00_000_000)); + } } } From 0acc86f75724e6d1e5f348ee7878b7249be6078c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 17 Oct 2020 15:31:35 +0200 Subject: [PATCH 1758/1782] Split line for readability --- osu.Game/Skinning/LegacyScoreCounter.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Skinning/LegacyScoreCounter.cs b/osu.Game/Skinning/LegacyScoreCounter.cs index fc7863fc4e..5bffeff5a8 100644 --- a/osu.Game/Skinning/LegacyScoreCounter.cs +++ b/osu.Game/Skinning/LegacyScoreCounter.cs @@ -33,6 +33,8 @@ namespace osu.Game.Skinning Margin = new MarginPadding(10); } - protected sealed override OsuSpriteText CreateSpriteText() => ((OsuSpriteText)skin.GetDrawableComponent(new HUDSkinComponent(HUDSkinComponents.ScoreText))).With(s => s.Anchor = s.Origin = Anchor.TopRight); + protected sealed override OsuSpriteText CreateSpriteText() + => (OsuSpriteText)skin.GetDrawableComponent(new HUDSkinComponent(HUDSkinComponents.ScoreText)) + .With(s => s.Anchor = s.Origin = Anchor.TopRight); } } From a5b0307cfb472342bb56a08548b8245d7a8604be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 17 Oct 2020 15:36:21 +0200 Subject: [PATCH 1759/1782] Apply same fix to legacy accuracy counter --- osu.Game/Skinning/LegacyAccuracyCounter.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Skinning/LegacyAccuracyCounter.cs b/osu.Game/Skinning/LegacyAccuracyCounter.cs index 29d7046694..5eda374337 100644 --- a/osu.Game/Skinning/LegacyAccuracyCounter.cs +++ b/osu.Game/Skinning/LegacyAccuracyCounter.cs @@ -29,7 +29,9 @@ namespace osu.Game.Skinning [Resolved(canBeNull: true)] private HUDOverlay hud { get; set; } - protected sealed override OsuSpriteText CreateSpriteText() => (OsuSpriteText)skin?.GetDrawableComponent(new HUDSkinComponent(HUDSkinComponents.ScoreText)); + protected sealed override OsuSpriteText CreateSpriteText() + => (OsuSpriteText)skin?.GetDrawableComponent(new HUDSkinComponent(HUDSkinComponents.ScoreText)) + ?.With(s => s.Anchor = s.Origin = Anchor.TopRight); protected override void Update() { From 8aeeed9402e2de7d6de6e477adc69bf914ed6f0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 17 Oct 2020 15:47:37 +0200 Subject: [PATCH 1760/1782] Fix weird number formatting in test --- .../Visual/Gameplay/TestSceneSkinnableScoreCounter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableScoreCounter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableScoreCounter.cs index fc63340f20..e212ceeba7 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableScoreCounter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableScoreCounter.cs @@ -48,7 +48,7 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestVeryLargeScore() { - AddStep("set large score", () => scoreCounters.ForEach(counter => counter.Current.Value = 1_00_000_000)); + AddStep("set large score", () => scoreCounters.ForEach(counter => counter.Current.Value = 1_000_000_000)); } } } From 5b96f0156413e2694853b1a67557189fd4c7fb01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 17 Oct 2020 14:53:29 +0200 Subject: [PATCH 1761/1782] Fix key counter actions displaying out of order --- osu.Game/Rulesets/UI/RulesetInputManager.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/UI/RulesetInputManager.cs b/osu.Game/Rulesets/UI/RulesetInputManager.cs index f2ac61eaf4..07de2bf601 100644 --- a/osu.Game/Rulesets/UI/RulesetInputManager.cs +++ b/osu.Game/Rulesets/UI/RulesetInputManager.cs @@ -136,7 +136,11 @@ namespace osu.Game.Rulesets.UI KeyBindingContainer.Add(receptor); keyCounter.SetReceptor(receptor); - keyCounter.AddRange(KeyBindingContainer.DefaultKeyBindings.Select(b => b.GetAction()).Distinct().Select(b => new KeyCounterAction(b))); + keyCounter.AddRange(KeyBindingContainer.DefaultKeyBindings + .Select(b => b.GetAction()) + .Distinct() + .OrderBy(action => action) + .Select(action => new KeyCounterAction(action))); } public class ActionReceptor : KeyCounterDisplay.Receptor, IKeyBindingHandler From 9cd595800a5b826f8fac7a62c8eabfd7157a32f2 Mon Sep 17 00:00:00 2001 From: Lucas A Date: Sun, 18 Oct 2020 19:42:05 +0200 Subject: [PATCH 1762/1782] Subscribe to event handler instead. --- osu.Android/GameplayScreenRotationLocker.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Android/GameplayScreenRotationLocker.cs b/osu.Android/GameplayScreenRotationLocker.cs index 07cca8c2f1..d25e22a0c2 100644 --- a/osu.Android/GameplayScreenRotationLocker.cs +++ b/osu.Android/GameplayScreenRotationLocker.cs @@ -17,7 +17,7 @@ namespace osu.Android private void load(OsuGame game) { localUserPlaying = game.LocalUserPlaying.GetBoundCopy(); - localUserPlaying.BindValueChanged(userPlaying => updateLock(userPlaying)); + localUserPlaying.ValueChanged += updateLock; } private void updateLock(ValueChangedEvent userPlaying) From 371aecfca0856499cd0b014b4afab71f0bcd4b9f Mon Sep 17 00:00:00 2001 From: Lucas A Date: Sun, 18 Oct 2020 20:07:42 +0200 Subject: [PATCH 1763/1782] Fetch OsuGameActivity through DI instead. --- osu.Android/GameplayScreenRotationLocker.cs | 7 +++++-- osu.Android/OsuGameActivity.cs | 6 +----- osu.Android/OsuGameAndroid.cs | 10 ++++++++++ 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/osu.Android/GameplayScreenRotationLocker.cs b/osu.Android/GameplayScreenRotationLocker.cs index d25e22a0c2..fb471ceb78 100644 --- a/osu.Android/GameplayScreenRotationLocker.cs +++ b/osu.Android/GameplayScreenRotationLocker.cs @@ -13,6 +13,9 @@ namespace osu.Android { private Bindable localUserPlaying; + [Resolved] + private OsuGameActivity gameActivity { get; set; } + [BackgroundDependencyLoader] private void load(OsuGame game) { @@ -22,9 +25,9 @@ namespace osu.Android private void updateLock(ValueChangedEvent userPlaying) { - OsuGameActivity.Activity.RunOnUiThread(() => + gameActivity.RunOnUiThread(() => { - OsuGameActivity.Activity.RequestedOrientation = userPlaying.NewValue ? ScreenOrientation.Locked : ScreenOrientation.FullUser; + gameActivity.RequestedOrientation = userPlaying.NewValue ? ScreenOrientation.Locked : ScreenOrientation.FullUser; }); } } diff --git a/osu.Android/OsuGameActivity.cs b/osu.Android/OsuGameActivity.cs index d4d2b83502..7e250dce0e 100644 --- a/osu.Android/OsuGameActivity.cs +++ b/osu.Android/OsuGameActivity.cs @@ -12,14 +12,10 @@ namespace osu.Android [Activity(Theme = "@android:style/Theme.NoTitleBar", MainLauncher = true, ScreenOrientation = ScreenOrientation.FullUser, SupportsPictureInPicture = false, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize, HardwareAccelerated = false)] public class OsuGameActivity : AndroidGameActivity { - internal static Activity Activity { get; private set; } - - protected override Framework.Game CreateGame() => new OsuGameAndroid(); + protected override Framework.Game CreateGame() => new OsuGameAndroid(this); protected override void OnCreate(Bundle savedInstanceState) { - Activity = this; - // 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 consistent current directory on all devices the full path of the app data directory is set as the current directory. diff --git a/osu.Android/OsuGameAndroid.cs b/osu.Android/OsuGameAndroid.cs index 887a8395e3..21d6336b2c 100644 --- a/osu.Android/OsuGameAndroid.cs +++ b/osu.Android/OsuGameAndroid.cs @@ -4,6 +4,7 @@ using System; using Android.App; using Android.OS; +using osu.Framework.Allocation; using osu.Game; using osu.Game.Updater; @@ -11,6 +12,15 @@ namespace osu.Android { public class OsuGameAndroid : OsuGame { + [Cached] + private readonly OsuGameActivity gameActivity; + + public OsuGameAndroid(OsuGameActivity activity) + : base(null) + { + gameActivity = activity; + } + public override Version AssemblyVersion { get From cb1784a846901a15673575d25d2dcfc92ce85515 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 19 Oct 2020 14:05:28 +0900 Subject: [PATCH 1764/1782] Fix score displays using non-matching zero padding depending on user score display mode --- .../Graphics/UserInterface/RollingCounter.cs | 12 +++++-- .../Graphics/UserInterface/ScoreCounter.cs | 14 ++++---- osu.Game/Screens/Play/HUD/IScoreCounter.cs | 6 ++++ .../Screens/Play/HUD/SkinnableScoreCounter.cs | 32 +++++++++++++++++++ 4 files changed, 54 insertions(+), 10 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/RollingCounter.cs b/osu.Game/Graphics/UserInterface/RollingCounter.cs index 91a557094d..b96181416d 100644 --- a/osu.Game/Graphics/UserInterface/RollingCounter.cs +++ b/osu.Game/Graphics/UserInterface/RollingCounter.cs @@ -56,8 +56,7 @@ namespace osu.Game.Graphics.UserInterface return; displayedCount = value; - if (displayedCountSpriteText != null) - displayedCountSpriteText.Text = FormatCount(value); + UpdateDisplay(); } } @@ -73,10 +72,17 @@ namespace osu.Game.Graphics.UserInterface private void load() { displayedCountSpriteText = CreateSpriteText(); - displayedCountSpriteText.Text = FormatCount(DisplayedCount); + + UpdateDisplay(); Child = displayedCountSpriteText; } + protected void UpdateDisplay() + { + if (displayedCountSpriteText != null) + displayedCountSpriteText.Text = FormatCount(DisplayedCount); + } + protected override void LoadComplete() { base.LoadComplete(); diff --git a/osu.Game/Graphics/UserInterface/ScoreCounter.cs b/osu.Game/Graphics/UserInterface/ScoreCounter.cs index 17e5ceedb9..d75e49a4ce 100644 --- a/osu.Game/Graphics/UserInterface/ScoreCounter.cs +++ b/osu.Game/Graphics/UserInterface/ScoreCounter.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.Game.Graphics.Sprites; using osu.Game.Screens.Play.HUD; @@ -17,20 +18,19 @@ namespace osu.Game.Graphics.UserInterface /// public bool UseCommaSeparator { get; } - /// - /// How many leading zeroes the counter has. - /// - public uint LeadingZeroes { get; } + public Bindable RequiredDisplayDigits { get; } = new Bindable(); /// /// Displays score. /// /// How many leading zeroes the counter will have. /// Whether comma separators should be displayed. - protected ScoreCounter(uint leading = 0, bool useCommaSeparator = false) + protected ScoreCounter(int leading = 0, bool useCommaSeparator = false) { UseCommaSeparator = useCommaSeparator; - LeadingZeroes = leading; + + RequiredDisplayDigits.Value = leading; + RequiredDisplayDigits.BindValueChanged(_ => UpdateDisplay()); } protected override double GetProportionalDuration(double currentValue, double newValue) @@ -40,7 +40,7 @@ namespace osu.Game.Graphics.UserInterface protected override string FormatCount(double count) { - string format = new string('0', (int)LeadingZeroes); + string format = new string('0', RequiredDisplayDigits.Value); if (UseCommaSeparator) { diff --git a/osu.Game/Screens/Play/HUD/IScoreCounter.cs b/osu.Game/Screens/Play/HUD/IScoreCounter.cs index 2d39a64cfe..7f5e81d5ef 100644 --- a/osu.Game/Screens/Play/HUD/IScoreCounter.cs +++ b/osu.Game/Screens/Play/HUD/IScoreCounter.cs @@ -15,5 +15,11 @@ namespace osu.Game.Screens.Play.HUD /// The current score to be displayed. /// Bindable Current { get; } + + /// + /// The number of digits required to display most sane scores. + /// This may be exceeded in very rare cases, but is useful to pad or space the display to avoid it jumping around. + /// + Bindable RequiredDisplayDigits { get; } } } diff --git a/osu.Game/Screens/Play/HUD/SkinnableScoreCounter.cs b/osu.Game/Screens/Play/HUD/SkinnableScoreCounter.cs index a442ad0d9a..b46f5684b1 100644 --- a/osu.Game/Screens/Play/HUD/SkinnableScoreCounter.cs +++ b/osu.Game/Screens/Play/HUD/SkinnableScoreCounter.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 System; +using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Game.Configuration; +using osu.Game.Rulesets.Scoring; using osu.Game.Skinning; namespace osu.Game.Screens.Play.HUD @@ -10,12 +14,38 @@ namespace osu.Game.Screens.Play.HUD { public Bindable Current { get; } = new Bindable(); + private Bindable scoreDisplayMode; + + public Bindable RequiredDisplayDigits { get; } = new Bindable(); + public SkinnableScoreCounter() : base(new HUDSkinComponent(HUDSkinComponents.ScoreCounter), _ => new DefaultScoreCounter()) { CentreComponent = false; } + [BackgroundDependencyLoader] + private void load(OsuConfigManager config) + { + scoreDisplayMode = config.GetBindable(OsuSetting.ScoreDisplayMode); + scoreDisplayMode.BindValueChanged(scoreMode => + { + switch (scoreMode.NewValue) + { + case ScoringMode.Standardised: + RequiredDisplayDigits.Value = 6; + break; + + case ScoringMode.Classic: + RequiredDisplayDigits.Value = 8; + break; + + default: + throw new ArgumentOutOfRangeException(nameof(scoreMode)); + } + }, true); + } + private IScoreCounter skinnedCounter; protected override void SkinChanged(ISkinSource skin, bool allowFallback) @@ -23,7 +53,9 @@ namespace osu.Game.Screens.Play.HUD base.SkinChanged(skin, allowFallback); skinnedCounter = Drawable as IScoreCounter; + skinnedCounter?.Current.BindTo(Current); + skinnedCounter?.RequiredDisplayDigits.BindTo(RequiredDisplayDigits); } } } From e3b47083fc85ebc324575645b8d4c33c5661253f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 19 Oct 2020 14:05:41 +0900 Subject: [PATCH 1765/1782] Add "scoring" as keyword to more easily find score display mode setting --- .../Overlays/Settings/Sections/Gameplay/GeneralSettings.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs index 73968761e2..66b3b8c4ca 100644 --- a/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs @@ -76,7 +76,8 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay new SettingsEnumDropdown { LabelText = "Score display mode", - Current = config.GetBindable(OsuSetting.ScoreDisplayMode) + Current = config.GetBindable(OsuSetting.ScoreDisplayMode), + Keywords = new[] { "scoring" } } }; From cdb649476b8bf327c9ca561a26ce9ffabd660e32 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 19 Oct 2020 14:33:53 +0900 Subject: [PATCH 1766/1782] Allow legacy text to display fixed width correctly --- osu.Game/Skinning/LegacySpriteText.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Skinning/LegacySpriteText.cs b/osu.Game/Skinning/LegacySpriteText.cs index 8394657b1c..d7a3975c72 100644 --- a/osu.Game/Skinning/LegacySpriteText.cs +++ b/osu.Game/Skinning/LegacySpriteText.cs @@ -5,6 +5,7 @@ using System.Threading.Tasks; using osu.Framework.Graphics.Sprites; using osu.Framework.Text; using osu.Game.Graphics.Sprites; +using osuTK; namespace osu.Game.Skinning { @@ -17,11 +18,12 @@ namespace osu.Game.Skinning Shadow = false; UseFullGlyphHeight = false; - Font = new FontUsage(font, 1); + Font = new FontUsage(font, 1, fixedWidth: true); glyphStore = new LegacyGlyphStore(skin); } - protected override TextBuilder CreateTextBuilder(ITexturedGlyphLookupStore store) => base.CreateTextBuilder(glyphStore); + protected override TextBuilder CreateTextBuilder(ITexturedGlyphLookupStore store) => + new TextBuilder(glyphStore, Font, MaxWidth, UseFullGlyphHeight, Vector2.Zero, Spacing, CharactersBacking, neverFixedWidthCharacters: new[] { ',', '.', '%', 'x' }, fixedWidthCalculationCharacter: '5'); private class LegacyGlyphStore : ITexturedGlyphLookupStore { From ba99c5c134d5563725c46b57b6854befa2c91ac8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 19 Oct 2020 14:39:02 +0900 Subject: [PATCH 1767/1782] Remove rolling delay on default combo counter --- osu.Game/Screens/Play/HUD/DefaultComboCounter.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/DefaultComboCounter.cs b/osu.Game/Screens/Play/HUD/DefaultComboCounter.cs index a5c33f6dbe..63e7a88550 100644 --- a/osu.Game/Screens/Play/HUD/DefaultComboCounter.cs +++ b/osu.Game/Screens/Play/HUD/DefaultComboCounter.cs @@ -15,8 +15,6 @@ namespace osu.Game.Screens.Play.HUD { private readonly Vector2 offset = new Vector2(20, 5); - protected override double RollingDuration => 750; - [Resolved(canBeNull: true)] private HUDOverlay hud { get; set; } From 39cf27637e157ec13d8378ba89999d8a64809fd2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 19 Oct 2020 14:59:03 +0900 Subject: [PATCH 1768/1782] Update to use virtual methods instead of reconstructing TextBuilder --- osu.Game/Skinning/LegacySpriteText.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game/Skinning/LegacySpriteText.cs b/osu.Game/Skinning/LegacySpriteText.cs index d7a3975c72..5d0e312f7c 100644 --- a/osu.Game/Skinning/LegacySpriteText.cs +++ b/osu.Game/Skinning/LegacySpriteText.cs @@ -5,7 +5,6 @@ using System.Threading.Tasks; using osu.Framework.Graphics.Sprites; using osu.Framework.Text; using osu.Game.Graphics.Sprites; -using osuTK; namespace osu.Game.Skinning { @@ -13,6 +12,10 @@ namespace osu.Game.Skinning { private readonly LegacyGlyphStore glyphStore; + protected override char FixedWidthReferenceCharacter => '5'; + + protected override char[] FixedWidthExcludeCharacters => new[] { ',', '.', '%', 'x' }; + public LegacySpriteText(ISkin skin, string font = "score") { Shadow = false; @@ -22,8 +25,7 @@ namespace osu.Game.Skinning glyphStore = new LegacyGlyphStore(skin); } - protected override TextBuilder CreateTextBuilder(ITexturedGlyphLookupStore store) => - new TextBuilder(glyphStore, Font, MaxWidth, UseFullGlyphHeight, Vector2.Zero, Spacing, CharactersBacking, neverFixedWidthCharacters: new[] { ',', '.', '%', 'x' }, fixedWidthCalculationCharacter: '5'); + protected override TextBuilder CreateTextBuilder(ITexturedGlyphLookupStore store) => base.CreateTextBuilder(glyphStore); private class LegacyGlyphStore : ITexturedGlyphLookupStore { From 7ed862edd7f81ff83a8b1dc98bed01a402977af6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 19 Oct 2020 15:08:49 +0900 Subject: [PATCH 1769/1782] Add comment about migration code --- osu.Game.Tournament/IO/TournamentStorage.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game.Tournament/IO/TournamentStorage.cs b/osu.Game.Tournament/IO/TournamentStorage.cs index 6505135b42..49906eff6d 100644 --- a/osu.Game.Tournament/IO/TournamentStorage.cs +++ b/osu.Game.Tournament/IO/TournamentStorage.cs @@ -36,6 +36,10 @@ namespace osu.Game.Tournament.IO public override void Migrate(Storage newStorage) { + // this migration only happens once on moving to the per-tournament storage system. + // listed files are those known at that point in time. + // this can be removed at some point in the future (6 months obsoletion would mean 2021-04-19) + var source = new DirectoryInfo(storage.GetFullPath("tournament")); var destination = new DirectoryInfo(newStorage.GetFullPath(".")); @@ -50,6 +54,7 @@ namespace osu.Game.Tournament.IO moveFileIfExists("drawings.txt", destination); moveFileIfExists("drawings_results.txt", destination); moveFileIfExists("drawings.ini", destination); + ChangeTargetStorage(newStorage); storageConfig.Set(StorageConfig.CurrentTournament, default_tournament); storageConfig.Save(); From 9c566e7ffbe2c289306a17d494f82802d8fc4ec7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 19 Oct 2020 15:34:46 +0900 Subject: [PATCH 1770/1782] Update tests to use correct parameters of CleanRunGameHost --- .../NonVisual/CustomTourneyDirectoryTest.cs | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs b/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs index b75a9a6929..567d9f0d62 100644 --- a/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs +++ b/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs @@ -20,14 +20,14 @@ namespace osu.Game.Tournament.Tests.NonVisual [Test] public void TestDefaultDirectory() { - using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestDefaultDirectory))) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) { try { var osu = loadOsu(host); var storage = osu.Dependencies.Get(); - var defaultStorage = Path.Combine(tournamentBasePath(nameof(TestDefaultDirectory)), "default"); - Assert.That(storage.GetFullPath("."), Is.EqualTo(defaultStorage)); + + Assert.That(storage.GetFullPath("."), Is.EqualTo(Path.Combine(host.Storage.GetFullPath("."), "tournaments", "default"))); } finally { @@ -39,7 +39,7 @@ namespace osu.Game.Tournament.Tests.NonVisual [Test] public void TestCustomDirectory() { - using (HeadlessGameHost host = new HeadlessGameHost(nameof(TestCustomDirectory))) + using (HeadlessGameHost host = new HeadlessGameHost(nameof(TestCustomDirectory))) // don't use clean run as we are writing a config file. { string osuDesktopStorage = basePath(nameof(TestCustomDirectory)); const string custom_tournament = "custom"; @@ -58,7 +58,7 @@ namespace osu.Game.Tournament.Tests.NonVisual storage = osu.Dependencies.Get(); - Assert.That(storage.GetFullPath("."), Is.EqualTo(Path.Combine(tournamentBasePath(nameof(TestCustomDirectory)), custom_tournament))); + Assert.That(storage.GetFullPath("."), Is.EqualTo(Path.Combine(host.Storage.GetFullPath("."), "tournaments", custom_tournament))); } finally { @@ -70,7 +70,7 @@ namespace osu.Game.Tournament.Tests.NonVisual [Test] public void TestMigration() { - using (HeadlessGameHost host = new HeadlessGameHost(nameof(TestMigration))) + using (HeadlessGameHost host = new HeadlessGameHost(nameof(TestMigration))) // don't use clean run as we are writing test files for migration. { string osuRoot = basePath(nameof(TestMigration)); string configFile = Path.Combine(osuRoot, "tournament.ini"); @@ -115,7 +115,7 @@ namespace osu.Game.Tournament.Tests.NonVisual var storage = osu.Dependencies.Get(); - var migratedPath = Path.Combine(tournamentBasePath(nameof(TestMigration)), "default"); + string migratedPath = Path.Combine(host.Storage.GetFullPath("."), "tournaments", "default"); videosPath = Path.Combine(migratedPath, "videos"); modsPath = Path.Combine(migratedPath, "mods"); @@ -165,7 +165,5 @@ namespace osu.Game.Tournament.Tests.NonVisual } private string basePath(string testInstance) => Path.Combine(RuntimeInfo.StartupDirectory, "headless", testInstance); - - private string tournamentBasePath(string testInstance) => Path.Combine(basePath(testInstance), "tournaments"); } } From 31f6051db9504e4cef04fcc7fdd819a7f0827343 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 19 Oct 2020 15:36:27 +0900 Subject: [PATCH 1771/1782] Add missing xmldoc --- osu.Game/IO/MigratableStorage.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/osu.Game/IO/MigratableStorage.cs b/osu.Game/IO/MigratableStorage.cs index 21087d7dc6..1b76725b04 100644 --- a/osu.Game/IO/MigratableStorage.cs +++ b/osu.Game/IO/MigratableStorage.cs @@ -14,7 +14,14 @@ namespace osu.Game.IO /// public abstract class MigratableStorage : WrappedStorage { + /// + /// A relative list of directory paths which should not be migrated. + /// public virtual string[] IgnoreDirectories => Array.Empty(); + + /// + /// A relative list of file paths which should not be migrated. + /// public virtual string[] IgnoreFiles => Array.Empty(); protected MigratableStorage(Storage storage, string subPath = null) From 3f41003d355c82b28fffd11f27394283f2e67847 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 19 Oct 2020 15:48:15 +0900 Subject: [PATCH 1772/1782] Move video store out of TournamentStorage There was no reason it should be nested inside. --- osu.Game.Tournament/Components/TourneyVideo.cs | 5 ++--- osu.Game.Tournament/IO/TournamentStorage.cs | 2 -- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tournament/Components/TourneyVideo.cs b/osu.Game.Tournament/Components/TourneyVideo.cs index 794b72b3a9..2709580385 100644 --- a/osu.Game.Tournament/Components/TourneyVideo.cs +++ b/osu.Game.Tournament/Components/TourneyVideo.cs @@ -7,7 +7,6 @@ using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Video; -using osu.Framework.Platform; using osu.Framework.Timing; using osu.Game.Graphics; using osu.Game.Tournament.IO; @@ -28,9 +27,9 @@ namespace osu.Game.Tournament.Components } [BackgroundDependencyLoader] - private void load(Storage storage) + private void load(TournamentVideoResourceStore storage) { - var stream = (storage as TournamentStorage)?.VideoStore.GetStream(filename); + var stream = storage.GetStream(filename); if (stream != null) { diff --git a/osu.Game.Tournament/IO/TournamentStorage.cs b/osu.Game.Tournament/IO/TournamentStorage.cs index 49906eff6d..2e8a6ce667 100644 --- a/osu.Game.Tournament/IO/TournamentStorage.cs +++ b/osu.Game.Tournament/IO/TournamentStorage.cs @@ -14,7 +14,6 @@ namespace osu.Game.Tournament.IO private const string default_tournament = "default"; private readonly Storage storage; private readonly TournamentStorageManager storageConfig; - public TournamentVideoResourceStore VideoStore { get; } public TournamentStorage(Storage storage) : base(storage.GetStorageForDirectory("tournaments"), string.Empty) @@ -30,7 +29,6 @@ namespace osu.Game.Tournament.IO else Migrate(UnderlyingStorage.GetStorageForDirectory(default_tournament)); - VideoStore = new TournamentVideoResourceStore(this); Logger.Log("Using tournament storage: " + GetFullPath(string.Empty)); } From daceb0c04991d0b103ed81434cd05db4decd64f5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 19 Oct 2020 15:48:33 +0900 Subject: [PATCH 1773/1782] Fix texture store not being initialised correctly Without this change flags/mods would not work as expected. The video store was being added as the texture store incorrectly. --- osu.Game.Tournament/TournamentGameBase.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tournament/TournamentGameBase.cs b/osu.Game.Tournament/TournamentGameBase.cs index 6a533f96d8..dbda6aa023 100644 --- a/osu.Game.Tournament/TournamentGameBase.cs +++ b/osu.Game.Tournament/TournamentGameBase.cs @@ -40,8 +40,9 @@ namespace osu.Game.Tournament Resources.AddStore(new DllResourceStore(typeof(TournamentGameBase).Assembly)); dependencies.CacheAs(storage = new TournamentStorage(baseStorage)); + dependencies.Cache(new TournamentVideoResourceStore(storage)); - Textures.AddStore(new TextureLoaderStore(storage.VideoStore)); + Textures.AddStore(new TextureLoaderStore(new StorageBackedResourceStore(storage))); readBracket(); From f597572d739eeb71ac1a086cbf353e7233d236e3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 19 Oct 2020 16:02:39 +0900 Subject: [PATCH 1774/1782] Add comment with reasoning for TopRight anchor --- osu.Game/Graphics/UserInterfaceV2/LabelledDrawable.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Graphics/UserInterfaceV2/LabelledDrawable.cs b/osu.Game/Graphics/UserInterfaceV2/LabelledDrawable.cs index af0043436a..ec68223a3d 100644 --- a/osu.Game/Graphics/UserInterfaceV2/LabelledDrawable.cs +++ b/osu.Game/Graphics/UserInterfaceV2/LabelledDrawable.cs @@ -73,6 +73,7 @@ namespace osu.Game.Graphics.UserInterfaceV2 }, new Container { + // top right works better when the vertical height of the component changes smoothly (avoids weird layout animations). Anchor = Anchor.TopRight, Origin = Anchor.TopRight, RelativeSizeAxes = Axes.X, From 401dd2db37a55460a8f1332daa3d174e59f14a41 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 19 Oct 2020 16:55:00 +0900 Subject: [PATCH 1775/1782] 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 1d2cf22b28..2d531cf01e 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,6 +52,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 133855c6c4..de7bde824f 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -24,7 +24,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 73faa8541e..9c22dec330 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -80,7 +80,7 @@ - + From 79a17b23715b9fe0982d11975494de0cb43413da Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 19 Oct 2020 16:57:08 +0900 Subject: [PATCH 1776/1782] Reapply waveform colour fix --- osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs index be3bca3242..9aff4ddf8f 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs @@ -81,7 +81,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline waveform = new WaveformGraph { RelativeSizeAxes = Axes.Both, - Colour = colours.Blue.Opacity(0.2f), + BaseColour = colours.Blue.Opacity(0.2f), LowColour = colours.BlueLighter, MidColour = colours.BlueDark, HighColour = colours.BlueDarker, From cd7c3021caf54210b5f2e3d80fb54d8d9a2917f8 Mon Sep 17 00:00:00 2001 From: Lucas A Date: Mon, 19 Oct 2020 10:01:24 +0200 Subject: [PATCH 1777/1782] Trigger lock update on loading. --- osu.Android/GameplayScreenRotationLocker.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Android/GameplayScreenRotationLocker.cs b/osu.Android/GameplayScreenRotationLocker.cs index fb471ceb78..25bd659a5d 100644 --- a/osu.Android/GameplayScreenRotationLocker.cs +++ b/osu.Android/GameplayScreenRotationLocker.cs @@ -20,7 +20,7 @@ namespace osu.Android private void load(OsuGame game) { localUserPlaying = game.LocalUserPlaying.GetBoundCopy(); - localUserPlaying.ValueChanged += updateLock; + localUserPlaying.BindValueChanged(updateLock, true); } private void updateLock(ValueChangedEvent userPlaying) From 6d22f0e1962dd0530b473aca1105d6a6b01258c6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 19 Oct 2020 17:15:13 +0900 Subject: [PATCH 1778/1782] Use existing copy method and update xmldoc --- osu.Game/Online/Multiplayer/Room.cs | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/osu.Game/Online/Multiplayer/Room.cs b/osu.Game/Online/Multiplayer/Room.cs index 5feebe8da1..9a21543b2e 100644 --- a/osu.Game/Online/Multiplayer/Room.cs +++ b/osu.Game/Online/Multiplayer/Room.cs @@ -104,21 +104,17 @@ namespace osu.Game.Online.Multiplayer public readonly Bindable Position = new Bindable(-1); /// - /// Create a copy of this room, without information specific to it, such as Room ID or host + /// Create a copy of this room without online information. + /// Should be used to create a local copy of a room for submitting in the future. /// public Room CreateCopy() { - Room newRoom = new Room - { - Name = { Value = Name.Value }, - Availability = { Value = Availability.Value }, - Type = { Value = Type.Value }, - MaxParticipants = { Value = MaxParticipants.Value } - }; + var copy = new Room(); - newRoom.Playlist.AddRange(Playlist); + copy.CopyFrom(this); + copy.RoomID.Value = null; - return newRoom; + return copy; } public void CopyFrom(Room other) From 437ca91b9441dc5891b39f81301b2c2af0989bf3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 19 Oct 2020 17:15:35 +0900 Subject: [PATCH 1779/1782] Use DI to open the copy rather than passing in an ugly action --- .../Screens/Multi/Lounge/Components/DrawableRoom.cs | 10 +++++++--- .../Screens/Multi/Lounge/Components/RoomsContainer.cs | 8 -------- osu.Game/Screens/Multi/Multiplayer.cs | 11 ++++++----- 3 files changed, 13 insertions(+), 16 deletions(-) diff --git a/osu.Game/Screens/Multi/Lounge/Components/DrawableRoom.cs b/osu.Game/Screens/Multi/Lounge/Components/DrawableRoom.cs index db75df6054..01a85382e4 100644 --- a/osu.Game/Screens/Multi/Lounge/Components/DrawableRoom.cs +++ b/osu.Game/Screens/Multi/Lounge/Components/DrawableRoom.cs @@ -38,11 +38,12 @@ namespace osu.Game.Screens.Multi.Lounge.Components public event Action StateChanged; - public Action DuplicateRoom; - private readonly Box selectionBox; private CachedModelDependencyContainer dependencies; + [Resolved(canBeNull: true)] + private Multiplayer multiplayer { get; set; } + [Resolved] private BeatmapManager beatmaps { get; set; } @@ -239,7 +240,10 @@ namespace osu.Game.Screens.Multi.Lounge.Components public MenuItem[] ContextMenuItems => new MenuItem[] { - new OsuMenuItem("Create copy", MenuItemType.Standard, DuplicateRoom) + new OsuMenuItem("Create copy", MenuItemType.Standard, () => + { + multiplayer?.CreateRoom(Room.CreateCopy()); + }) }; } } diff --git a/osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs b/osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs index d9af38d19e..60c6aa1d8a 100644 --- a/osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs +++ b/osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs @@ -111,14 +111,6 @@ namespace osu.Game.Screens.Multi.Lounge.Components { roomFlow.Add(new DrawableRoom(room) { - DuplicateRoom = () => - { - Room newRoom = room.CreateCopy(); - if (!newRoom.Name.Value.StartsWith("Copy of ")) - newRoom.Name.Value = $"Copy of {room.Name.Value}"; - - loungeSubScreen?.Open(newRoom); - }, Action = () => { if (room == selectedRoom.Value) diff --git a/osu.Game/Screens/Multi/Multiplayer.cs b/osu.Game/Screens/Multi/Multiplayer.cs index cdaeebefb7..27f774e9ec 100644 --- a/osu.Game/Screens/Multi/Multiplayer.cs +++ b/osu.Game/Screens/Multi/Multiplayer.cs @@ -134,7 +134,7 @@ namespace osu.Game.Screens.Multi { Anchor = Anchor.TopRight, Origin = Anchor.TopRight, - Action = createRoom + Action = () => CreateRoom() }, roomManager = new RoomManager() } @@ -289,10 +289,11 @@ namespace osu.Game.Screens.Multi logo.Delay(WaveContainer.DISAPPEAR_DURATION / 2).FadeOut(); } - private void createRoom() - { - loungeSubScreen.Open(new Room { Name = { Value = $"{api.LocalUser}'s awesome room" } }); - } + /// + /// Create a new room. + /// + /// An optional template to use when creating the room. + public void CreateRoom(Room room = null) => loungeSubScreen.Open(room ?? new Room { Name = { Value = $"{api.LocalUser}'s awesome room" } }); private void beginHandlingTrack() { From 4024b44a53b0642bb15a18871caa9ecd80342925 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 19 Oct 2020 17:41:21 +0900 Subject: [PATCH 1780/1782] Fix unsafe manipulation of parent's children from child --- osu.Game.Rulesets.Catch/UI/Catcher.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch/UI/Catcher.cs b/osu.Game.Rulesets.Catch/UI/Catcher.cs index 9289a6162c..a221ca7966 100644 --- a/osu.Game.Rulesets.Catch/UI/Catcher.cs +++ b/osu.Game.Rulesets.Catch/UI/Catcher.cs @@ -145,11 +145,19 @@ namespace osu.Game.Rulesets.Catch.UI } }; - trailsTarget.Add(trails = new CatcherTrailDisplay(this)); + trails = new CatcherTrailDisplay(this); updateCatcher(); } + protected override void LoadComplete() + { + base.LoadComplete(); + + // don't add in above load as we may potentially modify a parent in an unsafe manner. + trailsTarget.Add(trails); + } + /// /// Creates proxied content to be displayed beneath hitobjects. /// From 28eae5d26b0a1e8b69cfa4dbf7ea6b5f03f62b9c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 19 Oct 2020 19:03:22 +0900 Subject: [PATCH 1781/1782] Fix migration test failures due to finalizer disposal of LocalConfigManager --- osu.Game/OsuGameBase.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 84766f196a..2d609668af 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -371,8 +371,10 @@ namespace osu.Game protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); + RulesetStore?.Dispose(); BeatmapManager?.Dispose(); + LocalConfig?.Dispose(); contextFactory.FlushConnections(); } From a50ca0a1edba6a40af2ad24cc30c1abb86a6a95d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 19 Oct 2020 20:00:17 +0900 Subject: [PATCH 1782/1782] Fix osu!catch test failures due to trying to retrieve container too early --- osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs index 1e708cce4b..1b8368794c 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs @@ -123,7 +123,10 @@ namespace osu.Game.Rulesets.Catch.Tests Origin = Anchor.Centre, Scale = new Vector2(4f), }, skin); + }); + AddStep("get trails container", () => + { trails = catcherArea.OfType().Single(); catcherArea.MovableCatcher.SetHyperDashState(2); });
/// If true, the roll-up duration will be proportional to change in value. @@ -46,29 +47,49 @@ namespace osu.Game.Graphics.UserInterface public virtual T DisplayedCount { get => displayedCount; - set { if (EqualityComparer.Default.Equals(displayedCount, value)) return; displayedCount = value; - DisplayedCountSpriteText.Text = FormatCount(value); + if (displayedCountSpriteText != null) + displayedCountSpriteText.Text = FormatCount(value); } } public abstract void Increment(T amount); + private float textSize = 40f; + public float TextSize { - get => DisplayedCountSpriteText.Font.Size; - set => DisplayedCountSpriteText.Font = DisplayedCountSpriteText.Font.With(size: value); + get => displayedCountSpriteText?.Font.Size ?? textSize; + set + { + if (TextSize == value) + return; + + textSize = value; + if (displayedCountSpriteText != null) + displayedCountSpriteText.Font = displayedCountSpriteText.Font.With(size: value); + } } + private Color4 accentColour; + public Color4 AccentColour { - get => DisplayedCountSpriteText.Colour; - set => DisplayedCountSpriteText.Colour = value; + get => displayedCountSpriteText?.Colour ?? accentColour; + set + { + if (AccentColour == value) + return; + + accentColour = value; + if (displayedCountSpriteText != null) + displayedCountSpriteText.Colour = value; + } } /// @@ -76,27 +97,21 @@ namespace osu.Game.Graphics.UserInterface /// protected RollingCounter() { - Children = new Drawable[] - { - DisplayedCountSpriteText = new OsuSpriteText { Font = OsuFont.Numeric } - }; - - TextSize = 40; AutoSizeAxes = Axes.Both; - DisplayedCount = Current.Value; - Current.ValueChanged += val => { - if (IsLoaded) TransformCount(displayedCount, val.NewValue); + if (IsLoaded) + TransformCount(DisplayedCount, val.NewValue); }; } - protected override void LoadComplete() + [BackgroundDependencyLoader] + private void load() { - base.LoadComplete(); - - DisplayedCountSpriteText.Text = FormatCount(Current.Value); + displayedCountSpriteText = CreateSpriteText(); + displayedCountSpriteText.Text = FormatCount(displayedCount); + Child = displayedCountSpriteText; } /// @@ -167,5 +182,11 @@ namespace osu.Game.Graphics.UserInterface this.TransformTo(nameof(DisplayedCount), newValue, rollingTotalDuration, RollingEasing); } + + protected virtual OsuSpriteText CreateSpriteText() => new OsuSpriteText + { + Font = OsuFont.Numeric.With(size: textSize), + Colour = accentColour, + }; } } diff --git a/osu.Game/Graphics/UserInterface/ScoreCounter.cs b/osu.Game/Graphics/UserInterface/ScoreCounter.cs index 01d8edaecf..438fe6c13b 100644 --- a/osu.Game/Graphics/UserInterface/ScoreCounter.cs +++ b/osu.Game/Graphics/UserInterface/ScoreCounter.cs @@ -3,6 +3,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Game.Graphics.Sprites; namespace osu.Game.Graphics.UserInterface { @@ -24,7 +25,6 @@ namespace osu.Game.Graphics.UserInterface /// How many leading zeroes the counter will have. public ScoreCounter(uint leading = 0) { - DisplayedCountSpriteText.Font = DisplayedCountSpriteText.Font.With(fixedWidth: true); LeadingZeroes = leading; } @@ -49,6 +49,13 @@ namespace osu.Game.Graphics.UserInterface return ((long)count).ToString(format); } + protected override OsuSpriteText CreateSpriteText() + { + var spriteText = base.CreateSpriteText(); + spriteText.Font = spriteText.Font.With(fixedWidth: true); + return spriteText; + } + public override void Increment(double amount) { Current.Value += amount; diff --git a/osu.Game/Screens/Ranking/Expanded/Statistics/AccuracyStatistic.cs b/osu.Game/Screens/Ranking/Expanded/Statistics/AccuracyStatistic.cs index 2a0e33aab7..921ad80976 100644 --- a/osu.Game/Screens/Ranking/Expanded/Statistics/AccuracyStatistic.cs +++ b/osu.Game/Screens/Ranking/Expanded/Statistics/AccuracyStatistic.cs @@ -3,6 +3,7 @@ using osu.Framework.Graphics; using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Screens.Ranking.Expanded.Accuracy; using osu.Game.Utils; @@ -43,16 +44,18 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics protected override Easing RollingEasing => AccuracyCircle.ACCURACY_TRANSFORM_EASING; - public Counter() - { - DisplayedCountSpriteText.Font = OsuFont.Torus.With(size: 20, fixedWidth: true); - DisplayedCountSpriteText.Spacing = new Vector2(-2, 0); - } - protected override string FormatCount(double count) => count.FormatAccuracy(); public override void Increment(double amount) => Current.Value += amount; + + protected override OsuSpriteText CreateSpriteText() + { + var spriteText = base.CreateSpriteText(); + spriteText.Font = OsuFont.Torus.With(size: 20, fixedWidth: true); + spriteText.Spacing = new Vector2(-2, 0); + return spriteText; + } } } } diff --git a/osu.Game/Screens/Ranking/Expanded/Statistics/CounterStatistic.cs b/osu.Game/Screens/Ranking/Expanded/Statistics/CounterStatistic.cs index 817cc9b8c2..cc0f49c968 100644 --- a/osu.Game/Screens/Ranking/Expanded/Statistics/CounterStatistic.cs +++ b/osu.Game/Screens/Ranking/Expanded/Statistics/CounterStatistic.cs @@ -3,6 +3,7 @@ using osu.Framework.Graphics; using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Screens.Ranking.Expanded.Accuracy; using osuTK; @@ -43,10 +44,12 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics protected override Easing RollingEasing => AccuracyCircle.ACCURACY_TRANSFORM_EASING; - public Counter() + protected override OsuSpriteText CreateSpriteText() { - DisplayedCountSpriteText.Font = OsuFont.Torus.With(size: 20, fixedWidth: true); - DisplayedCountSpriteText.Spacing = new Vector2(-2, 0); + var spriteText = base.CreateSpriteText(); + spriteText.Font = OsuFont.Torus.With(size: 20, fixedWidth: true); + spriteText.Spacing = new Vector2(-2, 0); + return spriteText; } public override void Increment(int amount) diff --git a/osu.Game/Screens/Ranking/Expanded/TotalScoreCounter.cs b/osu.Game/Screens/Ranking/Expanded/TotalScoreCounter.cs index cab04edb8b..b0060d19ac 100644 --- a/osu.Game/Screens/Ranking/Expanded/TotalScoreCounter.cs +++ b/osu.Game/Screens/Ranking/Expanded/TotalScoreCounter.cs @@ -3,6 +3,7 @@ using osu.Framework.Graphics; using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Screens.Ranking.Expanded.Accuracy; using osuTK; @@ -23,15 +24,21 @@ namespace osu.Game.Screens.Ranking.Expanded // Todo: AutoSize X removed here due to https://github.com/ppy/osu-framework/issues/3369 AutoSizeAxes = Axes.Y; RelativeSizeAxes = Axes.X; - DisplayedCountSpriteText.Anchor = Anchor.TopCentre; - DisplayedCountSpriteText.Origin = Anchor.TopCentre; - - DisplayedCountSpriteText.Font = OsuFont.Torus.With(size: 60, weight: FontWeight.Light, fixedWidth: true); - DisplayedCountSpriteText.Spacing = new Vector2(-5, 0); } protected override string FormatCount(long count) => count.ToString("N0"); + protected override OsuSpriteText CreateSpriteText() + { + var spriteText = base.CreateSpriteText(); + spriteText.Anchor = Anchor.TopCentre; + spriteText.Origin = Anchor.TopCentre; + + spriteText.Font = OsuFont.Torus.With(size: 60, weight: FontWeight.Light, fixedWidth: true); + spriteText.Spacing = new Vector2(-5, 0); + return spriteText; + } + public override void Increment(long amount) => Current.Value += amount; } From 29053048ff8ec0e33aef5f2d9ef8966e59b29c75 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 3 Aug 2020 21:40:13 +0300 Subject: [PATCH 0378/1782] Add support to use legacy combo fonts for the counter on legacy skins --- osu.Game.Rulesets.Catch/CatchSkinComponents.cs | 3 ++- .../Skinning/CatchLegacySkinTransformer.cs | 11 +++++++++++ .../Skinning/OsuLegacySkinTransformer.cs | 4 +--- osu.Game/Skinning/LegacySkinConfiguration.cs | 2 ++ osu.Game/Skinning/LegacySkinTransformer.cs | 2 ++ 5 files changed, 18 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Catch/CatchSkinComponents.cs b/osu.Game.Rulesets.Catch/CatchSkinComponents.cs index 80390705fe..23d8428fec 100644 --- a/osu.Game.Rulesets.Catch/CatchSkinComponents.cs +++ b/osu.Game.Rulesets.Catch/CatchSkinComponents.cs @@ -13,6 +13,7 @@ namespace osu.Game.Rulesets.Catch Droplet, CatcherIdle, CatcherFail, - CatcherKiai + CatcherKiai, + CatchComboCounter } } diff --git a/osu.Game.Rulesets.Catch/Skinning/CatchLegacySkinTransformer.cs b/osu.Game.Rulesets.Catch/Skinning/CatchLegacySkinTransformer.cs index d929da1a29..c2432e1dbb 100644 --- a/osu.Game.Rulesets.Catch/Skinning/CatchLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Catch/Skinning/CatchLegacySkinTransformer.cs @@ -6,6 +6,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Skinning; using osuTK; +using static osu.Game.Skinning.LegacySkinConfiguration; namespace osu.Game.Rulesets.Catch.Skinning { @@ -51,6 +52,16 @@ namespace osu.Game.Rulesets.Catch.Skinning case CatchSkinComponents.CatcherKiai: return this.GetAnimation("fruit-catcher-kiai", true, true, true) ?? this.GetAnimation("fruit-ryuuta", true, true, true); + + case CatchSkinComponents.CatchComboCounter: + var comboFont = GetConfig(LegacySetting.ComboPrefix)?.Value ?? "score"; + var fontOverlap = GetConfig(LegacySetting.ComboOverlap)?.Value ?? -2f; + + // For simplicity, let's use legacy combo font texture existence as a way to identify legacy skins from default. + if (HasFont(comboFont)) + return new LegacyComboCounter(Source, comboFont, fontOverlap); + + break; } return null; diff --git a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs index 81d1d05b66..b955150c90 100644 --- a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs @@ -94,7 +94,7 @@ namespace osu.Game.Rulesets.Osu.Skinning var font = GetConfig(OsuSkinConfiguration.HitCirclePrefix)?.Value ?? "default"; var overlap = GetConfig(OsuSkinConfiguration.HitCircleOverlap)?.Value ?? -2; - return !hasFont(font) + return !HasFont(font) ? null : new LegacySpriteText(Source, font) { @@ -145,7 +145,5 @@ namespace osu.Game.Rulesets.Osu.Skinning return Source.GetConfig(lookup); } - - private bool hasFont(string fontName) => Source.GetTexture($"{fontName}-0") != null; } } diff --git a/osu.Game/Skinning/LegacySkinConfiguration.cs b/osu.Game/Skinning/LegacySkinConfiguration.cs index 41b7aea34b..1d5412d93f 100644 --- a/osu.Game/Skinning/LegacySkinConfiguration.cs +++ b/osu.Game/Skinning/LegacySkinConfiguration.cs @@ -15,6 +15,8 @@ namespace osu.Game.Skinning public enum LegacySetting { Version, + ComboPrefix, + ComboOverlap, AnimationFramerate, LayeredHitSounds, } diff --git a/osu.Game/Skinning/LegacySkinTransformer.cs b/osu.Game/Skinning/LegacySkinTransformer.cs index ebc4757e75..acca53835c 100644 --- a/osu.Game/Skinning/LegacySkinTransformer.cs +++ b/osu.Game/Skinning/LegacySkinTransformer.cs @@ -47,5 +47,7 @@ namespace osu.Game.Skinning } public abstract IBindable GetConfig(TLookup lookup); + + protected bool HasFont(string fontPrefix) => GetTexture($"{fontPrefix}-0") != null; } } From f5af81d775874f928c8bf8cdeb12fffee23d9cce Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 3 Aug 2020 21:41:22 +0300 Subject: [PATCH 0379/1782] Add legacy combo font glyphs to legacy skins of test purposes --- .../Resources/old-skin/score-0.png | Bin 0 -> 3092 bytes .../Resources/old-skin/score-1.png | Bin 0 -> 1237 bytes .../Resources/old-skin/score-2.png | Bin 0 -> 3134 bytes .../Resources/old-skin/score-3.png | Bin 0 -> 3712 bytes .../Resources/old-skin/score-4.png | Bin 0 -> 2395 bytes .../Resources/old-skin/score-5.png | Bin 0 -> 3067 bytes .../Resources/old-skin/score-6.png | Bin 0 -> 3337 bytes .../Resources/old-skin/score-7.png | Bin 0 -> 1910 bytes .../Resources/old-skin/score-8.png | Bin 0 -> 3652 bytes .../Resources/old-skin/score-9.png | Bin 0 -> 3561 bytes .../Resources/special-skin/score-0@2x.png | Bin 0 -> 2184 bytes .../Resources/special-skin/score-1@2x.png | Bin 0 -> 923 bytes .../Resources/special-skin/score-2@2x.png | Bin 0 -> 2196 bytes .../Resources/special-skin/score-3@2x.png | Bin 0 -> 2510 bytes .../Resources/special-skin/score-4@2x.png | Bin 0 -> 1551 bytes .../Resources/special-skin/score-5@2x.png | Bin 0 -> 2265 bytes .../Resources/special-skin/score-6@2x.png | Bin 0 -> 2438 bytes .../Resources/special-skin/score-7@2x.png | Bin 0 -> 1700 bytes .../Resources/special-skin/score-8@2x.png | Bin 0 -> 2498 bytes .../Resources/special-skin/score-9@2x.png | Bin 0 -> 2494 bytes 20 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 osu.Game.Rulesets.Catch.Tests/Resources/old-skin/score-0.png create mode 100644 osu.Game.Rulesets.Catch.Tests/Resources/old-skin/score-1.png create mode 100644 osu.Game.Rulesets.Catch.Tests/Resources/old-skin/score-2.png create mode 100644 osu.Game.Rulesets.Catch.Tests/Resources/old-skin/score-3.png create mode 100644 osu.Game.Rulesets.Catch.Tests/Resources/old-skin/score-4.png create mode 100644 osu.Game.Rulesets.Catch.Tests/Resources/old-skin/score-5.png create mode 100644 osu.Game.Rulesets.Catch.Tests/Resources/old-skin/score-6.png create mode 100644 osu.Game.Rulesets.Catch.Tests/Resources/old-skin/score-7.png create mode 100644 osu.Game.Rulesets.Catch.Tests/Resources/old-skin/score-8.png create mode 100644 osu.Game.Rulesets.Catch.Tests/Resources/old-skin/score-9.png create mode 100644 osu.Game.Rulesets.Catch.Tests/Resources/special-skin/score-0@2x.png create mode 100644 osu.Game.Rulesets.Catch.Tests/Resources/special-skin/score-1@2x.png create mode 100644 osu.Game.Rulesets.Catch.Tests/Resources/special-skin/score-2@2x.png create mode 100644 osu.Game.Rulesets.Catch.Tests/Resources/special-skin/score-3@2x.png create mode 100644 osu.Game.Rulesets.Catch.Tests/Resources/special-skin/score-4@2x.png create mode 100644 osu.Game.Rulesets.Catch.Tests/Resources/special-skin/score-5@2x.png create mode 100644 osu.Game.Rulesets.Catch.Tests/Resources/special-skin/score-6@2x.png create mode 100644 osu.Game.Rulesets.Catch.Tests/Resources/special-skin/score-7@2x.png create mode 100644 osu.Game.Rulesets.Catch.Tests/Resources/special-skin/score-8@2x.png create mode 100644 osu.Game.Rulesets.Catch.Tests/Resources/special-skin/score-9@2x.png diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/old-skin/score-0.png b/osu.Game.Rulesets.Catch.Tests/Resources/old-skin/score-0.png new file mode 100644 index 0000000000000000000000000000000000000000..8304617d8c94a8400b50a90f364941bb02983065 GIT binary patch literal 3092 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 Y0OO;-v8NaCZ2$lO07*qoM6N<$f&jDokN^Mx literal 0 HcmV?d00001 diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/old-skin/score-3.png b/osu.Game.Rulesets.Catch.Tests/Resources/old-skin/score-3.png new file mode 100644 index 0000000000000000000000000000000000000000..82bec3babebd59263dc486e5900a3a01ea4aaed7 GIT binary patch literal 3712 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 JPDHLkV1kmK?iT<6 literal 0 HcmV?d00001 diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/old-skin/score-6.png b/osu.Game.Rulesets.Catch.Tests/Resources/old-skin/score-6.png new file mode 100644 index 0000000000000000000000000000000000000000..b4cf81f26e5cab5a068ce282ee22b15b92d0df12 GIT binary patch literal 3337 zcmV+k4fgVhP)oInXkiAowm zxg2vDgN+Ra8+@(L^|fAmr|*rvpF50qmLrXnksfBpyR&b;^L^jgq3gQ#Lp>Z%`8lV2 zR{d1aO$b|Fe{bXz5f|VFVg#}B+GQd~QvE>4f&uwm__4{IJ$u&nmkSmwP^3s84)6d8 zCV1t%Ti&M&u^`Y3bPKwG9yxCi#rF<8ikv$NF-0m?h$I7Pz;HpTob$?i6uFRzP&?2D zv zJ$tsx)XH`phgI{ zh=qef(mMD_!?d{b% zJ3F;6zW5@vckkZPt5>fUOU@kBkNRm);C{Aum?ea*VGp?ra zgGY`W`RVh|KOaMDxm>QND~fP?e0;n%a^y&D%a$!k^XJcBv3>jY47fU82ste^v47BV z@=LD-`~u*Yz~6T5+SS?C*7n%ef*bWvC}dd5?%=g(#mkp3>v?&3m+?J&fu93&B)%Ez zE`yhlixGD|3#QJoI_0yqG7b_W81M-oFV(_eb& zC1%-a;IF_R5gR3JSVz+_+(TqjYgmQ4wS8Fz_qj$Kqa(OH7z588davm@$j@ z?b|m7MfRckWj^3at)--B0czx;9r{+l6uyk>G4aAyYJWOuL~-MxEP`}EUKFPqwrU;1N6DGd1;lJhNM z$=a{K{<^8BrzhgI>2CIGv09R{T!N5GU%Ys6tTiUg2Qo{0_uY59rGeL4EcTG}k=K1f zkU`#9TwHw5&T@=Bem6Lb7Bv(*NDjsD8K2K*+oIUW>C(?X|GY|wHA>L;ipq_c*z$X3 zUo%4CzTfYU7X;6z_3BDXOZyle zwgS`b?ol`F7II0jS|U^0?x6I62xu3<*sR>iJA~AVkvYOV@C=5NkmUR8>+0%Sk@3E` zxVVS!0y>Zez!T;~ZeDcXBC8tiE@sTqU)7t2p*FErgAilbbQ{wZ`e|yk!b`K4m9fwp zvOOTBhZl`WO-&t+L1z>Oo#6=y32_LII8)3~{e~bAdZ9j=&zw1PU&5kQ2>2!Io8D* zBRk^}Q)y`K6F&a<B-&wu^(*EPgsNLCiTQa=Zr-(}mRW>fY{D|kNI zdW~_(iEPrONe-x-e7JZPPLDH{s3<9F*DO)KMur#41Y7m)*9I~qh z)Q(6t7gY6P2-tg4P7JZBKJi%lGg^d1Fu79zG+;KUmJ4-D&C;a+GiHV*Cnx8uTemI; zgN`<0#0V`?@1_NL18=?cRvMIW?rj`6{eahXn|5kZ54M+Ew{B@`)~xA)>@Jphp;iiL zuMEP999g7wbadoE0Z&6)%16klU_n^{d))c1H7#Qo{J2!CkXcJ{&lG`gEA;!|tv`dp&Kje(a4p z$T`h2op;rC{rdH8TFFJ!xeUeFu7Tp$U3-6X2;d@i&&6^i>Mp20eiKUcwNRewkfBt+Gv&k+lSjmx1 zS<+t86hYY`54j&9mMlvm-*eABmmY1CprkqyPf4htBIid^+&y~qXpk+*t`4nUy*fn} zJ|iUyseav(o#usioDfTwjnOkNzW8Fyqt+bA(vWl6Mx2zrH}3vio=`DEue@s(Fz$-~SLbrdWZurl!V&i%gw7 zd9u?wi7{gtd${}L&p!LCQ_4!u;Qsc1gHEbgYZ5R6(rDpFAAOXam6c^Xqfo(ARaI$) zg@xb1bC;xuH@ZxZkBp0B0?L33jGc)!yLfSKgV4f!K#xdihgd5~;wwwGlQV#M@4WNQ zEX12;=Tc@Uy|ri09zWcAN;(R6Fs&|=o-@*(5*jnXggJBOcsLuFpEamm8q{kn#=8vM zh|?7KBP1RtiK5Mi8p)@SSqp1ZVC#%K4ulelZ z!w)|UqS;5B5xC1mUDRKD?KK84S79fCe*i}*2J-Xsn~REybZ+9KExgszT6V(QfuG3D zF*Mx90|{9tBuJT{8qI@PTm-qZvY?>AO}%A}7iJe1I~azwW5_jr2?%~Es-|3ec84L=P0t}b;+m;)2b+OITeog)gu%P$B%bnpuU1Ga8(xERg#%aYO`d1IA`}rQ`M}&_9G_G zlAohMbUfVR%goI5qK3tSloP$zfl}jOvT!ctJbv-w#Q`9!rQH7S(=nLX83vrXEPU#!$D=TsqQ)`zW2$cX(! z#9|mE)#ABFT8dv>o+$8|h{c$ehe~YrnZ#y5$OPnEujzSz_`Cddg!L~YRLhGqEe>6l zKj9cK5ey3Y1pSZmml98-Y@L=r<#3wduqfZK`ym-$bXUJ)PBIne+3u-Y^mm; TZ*tsI00000NkvXXu0mjfuoP_o literal 0 HcmV?d00001 diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/old-skin/score-7.png b/osu.Game.Rulesets.Catch.Tests/Resources/old-skin/score-7.png new file mode 100644 index 0000000000000000000000000000000000000000..a23f5379b223d61079e055162fdd93f107f0ec02 GIT binary patch literal 1910 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 literal 0 HcmV?d00001 diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/score-0@2x.png b/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/score-0@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..508cc85e4ae2ad01c53c93c314502071de8a5e1f GIT binary patch literal 2184 zcmV;32zU31P)EX>4Tx04R}tkv&MmKpe$iQ^g_`2aAX}M5s;{L`5963Pq?8YK2xEOfLO`CJjl7 zi=*ILaPVWX>fqw6tAnc`2!4P#IXWr2NQwVT3N2zhIPS;0dyl(!fKV?p&FUBjG~G5+ ziMW`_u8Li+2thzU!WfpBWz0!Z629Z>9s$1I#dwzgxj#pbnzI-X5Q%4*VcNtS#M7I$ z!FiuJ!ius=d`>)O(glehxvqHp#<}3Kz%wIeIyFxmAr=d5th6yJni}yGaa7fG$`>*o ztDLtuYvn3y-jlyDoYPm9xlVHk2`pj>5=1DdqJ%PR#Aww?v5=zuxQ~C(^-JVZ$W;O( z#{w$QAiI9>Klt6Pm7kpOlEQJI^TlyKMu4tepiy(2?_(i|qi@OreYZgOn%7%%AEysMnz~Bf00)P_ zXo0fVecl~v@9p0+&HjD>Ds*y+$NP(?00006VoOIv00000008+zyMF)x010qNS#tmY z3ljhU3ljkVnw%H_000McNlirueSad^gZEa<4bO1wgWnpw> zWFU8GbZ8()Nlj2!fese{00v-5L_t(|+U=cNh*eb>$Ny_|9B0f+-ZC$lm$b}CYw}WN z6j2r=`p|=E(gh8=M+BigWDrGxR0JhdP+;kUg_4=l1xjX6Obr#y#Ika7GR<4gIL^$k zhqFZ(^6a(tIeYJQ&g?&&w{_0<-@o;pz1BYa?C+B@Wy+K(Q>KVAX&V4~Ntz_-MoBkF z8YHP-@aitF6jF)gK>brrx1Kx>|iISafCT;Z6_bAf#!*|-%LQ>yST0@jBd;JQK%5U2zm2TtXQ zaSFJ*)Zq08Hiy7_6{sjuya~X8kSw29D)5E?yF=hT97EiJz>YjI+kq#2hu1sgAOK#B zG5&DiP)PiFzQXGkY$5V#Wdc4X11)(xv;epH2=Jp2c!z<033+)r#LF>YoKNr`ve@9x zPtZ@b#TI7=(A@`kV?wqO0Gm_rHQQoR-%cA|CGdlV(b*|@GZwo6fSG9me9pq)o?`9y zBKUjI!rvc2k2K5;22NTSd?MwY!BGp3ucTq_V+&)gz`&GGGs`S|o=NnY8JJ@E)6zN* z7P;w`URNh+>I+L#3q1g6fWIvLPDsK~t;3I-S9-Ef{nXO$swB*;wlwoMU_1f%tfl96 zV7%++S2!3ME$JT1;Tw%Hcyh6{(Q=%T^i*sBB`uKsEr5;kM_VN|S&p3p^ej3+piKxcT3U$qhw&Kuk?&DZAk;{UQPfD@Yk=9D-l<tK`0F5y}D_6%VPNPX_gyZ<{3kDeI%b1P&n1R?Hw++D0 zN<9OCK8}mXCSy!Pu>fw6^lyo0psOeAqzMIRjA@p%J_8s%1EpL6MO$4RJe=s0u5}o@ zC0IO*3UH0XYFsVpI?n)3a~QkK<%6vN8e>`|eGrr3xEGMFviP%jpp%kT6dhoo_k!iU zqyS2qDEnvqJ~76eigPwsS=Jvo>`?|jW9hjq_L|f|xlLP*^#q{pq_0TI&>Bla3p{{l zfJV!T9oHmfsx}B1rnMdboaRl6UpDz2SG)}&#+N7Hqc$k% znXlaEE%H%7Phfip#Kkej9~$&o%C~6Dkb6ve|SFY0J1w%?& zDrr$kclGK3u{y}cVmIXLsIP#DDp$qW*|5H>I~)EF$eQ31@Y@1ILrGG)rtiS-|NH3fs%kaO4o0000< KMNUMnLSTYH3*fx~ literal 0 HcmV?d00001 diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/score-1@2x.png b/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/score-1@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..84f74e1ec9d45818d76353aa66ed1a728a2ad118 GIT binary patch literal 923 zcmeAS@N?(olHy`uVBq!ia0vp^mOz}v!3HGRn10@2U|?*?baoE#baqxKD9TUE%t>Wn z(3n^|(bnUzgUr$R;G;^S?A;v}E-U=RTwGNj4Vmj_xx~^;pkw0NMhQ|)H9wzLi--SmSfy^?`Xxe|2ys7`)6&I5D@50pP6{;i1d-mv$q|r zoNsZcYm--vpH12&)=v4I>ni>n+xcMA0b}JfD^q_NMKSK&HCqg|XGwh&DGa$pH808$vxazra!lJ)B`s*)z^?nj` zB9uw_IFr@|h23=@?0?){J9Yk?50efRF8F-3(9W%4UF?Fu;yLe=k7v$dIGFyxEB~i< zX~MI~U!uIbmCDp`+`Ki;O^-g5Z=fq4vg+%PZ!6Kid%2*?DskxAmQ+__%h$F5U#QS8JRDvEEBsp z9z96gT`K%h@y+TToPBvs&HLK&AG~<Km8VxsGZpplN_U0GQbAsLngbaTCSj)V3+1^c`<~?C7xb^LO(J_0EC4Xxk z`7Mr6p1;InqLOQuh-MJkjMS?$KYs0XSSPxtZfod?jtGW6u6t&B)$*F@lWn`|A8CI4 zm)Ya{K<38(qx;r1az7BPI2miOboqV-{mg^uhCyB@Uus=CbLP$MdkmNE&1rvga=AM& Pju<>${an^LB{Ts5NqUv} literal 0 HcmV?d00001 diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/score-2@2x.png b/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/score-2@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..49625c6623c2d07188e263708d2649af82eefdd7 GIT binary patch literal 2196 zcmV;F2y6F=P)UW&V0004mX+uL$Nkc;* zaB^>EX>4Tx04R}tkv&MmKpe$iQ^g_`2aAX}M5s;{L`5963Pq?8YK2xEOfLO`CJjl7 zi=*ILaPVWX>fqw6tAnc`2!4P#IXWr2NQwVT3N2zhIPS;0dyl(!fKV?p&FUBjG~G5+ ziMW`_u8Li+2thzU!WfpBWz0!Z629Z>9s$1I#dwzgxj#pbnzI-X5Q%4*VcNtS#M7I$ z!FiuJ!ius=d`>)O(glehxvqHp#<}3Kz%wIeIyFxmAr=d5th6yJni}yGaa7fG$`>*o ztDLtuYvn3y-jlyDoYPm9xlVHk2`pj>5=1DdqJ%PR#Aww?v5=zuxQ~C(^-JVZ$W;O( z#{w$QAiI9>Klt6Pm7kpOlEQJI^TlyKMu4tepiy(2?_(i|qi@OreYZgOn%7%%AEysMnz~Bf00)P_ zXo0fVecl~v@9p0+&HjD>Ds*y+$NP(?00006VoOIv00000008+zyMF)x010qNS#tmY z3ljhU3ljkVnw%H_000McNlirueSad^gZEa<4bO1wgWnpw> zWFU8GbZ8()Nlj2!fese{00wMHL_t(|+U=ZMY!p=($NvXf5DMjvqNv3JMHCQ_fG8jk zLeX&1hy)?gfRd2-q%q;e3sGaDz9=zb5Ce!3yd(*mBmh`Qp&BmBRmoyZhr=$rEh|!YDBgpoEq|MIjJ!8zlOCt1uk-$5^ z1>)5O-~*r%Xpuw^#lURfdzwdkfdxR@L=+!jh@=&gZfN4GCnX(~bj*3xICV;QNtZkC ztwOr9N76iF%=+evjw0Z3;JnMf_ZRRsFc0YKBfplwt-!Ou_K=bD2GBXzKq@?n$`8N; zKBZ2K|C zR#2S<#>eGU8E_!O;6K2W92VOdj!hdd6QXve1o$n(&^cgIGgR=!MttYhAd%7Fn{{+U;rxwB}1b(UbJL}C1!j7;~$U8(x?tp`wT0p$SDK8)d6 zSu&lQpy9;}meKN57^7PrEU`>C*#=5wmS=kt2xW_;T8ng*fuK0$ZCI9%4ke9oKOFvl&^l?H)Q z?~&1)&Va0S9zbaq1WJ`hMy(}1CuukEA}|hU)tp3(F=s5B#KmvkIh z1Iz)6n~6=0MViw=pd7Xc!^x7CIuSGnXqjUur50(bf{Z6x0;fVQGkg@);*<}6+gkcK z(|4p8V=k&;y*7;Qko0nnqNJl`y4^v?J{u!&b}n#Z4xn_kNK-9oM^GrsB-KUW@QfTl z8DNp-V`EHRASlL|Ba)(+=d{B{lxN*Zj8`CIuaU|N6(sRM3PKFetlw*`Q*K>041|S1`?pj*e+$#&Y z0#X4S@d4x+r9nWsv+Kx9HNfn&0O<$pYXZm)pkG>mjPf!1eLe5{AdP_B+r(D$)4;5> zz%am*O*FeZfQqyLDFNPZqPQ$cQ*kK+e)ND*?Y6v`uRv}Gj(Gt24!AB&K<0bwmaccU zm=~u7MltYO6KDS>rYSnQ0bh9lSr_R+rAq+FO&&eeI%gl9NfVIi&dE==)8`}71j7JN zdlZ+|shA9E1f)Iip~rdQhmG0dtY z&8!1$GOiuz6PKYe&YcIDT9mZjDKIR<$F6Z0=?xsu0%UPQ8-WKi{49#vKq;`xrO23s zLRr+v&y#T&Se^yQ_9XW~fG->0*AR8)Q(P`>DNitv%QE^;TcgapbC&Ce0G>#Ar*dNE zyiJsuuX4G;rcJ`3yp%aQk1FHQF5{V36Aq*Z_#=qVLGE01Dk z{B(8iY2x#TfrFA3hdM(O4n!+8ocS3eX|5W!#(T-LPBA_VZjt0?m|dra)0@?DIT;?Y ziX?SV!(|mguWcP@jM-@g#krDszjAmNc8=83>%Q!!>XZ?A2qbhxJ2Vf0)EZ;Xg@H08 zZBgO}N<{(6|JA(!<-aXYsib^dxH=IgQaUL=6{`5ODnPLt0_0`!DX5@=3M#0e-0NR0 W2Bg8WSc5SD0000UW&V0004mX+uL$Nkc;* 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 BxF!Gq literal 0 HcmV?d00001 diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/score-5@2x.png b/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/score-5@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..d8250b0c63d96fe5e5ecfa92b639d8fc5f61ded7 GIT binary patch literal 2265 zcmV;~2qyQ5P)EX>4Tx04R}tkv&MmKpe$iQ^g_`2aAX}M5s;{L`5963Pq?8YK2xEOfLO`CJjl7 zi=*ILaPVWX>fqw6tAnc`2!4P#IXWr2NQwVT3N2zhIPS;0dyl(!fKV?p&FUBjG~G5+ ziMW`_u8Li+2thzU!WfpBWz0!Z629Z>9s$1I#dwzgxj#pbnzI-X5Q%4*VcNtS#M7I$ z!FiuJ!ius=d`>)O(glehxvqHp#<}3Kz%wIeIyFxmAr=d5th6yJni}yGaa7fG$`>*o ztDLtuYvn3y-jlyDoYPm9xlVHk2`pj>5=1DdqJ%PR#Aww?v5=zuxQ~C(^-JVZ$W;O( z#{w$QAiI9>Klt6Pm7kpOlEQJI^TlyKMu4tepiy(2?_(i|qi@OreYZgOn%7%%AEysMnz~Bf00)P_ zXo0fVecl~v@9p0+&HjD>Ds*y+$NP(?00006VoOIv00000008+zyMF)x010qNS#tmY z3ljhU3ljkVnw%H_000McNlirueSad^gZEa<4bO1wgWnpw> zWFU8GbZ8()Nlj2!fese{00yy1L_t(|+U=cbY!y`$$N#4mfwm|DvWc-GihwJEDA8zC zM3D~=7ec^&P1FR97=p_e6B9pRB8ef8sEG=KalsFUXo6UciW^}O1rci^xI%@33Y7Ne zhq;8;rmvm3Z{Ezb&Of;cDKm4<`@Q?_a?ZVXq+Gdj<;vCd1WX1t1NQvC6kj^fVM(*iY(*SSRVo{b z1I)jYW|`UBaXFP0IHhWfq?^oaQ=AX2=mubxN*W(Gn63`zCrQ)IY_^&0FNcAy2iVJZ3scnIi|*iQ1f&DSSI1GFOJOYg9661?DgTNhTc5*w{^pte5q%o34yV31nH{z{vrKw~aB}5vayU7h98Nk) zJT>F;=_e`3UXGA7Ku2Rem6G7g|mdAP9e?oGWR%B%crZucUcq zwlEVl2e`gSN@f)>4d~%v%Ycc%v%v2jHEB`Y3#X#M`OW=|ZUdUzIQAKEQH*v^0ABI{ z^geKW8gP#E7%gp*ar1#=%4Gj)pgtm?Z-GmK95_k?DlY+ws_ogRSu`PCj4LE)O*}h+tGNZ zJHheUR@6&DfIo**L?`8NimN(@Q(RR!oQ~U_ZV$&dT>DS4|M1ER0>dSZQ;@Q4mb61s zotZVH2&XFH8rKR)1#l~{se>JMOMvQ>=2x>k=U3;HYPc3y7t+|4k`4O#c#kKXq26r{ zFjms%lFkq5=SWF!0LLcG$EU{rZDzlC!2wQ`^p2!~5sjWMX->j$j`O(Zhf?NNFG?Eb z>FnHu;naHE^Su`wmmQp1g0lmF!3o0g*=D`gx8}@?!Qn9(hf^0Bj@uu8bqo$Ca@d($ zViL8MHcDFWQ*$nn)H?>hw`RE3yk}<3K5)jx;P5^L$G9XRpVbpfJG-D5oPjYoys)!x zCPqz;c>Q0JR{6p?u|#M8ko0Uq?MF~Kf8>Q8F9nbnt4Rp)rDT9~1P1yn{{e%l0$F4gFbA&vdz!WgLp z%{;;s3*s;uX3fhkpjIgg*cbuKFR>kM)y~nmZFPUjV5UR>(+o_F)A;;O3|^Qbm}7vo z5#T%;w*#kjVsLzlaGs6;=9O|ev8)55GgAUHwatc>EcJFSei!d#CEY){W0DU^?!8QT+0fRiB zKM1%B_%uRSy}n#qh0OCq+#qR@f^#U^Q;UK`;5r4T_it0MRk~5Z_U0-VX6UOR33#@G zq~UmlU)<0j=?O`T6AY?F`j%fS!%+-jdZKwiUu9juDZn#sw{W@C41DNbmy+1)TP#LM zy(P_b!KX_l4Jq-F`y_oT>3vBn(l|Za%2=a@J4h20hH>tBVztm>$7sUDac=c nC24);+LBzka^=dED|7k}Cp+Yxt)OTj00000NkvXXu0mjfFn}8V literal 0 HcmV?d00001 diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/score-6@2x.png b/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/score-6@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..75d3cbd3bd5931289026636b1426e69e97eca4ca GIT binary patch literal 2438 zcmV;133>L3P)EX>4Tx04R}tkv&MmKpe$iQ^g_`2aAX}M5s;{L`5963Pq?8YK2xEOfLO`CJjl7 zi=*ILaPVWX>fqw6tAnc`2!4P#IXWr2NQwVT3N2zhIPS;0dyl(!fKV?p&FUBjG~G5+ ziMW`_u8Li+2thzU!WfpBWz0!Z629Z>9s$1I#dwzgxj#pbnzI-X5Q%4*VcNtS#M7I$ z!FiuJ!ius=d`>)O(glehxvqHp#<}3Kz%wIeIyFxmAr=d5th6yJni}yGaa7fG$`>*o ztDLtuYvn3y-jlyDoYPm9xlVHk2`pj>5=1DdqJ%PR#Aww?v5=zuxQ~C(^-JVZ$W;O( z#{w$QAiI9>Klt6Pm7kpOlEQJI^TlyKMu4tepiy(2?_(i|qi@OreYZgOn%7%%AEysMnz~Bf00)P_ zXo0fVecl~v@9p0+&HjD>Ds*y+$NP(?00006VoOIv00000008+zyMF)x010qNS#tmY z3ljhU3ljkVnw%H_000McNlirueSad^gZEa<4bO1wgWnpw> zWFU8GbZ8()Nlj2!fese{00&-4L_t(|+U=cxtkzQ;$KU7X7WY(^n8OHpsN%MPL)AAz{5|>EH&!j}ZyZyLH-IqU})7I$ue$V+n z-*djty*=;k?*8$d=bZQ6=X1{aoX`23^GG$+P(uwhR0T{mkN|p08YSsCNkb(KmegC) z{*oFcZIZN3(t1g&CACPJCuyEBW>u%_6`(h8JunN{PIi0)Tn8M`8AKTdJOOMaU$g;F z0S9$NQ4R&B5gzM+D*;pWa}H21=@v;7BsC-yT`y_2q;KutN=dDDzUeBdhn<6tw)4{I zc8)5CHzkcR#{6Ba)-nM2ETNbG0iFZSwj%o0KM1%#VTjHI_Nzu=E&|q9r<4-Cwy zt^UCL1c6L72{YabuqUhnMh9rK0ca`H<~7wK%v}k?=nJ54z_yPs)9yy#(4vKTAVHXx z7`_OwuuS`p7b(om3BoJ}`b6UKK^YEfvrPJV5E`2>$Fu>%BJpUneoi@@A6uA{fo%zd z##PC%26!x{Fx`Ql63Dz8iANo9pM&?@98;Vp6Nu~thDYGg7kJ-sYq21fFc+o}c_spf zV}X?(`>r)Hgz06SxpLS6^pCKSUG2Fk^@{z}W#l$d(t!z|ziy2AEpQ)Amel0=oi(AY zW#J}G5p#t_FC(=O>&kFVTd zjQKiLPwfY+ap<9oBYNj`z>E|=+kmS=6S>^ceETXgfN^_pNLzuad4*xq2)yAS%r6DT zlDYsxfYa^&Zm}Bf>EQGMOj9R=%Y=MEiqp3XpX`m|jB%{%)6OQ2Nf}c0k~S#$q*9XF zB{fv->0%(u8R;O5BrWZ1!nlazy1V{Soe*i_oatE4uX~j^z&?^rb*yK_-ex&NBz51L z#Yx&o9_vw0ro23x{V-h8Np|udrcy3oZ%I8RZB;2p=TAwCB`uWnmN8~gp{fCQdxQi| z^{jg+a4)bdW4LpXO;{LSlsK<>kR9S!=LtY_&S`Efz!|Z{`N4zWfRwtLY!tCQU@qWG zX*}}Wx%#pw@RQYL}vw7_nMtChC`N9EDr z@=Vui#UH}B)7;Iv#mgJmU@(}mozY@<$S4PvBFxfD+b22oR%V$vA)$G z9$z^;i#SU&@>{9mvB{%s4{vddF`Jd`h<8I3h_ltBY-1M7k)-c45ZlSKtLB>}Rv3dCvfD7z}FINwOx60p}op)t7SSf62Dz+UUnmLrv7 zB%yjO6kLyhR~uuNRU%GvM-s;-Be-6QO})~XCeD0MulL}Bt>#FNvXzNrj6tQhuUs*x zU~!I!CXU6cdF^tm$Z|H?)wp7DW=UGsK`dv4W4U+iRmjT3v6VdK*yzxL#c{okkl#}= z_T32A9Kk}~#`3sM{jF=AP8n9lnB|gYIM#Vqm0QgtA$n%8JxWc(vjtg>%Mtd*Jtb!5 zMZo24(Uta6Nlal5aeNtfO6YOj=0IX>WN{`sc3Q8{L~e5+GB=_yb-+>wPGe$_sZV&Q zaCeM|B+g|HT;7an#v|?H8A*6A^n|{+B@&>rW;Wpk^_C20@$9lWj<$AG0mguBBEye^8={y?} zNLxOa+d$fUNk6FcdViZrw}ShqbW`{gN$09`TR5$*D~vI-RPC^@-A?hm3Yi7l?Io5* zbu<72fky-UI>;8_ktokjMG)ygdp}`z1=h5~et$gB6oa=ZK&U2_e(mFUm45MKkV*$} zyMB>qi%J3T4U&FVDFJhVq&dczHL9V88fvJah8lLmzfMgO{fEZ+o&W#<07*qoM6N<$ Ef(UtvIRF3v literal 0 HcmV?d00001 diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/score-7@2x.png b/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/score-7@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..cfe2021df40680208e385ab41ee12ab157899712 GIT binary patch literal 1700 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 IM6N<$g4va<@Bjb+ literal 0 HcmV?d00001 From d6f36457a8736963b7710cab79ceca1f5512ac35 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 3 Aug 2020 21:43:03 +0300 Subject: [PATCH 0380/1782] Fix legacy font glyphs being mistaken for animation and getting "extrapolated" --- osu.Game/Tests/Visual/SkinnableTestScene.cs | 28 +++++++++++++++------ 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/osu.Game/Tests/Visual/SkinnableTestScene.cs b/osu.Game/Tests/Visual/SkinnableTestScene.cs index 81c13112d0..49ba02fdea 100644 --- a/osu.Game/Tests/Visual/SkinnableTestScene.cs +++ b/osu.Game/Tests/Visual/SkinnableTestScene.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Text.RegularExpressions; using JetBrains.Annotations; using osu.Framework.Allocation; @@ -152,25 +153,38 @@ namespace osu.Game.Tests.Visual { private readonly bool extrapolateAnimations; + private readonly HashSet legacyFontPrefixes = new HashSet(); + public TestLegacySkin(SkinInfo skin, IResourceStore storage, AudioManager audioManager, bool extrapolateAnimations) : base(skin, storage, audioManager, "skin.ini") { this.extrapolateAnimations = extrapolateAnimations; + + legacyFontPrefixes.Add(GetConfig("HitCirclePrefix")?.Value ?? "default"); + legacyFontPrefixes.Add(GetConfig("ScorePrefix")?.Value ?? "score"); + legacyFontPrefixes.Add(GetConfig("ComboPrefix")?.Value ?? "score"); } public override Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) { // extrapolate frames to test longer animations - if (extrapolateAnimations) - { - var match = Regex.Match(componentName, "-([0-9]*)"); - - if (match.Length > 0 && int.TryParse(match.Groups[1].Value, out var number) && number < 60) - return base.GetTexture(componentName.Replace($"-{number}", $"-{number % 2}"), wrapModeS, wrapModeT); - } + if (extrapolateAnimations && isAnimationComponent(componentName, out var number) && number < 60) + return base.GetTexture(componentName.Replace($"-{number}", $"-{number % 2}"), wrapModeS, wrapModeT); return base.GetTexture(componentName, wrapModeS, wrapModeT); } + + private bool isAnimationComponent(string componentName, out int number) + { + number = 0; + + // legacy font glyph textures have the pattern "{fontPrefix}-{character}", which could be mistaken for an animation frame. + if (legacyFontPrefixes.Any(p => componentName.StartsWith($"{p}-"))) + return false; + + var match = Regex.Match(componentName, "-([0-9]*)"); + return match.Length > 0 && int.TryParse(match.Groups[1].Value, out number); + } } } } From f37ba49f7f624614b99ddfadc5e54785d0722b5b Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 3 Aug 2020 22:13:02 +0300 Subject: [PATCH 0381/1782] Add catch-specific combo counter with its legacy design --- .../Skinning/LegacyComboCounter.cs | 139 ++++++++++++++++++ .../UI/CatchComboDisplay.cs | 59 ++++++++ osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs | 32 +++- .../UI/ICatchComboCounter.cs | 34 +++++ 4 files changed, 262 insertions(+), 2 deletions(-) create mode 100644 osu.Game.Rulesets.Catch/Skinning/LegacyComboCounter.cs create mode 100644 osu.Game.Rulesets.Catch/UI/CatchComboDisplay.cs create mode 100644 osu.Game.Rulesets.Catch/UI/ICatchComboCounter.cs diff --git a/osu.Game.Rulesets.Catch/Skinning/LegacyComboCounter.cs b/osu.Game.Rulesets.Catch/Skinning/LegacyComboCounter.cs new file mode 100644 index 0000000000..9700bd0e08 --- /dev/null +++ b/osu.Game.Rulesets.Catch/Skinning/LegacyComboCounter.cs @@ -0,0 +1,139 @@ +// Copyright (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.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osu.Game.Rulesets.Catch.UI; +using osu.Game.Screens.Play; +using osu.Game.Skinning; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Rulesets.Catch.Skinning +{ + internal class LegacyComboCounter : CompositeDrawable, ICatchComboCounter + { + private readonly ISkin skin; + + private readonly string fontName; + private readonly float fontOverlap; + + private readonly LegacyRollingCounter counter; + private LegacyRollingCounter lastExplosion; + + public LegacyComboCounter(ISkin skin, string fontName, float fontOverlap) + { + this.skin = skin; + + this.fontName = fontName; + this.fontOverlap = fontOverlap; + + AutoSizeAxes = Axes.Both; + + Alpha = 0f; + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + Scale = new Vector2(0.8f); + + InternalChild = counter = new LegacyRollingCounter(skin, fontName, fontOverlap) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }; + } + + public void DisplayInitialCombo(int combo) => updateCombo(combo, null, true); + public void UpdateCombo(int combo, Color4? hitObjectColour) => updateCombo(combo, hitObjectColour, false); + + private void updateCombo(int combo, Color4? hitObjectColour, bool immediate) + { + // Combo fell to zero, roll down and fade out the counter. + if (combo == 0) + { + counter.Current.Value = 0; + if (lastExplosion != null) + lastExplosion.Current.Value = 0; + + this.FadeOut(immediate ? 0.0 : 400.0, Easing.Out); + return; + } + + // There may still be previous transforms being applied, finish them and remove explosion. + FinishTransforms(true); + if (lastExplosion != null) + RemoveInternal(lastExplosion); + + this.FadeIn().Delay(1000.0).FadeOut(300.0); + + // For simplicity, in the case of rewinding we'll just set the counter to the current combo value. + immediate |= Time.Elapsed < 0; + + if (immediate) + { + counter.SetCountWithoutRolling(combo); + return; + } + + counter.ScaleTo(1.5f).ScaleTo(0.8f, 250.0, Easing.Out) + .OnComplete(c => c.SetCountWithoutRolling(combo)); + + counter.Delay(250.0).ScaleTo(1f).ScaleTo(1.1f, 60.0).Then().ScaleTo(1f, 30.0); + + var explosion = new LegacyRollingCounter(skin, fontName, fontOverlap) + { + Alpha = 0.65f, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Scale = new Vector2(1.5f), + Colour = hitObjectColour ?? Color4.White, + Depth = 1f, + }; + + AddInternal(explosion); + + explosion.SetCountWithoutRolling(combo); + explosion.ScaleTo(1.9f, 400.0, Easing.Out) + .FadeOut(400.0) + .Expire(true); + + lastExplosion = explosion; + } + + private class LegacyRollingCounter : RollingCounter + { + private readonly ISkin skin; + + private readonly string fontName; + private readonly float fontOverlap; + + protected override bool IsRollingProportional => true; + + public LegacyRollingCounter(ISkin skin, string fontName, float fontOverlap) + { + this.skin = skin; + this.fontName = fontName; + this.fontOverlap = fontOverlap; + } + + public override void Increment(int amount) => Current.Value += amount; + + protected override double GetProportionalDuration(int currentValue, int newValue) + { + return Math.Abs(newValue - currentValue) * 75.0; + } + + protected override OsuSpriteText CreateSpriteText() + { + return new LegacySpriteText(skin, fontName) + { + Spacing = new Vector2(-fontOverlap, 0f) + }; + } + } + } +} diff --git a/osu.Game.Rulesets.Catch/UI/CatchComboDisplay.cs b/osu.Game.Rulesets.Catch/UI/CatchComboDisplay.cs new file mode 100644 index 0000000000..10aee2ea31 --- /dev/null +++ b/osu.Game.Rulesets.Catch/UI/CatchComboDisplay.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 JetBrains.Annotations; +using osu.Game.Rulesets.Catch.Objects.Drawables; +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Scoring; +using osu.Game.Skinning; +using osuTK.Graphics; + +namespace osu.Game.Rulesets.Catch.UI +{ + public class CatchComboDisplay : SkinnableDrawable + { + private int currentCombo; + + [CanBeNull] + public ICatchComboCounter ComboCounter => Drawable as ICatchComboCounter; + + public CatchComboDisplay() + : base(new CatchSkinComponent(CatchSkinComponents.CatchComboCounter), _ => Empty()) + { + } + + protected override void SkinChanged(ISkinSource skin, bool allowFallback) + { + base.SkinChanged(skin, allowFallback); + ComboCounter?.DisplayInitialCombo(currentCombo); + } + + public void OnNewResult(DrawableCatchHitObject judgedObject, JudgementResult result) + { + if (!result.Judgement.AffectsCombo || !result.HasResult) + return; + + if (result.Type == HitResult.Miss) + { + updateCombo(0, null); + return; + } + + updateCombo(result.ComboAtJudgement + 1, judgedObject.AccentColour.Value); + } + + public void OnRevertResult(DrawableCatchHitObject judgedObject, JudgementResult result) + { + if (!result.Judgement.AffectsCombo || !result.HasResult) + return; + + updateCombo(result.ComboAtJudgement, judgedObject.AccentColour.Value); + } + + private void updateCombo(int newCombo, Color4? hitObjectColour) + { + currentCombo = newCombo; + ComboCounter?.UpdateCombo(newCombo, hitObjectColour); + } + } +} diff --git a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs index 154e1576db..b5c040f80d 100644 --- a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs @@ -28,6 +28,7 @@ namespace osu.Game.Rulesets.Catch.UI public const float CENTER_X = WIDTH / 2; internal readonly CatcherArea CatcherArea; + private readonly CatchComboDisplay comboDisplay; public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => // only check the X position; handle all vertical space. @@ -48,12 +49,22 @@ namespace osu.Game.Rulesets.Catch.UI Origin = Anchor.TopLeft, }; + comboDisplay = new CatchComboDisplay + { + AutoSizeAxes = Axes.Both, + RelativeSizeAxes = Axes.None, + Anchor = Anchor.CentreLeft, + Origin = Anchor.Centre, + Y = 30f, + }; + InternalChildren = new[] { explodingFruitContainer, CatcherArea.MovableCatcher.CreateProxiedContent(), HitObjectContainer, - CatcherArea + CatcherArea, + comboDisplay, }; } @@ -62,6 +73,7 @@ namespace osu.Game.Rulesets.Catch.UI public override void Add(DrawableHitObject h) { h.OnNewResult += onNewResult; + h.OnRevertResult += onRevertResult; base.Add(h); @@ -69,7 +81,23 @@ namespace osu.Game.Rulesets.Catch.UI fruit.CheckPosition = CheckIfWeCanCatch; } + protected override void Update() + { + base.Update(); + comboDisplay.X = CatcherArea.MovableCatcher.X; + } + private void onNewResult(DrawableHitObject judgedObject, JudgementResult result) - => CatcherArea.OnResult((DrawableCatchHitObject)judgedObject, result); + { + var catchObject = (DrawableCatchHitObject)judgedObject; + CatcherArea.OnResult(catchObject, result); + + comboDisplay.OnNewResult(catchObject, result); + } + + private void onRevertResult(DrawableHitObject judgedObject, JudgementResult result) + { + comboDisplay.OnRevertResult((DrawableCatchHitObject)judgedObject, result); + } } } diff --git a/osu.Game.Rulesets.Catch/UI/ICatchComboCounter.cs b/osu.Game.Rulesets.Catch/UI/ICatchComboCounter.cs new file mode 100644 index 0000000000..1363ed1352 --- /dev/null +++ b/osu.Game.Rulesets.Catch/UI/ICatchComboCounter.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 osuTK.Graphics; + +namespace osu.Game.Rulesets.Catch.UI +{ + /// + /// An interface providing a set of methods to update the combo counter. + /// + public interface ICatchComboCounter : IDrawable + { + /// + /// Updates the counter to display the provided as initial value. + /// The value should be immediately displayed without any animation. + /// + /// + /// This is required for when instantiating a combo counter in middle of accumulating combo (via skin change). + /// + /// The combo value to be displayed as initial. + void DisplayInitialCombo(int combo); + + /// + /// Updates the counter to animate a transition from the old combo value it had to the current provided one. + /// + /// + /// This is called regardless of whether the clock is rewinding. + /// + /// The new combo value. + /// The colour of the object if hit, null on miss. + void UpdateCombo(int combo, Color4? hitObjectColour); + } +} From 21eaf0e99515fb708f5e00787295f94296f997ee Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 3 Aug 2020 22:14:00 +0300 Subject: [PATCH 0382/1782] Expose "is break time" bindable within GameplayBeatmap --- osu.Game/Screens/Play/GameplayBeatmap.cs | 5 +++++ osu.Game/Screens/Play/Player.cs | 1 + 2 files changed, 6 insertions(+) diff --git a/osu.Game/Screens/Play/GameplayBeatmap.cs b/osu.Game/Screens/Play/GameplayBeatmap.cs index 64894544f4..d7eed73275 100644 --- a/osu.Game/Screens/Play/GameplayBeatmap.cs +++ b/osu.Game/Screens/Play/GameplayBeatmap.cs @@ -16,6 +16,11 @@ namespace osu.Game.Screens.Play { public readonly IBeatmap PlayableBeatmap; + /// + /// Whether the gameplay is currently in a break. + /// + public IBindable IsBreakTime { get; } = new Bindable(); + public GameplayBeatmap(IBeatmap playableBeatmap) { PlayableBeatmap = playableBeatmap; diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 541275cf55..ee32ded93d 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -612,6 +612,7 @@ namespace osu.Game.Screens.Play // bind component bindables. Background.IsBreakTime.BindTo(breakTracker.IsBreakTime); + gameplayBeatmap.IsBreakTime.BindTo(breakTracker.IsBreakTime); DimmableStoryboard.IsBreakTime.BindTo(breakTracker.IsBreakTime); Background.StoryboardReplacesBackground.BindTo(storyboardReplacesBackground); From 65c269e473d15abb7bebd072dbb01f96f9849869 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 3 Aug 2020 22:17:11 +0300 Subject: [PATCH 0383/1782] Hide combo counter on gameplay break Intentionally inside LegacyComboCounter and not in CatchComboDisplay, to avoid conflicting with how the legacy combo counter fades away after 1 second of no combo update, can move to parent once a DefaultComboCounter design is decided and code is shareable between. --- .../Skinning/LegacyComboCounter.cs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/osu.Game.Rulesets.Catch/Skinning/LegacyComboCounter.cs b/osu.Game.Rulesets.Catch/Skinning/LegacyComboCounter.cs index 9700bd0e08..90dd1f4e9f 100644 --- a/osu.Game.Rulesets.Catch/Skinning/LegacyComboCounter.cs +++ b/osu.Game.Rulesets.Catch/Skinning/LegacyComboCounter.cs @@ -47,6 +47,23 @@ namespace osu.Game.Rulesets.Catch.Skinning }; } + private IBindable isBreakTime; + + [Resolved(canBeNull: true)] + private GameplayBeatmap beatmap { get; set; } + + protected override void LoadComplete() + { + base.LoadComplete(); + + isBreakTime = beatmap?.IsBreakTime.GetBoundCopy(); + isBreakTime?.BindValueChanged(b => + { + if (b.NewValue) + this.FadeOut(400.0, Easing.OutQuint); + }); + } + public void DisplayInitialCombo(int combo) => updateCombo(combo, null, true); public void UpdateCombo(int combo, Color4? hitObjectColour) => updateCombo(combo, hitObjectColour, false); From 5cd2841080caf8e41b135d740a246f5d6535abae Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 3 Aug 2020 22:17:42 +0300 Subject: [PATCH 0384/1782] Add test scene showing off the skinnable catch-specific combo counter --- .../TestSceneComboCounter.cs | 79 +++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 osu.Game.Rulesets.Catch.Tests/TestSceneComboCounter.cs diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneComboCounter.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneComboCounter.cs new file mode 100644 index 0000000000..2581e305dd --- /dev/null +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneComboCounter.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.Linq; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Game.Rulesets.Catch.Objects; +using osu.Game.Rulesets.Catch.Objects.Drawables; +using osu.Game.Rulesets.Catch.UI; +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Scoring; +using osu.Game.Screens.Play; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Rulesets.Catch.Tests +{ + public class TestSceneComboCounter : CatchSkinnableTestScene + { + private ScoreProcessor scoreProcessor; + private GameplayBeatmap gameplayBeatmap; + private readonly Bindable isBreakTime = new BindableBool(); + + [BackgroundDependencyLoader] + private void load() + { + gameplayBeatmap = new GameplayBeatmap(CreateBeatmapForSkinProvider()); + gameplayBeatmap.IsBreakTime.BindTo(isBreakTime); + Dependencies.Cache(gameplayBeatmap); + Add(gameplayBeatmap); + } + + [SetUp] + public void SetUp() => Schedule(() => + { + scoreProcessor = new ScoreProcessor(); + + SetContents(() => new CatchComboDisplay + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Scale = new Vector2(2.5f), + }); + }); + + [Test] + public void TestCatchComboCounter() + { + AddRepeatStep("perform hit", () => performJudgement(HitResult.Perfect), 20); + AddStep("perform miss", () => performJudgement(HitResult.Miss)); + AddToggleStep("toggle gameplay break", v => isBreakTime.Value = v); + } + + private void performJudgement(HitResult type, Judgement judgement = null) + { + var judgedObject = new TestDrawableCatchHitObject(new TestCatchHitObject()); + var result = new JudgementResult(judgedObject.HitObject, judgement ?? new Judgement()) { Type = type }; + scoreProcessor.ApplyResult(result); + + foreach (var counter in CreatedDrawables.Cast()) + counter.OnNewResult(judgedObject, result); + } + + private class TestDrawableCatchHitObject : DrawableCatchHitObject + { + public TestDrawableCatchHitObject(CatchHitObject hitObject) + : base(hitObject) + { + AccentColour.Value = Color4.White; + } + } + + private class TestCatchHitObject : CatchHitObject + { + } + } +} From 242a035f7e693dcbd00c0f0d381cd6f16c211f9e Mon Sep 17 00:00:00 2001 From: Lucas A Date: Mon, 3 Aug 2020 21:25:45 +0200 Subject: [PATCH 0385/1782] Apply review suggestions. --- .../Gameplay/TestSceneOverlayActivation.cs | 4 +-- osu.Game/Screens/Play/Player.cs | 25 +++++++++++-------- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneOverlayActivation.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneOverlayActivation.cs index 107a3a2a4b..9e93cf363d 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneOverlayActivation.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneOverlayActivation.cs @@ -14,11 +14,11 @@ namespace osu.Game.Tests.Visual.Gameplay private OverlayTestPlayer testPlayer; [Resolved] - private OsuConfigManager mng { get; set; } + private OsuConfigManager config { get; set; } public override void SetUpSteps() { - AddStep("disable overlay activation during gameplay", () => mng.Set(OsuSetting.GameplayDisableOverlayActivation, true)); + AddStep("disable overlay activation during gameplay", () => config.Set(OsuSetting.GameplayDisableOverlayActivation, true)); base.SetUpSteps(); } diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 45a9b442be..7906f5bfe1 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -90,7 +90,10 @@ namespace osu.Game.Screens.Play private SkipOverlay skipOverlay; - protected readonly Bindable OverlayActivationMode = new Bindable(OverlayActivation.Disabled); + /// + /// The current activation mode for overlays. + /// + protected readonly Bindable OverlayActivationMode = new Bindable(OverlayActivation.UserTriggered); protected ScoreProcessor ScoreProcessor { get; private set; } @@ -208,15 +211,9 @@ namespace osu.Game.Screens.Play if (game != null) OverlayActivationMode.BindTo(game.OverlayActivationMode); - gameplayOverlaysDisabled.ValueChanged += disabled => - { - if (DrawableRuleset.HasReplayLoaded.Value) - OverlayActivationMode.Value = OverlayActivation.UserTriggered; - else - OverlayActivationMode.Value = disabled.NewValue && !DrawableRuleset.IsPaused.Value ? OverlayActivation.Disabled : OverlayActivation.UserTriggered; - }; - DrawableRuleset.IsPaused.BindValueChanged(_ => gameplayOverlaysDisabled.TriggerChange()); - DrawableRuleset.HasReplayLoaded.BindValueChanged(_ => gameplayOverlaysDisabled.TriggerChange()); + gameplayOverlaysDisabled.ValueChanged += disabled => updateOverlayActivationMode(); + DrawableRuleset.IsPaused.BindValueChanged(_ => updateOverlayActivationMode()); + DrawableRuleset.HasReplayLoaded.BindValueChanged(_ => updateOverlayActivationMode()); DrawableRuleset.HasReplayLoaded.BindValueChanged(_ => updatePauseOnFocusLostState(), true); @@ -362,6 +359,14 @@ namespace osu.Game.Screens.Play HUDOverlay.KeyCounter.IsCounting = !isBreakTime.NewValue; } + private void updateOverlayActivationMode() + { + if (DrawableRuleset.HasReplayLoaded.Value) + OverlayActivationMode.Value = OverlayActivation.UserTriggered; + else + OverlayActivationMode.Value = gameplayOverlaysDisabled.Value && !DrawableRuleset.IsPaused.Value ? OverlayActivation.Disabled : OverlayActivation.UserTriggered; + } + private void updatePauseOnFocusLostState() => HUDOverlay.HoldToQuit.PauseOnFocusLost = PauseOnFocusLost && !DrawableRuleset.HasReplayLoaded.Value From 30c7a6f6a72d9c711c32e4dc351c6d74ed39a6b2 Mon Sep 17 00:00:00 2001 From: Lucas A Date: Mon, 3 Aug 2020 21:33:18 +0200 Subject: [PATCH 0386/1782] Fix CI issue and use method instead of triggering change on bindable. --- 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 7906f5bfe1..819942e6af 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -211,7 +211,7 @@ namespace osu.Game.Screens.Play if (game != null) OverlayActivationMode.BindTo(game.OverlayActivationMode); - gameplayOverlaysDisabled.ValueChanged += disabled => updateOverlayActivationMode(); + gameplayOverlaysDisabled.BindValueChanged(_ => updateOverlayActivationMode()); DrawableRuleset.IsPaused.BindValueChanged(_ => updateOverlayActivationMode()); DrawableRuleset.HasReplayLoaded.BindValueChanged(_ => updateOverlayActivationMode()); @@ -654,7 +654,7 @@ namespace osu.Game.Screens.Play foreach (var mod in Mods.Value.OfType()) mod.ApplyToHUD(HUDOverlay); - gameplayOverlaysDisabled.TriggerChange(); + updateOverlayActivationMode(); } public override void OnSuspending(IScreen next) From 4dbf695bca47f62ef35bc8724ae64871a752720c Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 4 Aug 2020 00:04:00 +0300 Subject: [PATCH 0387/1782] Fix wrong ordering --- osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs index b5c040f80d..d4a1740c12 100644 --- a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs @@ -51,8 +51,8 @@ namespace osu.Game.Rulesets.Catch.UI comboDisplay = new CatchComboDisplay { - AutoSizeAxes = Axes.Both, RelativeSizeAxes = Axes.None, + AutoSizeAxes = Axes.Both, Anchor = Anchor.CentreLeft, Origin = Anchor.Centre, Y = 30f, From 22b52d63c7767f4cc9d75c4ff249cf5d5e6828c0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 4 Aug 2020 20:51:59 +0900 Subject: [PATCH 0388/1782] 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 13b4b6ebbb..924e9c4a16 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,6 +52,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 745555e0e2..627c2f3d33 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -24,7 +24,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index f1080f0c8b..f443937017 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -80,7 +80,7 @@ - + From d7e82efb671016649e13a1afcaec640691567392 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 4 Aug 2020 21:16:59 +0900 Subject: [PATCH 0389/1782] Fix tests --- .../Visual/UserInterface/TestSceneLogoTrackingContainer.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneLogoTrackingContainer.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneLogoTrackingContainer.cs index 010e4330d7..5582cc6826 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneLogoTrackingContainer.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneLogoTrackingContainer.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Linq; using NUnit.Framework; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -263,7 +264,7 @@ namespace osu.Game.Tests.Visual.UserInterface private void moveLogoFacade() { - if (logoFacade?.Transforms.Count == 0 && transferContainer?.Transforms.Count == 0) + if (!(logoFacade?.Transforms).Any() && !(transferContainer?.Transforms).Any()) { Random random = new Random(); trackingContainer.Delay(500).MoveTo(new Vector2(random.Next(0, (int)logo.Parent.DrawWidth), random.Next(0, (int)logo.Parent.DrawHeight)), 300); From 9fb7b8f3d8b6e1c83c0de13c3d4eb1af8d4dc7dd Mon Sep 17 00:00:00 2001 From: Sebastian Krajewski Date: Tue, 4 Aug 2020 15:43:33 +0200 Subject: [PATCH 0390/1782] Rename the property to "AlignWithStoryboard" --- osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs | 2 +- .../UI/OsuPlayfieldAdjustmentContainer.cs | 11 ++++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs b/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs index e6e37fd58e..b2299398e1 100644 --- a/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs @@ -35,7 +35,7 @@ namespace osu.Game.Rulesets.Osu.UI protected override PassThroughInputManager CreateInputManager() => new OsuInputManager(Ruleset.RulesetInfo); - public override PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => new OsuPlayfieldAdjustmentContainer { PlayfieldShift = true }; + public override PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => new OsuPlayfieldAdjustmentContainer { AlignWithStoryboard = true }; protected override ResumeOverlay CreateResumeOverlay() => new OsuResumeOverlay(); diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfieldAdjustmentContainer.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfieldAdjustmentContainer.cs index 700601dbfd..0d1a5a8304 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfieldAdjustmentContainer.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfieldAdjustmentContainer.cs @@ -11,16 +11,17 @@ namespace osu.Game.Rulesets.Osu.UI public class OsuPlayfieldAdjustmentContainer : PlayfieldAdjustmentContainer { protected override Container Content => content; - private readonly Container content; + private readonly ScalingContainer content; private const float playfield_size_adjust = 0.8f; /// - /// Whether an osu-stable playfield offset should be applied (8 osu!pixels) + /// When true, an offset is applied to allow alignment with historical storyboards displayed in the same parent space. + /// This will shift the playfield downwards slightly. /// - public bool PlayfieldShift + public bool AlignWithStoryboard { - set => ((ScalingContainer)content).PlayfieldShift = value; + set => content.PlayfieldShift = value; } public OsuPlayfieldAdjustmentContainer() @@ -65,7 +66,7 @@ namespace osu.Game.Rulesets.Osu.UI // Scale = 819.2 / 512 // Scale = 1.6 Scale = new Vector2(Parent.ChildSize.X / OsuPlayfield.BASE_SIZE.X); - Position = new Vector2(0, (PlayfieldShift ? 8f : 0f) * Parent.ChildSize.Y / OsuPlayfield.BASE_SIZE.Y); + Position = new Vector2(0, (PlayfieldShift ? 8f : 0f) * Scale.X); // Size = 0.625 Size = Vector2.Divide(Vector2.One, Scale); } From 24bc9b33b1437cf845834743c7019a5429773b46 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 5 Aug 2020 15:40:45 +0900 Subject: [PATCH 0391/1782] Always place spinners behind hitcircles/sliders --- osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index 600efefca3..4ef9bbe091 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -23,7 +23,8 @@ namespace osu.Game.Rulesets.Osu.UI { public class OsuPlayfield : Playfield { - private readonly ApproachCircleProxyContainer approachCircles; + private readonly ProxyContainer approachCircles; + private readonly ProxyContainer spinnerProxies; private readonly JudgementContainer judgementLayer; private readonly FollowPointRenderer followPoints; private readonly OrderedHitPolicy hitPolicy; @@ -38,6 +39,10 @@ namespace osu.Game.Rulesets.Osu.UI { InternalChildren = new Drawable[] { + spinnerProxies = new ProxyContainer + { + RelativeSizeAxes = Axes.Both + }, followPoints = new FollowPointRenderer { RelativeSizeAxes = Axes.Both, @@ -54,7 +59,7 @@ namespace osu.Game.Rulesets.Osu.UI { Child = HitObjectContainer, }, - approachCircles = new ApproachCircleProxyContainer + approachCircles = new ProxyContainer { RelativeSizeAxes = Axes.Both, Depth = -1, @@ -76,6 +81,9 @@ namespace osu.Game.Rulesets.Osu.UI h.OnNewResult += onNewResult; h.OnLoadComplete += d => { + if (d is DrawableSpinner) + spinnerProxies.Add(d.CreateProxy()); + if (d is IDrawableHitObjectWithProxiedApproach c) approachCircles.Add(c.ProxiedLayer.CreateProxy()); }; @@ -113,9 +121,9 @@ namespace osu.Game.Rulesets.Osu.UI public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => HitObjectContainer.ReceivePositionalInputAt(screenSpacePos); - private class ApproachCircleProxyContainer : LifetimeManagementContainer + private class ProxyContainer : LifetimeManagementContainer { - public void Add(Drawable approachCircleProxy) => AddInternal(approachCircleProxy); + public void Add(Drawable proxy) => AddInternal(proxy); } private class DrawableJudgementPool : DrawablePool From 71895964f408c1fec6c8b6ca8155ca03aca0e044 Mon Sep 17 00:00:00 2001 From: Lucas A Date: Wed, 5 Aug 2020 11:21:09 +0200 Subject: [PATCH 0392/1782] Refactor overlay activation logic and reword tip. --- osu.Game/Screens/Menu/Disclaimer.cs | 2 +- osu.Game/Screens/Play/Player.cs | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Menu/Disclaimer.cs b/osu.Game/Screens/Menu/Disclaimer.cs index 986de1edf0..fcb9aacd76 100644 --- a/osu.Game/Screens/Menu/Disclaimer.cs +++ b/osu.Game/Screens/Menu/Disclaimer.cs @@ -190,7 +190,7 @@ namespace osu.Game.Screens.Menu { "You can press Ctrl-T anywhere in the game to toggle the toolbar!", "You can press Ctrl-O anywhere in the game to access options!", - "All settings are dynamic and take effect in real-time. Try changing the skin while playing!", + "All settings are dynamic and take effect in real-time. Try pausing and changing the skin while playing!", "New features are coming online every update. Make sure to stay up-to-date!", "If you find the UI too large or small, try adjusting UI scale in settings!", "Try adjusting the \"Screen Scaling\" mode to change your gameplay or UI area, even in fullscreen!", diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 819942e6af..8f8128abfc 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -361,10 +361,12 @@ namespace osu.Game.Screens.Play private void updateOverlayActivationMode() { - if (DrawableRuleset.HasReplayLoaded.Value) + bool canTriggerOverlays = DrawableRuleset.IsPaused.Value || !gameplayOverlaysDisabled.Value; + + if (DrawableRuleset.HasReplayLoaded.Value || canTriggerOverlays) OverlayActivationMode.Value = OverlayActivation.UserTriggered; else - OverlayActivationMode.Value = gameplayOverlaysDisabled.Value && !DrawableRuleset.IsPaused.Value ? OverlayActivation.Disabled : OverlayActivation.UserTriggered; + OverlayActivationMode.Value = OverlayActivation.Disabled; } private void updatePauseOnFocusLostState() => From bb73489ae5947c4be39ecaca7a3634f4b49c6db1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 5 Aug 2020 18:44:34 +0900 Subject: [PATCH 0393/1782] Fix very short spinners being impossible to complete --- .../TestSceneSpinner.cs | 40 ++++++++++++++----- .../Objects/Drawables/DrawableSpinner.cs | 13 +++++- osu.Game.Rulesets.Osu/Objects/Spinner.cs | 3 +- 3 files changed, 44 insertions(+), 12 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinner.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinner.cs index b57561f3e1..be92a25dbe 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinner.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinner.cs @@ -17,32 +17,54 @@ namespace osu.Game.Rulesets.Osu.Tests { private int depthIndex; - public TestSceneSpinner() + private TestDrawableSpinner drawableSpinner; + + [TestCase(false)] + [TestCase(true)] + public void TestVariousSpinners(bool autoplay) { AddStep("Miss Big", () => SetContents(() => testSingle(2))); AddStep("Miss Medium", () => SetContents(() => testSingle(5))); AddStep("Miss Small", () => SetContents(() => testSingle(7))); - AddStep("Hit Big", () => SetContents(() => testSingle(2, true))); - AddStep("Hit Medium", () => SetContents(() => testSingle(5, true))); - AddStep("Hit Small", () => SetContents(() => testSingle(7, true))); + AddStep("Hit Big", () => SetContents(() => testSingle(2, autoplay))); + AddStep("Hit Medium", () => SetContents(() => testSingle(5, autoplay))); + AddStep("Hit Small", () => SetContents(() => testSingle(7, autoplay))); } - private Drawable testSingle(float circleSize, bool auto = false) + [TestCase(false)] + [TestCase(true)] + public void TestLongSpinner(bool autoplay) { - var spinner = new Spinner { StartTime = Time.Current + 2000, EndTime = Time.Current + 5000 }; + AddStep("Very short spinner", () => SetContents(() => testSingle(5, autoplay, 2000))); + AddUntilStep("Wait for completion", () => drawableSpinner.Result.HasResult); + AddUntilStep("Check correct progress", () => drawableSpinner.Progress == (autoplay ? 1 : 0)); + } + + [TestCase(false)] + [TestCase(true)] + public void TestSuperShortSpinner(bool autoplay) + { + AddStep("Very short spinner", () => SetContents(() => testSingle(5, autoplay, 200))); + AddUntilStep("Wait for completion", () => drawableSpinner.Result.HasResult); + AddUntilStep("Short spinner implicitly completes", () => drawableSpinner.Progress == 1); + } + + private Drawable testSingle(float circleSize, bool auto = false, double length = 3000) + { + var spinner = new Spinner { StartTime = Time.Current + 2000, EndTime = Time.Current + +2000 + length }; spinner.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty { CircleSize = circleSize }); - var drawable = new TestDrawableSpinner(spinner, auto) + drawableSpinner = new TestDrawableSpinner(spinner, auto) { Anchor = Anchor.Centre, Depth = depthIndex++ }; foreach (var mod in SelectedMods.Value.OfType()) - mod.ApplyToDrawableHitObjects(new[] { drawable }); + mod.ApplyToDrawableHitObjects(new[] { drawableSpinner }); - return drawable; + return drawableSpinner; } private class TestDrawableSpinner : DrawableSpinner diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index 68516bedf8..7363da0de8 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -175,7 +175,18 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables /// /// The completion progress of this spinner from 0..1 (clamped). /// - public float Progress => Math.Clamp(RotationTracker.CumulativeRotation / 360 / Spinner.SpinsRequired, 0, 1); + public float Progress + { + get + { + if (Spinner.SpinsRequired == 0) + // some spinners are so short they can't require an integer spin count. + // these become implicitly hit. + return 1; + + return Math.Clamp(RotationTracker.CumulativeRotation / 360 / Spinner.SpinsRequired, 0, 1); + } + } protected override void CheckForResult(bool userTriggered, double timeOffset) { diff --git a/osu.Game.Rulesets.Osu/Objects/Spinner.cs b/osu.Game.Rulesets.Osu/Objects/Spinner.cs index 619b49926e..1658a4e7c2 100644 --- a/osu.Game.Rulesets.Osu/Objects/Spinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Spinner.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.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Judgements; @@ -45,7 +44,7 @@ namespace osu.Game.Rulesets.Osu.Objects double minimumRotationsPerSecond = stable_matching_fudge * BeatmapDifficulty.DifficultyRange(difficulty.OverallDifficulty, 3, 5, 7.5); - SpinsRequired = (int)Math.Max(1, (secondsDuration * minimumRotationsPerSecond)); + SpinsRequired = (int)(secondsDuration * minimumRotationsPerSecond); MaximumBonusSpins = (int)((maximum_rotations_per_second - minimumRotationsPerSecond) * secondsDuration); } From 3916d98e5292db1546b4b9ced3bcea8c27ae887f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 5 Aug 2020 18:50:37 +0900 Subject: [PATCH 0394/1782] Add comment for clarity --- osu.Game/Overlays/NewsOverlay.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Overlays/NewsOverlay.cs b/osu.Game/Overlays/NewsOverlay.cs index f8666e22c5..09fb445b1f 100644 --- a/osu.Game/Overlays/NewsOverlay.cs +++ b/osu.Game/Overlays/NewsOverlay.cs @@ -67,6 +67,8 @@ namespace osu.Game.Overlays protected override void LoadComplete() { base.LoadComplete(); + + // should not be run until first pop-in to avoid requesting data before user views. article.BindValueChanged(onArticleChanged); } From bf1bb3267420e64400d8991c34bb2b65f026c9c5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 5 Aug 2020 19:06:58 +0900 Subject: [PATCH 0395/1782] Add missing toolbar tooltips for right-hand icons --- osu.Game/Overlays/Toolbar/ToolbarBeatmapListingButton.cs | 2 ++ osu.Game/Overlays/Toolbar/ToolbarChangelogButton.cs | 2 ++ osu.Game/Overlays/Toolbar/ToolbarChatButton.cs | 2 ++ osu.Game/Overlays/Toolbar/ToolbarMusicButton.cs | 2 ++ osu.Game/Overlays/Toolbar/ToolbarNewsButton.cs | 2 ++ osu.Game/Overlays/Toolbar/ToolbarRankingsButton.cs | 2 ++ osu.Game/Overlays/Toolbar/ToolbarSocialButton.cs | 2 ++ 7 files changed, 14 insertions(+) diff --git a/osu.Game/Overlays/Toolbar/ToolbarBeatmapListingButton.cs b/osu.Game/Overlays/Toolbar/ToolbarBeatmapListingButton.cs index eecb368ee9..3c38fdd207 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarBeatmapListingButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarBeatmapListingButton.cs @@ -11,6 +11,8 @@ namespace osu.Game.Overlays.Toolbar public ToolbarBeatmapListingButton() { SetIcon(OsuIcon.ChevronDownCircle); + TooltipMain = "Beatmap Listing"; + TooltipSub = "Browse for new beatmaps"; } [BackgroundDependencyLoader(true)] diff --git a/osu.Game/Overlays/Toolbar/ToolbarChangelogButton.cs b/osu.Game/Overlays/Toolbar/ToolbarChangelogButton.cs index 84210e27a4..c88b418853 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarChangelogButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarChangelogButton.cs @@ -11,6 +11,8 @@ namespace osu.Game.Overlays.Toolbar public ToolbarChangelogButton() { SetIcon(FontAwesome.Solid.Bullhorn); + TooltipMain = "Changelog"; + TooltipSub = "Track recent dev updates in the osu! ecosystem"; } [BackgroundDependencyLoader(true)] diff --git a/osu.Game/Overlays/Toolbar/ToolbarChatButton.cs b/osu.Game/Overlays/Toolbar/ToolbarChatButton.cs index ad0e5be551..ec7da54571 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarChatButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarChatButton.cs @@ -11,6 +11,8 @@ namespace osu.Game.Overlays.Toolbar public ToolbarChatButton() { SetIcon(FontAwesome.Solid.Comments); + TooltipMain = "Chat"; + TooltipSub = "Join the real-time discussion"; } [BackgroundDependencyLoader(true)] diff --git a/osu.Game/Overlays/Toolbar/ToolbarMusicButton.cs b/osu.Game/Overlays/Toolbar/ToolbarMusicButton.cs index b29aec5842..712da12208 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarMusicButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarMusicButton.cs @@ -11,6 +11,8 @@ namespace osu.Game.Overlays.Toolbar public ToolbarMusicButton() { Icon = FontAwesome.Solid.Music; + TooltipMain = "Now playing"; + TooltipSub = "Manage the currently playing track"; } [BackgroundDependencyLoader(true)] diff --git a/osu.Game/Overlays/Toolbar/ToolbarNewsButton.cs b/osu.Game/Overlays/Toolbar/ToolbarNewsButton.cs index e813a3f4cb..106c67a041 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarNewsButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarNewsButton.cs @@ -11,6 +11,8 @@ namespace osu.Game.Overlays.Toolbar public ToolbarNewsButton() { Icon = FontAwesome.Solid.Newspaper; + TooltipMain = "News"; + TooltipSub = "Get up-to-date on community happenings"; } [BackgroundDependencyLoader(true)] diff --git a/osu.Game/Overlays/Toolbar/ToolbarRankingsButton.cs b/osu.Game/Overlays/Toolbar/ToolbarRankingsButton.cs index cbd097696d..c026ce99fe 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarRankingsButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarRankingsButton.cs @@ -11,6 +11,8 @@ namespace osu.Game.Overlays.Toolbar public ToolbarRankingsButton() { SetIcon(FontAwesome.Regular.ChartBar); + TooltipMain = "Ranking"; + TooltipSub = "Find out who's the best right now"; } [BackgroundDependencyLoader(true)] diff --git a/osu.Game/Overlays/Toolbar/ToolbarSocialButton.cs b/osu.Game/Overlays/Toolbar/ToolbarSocialButton.cs index f6646eb81d..0dbb552c15 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarSocialButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarSocialButton.cs @@ -11,6 +11,8 @@ namespace osu.Game.Overlays.Toolbar public ToolbarSocialButton() { Icon = FontAwesome.Solid.Users; + TooltipMain = "Friends"; + TooltipSub = "Interact with those close to you"; } [BackgroundDependencyLoader(true)] From 84f6b7608cf83a76db915d148d6c7f2090290742 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 5 Aug 2020 20:05:53 +0300 Subject: [PATCH 0396/1782] Remove misleading ExpandNumberPiece lookup --- osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs | 5 ----- osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs | 1 - 2 files changed, 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs index 80602dfa40..81d1d05b66 100644 --- a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs @@ -6,7 +6,6 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Skinning; using osuTK; -using static osu.Game.Skinning.LegacySkinConfiguration; namespace osu.Game.Rulesets.Osu.Skinning { @@ -139,10 +138,6 @@ namespace osu.Game.Rulesets.Osu.Skinning // HitCircleOverlayAboveNumer (with typo) should still be supported for now. return Source.GetConfig(OsuSkinConfiguration.HitCircleOverlayAboveNumber) ?? Source.GetConfig(OsuSkinConfiguration.HitCircleOverlayAboveNumer); - - case OsuSkinConfiguration.ExpandNumberPiece: - bool expand = !(source.GetConfig(LegacySetting.Version)?.Value >= 2.0m); - return SkinUtils.As(new BindableBool(expand)); } break; diff --git a/osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs b/osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs index c04312a2c3..154160fdb5 100644 --- a/osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs +++ b/osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs @@ -7,7 +7,6 @@ namespace osu.Game.Rulesets.Osu.Skinning { HitCirclePrefix, HitCircleOverlap, - ExpandNumberPiece, SliderBorderSize, SliderPathRadius, AllowSliderBallTint, From 1ab6110c05e7fc4e0bbc18ec3847636e847780b2 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 5 Aug 2020 20:07:05 +0300 Subject: [PATCH 0397/1782] Apply fade out to the number piece with quarter the pieces duration --- .../Skinning/LegacyMainCirclePiece.cs | 67 ++++++++++++------- 1 file changed, 43 insertions(+), 24 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs b/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs index fe28e69ff2..41fe170ae0 100644 --- a/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs +++ b/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs @@ -14,6 +14,7 @@ using osu.Game.Rulesets.Osu.Objects; using osu.Game.Skinning; using osuTK; using osuTK.Graphics; +using static osu.Game.Skinning.LegacySkinConfiguration; namespace osu.Game.Rulesets.Osu.Skinning { @@ -28,7 +29,9 @@ namespace osu.Game.Rulesets.Osu.Skinning Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2); } + private Container circlePieces; private Sprite hitCircleSprite, hitCircleOverlay; + private SkinnableSpriteText hitCircleText; private readonly IBindable state = new Bindable(); @@ -45,12 +48,27 @@ namespace osu.Game.Rulesets.Osu.Skinning InternalChildren = new Drawable[] { - hitCircleSprite = new Sprite + circlePieces = new Container { - Texture = getTextureWithFallback(string.Empty), - Colour = drawableObject.AccentColour.Value, Anchor = Anchor.Centre, Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Children = new[] + { + hitCircleSprite = new Sprite + { + Texture = getTextureWithFallback(string.Empty), + Colour = drawableObject.AccentColour.Value, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + hitCircleOverlay = new Sprite + { + Texture = getTextureWithFallback("overlay"), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + } + } }, hitCircleText = new SkinnableSpriteText(new OsuSkinComponent(OsuSkinComponents.HitCircleText), _ => new OsuSpriteText { @@ -61,31 +79,16 @@ namespace osu.Game.Rulesets.Osu.Skinning Anchor = Anchor.Centre, Origin = Anchor.Centre, }, - hitCircleOverlay = new Sprite - { - Texture = getTextureWithFallback("overlay"), - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - } }; bool overlayAboveNumber = skin.GetConfig(OsuSkinConfiguration.HitCircleOverlayAboveNumber)?.Value ?? true; - if (!overlayAboveNumber) - ChangeInternalChildDepth(hitCircleText, -float.MaxValue); + if (overlayAboveNumber) + AddInternal(hitCircleOverlay.CreateProxy()); state.BindTo(drawableObject.State); accentColour.BindTo(drawableObject.AccentColour); indexInCurrentCombo.BindTo(osuObject.IndexInCurrentComboBindable); - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - state.BindValueChanged(updateState, true); - accentColour.BindValueChanged(colour => hitCircleSprite.Colour = colour.NewValue, true); - indexInCurrentCombo.BindValueChanged(index => hitCircleText.Text = (index.NewValue + 1).ToString(), true); Texture getTextureWithFallback(string name) { @@ -98,6 +101,15 @@ namespace osu.Game.Rulesets.Osu.Skinning } } + protected override void LoadComplete() + { + base.LoadComplete(); + + state.BindValueChanged(updateState, true); + accentColour.BindValueChanged(colour => hitCircleSprite.Colour = colour.NewValue, true); + indexInCurrentCombo.BindValueChanged(index => hitCircleText.Text = (index.NewValue + 1).ToString(), true); + } + private void updateState(ValueChangedEvent state) { const double legacy_fade_duration = 240; @@ -105,13 +117,20 @@ namespace osu.Game.Rulesets.Osu.Skinning switch (state.NewValue) { case ArmedState.Hit: - this.FadeOut(legacy_fade_duration, Easing.Out); + circlePieces.FadeOut(legacy_fade_duration, Easing.Out); + circlePieces.ScaleTo(1.4f, legacy_fade_duration, Easing.Out); - hitCircleSprite.ScaleTo(1.4f, legacy_fade_duration, Easing.Out); - hitCircleOverlay.ScaleTo(1.4f, legacy_fade_duration, Easing.Out); + var legacyVersion = skin.GetConfig(LegacySetting.Version)?.Value; - if (skin.GetConfig(OsuSkinConfiguration.ExpandNumberPiece)?.Value ?? true) + if (legacyVersion >= 2.0m) + // legacy skins of version 2.0 and newer apply immediate fade out to the number piece and only that. + hitCircleText.FadeOut(legacy_fade_duration / 4, Easing.Out); + else + { + // old skins scale and fade it normally along other pieces. + hitCircleText.FadeOut(legacy_fade_duration, Easing.Out); hitCircleText.ScaleTo(1.4f, legacy_fade_duration, Easing.Out); + } break; } From 43161697f88983bc271715e792c7a89eed9631d0 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 5 Aug 2020 23:41:57 +0300 Subject: [PATCH 0398/1782] Fix wrong english --- osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs b/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs index 41fe170ae0..74a2cb7dc5 100644 --- a/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs +++ b/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs @@ -123,7 +123,7 @@ namespace osu.Game.Rulesets.Osu.Skinning var legacyVersion = skin.GetConfig(LegacySetting.Version)?.Value; if (legacyVersion >= 2.0m) - // legacy skins of version 2.0 and newer apply immediate fade out to the number piece and only that. + // legacy skins of version 2.0 and newer only apply very short fade out to the number piece. hitCircleText.FadeOut(legacy_fade_duration / 4, Easing.Out); else { From 9465e7abe10e22482d598f9529d43a9b7468706f Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 5 Aug 2020 23:45:00 +0300 Subject: [PATCH 0399/1782] Rename sprites container to "circleSprites" --- osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs b/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs index 74a2cb7dc5..9fbd9f50b4 100644 --- a/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs +++ b/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs @@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Osu.Skinning Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2); } - private Container circlePieces; + private Container circleSprites; private Sprite hitCircleSprite, hitCircleOverlay; private SkinnableSpriteText hitCircleText; @@ -48,7 +48,7 @@ namespace osu.Game.Rulesets.Osu.Skinning InternalChildren = new Drawable[] { - circlePieces = new Container + circleSprites = new Container { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -117,8 +117,8 @@ namespace osu.Game.Rulesets.Osu.Skinning switch (state.NewValue) { case ArmedState.Hit: - circlePieces.FadeOut(legacy_fade_duration, Easing.Out); - circlePieces.ScaleTo(1.4f, legacy_fade_duration, Easing.Out); + circleSprites.FadeOut(legacy_fade_duration, Easing.Out); + circleSprites.ScaleTo(1.4f, legacy_fade_duration, Easing.Out); var legacyVersion = skin.GetConfig(LegacySetting.Version)?.Value; From e3f314349abc74ebc3ef6e1774045325fd763a75 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 6 Aug 2020 12:27:30 +0900 Subject: [PATCH 0400/1782] Don't use title case Co-authored-by: Joseph Madamba --- osu.Game/Overlays/Toolbar/ToolbarBeatmapListingButton.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Toolbar/ToolbarBeatmapListingButton.cs b/osu.Game/Overlays/Toolbar/ToolbarBeatmapListingButton.cs index 3c38fdd207..64430c77ac 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarBeatmapListingButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarBeatmapListingButton.cs @@ -11,7 +11,7 @@ namespace osu.Game.Overlays.Toolbar public ToolbarBeatmapListingButton() { SetIcon(OsuIcon.ChevronDownCircle); - TooltipMain = "Beatmap Listing"; + TooltipMain = "Beatmap listing"; TooltipSub = "Browse for new beatmaps"; } From d5324be07d00ff9276831f2263ac326efa020f35 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 6 Aug 2020 12:33:40 +0900 Subject: [PATCH 0401/1782] Fix malformed testcase --- osu.Game.Rulesets.Osu.Tests/TestSceneSpinner.cs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinner.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinner.cs index be92a25dbe..ed89a4c991 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinner.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinner.cs @@ -23,12 +23,10 @@ namespace osu.Game.Rulesets.Osu.Tests [TestCase(true)] public void TestVariousSpinners(bool autoplay) { - AddStep("Miss Big", () => SetContents(() => testSingle(2))); - AddStep("Miss Medium", () => SetContents(() => testSingle(5))); - AddStep("Miss Small", () => SetContents(() => testSingle(7))); - AddStep("Hit Big", () => SetContents(() => testSingle(2, autoplay))); - AddStep("Hit Medium", () => SetContents(() => testSingle(5, autoplay))); - AddStep("Hit Small", () => SetContents(() => testSingle(7, autoplay))); + string term = autoplay ? "Hit" : "Miss"; + AddStep($"{term} Big", () => SetContents(() => testSingle(2, autoplay))); + AddStep($"{term} Medium", () => SetContents(() => testSingle(5, autoplay))); + AddStep($"{term} Small", () => SetContents(() => testSingle(7, autoplay))); } [TestCase(false)] From 3b15a50f0d343fbf985abc0167d86f04972958df Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 6 Aug 2020 12:34:42 +0900 Subject: [PATCH 0402/1782] Fix unnecessary + character --- osu.Game.Rulesets.Osu.Tests/TestSceneSpinner.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinner.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinner.cs index ed89a4c991..47b3926ceb 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinner.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinner.cs @@ -49,7 +49,13 @@ namespace osu.Game.Rulesets.Osu.Tests private Drawable testSingle(float circleSize, bool auto = false, double length = 3000) { - var spinner = new Spinner { StartTime = Time.Current + 2000, EndTime = Time.Current + +2000 + length }; + const double delay = 2000; + + var spinner = new Spinner + { + StartTime = Time.Current + delay, + EndTime = Time.Current + delay + length + }; spinner.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty { CircleSize = circleSize }); From 9a00ad48c617a4d490ab5420698b2656c3679a45 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 6 Aug 2020 14:43:39 +0900 Subject: [PATCH 0403/1782] Update components to use extension methods --- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs | 1 + osu.Game/Rulesets/UI/DrawableRulesetDependencies.cs | 2 ++ osu.Game/Screens/Play/PauseOverlay.cs | 1 + 3 files changed, 4 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index 7363da0de8..a2a49b5c42 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -4,6 +4,7 @@ using System; using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Audio; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; diff --git a/osu.Game/Rulesets/UI/DrawableRulesetDependencies.cs b/osu.Game/Rulesets/UI/DrawableRulesetDependencies.cs index 168e937256..3f7dc957fb 100644 --- a/osu.Game/Rulesets/UI/DrawableRulesetDependencies.cs +++ b/osu.Game/Rulesets/UI/DrawableRulesetDependencies.cs @@ -117,6 +117,8 @@ namespace osu.Game.Rulesets.UI public void RemoveAdjustment(AdjustableProperty type, BindableNumber adjustBindable) => throw new NotSupportedException(); + public void RemoveAllAdjustments(AdjustableProperty type) => throw new NotImplementedException(); + public BindableNumber Volume => throw new NotSupportedException(); public BindableNumber Balance => throw new NotSupportedException(); diff --git a/osu.Game/Screens/Play/PauseOverlay.cs b/osu.Game/Screens/Play/PauseOverlay.cs index fa917cda32..97f1d1c91d 100644 --- a/osu.Game/Screens/Play/PauseOverlay.cs +++ b/osu.Game/Screens/Play/PauseOverlay.cs @@ -4,6 +4,7 @@ using System; using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Audio; using osu.Framework.Graphics; using osu.Game.Audio; using osu.Game.Graphics; From 641279ec3e8397f23b2f21c200cf99af313d9253 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 6 Aug 2020 14:43:48 +0900 Subject: [PATCH 0404/1782] Make SkinnableSound an IAdjustableAudioComponent --- osu.Game/Skinning/SkinnableSound.cs | 42 ++++++++--------------------- 1 file changed, 11 insertions(+), 31 deletions(-) diff --git a/osu.Game/Skinning/SkinnableSound.cs b/osu.Game/Skinning/SkinnableSound.cs index 27f6c37895..11856fa581 100644 --- a/osu.Game/Skinning/SkinnableSound.cs +++ b/osu.Game/Skinning/SkinnableSound.cs @@ -4,19 +4,18 @@ using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Audio; using osu.Framework.Audio.Track; using osu.Framework.Bindables; using osu.Framework.Extensions.IEnumerableExtensions; -using osu.Framework.Graphics; using osu.Framework.Graphics.Audio; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Transforms; using osu.Game.Audio; using osu.Game.Screens.Play; namespace osu.Game.Skinning { - public class SkinnableSound : SkinReloadableDrawable + public class SkinnableSound : SkinReloadableDrawable, IAdjustableAudioComponent { private readonly ISampleInfo[] hitSamples; @@ -143,36 +142,17 @@ namespace osu.Game.Skinning public BindableNumber Tempo => samplesContainer.Tempo; + public void AddAdjustment(AdjustableProperty type, BindableNumber adjustBindable) + => samplesContainer.AddAdjustment(type, adjustBindable); + + public void RemoveAdjustment(AdjustableProperty type, BindableNumber adjustBindable) + => samplesContainer.RemoveAdjustment(type, adjustBindable); + + public void RemoveAllAdjustments(AdjustableProperty type) + => samplesContainer.RemoveAllAdjustments(type); + public bool IsPlaying => samplesContainer.Any(s => s.Playing); - /// - /// Smoothly adjusts over time. - /// - /// A to which further transforms can be added. - public TransformSequence VolumeTo(double newVolume, double duration = 0, Easing easing = Easing.None) => - samplesContainer.VolumeTo(newVolume, duration, easing); - - /// - /// Smoothly adjusts over time. - /// - /// A to which further transforms can be added. - public TransformSequence BalanceTo(double newBalance, double duration = 0, Easing easing = Easing.None) => - samplesContainer.BalanceTo(newBalance, duration, easing); - - /// - /// Smoothly adjusts over time. - /// - /// A to which further transforms can be added. - public TransformSequence FrequencyTo(double newFrequency, double duration = 0, Easing easing = Easing.None) => - samplesContainer.FrequencyTo(newFrequency, duration, easing); - - /// - /// Smoothly adjusts over time. - /// - /// A to which further transforms can be added. - public TransformSequence TempoTo(double newTempo, double duration = 0, Easing easing = Easing.None) => - samplesContainer.TempoTo(newTempo, duration, easing); - #endregion } } From 6e42b8219c4510b856923aa08712c50dbf37fa87 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 4 Aug 2020 21:53:00 +0900 Subject: [PATCH 0405/1782] Move track to MusicController, compiles --- .../TestSceneHoldNoteInput.cs | 2 +- .../TestSceneOutOfOrderHits.cs | 2 +- .../TestSceneSliderInput.cs | 2 +- .../TestSceneSliderSnaking.cs | 15 +- .../TestSceneSpinnerRotation.cs | 13 +- .../Skinning/TestSceneDrawableTaikoMascot.cs | 4 +- .../Skinning/TestSceneTaikoPlayfield.cs | 2 +- .../Skins/TestSceneBeatmapSkinResources.cs | 3 +- .../Visual/Editing/TimelineTestScene.cs | 11 +- .../TestSceneCompletionCancellation.cs | 15 +- .../Gameplay/TestSceneGameplayRewinding.cs | 15 +- .../TestSceneNightcoreBeatContainer.cs | 4 +- .../Visual/Gameplay/TestScenePause.cs | 2 +- .../Visual/Gameplay/TestScenePlayerLoader.cs | 6 +- .../Visual/Gameplay/TestSceneStoryboard.cs | 10 +- .../Visual/Menus/TestSceneIntroWelcome.cs | 7 +- .../Navigation/TestSceneScreenNavigation.cs | 23 +- .../TestSceneBeatSyncedContainer.cs | 5 +- .../TestSceneNowPlayingOverlay.cs | 4 +- osu.Game/Beatmaps/BeatmapManager.cs | 2 +- .../Beatmaps/BeatmapManager_WorkingBeatmap.cs | 16 -- osu.Game/Beatmaps/IWorkingBeatmap.cs | 5 - osu.Game/Beatmaps/WorkingBeatmap.cs | 22 +- .../Containers/BeatSyncedContainer.cs | 16 +- osu.Game/OsuGame.cs | 23 +- osu.Game/OsuGameBase.cs | 10 - osu.Game/Overlays/Music/PlaylistOverlay.cs | 10 +- osu.Game/Overlays/MusicController.cs | 221 ++++++++++++++++-- osu.Game/Overlays/NowPlayingOverlay.cs | 10 +- osu.Game/Rulesets/Mods/IApplicableToTrack.cs | 2 +- osu.Game/Rulesets/Mods/ModDaycore.cs | 6 +- osu.Game/Rulesets/Mods/ModNightcore.cs | 6 +- osu.Game/Rulesets/Mods/ModRateAdjust.cs | 4 +- osu.Game/Rulesets/Mods/ModTimeRamp.cs | 4 +- .../Edit/Components/BottomBarContainer.cs | 2 - .../Edit/Components/PlaybackControl.cs | 8 +- .../Timelines/Summary/Parts/MarkerPart.cs | 5 +- .../Timelines/Summary/Parts/TimelinePart.cs | 8 +- .../Compose/Components/Timeline/Timeline.cs | 34 +-- .../Timeline/TimelineTickDisplay.cs | 7 +- osu.Game/Screens/Edit/Editor.cs | 8 +- osu.Game/Screens/Edit/EditorClock.cs | 22 +- osu.Game/Screens/Menu/IntroScreen.cs | 8 +- osu.Game/Screens/Menu/IntroTriangles.cs | 2 +- osu.Game/Screens/Menu/IntroWelcome.cs | 6 +- osu.Game/Screens/Menu/LogoVisualisation.cs | 12 +- osu.Game/Screens/Menu/MainMenu.cs | 16 +- osu.Game/Screens/Menu/OsuLogo.cs | 9 +- .../Multi/Match/Components/ReadyButton.cs | 6 +- osu.Game/Screens/Multi/Multiplayer.cs | 23 +- osu.Game/Screens/Play/FailAnimation.cs | 12 +- .../Screens/Play/GameplayClockContainer.cs | 46 +--- osu.Game/Screens/Select/SongSelect.cs | 34 ++- osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs | 2 - osu.Game/Tests/Visual/EditorClockTestScene.cs | 2 +- osu.Game/Tests/Visual/OsuTestScene.cs | 8 +- .../Visual/RateAdjustedBeatmapTestScene.cs | 2 +- 57 files changed, 438 insertions(+), 346 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs index 95072cf4f8..c3e0c277a0 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs @@ -343,7 +343,7 @@ namespace osu.Game.Rulesets.Mania.Tests judgementResults = new List(); }); - AddUntilStep("Beatmap at 0", () => Beatmap.Value.Track.CurrentTime == 0); + AddUntilStep("Beatmap at 0", () => MusicController.CurrentTrackTime == 0); AddUntilStep("Wait until player is loaded", () => currentPlayer.IsCurrentScreen()); AddUntilStep("Wait for completion", () => currentPlayer.ScoreProcessor.HasCompleted.Value); } diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneOutOfOrderHits.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneOutOfOrderHits.cs index 854626d362..dc7e59b40d 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneOutOfOrderHits.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneOutOfOrderHits.cs @@ -385,7 +385,7 @@ namespace osu.Game.Rulesets.Osu.Tests judgementResults = new List(); }); - AddUntilStep("Beatmap at 0", () => Beatmap.Value.Track.CurrentTime == 0); + AddUntilStep("Beatmap at 0", () => MusicController.CurrentTrackTime == 0); AddUntilStep("Wait until player is loaded", () => currentPlayer.IsCurrentScreen()); AddUntilStep("Wait for completion", () => currentPlayer.ScoreProcessor.HasCompleted.Value); } diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs index b543b6fa94..2dffcfeabb 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs @@ -366,7 +366,7 @@ namespace osu.Game.Rulesets.Osu.Tests judgementResults = new List(); }); - AddUntilStep("Beatmap at 0", () => Beatmap.Value.Track.CurrentTime == 0); + AddUntilStep("Beatmap at 0", () => MusicController.CurrentTrackTime == 0); AddUntilStep("Wait until player is loaded", () => currentPlayer.IsCurrentScreen()); AddUntilStep("Wait for completion", () => currentPlayer.ScoreProcessor.HasCompleted.Value); } diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs index a69646507a..cd46e8c545 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs @@ -22,7 +22,6 @@ using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces; using osu.Game.Storyboards; using osuTK; -using static osu.Game.Tests.Visual.OsuTestScene.ClockBackedTestWorkingBeatmap; namespace osu.Game.Rulesets.Osu.Tests { @@ -32,8 +31,6 @@ namespace osu.Game.Rulesets.Osu.Tests [Resolved] private AudioManager audioManager { get; set; } - private TrackVirtualManual track; - protected override bool Autoplay => autoplay; private bool autoplay; @@ -44,11 +41,7 @@ namespace osu.Game.Rulesets.Osu.Tests private const double fade_in_modifier = -1200; protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null) - { - var working = new ClockBackedTestWorkingBeatmap(beatmap, storyboard, new FramedClock(new ManualClock { Rate = 1 }), audioManager); - track = (TrackVirtualManual)working.Track; - return working; - } + => new ClockBackedTestWorkingBeatmap(beatmap, storyboard, new FramedClock(new ManualClock { Rate = 1 }), audioManager); [BackgroundDependencyLoader] private void load(RulesetConfigCache configCache) @@ -72,7 +65,7 @@ namespace osu.Game.Rulesets.Osu.Tests { AddStep("enable autoplay", () => autoplay = true); base.SetUpSteps(); - AddUntilStep("wait for track to start running", () => track.IsRunning); + AddUntilStep("wait for track to start running", () => MusicController.IsPlaying); double startTime = hitObjects[sliderIndex].StartTime; retrieveDrawableSlider(sliderIndex); @@ -97,7 +90,7 @@ namespace osu.Game.Rulesets.Osu.Tests { AddStep("have autoplay", () => autoplay = true); base.SetUpSteps(); - AddUntilStep("wait for track to start running", () => track.IsRunning); + AddUntilStep("wait for track to start running", () => MusicController.IsPlaying); double startTime = hitObjects[sliderIndex].StartTime; retrieveDrawableSlider(sliderIndex); @@ -201,7 +194,7 @@ namespace osu.Game.Rulesets.Osu.Tests private void addSeekStep(double time) { - AddStep($"seek to {time}", () => track.Seek(time)); + AddStep($"seek to {time}", () => MusicController.SeekTo(time)); AddUntilStep("wait for seek to finish", () => Precision.AlmostEquals(time, Player.DrawableRuleset.FrameStableClock.CurrentTime, 100)); } diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs index b46964e8b7..105e19a73c 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs @@ -24,7 +24,6 @@ using osu.Game.Scoring; using osu.Game.Storyboards; using osu.Game.Tests.Visual; using osuTK; -using static osu.Game.Tests.Visual.OsuTestScene.ClockBackedTestWorkingBeatmap; namespace osu.Game.Rulesets.Osu.Tests { @@ -33,18 +32,12 @@ namespace osu.Game.Rulesets.Osu.Tests [Resolved] private AudioManager audioManager { get; set; } - private TrackVirtualManual track; - protected override bool Autoplay => true; protected override TestPlayer CreatePlayer(Ruleset ruleset) => new ScoreExposedPlayer(); protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null) - { - var working = new ClockBackedTestWorkingBeatmap(beatmap, storyboard, new FramedClock(new ManualClock { Rate = 1 }), audioManager); - track = (TrackVirtualManual)working.Track; - return working; - } + => new ClockBackedTestWorkingBeatmap(beatmap, storyboard, new FramedClock(new ManualClock { Rate = 1 }), audioManager); private DrawableSpinner drawableSpinner; private SpriteIcon spinnerSymbol => drawableSpinner.ChildrenOfType().Single(); @@ -54,7 +47,7 @@ namespace osu.Game.Rulesets.Osu.Tests { base.SetUpSteps(); - AddUntilStep("wait for track to start running", () => track.IsRunning); + AddUntilStep("wait for track to start running", () => MusicController.IsPlaying); AddStep("retrieve spinner", () => drawableSpinner = (DrawableSpinner)Player.DrawableRuleset.Playfield.AllHitObjects.First()); } @@ -198,7 +191,7 @@ namespace osu.Game.Rulesets.Osu.Tests private void addSeekStep(double time) { - AddStep($"seek to {time}", () => track.Seek(time)); + AddStep($"seek to {time}", () => MusicController.SeekTo(time)); AddUntilStep("wait for seek to finish", () => Precision.AlmostEquals(time, Player.DrawableRuleset.FrameStableClock.CurrentTime, 100)); } diff --git a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableTaikoMascot.cs b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableTaikoMascot.cs index 47d8a5c012..ba5aef5968 100644 --- a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableTaikoMascot.cs +++ b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableTaikoMascot.cs @@ -175,11 +175,11 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning private void createDrawableRuleset() { - AddUntilStep("wait for beatmap to be loaded", () => Beatmap.Value.Track.IsLoaded); + AddUntilStep("wait for beatmap to be loaded", () => MusicController.TrackLoaded); AddStep("create drawable ruleset", () => { - Beatmap.Value.Track.Start(); + MusicController.Play(true); SetContents(() => { diff --git a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneTaikoPlayfield.cs b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneTaikoPlayfield.cs index 7b7e2c43d1..5c54393fb8 100644 --- a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneTaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneTaikoPlayfield.cs @@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning beatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = 1000 }); - Beatmap.Value.Track.Start(); + MusicController.Play(true); }); AddStep("Load playfield", () => SetContents(() => new TaikoPlayfield(new ControlPointInfo()) diff --git a/osu.Game.Tests/Skins/TestSceneBeatmapSkinResources.cs b/osu.Game.Tests/Skins/TestSceneBeatmapSkinResources.cs index 4d3b73fb32..1a19326ac4 100644 --- a/osu.Game.Tests/Skins/TestSceneBeatmapSkinResources.cs +++ b/osu.Game.Tests/Skins/TestSceneBeatmapSkinResources.cs @@ -3,7 +3,6 @@ using NUnit.Framework; using osu.Framework.Allocation; -using osu.Framework.Audio.Track; using osu.Framework.Testing; using osu.Game.Audio; using osu.Game.Beatmaps; @@ -32,6 +31,6 @@ namespace osu.Game.Tests.Skins public void TestRetrieveOggSample() => AddAssert("sample is non-null", () => beatmap.Skin.GetSample(new SampleInfo("sample")) != null); [Test] - public void TestRetrieveOggTrack() => AddAssert("track is non-null", () => !(beatmap.Track is TrackVirtual)); + public void TestRetrieveOggTrack() => AddAssert("track is non-null", () => !MusicController.IsDummyDevice); } } diff --git a/osu.Game.Tests/Visual/Editing/TimelineTestScene.cs b/osu.Game.Tests/Visual/Editing/TimelineTestScene.cs index fdb8781563..5d6136d9fb 100644 --- a/osu.Game.Tests/Visual/Editing/TimelineTestScene.cs +++ b/osu.Game.Tests/Visual/Editing/TimelineTestScene.cs @@ -3,12 +3,11 @@ 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.Shapes; -using osu.Game.Beatmaps; using osu.Game.Graphics.UserInterface; +using osu.Game.Overlays; using osu.Game.Rulesets.Edit; using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Compose.Components.Timeline; @@ -65,10 +64,10 @@ namespace osu.Game.Tests.Visual.Editing private readonly Drawable marker; [Resolved] - private IBindable beatmap { get; set; } + private EditorClock editorClock { get; set; } [Resolved] - private EditorClock editorClock { get; set; } + private MusicController musicController { get; set; } public AudioVisualiser() { @@ -94,8 +93,8 @@ namespace osu.Game.Tests.Visual.Editing { base.Update(); - if (beatmap.Value.Track.IsLoaded) - marker.X = (float)(editorClock.CurrentTime / beatmap.Value.Track.Length); + if (musicController.TrackLoaded) + marker.X = (float)(editorClock.CurrentTime / musicController.TrackLength); } } diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneCompletionCancellation.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneCompletionCancellation.cs index 79275d70a7..b39cfc3699 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneCompletionCancellation.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneCompletionCancellation.cs @@ -4,7 +4,6 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; -using osu.Framework.Audio.Track; using osu.Framework.Screens; using osu.Framework.Testing; using osu.Framework.Timing; @@ -18,8 +17,6 @@ namespace osu.Game.Tests.Visual.Gameplay { public class TestSceneCompletionCancellation : OsuPlayerTestScene { - private Track track; - [Resolved] private AudioManager audio { get; set; } @@ -34,7 +31,7 @@ namespace osu.Game.Tests.Visual.Gameplay base.SetUpSteps(); // Ensure track has actually running before attempting to seek - AddUntilStep("wait for track to start running", () => track.IsRunning); + AddUntilStep("wait for track to start running", () => MusicController.IsPlaying); } [Test] @@ -73,13 +70,13 @@ namespace osu.Game.Tests.Visual.Gameplay private void complete() { - AddStep("seek to completion", () => track.Seek(5000)); + AddStep("seek to completion", () => MusicController.SeekTo(5000)); AddUntilStep("completion set by processor", () => Player.ScoreProcessor.HasCompleted.Value); } private void cancel() { - AddStep("rewind to cancel", () => track.Seek(4000)); + AddStep("rewind to cancel", () => MusicController.SeekTo(4000)); AddUntilStep("completion cleared by processor", () => !Player.ScoreProcessor.HasCompleted.Value); } @@ -91,11 +88,7 @@ namespace osu.Game.Tests.Visual.Gameplay } protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null) - { - var working = new ClockBackedTestWorkingBeatmap(beatmap, storyboard, new FramedClock(new ManualClock { Rate = 1 }), audio); - track = working.Track; - return working; - } + => new ClockBackedTestWorkingBeatmap(beatmap, storyboard, new FramedClock(new ManualClock { Rate = 1 }), audio); protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) { diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayRewinding.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayRewinding.cs index 2a119f5199..6bdc65078a 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayRewinding.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayRewinding.cs @@ -5,10 +5,10 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; -using osu.Framework.Audio.Track; using osu.Framework.Utils; using osu.Framework.Timing; using osu.Game.Beatmaps; +using osu.Game.Overlays; using osu.Game.Rulesets; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Storyboards; @@ -21,19 +21,16 @@ namespace osu.Game.Tests.Visual.Gameplay [Resolved] private AudioManager audioManager { get; set; } - private Track track; + [Resolved] + private MusicController musicController { get; set; } protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null) - { - var working = new ClockBackedTestWorkingBeatmap(beatmap, storyboard, new FramedClock(new ManualClock { Rate = 1 }), audioManager); - track = working.Track; - return working; - } + => new ClockBackedTestWorkingBeatmap(beatmap, storyboard, new FramedClock(new ManualClock { Rate = 1 }), audioManager); [Test] public void TestNoJudgementsOnRewind() { - AddUntilStep("wait for track to start running", () => track.IsRunning); + AddUntilStep("wait for track to start running", () => MusicController.IsPlaying); 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)); @@ -46,7 +43,7 @@ namespace osu.Game.Tests.Visual.Gameplay private void addSeekStep(double time) { - AddStep($"seek to {time}", () => track.Seek(time)); + AddStep($"seek to {time}", () => MusicController.SeekTo(time)); // Allow a few frames of lenience AddUntilStep("wait for seek to finish", () => Precision.AlmostEquals(time, Player.DrawableRuleset.FrameStableClock.CurrentTime, 100)); diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneNightcoreBeatContainer.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneNightcoreBeatContainer.cs index 951ee1489d..ce99d85e92 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneNightcoreBeatContainer.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneNightcoreBeatContainer.cs @@ -19,8 +19,8 @@ namespace osu.Game.Tests.Visual.Gameplay Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo); - Beatmap.Value.Track.Start(); - Beatmap.Value.Track.Seek(Beatmap.Value.Beatmap.HitObjects.First().StartTime - 1000); + MusicController.Play(true); + MusicController.SeekTo(Beatmap.Value.Beatmap.HitObjects.First().StartTime - 1000); Add(new ModNightcore.NightcoreBeatContainer()); diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs index 420bf29429..8f14de1578 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs @@ -288,7 +288,7 @@ namespace osu.Game.Tests.Visual.Gameplay private void confirmNoTrackAdjustments() { - AddAssert("track has no adjustments", () => Beatmap.Value.Track.AggregateFrequency.Value == 1); + AddAssert("track has no adjustments", () => MusicController.AggregateFrequency.Value == 1); } private void restart() => AddStep("restart", () => Player.Restart()); diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs index 4c73065087..2f86db1b25 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 Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo); foreach (var mod in SelectedMods.Value.OfType()) - mod.ApplyToTrack(Beatmap.Value.Track); + mod.ApplyToTrack(MusicController); InputManager.Child = container = new TestPlayerLoaderContainer( loader = new TestPlayerLoader(() => @@ -77,12 +77,12 @@ namespace osu.Game.Tests.Visual.Gameplay { 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); + AddAssert("mod rate applied", () => MusicController.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); + AddAssert("mod rate still applied", () => MusicController.Rate != 1); } [Test] diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboard.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboard.cs index 9f1492a25f..a4b558fce2 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboard.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboard.cs @@ -87,11 +87,9 @@ namespace osu.Game.Tests.Visual.Gameplay private void restart() { - var track = Beatmap.Value.Track; - - track.Reset(); + MusicController.Reset(); loadStoryboard(Beatmap.Value); - track.Start(); + MusicController.Play(true); } private void loadStoryboard(WorkingBeatmap working) @@ -106,7 +104,7 @@ namespace osu.Game.Tests.Visual.Gameplay storyboard.Passing = false; storyboardContainer.Add(storyboard); - decoupledClock.ChangeSource(working.Track); + decoupledClock.ChangeSource(musicController.GetTrackClock()); } private void loadStoryboardNoVideo() @@ -129,7 +127,7 @@ namespace osu.Game.Tests.Visual.Gameplay storyboard = sb.CreateDrawable(Beatmap.Value); storyboardContainer.Add(storyboard); - decoupledClock.ChangeSource(Beatmap.Value.Track); + decoupledClock.ChangeSource(musicController.GetTrackClock()); } } } diff --git a/osu.Game.Tests/Visual/Menus/TestSceneIntroWelcome.cs b/osu.Game.Tests/Visual/Menus/TestSceneIntroWelcome.cs index 8f20e38494..36f98b7a0c 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneIntroWelcome.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneIntroWelcome.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using NUnit.Framework; -using osu.Framework.Audio.Track; using osu.Framework.Screens; using osu.Game.Screens.Menu; @@ -15,11 +14,9 @@ namespace osu.Game.Tests.Visual.Menus public TestSceneIntroWelcome() { - AddUntilStep("wait for load", () => getTrack() != null); + AddUntilStep("wait for load", () => MusicController.TrackLoaded); - AddAssert("check if menu music loops", () => getTrack().Looping); + AddAssert("check if menu music loops", () => MusicController.Looping); } - - private Track getTrack() => (IntroStack?.CurrentScreen as MainMenu)?.Track; } } diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs index 8ccaca8630..455b7e56e6 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs @@ -4,7 +4,6 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; -using osu.Framework.Audio.Track; using osu.Framework.Graphics.Containers; using osu.Framework.Screens; using osu.Game.Beatmaps; @@ -46,7 +45,6 @@ namespace osu.Game.Tests.Visual.Navigation Player player = null; WorkingBeatmap beatmap() => Game.Beatmap.Value; - Track track() => beatmap().Track; PushAndConfirm(() => new TestSongSelect()); @@ -62,30 +60,27 @@ namespace osu.Game.Tests.Visual.Navigation 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); + AddUntilStep("wait for track stop", () => !MusicController.IsPlaying); + AddAssert("Ensure time before preview point", () => MusicController.CurrentTrackTime < 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); + AddUntilStep("wait for track playing", () => MusicController.IsPlaying); + AddAssert("Ensure time wasn't reset to preview point", () => MusicController.CurrentTrackTime < beatmap().Metadata.PreviewTime); } [Test] public void TestMenuMakesMusic() { - WorkingBeatmap beatmap() => Game.Beatmap.Value; - Track track() => beatmap().Track; - TestSongSelect songSelect = null; PushAndConfirm(() => songSelect = new TestSongSelect()); - AddUntilStep("wait for no track", () => track() is TrackVirtual); + AddUntilStep("wait for no track", () => MusicController.IsDummyDevice); AddStep("return to menu", () => songSelect.Exit()); - AddUntilStep("wait for track", () => !(track() is TrackVirtual) && track().IsRunning); + AddUntilStep("wait for track", () => !MusicController.IsDummyDevice && MusicController.IsPlaying); } [Test] @@ -140,12 +135,12 @@ namespace osu.Game.Tests.Visual.Navigation AddUntilStep("Wait for music controller", () => Game.MusicController.IsLoaded); AddStep("Seek close to end", () => { - Game.MusicController.SeekTo(Game.Beatmap.Value.Track.Length - 1000); - Game.Beatmap.Value.Track.Completed += () => trackCompleted = true; + Game.MusicController.SeekTo(MusicController.TrackLength - 1000); + // MusicController.Completed += () => trackCompleted = true; }); AddUntilStep("Track was completed", () => trackCompleted); - AddUntilStep("Track was restarted", () => Game.Beatmap.Value.Track.IsRunning); + AddUntilStep("Track was restarted", () => MusicController.IsPlaying); } private void pushEscape() => diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs index dd5ceec739..f3fef8c355 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs @@ -71,6 +71,9 @@ namespace osu.Game.Tests.Visual.UserInterface private readonly Box flashLayer; + [Resolved] + private MusicController musicController { get; set; } + public BeatContainer() { RelativeSizeAxes = Axes.X; @@ -165,7 +168,7 @@ namespace osu.Game.Tests.Visual.UserInterface if (timingPoints.Count == 0) return 0; if (timingPoints[^1] == current) - return (int)Math.Ceiling((Beatmap.Value.Track.Length - current.Time) / current.BeatLength); + return (int)Math.Ceiling((musicController.TrackLength - current.Time) / current.BeatLength); return (int)Math.Ceiling((getNextTimingPoint(current).Time - current.Time) / current.BeatLength); } diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneNowPlayingOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneNowPlayingOverlay.cs index 532744a0fc..3ecd8ab550 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneNowPlayingOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneNowPlayingOverlay.cs @@ -80,12 +80,12 @@ namespace osu.Game.Tests.Visual.UserInterface AddStep("Store track", () => currentBeatmap = Beatmap.Value); AddStep(@"Seek track to 6 second", () => musicController.SeekTo(6000)); - AddUntilStep(@"Wait for current time to update", () => currentBeatmap.Track.CurrentTime > 5000); + AddUntilStep(@"Wait for current time to update", () => musicController.CurrentTrackTime > 5000); AddStep(@"Set previous", () => musicController.PreviousTrack()); AddAssert(@"Check beatmap didn't change", () => currentBeatmap == Beatmap.Value); - AddUntilStep("Wait for current time to update", () => currentBeatmap.Track.CurrentTime < 5000); + AddUntilStep("Wait for current time to update", () => musicController.CurrentTrackTime < 5000); AddStep(@"Set previous", () => musicController.PreviousTrack()); AddAssert(@"Check beatmap did change", () => currentBeatmap != Beatmap.Value); diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index b4b341634c..b2329f58ad 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -255,7 +255,7 @@ namespace osu.Game.Beatmaps new LargeTextureStore(host?.CreateTextureLoaderStore(Files.Store)), beatmapInfo, audioManager)); } - previous?.TransferTo(working); + // previous?.TransferTo(working); return working; } } diff --git a/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs b/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs index 39c5ccab27..33945a9eb1 100644 --- a/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs @@ -79,22 +79,6 @@ namespace osu.Game.Beatmaps } } - public override void RecycleTrack() - { - base.RecycleTrack(); - - trackStore?.Dispose(); - trackStore = null; - } - - public override void TransferTo(WorkingBeatmap other) - { - base.TransferTo(other); - - if (other is BeatmapManagerWorkingBeatmap owb && textureStore != null && BeatmapInfo.BackgroundEquals(other.BeatmapInfo)) - owb.textureStore = textureStore; - } - protected override Waveform GetWaveform() { try diff --git a/osu.Game/Beatmaps/IWorkingBeatmap.cs b/osu.Game/Beatmaps/IWorkingBeatmap.cs index 31975157a0..086b7502a2 100644 --- a/osu.Game/Beatmaps/IWorkingBeatmap.cs +++ b/osu.Game/Beatmaps/IWorkingBeatmap.cs @@ -26,11 +26,6 @@ namespace osu.Game.Beatmaps /// Texture Background { get; } - /// - /// Retrieves the audio track for this . - /// - Track Track { get; } - /// /// Retrieves the for the of this . /// diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs index ac399e37c4..171201ca68 100644 --- a/osu.Game/Beatmaps/WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/WorkingBeatmap.cs @@ -40,7 +40,6 @@ namespace osu.Game.Beatmaps BeatmapSetInfo = beatmapInfo.BeatmapSet; Metadata = beatmapInfo.Metadata ?? BeatmapSetInfo?.Metadata ?? new BeatmapMetadata(); - track = new RecyclableLazy(() => GetTrack() ?? GetVirtualTrack(1000)); background = new RecyclableLazy(GetBackground, BackgroundStillValid); waveform = new RecyclableLazy(GetWaveform); storyboard = new RecyclableLazy(GetStoryboard); @@ -250,10 +249,9 @@ namespace osu.Game.Beatmaps protected abstract Texture GetBackground(); private readonly RecyclableLazy background; - public virtual bool TrackLoaded => track.IsResultAvailable; - public Track Track => track.Value; + public Track GetRealTrack() => GetTrack() ?? GetVirtualTrack(1000); + protected abstract Track GetTrack(); - private RecyclableLazy track; public bool WaveformLoaded => waveform.IsResultAvailable; public Waveform Waveform => waveform.Value; @@ -271,22 +269,6 @@ namespace osu.Game.Beatmaps 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. - /// - /// The new beatmap which is being switched to. - public virtual void TransferTo(WorkingBeatmap other) - { - if (track.IsResultAvailable && Track != null && BeatmapInfo.AudioEquals(other.BeatmapInfo)) - other.track = track; - } - - /// - /// Eagerly dispose of the audio track associated with this (if any). - /// Accessing track again will load a fresh instance. - /// - public virtual void RecycleTrack() => track.Recycle(); - ~WorkingBeatmap() { total_count.Value--; diff --git a/osu.Game/Graphics/Containers/BeatSyncedContainer.cs b/osu.Game/Graphics/Containers/BeatSyncedContainer.cs index df063f57d5..2dd28a01dc 100644 --- a/osu.Game/Graphics/Containers/BeatSyncedContainer.cs +++ b/osu.Game/Graphics/Containers/BeatSyncedContainer.cs @@ -7,6 +7,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Overlays; namespace osu.Game.Graphics.Containers { @@ -14,6 +15,9 @@ namespace osu.Game.Graphics.Containers { protected readonly IBindable Beatmap = new Bindable(); + [Resolved] + private MusicController musicController { get; set; } + private int lastBeat; private TimingControlPoint lastTimingPoint; @@ -47,22 +51,18 @@ namespace osu.Game.Graphics.Containers protected override void Update() { - Track track = null; IBeatmap beatmap = null; double currentTrackTime = 0; TimingControlPoint timingPoint = null; EffectControlPoint effectPoint = null; - if (Beatmap.Value.TrackLoaded && Beatmap.Value.BeatmapLoaded) - { - track = Beatmap.Value.Track; + if (musicController.TrackLoaded && Beatmap.Value.BeatmapLoaded) beatmap = Beatmap.Value.Beatmap; - } - if (track != null && beatmap != null && track.IsRunning && track.Length > 0) + if (beatmap != null && musicController.IsPlaying && musicController.TrackLength > 0) { - currentTrackTime = track.CurrentTime + EarlyActivationMilliseconds; + currentTrackTime = musicController.CurrentTrackTime + EarlyActivationMilliseconds; timingPoint = beatmap.ControlPointInfo.TimingPointAt(currentTrackTime); effectPoint = beatmap.ControlPointInfo.EffectPointAt(currentTrackTime); @@ -98,7 +98,7 @@ namespace osu.Game.Graphics.Containers return; using (BeginDelayedSequence(-TimeSinceLastBeat, true)) - OnNewBeat(beatIndex, timingPoint, effectPoint, track?.CurrentAmplitudes ?? ChannelAmplitudes.Empty); + OnNewBeat(beatIndex, timingPoint, effectPoint, musicController.CurrentAmplitudes); lastBeat = beatIndex; lastTimingPoint = timingPoint; diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 26f7c3b93b..929254e8ad 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -431,19 +431,19 @@ namespace osu.Game if (newBeatmap != null) { - newBeatmap.Track.Completed += () => Scheduler.AddOnce(() => trackCompleted(newBeatmap)); + // MusicController.Completed += () => Scheduler.AddOnce(() => trackCompleted(newBeatmap)); newBeatmap.BeginAsyncLoad(); } - void trackCompleted(WorkingBeatmap b) - { - // the source of track completion is the audio thread, so the beatmap may have changed before firing. - if (Beatmap.Value != b) - return; - - if (!Beatmap.Value.Track.Looping && !Beatmap.Disabled) - MusicController.NextTrack(); - } + // void trackCompleted(WorkingBeatmap b) + // { + // // the source of track completion is the audio thread, so the beatmap may have changed before firing. + // if (Beatmap.Value != b) + // return; + // + // if (!MusicController.Looping && !Beatmap.Disabled) + // MusicController.NextTrack(); + // } } private void modsChanged(ValueChangedEvent> mods) @@ -555,6 +555,7 @@ namespace osu.Game BackButton.Receptor receptor; dependencies.CacheAs(idleTracker = new GameIdleTracker(6000)); + dependencies.CacheAs(MusicController = new MusicController()); AddRange(new Drawable[] { @@ -617,7 +618,7 @@ namespace osu.Game loadComponentSingleFile(new OnScreenDisplay(), Add, true); - loadComponentSingleFile(MusicController = new MusicController(), Add, true); + loadComponentSingleFile(MusicController, Add); loadComponentSingleFile(notifications.With(d => { diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 98f60d52d3..24c1f7849c 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -238,16 +238,6 @@ namespace osu.Game Beatmap = new NonNullableBindable(defaultBeatmap); - // ScheduleAfterChildren is safety against something in the current frame accessing the previous beatmap's track - // and potentially causing a reload of it after just unloading. - // Note that the reason for this being added *has* been resolved, so it may be feasible to removed this if required. - 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/Overlays/Music/PlaylistOverlay.cs b/osu.Game/Overlays/Music/PlaylistOverlay.cs index b878aba489..c089158c01 100644 --- a/osu.Game/Overlays/Music/PlaylistOverlay.cs +++ b/osu.Game/Overlays/Music/PlaylistOverlay.cs @@ -30,6 +30,9 @@ namespace osu.Game.Overlays.Music [Resolved] private BeatmapManager beatmaps { get; set; } + [Resolved] + private MusicController musicController { get; set; } + private FilterControl filter; private Playlist list; @@ -80,10 +83,7 @@ namespace osu.Game.Overlays.Music BeatmapInfo toSelect = list.FirstVisibleSet?.Beatmaps?.FirstOrDefault(); if (toSelect != null) - { beatmap.Value = beatmaps.GetWorkingBeatmap(toSelect); - beatmap.Value.Track.Restart(); - } }; } @@ -116,12 +116,12 @@ namespace osu.Game.Overlays.Music { if (set.ID == (beatmap.Value?.BeatmapSetInfo?.ID ?? -1)) { - beatmap.Value?.Track?.Seek(0); + musicController.SeekTo(0); return; } beatmap.Value = beatmaps.GetWorkingBeatmap(set.Beatmaps.First()); - beatmap.Value.Track.Restart(); + musicController.Play(true); } } diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index a990f9a6ab..f5ca5a3a49 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -4,13 +4,18 @@ using System; using System.Collections.Generic; using System.Linq; +using JetBrains.Annotations; 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.Audio; +using osu.Framework.Graphics.Containers; using osu.Framework.Input.Bindings; using osu.Framework.Utils; using osu.Framework.Threading; +using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Input.Bindings; using osu.Game.Overlays.OSD; @@ -21,7 +26,7 @@ namespace osu.Game.Overlays /// /// Handles playback of the global music track. /// - public class MusicController : Component, IKeyBindingHandler + public class MusicController : CompositeDrawable, IKeyBindingHandler, ITrack { [Resolved] private BeatmapManager beatmaps { get; set; } @@ -61,9 +66,23 @@ namespace osu.Game.Overlays [Resolved(canBeNull: true)] private OnScreenDisplay onScreenDisplay { get; set; } + [NotNull] + private readonly TrackContainer trackContainer; + + [CanBeNull] + private DrawableTrack drawableTrack; + + [CanBeNull] + private Track track; + private IBindable> managerUpdated; private IBindable> managerRemoved; + public MusicController() + { + InternalChild = trackContainer = new TrackContainer { RelativeSizeAxes = Axes.Both }; + } + [BackgroundDependencyLoader] private void load() { @@ -95,9 +114,35 @@ namespace osu.Game.Overlays } /// - /// Returns whether the current beatmap track is playing. + /// Returns whether the beatmap track is playing. /// - public bool IsPlaying => current?.Track.IsRunning ?? false; + public bool IsPlaying => drawableTrack?.IsRunning ?? false; + + /// + /// Returns whether the beatmap track is loaded. + /// + public bool TrackLoaded => drawableTrack?.IsLoaded == true; + + /// + /// Returns the current time of the beatmap track. + /// + public double CurrentTrackTime => drawableTrack?.CurrentTime ?? 0; + + /// + /// Returns the length of the beatmap track. + /// + public double TrackLength => drawableTrack?.Length ?? 0; + + public void AddAdjustment(AdjustableProperty type, BindableNumber adjustBindable) + => trackContainer.AddAdjustment(type, adjustBindable); + + public void RemoveAdjustment(AdjustableProperty type, BindableNumber adjustBindable) + => trackContainer.RemoveAdjustment(type, adjustBindable); + + public void Reset() => drawableTrack?.Reset(); + + [CanBeNull] + public IAdjustableClock GetTrackClock() => track; private void beatmapUpdated(ValueChangedEvent> weakSet) { @@ -130,7 +175,7 @@ namespace osu.Game.Overlays seekDelegate = Schedule(() => { if (!beatmap.Disabled) - current?.Track.Seek(position); + drawableTrack?.Seek(position); }); } @@ -142,9 +187,7 @@ namespace osu.Game.Overlays { if (IsUserPaused) return; - var track = current?.Track; - - if (track == null || track is TrackVirtual) + if (drawableTrack == null || drawableTrack.IsDummyDevice) { if (beatmap.Disabled) return; @@ -163,17 +206,15 @@ namespace osu.Game.Overlays /// Whether the operation was successful. public bool Play(bool restart = false) { - var track = current?.Track; - IsUserPaused = false; - if (track == null) + if (drawableTrack == null) return false; if (restart) - track.Restart(); + drawableTrack.Restart(); else if (!IsPlaying) - track.Start(); + drawableTrack.Start(); return true; } @@ -183,11 +224,9 @@ namespace osu.Game.Overlays ///