From d6b9f385c17e3cc78ee19962a5cfed2db7fff6d7 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Sat, 27 Nov 2021 23:58:08 -0800 Subject: [PATCH 001/420] Add failing play button by touch input test --- .../Visual/Beatmaps/TestSceneBeatmapCard.cs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCard.cs b/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCard.cs index 6cb171974a..959e118fac 100644 --- a/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCard.cs +++ b/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCard.cs @@ -13,12 +13,14 @@ using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; using osu.Game.Beatmaps.Drawables.Cards; +using osu.Game.Beatmaps.Drawables.Cards.Buttons; using osu.Game.Graphics.Containers; using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays; using osuTK; +using osuTK.Input; namespace osu.Game.Tests.Visual.Beatmaps { @@ -293,5 +295,22 @@ namespace osu.Game.Tests.Visual.Beatmaps BeatmapCardNormal firstCard() => this.ChildrenOfType().First(); } + + [Test] + public void TestPlayButtonByTouchInput() + { + AddStep("create cards", () => Child = createContent(OverlayColourScheme.Blue, beatmapSetInfo => new BeatmapCardNormal(beatmapSetInfo))); + + // mimics touch input + AddStep("touch play button area on first card", () => + { + InputManager.MoveMouseTo(firstCard().ChildrenOfType().Single()); + InputManager.Click(MouseButton.Left); + }); + + AddAssert("first card is playing", () => firstCard().ChildrenOfType().Single().Playing.Value); + + BeatmapCardNormal firstCard() => this.ChildrenOfType().First(); + } } } From 1107e267e3fd4f181c6bff925eaf1ed564d0dd1c Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Fri, 10 Jun 2022 14:04:03 -0700 Subject: [PATCH 002/420] Fix beatmap card play button not working with touch inputs when not hovered --- osu.Game/Beatmaps/Drawables/Cards/Buttons/PlayButton.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Beatmaps/Drawables/Cards/Buttons/PlayButton.cs b/osu.Game/Beatmaps/Drawables/Cards/Buttons/PlayButton.cs index f7bab26666..51e690d156 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/Buttons/PlayButton.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/Buttons/PlayButton.cs @@ -43,6 +43,9 @@ namespace osu.Game.Beatmaps.Drawables.Cards.Buttons Anchor = Origin = Anchor.Centre; + // needed for touch input to work when card is not hovered/expanded + AlwaysPresent = true; + Children = new Drawable[] { icon = new SpriteIcon From a5bf16e87345d488ff5f95578002a9be29276f47 Mon Sep 17 00:00:00 2001 From: sw1tchbl4d3 Date: Sun, 19 Jun 2022 02:10:23 +0200 Subject: [PATCH 003/420] Make drum rolls and swells optional with `Classic` mod --- .../Judgements/TaikoDrumRollJudgement.cs | 4 ++++ .../Judgements/TaikoDrumRollTickJudgement.cs | 4 +++- .../Judgements/TaikoSwellJudgement.cs | 4 ++++ .../Mods/TaikoModClassic.cs | 11 ++++++++++- .../Objects/Drawables/DrawableDrumRoll.cs | 9 ++++++++- .../Objects/Drawables/DrawableSwell.cs | 11 +++++++++-- osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs | 18 +++++++++++++++++- .../Objects/DrumRollTick.cs | 7 ++++++- osu.Game.Rulesets.Taiko/Objects/Swell.cs | 12 +++++++++++- osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs | 9 +++++++++ 10 files changed, 81 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Judgements/TaikoDrumRollJudgement.cs b/osu.Game.Rulesets.Taiko/Judgements/TaikoDrumRollJudgement.cs index be128d85b5..93891769ec 100644 --- a/osu.Game.Rulesets.Taiko/Judgements/TaikoDrumRollJudgement.cs +++ b/osu.Game.Rulesets.Taiko/Judgements/TaikoDrumRollJudgement.cs @@ -9,6 +9,10 @@ namespace osu.Game.Rulesets.Taiko.Judgements { public class TaikoDrumRollJudgement : TaikoJudgement { + public bool IsBonus = false; + + public override HitResult MaxResult => IsBonus ? HitResult.LargeBonus : HitResult.Great; + protected override double HealthIncreaseFor(HitResult result) { // Drum rolls can be ignored with no health penalty diff --git a/osu.Game.Rulesets.Taiko/Judgements/TaikoDrumRollTickJudgement.cs b/osu.Game.Rulesets.Taiko/Judgements/TaikoDrumRollTickJudgement.cs index 5f2587a5d5..d6fb8dc2c9 100644 --- a/osu.Game.Rulesets.Taiko/Judgements/TaikoDrumRollTickJudgement.cs +++ b/osu.Game.Rulesets.Taiko/Judgements/TaikoDrumRollTickJudgement.cs @@ -9,7 +9,9 @@ namespace osu.Game.Rulesets.Taiko.Judgements { public class TaikoDrumRollTickJudgement : TaikoJudgement { - public override HitResult MaxResult => HitResult.SmallTickHit; + public bool IsBonus = false; + + public override HitResult MaxResult => IsBonus ? HitResult.LargeBonus : HitResult.SmallTickHit; protected override double HealthIncreaseFor(HitResult result) { diff --git a/osu.Game.Rulesets.Taiko/Judgements/TaikoSwellJudgement.cs b/osu.Game.Rulesets.Taiko/Judgements/TaikoSwellJudgement.cs index b2ac0b7f03..a2572133f2 100644 --- a/osu.Game.Rulesets.Taiko/Judgements/TaikoSwellJudgement.cs +++ b/osu.Game.Rulesets.Taiko/Judgements/TaikoSwellJudgement.cs @@ -9,6 +9,10 @@ namespace osu.Game.Rulesets.Taiko.Judgements { public class TaikoSwellJudgement : TaikoJudgement { + public bool IsBonus = false; + + public override HitResult MaxResult => IsBonus ? HitResult.LargeBonus : HitResult.Great; + protected override double HealthIncreaseFor(HitResult result) { switch (result) diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModClassic.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModClassic.cs index 6d1a18bb78..2564fd32ab 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModClassic.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModClassic.cs @@ -4,13 +4,14 @@ #nullable disable using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Rulesets.Taiko.UI; using osu.Game.Rulesets.UI; namespace osu.Game.Rulesets.Taiko.Mods { - public class TaikoModClassic : ModClassic, IApplicableToDrawableRuleset, IUpdatableByPlayfield + public class TaikoModClassic : ModClassic, IApplicableToDrawableRuleset, IUpdatableByPlayfield, IApplicableToHitObject { private DrawableTaikoRuleset drawableTaikoRuleset; @@ -20,6 +21,14 @@ namespace osu.Game.Rulesets.Taiko.Mods drawableTaikoRuleset.LockPlayfieldAspect.Value = false; } + public void ApplyToHitObject(HitObject hitObject) + { + if (hitObject is DrumRoll drumRoll) + drumRoll.SetBonus(true); + else if (hitObject is Swell swell) + swell.SetBonus(true); + } + public void Update(Playfield playfield) { // Classic taiko scrolls at a constant 100px per 1000ms. More notes become visible as the playfield is lengthened. diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs index 04ed6d0b87..fd8b8196cd 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs @@ -144,7 +144,14 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables if (countHit >= HitObject.RequiredGoodHits) { - ApplyResult(r => r.Type = countHit >= HitObject.RequiredGreatHits ? HitResult.Great : HitResult.Ok); + ApplyResult(r => + { + // With the Classic mod, don't award points for a finished drum roll, only for ticks. + if (r.Judgement.MaxResult == HitResult.LargeBonus) + r.Type = HitResult.IgnoreMiss; + else + r.Type = countHit >= HitObject.RequiredGreatHits ? HitResult.Great : HitResult.Ok; + }); } else ApplyResult(r => r.Type = r.Judgement.MinResult); diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs index 2451c79772..238aa2189f 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs @@ -201,7 +201,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables expandingRing.ScaleTo(1f + Math.Min(target_ring_scale - 1f, (target_ring_scale - 1f) * completion * 1.3f), 260, Easing.OutQuint); if (numHits == HitObject.RequiredHits) - ApplyResult(r => r.Type = HitResult.Great); + ApplyResult(r => r.Type = r.Judgement.MaxResult); } else { @@ -222,7 +222,14 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables tick.TriggerResult(false); } - ApplyResult(r => r.Type = numHits > HitObject.RequiredHits / 2 ? HitResult.Ok : r.Judgement.MinResult); + ApplyResult(r => + { + // With the Classic mod, don't award combo or accuracy for a finished swell. + if (r.Judgement.MaxResult == HitResult.LargeBonus) + r.Type = numHits > HitObject.RequiredHits / 2 ? HitResult.LargeBonus : r.Judgement.MinResult; + else + r.Type = numHits > HitObject.RequiredHits / 2 ? HitResult.Ok : r.Judgement.MinResult; + }); } } diff --git a/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs b/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs index e1619e2857..47ccb9cc72 100644 --- a/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs +++ b/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs @@ -52,6 +52,11 @@ namespace osu.Game.Rulesets.Taiko.Objects /// public double RequiredGreatHits { get; protected set; } + /// + /// Defines if drum rolls are affected by the Classic mod, making them bonus only. + /// + private bool isBonus; + /// /// The length (in milliseconds) between ticks of this drumroll. /// Half of this value is the hit window of the ticks. @@ -106,7 +111,18 @@ namespace osu.Game.Rulesets.Taiko.Objects } } - public override Judgement CreateJudgement() => new TaikoDrumRollJudgement(); + public void SetBonus(bool bonus) + { + isBonus = bonus; + + foreach (HitObject hitObject in NestedHitObjects) + { + if (hitObject is DrumRollTick drumRollTick) + drumRollTick.IsBonus = bonus; + } + } + + public override Judgement CreateJudgement() => new TaikoDrumRollJudgement { IsBonus = isBonus }; protected override HitWindows CreateHitWindows() => HitWindows.Empty; diff --git a/osu.Game.Rulesets.Taiko/Objects/DrumRollTick.cs b/osu.Game.Rulesets.Taiko/Objects/DrumRollTick.cs index 433fdab908..6aa08e7c76 100644 --- a/osu.Game.Rulesets.Taiko/Objects/DrumRollTick.cs +++ b/osu.Game.Rulesets.Taiko/Objects/DrumRollTick.cs @@ -27,7 +27,12 @@ namespace osu.Game.Rulesets.Taiko.Objects /// public double HitWindow => TickSpacing / 2; - public override Judgement CreateJudgement() => new TaikoDrumRollTickJudgement(); + /// + /// Defines if ticks are affected by the Classic mod, making them bonus only. + /// + public bool IsBonus = false; + + public override Judgement CreateJudgement() => new TaikoDrumRollTickJudgement { IsBonus = IsBonus }; protected override HitWindows CreateHitWindows() => HitWindows.Empty; diff --git a/osu.Game.Rulesets.Taiko/Objects/Swell.cs b/osu.Game.Rulesets.Taiko/Objects/Swell.cs index cb91c46b4d..bd91fcd076 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Swell.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Swell.cs @@ -26,6 +26,16 @@ namespace osu.Game.Rulesets.Taiko.Objects /// public int RequiredHits = 10; + /// + /// Defines if swells are affected by the Classic mod, making them bonus only. + /// + private bool isBonus; + + public void SetBonus(bool bonus) + { + isBonus = bonus; + } + protected override void CreateNestedHitObjects(CancellationToken cancellationToken) { base.CreateNestedHitObjects(cancellationToken); @@ -37,7 +47,7 @@ namespace osu.Game.Rulesets.Taiko.Objects } } - public override Judgement CreateJudgement() => new TaikoSwellJudgement(); + public override Judgement CreateJudgement() => new TaikoSwellJudgement { IsBonus = isBonus }; protected override HitWindows CreateHitWindows() => HitWindows.Empty; } diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs index 4e0c8029fb..7719f51f2f 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs @@ -295,6 +295,15 @@ namespace osu.Game.Rulesets.Taiko.UI break; default: + // Don't draw judgement results for bonus sliderticks with the Classic mod. + switch (result.Type) + { + case HitResult.IgnoreHit: + case HitResult.IgnoreMiss: + case HitResult.LargeBonus: + return; + } + judgementContainer.Add(judgementPools[result.Type].Get(j => { j.Apply(result, judgedObject); From c6ac60c0b5d5c88cb5d7fa611858507fa7f3c734 Mon Sep 17 00:00:00 2001 From: Pasi4K5 Date: Sun, 19 Jun 2022 13:07:10 +0200 Subject: [PATCH 004/420] Enhance target angle calculation --- osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs | 48 +++++++++++++++---- .../Utils/OsuHitObjectGenerationUtils.cs | 33 +++++++++++++ 2 files changed, 71 insertions(+), 10 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs index 4f83154728..475d5a2a67 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs @@ -37,23 +37,51 @@ namespace osu.Game.Rulesets.Osu.Mods var positionInfos = OsuHitObjectGenerationUtils.GeneratePositionInfos(osuBeatmap.HitObjects); - float rateOfChangeMultiplier = 0; + float sequenceOffset = 0; + bool flowDirection = false; - foreach (var positionInfo in positionInfos) + for (int i = 0; i < positionInfos.Count; i++) { - // rateOfChangeMultiplier only changes every 5 iterations in a combo - // to prevent shaky-line-shaped streams - if (positionInfo.HitObject.IndexInCurrentCombo % 5 == 0) - rateOfChangeMultiplier = (float)rng.NextDouble() * 2 - 1; + bool invertFlow = false; - if (positionInfo == positionInfos.First()) + if (i == 0 || + (positionInfos[i - 1].HitObject.NewCombo && (i <= 1 || !positionInfos[i - 2].HitObject.NewCombo) && (i <= 2 || !positionInfos[i - 3].HitObject.NewCombo)) || + OsuHitObjectGenerationUtils.IsHitObjectOnBeat(osuBeatmap, positionInfos[i - 1].HitObject, true) || + (OsuHitObjectGenerationUtils.IsHitObjectOnBeat(osuBeatmap, positionInfos[i - 1].HitObject) && rng.NextDouble() < 0.25)) { - positionInfo.DistanceFromPrevious = (float)(rng.NextDouble() * OsuPlayfield.BASE_SIZE.Y / 2); - positionInfo.RelativeAngle = (float)(rng.NextDouble() * 2 * Math.PI - Math.PI); + sequenceOffset = OsuHitObjectGenerationUtils.RandomGaussian(rng, 0, 0.02f); + + if (rng.NextDouble() < 0.6) + invertFlow = true; + } + + if (i == 0) + { + positionInfos[i].DistanceFromPrevious = (float)(rng.NextDouble() * OsuPlayfield.BASE_SIZE.Y / 2); + positionInfos[i].RelativeAngle = (float)(rng.NextDouble() * 2 * Math.PI - Math.PI); } else { - positionInfo.RelativeAngle = rateOfChangeMultiplier * 2 * (float)Math.PI * Math.Min(1f, positionInfo.DistanceFromPrevious / (playfield_diagonal * 0.5f)); + float flowChangeOffset = 0; + float oneTimeOffset = OsuHitObjectGenerationUtils.RandomGaussian(rng, 0, 0.03f); + + if (positionInfos[i - 1].HitObject.NewCombo && (i <= 1 || !positionInfos[i - 2].HitObject.NewCombo) && rng.NextDouble() < 0.6) + { + flowChangeOffset = OsuHitObjectGenerationUtils.RandomGaussian(rng, 0, 0.05f); + + if (rng.NextDouble() < 0.8) + invertFlow = true; + } + + if (invertFlow) + flowDirection ^= true; + + positionInfos[i].RelativeAngle = OsuHitObjectGenerationUtils.GetRelativeTargetAngle( + positionInfos[i].DistanceFromPrevious, + (sequenceOffset + oneTimeOffset) * (float)Math.Sqrt(positionInfos[i].DistanceFromPrevious) + + flowChangeOffset * (float)Math.Sqrt(640 - positionInfos[i].DistanceFromPrevious), + flowDirection + ); } } diff --git a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs index 5e827d4782..9044fe142b 100644 --- a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs +++ b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs @@ -8,6 +8,7 @@ using System.Linq; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Game.Rulesets.Osu.UI; using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Osu.Beatmaps; using osu.Game.Rulesets.Osu.Objects; using osuTK; @@ -186,5 +187,37 @@ namespace osu.Game.Rulesets.Osu.Utils length * MathF.Sin(angle) ); } + + public static bool IsHitObjectOnBeat(OsuBeatmap beatmap, OsuHitObject hitObject, bool downbeatsOnly = false) + { + var timingPoints = beatmap.ControlPointInfo.TimingPoints; + var currentTimingPoint = timingPoints.Reverse().FirstOrDefault(p => p.Time <= hitObject.StartTime); + + if (currentTimingPoint == null) + return false; + + double timeSinceTimingPoint = hitObject.StartTime - currentTimingPoint.Time; + + double length = downbeatsOnly + ? currentTimingPoint.BeatLength * currentTimingPoint.TimeSignature.Numerator + : currentTimingPoint.BeatLength; + + return (timeSinceTimingPoint + 1) % length < 2; + } + + public static float GetRelativeTargetAngle(float targetDistance, float offset, bool flowDirection) + { + float angle = (float)(3.3 / (1 + 200 * Math.Pow(MathHelper.E, 0.016 * (targetDistance - 466))) + 0.45 + offset); + float relativeAngle = MathHelper.Pi - angle; + return flowDirection ? -relativeAngle : relativeAngle; + } + + public static float RandomGaussian(Random rng, float mean = 0, float stdDev = 1) + { + double x1 = 1 - rng.NextDouble(); + double x2 = 1 - rng.NextDouble(); + double stdNormal = Math.Sqrt(-2 * Math.Log(x1)) * Math.Sin(MathHelper.TwoPi * x2); + return mean + stdDev * (float)stdNormal; + } } } From 33c6c6af6b2995f4717009a2cc646775e32d419b Mon Sep 17 00:00:00 2001 From: Pasi4K5 Date: Sun, 19 Jun 2022 13:50:09 +0200 Subject: [PATCH 005/420] Adjust target angle calculation parameters --- osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs index 9044fe142b..b9c384f842 100644 --- a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs +++ b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs @@ -207,7 +207,7 @@ namespace osu.Game.Rulesets.Osu.Utils public static float GetRelativeTargetAngle(float targetDistance, float offset, bool flowDirection) { - float angle = (float)(3.3 / (1 + 200 * Math.Pow(MathHelper.E, 0.016 * (targetDistance - 466))) + 0.45 + offset); + float angle = (float)(3 / (1 + 200 * Math.Pow(MathHelper.E, 0.016 * (targetDistance - 466))) + 0.45 + offset); float relativeAngle = MathHelper.Pi - angle; return flowDirection ? -relativeAngle : relativeAngle; } From 7317b9b90960a2b326ec858e61b668375fa7e0ac Mon Sep 17 00:00:00 2001 From: Pasi4K5 Date: Sun, 19 Jun 2022 14:59:28 +0200 Subject: [PATCH 006/420] Remove unused field --- osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs index 475d5a2a67..2560ed921d 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs @@ -22,8 +22,6 @@ namespace osu.Game.Rulesets.Osu.Mods public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(OsuModTarget)).ToArray(); - private static readonly float playfield_diagonal = OsuPlayfield.BASE_SIZE.LengthFast; - private Random? rng; public void ApplyToBeatmap(IBeatmap beatmap) From 98527fec26e7d88af58edd376a6d2da0ef4f32ff Mon Sep 17 00:00:00 2001 From: sw1tchbl4d3 Date: Sun, 19 Jun 2022 15:11:12 +0200 Subject: [PATCH 007/420] Make mod selfcontained --- .../Judgements/TaikoDrumRollJudgement.cs | 4 - .../Judgements/TaikoDrumRollTickJudgement.cs | 4 +- .../Judgements/TaikoSwellJudgement.cs | 4 - .../Mods/TaikoModClassic.cs | 184 +++++++++++++++++- .../Objects/Drawables/DrawableDrumRoll.cs | 9 +- .../Objects/Drawables/DrawableSwell.cs | 50 +++-- osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs | 32 +-- .../Objects/DrumRollTick.cs | 7 +- osu.Game.Rulesets.Taiko/Objects/Swell.cs | 12 +- osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs | 9 - 10 files changed, 213 insertions(+), 102 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Judgements/TaikoDrumRollJudgement.cs b/osu.Game.Rulesets.Taiko/Judgements/TaikoDrumRollJudgement.cs index 93891769ec..be128d85b5 100644 --- a/osu.Game.Rulesets.Taiko/Judgements/TaikoDrumRollJudgement.cs +++ b/osu.Game.Rulesets.Taiko/Judgements/TaikoDrumRollJudgement.cs @@ -9,10 +9,6 @@ namespace osu.Game.Rulesets.Taiko.Judgements { public class TaikoDrumRollJudgement : TaikoJudgement { - public bool IsBonus = false; - - public override HitResult MaxResult => IsBonus ? HitResult.LargeBonus : HitResult.Great; - protected override double HealthIncreaseFor(HitResult result) { // Drum rolls can be ignored with no health penalty diff --git a/osu.Game.Rulesets.Taiko/Judgements/TaikoDrumRollTickJudgement.cs b/osu.Game.Rulesets.Taiko/Judgements/TaikoDrumRollTickJudgement.cs index d6fb8dc2c9..5f2587a5d5 100644 --- a/osu.Game.Rulesets.Taiko/Judgements/TaikoDrumRollTickJudgement.cs +++ b/osu.Game.Rulesets.Taiko/Judgements/TaikoDrumRollTickJudgement.cs @@ -9,9 +9,7 @@ namespace osu.Game.Rulesets.Taiko.Judgements { public class TaikoDrumRollTickJudgement : TaikoJudgement { - public bool IsBonus = false; - - public override HitResult MaxResult => IsBonus ? HitResult.LargeBonus : HitResult.SmallTickHit; + public override HitResult MaxResult => HitResult.SmallTickHit; protected override double HealthIncreaseFor(HitResult result) { diff --git a/osu.Game.Rulesets.Taiko/Judgements/TaikoSwellJudgement.cs b/osu.Game.Rulesets.Taiko/Judgements/TaikoSwellJudgement.cs index a2572133f2..b2ac0b7f03 100644 --- a/osu.Game.Rulesets.Taiko/Judgements/TaikoSwellJudgement.cs +++ b/osu.Game.Rulesets.Taiko/Judgements/TaikoSwellJudgement.cs @@ -9,10 +9,6 @@ namespace osu.Game.Rulesets.Taiko.Judgements { public class TaikoSwellJudgement : TaikoJudgement { - public bool IsBonus = false; - - public override HitResult MaxResult => IsBonus ? HitResult.LargeBonus : HitResult.Great; - protected override double HealthIncreaseFor(HitResult result) { switch (result) diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModClassic.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModClassic.cs index 2564fd32ab..7141afe791 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModClassic.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModClassic.cs @@ -3,15 +3,22 @@ #nullable disable +using System.Linq; +using System.Threading; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Scoring; +using osu.Game.Rulesets.Taiko.Beatmaps; +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.Rulesets.UI; namespace osu.Game.Rulesets.Taiko.Mods { - public class TaikoModClassic : ModClassic, IApplicableToDrawableRuleset, IUpdatableByPlayfield, IApplicableToHitObject + public class TaikoModClassic : ModClassic, IApplicableToDrawableRuleset, IUpdatableByPlayfield, IApplicableAfterBeatmapConversion { private DrawableTaikoRuleset drawableTaikoRuleset; @@ -19,14 +26,177 @@ namespace osu.Game.Rulesets.Taiko.Mods { drawableTaikoRuleset = (DrawableTaikoRuleset)drawableRuleset; drawableTaikoRuleset.LockPlayfieldAspect.Value = false; + + drawableTaikoRuleset.Playfield.RegisterPool(5); + drawableTaikoRuleset.Playfield.RegisterPool(100); + drawableTaikoRuleset.Playfield.RegisterPool(5); } - public void ApplyToHitObject(HitObject hitObject) + public void ApplyToBeatmap(IBeatmap beatmap) { - if (hitObject is DrumRoll drumRoll) - drumRoll.SetBonus(true); - else if (hitObject is Swell swell) - swell.SetBonus(true); + var taikoBeatmap = (TaikoBeatmap)beatmap; + + if (taikoBeatmap.HitObjects.Count == 0) return; + + var hitObjects = taikoBeatmap.HitObjects.Select(ho => + { + if (ho is DrumRoll drumRoll) + { + var newDrumRoll = new ClassicDrumRoll(drumRoll); + return newDrumRoll; + } + + if (ho is Swell swell) + { + var newSwell = new ClassicSwell(swell); + return newSwell; + } + + return ho; + }).ToList(); + + taikoBeatmap.HitObjects = hitObjects; + } + + private class ClassicDrumRoll : DrumRoll + { + public ClassicDrumRoll(DrumRoll original) + { + StartTime = original.StartTime; + Samples = original.Samples; + EndTime = original.EndTime; + Duration = original.Duration; + TickRate = original.TickRate; + RequiredGoodHits = original.RequiredGoodHits; + RequiredGreatHits = original.RequiredGreatHits; + } + + public override Judgement CreateJudgement() => new TaikoClassicDrumRollJudgement(); + + protected override void CreateTicks(CancellationToken cancellationToken) + { + if (TickSpacing == 0) + return; + + bool first = true; + + for (double t = StartTime; t < EndTime + TickSpacing / 2; t += TickSpacing) + { + cancellationToken.ThrowIfCancellationRequested(); + + AddNested(new ClassicDrumRollTick + { + FirstTick = first, + TickSpacing = TickSpacing, + StartTime = t, + IsStrong = IsStrong + }); + + first = false; + } + } + } + + private class ClassicDrumRollTick : DrumRollTick + { + public override Judgement CreateJudgement() => new TaikoClassicDrumRollTickJudgement(); + } + + private class ClassicSwell : Swell + { + public ClassicSwell(Swell original) + { + StartTime = original.StartTime; + Samples = original.Samples; + EndTime = original.EndTime; + Duration = original.Duration; + RequiredHits = original.RequiredHits; + } + + public override Judgement CreateJudgement() => new TaikoClassicSwellJudgement(); + } + + private class TaikoClassicDrumRollJudgement : TaikoDrumRollJudgement + { + public override HitResult MaxResult => HitResult.LargeBonus; + } + + private class TaikoClassicDrumRollTickJudgement : TaikoDrumRollTickJudgement + { + public override HitResult MaxResult => HitResult.LargeBonus; + } + + private class TaikoClassicSwellJudgement : TaikoSwellJudgement + { + public override HitResult MaxResult => HitResult.LargeBonus; + } + + private class ClassicDrawableDrumRoll : DrawableDrumRoll + { + public override bool DisplayResult => false; + + protected override void CheckForResult(bool userTriggered, double timeOffset) + { + if (userTriggered) + return; + + if (timeOffset < 0) + return; + + ApplyResult(r => r.Type = HitResult.IgnoreMiss); + } + } + + private class ClassicDrawableSwell : DrawableSwell + { + public override bool DisplayResult => false; + + protected override void CheckForResult(bool userTriggered, double timeOffset) + { + if (userTriggered) + { + DrawableSwellTick nextTick = null; + + foreach (var t in Ticks) + { + if (!t.Result.HasResult) + { + nextTick = t; + break; + } + } + + nextTick?.TriggerResult(true); + + int numHits = Ticks.Count(r => r.IsHit); + + AnimateCompletion(numHits); + + if (numHits == HitObject.RequiredHits) + ApplyResult(r => r.Type = HitResult.LargeBonus); + } + else + { + if (timeOffset < 0) + return; + + int numHits = 0; + + foreach (var tick in Ticks) + { + if (tick.IsHit) + { + numHits++; + continue; + } + + if (!tick.Result.HasResult) + tick.TriggerResult(false); + } + + ApplyResult(r => r.Type = numHits > HitObject.RequiredHits / 2 ? HitResult.SmallBonus : HitResult.IgnoreMiss); + } + } } public void Update(Playfield playfield) diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs index fd8b8196cd..04ed6d0b87 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs @@ -144,14 +144,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables if (countHit >= HitObject.RequiredGoodHits) { - ApplyResult(r => - { - // With the Classic mod, don't award points for a finished drum roll, only for ticks. - if (r.Judgement.MaxResult == HitResult.LargeBonus) - r.Type = HitResult.IgnoreMiss; - else - r.Type = countHit >= HitObject.RequiredGreatHits ? HitResult.Great : HitResult.Ok; - }); + ApplyResult(r => r.Type = countHit >= HitObject.RequiredGreatHits ? HitResult.Great : HitResult.Ok); } else ApplyResult(r => r.Type = r.Judgement.MinResult); diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs index 238aa2189f..34fe6c5a73 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs @@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables /// private const double ring_appear_offset = 100; - private readonly Container ticks; + protected readonly Container Ticks; private readonly Container bodyContainer; private readonly CircularContainer targetRing; private readonly CircularContainer expandingRing; @@ -114,7 +114,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables } }); - AddInternal(ticks = new Container { RelativeSizeAxes = Axes.Both }); + AddInternal(Ticks = new Container { RelativeSizeAxes = Axes.Both }); } [BackgroundDependencyLoader] @@ -132,6 +132,20 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables Origin = Anchor.Centre, }); + protected void AnimateCompletion(int numHits) + { + float completion = (float)numHits / HitObject.RequiredHits; + + expandingRing + .FadeTo(expandingRing.Alpha + Math.Clamp(completion / 16, 0.1f, 0.6f), 50) + .Then() + .FadeTo(completion / 8, 2000, Easing.OutQuint); + + MainPiece.Drawable.RotateTo((float)(completion * HitObject.Duration / 8), 4000, Easing.OutQuint); + + expandingRing.ScaleTo(1f + Math.Min(target_ring_scale - 1f, (target_ring_scale - 1f) * completion * 1.3f), 260, Easing.OutQuint); + } + protected override void OnFree() { base.OnFree(); @@ -148,7 +162,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables switch (hitObject) { case DrawableSwellTick tick: - ticks.Add(tick); + Ticks.Add(tick); break; } } @@ -156,7 +170,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables protected override void ClearNestedHitObjects() { base.ClearNestedHitObjects(); - ticks.Clear(false); + Ticks.Clear(false); } protected override DrawableHitObject CreateNestedHitObject(HitObject hitObject) @@ -176,7 +190,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables { DrawableSwellTick nextTick = null; - foreach (var t in ticks) + foreach (var t in Ticks) { if (!t.Result.HasResult) { @@ -187,21 +201,12 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables nextTick?.TriggerResult(true); - int numHits = ticks.Count(r => r.IsHit); + int numHits = Ticks.Count(r => r.IsHit); - float completion = (float)numHits / HitObject.RequiredHits; - - expandingRing - .FadeTo(expandingRing.Alpha + Math.Clamp(completion / 16, 0.1f, 0.6f), 50) - .Then() - .FadeTo(completion / 8, 2000, Easing.OutQuint); - - MainPiece.Drawable.RotateTo((float)(completion * HitObject.Duration / 8), 4000, Easing.OutQuint); - - expandingRing.ScaleTo(1f + Math.Min(target_ring_scale - 1f, (target_ring_scale - 1f) * completion * 1.3f), 260, Easing.OutQuint); + AnimateCompletion(numHits); if (numHits == HitObject.RequiredHits) - ApplyResult(r => r.Type = r.Judgement.MaxResult); + ApplyResult(r => r.Type = HitResult.Great); } else { @@ -210,7 +215,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables int numHits = 0; - foreach (var tick in ticks) + foreach (var tick in Ticks) { if (tick.IsHit) { @@ -222,14 +227,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables tick.TriggerResult(false); } - ApplyResult(r => - { - // With the Classic mod, don't award combo or accuracy for a finished swell. - if (r.Judgement.MaxResult == HitResult.LargeBonus) - r.Type = numHits > HitObject.RequiredHits / 2 ? HitResult.LargeBonus : r.Judgement.MinResult; - else - r.Type = numHits > HitObject.RequiredHits / 2 ? HitResult.Ok : r.Judgement.MinResult; - }); + ApplyResult(r => r.Type = numHits > HitObject.RequiredHits / 2 ? HitResult.Ok : r.Judgement.MinResult); } } diff --git a/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs b/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs index 47ccb9cc72..80e8bec0cb 100644 --- a/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs +++ b/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs @@ -52,16 +52,11 @@ namespace osu.Game.Rulesets.Taiko.Objects /// public double RequiredGreatHits { get; protected set; } - /// - /// Defines if drum rolls are affected by the Classic mod, making them bonus only. - /// - private bool isBonus; - /// /// The length (in milliseconds) between ticks of this drumroll. /// Half of this value is the hit window of the ticks. /// - private double tickSpacing = 100; + protected double TickSpacing = 100; private float overallDifficulty = BeatmapDifficulty.DEFAULT_DIFFICULTY; @@ -74,13 +69,13 @@ namespace osu.Game.Rulesets.Taiko.Objects double scoringDistance = base_distance * difficulty.SliderMultiplier * DifficultyControlPoint.SliderVelocity; Velocity = scoringDistance / timingPoint.BeatLength; - tickSpacing = timingPoint.BeatLength / TickRate; + TickSpacing = timingPoint.BeatLength / TickRate; overallDifficulty = difficulty.OverallDifficulty; } protected override void CreateNestedHitObjects(CancellationToken cancellationToken) { - createTicks(cancellationToken); + CreateTicks(cancellationToken); RequiredGoodHits = NestedHitObjects.Count * Math.Min(0.15, 0.05 + 0.10 / 6 * overallDifficulty); RequiredGreatHits = NestedHitObjects.Count * Math.Min(0.30, 0.10 + 0.20 / 6 * overallDifficulty); @@ -88,21 +83,21 @@ namespace osu.Game.Rulesets.Taiko.Objects base.CreateNestedHitObjects(cancellationToken); } - private void createTicks(CancellationToken cancellationToken) + protected virtual void CreateTicks(CancellationToken cancellationToken) { - if (tickSpacing == 0) + if (TickSpacing == 0) return; bool first = true; - for (double t = StartTime; t < EndTime + tickSpacing / 2; t += tickSpacing) + for (double t = StartTime; t < EndTime + TickSpacing / 2; t += TickSpacing) { cancellationToken.ThrowIfCancellationRequested(); AddNested(new DrumRollTick { FirstTick = first, - TickSpacing = tickSpacing, + TickSpacing = TickSpacing, StartTime = t, IsStrong = IsStrong }); @@ -111,18 +106,7 @@ namespace osu.Game.Rulesets.Taiko.Objects } } - public void SetBonus(bool bonus) - { - isBonus = bonus; - - foreach (HitObject hitObject in NestedHitObjects) - { - if (hitObject is DrumRollTick drumRollTick) - drumRollTick.IsBonus = bonus; - } - } - - public override Judgement CreateJudgement() => new TaikoDrumRollJudgement { IsBonus = isBonus }; + public override Judgement CreateJudgement() => new TaikoDrumRollJudgement(); protected override HitWindows CreateHitWindows() => HitWindows.Empty; diff --git a/osu.Game.Rulesets.Taiko/Objects/DrumRollTick.cs b/osu.Game.Rulesets.Taiko/Objects/DrumRollTick.cs index 6aa08e7c76..433fdab908 100644 --- a/osu.Game.Rulesets.Taiko/Objects/DrumRollTick.cs +++ b/osu.Game.Rulesets.Taiko/Objects/DrumRollTick.cs @@ -27,12 +27,7 @@ namespace osu.Game.Rulesets.Taiko.Objects /// public double HitWindow => TickSpacing / 2; - /// - /// Defines if ticks are affected by the Classic mod, making them bonus only. - /// - public bool IsBonus = false; - - public override Judgement CreateJudgement() => new TaikoDrumRollTickJudgement { IsBonus = IsBonus }; + public override Judgement CreateJudgement() => new TaikoDrumRollTickJudgement(); protected override HitWindows CreateHitWindows() => HitWindows.Empty; diff --git a/osu.Game.Rulesets.Taiko/Objects/Swell.cs b/osu.Game.Rulesets.Taiko/Objects/Swell.cs index bd91fcd076..cb91c46b4d 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Swell.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Swell.cs @@ -26,16 +26,6 @@ namespace osu.Game.Rulesets.Taiko.Objects /// public int RequiredHits = 10; - /// - /// Defines if swells are affected by the Classic mod, making them bonus only. - /// - private bool isBonus; - - public void SetBonus(bool bonus) - { - isBonus = bonus; - } - protected override void CreateNestedHitObjects(CancellationToken cancellationToken) { base.CreateNestedHitObjects(cancellationToken); @@ -47,7 +37,7 @@ namespace osu.Game.Rulesets.Taiko.Objects } } - public override Judgement CreateJudgement() => new TaikoSwellJudgement { IsBonus = isBonus }; + public override Judgement CreateJudgement() => new TaikoSwellJudgement(); protected override HitWindows CreateHitWindows() => HitWindows.Empty; } diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs index 7719f51f2f..4e0c8029fb 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs @@ -295,15 +295,6 @@ namespace osu.Game.Rulesets.Taiko.UI break; default: - // Don't draw judgement results for bonus sliderticks with the Classic mod. - switch (result.Type) - { - case HitResult.IgnoreHit: - case HitResult.IgnoreMiss: - case HitResult.LargeBonus: - return; - } - judgementContainer.Add(judgementPools[result.Type].Get(j => { j.Apply(result, judgedObject); From 9090e7502075ed9731f75408b43f5d9afe35646d Mon Sep 17 00:00:00 2001 From: Pasi4K5 Date: Sun, 19 Jun 2022 20:43:17 +0200 Subject: [PATCH 008/420] Add XML documentation --- .../Utils/OsuHitObjectGenerationUtils.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs index b9c384f842..f503956aee 100644 --- a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs +++ b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs @@ -188,6 +188,11 @@ namespace osu.Game.Rulesets.Osu.Utils ); } + /// The beatmap hitObject is a part of. + /// The that should be checked. + /// If true, this method only returns true if hitObject is on a downbeat. + /// If false, it returns true if hitObject is on any beat. + /// true if hitObject is on a (down-)beat, false otherwise. public static bool IsHitObjectOnBeat(OsuBeatmap beatmap, OsuHitObject hitObject, bool downbeatsOnly = false) { var timingPoints = beatmap.ControlPointInfo.TimingPoints; @@ -205,6 +210,9 @@ namespace osu.Game.Rulesets.Osu.Utils return (timeSinceTimingPoint + 1) % length < 2; } + /// The target distance between the previous and the current . + /// The angle (in rad) by which the target angle should be offset. + /// Whether the relative angle should be positive or negative. public static float GetRelativeTargetAngle(float targetDistance, float offset, bool flowDirection) { float angle = (float)(3 / (1 + 200 * Math.Pow(MathHelper.E, 0.016 * (targetDistance - 466))) + 0.45 + offset); From 1bb27cd4880f4dd4766d1cace022c37e0f23585b Mon Sep 17 00:00:00 2001 From: Pasi4K5 Date: Sun, 19 Jun 2022 23:03:41 +0200 Subject: [PATCH 009/420] Code optimisation --- osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs | 23 +++++++++++++++---- .../Utils/OsuHitObjectGenerationUtils.cs | 18 ++++----------- 2 files changed, 22 insertions(+), 19 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs index 2560ed921d..55151ae2c1 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs @@ -8,6 +8,7 @@ using osu.Game.Beatmaps; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Beatmaps; +using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.UI; using osu.Game.Rulesets.Osu.Utils; @@ -22,6 +23,8 @@ namespace osu.Game.Rulesets.Osu.Mods public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(OsuModTarget)).ToArray(); + private static readonly float playfield_diagonal = OsuPlayfield.BASE_SIZE.LengthFast; + private Random? rng; public void ApplyToBeatmap(IBeatmap beatmap) @@ -43,7 +46,7 @@ namespace osu.Game.Rulesets.Osu.Mods bool invertFlow = false; if (i == 0 || - (positionInfos[i - 1].HitObject.NewCombo && (i <= 1 || !positionInfos[i - 2].HitObject.NewCombo) && (i <= 2 || !positionInfos[i - 3].HitObject.NewCombo)) || + (positionInfos[Math.Max(0, i - 2)].HitObject.IndexInCurrentCombo > 1 && positionInfos[i - 1].HitObject.NewCombo && rng.NextDouble() < 0.6) || OsuHitObjectGenerationUtils.IsHitObjectOnBeat(osuBeatmap, positionInfos[i - 1].HitObject, true) || (OsuHitObjectGenerationUtils.IsHitObjectOnBeat(osuBeatmap, positionInfos[i - 1].HitObject) && rng.NextDouble() < 0.25)) { @@ -63,7 +66,7 @@ namespace osu.Game.Rulesets.Osu.Mods float flowChangeOffset = 0; float oneTimeOffset = OsuHitObjectGenerationUtils.RandomGaussian(rng, 0, 0.03f); - if (positionInfos[i - 1].HitObject.NewCombo && (i <= 1 || !positionInfos[i - 2].HitObject.NewCombo) && rng.NextDouble() < 0.6) + if (positionInfos[Math.Max(0, i - 2)].HitObject.IndexInCurrentCombo > 1 && positionInfos[i - 1].HitObject.NewCombo && rng.NextDouble() < 0.6) { flowChangeOffset = OsuHitObjectGenerationUtils.RandomGaussian(rng, 0, 0.05f); @@ -72,12 +75,12 @@ namespace osu.Game.Rulesets.Osu.Mods } if (invertFlow) - flowDirection ^= true; + flowDirection = !flowDirection; - positionInfos[i].RelativeAngle = OsuHitObjectGenerationUtils.GetRelativeTargetAngle( + positionInfos[i].RelativeAngle = getRelativeTargetAngle( positionInfos[i].DistanceFromPrevious, (sequenceOffset + oneTimeOffset) * (float)Math.Sqrt(positionInfos[i].DistanceFromPrevious) + - flowChangeOffset * (float)Math.Sqrt(640 - positionInfos[i].DistanceFromPrevious), + flowChangeOffset * (float)Math.Sqrt(playfield_diagonal - positionInfos[i].DistanceFromPrevious), flowDirection ); } @@ -85,5 +88,15 @@ namespace osu.Game.Rulesets.Osu.Mods osuBeatmap.HitObjects = OsuHitObjectGenerationUtils.RepositionHitObjects(positionInfos); } + + /// The target distance between the previous and the current . + /// The angle (in rad) by which the target angle should be offset. + /// Whether the relative angle should be positive or negative. + private static float getRelativeTargetAngle(float targetDistance, float offset, bool flowDirection) + { + float angle = (float)(3 / (1 + 200 * Math.Exp(0.016 * (targetDistance - 466))) + 0.45 + offset); + float relativeAngle = (float)Math.PI - angle; + return flowDirection ? -relativeAngle : relativeAngle; + } } } diff --git a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs index f503956aee..117d6f3a80 100644 --- a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs +++ b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs @@ -196,7 +196,7 @@ namespace osu.Game.Rulesets.Osu.Utils public static bool IsHitObjectOnBeat(OsuBeatmap beatmap, OsuHitObject hitObject, bool downbeatsOnly = false) { var timingPoints = beatmap.ControlPointInfo.TimingPoints; - var currentTimingPoint = timingPoints.Reverse().FirstOrDefault(p => p.Time <= hitObject.StartTime); + var currentTimingPoint = timingPoints.LastOrDefault(p => p.Time <= hitObject.StartTime); if (currentTimingPoint == null) return false; @@ -210,21 +210,11 @@ namespace osu.Game.Rulesets.Osu.Utils return (timeSinceTimingPoint + 1) % length < 2; } - /// The target distance between the previous and the current . - /// The angle (in rad) by which the target angle should be offset. - /// Whether the relative angle should be positive or negative. - public static float GetRelativeTargetAngle(float targetDistance, float offset, bool flowDirection) - { - float angle = (float)(3 / (1 + 200 * Math.Pow(MathHelper.E, 0.016 * (targetDistance - 466))) + 0.45 + offset); - float relativeAngle = MathHelper.Pi - angle; - return flowDirection ? -relativeAngle : relativeAngle; - } - public static float RandomGaussian(Random rng, float mean = 0, float stdDev = 1) { - double x1 = 1 - rng.NextDouble(); - double x2 = 1 - rng.NextDouble(); - double stdNormal = Math.Sqrt(-2 * Math.Log(x1)) * Math.Sin(MathHelper.TwoPi * x2); + double x1 = rng.NextDouble(); + double x2 = rng.NextDouble(); + double stdNormal = Math.Sqrt(-2 * Math.Log(x1)) * Math.Sin(2 * Math.PI * x2); return mean + stdDev * (float)stdNormal; } } From 3356742ba2d5b74372c6641a4d857cf6138ad44f Mon Sep 17 00:00:00 2001 From: Pasi4K5 Date: Mon, 20 Jun 2022 00:05:03 +0200 Subject: [PATCH 010/420] Adjust angle offset caluclations --- osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs index 55151ae2c1..ef3eaf2a9d 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs @@ -50,7 +50,7 @@ namespace osu.Game.Rulesets.Osu.Mods OsuHitObjectGenerationUtils.IsHitObjectOnBeat(osuBeatmap, positionInfos[i - 1].HitObject, true) || (OsuHitObjectGenerationUtils.IsHitObjectOnBeat(osuBeatmap, positionInfos[i - 1].HitObject) && rng.NextDouble() < 0.25)) { - sequenceOffset = OsuHitObjectGenerationUtils.RandomGaussian(rng, 0, 0.02f); + sequenceOffset = OsuHitObjectGenerationUtils.RandomGaussian(rng, 0, 0.0015f); if (rng.NextDouble() < 0.6) invertFlow = true; @@ -64,11 +64,11 @@ namespace osu.Game.Rulesets.Osu.Mods else { float flowChangeOffset = 0; - float oneTimeOffset = OsuHitObjectGenerationUtils.RandomGaussian(rng, 0, 0.03f); + float oneTimeOffset = OsuHitObjectGenerationUtils.RandomGaussian(rng, 0, 0.0015f); if (positionInfos[Math.Max(0, i - 2)].HitObject.IndexInCurrentCombo > 1 && positionInfos[i - 1].HitObject.NewCombo && rng.NextDouble() < 0.6) { - flowChangeOffset = OsuHitObjectGenerationUtils.RandomGaussian(rng, 0, 0.05f); + flowChangeOffset = OsuHitObjectGenerationUtils.RandomGaussian(rng, 0, 0.002f); if (rng.NextDouble() < 0.8) invertFlow = true; @@ -79,8 +79,8 @@ namespace osu.Game.Rulesets.Osu.Mods positionInfos[i].RelativeAngle = getRelativeTargetAngle( positionInfos[i].DistanceFromPrevious, - (sequenceOffset + oneTimeOffset) * (float)Math.Sqrt(positionInfos[i].DistanceFromPrevious) + - flowChangeOffset * (float)Math.Sqrt(playfield_diagonal - positionInfos[i].DistanceFromPrevious), + (sequenceOffset + oneTimeOffset) * positionInfos[i].DistanceFromPrevious + + flowChangeOffset * (playfield_diagonal - positionInfos[i].DistanceFromPrevious), flowDirection ); } From a912bcadf82aa7eb4e885cf81da35e980824dd55 Mon Sep 17 00:00:00 2001 From: Pasi4K5 Date: Mon, 20 Jun 2022 00:19:29 +0200 Subject: [PATCH 011/420] Fix possible exception caused by `log(0)` --- osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs index 117d6f3a80..ce7f39cdf8 100644 --- a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs +++ b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs @@ -212,8 +212,8 @@ namespace osu.Game.Rulesets.Osu.Utils public static float RandomGaussian(Random rng, float mean = 0, float stdDev = 1) { - double x1 = rng.NextDouble(); - double x2 = rng.NextDouble(); + double x1 = 1 - rng.NextDouble(); + double x2 = 1 - rng.NextDouble(); double stdNormal = Math.Sqrt(-2 * Math.Log(x1)) * Math.Sin(2 * Math.PI * x2); return mean + stdDev * (float)stdNormal; } From 6c8042642aaf4e04c32a98e45ee330945bc280e9 Mon Sep 17 00:00:00 2001 From: sw1tchbl4d3 Date: Mon, 20 Jun 2022 17:22:41 +0200 Subject: [PATCH 012/420] Reduce code duplication --- .../Mods/TaikoModClassic.cs | 106 +++++------------- .../Objects/Drawables/DrawableSwell.cs | 44 ++++---- osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs | 16 ++- 3 files changed, 63 insertions(+), 103 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModClassic.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModClassic.cs index 7141afe791..704185cc3c 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModClassic.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModClassic.cs @@ -4,6 +4,7 @@ #nullable disable using System.Linq; +using System.Collections.Generic; using System.Threading; using osu.Game.Beatmaps; using osu.Game.Rulesets.Judgements; @@ -40,19 +41,17 @@ namespace osu.Game.Rulesets.Taiko.Mods var hitObjects = taikoBeatmap.HitObjects.Select(ho => { - if (ho is DrumRoll drumRoll) + switch (ho) { - var newDrumRoll = new ClassicDrumRoll(drumRoll); - return newDrumRoll; - } + case DrumRoll drumRoll: + return new ClassicDrumRoll(drumRoll); - if (ho is Swell swell) - { - var newSwell = new ClassicSwell(swell); - return newSwell; - } + case Swell swell: + return new ClassicSwell(swell); - return ho; + default: + return ho; + } }).ToList(); taikoBeatmap.HitObjects = hitObjects; @@ -73,33 +72,35 @@ namespace osu.Game.Rulesets.Taiko.Mods public override Judgement CreateJudgement() => new TaikoClassicDrumRollJudgement(); - protected override void CreateTicks(CancellationToken cancellationToken) + protected override List CreateTicks(CancellationToken cancellationToken) { - if (TickSpacing == 0) - return; + List oldTicks = base.CreateTicks(cancellationToken); - bool first = true; - - for (double t = StartTime; t < EndTime + TickSpacing / 2; t += TickSpacing) + List newTicks = oldTicks.Select(oldTick => { - cancellationToken.ThrowIfCancellationRequested(); - - AddNested(new ClassicDrumRollTick + if (oldTick is DrumRollTick drumRollTick) { - FirstTick = first, - TickSpacing = TickSpacing, - StartTime = t, - IsStrong = IsStrong - }); + return new ClassicDrumRollTick(drumRollTick); + } - first = false; - } + return oldTick; + }).ToList(); + + return newTicks; } } private class ClassicDrumRollTick : DrumRollTick { public override Judgement CreateJudgement() => new TaikoClassicDrumRollTickJudgement(); + + public ClassicDrumRollTick(DrumRollTick original) + { + StartTime = original.StartTime; + Samples = original.Samples; + FirstTick = original.FirstTick; + TickSpacing = original.TickSpacing; + } } private class ClassicSwell : Swell @@ -118,12 +119,12 @@ namespace osu.Game.Rulesets.Taiko.Mods private class TaikoClassicDrumRollJudgement : TaikoDrumRollJudgement { - public override HitResult MaxResult => HitResult.LargeBonus; + public override HitResult MaxResult => HitResult.IgnoreHit; } private class TaikoClassicDrumRollTickJudgement : TaikoDrumRollTickJudgement { - public override HitResult MaxResult => HitResult.LargeBonus; + public override HitResult MaxResult => HitResult.SmallBonus; } private class TaikoClassicSwellJudgement : TaikoSwellJudgement @@ -143,7 +144,7 @@ namespace osu.Game.Rulesets.Taiko.Mods if (timeOffset < 0) return; - ApplyResult(r => r.Type = HitResult.IgnoreMiss); + ApplyResult(r => r.Type = HitResult.IgnoreHit); } } @@ -151,52 +152,7 @@ namespace osu.Game.Rulesets.Taiko.Mods { public override bool DisplayResult => false; - protected override void CheckForResult(bool userTriggered, double timeOffset) - { - if (userTriggered) - { - DrawableSwellTick nextTick = null; - - foreach (var t in Ticks) - { - if (!t.Result.HasResult) - { - nextTick = t; - break; - } - } - - nextTick?.TriggerResult(true); - - int numHits = Ticks.Count(r => r.IsHit); - - AnimateCompletion(numHits); - - if (numHits == HitObject.RequiredHits) - ApplyResult(r => r.Type = HitResult.LargeBonus); - } - else - { - if (timeOffset < 0) - return; - - int numHits = 0; - - foreach (var tick in Ticks) - { - if (tick.IsHit) - { - numHits++; - continue; - } - - if (!tick.Result.HasResult) - tick.TriggerResult(false); - } - - ApplyResult(r => r.Type = numHits > HitObject.RequiredHits / 2 ? HitResult.SmallBonus : HitResult.IgnoreMiss); - } - } + protected override HitResult OkResult => HitResult.SmallBonus; } public void Update(Playfield playfield) diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs index 34fe6c5a73..5b70c59376 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs @@ -34,7 +34,8 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables /// private const double ring_appear_offset = 100; - protected readonly Container Ticks; + protected virtual HitResult OkResult => HitResult.Ok; + private readonly Container ticks; private readonly Container bodyContainer; private readonly CircularContainer targetRing; private readonly CircularContainer expandingRing; @@ -114,7 +115,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables } }); - AddInternal(Ticks = new Container { RelativeSizeAxes = Axes.Both }); + AddInternal(ticks = new Container { RelativeSizeAxes = Axes.Both }); } [BackgroundDependencyLoader] @@ -132,20 +133,6 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables Origin = Anchor.Centre, }); - protected void AnimateCompletion(int numHits) - { - float completion = (float)numHits / HitObject.RequiredHits; - - expandingRing - .FadeTo(expandingRing.Alpha + Math.Clamp(completion / 16, 0.1f, 0.6f), 50) - .Then() - .FadeTo(completion / 8, 2000, Easing.OutQuint); - - MainPiece.Drawable.RotateTo((float)(completion * HitObject.Duration / 8), 4000, Easing.OutQuint); - - expandingRing.ScaleTo(1f + Math.Min(target_ring_scale - 1f, (target_ring_scale - 1f) * completion * 1.3f), 260, Easing.OutQuint); - } - protected override void OnFree() { base.OnFree(); @@ -162,7 +149,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables switch (hitObject) { case DrawableSwellTick tick: - Ticks.Add(tick); + ticks.Add(tick); break; } } @@ -170,7 +157,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables protected override void ClearNestedHitObjects() { base.ClearNestedHitObjects(); - Ticks.Clear(false); + ticks.Clear(false); } protected override DrawableHitObject CreateNestedHitObject(HitObject hitObject) @@ -190,7 +177,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables { DrawableSwellTick nextTick = null; - foreach (var t in Ticks) + foreach (var t in ticks) { if (!t.Result.HasResult) { @@ -201,12 +188,21 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables nextTick?.TriggerResult(true); - int numHits = Ticks.Count(r => r.IsHit); + int numHits = ticks.Count(r => r.IsHit); - AnimateCompletion(numHits); + float completion = (float)numHits / HitObject.RequiredHits; + + expandingRing + .FadeTo(expandingRing.Alpha + Math.Clamp(completion / 16, 0.1f, 0.6f), 50) + .Then() + .FadeTo(completion / 8, 2000, Easing.OutQuint); + + MainPiece.Drawable.RotateTo((float)(completion * HitObject.Duration / 8), 4000, Easing.OutQuint); + + expandingRing.ScaleTo(1f + Math.Min(target_ring_scale - 1f, (target_ring_scale - 1f) * completion * 1.3f), 260, Easing.OutQuint); if (numHits == HitObject.RequiredHits) - ApplyResult(r => r.Type = HitResult.Great); + ApplyResult(r => r.Type = r.Judgement.MaxResult); } else { @@ -215,7 +211,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables int numHits = 0; - foreach (var tick in Ticks) + foreach (var tick in ticks) { if (tick.IsHit) { @@ -227,7 +223,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables tick.TriggerResult(false); } - ApplyResult(r => r.Type = numHits > HitObject.RequiredHits / 2 ? HitResult.Ok : r.Judgement.MinResult); + ApplyResult(r => r.Type = numHits > HitObject.RequiredHits / 2 ? OkResult : r.Judgement.MinResult); } } diff --git a/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs b/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs index 80e8bec0cb..5e5d9daeb1 100644 --- a/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs +++ b/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs @@ -5,6 +5,7 @@ using osu.Game.Rulesets.Objects.Types; using System; +using System.Collections.Generic; using System.Threading; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; @@ -75,7 +76,10 @@ namespace osu.Game.Rulesets.Taiko.Objects protected override void CreateNestedHitObjects(CancellationToken cancellationToken) { - CreateTicks(cancellationToken); + foreach (TaikoHitObject tick in CreateTicks(cancellationToken)) + { + AddNested(tick); + } RequiredGoodHits = NestedHitObjects.Count * Math.Min(0.15, 0.05 + 0.10 / 6 * overallDifficulty); RequiredGreatHits = NestedHitObjects.Count * Math.Min(0.30, 0.10 + 0.20 / 6 * overallDifficulty); @@ -83,10 +87,12 @@ namespace osu.Game.Rulesets.Taiko.Objects base.CreateNestedHitObjects(cancellationToken); } - protected virtual void CreateTicks(CancellationToken cancellationToken) + protected virtual List CreateTicks(CancellationToken cancellationToken) { + List ticks = new List(); + if (TickSpacing == 0) - return; + return ticks; bool first = true; @@ -94,7 +100,7 @@ namespace osu.Game.Rulesets.Taiko.Objects { cancellationToken.ThrowIfCancellationRequested(); - AddNested(new DrumRollTick + ticks.Add(new DrumRollTick { FirstTick = first, TickSpacing = TickSpacing, @@ -104,6 +110,8 @@ namespace osu.Game.Rulesets.Taiko.Objects first = false; } + + return ticks; } public override Judgement CreateJudgement() => new TaikoDrumRollJudgement(); From 2f1186d3284b8cad32d7c6ae83a50bc72d00b326 Mon Sep 17 00:00:00 2001 From: Pasi4K5 Date: Wed, 22 Jun 2022 16:49:07 +0200 Subject: [PATCH 013/420] Add comments and XML doc --- osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs index ce7f39cdf8..a890dbde43 100644 --- a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs +++ b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs @@ -210,10 +210,16 @@ namespace osu.Game.Rulesets.Osu.Utils return (timeSinceTimingPoint + 1) % length < 2; } + /// + /// Generates a random number from a normal distribution using the Box-Muller transform. + /// public static float RandomGaussian(Random rng, float mean = 0, float stdDev = 1) { + // Generate 2 random numbers in the interval (0,1]. + // x1 must not be 0 since log(0) = undefined. double x1 = 1 - rng.NextDouble(); double x2 = 1 - rng.NextDouble(); + double stdNormal = Math.Sqrt(-2 * Math.Log(x1)) * Math.Sin(2 * Math.PI * x2); return mean + stdDev * (float)stdNormal; } From 52de8bae9bfa6e1d6859e64335366bd445bbcf8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 24 Jun 2022 12:54:33 +0200 Subject: [PATCH 014/420] Apply NRT to `TaikoModClassic` --- osu.Game.Rulesets.Taiko/Mods/TaikoModClassic.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModClassic.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModClassic.cs index 704185cc3c..9f82d54e65 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModClassic.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModClassic.cs @@ -1,10 +1,9 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Linq; using System.Collections.Generic; +using System.Diagnostics; using System.Threading; using osu.Game.Beatmaps; using osu.Game.Rulesets.Judgements; @@ -21,7 +20,7 @@ namespace osu.Game.Rulesets.Taiko.Mods { public class TaikoModClassic : ModClassic, IApplicableToDrawableRuleset, IUpdatableByPlayfield, IApplicableAfterBeatmapConversion { - private DrawableTaikoRuleset drawableTaikoRuleset; + private DrawableTaikoRuleset? drawableTaikoRuleset; public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) { @@ -160,6 +159,8 @@ namespace osu.Game.Rulesets.Taiko.Mods // Classic taiko scrolls at a constant 100px per 1000ms. More notes become visible as the playfield is lengthened. const float scroll_rate = 10; + Debug.Assert(drawableTaikoRuleset != null); + // Since the time range will depend on a positional value, it is referenced to the x480 pixel space. float ratio = drawableTaikoRuleset.DrawHeight / 480; From bcb9cba2d7019473679e8eaf9c4c22dcbc61fa94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 24 Jun 2022 12:56:47 +0200 Subject: [PATCH 015/420] Reorder classes for legibility, group into regions --- .../Mods/TaikoModClassic.cs | 66 +++++++++++-------- 1 file changed, 37 insertions(+), 29 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModClassic.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModClassic.cs index 9f82d54e65..f5541f5191 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModClassic.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModClassic.cs @@ -56,6 +56,13 @@ namespace osu.Game.Rulesets.Taiko.Mods taikoBeatmap.HitObjects = hitObjects; } + #region Classic drum roll + + private class TaikoClassicDrumRollJudgement : TaikoDrumRollJudgement + { + public override HitResult MaxResult => HitResult.IgnoreHit; + } + private class ClassicDrumRoll : DrumRoll { public ClassicDrumRoll(DrumRoll original) @@ -89,6 +96,11 @@ namespace osu.Game.Rulesets.Taiko.Mods } } + private class TaikoClassicDrumRollTickJudgement : TaikoDrumRollTickJudgement + { + public override HitResult MaxResult => HitResult.SmallBonus; + } + private class ClassicDrumRollTick : DrumRollTick { public override Judgement CreateJudgement() => new TaikoClassicDrumRollTickJudgement(); @@ -102,35 +114,6 @@ namespace osu.Game.Rulesets.Taiko.Mods } } - private class ClassicSwell : Swell - { - public ClassicSwell(Swell original) - { - StartTime = original.StartTime; - Samples = original.Samples; - EndTime = original.EndTime; - Duration = original.Duration; - RequiredHits = original.RequiredHits; - } - - public override Judgement CreateJudgement() => new TaikoClassicSwellJudgement(); - } - - private class TaikoClassicDrumRollJudgement : TaikoDrumRollJudgement - { - public override HitResult MaxResult => HitResult.IgnoreHit; - } - - private class TaikoClassicDrumRollTickJudgement : TaikoDrumRollTickJudgement - { - public override HitResult MaxResult => HitResult.SmallBonus; - } - - private class TaikoClassicSwellJudgement : TaikoSwellJudgement - { - public override HitResult MaxResult => HitResult.LargeBonus; - } - private class ClassicDrawableDrumRoll : DrawableDrumRoll { public override bool DisplayResult => false; @@ -147,6 +130,29 @@ namespace osu.Game.Rulesets.Taiko.Mods } } + #endregion + + #region Classic swell + + private class TaikoClassicSwellJudgement : TaikoSwellJudgement + { + public override HitResult MaxResult => HitResult.LargeBonus; + } + + private class ClassicSwell : Swell + { + public ClassicSwell(Swell original) + { + StartTime = original.StartTime; + Samples = original.Samples; + EndTime = original.EndTime; + Duration = original.Duration; + RequiredHits = original.RequiredHits; + } + + public override Judgement CreateJudgement() => new TaikoClassicSwellJudgement(); + } + private class ClassicDrawableSwell : DrawableSwell { public override bool DisplayResult => false; @@ -154,6 +160,8 @@ namespace osu.Game.Rulesets.Taiko.Mods protected override HitResult OkResult => HitResult.SmallBonus; } + #endregion + public void Update(Playfield playfield) { // Classic taiko scrolls at a constant 100px per 1000ms. More notes become visible as the playfield is lengthened. From f47b74a93882444db536c2764bd531a6a712d820 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 24 Jun 2022 13:00:12 +0200 Subject: [PATCH 016/420] Move `OkResult` from drawable swell to judgement --- .../Judgements/TaikoSwellJudgement.cs | 5 +++++ osu.Game.Rulesets.Taiko/Mods/TaikoModClassic.cs | 4 ++-- .../Objects/Drawables/DrawableSwell.cs | 9 ++++++--- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Judgements/TaikoSwellJudgement.cs b/osu.Game.Rulesets.Taiko/Judgements/TaikoSwellJudgement.cs index b2ac0b7f03..90ba7e276c 100644 --- a/osu.Game.Rulesets.Taiko/Judgements/TaikoSwellJudgement.cs +++ b/osu.Game.Rulesets.Taiko/Judgements/TaikoSwellJudgement.cs @@ -9,6 +9,11 @@ namespace osu.Game.Rulesets.Taiko.Judgements { public class TaikoSwellJudgement : TaikoJudgement { + /// + /// The to grant when the player has hit more than half of swell ticks. + /// + public virtual HitResult OkResult => HitResult.Ok; + protected override double HealthIncreaseFor(HitResult result) { switch (result) diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModClassic.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModClassic.cs index f5541f5191..9100c0b4be 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModClassic.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModClassic.cs @@ -137,6 +137,8 @@ namespace osu.Game.Rulesets.Taiko.Mods private class TaikoClassicSwellJudgement : TaikoSwellJudgement { public override HitResult MaxResult => HitResult.LargeBonus; + + public override HitResult OkResult => HitResult.SmallBonus; } private class ClassicSwell : Swell @@ -156,8 +158,6 @@ namespace osu.Game.Rulesets.Taiko.Mods private class ClassicDrawableSwell : DrawableSwell { public override bool DisplayResult => false; - - protected override HitResult OkResult => HitResult.SmallBonus; } #endregion diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs index 5b70c59376..cb0f59a9fb 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs @@ -16,7 +16,7 @@ using osuTK.Graphics; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; using osu.Game.Rulesets.Objects; -using osu.Game.Rulesets.Scoring; +using osu.Game.Rulesets.Taiko.Judgements; using osu.Game.Rulesets.Taiko.Skinning.Default; using osu.Game.Skinning; @@ -34,7 +34,6 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables /// private const double ring_appear_offset = 100; - protected virtual HitResult OkResult => HitResult.Ok; private readonly Container ticks; private readonly Container bodyContainer; private readonly CircularContainer targetRing; @@ -223,7 +222,11 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables tick.TriggerResult(false); } - ApplyResult(r => r.Type = numHits > HitObject.RequiredHits / 2 ? OkResult : r.Judgement.MinResult); + ApplyResult(r => + { + var swellJudgement = (TaikoSwellJudgement)r.Judgement; + r.Type = numHits > HitObject.RequiredHits / 2 ? swellJudgement.OkResult : swellJudgement.MinResult; + }); } } From 3497e966fd97a834c17ad2046e59f37ba14f56c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 24 Jun 2022 13:01:16 +0200 Subject: [PATCH 017/420] Revert no longer needed access modifier change --- osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs b/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs index 5e5d9daeb1..21c7f13fec 100644 --- a/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs +++ b/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs @@ -57,7 +57,7 @@ namespace osu.Game.Rulesets.Taiko.Objects /// The length (in milliseconds) between ticks of this drumroll. /// Half of this value is the hit window of the ticks. /// - protected double TickSpacing = 100; + private double tickSpacing = 100; private float overallDifficulty = BeatmapDifficulty.DEFAULT_DIFFICULTY; @@ -70,7 +70,7 @@ namespace osu.Game.Rulesets.Taiko.Objects double scoringDistance = base_distance * difficulty.SliderMultiplier * DifficultyControlPoint.SliderVelocity; Velocity = scoringDistance / timingPoint.BeatLength; - TickSpacing = timingPoint.BeatLength / TickRate; + tickSpacing = timingPoint.BeatLength / TickRate; overallDifficulty = difficulty.OverallDifficulty; } @@ -91,19 +91,19 @@ namespace osu.Game.Rulesets.Taiko.Objects { List ticks = new List(); - if (TickSpacing == 0) + if (tickSpacing == 0) return ticks; bool first = true; - for (double t = StartTime; t < EndTime + TickSpacing / 2; t += TickSpacing) + for (double t = StartTime; t < EndTime + tickSpacing / 2; t += tickSpacing) { cancellationToken.ThrowIfCancellationRequested(); ticks.Add(new DrumRollTick { FirstTick = first, - TickSpacing = TickSpacing, + TickSpacing = tickSpacing, StartTime = t, IsStrong = IsStrong }); From 09768cd74054e6cb806f0858bb5732bc9b27fd61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 24 Jun 2022 13:43:02 +0200 Subject: [PATCH 018/420] Add test coverage --- .../Mods/TestSceneTaikoModClassic.cs | 80 +++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModClassic.cs diff --git a/osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModClassic.cs b/osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModClassic.cs new file mode 100644 index 0000000000..9028f411ce --- /dev/null +++ b/osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModClassic.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 NUnit.Framework; +using osu.Game.Rulesets.Replays; +using osu.Game.Rulesets.Taiko.Beatmaps; +using osu.Game.Rulesets.Taiko.Mods; +using osu.Game.Rulesets.Taiko.Objects; +using osu.Game.Rulesets.Taiko.Replays; + +namespace osu.Game.Rulesets.Taiko.Tests.Mods +{ + public class TestSceneTaikoModClassic : TaikoModTestScene + { + [Test] + public void TestHittingDrumRollsIsOptional() => CreateModTest(new ModTestData + { + Mod = new TaikoModClassic(), + Autoplay = false, + Beatmap = new TaikoBeatmap + { + BeatmapInfo = { Ruleset = CreatePlayerRuleset().RulesetInfo }, + HitObjects = new List + { + new Hit + { + StartTime = 1000, + Type = HitType.Centre + }, + new DrumRoll + { + StartTime = 3000, + EndTime = 6000 + } + } + }, + ReplayFrames = new List + { + new TaikoReplayFrame(1000, TaikoAction.LeftCentre), + new TaikoReplayFrame(1001) + }, + PassCondition = () => Player.ScoreProcessor.HasCompleted.Value + && Player.ScoreProcessor.Combo.Value == 1 + && Player.ScoreProcessor.Accuracy.Value == 1 + }); + + [Test] + public void TestHittingSwellsIsOptional() => CreateModTest(new ModTestData + { + Mod = new TaikoModClassic(), + Autoplay = false, + Beatmap = new TaikoBeatmap + { + BeatmapInfo = { Ruleset = CreatePlayerRuleset().RulesetInfo }, + HitObjects = new List + { + new Hit + { + StartTime = 1000, + Type = HitType.Centre + }, + new Swell + { + StartTime = 3000, + EndTime = 6000 + } + } + }, + ReplayFrames = new List + { + new TaikoReplayFrame(1000, TaikoAction.LeftCentre), + new TaikoReplayFrame(1001) + }, + PassCondition = () => Player.ScoreProcessor.HasCompleted.Value + && Player.ScoreProcessor.Combo.Value == 1 + && Player.ScoreProcessor.Accuracy.Value == 1 + }); + } +} From 855debd5f66c2c1a4cd25d63bfd61d0dc477fbaa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=82=BA=E4=BB=80=E9=BA=BC?= Date: Sun, 10 Jul 2022 09:29:17 +0800 Subject: [PATCH 019/420] Remove nullable disable annotation and mark some return value as nullable. --- osu.Game/Rulesets/Ruleset.cs | 25 +++++++++---------------- 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index c1ec6c30ef..7130f4bc35 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.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. -#nullable disable - using System; using System.Collections.Concurrent; using System.Collections.Generic; @@ -81,7 +79,7 @@ namespace osu.Game.Rulesets /// Returns a fresh instance of the mod matching the specified acronym. /// /// The acronym to query for . - public Mod CreateModFromAcronym(string acronym) + public Mod? CreateModFromAcronym(string acronym) { return AllMods.FirstOrDefault(m => m.Acronym == acronym)?.CreateInstance(); } @@ -89,7 +87,7 @@ namespace osu.Game.Rulesets /// /// Returns a fresh instance of the mod matching the specified type. /// - public T CreateMod() + public T? CreateMod() where T : Mod { return AllMods.FirstOrDefault(m => m is T)?.CreateInstance() as T; @@ -183,10 +181,9 @@ namespace osu.Game.Rulesets return value; } - [CanBeNull] - public ModAutoplay GetAutoplayMod() => CreateMod(); + public ModAutoplay? GetAutoplayMod() => CreateMod(); - public virtual ISkin CreateLegacySkinProvider([NotNull] ISkin skin, IBeatmap beatmap) => null; + public virtual ISkin? CreateLegacySkinProvider([NotNull] ISkin skin, IBeatmap beatmap) => null; protected Ruleset() { @@ -206,7 +203,7 @@ namespace osu.Game.Rulesets /// The beatmap to create the hit renderer for. /// The s to apply. /// Unable to successfully load the beatmap to be usable with this ruleset. - public abstract DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList mods = null); + public abstract DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList? mods = null); /// /// Creates a for this . @@ -240,8 +237,7 @@ namespace osu.Game.Rulesets /// Optionally creates a to generate performance data from the provided score. /// /// A performance calculator instance for the provided score. - [CanBeNull] - public virtual PerformanceCalculator CreatePerformanceCalculator() => null; + public virtual PerformanceCalculator? CreatePerformanceCalculator() => null; public virtual HitObjectComposer CreateHitObjectComposer() => null; @@ -253,7 +249,7 @@ namespace osu.Game.Rulesets public abstract string Description { get; } - public virtual RulesetSettingsSubsection CreateSettings() => null; + public virtual RulesetSettingsSubsection? CreateSettings() => null; /// /// Creates the for this . @@ -303,7 +299,6 @@ namespace osu.Game.Rulesets /// The to create the statistics for. The score is guaranteed to have populated. /// 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(); /// @@ -356,13 +351,11 @@ namespace osu.Game.Rulesets /// /// Creates ruleset-specific beatmap filter criteria to be used on the song select screen. /// - [CanBeNull] - public virtual IRulesetFilterCriteria CreateRulesetFilterCriteria() => null; + public virtual IRulesetFilterCriteria? CreateRulesetFilterCriteria() => null; /// /// Can be overridden to add a ruleset-specific section to the editor beatmap setup screen. /// - [CanBeNull] - public virtual RulesetSetupSection CreateEditorSetupSection() => null; + public virtual RulesetSetupSection? CreateEditorSetupSection() => null; } } From 4a503bab0a3d520965efeb54594053baaacc348c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=82=BA=E4=BB=80=E9=BA=BC?= Date: Sun, 10 Jul 2022 09:29:59 +0800 Subject: [PATCH 020/420] Remove unnecessary attribute. --- osu.Game/Rulesets/Ruleset.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index 7130f4bc35..af8ff10fa1 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -23,7 +23,6 @@ using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; using osu.Game.Skinning; using osu.Game.Users; -using JetBrains.Annotations; using osu.Framework.Extensions; using osu.Framework.Extensions.EnumExtensions; using osu.Framework.Testing; @@ -101,7 +100,6 @@ namespace osu.Game.Rulesets /// then the proper behaviour is to return an empty enumerable. /// mods should not be present in the returned enumerable. /// - [ItemNotNull] public abstract IEnumerable GetModsFor(ModType type); /// @@ -183,7 +181,7 @@ namespace osu.Game.Rulesets public ModAutoplay? GetAutoplayMod() => CreateMod(); - public virtual ISkin? CreateLegacySkinProvider([NotNull] ISkin skin, IBeatmap beatmap) => null; + public virtual ISkin? CreateLegacySkinProvider(ISkin skin, IBeatmap beatmap) => null; protected Ruleset() { From e67cb4c90520e4c328c653372bd11b20a67a9935 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=82=BA=E4=BB=80=E9=BA=BC?= Date: Sun, 10 Jul 2022 09:35:46 +0800 Subject: [PATCH 021/420] Mark create beatmap verifier as nullable because seems it's not requirement to be implemented. --- osu.Game/Rulesets/Ruleset.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index af8ff10fa1..14513b06b8 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -239,7 +239,7 @@ namespace osu.Game.Rulesets public virtual HitObjectComposer CreateHitObjectComposer() => null; - public virtual IBeatmapVerifier CreateBeatmapVerifier() => null; + public virtual IBeatmapVerifier? CreateBeatmapVerifier() => null; public virtual Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.Solid.QuestionCircle }; From 857377e1451402cb8848da21c9cd0d00c01dbb15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=82=BA=E4=BB=80=E9=BA=BC?= Date: Sun, 10 Jul 2022 09:53:05 +0800 Subject: [PATCH 022/420] Move CreateConvertibleReplayFrame() into legacy ruleset interface because technically only legacy ruleset use it to convert the legacy frame. But seems some of the customized ruleset use it for save the replay frame. --- 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/ILegacyRuleset.cs | 9 ++++++++- osu.Game/Rulesets/Ruleset.cs | 8 -------- osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs | 2 +- osu.Game/Screens/Play/SpectatorPlayer.cs | 6 +++++- 8 files changed, 18 insertions(+), 15 deletions(-) diff --git a/osu.Game.Rulesets.Catch/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs index f832d99807..766b5f6f58 100644 --- a/osu.Game.Rulesets.Catch/CatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs @@ -187,7 +187,7 @@ namespace osu.Game.Rulesets.Catch public int LegacyID => 2; - public override IConvertibleReplayFrame CreateConvertibleReplayFrame() => new CatchReplayFrame(); + public IConvertibleReplayFrame CreateConvertibleReplayFrame() => new CatchReplayFrame(); public override HitObjectComposer CreateHitObjectComposer() => new CatchHitObjectComposer(this); diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index 4723416c30..f45a2517df 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -280,7 +280,7 @@ namespace osu.Game.Rulesets.Mania public int LegacyID => 3; - public override IConvertibleReplayFrame CreateConvertibleReplayFrame() => new ManiaReplayFrame(); + public IConvertibleReplayFrame CreateConvertibleReplayFrame() => new ManiaReplayFrame(); public override IRulesetConfigManager CreateConfig(SettingsStore settings) => new ManiaRulesetConfigManager(settings, RulesetInfo); diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index 120ce32612..392574ef03 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -234,7 +234,7 @@ namespace osu.Game.Rulesets.Osu public int LegacyID => 0; - public override IConvertibleReplayFrame CreateConvertibleReplayFrame() => new OsuReplayFrame(); + public IConvertibleReplayFrame CreateConvertibleReplayFrame() => new OsuReplayFrame(); public override IRulesetConfigManager CreateConfig(SettingsStore settings) => new OsuRulesetConfigManager(settings, RulesetInfo); diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index 223e268d7f..66fc64c018 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -177,7 +177,7 @@ namespace osu.Game.Rulesets.Taiko public int LegacyID => 1; - public override IConvertibleReplayFrame CreateConvertibleReplayFrame() => new TaikoReplayFrame(); + public IConvertibleReplayFrame CreateConvertibleReplayFrame() => new TaikoReplayFrame(); protected override IEnumerable GetValidHitResults() { diff --git a/osu.Game/Rulesets/ILegacyRuleset.cs b/osu.Game/Rulesets/ILegacyRuleset.cs index a8cfed4866..c2617a065c 100644 --- a/osu.Game/Rulesets/ILegacyRuleset.cs +++ b/osu.Game/Rulesets/ILegacyRuleset.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. -#nullable disable +using osu.Game.Rulesets.Replays.Types; namespace osu.Game.Rulesets { @@ -13,5 +13,12 @@ namespace osu.Game.Rulesets /// Identifies the server-side ID of a legacy ruleset. /// int LegacyID { get; } + + /// + /// For rulesets which support legacy (osu-stable) replay conversion, this method will create an empty replay frame + /// for conversion use. + /// + /// An empty frame for the current ruleset, or null if unsupported. + IConvertibleReplayFrame CreateConvertibleReplayFrame(); } } diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index 14513b06b8..89ef6d2d54 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -13,7 +13,6 @@ using osu.Game.Beatmaps; using osu.Game.Overlays.Settings; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Replays.Types; using osu.Game.Rulesets.UI; using osu.Game.Beatmaps.Legacy; using osu.Game.Configuration; @@ -284,13 +283,6 @@ namespace osu.Game.Rulesets /// A descriptive name of the variant. public virtual string GetVariantName(int variant) => string.Empty; - /// - /// For rulesets which support legacy (osu-stable) replay conversion, this method will create an empty replay frame - /// for conversion use. - /// - /// An empty frame for the current ruleset, or null if unsupported. - public virtual IConvertibleReplayFrame CreateConvertibleReplayFrame() => null; - /// /// Creates the statistics for a to be displayed in the results screen. /// diff --git a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs index f64e730c06..2b22b4abc7 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs @@ -282,7 +282,7 @@ namespace osu.Game.Scoring.Legacy private ReplayFrame convertFrame(LegacyReplayFrame currentFrame, ReplayFrame lastFrame) { - var convertible = currentRuleset.CreateConvertibleReplayFrame(); + var convertible = (currentRuleset as ILegacyRuleset)?.CreateConvertibleReplayFrame(); if (convertible == null) throw new InvalidOperationException($"Legacy replay cannot be converted for the ruleset: {currentRuleset.Description}"); diff --git a/osu.Game/Screens/Play/SpectatorPlayer.cs b/osu.Game/Screens/Play/SpectatorPlayer.cs index 797787b194..f59fd9559c 100644 --- a/osu.Game/Screens/Play/SpectatorPlayer.cs +++ b/osu.Game/Screens/Play/SpectatorPlayer.cs @@ -10,6 +10,7 @@ using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Online.Spectator; +using osu.Game.Rulesets; using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.Replays.Types; using osu.Game.Scoring; @@ -79,11 +80,14 @@ namespace osu.Game.Screens.Play if (!this.IsCurrentScreen()) return; + if (GameplayState.Ruleset is not ILegacyRuleset legacyRuleset) + return; + bool isFirstBundle = score.Replay.Frames.Count == 0; foreach (var frame in bundle.Frames) { - IConvertibleReplayFrame convertibleFrame = GameplayState.Ruleset.CreateConvertibleReplayFrame(); + IConvertibleReplayFrame convertibleFrame = legacyRuleset.CreateConvertibleReplayFrame(); convertibleFrame.FromLegacy(frame, GameplayState.Beatmap); var convertedFrame = (ReplayFrame)convertibleFrame; From d39f53f1f0e2ab307c593eb928078f0c55a13684 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=82=BA=E4=BB=80=E9=BA=BC?= Date: Sun, 10 Jul 2022 10:01:56 +0800 Subject: [PATCH 023/420] Mark `CreateConfig()` return type as nullable because it's not required all ruleset to implement. Also, remove nullable disable annotation for all using classes. Setting store can be nullable because `RulesetConfigManager()` can accept null setting store. --- osu.Game/Rulesets/Ruleset.cs | 2 +- osu.Game/Rulesets/RulesetConfigCache.cs | 6 ++---- osu.Game/Tests/Rulesets/TestRulesetConfigCache.cs | 6 ++---- 3 files changed, 5 insertions(+), 9 deletions(-) diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index 89ef6d2d54..cd3ab4f726 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -252,7 +252,7 @@ namespace osu.Game.Rulesets /// Creates the for this . /// /// The to store the settings. - public virtual IRulesetConfigManager CreateConfig(SettingsStore settings) => null; + public virtual IRulesetConfigManager? CreateConfig(SettingsStore? settings) => null; /// /// A unique short name to reference this ruleset in online requests. diff --git a/osu.Game/Rulesets/RulesetConfigCache.cs b/osu.Game/Rulesets/RulesetConfigCache.cs index 017214df61..ab44e86048 100644 --- a/osu.Game/Rulesets/RulesetConfigCache.cs +++ b/osu.Game/Rulesets/RulesetConfigCache.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. -#nullable disable - using System; using System.Collections.Generic; using osu.Framework.Graphics; @@ -18,7 +16,7 @@ namespace osu.Game.Rulesets private readonly RealmAccess realm; private readonly RulesetStore rulesets; - private readonly Dictionary configCache = new Dictionary(); + private readonly Dictionary configCache = new Dictionary(); public RulesetConfigCache(RealmAccess realm, RulesetStore rulesets) { @@ -42,7 +40,7 @@ namespace osu.Game.Rulesets } } - public IRulesetConfigManager GetConfigFor(Ruleset ruleset) + public IRulesetConfigManager? GetConfigFor(Ruleset ruleset) { if (!IsLoaded) throw new InvalidOperationException($@"Cannot retrieve {nameof(IRulesetConfigManager)} before {nameof(RulesetConfigCache)} has loaded"); diff --git a/osu.Game/Tests/Rulesets/TestRulesetConfigCache.cs b/osu.Game/Tests/Rulesets/TestRulesetConfigCache.cs index a80154c38e..cf637983d9 100644 --- a/osu.Game/Tests/Rulesets/TestRulesetConfigCache.cs +++ b/osu.Game/Tests/Rulesets/TestRulesetConfigCache.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. -#nullable disable - using System.Collections.Concurrent; using osu.Game.Rulesets; using osu.Game.Rulesets.Configuration; @@ -14,8 +12,8 @@ namespace osu.Game.Tests.Rulesets /// public class TestRulesetConfigCache : IRulesetConfigCache { - private readonly ConcurrentDictionary configCache = new ConcurrentDictionary(); + private readonly ConcurrentDictionary configCache = new ConcurrentDictionary(); - public IRulesetConfigManager GetConfigFor(Ruleset ruleset) => configCache.GetOrAdd(ruleset.ShortName, _ => ruleset.CreateConfig(null)); + public IRulesetConfigManager? GetConfigFor(Ruleset ruleset) => configCache.GetOrAdd(ruleset.ShortName, _ => ruleset.CreateConfig(null)); } } From 57c6763556920d09399fe679c1dfc8d386686759 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=82=BA=E4=BB=80=E9=BA=BC?= Date: Sun, 10 Jul 2022 10:07:09 +0800 Subject: [PATCH 024/420] Mark the `CreateBeatmapProcessor()` as nullable. Also, should add the null check in the working beatmap. --- osu.Game/Beatmaps/WorkingBeatmap.cs | 11 +++++++---- osu.Game/Rulesets/Ruleset.cs | 2 +- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs index 16464932e0..22c3ad43e6 100644 --- a/osu.Game/Beatmaps/WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/WorkingBeatmap.cs @@ -279,12 +279,15 @@ namespace osu.Game.Beatmaps } } - IBeatmapProcessor processor = rulesetInstance.CreateBeatmapProcessor(converted); + var processor = rulesetInstance.CreateBeatmapProcessor(converted); - foreach (var mod in mods.OfType()) - mod.ApplyToBeatmapProcessor(processor); + if (processor != null) + { + foreach (var mod in mods.OfType()) + mod.ApplyToBeatmapProcessor(processor); - processor?.PreProcess(); + processor.PreProcess(); + } // Compute default values for hitobjects, including creating nested hitobjects in-case they're needed foreach (var obj in converted.HitObjects) diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index cd3ab4f726..bd2d147a08 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -226,7 +226,7 @@ namespace osu.Game.Rulesets /// /// The to be processed. /// The . - public virtual IBeatmapProcessor CreateBeatmapProcessor(IBeatmap beatmap) => null; + public virtual IBeatmapProcessor? CreateBeatmapProcessor(IBeatmap beatmap) => null; public abstract DifficultyCalculator CreateDifficultyCalculator(IWorkingBeatmap beatmap); From 8e1ed1c621d12c4b1a1f97812968043b8eb7017c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=82=BA=E4=BB=80=E9=BA=BC?= Date: Sun, 10 Jul 2022 10:09:32 +0800 Subject: [PATCH 025/420] Mark CreateHitObjectComposer() accept null. And add the null check in the test case. --- osu.Game.Tests/Visual/Editing/TimelineTestScene.cs | 8 +++++++- osu.Game/Rulesets/Ruleset.cs | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TimelineTestScene.cs b/osu.Game.Tests/Visual/Editing/TimelineTestScene.cs index 437f06c47f..7c11b4aa56 100644 --- a/osu.Game.Tests/Visual/Editing/TimelineTestScene.cs +++ b/osu.Game.Tests/Visual/Editing/TimelineTestScene.cs @@ -39,7 +39,13 @@ namespace osu.Game.Tests.Visual.Editing Dependencies.Cache(EditorBeatmap); Dependencies.CacheAs(EditorBeatmap); - Composer = playable.BeatmapInfo.Ruleset.CreateInstance().CreateHitObjectComposer().With(d => d.Alpha = 0); + Composer = playable.BeatmapInfo.Ruleset.CreateInstance().CreateHitObjectComposer().With(d => + { + if (d == null) + return; + + d.Alpha = 0; + }); Add(new OsuContextMenuContainer { diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index bd2d147a08..2cab529a4b 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -236,7 +236,7 @@ namespace osu.Game.Rulesets /// A performance calculator instance for the provided score. public virtual PerformanceCalculator? CreatePerformanceCalculator() => null; - public virtual HitObjectComposer CreateHitObjectComposer() => null; + public virtual HitObjectComposer? CreateHitObjectComposer() => null; public virtual IBeatmapVerifier? CreateBeatmapVerifier() => null; From 1725a76fa0183cd9e9c9b7a2139ee498f525a11a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=82=BA=E4=BB=80=E9=BA=BC?= Date: Sun, 10 Jul 2022 10:15:02 +0800 Subject: [PATCH 026/420] Remove the nullable disable annotation for all rulesets. --- osu.Game.Rulesets.Catch/CatchRuleset.cs | 4 +--- osu.Game.Rulesets.Mania/ManiaRuleset.cs | 6 ++---- osu.Game.Rulesets.Osu/OsuRuleset.cs | 6 ++---- osu.Game.Rulesets.Taiko/TaikoRuleset.cs | 4 +--- 4 files changed, 6 insertions(+), 14 deletions(-) diff --git a/osu.Game.Rulesets.Catch/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs index 766b5f6f58..964598dcaf 100644 --- a/osu.Game.Rulesets.Catch/CatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/CatchRuleset.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. -#nullable disable - using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Rulesets.Catch.Mods; @@ -32,7 +30,7 @@ namespace osu.Game.Rulesets.Catch { public class CatchRuleset : Ruleset, ILegacyRuleset { - public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList mods = null) => new DrawableCatchRuleset(this, beatmap, mods); + public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList? mods = null) => new DrawableCatchRuleset(this, beatmap, mods); public override ScoreProcessor CreateScoreProcessor() => new CatchScoreProcessor(); diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index f45a2517df..b391c64572 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.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. -#nullable disable - using System; using osu.Game.Beatmaps; using osu.Game.Rulesets.Mania.Mods; @@ -47,7 +45,7 @@ namespace osu.Game.Rulesets.Mania /// public const int MAX_STAGE_KEYS = 10; - public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList mods = null) => new DrawableManiaRuleset(this, beatmap, mods); + public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList? mods = null) => new DrawableManiaRuleset(this, beatmap, mods); public override ScoreProcessor CreateScoreProcessor() => new ManiaScoreProcessor(); @@ -282,7 +280,7 @@ namespace osu.Game.Rulesets.Mania public IConvertibleReplayFrame CreateConvertibleReplayFrame() => new ManiaReplayFrame(); - public override IRulesetConfigManager CreateConfig(SettingsStore settings) => new ManiaRulesetConfigManager(settings, RulesetInfo); + public override IRulesetConfigManager CreateConfig(SettingsStore? settings) => new ManiaRulesetConfigManager(settings, RulesetInfo); public override RulesetSettingsSubsection CreateSettings() => new ManiaSettingsSubsection(this); diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index 392574ef03..458a0a12f4 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.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. -#nullable disable - using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Rulesets.Mods; @@ -43,7 +41,7 @@ namespace osu.Game.Rulesets.Osu { public class OsuRuleset : Ruleset, ILegacyRuleset { - public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList mods = null) => new DrawableOsuRuleset(this, beatmap, mods); + public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList? mods = null) => new DrawableOsuRuleset(this, beatmap, mods); public override ScoreProcessor CreateScoreProcessor() => new OsuScoreProcessor(); @@ -236,7 +234,7 @@ namespace osu.Game.Rulesets.Osu public IConvertibleReplayFrame CreateConvertibleReplayFrame() => new OsuReplayFrame(); - public override IRulesetConfigManager CreateConfig(SettingsStore settings) => new OsuRulesetConfigManager(settings, RulesetInfo); + public override IRulesetConfigManager CreateConfig(SettingsStore? settings) => new OsuRulesetConfigManager(settings, RulesetInfo); protected override IEnumerable GetValidHitResults() { diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index 66fc64c018..19aa3e1016 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.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. -#nullable disable - using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Rulesets.Mods; @@ -36,7 +34,7 @@ namespace osu.Game.Rulesets.Taiko { public class TaikoRuleset : Ruleset, ILegacyRuleset { - public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList mods = null) => new DrawableTaikoRuleset(this, beatmap, mods); + public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList? mods = null) => new DrawableTaikoRuleset(this, beatmap, mods); public override ScoreProcessor CreateScoreProcessor() => new TaikoScoreProcessor(); From 3dbd0c9055dac6c32e9b8bd31baad2d6d42ac7cb Mon Sep 17 00:00:00 2001 From: andy Date: Tue, 12 Jul 2022 09:37:16 +0800 Subject: [PATCH 027/420] Update osu.Game.Tests/Visual/Editing/TimelineTestScene.cs Co-authored-by: Salman Ahmed --- osu.Game.Tests/Visual/Editing/TimelineTestScene.cs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TimelineTestScene.cs b/osu.Game.Tests/Visual/Editing/TimelineTestScene.cs index 7c11b4aa56..40d91dcbaa 100644 --- a/osu.Game.Tests/Visual/Editing/TimelineTestScene.cs +++ b/osu.Game.Tests/Visual/Editing/TimelineTestScene.cs @@ -39,13 +39,10 @@ namespace osu.Game.Tests.Visual.Editing Dependencies.Cache(EditorBeatmap); Dependencies.CacheAs(EditorBeatmap); - Composer = playable.BeatmapInfo.Ruleset.CreateInstance().CreateHitObjectComposer().With(d => - { - if (d == null) - return; + Composer = playable.BeatmapInfo.Ruleset.CreateInstance().CreateHitObjectComposer(); + Debug.Assert(Composer != null); - d.Alpha = 0; - }); + Composer.Alpha = 0; Add(new OsuContextMenuContainer { From 079fcf13a6245a987e12fa7db7a06401452be51f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=82=BA=E4=BB=80=E9=BA=BC?= Date: Tue, 12 Jul 2022 09:41:03 +0800 Subject: [PATCH 028/420] Add missing import. --- osu.Game.Tests/Visual/Editing/TimelineTestScene.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Tests/Visual/Editing/TimelineTestScene.cs b/osu.Game.Tests/Visual/Editing/TimelineTestScene.cs index 40d91dcbaa..d4c96a837b 100644 --- a/osu.Game.Tests/Visual/Editing/TimelineTestScene.cs +++ b/osu.Game.Tests/Visual/Editing/TimelineTestScene.cs @@ -3,6 +3,7 @@ #nullable disable +using System.Diagnostics; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Bindables; From 6a643cb6eae329fe7e434c07392f195f283890ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=82=BA=E4=BB=80=E9=BA=BC?= Date: Tue, 12 Jul 2022 09:41:19 +0800 Subject: [PATCH 029/420] Use Debug.Assert instead. --- osu.Game/Screens/Play/SpectatorPlayer.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/SpectatorPlayer.cs b/osu.Game/Screens/Play/SpectatorPlayer.cs index f59fd9559c..659d7d5d6c 100644 --- a/osu.Game/Screens/Play/SpectatorPlayer.cs +++ b/osu.Game/Screens/Play/SpectatorPlayer.cs @@ -3,6 +3,7 @@ #nullable disable +using System.Diagnostics; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Screens; @@ -80,8 +81,8 @@ namespace osu.Game.Screens.Play if (!this.IsCurrentScreen()) return; - if (GameplayState.Ruleset is not ILegacyRuleset legacyRuleset) - return; + var legacyRuleset = GameplayState.Ruleset as ILegacyRuleset; + Debug.Assert(legacyRuleset != null); bool isFirstBundle = score.Replay.Frames.Count == 0; From a5a8fac66f6e58afd9b4984c94be0dedef7a2e4a Mon Sep 17 00:00:00 2001 From: solstice23 Date: Thu, 21 Jul 2022 18:24:31 +0800 Subject: [PATCH 030/420] Add multiple units support in search length criteria --- osu.Game/Screens/Select/FilterQueryParser.cs | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Select/FilterQueryParser.cs b/osu.Game/Screens/Select/FilterQueryParser.cs index 03b72bf5e9..436beae30d 100644 --- a/osu.Game/Screens/Select/FilterQueryParser.cs +++ b/osu.Game/Screens/Select/FilterQueryParser.cs @@ -312,11 +312,18 @@ namespace osu.Game.Screens.Select private static bool tryUpdateLengthRange(FilterCriteria criteria, Operator op, string val) { - if (!tryParseDoubleWithPoint(val.TrimEnd('m', 's', 'h'), out double length)) - return false; - - int scale = getLengthScale(val); - return tryUpdateCriteriaRange(ref criteria.Length, op, length * scale, scale / 2.0); + string[] parts = Regex.Split(val, @"(?<=[msh])").Where(x => x.Length > 0).ToArray(); + double totalLength = 0; + int minScale = 1000; + foreach (string part in parts) + { + if (!tryParseDoubleWithPoint(part.TrimEnd('m', 's', 'h'), out double length)) + return false; + int scale = getLengthScale(part); + totalLength += length * scale; + minScale = Math.Min(minScale, scale); + } + return tryUpdateCriteriaRange(ref criteria.Length, op, totalLength, minScale / 2.0); } } } From 6baaef432f086209c163b52d5393d3d32ce62e7c Mon Sep 17 00:00:00 2001 From: solstice23 Date: Thu, 21 Jul 2022 18:49:13 +0800 Subject: [PATCH 031/420] Add colon parsing support in search length criteria --- osu.Game/Screens/Select/FilterQueryParser.cs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/osu.Game/Screens/Select/FilterQueryParser.cs b/osu.Game/Screens/Select/FilterQueryParser.cs index 436beae30d..ee7a1617cd 100644 --- a/osu.Game/Screens/Select/FilterQueryParser.cs +++ b/osu.Game/Screens/Select/FilterQueryParser.cs @@ -312,9 +312,25 @@ namespace osu.Game.Screens.Select private static bool tryUpdateLengthRange(FilterCriteria criteria, Operator op, string val) { + if (val.Contains(':')) + { + if (val.Contains('h') || val.Contains('m') || val.Contains('s')) + return false; + + int count = val.Count(c => c == ':'); + if (count > 3) + return false; + + if (count > 2) + val = val.Replace(':', 'h'); + + val = val.Replace(':', 'm'); + } + string[] parts = Regex.Split(val, @"(?<=[msh])").Where(x => x.Length > 0).ToArray(); double totalLength = 0; int minScale = 1000; + foreach (string part in parts) { if (!tryParseDoubleWithPoint(part.TrimEnd('m', 's', 'h'), out double length)) @@ -323,6 +339,7 @@ namespace osu.Game.Screens.Select totalLength += length * scale; minScale = Math.Min(minScale, scale); } + return tryUpdateCriteriaRange(ref criteria.Length, op, totalLength, minScale / 2.0); } } From 52fad1e14d4292f0c867cb099eb1fcee1b6b960f Mon Sep 17 00:00:00 2001 From: solstice23 Date: Thu, 21 Jul 2022 18:59:48 +0800 Subject: [PATCH 032/420] Add test cases for search length criteria --- osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs index bd0617515b..9f97c39462 100644 --- a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs +++ b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs @@ -125,6 +125,10 @@ namespace osu.Game.Tests.NonVisual.Filtering new object[] { "9m", TimeSpan.FromMinutes(9), TimeSpan.FromMinutes(1) }, new object[] { "0.25h", TimeSpan.FromHours(0.25), TimeSpan.FromHours(1) }, new object[] { "70", TimeSpan.FromSeconds(70), TimeSpan.FromSeconds(1) }, + new object[] { "7m27s", TimeSpan.FromSeconds(447), TimeSpan.FromSeconds(1) }, + new object[] { "6h5m", TimeSpan.FromMinutes(365), TimeSpan.FromMinutes(1) }, + new object[] { "7:27", TimeSpan.FromSeconds(447), TimeSpan.FromSeconds(1) }, + new object[] { "0:2:35", TimeSpan.FromSeconds(155), TimeSpan.FromSeconds(1) }, }; [Test] From ae0902ca86e30d690459c1dedb28a9d01f0d6ff7 Mon Sep 17 00:00:00 2001 From: solstice23 Date: Fri, 22 Jul 2022 02:55:11 +0800 Subject: [PATCH 033/420] Fix lax in search criteria parsing --- osu.Game/Screens/Select/FilterQueryParser.cs | 49 +++++++++++++------- 1 file changed, 33 insertions(+), 16 deletions(-) diff --git a/osu.Game/Screens/Select/FilterQueryParser.cs b/osu.Game/Screens/Select/FilterQueryParser.cs index ee7a1617cd..25dd342353 100644 --- a/osu.Game/Screens/Select/FilterQueryParser.cs +++ b/osu.Game/Screens/Select/FilterQueryParser.cs @@ -4,6 +4,7 @@ #nullable disable using System; +using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Text.RegularExpressions; @@ -312,34 +313,50 @@ namespace osu.Game.Screens.Select private static bool tryUpdateLengthRange(FilterCriteria criteria, Operator op, string val) { - if (val.Contains(':')) + List parts = new List(); + + if (Regex.IsMatch(val, @"^\d+(:\d+(:\d+)?)?$")) // formats like 12:34 { - if (val.Contains('h') || val.Contains('m') || val.Contains('s')) + string[] splited = val.Split(':'); + for (int i = splited.Length - 1; i >= 0; i--) + parts.Add(splited[i] + "smh"[splited.Length - i - 1]); + } + else if (Regex.IsMatch(val, @"^(\d+(\.\d+)?[hms]){1,3}$")) // formats like 1h2m3s + { + if (!"hms".Contains(Regex.Replace(val, @"[\d\.]", ""))) return false; - int count = val.Count(c => c == ':'); - if (count > 3) - return false; - - if (count > 2) - val = val.Replace(':', 'h'); - - val = val.Replace(':', 'm'); + string[] splited = Regex.Split(val, @"(?<=[hms])").Where(x => x.Length > 0).ToArray(); + for (int i = splited.Length - 1; i >= 0; i--) + parts.Add(splited[i]); + } + else if (Regex.IsMatch(val, @"^\d+(\.\d+)?$")) // only one number + { + parts.Add(val + 's'); } - string[] parts = Regex.Split(val, @"(?<=[msh])").Where(x => x.Length > 0).ToArray(); - double totalLength = 0; - int minScale = 1000; + if (parts.Count == 0) + return false; - foreach (string part in parts) + double totalLength = 0; + int minScale = 3600000; + + for (int i = 0; i < parts.Count; i++) { - if (!tryParseDoubleWithPoint(part.TrimEnd('m', 's', 'h'), out double length)) + string part = parts[i]; + string partNoUnit = part.TrimEnd('m', 's', 'h'); + if (!tryParseDoubleWithPoint(partNoUnit, out double length)) return false; + + if (i != parts.Count - 1 && length >= 60) + return false; + if (i != 0 && partNoUnit.Contains('.')) + return false; + int scale = getLengthScale(part); totalLength += length * scale; minScale = Math.Min(minScale, scale); } - return tryUpdateCriteriaRange(ref criteria.Length, op, totalLength, minScale / 2.0); } } From de25830b2ba1b0a696331d79da54f2ea7605a8e2 Mon Sep 17 00:00:00 2001 From: solstice23 Date: Fri, 22 Jul 2022 03:27:08 +0800 Subject: [PATCH 034/420] Add more test cases --- .../NonVisual/Filtering/FilterQueryParserTest.cs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs index 9f97c39462..8f17410cad 100644 --- a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs +++ b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs @@ -126,9 +126,16 @@ namespace osu.Game.Tests.NonVisual.Filtering new object[] { "0.25h", TimeSpan.FromHours(0.25), TimeSpan.FromHours(1) }, new object[] { "70", TimeSpan.FromSeconds(70), TimeSpan.FromSeconds(1) }, new object[] { "7m27s", TimeSpan.FromSeconds(447), TimeSpan.FromSeconds(1) }, - new object[] { "6h5m", TimeSpan.FromMinutes(365), TimeSpan.FromMinutes(1) }, new object[] { "7:27", TimeSpan.FromSeconds(447), TimeSpan.FromSeconds(1) }, - new object[] { "0:2:35", TimeSpan.FromSeconds(155), TimeSpan.FromSeconds(1) }, + new object[] { "1h2m3s", TimeSpan.FromSeconds(3723), TimeSpan.FromSeconds(1) }, + new object[] { "1h2m3.5s", TimeSpan.FromSeconds(3723.5), TimeSpan.FromSeconds(1) }, + new object[] { "1:2:3", TimeSpan.FromSeconds(3723), TimeSpan.FromSeconds(1) }, + new object[] { "1:02:03", TimeSpan.FromSeconds(3723), TimeSpan.FromSeconds(1) }, + new object[] { "6", TimeSpan.FromSeconds(6), TimeSpan.FromSeconds(1) }, + new object[] { "6.5", TimeSpan.FromSeconds(6.5), TimeSpan.FromSeconds(1) }, + new object[] { "6.5s", TimeSpan.FromSeconds(6.5), TimeSpan.FromSeconds(1) }, + new object[] { "6.5m", TimeSpan.FromMinutes(6.5), TimeSpan.FromMinutes(1) }, + new object[] { "6h5m", TimeSpan.FromMinutes(365), TimeSpan.FromMinutes(1) }, }; [Test] From b36e23c0da7c02e61d87862bbe30eb5f8b3ccc1e Mon Sep 17 00:00:00 2001 From: solstice23 Date: Fri, 22 Jul 2022 03:30:31 +0800 Subject: [PATCH 035/420] Simplify the regex expression --- osu.Game/Screens/Select/FilterQueryParser.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/FilterQueryParser.cs b/osu.Game/Screens/Select/FilterQueryParser.cs index 25dd342353..9245c13243 100644 --- a/osu.Game/Screens/Select/FilterQueryParser.cs +++ b/osu.Game/Screens/Select/FilterQueryParser.cs @@ -315,7 +315,7 @@ namespace osu.Game.Screens.Select { List parts = new List(); - if (Regex.IsMatch(val, @"^\d+(:\d+(:\d+)?)?$")) // formats like 12:34 + if (Regex.IsMatch(val, @"^\d+(:\d+){1,2}$")) // formats like 12:34 { string[] splited = val.Split(':'); for (int i = splited.Length - 1; i >= 0; i--) From 80e82763e35304022e9c42a35a4f4c18fba7ef2e Mon Sep 17 00:00:00 2001 From: solstice23 Date: Fri, 22 Jul 2022 12:09:47 +0800 Subject: [PATCH 036/420] Add negative test cases --- .../Filtering/FilterQueryParserTest.cs | 61 ++++++++++++------- 1 file changed, 40 insertions(+), 21 deletions(-) diff --git a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs index 8f17410cad..4b20fc42c9 100644 --- a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs +++ b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs @@ -120,35 +120,54 @@ namespace osu.Game.Tests.NonVisual.Filtering private static readonly object[] length_query_examples = { - new object[] { "6ms", TimeSpan.FromMilliseconds(6), TimeSpan.FromMilliseconds(1) }, - new object[] { "23s", TimeSpan.FromSeconds(23), TimeSpan.FromSeconds(1) }, - new object[] { "9m", TimeSpan.FromMinutes(9), TimeSpan.FromMinutes(1) }, - new object[] { "0.25h", TimeSpan.FromHours(0.25), TimeSpan.FromHours(1) }, - new object[] { "70", TimeSpan.FromSeconds(70), TimeSpan.FromSeconds(1) }, - new object[] { "7m27s", TimeSpan.FromSeconds(447), TimeSpan.FromSeconds(1) }, - new object[] { "7:27", TimeSpan.FromSeconds(447), TimeSpan.FromSeconds(1) }, - new object[] { "1h2m3s", TimeSpan.FromSeconds(3723), TimeSpan.FromSeconds(1) }, - new object[] { "1h2m3.5s", TimeSpan.FromSeconds(3723.5), TimeSpan.FromSeconds(1) }, - new object[] { "1:2:3", TimeSpan.FromSeconds(3723), TimeSpan.FromSeconds(1) }, - new object[] { "1:02:03", TimeSpan.FromSeconds(3723), TimeSpan.FromSeconds(1) }, - new object[] { "6", TimeSpan.FromSeconds(6), TimeSpan.FromSeconds(1) }, - new object[] { "6.5", TimeSpan.FromSeconds(6.5), TimeSpan.FromSeconds(1) }, - new object[] { "6.5s", TimeSpan.FromSeconds(6.5), TimeSpan.FromSeconds(1) }, - new object[] { "6.5m", TimeSpan.FromMinutes(6.5), TimeSpan.FromMinutes(1) }, - new object[] { "6h5m", TimeSpan.FromMinutes(365), TimeSpan.FromMinutes(1) }, + new object[] { "23s", TimeSpan.FromSeconds(23), TimeSpan.FromSeconds(1), true }, + new object[] { "9m", TimeSpan.FromMinutes(9), TimeSpan.FromMinutes(1), true }, + new object[] { "0.25h", TimeSpan.FromHours(0.25), TimeSpan.FromHours(1), true }, + new object[] { "70", TimeSpan.FromSeconds(70), TimeSpan.FromSeconds(1), true }, + new object[] { "7m27s", TimeSpan.FromSeconds(447), TimeSpan.FromSeconds(1), true }, + new object[] { "7:27", TimeSpan.FromSeconds(447), TimeSpan.FromSeconds(1), true }, + new object[] { "1h2m3s", TimeSpan.FromSeconds(3723), TimeSpan.FromSeconds(1), true }, + new object[] { "1h2m3.5s", TimeSpan.FromSeconds(3723.5), TimeSpan.FromSeconds(1), true }, + new object[] { "1:2:3", TimeSpan.FromSeconds(3723), TimeSpan.FromSeconds(1), true }, + new object[] { "1:02:03", TimeSpan.FromSeconds(3723), TimeSpan.FromSeconds(1), true }, + new object[] { "6", TimeSpan.FromSeconds(6), TimeSpan.FromSeconds(1), true }, + new object[] { "6.5", TimeSpan.FromSeconds(6.5), TimeSpan.FromSeconds(1), true }, + new object[] { "6.5s", TimeSpan.FromSeconds(6.5), TimeSpan.FromSeconds(1), true }, + new object[] { "6.5m", TimeSpan.FromMinutes(6.5), TimeSpan.FromMinutes(1), true }, + new object[] { "6h5m", TimeSpan.FromMinutes(365), TimeSpan.FromMinutes(1), true }, + new object[] { "65m", TimeSpan.FromMinutes(65), TimeSpan.FromMinutes(1), true }, + new object[] { "90s", TimeSpan.FromSeconds(90), TimeSpan.FromSeconds(1), true }, + new object[] { "80m20s", TimeSpan.FromSeconds(4820), TimeSpan.FromSeconds(1), true }, + new object[] { "7.5m27s", new TimeSpan(), new TimeSpan(), false }, + new object[] { "7m27", new TimeSpan(), new TimeSpan(), false }, + new object[] { "7m7m7m", new TimeSpan(), new TimeSpan(), false }, + new object[] { "7m70s", new TimeSpan(), new TimeSpan(), false }, + new object[] { "5s6m", new TimeSpan(), new TimeSpan(), false }, + new object[] { "0:", new TimeSpan(), new TimeSpan(), false }, + new object[] { ":0", new TimeSpan(), new TimeSpan(), false }, + new object[] { "0:3:", new TimeSpan(), new TimeSpan(), false }, + new object[] { "3:15.5", new TimeSpan(), new TimeSpan(), false }, }; [Test] [TestCaseSource(nameof(length_query_examples))] - public void TestApplyLengthQueries(string lengthQuery, TimeSpan expectedLength, TimeSpan scale) + public void TestApplyLengthQueries(string lengthQuery, TimeSpan expectedLength, TimeSpan scale, bool isValid) { string query = $"length={lengthQuery} time"; var filterCriteria = new FilterCriteria(); FilterQueryParser.ApplyQueries(filterCriteria, query); - Assert.AreEqual("time", filterCriteria.SearchText.Trim()); - Assert.AreEqual(1, filterCriteria.SearchTerms.Length); - Assert.AreEqual(expectedLength.TotalMilliseconds - scale.TotalMilliseconds / 2.0, filterCriteria.Length.Min); - Assert.AreEqual(expectedLength.TotalMilliseconds + scale.TotalMilliseconds / 2.0, filterCriteria.Length.Max); + if (isValid) + { + Assert.AreEqual("time", filterCriteria.SearchText.Trim()); + Assert.AreEqual(1, filterCriteria.SearchTerms.Length); + Assert.AreEqual(expectedLength.TotalMilliseconds - scale.TotalMilliseconds / 2.0, filterCriteria.Length.Min); + Assert.AreEqual(expectedLength.TotalMilliseconds + scale.TotalMilliseconds / 2.0, filterCriteria.Length.Max); + } + else + { + Assert.AreEqual(false, filterCriteria.Length.HasFilter); + } + } [Test] From 7c222505e941f0afd2f4925cacb3922c6d10938a Mon Sep 17 00:00:00 2001 From: solstice23 Date: Fri, 22 Jul 2022 14:24:17 +0800 Subject: [PATCH 037/420] Simplify length parsing --- .../Filtering/FilterQueryParserTest.cs | 1 - osu.Game/Screens/Select/FilterQueryParser.cs | 22 +++++++++---------- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs index 4b20fc42c9..f08bd09cf5 100644 --- a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs +++ b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs @@ -167,7 +167,6 @@ namespace osu.Game.Tests.NonVisual.Filtering { Assert.AreEqual(false, filterCriteria.Length.HasFilter); } - } [Test] diff --git a/osu.Game/Screens/Select/FilterQueryParser.cs b/osu.Game/Screens/Select/FilterQueryParser.cs index 9245c13243..b8c85c5ce0 100644 --- a/osu.Game/Screens/Select/FilterQueryParser.cs +++ b/osu.Game/Screens/Select/FilterQueryParser.cs @@ -317,18 +317,18 @@ namespace osu.Game.Screens.Select if (Regex.IsMatch(val, @"^\d+(:\d+){1,2}$")) // formats like 12:34 { - string[] splited = val.Split(':'); - for (int i = splited.Length - 1; i >= 0; i--) - parts.Add(splited[i] + "smh"[splited.Length - i - 1]); - } - else if (Regex.IsMatch(val, @"^(\d+(\.\d+)?[hms]){1,3}$")) // formats like 1h2m3s - { - if (!"hms".Contains(Regex.Replace(val, @"[\d\.]", ""))) - return false; + List splitted = val.Split(':').ToList(); + while (splitted.Count < 3) + splitted.Insert(0, "0"); - string[] splited = Regex.Split(val, @"(?<=[hms])").Where(x => x.Length > 0).ToArray(); - for (int i = splited.Length - 1; i >= 0; i--) - parts.Add(splited[i]); + parts.Add(splitted[2] + 's'); + parts.Add(splitted[1] + 'm'); + parts.Add(splitted[0] + 'h'); + } + else if (Regex.IsMatch(val, @"^(\d+(\.\d+)?[hms]){1,3}$") && "hms".Contains(Regex.Replace(val, @"[\d\.]", ""))) // formats like 1h2m3s + { + string[] splitted = Regex.Split(val, @"(?<=[hms])").Where(x => x.Length > 0).Reverse().ToArray(); + parts.AddRange(splitted); } else if (Regex.IsMatch(val, @"^\d+(\.\d+)?$")) // only one number { From 1220250bb677567498a10e4f9604ce8291bc8b3d Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 23 Jul 2022 07:26:34 +0300 Subject: [PATCH 038/420] Improve test scene and add failing test case --- osu.Game.Tests/Resources/TestResources.cs | 2 +- .../SongSelect/TestSceneTopLocalRank.cs | 79 ++++++++++++++++--- 2 files changed, 67 insertions(+), 14 deletions(-) diff --git a/osu.Game.Tests/Resources/TestResources.cs b/osu.Game.Tests/Resources/TestResources.cs index ee29cc8644..37bcef0414 100644 --- a/osu.Game.Tests/Resources/TestResources.cs +++ b/osu.Game.Tests/Resources/TestResources.cs @@ -190,7 +190,7 @@ namespace osu.Game.Tests.Resources [HitResult.SmallTickMiss] = 25, [HitResult.LargeTickHit] = 100, [HitResult.LargeTickMiss] = 50, - [HitResult.SmallBonus] = 10, + [HitResult.LargeBonus] = 10, [HitResult.SmallBonus] = 50 }, }; diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneTopLocalRank.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneTopLocalRank.cs index 05b5c5c0cd..d998c4e161 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneTopLocalRank.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneTopLocalRank.cs @@ -3,6 +3,8 @@ #nullable disable +using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; @@ -13,6 +15,7 @@ using osu.Framework.Platform; using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Rulesets; +using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; using osu.Game.Screens.Select.Carousel; using osu.Game.Tests.Resources; @@ -54,6 +57,8 @@ namespace osu.Game.Tests.Visual.SongSelect Scale = new Vector2(10), }); }); + + AddAssert("No rank displayed initially", () => topLocalRank.Rank == null); } [Test] @@ -61,8 +66,6 @@ namespace osu.Game.Tests.Visual.SongSelect { ScoreInfo testScoreInfo = null; - AddAssert("Initially not present", () => !topLocalRank.IsPresent); - AddStep("Add score for current user", () => { testScoreInfo = TestResources.CreateTestScoreInfo(importedBeatmap); @@ -73,15 +76,14 @@ namespace osu.Game.Tests.Visual.SongSelect scoreManager.Import(testScoreInfo); }); - AddUntilStep("Became present", () => topLocalRank.IsPresent); - AddAssert("Correct rank", () => topLocalRank.Rank == ScoreRank.B); + AddUntilStep("B rank displayed", () => topLocalRank.Rank == ScoreRank.B); AddStep("Delete score", () => { scoreManager.Delete(testScoreInfo); }); - AddUntilStep("Became not present", () => !topLocalRank.IsPresent); + AddUntilStep("No rank displayed", () => topLocalRank.Rank == null); } [Test] @@ -99,13 +101,13 @@ namespace osu.Game.Tests.Visual.SongSelect scoreManager.Import(testScoreInfo); }); - AddUntilStep("Wait for initial presence", () => topLocalRank.IsPresent); + AddUntilStep("Wait for initial display", () => topLocalRank.Rank == ScoreRank.B); AddStep("Change ruleset", () => Ruleset.Value = rulesets.GetRuleset("fruits")); - AddUntilStep("Became not present", () => !topLocalRank.IsPresent); + AddUntilStep("No rank displayed", () => topLocalRank.Rank == null); AddStep("Change ruleset back", () => Ruleset.Value = rulesets.GetRuleset("osu")); - AddUntilStep("Became present", () => topLocalRank.IsPresent); + AddUntilStep("B rank displayed", () => topLocalRank.Rank == ScoreRank.B); } [Test] @@ -113,8 +115,6 @@ namespace osu.Game.Tests.Visual.SongSelect { ScoreInfo testScoreInfo = null; - AddAssert("Initially not present", () => !topLocalRank.IsPresent); - AddStep("Add score for current user", () => { testScoreInfo = TestResources.CreateTestScoreInfo(importedBeatmap); @@ -125,8 +125,7 @@ namespace osu.Game.Tests.Visual.SongSelect scoreManager.Import(testScoreInfo); }); - AddUntilStep("Became present", () => topLocalRank.IsPresent); - AddUntilStep("Correct rank", () => topLocalRank.Rank == ScoreRank.B); + AddUntilStep("B rank displayed", () => topLocalRank.Rank == ScoreRank.B); AddStep("Add higher score for current user", () => { @@ -135,11 +134,65 @@ namespace osu.Game.Tests.Visual.SongSelect testScoreInfo2.User = API.LocalUser.Value; testScoreInfo2.Rank = ScoreRank.S; testScoreInfo2.TotalScore = testScoreInfo.TotalScore + 1; + testScoreInfo2.Statistics = new Dictionary + { + [HitResult.Miss] = 0, + [HitResult.Perfect] = 970, + [HitResult.SmallTickHit] = 75, + [HitResult.LargeTickHit] = 150, + [HitResult.LargeBonus] = 10, + [HitResult.SmallBonus] = 50 + }; scoreManager.Import(testScoreInfo2); }); - AddUntilStep("Correct rank", () => topLocalRank.Rank == ScoreRank.S); + AddUntilStep("S rank displayed", () => topLocalRank.Rank == ScoreRank.S); + } + + [Test] + public void TestLegacyScore() + { + ScoreInfo testScoreInfo = null; + + AddStep("Add legacy score for current user", () => + { + testScoreInfo = TestResources.CreateTestScoreInfo(importedBeatmap); + + testScoreInfo.User = API.LocalUser.Value; + testScoreInfo.Rank = ScoreRank.B; + testScoreInfo.TotalScore = scoreManager.GetTotalScoreAsync(testScoreInfo, ScoringMode.Classic).GetResultSafely(); + + scoreManager.Import(testScoreInfo); + }); + + AddUntilStep("B rank displayed", () => topLocalRank.Rank == ScoreRank.B); + + AddStep("Add higher score for current user", () => + { + var testScoreInfo2 = TestResources.CreateTestScoreInfo(importedBeatmap); + + testScoreInfo2.User = API.LocalUser.Value; + testScoreInfo2.Rank = ScoreRank.S; + testScoreInfo2.Statistics = new Dictionary + { + [HitResult.Miss] = 0, + [HitResult.Perfect] = 970, + [HitResult.SmallTickHit] = 75, + [HitResult.LargeTickHit] = 150, + [HitResult.LargeBonus] = 10, + [HitResult.SmallBonus] = 50 + }; + + testScoreInfo2.TotalScore = scoreManager.GetTotalScoreAsync(testScoreInfo).GetResultSafely(); + + // ensure standardised total score is less than classic, otherwise this test is pointless. + Debug.Assert(testScoreInfo2.TotalScore < testScoreInfo.TotalScore); + + scoreManager.Import(testScoreInfo2); + }); + + AddUntilStep("S rank displayed", () => topLocalRank.Rank == ScoreRank.S); } } } From 727fe76b6090e2071507e1e1622e1f79fd2d22ca Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 23 Jul 2022 08:09:00 +0300 Subject: [PATCH 039/420] Fix `TopLocalRank` hacking around presence to hide on null rank Fixed this here because that blocks `Schedule` from running, and I don't want to add another override to the `IsPresent` flag. --- .../SongSelect/TestSceneTopLocalRank.cs | 20 ++++++------- .../Online/Leaderboards/UpdateableRank.cs | 2 +- .../Screens/Select/Carousel/TopLocalRank.cs | 29 ++++++++++++++----- 3 files changed, 32 insertions(+), 19 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneTopLocalRank.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneTopLocalRank.cs index d998c4e161..90d7aaacc3 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneTopLocalRank.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneTopLocalRank.cs @@ -58,7 +58,7 @@ namespace osu.Game.Tests.Visual.SongSelect }); }); - AddAssert("No rank displayed initially", () => topLocalRank.Rank == null); + AddAssert("No rank displayed initially", () => topLocalRank.DisplayedRank == null); } [Test] @@ -76,14 +76,14 @@ namespace osu.Game.Tests.Visual.SongSelect scoreManager.Import(testScoreInfo); }); - AddUntilStep("B rank displayed", () => topLocalRank.Rank == ScoreRank.B); + AddUntilStep("B rank displayed", () => topLocalRank.DisplayedRank == ScoreRank.B); AddStep("Delete score", () => { scoreManager.Delete(testScoreInfo); }); - AddUntilStep("No rank displayed", () => topLocalRank.Rank == null); + AddUntilStep("No rank displayed", () => topLocalRank.DisplayedRank == null); } [Test] @@ -101,13 +101,13 @@ namespace osu.Game.Tests.Visual.SongSelect scoreManager.Import(testScoreInfo); }); - AddUntilStep("Wait for initial display", () => topLocalRank.Rank == ScoreRank.B); + AddUntilStep("Wait for initial display", () => topLocalRank.DisplayedRank == ScoreRank.B); AddStep("Change ruleset", () => Ruleset.Value = rulesets.GetRuleset("fruits")); - AddUntilStep("No rank displayed", () => topLocalRank.Rank == null); + AddUntilStep("No rank displayed", () => topLocalRank.DisplayedRank == null); AddStep("Change ruleset back", () => Ruleset.Value = rulesets.GetRuleset("osu")); - AddUntilStep("B rank displayed", () => topLocalRank.Rank == ScoreRank.B); + AddUntilStep("B rank displayed", () => topLocalRank.DisplayedRank == ScoreRank.B); } [Test] @@ -125,7 +125,7 @@ namespace osu.Game.Tests.Visual.SongSelect scoreManager.Import(testScoreInfo); }); - AddUntilStep("B rank displayed", () => topLocalRank.Rank == ScoreRank.B); + AddUntilStep("B rank displayed", () => topLocalRank.DisplayedRank == ScoreRank.B); AddStep("Add higher score for current user", () => { @@ -147,7 +147,7 @@ namespace osu.Game.Tests.Visual.SongSelect scoreManager.Import(testScoreInfo2); }); - AddUntilStep("S rank displayed", () => topLocalRank.Rank == ScoreRank.S); + AddUntilStep("S rank displayed", () => topLocalRank.DisplayedRank == ScoreRank.S); } [Test] @@ -166,7 +166,7 @@ namespace osu.Game.Tests.Visual.SongSelect scoreManager.Import(testScoreInfo); }); - AddUntilStep("B rank displayed", () => topLocalRank.Rank == ScoreRank.B); + AddUntilStep("B rank displayed", () => topLocalRank.DisplayedRank == ScoreRank.B); AddStep("Add higher score for current user", () => { @@ -192,7 +192,7 @@ namespace osu.Game.Tests.Visual.SongSelect scoreManager.Import(testScoreInfo2); }); - AddUntilStep("S rank displayed", () => topLocalRank.Rank == ScoreRank.S); + AddUntilStep("S rank displayed", () => topLocalRank.DisplayedRank == ScoreRank.S); } } } diff --git a/osu.Game/Online/Leaderboards/UpdateableRank.cs b/osu.Game/Online/Leaderboards/UpdateableRank.cs index e4f5f72886..e640fe8494 100644 --- a/osu.Game/Online/Leaderboards/UpdateableRank.cs +++ b/osu.Game/Online/Leaderboards/UpdateableRank.cs @@ -17,7 +17,7 @@ namespace osu.Game.Online.Leaderboards set => Model = value; } - public UpdateableRank(ScoreRank? rank) + public UpdateableRank(ScoreRank? rank = null) { Rank = rank; } diff --git a/osu.Game/Screens/Select/Carousel/TopLocalRank.cs b/osu.Game/Screens/Select/Carousel/TopLocalRank.cs index cc01f61c57..033da61461 100644 --- a/osu.Game/Screens/Select/Carousel/TopLocalRank.cs +++ b/osu.Game/Screens/Select/Carousel/TopLocalRank.cs @@ -5,9 +5,12 @@ using System; using System.Linq; +using System.Threading; +using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps; using osu.Game.Database; using osu.Game.Models; @@ -20,7 +23,7 @@ using Realms; namespace osu.Game.Screens.Select.Carousel { - public class TopLocalRank : UpdateableRank + public class TopLocalRank : CompositeDrawable { private readonly BeatmapInfo beatmapInfo; @@ -34,13 +37,25 @@ namespace osu.Game.Screens.Select.Carousel private IAPIProvider api { get; set; } private IDisposable scoreSubscription; + private CancellationTokenSource scoreOrderCancellationSource; + + private readonly UpdateableRank updateable; + + public ScoreRank? DisplayedRank => updateable.Rank; public TopLocalRank(BeatmapInfo beatmapInfo) - : base(null) { this.beatmapInfo = beatmapInfo; - Size = new Vector2(40, 20); + AutoSizeAxes = Axes.Both; + + InternalChild = updateable = new UpdateableRank + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(40, 20), + Alpha = 0, + }; } protected override void LoadComplete() @@ -59,19 +74,17 @@ namespace osu.Game.Screens.Select.Carousel .OrderByDescending(s => s.TotalScore), (items, _, _) => { - Rank = items.FirstOrDefault()?.Rank; - // Required since presence is changed via IsPresent override - Invalidate(Invalidation.Presence); + updateable.Rank = items.FirstOrDefault()?.Rank; + updateable.Alpha = updateable.Rank != null ? 1 : 0; }); }, true); } - public override bool IsPresent => base.IsPresent && Rank != null; - protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); + scoreOrderCancellationSource?.Cancel(); scoreSubscription?.Dispose(); } } From 1d0306810ab63e471a459c5488bef69cfe96610c Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 23 Jul 2022 08:13:05 +0300 Subject: [PATCH 040/420] Fix `TopLocalRank` not handling legacy scores properly --- .../Screens/Select/Carousel/TopLocalRank.cs | 33 +++++++++++++++---- 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/Select/Carousel/TopLocalRank.cs b/osu.Game/Screens/Select/Carousel/TopLocalRank.cs index 033da61461..d372cb8dbc 100644 --- a/osu.Game/Screens/Select/Carousel/TopLocalRank.cs +++ b/osu.Game/Screens/Select/Carousel/TopLocalRank.cs @@ -9,6 +9,7 @@ using System.Threading; using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps; @@ -33,6 +34,9 @@ namespace osu.Game.Screens.Select.Carousel [Resolved] private RealmAccess realm { get; set; } + [Resolved] + private ScoreManager scoreManager { get; set; } + [Resolved] private IAPIProvider api { get; set; } @@ -70,14 +74,29 @@ namespace osu.Game.Screens.Select.Carousel .Filter($"{nameof(ScoreInfo.User)}.{nameof(RealmUser.OnlineID)} == $0" + $" && {nameof(ScoreInfo.BeatmapInfo)}.{nameof(BeatmapInfo.ID)} == $1" + $" && {nameof(ScoreInfo.Ruleset)}.{nameof(RulesetInfo.ShortName)} == $2" - + $" && {nameof(ScoreInfo.DeletePending)} == false", api.LocalUser.Value.Id, beatmapInfo.ID, ruleset.Value.ShortName) - .OrderByDescending(s => s.TotalScore), - (items, _, _) => - { - updateable.Rank = items.FirstOrDefault()?.Rank; - updateable.Alpha = updateable.Rank != null ? 1 : 0; - }); + + $" && {nameof(ScoreInfo.DeletePending)} == false", api.LocalUser.Value.Id, beatmapInfo.ID, ruleset.Value.ShortName), + localScoresChanged); }, true); + + void localScoresChanged(IRealmCollection sender, ChangeSet changes, Exception error) + { + // This subscription may fire from changes to linked beatmaps, which we don't care about. + // It's currently not possible for a score to be modified after insertion, so we can safely ignore callbacks with only modifications. + if (changes?.HasCollectionChanges() == false) + return; + + scoreOrderCancellationSource?.Cancel(); + + scoreManager.OrderByTotalScoreAsync(sender.Detach().ToArray(), (scoreOrderCancellationSource = new CancellationTokenSource()).Token) + .ContinueWith(ordered => Schedule(() => + { + if (scoreOrderCancellationSource.IsCancellationRequested) + return; + + updateable.Rank = ordered.GetResultSafely().FirstOrDefault()?.Rank; + updateable.Alpha = updateable.Rank != null ? 1 : 0; + }), TaskContinuationOptions.OnlyOnRanToCompletion); + } } protected override void Dispose(bool isDisposing) From 3aecd288e22d985bb42b934bfc749fbe2f73b97d Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 23 Jul 2022 09:25:06 +0300 Subject: [PATCH 041/420] Enable NRT on `TopLocalRank` --- .../SongSelect/TestSceneTopLocalRank.cs | 25 +++++++++---------- .../Screens/Select/Carousel/TopLocalRank.cs | 16 ++++++------ 2 files changed, 19 insertions(+), 22 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneTopLocalRank.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneTopLocalRank.cs index 90d7aaacc3..b122898346 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneTopLocalRank.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneTopLocalRank.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. -#nullable disable - using System.Collections.Generic; using System.Diagnostics; using System.Linq; @@ -10,6 +8,7 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Extensions; +using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Platform; using osu.Framework.Testing; @@ -25,10 +24,10 @@ namespace osu.Game.Tests.Visual.SongSelect { public class TestSceneTopLocalRank : OsuTestScene { - private RulesetStore rulesets; - private BeatmapManager beatmapManager; - private ScoreManager scoreManager; - private TopLocalRank topLocalRank; + private RulesetStore rulesets = null!; + private BeatmapManager beatmapManager = null!; + private ScoreManager scoreManager = null!; + private TopLocalRank topLocalRank = null!; [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) @@ -64,7 +63,7 @@ namespace osu.Game.Tests.Visual.SongSelect [Test] public void TestBasicImportDelete() { - ScoreInfo testScoreInfo = null; + ScoreInfo? testScoreInfo = null; AddStep("Add score for current user", () => { @@ -80,7 +79,7 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("Delete score", () => { - scoreManager.Delete(testScoreInfo); + scoreManager.Delete(testScoreInfo.AsNonNull()); }); AddUntilStep("No rank displayed", () => topLocalRank.DisplayedRank == null); @@ -113,7 +112,7 @@ namespace osu.Game.Tests.Visual.SongSelect [Test] public void TestHigherScoreSet() { - ScoreInfo testScoreInfo = null; + ScoreInfo? testScoreInfo = null; AddStep("Add score for current user", () => { @@ -133,7 +132,7 @@ namespace osu.Game.Tests.Visual.SongSelect testScoreInfo2.User = API.LocalUser.Value; testScoreInfo2.Rank = ScoreRank.S; - testScoreInfo2.TotalScore = testScoreInfo.TotalScore + 1; + testScoreInfo2.TotalScore = testScoreInfo.AsNonNull().TotalScore + 1; testScoreInfo2.Statistics = new Dictionary { [HitResult.Miss] = 0, @@ -153,7 +152,7 @@ namespace osu.Game.Tests.Visual.SongSelect [Test] public void TestLegacyScore() { - ScoreInfo testScoreInfo = null; + ScoreInfo? testScoreInfo = null; AddStep("Add legacy score for current user", () => { @@ -184,10 +183,10 @@ namespace osu.Game.Tests.Visual.SongSelect [HitResult.SmallBonus] = 50 }; - testScoreInfo2.TotalScore = scoreManager.GetTotalScoreAsync(testScoreInfo).GetResultSafely(); + testScoreInfo2.TotalScore = scoreManager.GetTotalScoreAsync(testScoreInfo.AsNonNull()).GetResultSafely(); // ensure standardised total score is less than classic, otherwise this test is pointless. - Debug.Assert(testScoreInfo2.TotalScore < testScoreInfo.TotalScore); + Debug.Assert(testScoreInfo2.TotalScore < testScoreInfo.AsNonNull().TotalScore); scoreManager.Import(testScoreInfo2); }); diff --git a/osu.Game/Screens/Select/Carousel/TopLocalRank.cs b/osu.Game/Screens/Select/Carousel/TopLocalRank.cs index d372cb8dbc..2a9dae6712 100644 --- a/osu.Game/Screens/Select/Carousel/TopLocalRank.cs +++ b/osu.Game/Screens/Select/Carousel/TopLocalRank.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. -#nullable disable - using System; using System.Linq; using System.Threading; @@ -29,19 +27,19 @@ namespace osu.Game.Screens.Select.Carousel private readonly BeatmapInfo beatmapInfo; [Resolved] - private IBindable ruleset { get; set; } + private IBindable ruleset { get; set; } = null!; [Resolved] - private RealmAccess realm { get; set; } + private RealmAccess realm { get; set; } = null!; [Resolved] - private ScoreManager scoreManager { get; set; } + private ScoreManager scoreManager { get; set; } = null!; [Resolved] - private IAPIProvider api { get; set; } + private IAPIProvider api { get; set; } = null!; - private IDisposable scoreSubscription; - private CancellationTokenSource scoreOrderCancellationSource; + private IDisposable? scoreSubscription; + private CancellationTokenSource? scoreOrderCancellationSource; private readonly UpdateableRank updateable; @@ -78,7 +76,7 @@ namespace osu.Game.Screens.Select.Carousel localScoresChanged); }, true); - void localScoresChanged(IRealmCollection sender, ChangeSet changes, Exception error) + void localScoresChanged(IRealmCollection sender, ChangeSet? changes, Exception _) { // This subscription may fire from changes to linked beatmaps, which we don't care about. // It's currently not possible for a score to be modified after insertion, so we can safely ignore callbacks with only modifications. From 3f2c3413691d1745b7688b01b0fbe159cc633ec2 Mon Sep 17 00:00:00 2001 From: solstice23 Date: Sat, 23 Jul 2022 18:13:19 +0800 Subject: [PATCH 042/420] Simplify length parsing --- .../Filtering/FilterQueryParserTest.cs | 1 + osu.Game/Screens/Select/FilterQueryParser.cs | 42 ++++++++++--------- 2 files changed, 24 insertions(+), 19 deletions(-) diff --git a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs index f08bd09cf5..181b740729 100644 --- a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs +++ b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs @@ -138,6 +138,7 @@ namespace osu.Game.Tests.NonVisual.Filtering new object[] { "65m", TimeSpan.FromMinutes(65), TimeSpan.FromMinutes(1), true }, new object[] { "90s", TimeSpan.FromSeconds(90), TimeSpan.FromSeconds(1), true }, new object[] { "80m20s", TimeSpan.FromSeconds(4820), TimeSpan.FromSeconds(1), true }, + new object[] { "1h20s", TimeSpan.FromSeconds(3620), TimeSpan.FromSeconds(1), true }, new object[] { "7.5m27s", new TimeSpan(), new TimeSpan(), false }, new object[] { "7m27", new TimeSpan(), new TimeSpan(), false }, new object[] { "7m7m7m", new TimeSpan(), new TimeSpan(), false }, diff --git a/osu.Game/Screens/Select/FilterQueryParser.cs b/osu.Game/Screens/Select/FilterQueryParser.cs index b8c85c5ce0..19fb8665d2 100644 --- a/osu.Game/Screens/Select/FilterQueryParser.cs +++ b/osu.Game/Screens/Select/FilterQueryParser.cs @@ -129,6 +129,16 @@ namespace osu.Game.Screens.Select value = Enum.GetNames(typeof(TEnum)).FirstOrDefault(name => name.StartsWith(value, true, CultureInfo.InvariantCulture)); return Enum.TryParse(value, true, out result); } + private static bool tryMatchRegex(string value, string regex, ref GroupCollection result) + { + Match matchs = Regex.Match(value, regex); + if (matchs.Success) + { + result = matchs.Groups; + return true; + } + return false; + } /// /// Attempts to parse a keyword filter with the specified and textual . @@ -314,28 +324,22 @@ namespace osu.Game.Screens.Select private static bool tryUpdateLengthRange(FilterCriteria criteria, Operator op, string val) { List parts = new List(); + GroupCollection groups = null; - if (Regex.IsMatch(val, @"^\d+(:\d+){1,2}$")) // formats like 12:34 + if ( + tryMatchRegex(val, @"^((?\d+):)?(?\d+):(?\d+)$", ref groups) || + tryMatchRegex(val, @"^((?\d+(\.\d+)?)h)?((?\d+(\.\d+)?)m)?((?\d+(\.\d+)?)s)?$", ref groups) || + tryMatchRegex(val, @"^(?\d+(\.\d+)?)$", ref groups) + ) { - List splitted = val.Split(':').ToList(); - while (splitted.Count < 3) - splitted.Insert(0, "0"); - - parts.Add(splitted[2] + 's'); - parts.Add(splitted[1] + 'm'); - parts.Add(splitted[0] + 'h'); + if (groups["seconds"].Success) + parts.Add(groups["seconds"].Value + "s"); + if (groups["minutes"].Success) + parts.Add(groups["minutes"].Value + "m"); + if (groups["hours"].Success) + parts.Add(groups["hours"].Value + "h"); } - else if (Regex.IsMatch(val, @"^(\d+(\.\d+)?[hms]){1,3}$") && "hms".Contains(Regex.Replace(val, @"[\d\.]", ""))) // formats like 1h2m3s - { - string[] splitted = Regex.Split(val, @"(?<=[hms])").Where(x => x.Length > 0).Reverse().ToArray(); - parts.AddRange(splitted); - } - else if (Regex.IsMatch(val, @"^\d+(\.\d+)?$")) // only one number - { - parts.Add(val + 's'); - } - - if (parts.Count == 0) + else return false; double totalLength = 0; From aaad2e474ce271166de0aa9655352cc70cd4db07 Mon Sep 17 00:00:00 2001 From: solstice23 Date: Sat, 23 Jul 2022 21:43:27 +0800 Subject: [PATCH 043/420] Refactor the multiple regex checks in criteria parsing --- osu.Game/Screens/Select/FilterQueryParser.cs | 35 +++++++++----------- 1 file changed, 16 insertions(+), 19 deletions(-) diff --git a/osu.Game/Screens/Select/FilterQueryParser.cs b/osu.Game/Screens/Select/FilterQueryParser.cs index 19fb8665d2..75574c061d 100644 --- a/osu.Game/Screens/Select/FilterQueryParser.cs +++ b/osu.Game/Screens/Select/FilterQueryParser.cs @@ -129,15 +129,14 @@ namespace osu.Game.Screens.Select value = Enum.GetNames(typeof(TEnum)).FirstOrDefault(name => name.StartsWith(value, true, CultureInfo.InvariantCulture)); return Enum.TryParse(value, true, out result); } - private static bool tryMatchRegex(string value, string regex, ref GroupCollection result) + private static GroupCollection tryMatchRegex(string value, string regex) { Match matchs = Regex.Match(value, regex); if (matchs.Success) { - result = matchs.Groups; - return true; + return matchs.Groups; } - return false; + return null; } /// @@ -324,24 +323,22 @@ namespace osu.Game.Screens.Select private static bool tryUpdateLengthRange(FilterCriteria criteria, Operator op, string val) { List parts = new List(); - GroupCollection groups = null; - if ( - tryMatchRegex(val, @"^((?\d+):)?(?\d+):(?\d+)$", ref groups) || - tryMatchRegex(val, @"^((?\d+(\.\d+)?)h)?((?\d+(\.\d+)?)m)?((?\d+(\.\d+)?)s)?$", ref groups) || - tryMatchRegex(val, @"^(?\d+(\.\d+)?)$", ref groups) - ) - { - if (groups["seconds"].Success) - parts.Add(groups["seconds"].Value + "s"); - if (groups["minutes"].Success) - parts.Add(groups["minutes"].Value + "m"); - if (groups["hours"].Success) - parts.Add(groups["hours"].Value + "h"); - } - else + GroupCollection match = null; + match ??= tryMatchRegex(val, @"^((?\d+):)?(?\d+):(?\d+)$"); + match ??= tryMatchRegex(val, @"^((?\d+(\.\d+)?)h)?((?\d+(\.\d+)?)m)?((?\d+(\.\d+)?)s)?$"); + match ??= tryMatchRegex(val, @"^(?\d+(\.\d+)?)$"); + + if (match == null) return false; + if (match["seconds"].Success) + parts.Add(match["seconds"].Value + "s"); + if (match["minutes"].Success) + parts.Add(match["minutes"].Value + "m"); + if (match["hours"].Success) + parts.Add(match["hours"].Value + "h"); + double totalLength = 0; int minScale = 3600000; From ea97122425706078cd91944eb099dff9f4d8311d Mon Sep 17 00:00:00 2001 From: solstice23 Date: Sat, 23 Jul 2022 21:47:51 +0800 Subject: [PATCH 044/420] Separate positive and negative test cases --- .../Filtering/FilterQueryParserTest.cs | 91 ++++++++++--------- 1 file changed, 49 insertions(+), 42 deletions(-) diff --git a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs index 181b740729..ac16c59e5b 100644 --- a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs +++ b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs @@ -118,56 +118,63 @@ namespace osu.Game.Tests.NonVisual.Filtering Assert.IsNull(filterCriteria.BPM.Max); } - private static readonly object[] length_query_examples = + private static readonly object[] correct_length_query_examples = { - new object[] { "23s", TimeSpan.FromSeconds(23), TimeSpan.FromSeconds(1), true }, - new object[] { "9m", TimeSpan.FromMinutes(9), TimeSpan.FromMinutes(1), true }, - new object[] { "0.25h", TimeSpan.FromHours(0.25), TimeSpan.FromHours(1), true }, - new object[] { "70", TimeSpan.FromSeconds(70), TimeSpan.FromSeconds(1), true }, - new object[] { "7m27s", TimeSpan.FromSeconds(447), TimeSpan.FromSeconds(1), true }, - new object[] { "7:27", TimeSpan.FromSeconds(447), TimeSpan.FromSeconds(1), true }, - new object[] { "1h2m3s", TimeSpan.FromSeconds(3723), TimeSpan.FromSeconds(1), true }, - new object[] { "1h2m3.5s", TimeSpan.FromSeconds(3723.5), TimeSpan.FromSeconds(1), true }, - new object[] { "1:2:3", TimeSpan.FromSeconds(3723), TimeSpan.FromSeconds(1), true }, - new object[] { "1:02:03", TimeSpan.FromSeconds(3723), TimeSpan.FromSeconds(1), true }, - new object[] { "6", TimeSpan.FromSeconds(6), TimeSpan.FromSeconds(1), true }, - new object[] { "6.5", TimeSpan.FromSeconds(6.5), TimeSpan.FromSeconds(1), true }, - new object[] { "6.5s", TimeSpan.FromSeconds(6.5), TimeSpan.FromSeconds(1), true }, - new object[] { "6.5m", TimeSpan.FromMinutes(6.5), TimeSpan.FromMinutes(1), true }, - new object[] { "6h5m", TimeSpan.FromMinutes(365), TimeSpan.FromMinutes(1), true }, - new object[] { "65m", TimeSpan.FromMinutes(65), TimeSpan.FromMinutes(1), true }, - new object[] { "90s", TimeSpan.FromSeconds(90), TimeSpan.FromSeconds(1), true }, - new object[] { "80m20s", TimeSpan.FromSeconds(4820), TimeSpan.FromSeconds(1), true }, - new object[] { "1h20s", TimeSpan.FromSeconds(3620), TimeSpan.FromSeconds(1), true }, - new object[] { "7.5m27s", new TimeSpan(), new TimeSpan(), false }, - new object[] { "7m27", new TimeSpan(), new TimeSpan(), false }, - new object[] { "7m7m7m", new TimeSpan(), new TimeSpan(), false }, - new object[] { "7m70s", new TimeSpan(), new TimeSpan(), false }, - new object[] { "5s6m", new TimeSpan(), new TimeSpan(), false }, - new object[] { "0:", new TimeSpan(), new TimeSpan(), false }, - new object[] { ":0", new TimeSpan(), new TimeSpan(), false }, - new object[] { "0:3:", new TimeSpan(), new TimeSpan(), false }, - new object[] { "3:15.5", new TimeSpan(), new TimeSpan(), false }, + new object[] { "23s", TimeSpan.FromSeconds(23), TimeSpan.FromSeconds(1) }, + new object[] { "9m", TimeSpan.FromMinutes(9), TimeSpan.FromMinutes(1) }, + new object[] { "0.25h", TimeSpan.FromHours(0.25), TimeSpan.FromHours(1) }, + new object[] { "70", TimeSpan.FromSeconds(70), TimeSpan.FromSeconds(1) }, + new object[] { "7m27s", TimeSpan.FromSeconds(447), TimeSpan.FromSeconds(1) }, + new object[] { "7:27", TimeSpan.FromSeconds(447), TimeSpan.FromSeconds(1) }, + new object[] { "1h2m3s", TimeSpan.FromSeconds(3723), TimeSpan.FromSeconds(1) }, + new object[] { "1h2m3.5s", TimeSpan.FromSeconds(3723.5), TimeSpan.FromSeconds(1) }, + new object[] { "1:2:3", TimeSpan.FromSeconds(3723), TimeSpan.FromSeconds(1) }, + new object[] { "1:02:03", TimeSpan.FromSeconds(3723), TimeSpan.FromSeconds(1) }, + new object[] { "6", TimeSpan.FromSeconds(6), TimeSpan.FromSeconds(1) }, + new object[] { "6.5", TimeSpan.FromSeconds(6.5), TimeSpan.FromSeconds(1) }, + new object[] { "6.5s", TimeSpan.FromSeconds(6.5), TimeSpan.FromSeconds(1) }, + new object[] { "6.5m", TimeSpan.FromMinutes(6.5), TimeSpan.FromMinutes(1) }, + new object[] { "6h5m", TimeSpan.FromMinutes(365), TimeSpan.FromMinutes(1) }, + new object[] { "65m", TimeSpan.FromMinutes(65), TimeSpan.FromMinutes(1) }, + new object[] { "90s", TimeSpan.FromSeconds(90), TimeSpan.FromSeconds(1) }, + new object[] { "80m20s", TimeSpan.FromSeconds(4820), TimeSpan.FromSeconds(1) }, + new object[] { "1h20s", TimeSpan.FromSeconds(3620), TimeSpan.FromSeconds(1) }, }; [Test] - [TestCaseSource(nameof(length_query_examples))] - public void TestApplyLengthQueries(string lengthQuery, TimeSpan expectedLength, TimeSpan scale, bool isValid) + [TestCaseSource(nameof(correct_length_query_examples))] + public void TestApplyLengthQueries(string lengthQuery, TimeSpan expectedLength, TimeSpan scale) { string query = $"length={lengthQuery} time"; var filterCriteria = new FilterCriteria(); FilterQueryParser.ApplyQueries(filterCriteria, query); - if (isValid) - { - Assert.AreEqual("time", filterCriteria.SearchText.Trim()); - Assert.AreEqual(1, filterCriteria.SearchTerms.Length); - Assert.AreEqual(expectedLength.TotalMilliseconds - scale.TotalMilliseconds / 2.0, filterCriteria.Length.Min); - Assert.AreEqual(expectedLength.TotalMilliseconds + scale.TotalMilliseconds / 2.0, filterCriteria.Length.Max); - } - else - { - Assert.AreEqual(false, filterCriteria.Length.HasFilter); - } + Assert.AreEqual("time", filterCriteria.SearchText.Trim()); + Assert.AreEqual(1, filterCriteria.SearchTerms.Length); + Assert.AreEqual(expectedLength.TotalMilliseconds - scale.TotalMilliseconds / 2.0, filterCriteria.Length.Min); + Assert.AreEqual(expectedLength.TotalMilliseconds + scale.TotalMilliseconds / 2.0, filterCriteria.Length.Max); + } + + private static readonly object[] incorrect_length_query_examples = + { + new object[] { "7.5m27s" }, + new object[] { "7m27" }, + new object[] { "7m7m7m" }, + new object[] { "7m70s" }, + new object[] { "5s6m" }, + new object[] { "0:" }, + new object[] { ":0" }, + new object[] { "0:3:" }, + new object[] { "3:15.5" }, + }; + + [Test] + [TestCaseSource(nameof(incorrect_length_query_examples))] + public void TestInvalidLengthQueries(string lengthQuery) + { + string query = $"length={lengthQuery} time"; + var filterCriteria = new FilterCriteria(); + FilterQueryParser.ApplyQueries(filterCriteria, query); + Assert.AreEqual(false, filterCriteria.Length.HasFilter); } [Test] From 0a0f3c93ddf5574feb583b036808945f2a7be073 Mon Sep 17 00:00:00 2001 From: sw1tchbl4d3 Date: Sun, 24 Jul 2022 20:55:13 +0200 Subject: [PATCH 045/420] Rename OkResult, rephrase "strong bonus" --- osu.Game.Rulesets.Taiko/Judgements/TaikoSwellJudgement.cs | 2 +- osu.Game.Rulesets.Taiko/Mods/TaikoModClassic.cs | 2 +- osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs | 2 +- osu.Game.Rulesets.Taiko/TaikoRuleset.cs | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Judgements/TaikoSwellJudgement.cs b/osu.Game.Rulesets.Taiko/Judgements/TaikoSwellJudgement.cs index 90ba7e276c..6d469bd1d7 100644 --- a/osu.Game.Rulesets.Taiko/Judgements/TaikoSwellJudgement.cs +++ b/osu.Game.Rulesets.Taiko/Judgements/TaikoSwellJudgement.cs @@ -12,7 +12,7 @@ namespace osu.Game.Rulesets.Taiko.Judgements /// /// The to grant when the player has hit more than half of swell ticks. /// - public virtual HitResult OkResult => HitResult.Ok; + public virtual HitResult PartialCompletionResult => HitResult.Ok; protected override double HealthIncreaseFor(HitResult result) { diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModClassic.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModClassic.cs index ec4ad18080..efb67825db 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModClassic.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModClassic.cs @@ -141,7 +141,7 @@ namespace osu.Game.Rulesets.Taiko.Mods { public override HitResult MaxResult => HitResult.LargeBonus; - public override HitResult OkResult => HitResult.SmallBonus; + public override HitResult PartialCompletionResult => HitResult.SmallBonus; } private class ClassicSwell : Swell diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs index cb0f59a9fb..a90e9ce676 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs @@ -225,7 +225,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables ApplyResult(r => { var swellJudgement = (TaikoSwellJudgement)r.Judgement; - r.Type = numHits > HitObject.RequiredHits / 2 ? swellJudgement.OkResult : swellJudgement.MinResult; + r.Type = numHits > HitObject.RequiredHits / 2 ? swellJudgement.PartialCompletionResult : swellJudgement.MinResult; }); } } diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index 223e268d7f..eb0706f131 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -200,7 +200,7 @@ namespace osu.Game.Rulesets.Taiko return "drum tick"; case HitResult.SmallBonus: - return "strong bonus"; + return "bonus"; } return base.GetDisplayNameForHitResult(result); From da7d297d85e4ae5d59747dac3ae8686e1849433f Mon Sep 17 00:00:00 2001 From: Pasi4K5 Date: Tue, 26 Jul 2022 19:07:25 +0200 Subject: [PATCH 046/420] Adjust parameters --- osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs | 21 ++++++--------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs index ef3eaf2a9d..716fa990c9 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs @@ -43,17 +43,13 @@ namespace osu.Game.Rulesets.Osu.Mods for (int i = 0; i < positionInfos.Count; i++) { - bool invertFlow = false; - if (i == 0 || (positionInfos[Math.Max(0, i - 2)].HitObject.IndexInCurrentCombo > 1 && positionInfos[i - 1].HitObject.NewCombo && rng.NextDouble() < 0.6) || OsuHitObjectGenerationUtils.IsHitObjectOnBeat(osuBeatmap, positionInfos[i - 1].HitObject, true) || - (OsuHitObjectGenerationUtils.IsHitObjectOnBeat(osuBeatmap, positionInfos[i - 1].HitObject) && rng.NextDouble() < 0.25)) + (OsuHitObjectGenerationUtils.IsHitObjectOnBeat(osuBeatmap, positionInfos[i - 1].HitObject) && rng.NextDouble() < 0.3)) { - sequenceOffset = OsuHitObjectGenerationUtils.RandomGaussian(rng, 0, 0.0015f); - - if (rng.NextDouble() < 0.6) - invertFlow = true; + sequenceOffset = OsuHitObjectGenerationUtils.RandomGaussian(rng, 0, 0.0012f); + flowDirection = !flowDirection; } if (i == 0) @@ -64,18 +60,13 @@ namespace osu.Game.Rulesets.Osu.Mods else { float flowChangeOffset = 0; - float oneTimeOffset = OsuHitObjectGenerationUtils.RandomGaussian(rng, 0, 0.0015f); + float oneTimeOffset = OsuHitObjectGenerationUtils.RandomGaussian(rng, 0, 0.002f); if (positionInfos[Math.Max(0, i - 2)].HitObject.IndexInCurrentCombo > 1 && positionInfos[i - 1].HitObject.NewCombo && rng.NextDouble() < 0.6) { flowChangeOffset = OsuHitObjectGenerationUtils.RandomGaussian(rng, 0, 0.002f); - - if (rng.NextDouble() < 0.8) - invertFlow = true; - } - - if (invertFlow) flowDirection = !flowDirection; + } positionInfos[i].RelativeAngle = getRelativeTargetAngle( positionInfos[i].DistanceFromPrevious, @@ -94,7 +85,7 @@ namespace osu.Game.Rulesets.Osu.Mods /// Whether the relative angle should be positive or negative. private static float getRelativeTargetAngle(float targetDistance, float offset, bool flowDirection) { - float angle = (float)(3 / (1 + 200 * Math.Exp(0.016 * (targetDistance - 466))) + 0.45 + offset); + float angle = (float)(2.16 / (1 + 200 * Math.Exp(0.036 * (targetDistance - 320))) + 0.5 + offset); float relativeAngle = (float)Math.PI - angle; return flowDirection ? -relativeAngle : relativeAngle; } From 8105d4854a53c48617ca7638a989a9442174e69a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 27 Jul 2022 13:30:38 +0900 Subject: [PATCH 047/420] Fix beatmap carousel not maintaining selection if currently selected beatmap is updated --- osu.Game/Screens/Select/BeatmapCarousel.cs | 32 ++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 75caa3c9a3..c6f2b61049 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -265,6 +265,38 @@ namespace osu.Game.Screens.Select foreach (int i in changes.InsertedIndices) UpdateBeatmapSet(sender[i].Detach()); + + if (changes.DeletedIndices.Length > 0) + { + // To handle the beatmap update flow, attempt to track selection changes across delete-insert transactions. + // When an update occurs, the previous beatmap set is either soft or hard deleted. + // Check if the current selection was potentially deleted by re-querying its validity. + bool selectedSetMarkedDeleted = realm.Run(r => r.Find(SelectedBeatmapSet.ID))?.DeletePending != false; + + if (selectedSetMarkedDeleted) + { + // If it is no longer valid, make the bold assumption that an updated version will be available in the modified indices. + // This relies on the full update operation being in a single transaction, so please don't change that. + foreach (int i in changes.NewModifiedIndices) + { + var beatmapSetInfo = sender[i]; + + foreach (var beatmapInfo in beatmapSetInfo.Beatmaps) + { + // Best effort matching. We can't use ID because in the update flow a new version will get its own GUID. + bool selectionMatches = + ((IBeatmapMetadataInfo)beatmapInfo.Metadata).Equals(SelectedBeatmapInfo.Metadata) + && beatmapInfo.DifficultyName == SelectedBeatmapInfo.DifficultyName; + + if (selectionMatches) + { + SelectBeatmap(beatmapInfo); + break; + } + } + } + } + } } private void beatmapsChanged(IRealmCollection sender, ChangeSet changes, Exception error) From 24d75612e222fe3e055d1f5fabf623727dc81727 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 27 Jul 2022 14:18:53 +0900 Subject: [PATCH 048/420] Always attempt to follow selection, even if difficulty name / metadata change --- osu.Game/Screens/Select/BeatmapCarousel.cs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index c6f2b61049..0430c15a1d 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -283,18 +283,21 @@ namespace osu.Game.Screens.Select foreach (var beatmapInfo in beatmapSetInfo.Beatmaps) { - // Best effort matching. We can't use ID because in the update flow a new version will get its own GUID. - bool selectionMatches = - ((IBeatmapMetadataInfo)beatmapInfo.Metadata).Equals(SelectedBeatmapInfo.Metadata) - && beatmapInfo.DifficultyName == SelectedBeatmapInfo.DifficultyName; + if (!((IBeatmapMetadataInfo)beatmapInfo.Metadata).Equals(SelectedBeatmapInfo.Metadata)) + continue; - if (selectionMatches) + // Best effort matching. We can't use ID because in the update flow a new version will get its own GUID. + if (beatmapInfo.DifficultyName == SelectedBeatmapInfo.DifficultyName) { SelectBeatmap(beatmapInfo); - break; + return; } } } + + // If a direct selection couldn't be made, it's feasible that the difficulty name (or beatmap metadata) changed. + // Let's attempt to follow set-level selection anyway. + SelectBeatmap(sender[changes.NewModifiedIndices.First()].Beatmaps.First()); } } } From e6a3659581e62b9c181d1c0ffe4f82a8988a8d05 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 27 Jul 2022 14:23:47 +0900 Subject: [PATCH 049/420] Guard against `NewModifiedIndices` being empty --- osu.Game/Screens/Select/BeatmapCarousel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 0430c15a1d..e9419e7156 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -273,7 +273,7 @@ namespace osu.Game.Screens.Select // Check if the current selection was potentially deleted by re-querying its validity. bool selectedSetMarkedDeleted = realm.Run(r => r.Find(SelectedBeatmapSet.ID))?.DeletePending != false; - if (selectedSetMarkedDeleted) + if (selectedSetMarkedDeleted && changes.NewModifiedIndices.Any()) { // If it is no longer valid, make the bold assumption that an updated version will be available in the modified indices. // This relies on the full update operation being in a single transaction, so please don't change that. From e6fad601cc2106e983d13ba9a2383f8500b917ba Mon Sep 17 00:00:00 2001 From: Micahel Kelly Date: Wed, 27 Jul 2022 19:15:43 +1000 Subject: [PATCH 050/420] Adds delete difficulty option to editor --- osu.Game/Screens/Edit/Editor.cs | 18 ++++++++++ .../Edit/PromptForDifficultyDeleteDialog.cs | 33 +++++++++++++++++++ 2 files changed, 51 insertions(+) create mode 100644 osu.Game/Screens/Edit/PromptForDifficultyDeleteDialog.cs diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 3e3940c5ba..5cee634fd8 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -849,6 +849,20 @@ namespace osu.Game.Screens.Edit new LegacyBeatmapExporter(storage).Export(Beatmap.Value.BeatmapSetInfo); } + private void deleteDifficulty() + { + dialogOverlay?.Push(new PromptForDifficultyDeleteDialog(confirmDifficultyDelete, () => { })); + } + + private void confirmDifficultyDelete() + { + var current = playableBeatmap.BeatmapInfo; + if (current is null) return; + + beatmapManager.Hide(current); + this.Exit(); + } + private void updateLastSavedHash() { lastSavedHash = changeHandler?.CurrentStateHash; @@ -869,6 +883,10 @@ namespace osu.Game.Screens.Edit fileMenuItems.Add(createDifficultyCreationMenu()); fileMenuItems.Add(createDifficultySwitchMenu()); + fileMenuItems.Add(new EditorMenuItemSpacer()); + + fileMenuItems.Add(new EditorMenuItem("Delete difficulty", MenuItemType.Standard, deleteDifficulty)); + fileMenuItems.Add(new EditorMenuItemSpacer()); fileMenuItems.Add(new EditorMenuItem("Exit", MenuItemType.Standard, this.Exit)); return fileMenuItems; diff --git a/osu.Game/Screens/Edit/PromptForDifficultyDeleteDialog.cs b/osu.Game/Screens/Edit/PromptForDifficultyDeleteDialog.cs new file mode 100644 index 0000000000..abf66d7a5b --- /dev/null +++ b/osu.Game/Screens/Edit/PromptForDifficultyDeleteDialog.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 PromptForDifficultyDeleteDialog : PopupDialog + { + public PromptForDifficultyDeleteDialog(Action delete, Action cancel) + { + HeaderText = "Are you sure you want to delete this difficulty?"; + + Icon = FontAwesome.Regular.Save; + + Buttons = new PopupDialogButton[] + { + new PopupDialogDangerousButton + { + Text = @"Yes delete this difficulty!", + Action = delete + }, + new PopupDialogCancelButton + { + Text = @"Oops, continue editing", + Action = cancel + }, + }; + } + } +} From 3febd6d6443a75ea3e6d9ff29c390301f0a03872 Mon Sep 17 00:00:00 2001 From: Micahel Kelly Date: Wed, 27 Jul 2022 19:23:55 +1000 Subject: [PATCH 051/420] Dialogue touchups --- osu.Game/Screens/Edit/PromptForDifficultyDeleteDialog.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/PromptForDifficultyDeleteDialog.cs b/osu.Game/Screens/Edit/PromptForDifficultyDeleteDialog.cs index abf66d7a5b..a8f8afd47c 100644 --- a/osu.Game/Screens/Edit/PromptForDifficultyDeleteDialog.cs +++ b/osu.Game/Screens/Edit/PromptForDifficultyDeleteDialog.cs @@ -13,13 +13,13 @@ namespace osu.Game.Screens.Edit { HeaderText = "Are you sure you want to delete this difficulty?"; - Icon = FontAwesome.Regular.Save; + Icon = FontAwesome.Regular.TrashAlt; Buttons = new PopupDialogButton[] { new PopupDialogDangerousButton { - Text = @"Yes delete this difficulty!", + Text = @"Yes, delete this difficulty!", Action = delete }, new PopupDialogCancelButton From dd315ff97b3d0f6c96fcd75796738f9e909e88f1 Mon Sep 17 00:00:00 2001 From: Micahel Kelly Date: Wed, 27 Jul 2022 21:21:16 +1000 Subject: [PATCH 052/420] Adds hard-delete for a beatmap diff --- osu.Game/Beatmaps/BeatmapManager.cs | 25 ++++++++++++++++++ osu.Game/Screens/Edit/Editor.cs | 26 ++++++++++++++++--- .../Edit/PromptForDifficultyDeleteDialog.cs | 9 +++++-- 3 files changed, 55 insertions(+), 5 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index debe4c6829..6db038355f 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -358,6 +358,31 @@ namespace osu.Game.Beatmaps }); } + /// + /// Hard-Delete a beatmap difficulty locally. + /// + /// is generally preferred as it keeps the local beatmap set in sync with the server. + /// The beatmap difficulty to hide. + public void DeleteLocal(BeatmapInfo beatmapInfo) + { + Realm.Run(r => + { + using (var transaction = r.BeginWrite()) + { + if (!beatmapInfo.IsManaged) + beatmapInfo = r.Find(beatmapInfo.ID); + + var setInfo = beatmapInfo.BeatmapSet!; + DeleteFile(setInfo, beatmapInfo.File!); + setInfo.Beatmaps.Remove(beatmapInfo); + + var liveSetInfo = r.Find(setInfo.ID); + setInfo.CopyChangesToRealm(liveSetInfo); + transaction.Commit(); + } + }); + } + /// /// Delete videos from a list of beatmaps. /// This will post notifications tracking progress. diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 5cee634fd8..129e71ea30 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -851,7 +851,16 @@ namespace osu.Game.Screens.Edit private void deleteDifficulty() { - dialogOverlay?.Push(new PromptForDifficultyDeleteDialog(confirmDifficultyDelete, () => { })); + dialogOverlay?.Push(new PromptForDifficultyDeleteDialog(confirmDifficultyHide, confirmDifficultyDelete, () => { })); + } + + private void confirmDifficultyHide() + { + var current = playableBeatmap.BeatmapInfo; + if (current is null) return; + + beatmapManager.Hide(current); + switchBeatmapOrExit(current.BeatmapSet); } private void confirmDifficultyDelete() @@ -859,8 +868,19 @@ namespace osu.Game.Screens.Edit var current = playableBeatmap.BeatmapInfo; if (current is null) return; - beatmapManager.Hide(current); - this.Exit(); + beatmapManager.DeleteLocal(current); + switchBeatmapOrExit(current.BeatmapSet); + } + + private void switchBeatmapOrExit([CanBeNull] BeatmapSetInfo setInfo) + { + if (setInfo is null || setInfo.Beatmaps.Count() <= 1) + this.Exit(); + var nextBeatmap = setInfo!.Beatmaps.First(); + + // Force a refresh of the beatmap (and beatmap set) so the `Change difficulty` list is also updated. + Beatmap.Value = beatmapManager.GetWorkingBeatmap(nextBeatmap, true); + SwitchToDifficulty(Beatmap.Value.BeatmapInfo); } private void updateLastSavedHash() diff --git a/osu.Game/Screens/Edit/PromptForDifficultyDeleteDialog.cs b/osu.Game/Screens/Edit/PromptForDifficultyDeleteDialog.cs index a8f8afd47c..dcea9f1210 100644 --- a/osu.Game/Screens/Edit/PromptForDifficultyDeleteDialog.cs +++ b/osu.Game/Screens/Edit/PromptForDifficultyDeleteDialog.cs @@ -9,7 +9,7 @@ namespace osu.Game.Screens.Edit { public class PromptForDifficultyDeleteDialog : PopupDialog { - public PromptForDifficultyDeleteDialog(Action delete, Action cancel) + public PromptForDifficultyDeleteDialog(Action hide, Action delete, Action cancel) { HeaderText = "Are you sure you want to delete this difficulty?"; @@ -17,9 +17,14 @@ namespace osu.Game.Screens.Edit Buttons = new PopupDialogButton[] { + new PopupDialogOkButton + { + Text = @"Hide this difficulty instead (recommended)", + Action = hide + }, new PopupDialogDangerousButton { - Text = @"Yes, delete this difficulty!", + Text = @"Yes, DELETE this difficulty!", Action = delete }, new PopupDialogCancelButton From b2e7da5aa06189bf52e0f5e38bf7cb9bd8b6a712 Mon Sep 17 00:00:00 2001 From: Ryuki Date: Thu, 28 Jul 2022 18:37:12 +0200 Subject: [PATCH 053/420] Add basic Queue based implementation of KPS --- .../Gameplay/TestSceneKeysPerSecondCounter.cs | 10 ++ .../Screens/Play/HUD/KeysPerSecondCounter.cs | 125 ++++++++++++++++++ osu.Game/Screens/Play/KeyCounter.cs | 2 + 3 files changed, 137 insertions(+) create mode 100644 osu.Game.Tests/Visual/Gameplay/TestSceneKeysPerSecondCounter.cs create mode 100644 osu.Game/Screens/Play/HUD/KeysPerSecondCounter.cs diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneKeysPerSecondCounter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneKeysPerSecondCounter.cs new file mode 100644 index 0000000000..451e297e05 --- /dev/null +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneKeysPerSecondCounter.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.Tests.Visual.Gameplay +{ + public class TestSceneKeysPerSecondCounter + { + + } +} diff --git a/osu.Game/Screens/Play/HUD/KeysPerSecondCounter.cs b/osu.Game/Screens/Play/HUD/KeysPerSecondCounter.cs new file mode 100644 index 0000000000..dc9a51dbf3 --- /dev/null +++ b/osu.Game/Screens/Play/HUD/KeysPerSecondCounter.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 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.Graphics.Sprites; +using osu.Framework.Localisation; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osu.Game.Skinning; +using osuTK; + +namespace osu.Game.Screens.Play.HUD +{ + public class KeysPerSecondCounter : RollingCounter, ISkinnableDrawable + { + private static Queue? timestamps; + + private static event Action? onNewInput; + private readonly TimeSpan refreshSpan = TimeSpan.FromSeconds(1); + + private const float alpha_when_invalid = 0.3f; + private readonly Bindable valid = new Bindable(); + + public static void AddTimestamp() + { + timestamps?.Enqueue(DateTime.Now); + onNewInput?.Invoke(); + } + + protected override double RollingDuration => 250; + + public bool UsesFixedAnchor { get; set; } + + public KeysPerSecondCounter() + { + timestamps ??= new Queue(); + Current.Value = 0; + onNewInput += updateCounter; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + Colour = colours.BlueLighter; + valid.BindValueChanged(e => + DrawableCount.FadeTo(e.NewValue ? 1 : alpha_when_invalid, 1000, Easing.OutQuint)); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + updateCounter(); + } + + protected override void Update() + { + if (timestamps != null) + { + if (timestamps.TryPeek(out var earliest) && DateTime.Now - earliest >= refreshSpan) + timestamps.Dequeue(); + } + + updateCounter(); + + base.Update(); + } + + private void updateCounter() + { + valid.Value = timestamps != null; + Current.Value = timestamps?.Count ?? 0; + } + + protected override IHasText CreateText() => new TextComponent + { + Alpha = alpha_when_invalid + }; + + private class TextComponent : CompositeDrawable, IHasText + { + public LocalisableString Text + { + get => text.Text; + set => text.Text = value; + } + + private readonly OsuSpriteText text; + + public TextComponent() + { + AutoSizeAxes = Axes.Both; + + InternalChild = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Spacing = new Vector2(2), + Children = new Drawable[] + { + text = new OsuSpriteText + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Font = OsuFont.Numeric.With(size: 16, fixedWidth: true) + }, + new OsuSpriteText + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Font = OsuFont.Numeric.With(size: 8, fixedWidth: true), + Text = @"KPS", + Padding = new MarginPadding { Bottom = 1.5f }, // align baseline better + } + } + }; + } + } + } +} diff --git a/osu.Game/Screens/Play/KeyCounter.cs b/osu.Game/Screens/Play/KeyCounter.cs index 1e5ada5295..b8bbac9a7e 100644 --- a/osu.Game/Screens/Play/KeyCounter.cs +++ b/osu.Game/Screens/Play/KeyCounter.cs @@ -10,6 +10,7 @@ using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; +using osu.Game.Screens.Play.HUD; using osuTK; using osuTK.Graphics; @@ -55,6 +56,7 @@ namespace osu.Game.Screens.Play public void Increment() { + KeysPerSecondCounter.AddTimestamp(); if (!IsCounting) return; From 079150849a53ee3545a8b58c137bb7648fdb4b3d Mon Sep 17 00:00:00 2001 From: Ryuki Date: Thu, 28 Jul 2022 18:37:50 +0200 Subject: [PATCH 054/420] Add some tests --- .../Gameplay/TestSceneKeysPerSecondCounter.cs | 69 ++++++++++++++++++- 1 file changed, 67 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneKeysPerSecondCounter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneKeysPerSecondCounter.cs index 451e297e05..e20a83b54a 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneKeysPerSecondCounter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneKeysPerSecondCounter.cs @@ -1,10 +1,75 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +#nullable disable + +using NUnit.Framework; +using osu.Framework.Graphics; +using osu.Framework.Testing; +using osu.Game.Screens.Play; +using osu.Game.Screens.Play.HUD; +using osuTK; +using osuTK.Input; + namespace osu.Game.Tests.Visual.Gameplay { - public class TestSceneKeysPerSecondCounter + public class TestSceneKeysPerSecondCounter : OsuManualInputManagerTestScene { - + private KeysPerSecondCounter counter; + + [SetUpSteps] + public void Setup() + { + createCounter(); + } + + private void createCounter() => AddStep("Create counter", () => + { + Child = counter = new KeysPerSecondCounter + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Scale = new Vector2(5) + }; + }); + + [Test] + public void TestManualTrigger() + { + AddAssert("Counter = 0", () => counter.Current.Value == 0); + AddRepeatStep("manual trigger", KeysPerSecondCounter.AddTimestamp, 20); + AddAssert("Counter is not 0", () => counter.Current.Value > 0); + } + + [Test] + public void TestKpsAsideKeyCounter() + { + AddStep("Create key counter display", () => + Add(new KeyCounterDisplay + { + Origin = Anchor.BottomCentre, + Anchor = Anchor.BottomCentre, + Y = 100, + Children = new KeyCounter[] + { + new KeyCounterKeyboard(Key.W), + new KeyCounterKeyboard(Key.X), + new KeyCounterKeyboard(Key.C), + new KeyCounterKeyboard(Key.V) + } + }) + ); + AddAssert("Counter = 0", () => counter.Current.Value == 0); + addPressKeyStep(Key.W); + addPressKeyStep(Key.X); + addPressKeyStep(Key.C); + addPressKeyStep(Key.V); + AddAssert("Counter = 4", () => counter.Current.Value == 4); + } + + private void addPressKeyStep(Key key) + { + AddStep($"Press {key} key", () => InputManager.Key(key)); + } } } From 89855cc1d6af208b5e132264e610b16a2386c8c4 Mon Sep 17 00:00:00 2001 From: Ryuki Date: Sun, 31 Jul 2022 01:29:57 +0200 Subject: [PATCH 055/420] Change KPS Counter implementation base and add better replay integration The counter implementaiton is now list based, and will not invalidate previous hits by removing them but by testing if they are within the 1 second span, allowing better integration with replays and spectators. --- .../Screens/Play/HUD/KeysPerSecondCounter.cs | 67 ++++++++++++++----- osu.Game/Screens/Play/KeyCounterDisplay.cs | 2 + osu.Game/Screens/Play/Player.cs | 4 ++ 3 files changed, 57 insertions(+), 16 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/KeysPerSecondCounter.cs b/osu.Game/Screens/Play/HUD/KeysPerSecondCounter.cs index dc9a51dbf3..d32ca1410a 100644 --- a/osu.Game/Screens/Play/HUD/KeysPerSecondCounter.cs +++ b/osu.Game/Screens/Play/HUD/KeysPerSecondCounter.cs @@ -1,17 +1,24 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +#nullable disable + using System; 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.Sprites; using osu.Framework.Localisation; +using osu.Framework.Logging; +using osu.Framework.Timing; +using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; +using osu.Game.Rulesets.UI; using osu.Game.Skinning; using osuTK; @@ -19,37 +26,71 @@ namespace osu.Game.Screens.Play.HUD { public class KeysPerSecondCounter : RollingCounter, ISkinnableDrawable { - private static Queue? timestamps; + private static List timestamps; + private static double maxTime = double.NegativeInfinity; - private static event Action? onNewInput; - private readonly TimeSpan refreshSpan = TimeSpan.FromSeconds(1); + private static event Action onNewInput; + private const int invalidation_timeout = 1000; private const float alpha_when_invalid = 0.3f; + private readonly Bindable valid = new Bindable(); + private static GameplayClock gameplayClock; + private static IClock referenceClock; + + private static IClock clock => referenceClock ?? gameplayClock; + + [Resolved(canBeNull: true)] + private DrawableRuleset drawableRuleset { get; set; } + + [SettingSource("Smoothing time", "How smooth the counter should change\nThe more it is smooth, the less it's accurate.")] + public BindableNumber SmoothingTime { get; } = new BindableNumber(350) + { + MaxValue = 1000, + MinValue = 0 + }; + public static void AddTimestamp() { - timestamps?.Enqueue(DateTime.Now); + Logger.Log($"Input timestamp attempt C: {clock.CurrentTime}ms | GC: {gameplayClock.CurrentTime} | RC: {referenceClock?.CurrentTime ?? -1} | Max: {maxTime})", level: LogLevel.Debug); + + if (clock.CurrentTime >= maxTime) + { + Logger.Log("Input timestamp added.", level: LogLevel.Debug); + timestamps?.Add(clock.CurrentTime); + maxTime = timestamps?.Max() ?? clock.CurrentTime; + } + onNewInput?.Invoke(); } - protected override double RollingDuration => 250; + public static void Reset() + { + timestamps?.Clear(); + maxTime = int.MinValue; + } + + protected override double RollingDuration => SmoothingTime.Value; public bool UsesFixedAnchor { get; set; } public KeysPerSecondCounter() { - timestamps ??= new Queue(); + timestamps ??= new List(); Current.Value = 0; onNewInput += updateCounter; + Scheduler.AddOnce(updateCounter); } [BackgroundDependencyLoader] - private void load(OsuColour colours) + private void load(OsuColour colours, GameplayClock clock) { + gameplayClock = clock; Colour = colours.BlueLighter; valid.BindValueChanged(e => DrawableCount.FadeTo(e.NewValue ? 1 : alpha_when_invalid, 1000, Easing.OutQuint)); + referenceClock = drawableRuleset?.FrameStableClock; } protected override void LoadComplete() @@ -61,21 +102,15 @@ namespace osu.Game.Screens.Play.HUD protected override void Update() { - if (timestamps != null) - { - if (timestamps.TryPeek(out var earliest) && DateTime.Now - earliest >= refreshSpan) - timestamps.Dequeue(); - } + base.Update(); updateCounter(); - - base.Update(); } private void updateCounter() { - valid.Value = timestamps != null; - Current.Value = timestamps?.Count ?? 0; + valid.Value = timestamps != null && MathHelper.ApproximatelyEquivalent(gameplayClock.CurrentTime, referenceClock.CurrentTime, 500); + Current.Value = timestamps?.Count(timestamp => clock.CurrentTime - timestamp is >= 0 and <= invalidation_timeout) ?? 0; } protected override IHasText CreateText() => new TextComponent diff --git a/osu.Game/Screens/Play/KeyCounterDisplay.cs b/osu.Game/Screens/Play/KeyCounterDisplay.cs index b6094726c0..aaf2e997f2 100644 --- a/osu.Game/Screens/Play/KeyCounterDisplay.cs +++ b/osu.Game/Screens/Play/KeyCounterDisplay.cs @@ -11,6 +11,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Events; using osu.Game.Configuration; +using osu.Game.Screens.Play.HUD; using osuTK; using osuTK.Graphics; @@ -65,6 +66,7 @@ namespace osu.Game.Screens.Play [BackgroundDependencyLoader] private void load(OsuConfigManager config) { + KeysPerSecondCounter.Reset(); config.BindWith(OsuSetting.KeyOverlay, configVisibility); } diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 9a058e45c5..fb2f556611 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -34,6 +34,7 @@ using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; using osu.Game.Scoring; using osu.Game.Scoring.Legacy; +using osu.Game.Screens.Play.HUD; using osu.Game.Screens.Ranking; using osu.Game.Skinning; using osu.Game.Users; @@ -1044,6 +1045,9 @@ namespace osu.Game.Screens.Play musicController.ResetTrackAdjustments(); fadeOut(); + + KeysPerSecondCounter.Reset(); + return base.OnExiting(e); } From bc059cc1d22ef01c4ec10aa6b58a1249febb1fdf Mon Sep 17 00:00:00 2001 From: HiddenNode Date: Mon, 1 Aug 2022 21:46:01 +0100 Subject: [PATCH 056/420] Implemented KeepUpright --- osu.Game/Extensions/DrawableExtensions.cs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/osu.Game/Extensions/DrawableExtensions.cs b/osu.Game/Extensions/DrawableExtensions.cs index 35f2d61437..5b92600cd1 100644 --- a/osu.Game/Extensions/DrawableExtensions.cs +++ b/osu.Game/Extensions/DrawableExtensions.cs @@ -8,6 +8,7 @@ using osu.Game.Configuration; using osu.Game.Screens.Play.HUD; using osu.Game.Skinning; using osuTK; +using System; namespace osu.Game.Extensions { @@ -79,5 +80,24 @@ namespace osu.Game.Extensions container.Add(child.CreateInstance()); } } + + /// + /// Keeps the drawable upright no matter the Rotation of its parents. + /// + /// The drawable. + public static void KeepUpright(this Drawable drawable) + { + var result = drawable.Parent.DrawInfo; + var scale = result.Matrix.ExtractScale(); + var rotation = new Matrix3( + result.Matrix.Row0 / scale.X, + result.Matrix.Row1 / scale.Y, + new Vector3(0.0f, 0.0f, 1.0f) + ); + float angle = MathF.Atan2(rotation.M12, rotation.M11); + angle *= (360 / (2 * MathF.PI)); + drawable.Rotation = -angle; + } + } } From df85bd74d7ab9eabd2c51b283ab0e15bafa25edc Mon Sep 17 00:00:00 2001 From: HiddenNode Date: Mon, 1 Aug 2022 21:46:37 +0100 Subject: [PATCH 057/420] Keep TextSprites in SongProgressInfo upright --- osu.Game/Screens/Play/HUD/SongProgressInfo.cs | 69 ++++++++++++++----- 1 file changed, 53 insertions(+), 16 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/SongProgressInfo.cs b/osu.Game/Screens/Play/HUD/SongProgressInfo.cs index 8f10e84509..520d0c661d 100644 --- a/osu.Game/Screens/Play/HUD/SongProgressInfo.cs +++ b/osu.Game/Screens/Play/HUD/SongProgressInfo.cs @@ -48,38 +48,67 @@ namespace osu.Game.Screens.Play.HUD Children = new Drawable[] { - timeCurrent = new OsuSpriteText + new Container { - Origin = Anchor.BottomLeft, Anchor = Anchor.BottomLeft, - Colour = colours.BlueLighter, - Font = OsuFont.Numeric, - Margin = new MarginPadding + Origin = Anchor.BottomLeft, + AutoSizeAxes = Axes.Both, + Children = new Drawable[] { - Left = margin, - }, + timeCurrent = new OsuSpriteText + { + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + Colour = colours.BlueLighter, + Font = OsuFont.Numeric, + } + } }, - progress = new OsuSpriteText + new Container { Origin = Anchor.BottomCentre, Anchor = Anchor.BottomCentre, - Colour = colours.BlueLighter, - Font = OsuFont.Numeric, + AutoSizeAxes = Axes.Both, + Children = new Drawable[] + { + progress = new OsuSpriteText + { + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + Colour = colours.BlueLighter, + Font = OsuFont.Numeric, + } + } }, - timeLeft = new OsuSpriteText + new Container { Origin = Anchor.BottomRight, Anchor = Anchor.BottomRight, - Colour = colours.BlueLighter, - Font = OsuFont.Numeric, - Margin = new MarginPadding + AutoSizeAxes = Axes.Both, + Children = new Drawable[] { - Right = margin, - }, + timeLeft = new OsuSpriteText + { + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + Colour = colours.BlueLighter, + Font = OsuFont.Numeric, + Margin = new MarginPadding + { + Right = margin, + }, + } + } } }; } + protected override void LoadComplete() + { + base.LoadComplete(); + keepTextSpritesUpright(); + } + protected override void Update() { base.Update(); @@ -106,5 +135,13 @@ namespace osu.Game.Screens.Play.HUD } private string formatTime(TimeSpan timeSpan) => $"{(timeSpan < TimeSpan.Zero ? "-" : "")}{Math.Floor(timeSpan.Duration().TotalMinutes)}:{timeSpan.Duration().Seconds:D2}"; + + private void keepTextSpritesUpright() + { + timeCurrent.OnUpdate += (timeCurrent) => { Extensions.DrawableExtensions.KeepUpright(timeCurrent); }; + progress.OnUpdate += (timeCurrent) => { Extensions.DrawableExtensions.KeepUpright(timeCurrent); }; + timeLeft.OnUpdate += (timeCurrent) => { Extensions.DrawableExtensions.KeepUpright(timeCurrent); }; + } + } } From 78a98cdb9c23ef9b0827d77adfc8a07c571308bd Mon Sep 17 00:00:00 2001 From: HiddenNode Date: Tue, 2 Aug 2022 17:03:02 +0100 Subject: [PATCH 058/420] Prevent TextSprites inside SongProgressInfo from being stretched or flipped --- osu.Game/Extensions/DrawableExtensions.cs | 30 ++++++++++++++++--- .../Screens/Play/HUD/DefaultSongProgress.cs | 5 ++-- osu.Game/Screens/Play/HUD/SongProgressInfo.cs | 7 +++-- 3 files changed, 32 insertions(+), 10 deletions(-) diff --git a/osu.Game/Extensions/DrawableExtensions.cs b/osu.Game/Extensions/DrawableExtensions.cs index 5b92600cd1..46105218c5 100644 --- a/osu.Game/Extensions/DrawableExtensions.cs +++ b/osu.Game/Extensions/DrawableExtensions.cs @@ -82,11 +82,12 @@ namespace osu.Game.Extensions } /// - /// Keeps the drawable upright no matter the Rotation of its parents. + /// Keeps the drawable upright and prevents it from being scaled or flipped with its Parent. /// /// The drawable. - public static void KeepUpright(this Drawable drawable) + public static void KeepUprightAndUnstretched(this Drawable drawable) { + // Fix the rotation var result = drawable.Parent.DrawInfo; var scale = result.Matrix.ExtractScale(); var rotation = new Matrix3( @@ -94,9 +95,30 @@ namespace osu.Game.Extensions result.Matrix.Row1 / scale.Y, new Vector3(0.0f, 0.0f, 1.0f) ); - float angle = MathF.Atan2(rotation.M12, rotation.M11); + rotation.Invert(); + float angle = MathF.Atan(rotation.M12 / rotation.M11); angle *= (360 / (2 * MathF.PI)); - drawable.Rotation = -angle; + drawable.Rotation = angle; + + // Fix the scale (includes flip) + var containerOriginToSpaceOrigin = new Matrix3( + new Vector3(1.0f, 0.0f, 0.0f), + new Vector3(0.0f, 1.0f, 0.0f), + new Vector3(drawable.DrawSize.X / 2, drawable.DrawSize.Y / 2, 1.0f) + ); + var containerOriginToSpaceOriginInverse = containerOriginToSpaceOrigin; + containerOriginToSpaceOriginInverse.Invert(); + Matrix3 rotatedBack = (containerOriginToSpaceOriginInverse * (rotation * (containerOriginToSpaceOrigin * result.Matrix))); + + bool xFliped = rotation.M11 < 0; + bool yFliped = rotation.M22 < 0; + + var rotatedBackScale = rotatedBack.ExtractScale(); + + drawable.Scale = new Vector2( + (xFliped ? -1 : 1) / rotatedBackScale.X, + (yFliped ? -1 : 1) / rotatedBackScale.Y + ); } } diff --git a/osu.Game/Screens/Play/HUD/DefaultSongProgress.cs b/osu.Game/Screens/Play/HUD/DefaultSongProgress.cs index 96a6c56860..9ed99ab5f6 100644 --- a/osu.Game/Screens/Play/HUD/DefaultSongProgress.cs +++ b/osu.Game/Screens/Play/HUD/DefaultSongProgress.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Graphics.Shapes; using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Rulesets.Objects; @@ -16,7 +17,6 @@ namespace osu.Game.Screens.Play.HUD { public class DefaultSongProgress : SongProgress { - private const float info_height = 20; private const float bottom_bar_height = 5; private const float graph_height = SquareGraph.Column.WIDTH * 6; private const float handle_height = 18; @@ -67,7 +67,6 @@ namespace osu.Game.Screens.Play.HUD Origin = Anchor.BottomLeft, Anchor = Anchor.BottomLeft, RelativeSizeAxes = Axes.X, - Height = info_height, }, graph = new SongProgressGraph { @@ -180,7 +179,7 @@ namespace osu.Game.Screens.Play.HUD protected override void Update() { base.Update(); - Height = bottom_bar_height + graph_height + handle_size.Y + info_height - graph.Y; + Height = bottom_bar_height + graph_height + handle_size.Y + info.Height - graph.Y; } private void updateBarVisibility() diff --git a/osu.Game/Screens/Play/HUD/SongProgressInfo.cs b/osu.Game/Screens/Play/HUD/SongProgressInfo.cs index 520d0c661d..656498fc43 100644 --- a/osu.Game/Screens/Play/HUD/SongProgressInfo.cs +++ b/osu.Game/Screens/Play/HUD/SongProgressInfo.cs @@ -46,6 +46,7 @@ namespace osu.Game.Screens.Play.HUD if (clock != null) gameplayClock = clock; + AutoSizeAxes = Axes.Y; Children = new Drawable[] { new Container @@ -138,9 +139,9 @@ namespace osu.Game.Screens.Play.HUD private void keepTextSpritesUpright() { - timeCurrent.OnUpdate += (timeCurrent) => { Extensions.DrawableExtensions.KeepUpright(timeCurrent); }; - progress.OnUpdate += (timeCurrent) => { Extensions.DrawableExtensions.KeepUpright(timeCurrent); }; - timeLeft.OnUpdate += (timeCurrent) => { Extensions.DrawableExtensions.KeepUpright(timeCurrent); }; + timeCurrent.OnUpdate += (timeCurrent) => { Extensions.DrawableExtensions.KeepUprightAndUnstretched(timeCurrent); }; + progress.OnUpdate += (timeCurrent) => { Extensions.DrawableExtensions.KeepUprightAndUnstretched(timeCurrent); }; + timeLeft.OnUpdate += (timeCurrent) => { Extensions.DrawableExtensions.KeepUprightAndUnstretched(timeCurrent); }; } } From bc21a2ed569a443bd03fb5e230aebc280fa1bcc1 Mon Sep 17 00:00:00 2001 From: HiddenNode Date: Tue, 2 Aug 2022 17:41:17 +0100 Subject: [PATCH 059/420] Remove unnecessary using directive --- osu.Game/Screens/Play/HUD/DefaultSongProgress.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Screens/Play/HUD/DefaultSongProgress.cs b/osu.Game/Screens/Play/HUD/DefaultSongProgress.cs index 9ed99ab5f6..361e35ed45 100644 --- a/osu.Game/Screens/Play/HUD/DefaultSongProgress.cs +++ b/osu.Game/Screens/Play/HUD/DefaultSongProgress.cs @@ -5,7 +5,6 @@ using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Framework.Graphics.Shapes; using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Rulesets.Objects; From 42d1bdfc95fe78555fd5ce0426bc79b6a4d6f736 Mon Sep 17 00:00:00 2001 From: Ryuki Date: Fri, 5 Aug 2022 04:17:01 +0200 Subject: [PATCH 060/420] Move KPS calculation to a standalone class --- .../Gameplay/TestSceneKeyPerSecondCounter.cs | 91 +++++++++++++++++++ .../Gameplay/TestSceneKeysPerSecondCounter.cs | 4 +- .../HUD/KPSCounter/KeysPerSecondCalculator.cs | 91 +++++++++++++++++++ .../{ => KPSCounter}/KeysPerSecondCounter.cs | 63 ++----------- osu.Game/Screens/Play/KeyCounter.cs | 4 +- osu.Game/Screens/Play/KeyCounterDisplay.cs | 2 - osu.Game/Screens/Play/Player.cs | 4 +- 7 files changed, 195 insertions(+), 64 deletions(-) create mode 100644 osu.Game.Tests/Visual/Gameplay/TestSceneKeyPerSecondCounter.cs create mode 100644 osu.Game/Screens/Play/HUD/KPSCounter/KeysPerSecondCalculator.cs rename osu.Game/Screens/Play/HUD/{ => KPSCounter}/KeysPerSecondCounter.cs (63%) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneKeyPerSecondCounter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneKeyPerSecondCounter.cs new file mode 100644 index 0000000000..a2eaea29eb --- /dev/null +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneKeyPerSecondCounter.cs @@ -0,0 +1,91 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable disable + +using NUnit.Framework; +using osu.Framework.Graphics; +using osu.Framework.Testing; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Mania; +using osu.Game.Rulesets.UI; +using osu.Game.Screens.Play; +using osu.Game.Screens.Play.HUD.KPSCounter; +using osuTK; + +namespace osu.Game.Tests.Visual.Gameplay +{ + public class TestSceneKeyPerSecondCounter : PlayerTestScene + { + protected override Ruleset CreatePlayerRuleset() => new ManiaRuleset(); + protected override bool HasCustomSteps => false; + protected override bool Autoplay => true; + + private GameplayClock gameplayClock; + private DrawableRuleset drawableRuleset; + + // private DependencyProvidingContainer dependencyContainer; + private KeysPerSecondCounter counter; + + [SetUpSteps] + public new void SetUpSteps() + { + /* + CreateTest(() => AddStep("Create components", () => + { + Logger.Log($"{(Player != null ? Player.ToString() : "null")}", level: LogLevel.Debug); + dependencyContainer = new DependencyProvidingContainer + { + RelativePositionAxes = Axes.Both, + }; + })); + */ + } + + private void createCounter() + { + AddStep("Create counter", () => + { + /* + if (!Contains(dependencyContainer)) + { + Add(dependencyContainer); + } + + if (dependencyContainer.CachedDependencies.Length == 0) + { + dependencyContainer.CachedDependencies = new (Type, object)[] + { + (typeof(GameplayClock), , + (typeof(DrawableRuleset),) + }; + } + Dependencies.Cache(gameplayClock = Player.GameplayClockContainer.GameplayClock)); + */ + + Dependencies.Cache(gameplayClock = Player.GameplayClockContainer.GameplayClock); + Dependencies.Cache(drawableRuleset = Player.DrawableRuleset); + + Add(counter = new KeysPerSecondCounter + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Scale = new Vector2(5), + Position = new Vector2(10, 100) + } + ); + }); + AddAssert("ensure counter added", () => Contains(counter)); + } + + [Test] + public void TestInGameTimeConsistency() + { + createCounter(); + + AddUntilStep("Wait until first note", () => counter.Current.Value != 0); + AddStep("Pause gameplay", () => gameplayClock.IsPaused.Value = true); + AddAssert("KPS = 1", () => counter.Current.Value == 1); + } + } +} diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneKeysPerSecondCounter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneKeysPerSecondCounter.cs index e20a83b54a..c8c31d1366 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneKeysPerSecondCounter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneKeysPerSecondCounter.cs @@ -7,7 +7,7 @@ using NUnit.Framework; using osu.Framework.Graphics; using osu.Framework.Testing; using osu.Game.Screens.Play; -using osu.Game.Screens.Play.HUD; +using osu.Game.Screens.Play.HUD.KPSCounter; using osuTK; using osuTK.Input; @@ -37,7 +37,7 @@ namespace osu.Game.Tests.Visual.Gameplay public void TestManualTrigger() { AddAssert("Counter = 0", () => counter.Current.Value == 0); - AddRepeatStep("manual trigger", KeysPerSecondCounter.AddTimestamp, 20); + AddRepeatStep("manual trigger", KeysPerSecondCalculator.AddInput, 20); AddAssert("Counter is not 0", () => counter.Current.Value > 0); } diff --git a/osu.Game/Screens/Play/HUD/KPSCounter/KeysPerSecondCalculator.cs b/osu.Game/Screens/Play/HUD/KPSCounter/KeysPerSecondCalculator.cs new file mode 100644 index 0000000000..f9839abde4 --- /dev/null +++ b/osu.Game/Screens/Play/HUD/KPSCounter/KeysPerSecondCalculator.cs @@ -0,0 +1,91 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable disable + +using System; +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Timing; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.UI; + +namespace osu.Game.Screens.Play.HUD.KPSCounter +{ + public class KeysPerSecondCalculator : IDisposable + { + private static KeysPerSecondCalculator instance; + + public static void AddInput() + { + instance?.onNewInput.Invoke(); + } + + public static KeysPerSecondCalculator GetInstance(GameplayClock gameplayClock = null, DrawableRuleset drawableRuleset = null) + { + if (instance != null) return instance; + + try + { + return new KeysPerSecondCalculator(gameplayClock, drawableRuleset); + } + catch (ArgumentNullException) + { + return null; + } + } + + private readonly List timestamps; + private readonly GameplayClock gameplayClock; + private readonly DrawableRuleset drawableRuleset; + + private event Action onNewInput; + + private IClock workingClock => (IClock)drawableRuleset.FrameStableClock ?? gameplayClock; + + // Having the rate from mods is preffered to using GameplayClock.TrueGameplayRate() + // as it returns 0 when paused in replays, not useful for players who want to "analyze" a replay. + private double rate => (drawableRuleset.Mods.FirstOrDefault(m => m is ModRateAdjust) as ModRateAdjust)?.SpeedChange.Value + ?? 1; + + private double maxTime = double.NegativeInfinity; + + public bool Ready => workingClock != null && gameplayClock != null; + public int Value => timestamps.Count(isTimestampWithinSpan); + + private KeysPerSecondCalculator(GameplayClock gameplayClock, DrawableRuleset drawableRuleset) + { + instance = this; + timestamps = new List(); + this.gameplayClock = gameplayClock ?? throw new ArgumentNullException(nameof(gameplayClock)); + this.drawableRuleset = drawableRuleset; + onNewInput += addTimestamp; + } + + private void addTimestamp() + { + if (workingClock != null && workingClock.CurrentTime >= maxTime) + { + timestamps.Add(workingClock.CurrentTime); + maxTime = workingClock.CurrentTime; + } + } + + private bool isTimestampWithinSpan(double timestamp) + { + if (!Ready) + return false; + + double span = 1000 * rate; + double relativeTime = workingClock.CurrentTime - timestamp; + return relativeTime >= 0 && relativeTime <= span; + } + + public void Dispose() + { + instance = null; + } + + ~KeysPerSecondCalculator() => Dispose(); + } +} diff --git a/osu.Game/Screens/Play/HUD/KeysPerSecondCounter.cs b/osu.Game/Screens/Play/HUD/KPSCounter/KeysPerSecondCounter.cs similarity index 63% rename from osu.Game/Screens/Play/HUD/KeysPerSecondCounter.cs rename to osu.Game/Screens/Play/HUD/KPSCounter/KeysPerSecondCounter.cs index d32ca1410a..2fcca2ffce 100644 --- a/osu.Game/Screens/Play/HUD/KeysPerSecondCounter.cs +++ b/osu.Game/Screens/Play/HUD/KPSCounter/KeysPerSecondCounter.cs @@ -3,17 +3,12 @@ #nullable disable -using System; -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.Sprites; using osu.Framework.Localisation; -using osu.Framework.Logging; -using osu.Framework.Timing; using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; @@ -22,28 +17,20 @@ using osu.Game.Rulesets.UI; using osu.Game.Skinning; using osuTK; -namespace osu.Game.Screens.Play.HUD +namespace osu.Game.Screens.Play.HUD.KPSCounter { public class KeysPerSecondCounter : RollingCounter, ISkinnableDrawable { - private static List timestamps; - private static double maxTime = double.NegativeInfinity; - - private static event Action onNewInput; - - private const int invalidation_timeout = 1000; private const float alpha_when_invalid = 0.3f; private readonly Bindable valid = new Bindable(); - - private static GameplayClock gameplayClock; - private static IClock referenceClock; - - private static IClock clock => referenceClock ?? gameplayClock; + private GameplayClock gameplayClock; [Resolved(canBeNull: true)] private DrawableRuleset drawableRuleset { get; set; } + private KeysPerSecondCalculator calculator => KeysPerSecondCalculator.GetInstance(gameplayClock, drawableRuleset); + [SettingSource("Smoothing time", "How smooth the counter should change\nThe more it is smooth, the less it's accurate.")] public BindableNumber SmoothingTime { get; } = new BindableNumber(350) { @@ -51,66 +38,30 @@ namespace osu.Game.Screens.Play.HUD MinValue = 0 }; - public static void AddTimestamp() - { - Logger.Log($"Input timestamp attempt C: {clock.CurrentTime}ms | GC: {gameplayClock.CurrentTime} | RC: {referenceClock?.CurrentTime ?? -1} | Max: {maxTime})", level: LogLevel.Debug); - - if (clock.CurrentTime >= maxTime) - { - Logger.Log("Input timestamp added.", level: LogLevel.Debug); - timestamps?.Add(clock.CurrentTime); - maxTime = timestamps?.Max() ?? clock.CurrentTime; - } - - onNewInput?.Invoke(); - } - - public static void Reset() - { - timestamps?.Clear(); - maxTime = int.MinValue; - } - protected override double RollingDuration => SmoothingTime.Value; public bool UsesFixedAnchor { get; set; } public KeysPerSecondCounter() { - timestamps ??= new List(); Current.Value = 0; - onNewInput += updateCounter; - Scheduler.AddOnce(updateCounter); } [BackgroundDependencyLoader] - private void load(OsuColour colours, GameplayClock clock) + private void load(OsuColour colours, GameplayClock clock, DrawableRuleset ruleset) { gameplayClock = clock; Colour = colours.BlueLighter; valid.BindValueChanged(e => DrawableCount.FadeTo(e.NewValue ? 1 : alpha_when_invalid, 1000, Easing.OutQuint)); - referenceClock = drawableRuleset?.FrameStableClock; - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - updateCounter(); } protected override void Update() { base.Update(); - updateCounter(); - } - - private void updateCounter() - { - valid.Value = timestamps != null && MathHelper.ApproximatelyEquivalent(gameplayClock.CurrentTime, referenceClock.CurrentTime, 500); - Current.Value = timestamps?.Count(timestamp => clock.CurrentTime - timestamp is >= 0 and <= invalidation_timeout) ?? 0; + valid.Value = calculator.Ready; + Current.Value = calculator.Value; } protected override IHasText CreateText() => new TextComponent diff --git a/osu.Game/Screens/Play/KeyCounter.cs b/osu.Game/Screens/Play/KeyCounter.cs index b8bbac9a7e..044c9ee24e 100644 --- a/osu.Game/Screens/Play/KeyCounter.cs +++ b/osu.Game/Screens/Play/KeyCounter.cs @@ -10,7 +10,7 @@ using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; -using osu.Game.Screens.Play.HUD; +using osu.Game.Screens.Play.HUD.KPSCounter; using osuTK; using osuTK.Graphics; @@ -56,7 +56,7 @@ namespace osu.Game.Screens.Play public void Increment() { - KeysPerSecondCounter.AddTimestamp(); + KeysPerSecondCalculator.AddInput(); if (!IsCounting) return; diff --git a/osu.Game/Screens/Play/KeyCounterDisplay.cs b/osu.Game/Screens/Play/KeyCounterDisplay.cs index aaf2e997f2..b6094726c0 100644 --- a/osu.Game/Screens/Play/KeyCounterDisplay.cs +++ b/osu.Game/Screens/Play/KeyCounterDisplay.cs @@ -11,7 +11,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Events; using osu.Game.Configuration; -using osu.Game.Screens.Play.HUD; using osuTK; using osuTK.Graphics; @@ -66,7 +65,6 @@ namespace osu.Game.Screens.Play [BackgroundDependencyLoader] private void load(OsuConfigManager config) { - KeysPerSecondCounter.Reset(); config.BindWith(OsuSetting.KeyOverlay, configVisibility); } diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index fb2f556611..88cd197076 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -34,7 +34,7 @@ using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; using osu.Game.Scoring; using osu.Game.Scoring.Legacy; -using osu.Game.Screens.Play.HUD; +using osu.Game.Screens.Play.HUD.KPSCounter; using osu.Game.Screens.Ranking; using osu.Game.Skinning; using osu.Game.Users; @@ -1046,7 +1046,7 @@ namespace osu.Game.Screens.Play fadeOut(); - KeysPerSecondCounter.Reset(); + KeysPerSecondCalculator.GetInstance().Dispose(); return base.OnExiting(e); } From 15fb4d8dd5f3b8ba0b53e34882ef14164c387aa6 Mon Sep 17 00:00:00 2001 From: HiddenNode Date: Fri, 5 Aug 2022 12:53:14 +0100 Subject: [PATCH 061/420] Change Implementation and name of KeepUprightAndUnstretched --- osu.Game/Extensions/DrawableExtensions.cs | 56 +++++++++++------------ 1 file changed, 27 insertions(+), 29 deletions(-) diff --git a/osu.Game/Extensions/DrawableExtensions.cs b/osu.Game/Extensions/DrawableExtensions.cs index 46105218c5..fd5bbab567 100644 --- a/osu.Game/Extensions/DrawableExtensions.cs +++ b/osu.Game/Extensions/DrawableExtensions.cs @@ -81,45 +81,43 @@ namespace osu.Game.Extensions } } + /// /// Keeps the drawable upright and prevents it from being scaled or flipped with its Parent. /// /// The drawable. - public static void KeepUprightAndUnstretched(this Drawable drawable) + public static void KeepUprightAndUnscaled(this Drawable drawable) { - // Fix the rotation - var result = drawable.Parent.DrawInfo; - var scale = result.Matrix.ExtractScale(); - var rotation = new Matrix3( - result.Matrix.Row0 / scale.X, - result.Matrix.Row1 / scale.Y, - new Vector3(0.0f, 0.0f, 1.0f) - ); - rotation.Invert(); - float angle = MathF.Atan(rotation.M12 / rotation.M11); + var parentMatrix = drawable.Parent.DrawInfo.Matrix; + float angle = MathF.Atan(parentMatrix.M12 / parentMatrix.M11); angle *= (360 / (2 * MathF.PI)); - drawable.Rotation = angle; - // Fix the scale (includes flip) - var containerOriginToSpaceOrigin = new Matrix3( - new Vector3(1.0f, 0.0f, 0.0f), - new Vector3(0.0f, 1.0f, 0.0f), - new Vector3(drawable.DrawSize.X / 2, drawable.DrawSize.Y / 2, 1.0f) - ); - var containerOriginToSpaceOriginInverse = containerOriginToSpaceOrigin; - containerOriginToSpaceOriginInverse.Invert(); - Matrix3 rotatedBack = (containerOriginToSpaceOriginInverse * (rotation * (containerOriginToSpaceOrigin * result.Matrix))); + parentMatrix.Transpose(); + parentMatrix.M13 = 0.0f; + parentMatrix.M23 = 0.0f; - bool xFliped = rotation.M11 < 0; - bool yFliped = rotation.M22 < 0; + if ((Math.Abs(Math.Abs(angle) - 90.0)) < 2.0f) + { + Matrix3 m = Matrix3.CreateRotationZ(MathHelper.DegreesToRadians(40.0f)); + m.Transpose(); + parentMatrix *= m; + drawable.Rotation = 40.0f; + } + else + drawable.Rotation = 0.0f; - var rotatedBackScale = rotatedBack.ExtractScale(); + Matrix3 C = parentMatrix.Inverted(); + + float alpha, beta, sx, sy; + sy = C.M22; + alpha = C.M12 / C.M22; + + beta = (C.M21 == 0.0f) ? 0.0f : 1 / ((C.M11 / C.M21) - alpha); + sx = (beta == 0.0f) ? C.M11 : C.M21 / beta; + + drawable.Scale = new Vector2(sx, sy); + drawable.Shear = new Vector2(-alpha, -beta); - drawable.Scale = new Vector2( - (xFliped ? -1 : 1) / rotatedBackScale.X, - (yFliped ? -1 : 1) / rotatedBackScale.Y - ); } - } } From 8618d9ea0d21ea9f67d9f2f3751c3b7cda08f614 Mon Sep 17 00:00:00 2001 From: HiddenNode Date: Fri, 5 Aug 2022 12:55:41 +0100 Subject: [PATCH 062/420] Implement GrowToFitContainer --- .../Graphics/Containers/GrowToFitContainer.cs | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 osu.Game/Graphics/Containers/GrowToFitContainer.cs diff --git a/osu.Game/Graphics/Containers/GrowToFitContainer.cs b/osu.Game/Graphics/Containers/GrowToFitContainer.cs new file mode 100644 index 0000000000..6333718928 --- /dev/null +++ b/osu.Game/Graphics/Containers/GrowToFitContainer.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; +using osu.Framework.Graphics.Containers; + +namespace osu.Game.Graphics.Containers +{ + /// + /// A container that grows in size to fit its child and retains its size when its child shrinks + /// + public class GrowToFitContainer : Container + { + protected override void Update() + { + base.Update(); + Height = Math.Max(Child.Height, Height); + Width = Math.Max(Child.Width, Width); + } + } + +} From 12ef99a1a1534c255138eab4c336f385e145c144 Mon Sep 17 00:00:00 2001 From: HiddenNode Date: Fri, 5 Aug 2022 12:56:08 +0100 Subject: [PATCH 063/420] Fix text position --- osu.Game/Screens/Play/HUD/SongProgressInfo.cs | 49 +++++++++++-------- 1 file changed, 29 insertions(+), 20 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/SongProgressInfo.cs b/osu.Game/Screens/Play/HUD/SongProgressInfo.cs index 656498fc43..8c9f3aaa8d 100644 --- a/osu.Game/Screens/Play/HUD/SongProgressInfo.cs +++ b/osu.Game/Screens/Play/HUD/SongProgressInfo.cs @@ -5,15 +5,22 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Containers; using osu.Game.Graphics; +using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using System; + namespace osu.Game.Screens.Play.HUD { public class SongProgressInfo : Container { + private GrowToFitContainer timeCurrentContainer; + private GrowToFitContainer timeLeftContainer; + private GrowToFitContainer progressContainer; + private OsuSpriteText timeCurrent; private OsuSpriteText timeLeft; private OsuSpriteText progress; @@ -51,12 +58,14 @@ namespace osu.Game.Screens.Play.HUD { new Container { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, AutoSizeAxes = Axes.Both, - Children = new Drawable[] + Child = timeCurrentContainer = new GrowToFitContainer { - timeCurrent = new OsuSpriteText + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + Child = timeCurrent = new OsuSpriteText { Origin = Anchor.Centre, Anchor = Anchor.Centre, @@ -67,37 +76,37 @@ namespace osu.Game.Screens.Play.HUD }, new Container { - Origin = Anchor.BottomCentre, - Anchor = Anchor.BottomCentre, + Origin = Anchor.Centre, + Anchor = Anchor.Centre, AutoSizeAxes = Axes.Both, - Children = new Drawable[] + Child = timeLeftContainer = new GrowToFitContainer { - progress = new OsuSpriteText + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + Child = progress = new OsuSpriteText { Origin = Anchor.Centre, Anchor = Anchor.Centre, Colour = colours.BlueLighter, Font = OsuFont.Numeric, - } + } } }, new Container { - Origin = Anchor.BottomRight, - Anchor = Anchor.BottomRight, + Origin = Anchor.CentreRight, + Anchor = Anchor.CentreRight, AutoSizeAxes = Axes.Both, - Children = new Drawable[] + Child = progressContainer = new GrowToFitContainer { - timeLeft = new OsuSpriteText + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + Child = timeLeft = new OsuSpriteText { Origin = Anchor.Centre, Anchor = Anchor.Centre, Colour = colours.BlueLighter, Font = OsuFont.Numeric, - Margin = new MarginPadding - { - Right = margin, - }, } } } @@ -139,9 +148,9 @@ namespace osu.Game.Screens.Play.HUD private void keepTextSpritesUpright() { - timeCurrent.OnUpdate += (timeCurrent) => { Extensions.DrawableExtensions.KeepUprightAndUnstretched(timeCurrent); }; - progress.OnUpdate += (timeCurrent) => { Extensions.DrawableExtensions.KeepUprightAndUnstretched(timeCurrent); }; - timeLeft.OnUpdate += (timeCurrent) => { Extensions.DrawableExtensions.KeepUprightAndUnstretched(timeCurrent); }; + timeCurrentContainer.OnUpdate += (timeCurrentContainer) => { Extensions.DrawableExtensions.KeepUprightAndUnscaled(timeCurrentContainer); }; + progressContainer.OnUpdate += (progressContainer) => { Extensions.DrawableExtensions.KeepUprightAndUnscaled(progressContainer); }; + timeLeftContainer.OnUpdate += (timeLeftContainer) => { Extensions.DrawableExtensions.KeepUprightAndUnscaled(timeLeftContainer); }; } } From 0243f8d6ac0f97716f017468075c6a0ede2aa220 Mon Sep 17 00:00:00 2001 From: HiddenNode Date: Fri, 5 Aug 2022 14:28:15 +0100 Subject: [PATCH 064/420] Clean up --- osu.Game/Extensions/DrawableExtensions.cs | 4 +--- osu.Game/Graphics/Containers/GrowToFitContainer.cs | 1 - osu.Game/Screens/Play/HUD/SongProgressInfo.cs | 8 +++----- 3 files changed, 4 insertions(+), 9 deletions(-) diff --git a/osu.Game/Extensions/DrawableExtensions.cs b/osu.Game/Extensions/DrawableExtensions.cs index fd5bbab567..f587b1c55b 100644 --- a/osu.Game/Extensions/DrawableExtensions.cs +++ b/osu.Game/Extensions/DrawableExtensions.cs @@ -81,7 +81,6 @@ namespace osu.Game.Extensions } } - /// /// Keeps the drawable upright and prevents it from being scaled or flipped with its Parent. /// @@ -90,7 +89,7 @@ namespace osu.Game.Extensions { var parentMatrix = drawable.Parent.DrawInfo.Matrix; float angle = MathF.Atan(parentMatrix.M12 / parentMatrix.M11); - angle *= (360 / (2 * MathF.PI)); + angle = MathHelper.RadiansToDegrees(angle); parentMatrix.Transpose(); parentMatrix.M13 = 0.0f; @@ -117,7 +116,6 @@ namespace osu.Game.Extensions drawable.Scale = new Vector2(sx, sy); drawable.Shear = new Vector2(-alpha, -beta); - } } } diff --git a/osu.Game/Graphics/Containers/GrowToFitContainer.cs b/osu.Game/Graphics/Containers/GrowToFitContainer.cs index 6333718928..9b4ad0dba9 100644 --- a/osu.Game/Graphics/Containers/GrowToFitContainer.cs +++ b/osu.Game/Graphics/Containers/GrowToFitContainer.cs @@ -18,5 +18,4 @@ namespace osu.Game.Graphics.Containers Width = Math.Max(Child.Width, Width); } } - } diff --git a/osu.Game/Screens/Play/HUD/SongProgressInfo.cs b/osu.Game/Screens/Play/HUD/SongProgressInfo.cs index 8c9f3aaa8d..34f773509a 100644 --- a/osu.Game/Screens/Play/HUD/SongProgressInfo.cs +++ b/osu.Game/Screens/Play/HUD/SongProgressInfo.cs @@ -5,7 +5,6 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; -using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Containers; using osu.Game.Graphics; using osu.Game.Graphics.Containers; @@ -148,10 +147,9 @@ namespace osu.Game.Screens.Play.HUD private void keepTextSpritesUpright() { - timeCurrentContainer.OnUpdate += (timeCurrentContainer) => { Extensions.DrawableExtensions.KeepUprightAndUnscaled(timeCurrentContainer); }; - progressContainer.OnUpdate += (progressContainer) => { Extensions.DrawableExtensions.KeepUprightAndUnscaled(progressContainer); }; - timeLeftContainer.OnUpdate += (timeLeftContainer) => { Extensions.DrawableExtensions.KeepUprightAndUnscaled(timeLeftContainer); }; + timeCurrentContainer.OnUpdate += Extensions.DrawableExtensions.KeepUprightAndUnscaled; + progressContainer.OnUpdate += Extensions.DrawableExtensions.KeepUprightAndUnscaled; + timeLeftContainer.OnUpdate += Extensions.DrawableExtensions.KeepUprightAndUnscaled; } - } } From 24c29b7e2f5d6ec717a056db1149099c1632df46 Mon Sep 17 00:00:00 2001 From: Ryuki Date: Fri, 5 Aug 2022 15:51:07 +0200 Subject: [PATCH 065/420] Do not add KPS calculation when gameplay rate is 0 --- osu.Game/Screens/Play/HUD/KPSCounter/KeysPerSecondCalculator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/HUD/KPSCounter/KeysPerSecondCalculator.cs b/osu.Game/Screens/Play/HUD/KPSCounter/KeysPerSecondCalculator.cs index f9839abde4..5ac3647e0e 100644 --- a/osu.Game/Screens/Play/HUD/KPSCounter/KeysPerSecondCalculator.cs +++ b/osu.Game/Screens/Play/HUD/KPSCounter/KeysPerSecondCalculator.cs @@ -64,7 +64,7 @@ namespace osu.Game.Screens.Play.HUD.KPSCounter private void addTimestamp() { - if (workingClock != null && workingClock.CurrentTime >= maxTime) + if (workingClock != null && workingClock.CurrentTime >= maxTime && gameplayClock.TrueGameplayRate > 0) { timestamps.Add(workingClock.CurrentTime); maxTime = workingClock.CurrentTime; From b4e0fa7c53cf3d1a8d580562c8301a6e70d4d2bc Mon Sep 17 00:00:00 2001 From: Ryuki Date: Fri, 5 Aug 2022 15:53:06 +0200 Subject: [PATCH 066/420] Rewrite tests for KPS --- .../Gameplay/TestSceneKeyPerSecondCounter.cs | 91 -------------- .../Gameplay/TestSceneKeysPerSecondCounter.cs | 114 ++++++++++-------- 2 files changed, 66 insertions(+), 139 deletions(-) delete mode 100644 osu.Game.Tests/Visual/Gameplay/TestSceneKeyPerSecondCounter.cs diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneKeyPerSecondCounter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneKeyPerSecondCounter.cs deleted file mode 100644 index a2eaea29eb..0000000000 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneKeyPerSecondCounter.cs +++ /dev/null @@ -1,91 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -#nullable disable - -using NUnit.Framework; -using osu.Framework.Graphics; -using osu.Framework.Testing; -using osu.Game.Rulesets; -using osu.Game.Rulesets.Mania; -using osu.Game.Rulesets.UI; -using osu.Game.Screens.Play; -using osu.Game.Screens.Play.HUD.KPSCounter; -using osuTK; - -namespace osu.Game.Tests.Visual.Gameplay -{ - public class TestSceneKeyPerSecondCounter : PlayerTestScene - { - protected override Ruleset CreatePlayerRuleset() => new ManiaRuleset(); - protected override bool HasCustomSteps => false; - protected override bool Autoplay => true; - - private GameplayClock gameplayClock; - private DrawableRuleset drawableRuleset; - - // private DependencyProvidingContainer dependencyContainer; - private KeysPerSecondCounter counter; - - [SetUpSteps] - public new void SetUpSteps() - { - /* - CreateTest(() => AddStep("Create components", () => - { - Logger.Log($"{(Player != null ? Player.ToString() : "null")}", level: LogLevel.Debug); - dependencyContainer = new DependencyProvidingContainer - { - RelativePositionAxes = Axes.Both, - }; - })); - */ - } - - private void createCounter() - { - AddStep("Create counter", () => - { - /* - if (!Contains(dependencyContainer)) - { - Add(dependencyContainer); - } - - if (dependencyContainer.CachedDependencies.Length == 0) - { - dependencyContainer.CachedDependencies = new (Type, object)[] - { - (typeof(GameplayClock), , - (typeof(DrawableRuleset),) - }; - } - Dependencies.Cache(gameplayClock = Player.GameplayClockContainer.GameplayClock)); - */ - - Dependencies.Cache(gameplayClock = Player.GameplayClockContainer.GameplayClock); - Dependencies.Cache(drawableRuleset = Player.DrawableRuleset); - - Add(counter = new KeysPerSecondCounter - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - Scale = new Vector2(5), - Position = new Vector2(10, 100) - } - ); - }); - AddAssert("ensure counter added", () => Contains(counter)); - } - - [Test] - public void TestInGameTimeConsistency() - { - createCounter(); - - AddUntilStep("Wait until first note", () => counter.Current.Value != 0); - AddStep("Pause gameplay", () => gameplayClock.IsPaused.Value = true); - AddAssert("KPS = 1", () => counter.Current.Value == 1); - } - } -} diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneKeysPerSecondCounter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneKeysPerSecondCounter.cs index c8c31d1366..0cc9b91d71 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneKeysPerSecondCounter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneKeysPerSecondCounter.cs @@ -3,9 +3,15 @@ #nullable disable +using System.Linq; +using AutoMapper.Internal; using NUnit.Framework; +using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; -using osu.Framework.Testing; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Mania; +using osu.Game.Rulesets.Mania.Mods; +using osu.Game.Rulesets.UI; using osu.Game.Screens.Play; using osu.Game.Screens.Play.HUD.KPSCounter; using osuTK; @@ -13,63 +19,75 @@ using osuTK.Input; namespace osu.Game.Tests.Visual.Gameplay { - public class TestSceneKeysPerSecondCounter : OsuManualInputManagerTestScene + public class TestSceneKeysPerSecondCounter : PlayerTestScene { + protected override Ruleset CreatePlayerRuleset() => new ManiaRuleset(); + protected override bool HasCustomSteps => false; + protected override bool Autoplay => false; + protected override TestPlayer CreatePlayer(Ruleset ruleset) => new TestPlayer(true, false); + + private GameplayClock gameplayClock; + private DrawableRuleset drawableRuleset; + private KeysPerSecondCounter counter; - [SetUpSteps] - public void Setup() + private void createCounter() + { + AddStep("Create counter", () => + { + gameplayClock = Player.GameplayClockContainer.GameplayClock; + drawableRuleset = Player.DrawableRuleset; + + Player.HUDOverlay.Add(counter = new KeysPerSecondCounter + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Scale = new Vector2(5), + }); + counter.SmoothingTime.Value = 0; + }); + AddUntilStep("Counter created", () => Player.HUDOverlay.Contains(counter)); + } + + [Test] + public void TestBasic() { createCounter(); - } - private void createCounter() => AddStep("Create counter", () => - { - Child = counter = new KeysPerSecondCounter + AddStep("press 1 key", () => InputManager.Key(Key.D)); + AddAssert("KPS = 1", () => counter.Current.Value == 1); + AddUntilStep("Wait for KPS cooldown", () => counter.Current.Value <= 0); + AddStep("press 4 keys", () => { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Scale = new Vector2(5) - }; - }); - - [Test] - public void TestManualTrigger() - { - AddAssert("Counter = 0", () => counter.Current.Value == 0); - AddRepeatStep("manual trigger", KeysPerSecondCalculator.AddInput, 20); - AddAssert("Counter is not 0", () => counter.Current.Value > 0); - } - - [Test] - public void TestKpsAsideKeyCounter() - { - AddStep("Create key counter display", () => - Add(new KeyCounterDisplay + InputManager.Key(Key.D); + InputManager.Key(Key.F); + InputManager.Key(Key.J); + InputManager.Key(Key.K); + }); + AddAssert("KPS = 4", () => counter.Current.Value == 4); + AddStep("Pause player", () => Player.Pause()); + AddAssert("KPS = 4", () => counter.Current.Value == 4); + AddStep("Resume player", () => Player.Resume()); + AddStep("press 4 keys", () => + { + InputManager.Key(Key.D); + InputManager.Key(Key.F); + InputManager.Key(Key.J); + InputManager.Key(Key.K); + }); + AddAssert("KPS = 8", () => counter.Current.Value == 8); + AddUntilStep("Wait for KPS cooldown", () => counter.Current.Value <= 0); + AddStep("Add DT", () => + { + var dt = new ManiaModDoubleTime { - Origin = Anchor.BottomCentre, - Anchor = Anchor.BottomCentre, - Y = 100, - Children = new KeyCounter[] + SpeedChange = { - new KeyCounterKeyboard(Key.W), - new KeyCounterKeyboard(Key.X), - new KeyCounterKeyboard(Key.C), - new KeyCounterKeyboard(Key.V) + Value = 2 } - }) - ); - AddAssert("Counter = 0", () => counter.Current.Value == 0); - addPressKeyStep(Key.W); - addPressKeyStep(Key.X); - addPressKeyStep(Key.C); - addPressKeyStep(Key.V); - AddAssert("Counter = 4", () => counter.Current.Value == 4); - } - - private void addPressKeyStep(Key key) - { - AddStep($"Press {key} key", () => InputManager.Key(key)); + }; + Player.Mods.Value.Concat((dt.Yield()).ToArray()); + }); } } } From b46bc5d65b9e4441bb3dc4509f4ff328317f22b6 Mon Sep 17 00:00:00 2001 From: HiddenNode Date: Fri, 5 Aug 2022 14:57:33 +0100 Subject: [PATCH 067/420] Remove empty line --- osu.Game/Screens/Play/HUD/SongProgressInfo.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Screens/Play/HUD/SongProgressInfo.cs b/osu.Game/Screens/Play/HUD/SongProgressInfo.cs index 34f773509a..e9d0ffe4b9 100644 --- a/osu.Game/Screens/Play/HUD/SongProgressInfo.cs +++ b/osu.Game/Screens/Play/HUD/SongProgressInfo.cs @@ -11,7 +11,6 @@ using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using System; - namespace osu.Game.Screens.Play.HUD { public class SongProgressInfo : Container From e0426836c19391139a2976ea364ed0122c94aa77 Mon Sep 17 00:00:00 2001 From: sw1tchbl4d3 Date: Fri, 5 Aug 2022 16:30:07 +0200 Subject: [PATCH 068/420] Make swells and drumrolls optional by default --- .../Mods/TestSceneTaikoModClassic.cs | 80 ---------- .../Mods/TestSceneTaikoModPerfect.cs | 4 +- .../TestSceneTaikoSuddenDeath.cs | 4 +- .../Judgements/TaikoDrumRollJudgement.cs | 13 +- .../Judgements/TaikoDrumRollTickJudgement.cs | 14 +- .../Judgements/TaikoSwellJudgement.cs | 7 +- .../Mods/TaikoModClassic.cs | 151 +----------------- .../Objects/Drawables/DrawableDrumRoll.cs | 13 +- .../Objects/Drawables/DrawableSwell.cs | 10 +- osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs | 33 +--- osu.Game.Rulesets.Taiko/TaikoRuleset.cs | 3 - 11 files changed, 24 insertions(+), 308 deletions(-) delete mode 100644 osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModClassic.cs diff --git a/osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModClassic.cs b/osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModClassic.cs deleted file mode 100644 index 9028f411ce..0000000000 --- a/osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModClassic.cs +++ /dev/null @@ -1,80 +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 NUnit.Framework; -using osu.Game.Rulesets.Replays; -using osu.Game.Rulesets.Taiko.Beatmaps; -using osu.Game.Rulesets.Taiko.Mods; -using osu.Game.Rulesets.Taiko.Objects; -using osu.Game.Rulesets.Taiko.Replays; - -namespace osu.Game.Rulesets.Taiko.Tests.Mods -{ - public class TestSceneTaikoModClassic : TaikoModTestScene - { - [Test] - public void TestHittingDrumRollsIsOptional() => CreateModTest(new ModTestData - { - Mod = new TaikoModClassic(), - Autoplay = false, - Beatmap = new TaikoBeatmap - { - BeatmapInfo = { Ruleset = CreatePlayerRuleset().RulesetInfo }, - HitObjects = new List - { - new Hit - { - StartTime = 1000, - Type = HitType.Centre - }, - new DrumRoll - { - StartTime = 3000, - EndTime = 6000 - } - } - }, - ReplayFrames = new List - { - new TaikoReplayFrame(1000, TaikoAction.LeftCentre), - new TaikoReplayFrame(1001) - }, - PassCondition = () => Player.ScoreProcessor.HasCompleted.Value - && Player.ScoreProcessor.Combo.Value == 1 - && Player.ScoreProcessor.Accuracy.Value == 1 - }); - - [Test] - public void TestHittingSwellsIsOptional() => CreateModTest(new ModTestData - { - Mod = new TaikoModClassic(), - Autoplay = false, - Beatmap = new TaikoBeatmap - { - BeatmapInfo = { Ruleset = CreatePlayerRuleset().RulesetInfo }, - HitObjects = new List - { - new Hit - { - StartTime = 1000, - Type = HitType.Centre - }, - new Swell - { - StartTime = 3000, - EndTime = 6000 - } - } - }, - ReplayFrames = new List - { - new TaikoReplayFrame(1000, TaikoAction.LeftCentre), - new TaikoReplayFrame(1001) - }, - PassCondition = () => Player.ScoreProcessor.HasCompleted.Value - && Player.ScoreProcessor.Combo.Value == 1 - && Player.ScoreProcessor.Accuracy.Value == 1 - }); - } -} diff --git a/osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModPerfect.cs b/osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModPerfect.cs index a83cc16413..92503a9f03 100644 --- a/osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModPerfect.cs +++ b/osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModPerfect.cs @@ -25,11 +25,11 @@ namespace osu.Game.Rulesets.Taiko.Tests.Mods [TestCase(false)] [TestCase(true)] - public void TestDrumRoll(bool shouldMiss) => CreateHitObjectTest(new HitObjectTestData(new DrumRoll { StartTime = 1000, EndTime = 3000 }), shouldMiss); + public void TestDrumRoll(bool shouldMiss) => CreateHitObjectTest(new HitObjectTestData(new DrumRoll { StartTime = 1000, EndTime = 3000 }, false), shouldMiss); [TestCase(false)] [TestCase(true)] - public void TestSwell(bool shouldMiss) => CreateHitObjectTest(new HitObjectTestData(new Swell { StartTime = 1000, EndTime = 3000 }), shouldMiss); + public void TestSwell(bool shouldMiss) => CreateHitObjectTest(new HitObjectTestData(new Swell { StartTime = 1000, EndTime = 3000 }, false), shouldMiss); private class TestTaikoRuleset : TaikoRuleset { diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoSuddenDeath.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoSuddenDeath.cs index cdfab4a215..2169ac5581 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoSuddenDeath.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoSuddenDeath.cs @@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Taiko.Tests }; [Test] - public void TestSpinnerDoesFail() + public void TestSwellDoesNotFail() { bool judged = false; AddStep("Setup judgements", () => @@ -47,7 +47,7 @@ namespace osu.Game.Rulesets.Taiko.Tests Player.ScoreProcessor.NewJudgement += _ => judged = true; }); AddUntilStep("swell judged", () => judged); - AddAssert("failed", () => Player.GameplayState.HasFailed); + AddAssert("not failed", () => !Player.GameplayState.HasFailed); } } } diff --git a/osu.Game.Rulesets.Taiko/Judgements/TaikoDrumRollJudgement.cs b/osu.Game.Rulesets.Taiko/Judgements/TaikoDrumRollJudgement.cs index be128d85b5..f7f923e76e 100644 --- a/osu.Game.Rulesets.Taiko/Judgements/TaikoDrumRollJudgement.cs +++ b/osu.Game.Rulesets.Taiko/Judgements/TaikoDrumRollJudgement.cs @@ -9,17 +9,8 @@ namespace osu.Game.Rulesets.Taiko.Judgements { public class TaikoDrumRollJudgement : TaikoJudgement { - protected override double HealthIncreaseFor(HitResult result) - { - // Drum rolls can be ignored with no health penalty - switch (result) - { - case HitResult.Miss: - return 0; + public override HitResult MaxResult => HitResult.IgnoreHit; - default: - return base.HealthIncreaseFor(result); - } - } + protected override double HealthIncreaseFor(HitResult result) => 0; } } diff --git a/osu.Game.Rulesets.Taiko/Judgements/TaikoDrumRollTickJudgement.cs b/osu.Game.Rulesets.Taiko/Judgements/TaikoDrumRollTickJudgement.cs index 5f2587a5d5..de56c76f56 100644 --- a/osu.Game.Rulesets.Taiko/Judgements/TaikoDrumRollTickJudgement.cs +++ b/osu.Game.Rulesets.Taiko/Judgements/TaikoDrumRollTickJudgement.cs @@ -9,18 +9,8 @@ namespace osu.Game.Rulesets.Taiko.Judgements { public class TaikoDrumRollTickJudgement : TaikoJudgement { - public override HitResult MaxResult => HitResult.SmallTickHit; + public override HitResult MaxResult => HitResult.SmallBonus; - protected override double HealthIncreaseFor(HitResult result) - { - switch (result) - { - case HitResult.SmallTickHit: - return 0.15; - - default: - return 0; - } - } + protected override double HealthIncreaseFor(HitResult result) => 0; } } diff --git a/osu.Game.Rulesets.Taiko/Judgements/TaikoSwellJudgement.cs b/osu.Game.Rulesets.Taiko/Judgements/TaikoSwellJudgement.cs index 6d469bd1d7..146621997d 100644 --- a/osu.Game.Rulesets.Taiko/Judgements/TaikoSwellJudgement.cs +++ b/osu.Game.Rulesets.Taiko/Judgements/TaikoSwellJudgement.cs @@ -9,16 +9,13 @@ namespace osu.Game.Rulesets.Taiko.Judgements { public class TaikoSwellJudgement : TaikoJudgement { - /// - /// The to grant when the player has hit more than half of swell ticks. - /// - public virtual HitResult PartialCompletionResult => HitResult.Ok; + public override HitResult MaxResult => HitResult.LargeBonus; protected override double HealthIncreaseFor(HitResult result) { switch (result) { - case HitResult.Miss: + case HitResult.IgnoreMiss: return -0.65; default: diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModClassic.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModClassic.cs index 28124d16e1..f7fdd447d6 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModClassic.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModClassic.cs @@ -1,170 +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 System.Collections.Generic; using System.Diagnostics; -using System.Threading; -using osu.Game.Beatmaps; -using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Scoring; -using osu.Game.Rulesets.Taiko.Beatmaps; -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.Rulesets.UI; namespace osu.Game.Rulesets.Taiko.Mods { - public class TaikoModClassic : ModClassic, IApplicableToDrawableRuleset, IUpdatableByPlayfield, IApplicableAfterBeatmapConversion + public class TaikoModClassic : ModClassic, IApplicableToDrawableRuleset, IUpdatableByPlayfield { private DrawableTaikoRuleset? drawableTaikoRuleset; public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) { - var playfield = (TaikoPlayfield)drawableRuleset.Playfield; - playfield.ClassicHitTargetPosition.Value = true; - drawableTaikoRuleset = (DrawableTaikoRuleset)drawableRuleset; drawableTaikoRuleset.LockPlayfieldAspect.Value = false; - drawableTaikoRuleset.Playfield.RegisterPool(5); - drawableTaikoRuleset.Playfield.RegisterPool(100); - drawableTaikoRuleset.Playfield.RegisterPool(5); + var playfield = (TaikoPlayfield)drawableRuleset.Playfield; + playfield.ClassicHitTargetPosition.Value = true; } - public void ApplyToBeatmap(IBeatmap beatmap) - { - var taikoBeatmap = (TaikoBeatmap)beatmap; - - if (taikoBeatmap.HitObjects.Count == 0) return; - - var hitObjects = taikoBeatmap.HitObjects.Select(ho => - { - switch (ho) - { - case DrumRoll drumRoll: - return new ClassicDrumRoll(drumRoll); - - case Swell swell: - return new ClassicSwell(swell); - - default: - return ho; - } - }).ToList(); - - taikoBeatmap.HitObjects = hitObjects; - } - - #region Classic drum roll - - private class TaikoClassicDrumRollJudgement : TaikoDrumRollJudgement - { - public override HitResult MaxResult => HitResult.IgnoreHit; - } - - private class ClassicDrumRoll : DrumRoll - { - public ClassicDrumRoll(DrumRoll original) - { - StartTime = original.StartTime; - Samples = original.Samples; - EndTime = original.EndTime; - Duration = original.Duration; - TickRate = original.TickRate; - RequiredGoodHits = original.RequiredGoodHits; - RequiredGreatHits = original.RequiredGreatHits; - } - - public override Judgement CreateJudgement() => new TaikoClassicDrumRollJudgement(); - - protected override List CreateTicks(CancellationToken cancellationToken) - { - List oldTicks = base.CreateTicks(cancellationToken); - - List newTicks = oldTicks.Select(oldTick => - { - if (oldTick is DrumRollTick drumRollTick) - { - return new ClassicDrumRollTick(drumRollTick); - } - - return oldTick; - }).ToList(); - - return newTicks; - } - } - - private class TaikoClassicDrumRollTickJudgement : TaikoDrumRollTickJudgement - { - public override HitResult MaxResult => HitResult.SmallBonus; - } - - private class ClassicDrumRollTick : DrumRollTick - { - public override Judgement CreateJudgement() => new TaikoClassicDrumRollTickJudgement(); - - public ClassicDrumRollTick(DrumRollTick original) - { - StartTime = original.StartTime; - Samples = original.Samples; - FirstTick = original.FirstTick; - TickSpacing = original.TickSpacing; - } - } - - private class ClassicDrawableDrumRoll : DrawableDrumRoll - { - public override bool DisplayResult => false; - - protected override void CheckForResult(bool userTriggered, double timeOffset) - { - if (userTriggered) - return; - - if (timeOffset < 0) - return; - - ApplyResult(r => r.Type = HitResult.IgnoreHit); - } - } - - #endregion - - #region Classic swell - - private class TaikoClassicSwellJudgement : TaikoSwellJudgement - { - public override HitResult MaxResult => HitResult.LargeBonus; - - public override HitResult PartialCompletionResult => HitResult.SmallBonus; - } - - private class ClassicSwell : Swell - { - public ClassicSwell(Swell original) - { - StartTime = original.StartTime; - Samples = original.Samples; - EndTime = original.EndTime; - Duration = original.Duration; - RequiredHits = original.RequiredHits; - } - - public override Judgement CreateJudgement() => new TaikoClassicSwellJudgement(); - } - - private class ClassicDrawableSwell : DrawableSwell - { - public override bool DisplayResult => false; - } - - #endregion - public void Update(Playfield playfield) { Debug.Assert(drawableTaikoRuleset != null); @@ -172,8 +29,6 @@ namespace osu.Game.Rulesets.Taiko.Mods // Classic taiko scrolls at a constant 100px per 1000ms. More notes become visible as the playfield is lengthened. const float scroll_rate = 10; - Debug.Assert(drawableTaikoRuleset != null); - // Since the time range will depend on a positional value, it is referenced to the x480 pixel space. float ratio = drawableTaikoRuleset.DrawHeight / 480; diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs index 04ed6d0b87..418c4673e2 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs @@ -4,7 +4,6 @@ #nullable disable using System; -using System.Linq; using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Utils; @@ -16,7 +15,6 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Input.Events; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects; -using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Taiko.Skinning.Default; using osu.Game.Skinning; using osuTK; @@ -40,6 +38,8 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables private Color4 colourIdle; private Color4 colourEngaged; + public override bool DisplayResult => false; + public DrawableDrumRoll() : this(null) { @@ -140,14 +140,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables if (timeOffset < 0) return; - int countHit = NestedHitObjects.Count(o => o.IsHit); - - if (countHit >= HitObject.RequiredGoodHits) - { - ApplyResult(r => r.Type = countHit >= HitObject.RequiredGreatHits ? HitResult.Great : HitResult.Ok); - } - else - ApplyResult(r => r.Type = r.Judgement.MinResult); + ApplyResult(r => r.Type = r.Judgement.MinResult); } protected override void UpdateHitStateTransforms(ArmedState state) diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs index a90e9ce676..84edb30890 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs @@ -16,7 +16,7 @@ using osuTK.Graphics; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; using osu.Game.Rulesets.Objects; -using osu.Game.Rulesets.Taiko.Judgements; +using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Taiko.Skinning.Default; using osu.Game.Skinning; @@ -39,6 +39,8 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables private readonly CircularContainer targetRing; private readonly CircularContainer expandingRing; + public override bool DisplayResult => false; + public DrawableSwell() : this(null) { @@ -222,11 +224,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables tick.TriggerResult(false); } - ApplyResult(r => - { - var swellJudgement = (TaikoSwellJudgement)r.Judgement; - r.Type = numHits > HitObject.RequiredHits / 2 ? swellJudgement.PartialCompletionResult : swellJudgement.MinResult; - }); + ApplyResult(r => r.Type = numHits > HitObject.RequiredHits / 2 ? HitResult.SmallBonus : r.Judgement.MinResult); } } diff --git a/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs b/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs index 21c7f13fec..db752c6691 100644 --- a/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs +++ b/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs @@ -4,8 +4,6 @@ #nullable disable using osu.Game.Rulesets.Objects.Types; -using System; -using System.Collections.Generic; using System.Threading; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; @@ -43,24 +41,12 @@ namespace osu.Game.Rulesets.Taiko.Objects /// public int TickRate = 1; - /// - /// Number of drum roll ticks required for a "Good" hit. - /// - public double RequiredGoodHits { get; protected set; } - - /// - /// Number of drum roll ticks required for a "Great" hit. - /// - public double RequiredGreatHits { get; protected set; } - /// /// The length (in milliseconds) between ticks of this drumroll. /// Half of this value is the hit window of the ticks. /// private double tickSpacing = 100; - private float overallDifficulty = BeatmapDifficulty.DEFAULT_DIFFICULTY; - protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, IBeatmapDifficultyInfo difficulty) { base.ApplyDefaultsToSelf(controlPointInfo, difficulty); @@ -71,28 +57,19 @@ namespace osu.Game.Rulesets.Taiko.Objects Velocity = scoringDistance / timingPoint.BeatLength; tickSpacing = timingPoint.BeatLength / TickRate; - overallDifficulty = difficulty.OverallDifficulty; } protected override void CreateNestedHitObjects(CancellationToken cancellationToken) { - foreach (TaikoHitObject tick in CreateTicks(cancellationToken)) - { - AddNested(tick); - } - - RequiredGoodHits = NestedHitObjects.Count * Math.Min(0.15, 0.05 + 0.10 / 6 * overallDifficulty); - RequiredGreatHits = NestedHitObjects.Count * Math.Min(0.30, 0.10 + 0.20 / 6 * overallDifficulty); + createTicks(cancellationToken); base.CreateNestedHitObjects(cancellationToken); } - protected virtual List CreateTicks(CancellationToken cancellationToken) + private void createTicks(CancellationToken cancellationToken) { - List ticks = new List(); - if (tickSpacing == 0) - return ticks; + return; bool first = true; @@ -100,7 +77,7 @@ namespace osu.Game.Rulesets.Taiko.Objects { cancellationToken.ThrowIfCancellationRequested(); - ticks.Add(new DrumRollTick + AddNested(new DrumRollTick { FirstTick = first, TickSpacing = tickSpacing, @@ -110,8 +87,6 @@ namespace osu.Game.Rulesets.Taiko.Objects first = false; } - - return ticks; } public override Judgement CreateJudgement() => new TaikoDrumRollJudgement(); diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index eb0706f131..499bb8cf1d 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -196,9 +196,6 @@ namespace osu.Game.Rulesets.Taiko { switch (result) { - case HitResult.SmallTickHit: - return "drum tick"; - case HitResult.SmallBonus: return "bonus"; } From 0c07df2c267c36fb1c5ac5cf4dae095b94d01521 Mon Sep 17 00:00:00 2001 From: Ryuki Date: Fri, 5 Aug 2022 17:04:33 +0200 Subject: [PATCH 069/420] Remove DT from KPS test --- .../Gameplay/TestSceneKeysPerSecondCounter.cs | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneKeysPerSecondCounter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneKeysPerSecondCounter.cs index 0cc9b91d71..6e59c53a1f 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneKeysPerSecondCounter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneKeysPerSecondCounter.cs @@ -3,14 +3,10 @@ #nullable disable -using System.Linq; -using AutoMapper.Internal; using NUnit.Framework; -using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Game.Rulesets; using osu.Game.Rulesets.Mania; -using osu.Game.Rulesets.Mania.Mods; using osu.Game.Rulesets.UI; using osu.Game.Screens.Play; using osu.Game.Screens.Play.HUD.KPSCounter; @@ -76,18 +72,6 @@ namespace osu.Game.Tests.Visual.Gameplay InputManager.Key(Key.K); }); AddAssert("KPS = 8", () => counter.Current.Value == 8); - AddUntilStep("Wait for KPS cooldown", () => counter.Current.Value <= 0); - AddStep("Add DT", () => - { - var dt = new ManiaModDoubleTime - { - SpeedChange = - { - Value = 2 - } - }; - Player.Mods.Value.Concat((dt.Yield()).ToArray()); - }); } } } From 0886137e39b965571143387cd44b516b185fd189 Mon Sep 17 00:00:00 2001 From: Ryuki Date: Fri, 5 Aug 2022 16:31:20 +0200 Subject: [PATCH 070/420] Prevent KeysPerSecondCounter from NRE when no instance is initialized --- .../Play/HUD/KPSCounter/KeysPerSecondCalculator.cs | 8 ++++---- osu.Game/Screens/Play/Player.cs | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/KPSCounter/KeysPerSecondCalculator.cs b/osu.Game/Screens/Play/HUD/KPSCounter/KeysPerSecondCalculator.cs index 5ac3647e0e..a29f4c9706 100644 --- a/osu.Game/Screens/Play/HUD/KPSCounter/KeysPerSecondCalculator.cs +++ b/osu.Game/Screens/Play/HUD/KPSCounter/KeysPerSecondCalculator.cs @@ -18,7 +18,7 @@ namespace osu.Game.Screens.Play.HUD.KPSCounter public static void AddInput() { - instance?.onNewInput.Invoke(); + instance?.onNewInput?.Invoke(); } public static KeysPerSecondCalculator GetInstance(GameplayClock gameplayClock = null, DrawableRuleset drawableRuleset = null) @@ -41,9 +41,9 @@ namespace osu.Game.Screens.Play.HUD.KPSCounter private event Action onNewInput; - private IClock workingClock => (IClock)drawableRuleset.FrameStableClock ?? gameplayClock; + private IClock workingClock => (IClock)drawableRuleset?.FrameStableClock ?? gameplayClock; - // Having the rate from mods is preffered to using GameplayClock.TrueGameplayRate() + // Having the rate from mods is preferred to using GameplayClock.TrueGameplayRate() // as it returns 0 when paused in replays, not useful for players who want to "analyze" a replay. private double rate => (drawableRuleset.Mods.FirstOrDefault(m => m is ModRateAdjust) as ModRateAdjust)?.SpeedChange.Value ?? 1; @@ -64,7 +64,7 @@ namespace osu.Game.Screens.Play.HUD.KPSCounter private void addTimestamp() { - if (workingClock != null && workingClock.CurrentTime >= maxTime && gameplayClock.TrueGameplayRate > 0) + if (Ready && workingClock.CurrentTime >= maxTime && gameplayClock.TrueGameplayRate > 0) { timestamps.Add(workingClock.CurrentTime); maxTime = workingClock.CurrentTime; diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 88cd197076..70b1dc9a41 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -1046,7 +1046,7 @@ namespace osu.Game.Screens.Play fadeOut(); - KeysPerSecondCalculator.GetInstance().Dispose(); + KeysPerSecondCalculator.GetInstance()?.Dispose(); return base.OnExiting(e); } From b2557a8d2d4ca1ecde0cc6b709085df912316ede Mon Sep 17 00:00:00 2001 From: Ryuki Date: Sun, 7 Aug 2022 00:53:00 +0200 Subject: [PATCH 071/420] Refactor KPS - Remove '#nullable disable' in KeysPerSecondCalculator and KeysPerSecondCounter - Remove KeysPerSecondCalculator IDisposable implementation - Make KeysPerSecondCalculator static instance initialized once by KeysPerSecondCounters - Auto transfer dependencies from KeysPerSecondCounter to KeysPerSecondCalculator using Resolved properties - Add internal reset logic to KeysPerSecondCalculator and make it independent from Player - Use GameplayClock.TrueGameplayRate to get real-time rate. If 0 then it defaults to the last non 0 rate if no such mod is enabled --- .../HUD/KPSCounter/KeysPerSecondCalculator.cs | 106 +++++++++++------- .../HUD/KPSCounter/KeysPerSecondCounter.cs | 33 +++--- osu.Game/Screens/Play/Player.cs | 3 - 3 files changed, 81 insertions(+), 61 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/KPSCounter/KeysPerSecondCalculator.cs b/osu.Game/Screens/Play/HUD/KPSCounter/KeysPerSecondCalculator.cs index a29f4c9706..3c0d585984 100644 --- a/osu.Game/Screens/Play/HUD/KPSCounter/KeysPerSecondCalculator.cs +++ b/osu.Game/Screens/Play/HUD/KPSCounter/KeysPerSecondCalculator.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. -#nullable disable - using System; using System.Collections.Generic; using System.Linq; @@ -12,59 +10,93 @@ using osu.Game.Rulesets.UI; namespace osu.Game.Screens.Play.HUD.KPSCounter { - public class KeysPerSecondCalculator : IDisposable + public class KeysPerSecondCalculator { - private static KeysPerSecondCalculator instance; - public static void AddInput() { - instance?.onNewInput?.Invoke(); - } - - public static KeysPerSecondCalculator GetInstance(GameplayClock gameplayClock = null, DrawableRuleset drawableRuleset = null) - { - if (instance != null) return instance; - - try - { - return new KeysPerSecondCalculator(gameplayClock, drawableRuleset); - } - catch (ArgumentNullException) - { - return null; - } + onNewInput?.Invoke(); } private readonly List timestamps; - private readonly GameplayClock gameplayClock; - private readonly DrawableRuleset drawableRuleset; + private GameplayClock? gameplayClock; + private DrawableRuleset? drawableRuleset; - private event Action onNewInput; + public GameplayClock? GameplayClock + { + get => gameplayClock; + set + { + onResetRequested?.Invoke(); - private IClock workingClock => (IClock)drawableRuleset?.FrameStableClock ?? gameplayClock; + if (value != null) + { + gameplayClock = value; + } + } + } - // Having the rate from mods is preferred to using GameplayClock.TrueGameplayRate() - // as it returns 0 when paused in replays, not useful for players who want to "analyze" a replay. - private double rate => (drawableRuleset.Mods.FirstOrDefault(m => m is ModRateAdjust) as ModRateAdjust)?.SpeedChange.Value + public DrawableRuleset? DrawableRuleset + { + get => drawableRuleset; + set + { + onResetRequested?.Invoke(); + + if (value != null) + { + drawableRuleset = value; + baseRate = (drawableRuleset.Mods.FirstOrDefault(m => m is ModRateAdjust) as ModRateAdjust)?.SpeedChange.Value ?? 1; + } + } + } + + private static event Action? onNewInput; + private static event Action? onResetRequested; + + private IClock? workingClock => drawableRuleset?.FrameStableClock; + + private double baseRate; + + private double rate + { + get + { + if (gameplayClock != null) + { + if (gameplayClock.TrueGameplayRate > 0) + { + baseRate = gameplayClock.TrueGameplayRate; + } + } + + return baseRate; + } + } private double maxTime = double.NegativeInfinity; public bool Ready => workingClock != null && gameplayClock != null; public int Value => timestamps.Count(isTimestampWithinSpan); - private KeysPerSecondCalculator(GameplayClock gameplayClock, DrawableRuleset drawableRuleset) + public KeysPerSecondCalculator() { - instance = this; timestamps = new List(); - this.gameplayClock = gameplayClock ?? throw new ArgumentNullException(nameof(gameplayClock)); - this.drawableRuleset = drawableRuleset; onNewInput += addTimestamp; + onResetRequested += cleanUp; + } + + private void cleanUp() + { + timestamps.Clear(); + maxTime = double.NegativeInfinity; } private void addTimestamp() { - if (Ready && workingClock.CurrentTime >= maxTime && gameplayClock.TrueGameplayRate > 0) + if (workingClock == null) return; + + if (workingClock.CurrentTime >= maxTime) { timestamps.Add(workingClock.CurrentTime); maxTime = workingClock.CurrentTime; @@ -73,19 +105,11 @@ namespace osu.Game.Screens.Play.HUD.KPSCounter private bool isTimestampWithinSpan(double timestamp) { - if (!Ready) - return false; + if (workingClock == null) return false; double span = 1000 * rate; double relativeTime = workingClock.CurrentTime - timestamp; return relativeTime >= 0 && relativeTime <= span; } - - public void Dispose() - { - instance = null; - } - - ~KeysPerSecondCalculator() => Dispose(); } } diff --git a/osu.Game/Screens/Play/HUD/KPSCounter/KeysPerSecondCounter.cs b/osu.Game/Screens/Play/HUD/KPSCounter/KeysPerSecondCounter.cs index 2fcca2ffce..ad7b6c8f5c 100644 --- a/osu.Game/Screens/Play/HUD/KPSCounter/KeysPerSecondCounter.cs +++ b/osu.Game/Screens/Play/HUD/KPSCounter/KeysPerSecondCounter.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. -#nullable disable - 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.Localisation; -using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; @@ -24,21 +21,24 @@ namespace osu.Game.Screens.Play.HUD.KPSCounter private const float alpha_when_invalid = 0.3f; private readonly Bindable valid = new Bindable(); - private GameplayClock gameplayClock; + + private static readonly KeysPerSecondCalculator calculator = new KeysPerSecondCalculator(); + + [Resolved] + private GameplayClock? gameplayClock + { + get => calculator.GameplayClock; + set => calculator.GameplayClock = value; + } [Resolved(canBeNull: true)] - private DrawableRuleset drawableRuleset { get; set; } - - private KeysPerSecondCalculator calculator => KeysPerSecondCalculator.GetInstance(gameplayClock, drawableRuleset); - - [SettingSource("Smoothing time", "How smooth the counter should change\nThe more it is smooth, the less it's accurate.")] - public BindableNumber SmoothingTime { get; } = new BindableNumber(350) + private DrawableRuleset? drawableRuleset { - MaxValue = 1000, - MinValue = 0 - }; + get => calculator.DrawableRuleset; + set => calculator.DrawableRuleset = value; + } - protected override double RollingDuration => SmoothingTime.Value; + protected override double RollingDuration => 350; public bool UsesFixedAnchor { get; set; } @@ -48,9 +48,8 @@ namespace osu.Game.Screens.Play.HUD.KPSCounter } [BackgroundDependencyLoader] - private void load(OsuColour colours, GameplayClock clock, DrawableRuleset ruleset) + private void load(OsuColour colours) { - gameplayClock = clock; Colour = colours.BlueLighter; valid.BindValueChanged(e => DrawableCount.FadeTo(e.NewValue ? 1 : alpha_when_invalid, 1000, Easing.OutQuint)); @@ -61,7 +60,7 @@ namespace osu.Game.Screens.Play.HUD.KPSCounter base.Update(); valid.Value = calculator.Ready; - Current.Value = calculator.Value; + Current.Value = calculator.Ready ? calculator.Value : 0; } protected override IHasText CreateText() => new TextComponent diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 70b1dc9a41..e3844088e2 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -34,7 +34,6 @@ using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; using osu.Game.Scoring; using osu.Game.Scoring.Legacy; -using osu.Game.Screens.Play.HUD.KPSCounter; using osu.Game.Screens.Ranking; using osu.Game.Skinning; using osu.Game.Users; @@ -1046,8 +1045,6 @@ namespace osu.Game.Screens.Play fadeOut(); - KeysPerSecondCalculator.GetInstance()?.Dispose(); - return base.OnExiting(e); } From 0bfa6fa975e5bb3e0510b2c71d9e62416f17bbf2 Mon Sep 17 00:00:00 2001 From: HiddenNode Date: Sun, 7 Aug 2022 13:18:29 +0100 Subject: [PATCH 072/420] Implement UprightUnscaledContainer --- .../Graphics/Containers/UprightContainer.cs | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 osu.Game/Graphics/Containers/UprightContainer.cs diff --git a/osu.Game/Graphics/Containers/UprightContainer.cs b/osu.Game/Graphics/Containers/UprightContainer.cs new file mode 100644 index 0000000000..21035e82b9 --- /dev/null +++ b/osu.Game/Graphics/Containers/UprightContainer.cs @@ -0,0 +1,32 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Layout; + +namespace osu.Game.Graphics.Containers +{ + /// + /// A container that prevents itself and its children from getting rotated, scaled or flipped with its Parent. + /// + public class UprightUnscaledContainer : Container + { + public UprightUnscaledContainer() + { + AddLayout(layout); + } + + private LayoutValue layout = new LayoutValue(Invalidation.DrawInfo); + + protected override void Update() + { + base.Update(); + if (!layout.IsValid) + { + Extensions.DrawableExtensions.KeepUprightAndUnscaled(this); + layout.Validate(); + } + } + } +} From ed86255e2b45f7e658b9a47975044652902df107 Mon Sep 17 00:00:00 2001 From: HiddenNode Date: Sun, 7 Aug 2022 13:20:22 +0100 Subject: [PATCH 073/420] Use UprightUnscaledContainer instead of KeepUprightAndUnscaled --- osu.Game/Screens/Play/HUD/SongProgressInfo.cs | 61 ++++++++++--------- 1 file changed, 31 insertions(+), 30 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/SongProgressInfo.cs b/osu.Game/Screens/Play/HUD/SongProgressInfo.cs index e9d0ffe4b9..069492fd80 100644 --- a/osu.Game/Screens/Play/HUD/SongProgressInfo.cs +++ b/osu.Game/Screens/Play/HUD/SongProgressInfo.cs @@ -15,10 +15,6 @@ namespace osu.Game.Screens.Play.HUD { public class SongProgressInfo : Container { - private GrowToFitContainer timeCurrentContainer; - private GrowToFitContainer timeLeftContainer; - private GrowToFitContainer progressContainer; - private OsuSpriteText timeCurrent; private OsuSpriteText timeLeft; private OsuSpriteText progress; @@ -59,16 +55,22 @@ namespace osu.Game.Screens.Play.HUD Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, AutoSizeAxes = Axes.Both, - Child = timeCurrentContainer = new GrowToFitContainer + Child = new UprightUnscaledContainer { Origin = Anchor.Centre, Anchor = Anchor.Centre, - Child = timeCurrent = new OsuSpriteText + AutoSizeAxes = Axes.Both, + Child = new GrowToFitContainer { Origin = Anchor.Centre, Anchor = Anchor.Centre, - Colour = colours.BlueLighter, - Font = OsuFont.Numeric, + Child = timeCurrent = new OsuSpriteText + { + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + Colour = colours.BlueLighter, + Font = OsuFont.Numeric, + } } } }, @@ -77,17 +79,23 @@ namespace osu.Game.Screens.Play.HUD Origin = Anchor.Centre, Anchor = Anchor.Centre, AutoSizeAxes = Axes.Both, - Child = timeLeftContainer = new GrowToFitContainer + Child = new UprightUnscaledContainer { Origin = Anchor.Centre, Anchor = Anchor.Centre, - Child = progress = new OsuSpriteText + AutoSizeAxes = Axes.Both, + Child = new GrowToFitContainer { Origin = Anchor.Centre, Anchor = Anchor.Centre, - Colour = colours.BlueLighter, - Font = OsuFont.Numeric, - } + Child = progress = new OsuSpriteText + { + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + Colour = colours.BlueLighter, + Font = OsuFont.Numeric, + } + } } }, new Container @@ -95,28 +103,28 @@ namespace osu.Game.Screens.Play.HUD Origin = Anchor.CentreRight, Anchor = Anchor.CentreRight, AutoSizeAxes = Axes.Both, - Child = progressContainer = new GrowToFitContainer + Child = new UprightUnscaledContainer { Origin = Anchor.Centre, Anchor = Anchor.Centre, - Child = timeLeft = new OsuSpriteText + AutoSizeAxes = Axes.Both, + Child = new GrowToFitContainer { Origin = Anchor.Centre, Anchor = Anchor.Centre, - Colour = colours.BlueLighter, - Font = OsuFont.Numeric, + Child = timeLeft = new OsuSpriteText + { + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + Colour = colours.BlueLighter, + Font = OsuFont.Numeric, + } } } } }; } - protected override void LoadComplete() - { - base.LoadComplete(); - keepTextSpritesUpright(); - } - protected override void Update() { base.Update(); @@ -143,12 +151,5 @@ namespace osu.Game.Screens.Play.HUD } private string formatTime(TimeSpan timeSpan) => $"{(timeSpan < TimeSpan.Zero ? "-" : "")}{Math.Floor(timeSpan.Duration().TotalMinutes)}:{timeSpan.Duration().Seconds:D2}"; - - private void keepTextSpritesUpright() - { - timeCurrentContainer.OnUpdate += Extensions.DrawableExtensions.KeepUprightAndUnscaled; - progressContainer.OnUpdate += Extensions.DrawableExtensions.KeepUprightAndUnscaled; - timeLeftContainer.OnUpdate += Extensions.DrawableExtensions.KeepUprightAndUnscaled; - } } } From cfd07cb366bb007b3936bc387eebd37857de1f9f Mon Sep 17 00:00:00 2001 From: HiddenNode Date: Sun, 7 Aug 2022 15:04:11 +0100 Subject: [PATCH 074/420] Set InvalidationSource to parent and clean up --- .../{UprightContainer.cs => UprightUnscaledContainer.cs} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename osu.Game/Graphics/Containers/{UprightContainer.cs => UprightUnscaledContainer.cs} (95%) diff --git a/osu.Game/Graphics/Containers/UprightContainer.cs b/osu.Game/Graphics/Containers/UprightUnscaledContainer.cs similarity index 95% rename from osu.Game/Graphics/Containers/UprightContainer.cs rename to osu.Game/Graphics/Containers/UprightUnscaledContainer.cs index 21035e82b9..cdb839fdec 100644 --- a/osu.Game/Graphics/Containers/UprightContainer.cs +++ b/osu.Game/Graphics/Containers/UprightUnscaledContainer.cs @@ -17,7 +17,7 @@ namespace osu.Game.Graphics.Containers AddLayout(layout); } - private LayoutValue layout = new LayoutValue(Invalidation.DrawInfo); + private LayoutValue layout = new LayoutValue(Invalidation.DrawInfo, InvalidationSource.Parent); protected override void Update() { From b52a07c16a6559b3661453f533783ae9abeb7a69 Mon Sep 17 00:00:00 2001 From: Ryuki Date: Mon, 8 Aug 2022 21:27:46 +0200 Subject: [PATCH 075/420] Use DI to provide dependencies for KPS Calculator and improve input gathering KPS Calculator now uses DI to retrieve the clocks. Using `HUDOverlay` it is now cached for `KeysPerSecondCounter`s to resolve it. This also allows to make an "Attach" flow like `KeyCounter`. --- osu.Game/Rulesets/UI/DrawableRuleset.cs | 5 +- osu.Game/Rulesets/UI/RulesetInputManager.cs | 37 +++++++++- .../HUD/KPSCounter/KeysPerSecondCalculator.cs | 70 +++++++++---------- .../HUD/KPSCounter/KeysPerSecondCounter.cs | 22 +++--- osu.Game/Screens/Play/HUDOverlay.cs | 8 ++- osu.Game/Screens/Play/KeyCounter.cs | 2 - 6 files changed, 90 insertions(+), 54 deletions(-) diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index f7f62d2af0..b28e3355a4 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -30,6 +30,7 @@ using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; using osu.Game.Screens.Play; +using osu.Game.Screens.Play.HUD.KPSCounter; using osuTK; namespace osu.Game.Rulesets.UI @@ -38,7 +39,7 @@ namespace osu.Game.Rulesets.UI /// Displays an interactive ruleset gameplay instance. /// /// The type of HitObject contained by this DrawableRuleset. - public abstract class DrawableRuleset : DrawableRuleset, IProvideCursor, ICanAttachKeyCounter + public abstract class DrawableRuleset : DrawableRuleset, IProvideCursor, ICanAttachKeyCounter, ICanAttachKpsCalculator where TObject : HitObject { public override event Action NewResult; @@ -340,6 +341,8 @@ namespace osu.Game.Rulesets.UI public void Attach(KeyCounterDisplay keyCounter) => (KeyBindingInputManager as ICanAttachKeyCounter)?.Attach(keyCounter); + public void Attach(KeysPerSecondCalculator kps) => (KeyBindingInputManager as ICanAttachKpsCalculator)?.Attach(kps); + /// /// Creates a key conversion input manager. An exception will be thrown if a valid is not returned. /// diff --git a/osu.Game/Rulesets/UI/RulesetInputManager.cs b/osu.Game/Rulesets/UI/RulesetInputManager.cs index 7c37913576..23e64153eb 100644 --- a/osu.Game/Rulesets/UI/RulesetInputManager.cs +++ b/osu.Game/Rulesets/UI/RulesetInputManager.cs @@ -20,11 +20,12 @@ using osu.Game.Input.Bindings; using osu.Game.Input.Handlers; using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Play; +using osu.Game.Screens.Play.HUD.KPSCounter; using static osu.Game.Input.Handlers.ReplayInputHandler; namespace osu.Game.Rulesets.UI { - public abstract class RulesetInputManager : PassThroughInputManager, ICanAttachKeyCounter, IHasReplayHandler, IHasRecordingHandler + public abstract class RulesetInputManager : PassThroughInputManager, ICanAttachKeyCounter, IHasReplayHandler, IHasRecordingHandler, ICanAttachKpsCalculator where T : struct { public readonly KeyBindingContainer KeyBindingContainer; @@ -186,6 +187,35 @@ namespace osu.Game.Rulesets.UI #endregion + #region KPS Counter Attachment + + public void Attach(KeysPerSecondCalculator kps) + { + var listener = new ActionListener(); + + KeyBindingContainer.Add(listener); + + kps.Listener = listener; + } + + public class ActionListener : KeysPerSecondCalculator.InputListener, IKeyBindingHandler + { + public override event Action OnNewInput; + + public bool OnPressed(KeyBindingPressEvent e) + { + OnNewInput?.Invoke(); + + return false; + } + + public void OnReleased(KeyBindingReleaseEvent e) + { + } + } + + #endregion + protected virtual KeyBindingContainer CreateKeyBindingContainer(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique) => new RulesetKeyBindingContainer(ruleset, variant, unique); @@ -229,6 +259,11 @@ namespace osu.Game.Rulesets.UI void Attach(KeyCounterDisplay keyCounter); } + public interface ICanAttachKpsCalculator + { + void Attach(KeysPerSecondCalculator keysPerSecondCalculator); + } + public class RulesetInputManagerInputState : InputState where T : struct { diff --git a/osu.Game/Screens/Play/HUD/KPSCounter/KeysPerSecondCalculator.cs b/osu.Game/Screens/Play/HUD/KPSCounter/KeysPerSecondCalculator.cs index 3c0d585984..96a6d5b8eb 100644 --- a/osu.Game/Screens/Play/HUD/KPSCounter/KeysPerSecondCalculator.cs +++ b/osu.Game/Screens/Play/HUD/KPSCounter/KeysPerSecondCalculator.cs @@ -4,55 +4,36 @@ using System; using System.Collections.Generic; using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Graphics; using osu.Framework.Timing; -using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.UI; namespace osu.Game.Screens.Play.HUD.KPSCounter { - public class KeysPerSecondCalculator + public class KeysPerSecondCalculator : Component { - public static void AddInput() - { - onNewInput?.Invoke(); - } - private readonly List timestamps; - private GameplayClock? gameplayClock; - private DrawableRuleset? drawableRuleset; - public GameplayClock? GameplayClock + private InputListener? listener; + + [Resolved] + private GameplayClock? gameplayClock { get; set; } + + [Resolved(canBeNull: true)] + private DrawableRuleset? drawableRuleset { get; set; } + + public InputListener Listener { - get => gameplayClock; set { onResetRequested?.Invoke(); - - if (value != null) - { - gameplayClock = value; - } + listener = value; + listener.OnNewInput += addTimestamp; } } - public DrawableRuleset? DrawableRuleset - { - get => drawableRuleset; - set - { - onResetRequested?.Invoke(); - - if (value != null) - { - drawableRuleset = value; - baseRate = (drawableRuleset.Mods.FirstOrDefault(m => m is ModRateAdjust) as ModRateAdjust)?.SpeedChange.Value - ?? 1; - } - } - } - - private static event Action? onNewInput; - private static event Action? onResetRequested; + private event Action? onResetRequested; private IClock? workingClock => drawableRuleset?.FrameStableClock; @@ -81,8 +62,8 @@ namespace osu.Game.Screens.Play.HUD.KPSCounter public KeysPerSecondCalculator() { + RelativeSizeAxes = Axes.Both; timestamps = new List(); - onNewInput += addTimestamp; onResetRequested += cleanUp; } @@ -90,6 +71,9 @@ namespace osu.Game.Screens.Play.HUD.KPSCounter { timestamps.Clear(); maxTime = double.NegativeInfinity; + + if (listener != null) + listener.OnNewInput -= addTimestamp; } private void addTimestamp() @@ -111,5 +95,21 @@ namespace osu.Game.Screens.Play.HUD.KPSCounter double relativeTime = workingClock.CurrentTime - timestamp; return relativeTime >= 0 && relativeTime <= span; } + + ~KeysPerSecondCalculator() + { + cleanUp(); + } + + public abstract class InputListener : Component + { + protected InputListener() + { + RelativeSizeAxes = Axes.Both; + Depth = float.MinValue; + } + + public abstract event Action? OnNewInput; + } } } diff --git a/osu.Game/Screens/Play/HUD/KPSCounter/KeysPerSecondCounter.cs b/osu.Game/Screens/Play/HUD/KPSCounter/KeysPerSecondCounter.cs index ad7b6c8f5c..d6f1d19770 100644 --- a/osu.Game/Screens/Play/HUD/KPSCounter/KeysPerSecondCounter.cs +++ b/osu.Game/Screens/Play/HUD/KPSCounter/KeysPerSecondCounter.cs @@ -22,21 +22,15 @@ namespace osu.Game.Screens.Play.HUD.KPSCounter private readonly Bindable valid = new Bindable(); - private static readonly KeysPerSecondCalculator calculator = new KeysPerSecondCalculator(); - [Resolved] - private GameplayClock? gameplayClock - { - get => calculator.GameplayClock; - set => calculator.GameplayClock = value; - } + private KeysPerSecondCalculator? calculator { get; set; } + + // This is to force the skin editor to show the component only in a Gameplay context + [Resolved] + private GameplayClock? gameplayClock { get; set; } [Resolved(canBeNull: true)] - private DrawableRuleset? drawableRuleset - { - get => calculator.DrawableRuleset; - set => calculator.DrawableRuleset = value; - } + private DrawableRuleset? drawableRuleset { get; set; } protected override double RollingDuration => 350; @@ -59,8 +53,8 @@ namespace osu.Game.Screens.Play.HUD.KPSCounter { base.Update(); - valid.Value = calculator.Ready; - Current.Value = calculator.Ready ? calculator.Value : 0; + valid.Value = calculator != null && calculator.Ready; + Current.Value = calculator != null ? calculator.Ready ? calculator.Value : 0 : 0; } protected override IHasText CreateText() => new TextComponent diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 8f80644d52..1c28e04950 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -22,6 +22,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.Screens.Play.HUD.KPSCounter; using osu.Game.Skinning; using osuTK; @@ -49,6 +50,9 @@ namespace osu.Game.Screens.Play public readonly HoldForMenuButton HoldToQuit; public readonly PlayerSettingsOverlay PlayerSettingsOverlay; + [Cached] + private readonly KeysPerSecondCalculator keysPerSecondCalculator; + public Bindable ShowHealthBar = new Bindable(true); private readonly DrawableRuleset drawableRuleset; @@ -122,7 +126,8 @@ namespace osu.Game.Screens.Play KeyCounter = CreateKeyCounter(), HoldToQuit = CreateHoldForMenuButton(), } - } + }, + keysPerSecondCalculator = new KeysPerSecondCalculator() }; } @@ -260,6 +265,7 @@ namespace osu.Game.Screens.Play protected virtual void BindDrawableRuleset(DrawableRuleset drawableRuleset) { (drawableRuleset as ICanAttachKeyCounter)?.Attach(KeyCounter); + (drawableRuleset as ICanAttachKpsCalculator)?.Attach(keysPerSecondCalculator); replayLoaded.BindTo(drawableRuleset.HasReplayLoaded); } diff --git a/osu.Game/Screens/Play/KeyCounter.cs b/osu.Game/Screens/Play/KeyCounter.cs index 044c9ee24e..1e5ada5295 100644 --- a/osu.Game/Screens/Play/KeyCounter.cs +++ b/osu.Game/Screens/Play/KeyCounter.cs @@ -10,7 +10,6 @@ using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; -using osu.Game.Screens.Play.HUD.KPSCounter; using osuTK; using osuTK.Graphics; @@ -56,7 +55,6 @@ namespace osu.Game.Screens.Play public void Increment() { - KeysPerSecondCalculator.AddInput(); if (!IsCounting) return; From edb8e5e33e3649f0be60e8b4a53498ab200076a4 Mon Sep 17 00:00:00 2001 From: Ryuki Date: Tue, 9 Aug 2022 02:43:41 +0200 Subject: [PATCH 076/420] Temporarily emptying `TestSceneKeysPerSecondCounter` until a good test can be found --- .../Gameplay/TestSceneKeysPerSecondCounter.cs | 70 +------------------ 1 file changed, 1 insertion(+), 69 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneKeysPerSecondCounter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneKeysPerSecondCounter.cs index 6e59c53a1f..8bc2eae1d4 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneKeysPerSecondCounter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneKeysPerSecondCounter.cs @@ -1,77 +1,9 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - -using NUnit.Framework; -using osu.Framework.Graphics; -using osu.Game.Rulesets; -using osu.Game.Rulesets.Mania; -using osu.Game.Rulesets.UI; -using osu.Game.Screens.Play; -using osu.Game.Screens.Play.HUD.KPSCounter; -using osuTK; -using osuTK.Input; - namespace osu.Game.Tests.Visual.Gameplay { - public class TestSceneKeysPerSecondCounter : PlayerTestScene + public class TestSceneKeysPerSecondCounter : OsuManualInputManagerTestScene { - protected override Ruleset CreatePlayerRuleset() => new ManiaRuleset(); - protected override bool HasCustomSteps => false; - protected override bool Autoplay => false; - protected override TestPlayer CreatePlayer(Ruleset ruleset) => new TestPlayer(true, false); - - private GameplayClock gameplayClock; - private DrawableRuleset drawableRuleset; - - private KeysPerSecondCounter counter; - - private void createCounter() - { - AddStep("Create counter", () => - { - gameplayClock = Player.GameplayClockContainer.GameplayClock; - drawableRuleset = Player.DrawableRuleset; - - Player.HUDOverlay.Add(counter = new KeysPerSecondCounter - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Scale = new Vector2(5), - }); - counter.SmoothingTime.Value = 0; - }); - AddUntilStep("Counter created", () => Player.HUDOverlay.Contains(counter)); - } - - [Test] - public void TestBasic() - { - createCounter(); - - AddStep("press 1 key", () => InputManager.Key(Key.D)); - AddAssert("KPS = 1", () => counter.Current.Value == 1); - AddUntilStep("Wait for KPS cooldown", () => counter.Current.Value <= 0); - AddStep("press 4 keys", () => - { - InputManager.Key(Key.D); - InputManager.Key(Key.F); - InputManager.Key(Key.J); - InputManager.Key(Key.K); - }); - AddAssert("KPS = 4", () => counter.Current.Value == 4); - AddStep("Pause player", () => Player.Pause()); - AddAssert("KPS = 4", () => counter.Current.Value == 4); - AddStep("Resume player", () => Player.Resume()); - AddStep("press 4 keys", () => - { - InputManager.Key(Key.D); - InputManager.Key(Key.F); - InputManager.Key(Key.J); - InputManager.Key(Key.K); - }); - AddAssert("KPS = 8", () => counter.Current.Value == 8); - } } } From 2367dc96107091207c468738a93f6ece62310f05 Mon Sep 17 00:00:00 2001 From: HiddenNode Date: Tue, 9 Aug 2022 13:06:11 +0100 Subject: [PATCH 077/420] Improved KeepUprightAndUnscaled --- osu.Game/Extensions/DrawableExtensions.cs | 36 +++++++++++------------ 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/osu.Game/Extensions/DrawableExtensions.cs b/osu.Game/Extensions/DrawableExtensions.cs index f587b1c55b..320b9d7996 100644 --- a/osu.Game/Extensions/DrawableExtensions.cs +++ b/osu.Game/Extensions/DrawableExtensions.cs @@ -82,40 +82,38 @@ namespace osu.Game.Extensions } /// - /// Keeps the drawable upright and prevents it from being scaled or flipped with its Parent. + /// Keeps the drawable upright and unstretched preventing it from being rotated, sheared, scaled or flipped with its Parent. /// /// The drawable. public static void KeepUprightAndUnscaled(this Drawable drawable) { + // Decomposes the inverse of the parent FrawInfo.Matrix into rotation, shear and scale. var parentMatrix = drawable.Parent.DrawInfo.Matrix; - float angle = MathF.Atan(parentMatrix.M12 / parentMatrix.M11); - angle = MathHelper.RadiansToDegrees(angle); - parentMatrix.Transpose(); + + // Remove Translation. parentMatrix.M13 = 0.0f; parentMatrix.M23 = 0.0f; - if ((Math.Abs(Math.Abs(angle) - 90.0)) < 2.0f) - { - Matrix3 m = Matrix3.CreateRotationZ(MathHelper.DegreesToRadians(40.0f)); - m.Transpose(); - parentMatrix *= m; - drawable.Rotation = 40.0f; - } - else - drawable.Rotation = 0.0f; - Matrix3 C = parentMatrix.Inverted(); - float alpha, beta, sx, sy; + // Extract the rotation. + float angle = MathF.Atan2(C.M21, C.M11); + drawable.Rotation = MathHelper.RadiansToDegrees(angle); + + // Remove rotation from the C matrix so that it only contains shear and scale. + Matrix3 m = Matrix3.CreateRotationZ(-angle); + m.Transpose(); + C = m * C; + + // Extract shear and scale. + float alpha, sx, sy; + sx = C.M11; sy = C.M22; alpha = C.M12 / C.M22; - beta = (C.M21 == 0.0f) ? 0.0f : 1 / ((C.M11 / C.M21) - alpha); - sx = (beta == 0.0f) ? C.M11 : C.M21 / beta; - drawable.Scale = new Vector2(sx, sy); - drawable.Shear = new Vector2(-alpha, -beta); + drawable.Shear = new Vector2(-alpha, 0); } } } From 9e80d3f71c01a2e5789348a9237bc1d92c4b6c2b Mon Sep 17 00:00:00 2001 From: Ryuki Date: Thu, 11 Aug 2022 00:42:22 +0200 Subject: [PATCH 078/420] Re-adjust timespan conditions in `KeysPerSecondCalculator` --- osu.Game/Screens/Play/HUD/KPSCounter/KeysPerSecondCalculator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/HUD/KPSCounter/KeysPerSecondCalculator.cs b/osu.Game/Screens/Play/HUD/KPSCounter/KeysPerSecondCalculator.cs index 96a6d5b8eb..890404a0aa 100644 --- a/osu.Game/Screens/Play/HUD/KPSCounter/KeysPerSecondCalculator.cs +++ b/osu.Game/Screens/Play/HUD/KPSCounter/KeysPerSecondCalculator.cs @@ -93,7 +93,7 @@ namespace osu.Game.Screens.Play.HUD.KPSCounter double span = 1000 * rate; double relativeTime = workingClock.CurrentTime - timestamp; - return relativeTime >= 0 && relativeTime <= span; + return relativeTime > 0 && relativeTime <= span; } ~KeysPerSecondCalculator() From 46e372cb99ba47a86f17418353086e95a4ddd3fd Mon Sep 17 00:00:00 2001 From: Ryuki Date: Thu, 11 Aug 2022 00:43:15 +0200 Subject: [PATCH 079/420] Add more readiness checks in `KeysPerSecondCalculator` --- osu.Game/Screens/Play/HUD/KPSCounter/KeysPerSecondCalculator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/HUD/KPSCounter/KeysPerSecondCalculator.cs b/osu.Game/Screens/Play/HUD/KPSCounter/KeysPerSecondCalculator.cs index 890404a0aa..dddef9abaf 100644 --- a/osu.Game/Screens/Play/HUD/KPSCounter/KeysPerSecondCalculator.cs +++ b/osu.Game/Screens/Play/HUD/KPSCounter/KeysPerSecondCalculator.cs @@ -57,7 +57,7 @@ namespace osu.Game.Screens.Play.HUD.KPSCounter private double maxTime = double.NegativeInfinity; - public bool Ready => workingClock != null && gameplayClock != null; + public bool Ready => workingClock != null && gameplayClock != null && listener != null; public int Value => timestamps.Count(isTimestampWithinSpan); public KeysPerSecondCalculator() From 0a94fb4039df50a5a48e55cae546221557fcff4d Mon Sep 17 00:00:00 2001 From: Ryuki Date: Thu, 11 Aug 2022 00:46:31 +0200 Subject: [PATCH 080/420] Make KPS counter strictly depending only on KPS calculator `KeysPerSecondCounter` now depends on `KeysPerSecondCalculator` via the `BackgroundDependencyLoaderAttribute` method, making it appear only in a gameplay context without requiring `GameplayClock` without using it. --- .../Play/HUD/KPSCounter/KeysPerSecondCounter.cs | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/KPSCounter/KeysPerSecondCounter.cs b/osu.Game/Screens/Play/HUD/KPSCounter/KeysPerSecondCounter.cs index d6f1d19770..47ebede623 100644 --- a/osu.Game/Screens/Play/HUD/KPSCounter/KeysPerSecondCounter.cs +++ b/osu.Game/Screens/Play/HUD/KPSCounter/KeysPerSecondCounter.cs @@ -10,7 +10,7 @@ using osu.Framework.Localisation; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; -using osu.Game.Rulesets.UI; +// using osu.Game.Rulesets.UI; using osu.Game.Skinning; using osuTK; @@ -22,15 +22,7 @@ namespace osu.Game.Screens.Play.HUD.KPSCounter private readonly Bindable valid = new Bindable(); - [Resolved] - private KeysPerSecondCalculator? calculator { get; set; } - - // This is to force the skin editor to show the component only in a Gameplay context - [Resolved] - private GameplayClock? gameplayClock { get; set; } - - [Resolved(canBeNull: true)] - private DrawableRuleset? drawableRuleset { get; set; } + private KeysPerSecondCalculator? calculator; protected override double RollingDuration => 350; @@ -42,8 +34,9 @@ namespace osu.Game.Screens.Play.HUD.KPSCounter } [BackgroundDependencyLoader] - private void load(OsuColour colours) + private void load(OsuColour colours, KeysPerSecondCalculator calculator) { + this.calculator = calculator; Colour = colours.BlueLighter; valid.BindValueChanged(e => DrawableCount.FadeTo(e.NewValue ? 1 : alpha_when_invalid, 1000, Easing.OutQuint)); From d58d5eebe2ec52e19142442dcdfd162d6263f213 Mon Sep 17 00:00:00 2001 From: Ryuki Date: Thu, 11 Aug 2022 00:51:13 +0200 Subject: [PATCH 081/420] Add basic tests for KPS Created private mock classes to use them in place of `GameplayClock` and `DrawableRuleset`. --- .../Visual/Gameplay/TestSceneKeysPerSecond.cs | 306 ++++++++++++++++++ .../Gameplay/TestSceneKeysPerSecondCounter.cs | 9 - 2 files changed, 306 insertions(+), 9 deletions(-) create mode 100644 osu.Game.Tests/Visual/Gameplay/TestSceneKeysPerSecond.cs delete mode 100644 osu.Game.Tests/Visual/Gameplay/TestSceneKeysPerSecondCounter.cs diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneKeysPerSecond.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneKeysPerSecond.cs new file mode 100644 index 0000000000..4bda998c49 --- /dev/null +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneKeysPerSecond.cs @@ -0,0 +1,306 @@ +// Copyright (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 NUnit.Framework; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Testing; +using osu.Framework.Timing; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Mania; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.UI; +using osu.Game.Scoring; +using osu.Game.Screens.Play; +using osu.Game.Screens.Play.HUD.KPSCounter; +using osuTK; + +namespace osu.Game.Tests.Visual.Gameplay +{ + public class TestSceneKeysPerSecond : OsuTestScene + { + private DependencyProvidingContainer? dependencyContainer; + private MockFrameStableClock? mainClock; + private KeysPerSecondCalculator? calculator; + private ManualInputListener? listener; + + [SetUpSteps] + public void SetUpSteps() + { + AddStep("create components", () => + { + var ruleset = CreateRuleset(); + + Debug.Assert(ruleset != null); + + Children = new Drawable[] + { + dependencyContainer = new DependencyProvidingContainer + { + RelativeSizeAxes = Axes.Both, + CachedDependencies = new (Type, object)[] + { + (typeof(GameplayClock), mainClock = new MockFrameStableClock(new MockFrameBasedClock())), + (typeof(DrawableRuleset), new DrawableCookieziRuleset(ruleset, mainClock)) + } + }, + }; + }); + } + + private void createCalculator() + { + AddStep("create calculator", () => + { + dependencyContainer!.Children = new Drawable[] + { + calculator = new KeysPerSecondCalculator + { + Listener = listener = new ManualInputListener() + }, + new DependencyProvidingContainer + { + RelativeSizeAxes = Axes.Both, + CachedDependencies = new (Type, object)[] { (typeof(KeysPerSecondCalculator), calculator) }, + Child = new KeysPerSecondCounter // For visual debugging, has no real purpose in the tests + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Scale = new Vector2(5), + } + } + }; + }); + } + + [Test] + public void TestBasicConsistency() + { + createCalculator(); + + AddStep("Create gradually increasing KPS inputs", () => + { + addInputs(generateGraduallyIncreasingKps()); + }); + + for (int i = 0; i < 10; i++) + { + seek(i * 10000); + advanceForwards(2); + int kps = i + 1; + AddAssert($"{kps} KPS", () => calculator!.Value == kps); + } + } + + [Test] + public void TestRateAdjustConsistency() + { + createCalculator(); + + AddStep("Create consistent KPS inputs", () => addInputs(generateConsistentKps(10))); + + advanceForwards(2); + + for (double i = 1; i <= 2; i += 0.25) + { + changeRate(i); + double rate = i; + AddAssert($"KPS approx. = {i}", () => MathHelper.ApproximatelyEquivalent(calculator!.Value, 10 * rate, 0.5)); + } + + for (double i = 1; i >= 0.5; i -= 0.25) + { + changeRate(i); + double rate = i; + AddAssert($"KPS approx. = {i}", () => MathHelper.ApproximatelyEquivalent(calculator!.Value, 10 * rate, 0.5)); + } + } + + [Test] + public void TestInputsDiscardedOnRewind() + { + createCalculator(); + + AddStep("Create consistent KPS inputs", () => addInputs(generateConsistentKps(10))); + seek(1000); + + AddAssert("KPS = 10", () => calculator!.Value == 10); + + AddStep("Create delayed inputs", () => addInputs(generateConsistentKps(10, 50))); + seek(1000); + AddAssert("KPS didn't changed", () => calculator!.Value == 10); + } + + private void seek(double time) => AddStep($"Seek main clock to {time}ms", () => mainClock?.Seek(time)); + + private void changeRate(double rate) => AddStep($"Change rate to x{rate}", () => + (mainClock?.UnderlyingClock as MockFrameBasedClock)!.Rate = rate); + + private void advanceForwards(int frames = 1) => AddStep($"Advance main clock {frames} frame(s) forward.", () => + { + if (mainClock == null) return; + + MockFrameBasedClock underlyingClock = (MockFrameBasedClock)mainClock.UnderlyingClock; + underlyingClock.Backwards = false; + + for (int i = 0; i < frames; i++) + { + underlyingClock.ProcessFrame(); + } + }); + + private void addInputs(IEnumerable inputs) + { + Debug.Assert(mainClock != null && listener != null); + if (!inputs.Any()) return; + + double baseTime = mainClock.CurrentTime; + + foreach (double timestamp in inputs) + { + mainClock.Seek(timestamp); + listener.AddInput(); + } + + mainClock.Seek(baseTime); + } + + private IEnumerable generateGraduallyIncreasingKps() + { + IEnumerable? final = null; + + for (int i = 1; i <= 10; i++) + { + var currentKps = generateConsistentKps(i, (i - 1) * 10000); + + if (i == 1) + { + final = currentKps; + continue; + } + + final = final!.Concat(currentKps); + } + + return final!; + } + + private IEnumerable generateConsistentKps(double kps, double start = 0, double duration = 10) + { + double end = start + 1000 * duration; + + for (; start < end; start += 1000 / kps) + { + yield return start; + } + } + + protected override Ruleset CreateRuleset() => new ManiaRuleset(); + + #region Mock classes + + private class ManualInputListener : KeysPerSecondCalculator.InputListener + { + public override event Action? OnNewInput; + + public void AddInput() => OnNewInput?.Invoke(); + } + + private class MockFrameBasedClock : ManualClock, IFrameBasedClock + { + public const double FRAME_INTERVAL = 1000; + public bool Backwards; + + public MockFrameBasedClock() + { + Rate = 1; + IsRunning = true; + } + + public void ProcessFrame() + { + CurrentTime += FRAME_INTERVAL * Rate * (Backwards ? -1 : 1); + TimeInfo = new FrameTimeInfo + { + Current = CurrentTime, + Elapsed = FRAME_INTERVAL * Rate * (Backwards ? -1 : 1) + }; + } + + public void Seek(double time) + { + TimeInfo = new FrameTimeInfo + { + Elapsed = time - CurrentTime, + Current = CurrentTime = time + }; + } + + public double ElapsedFrameTime => TimeInfo.Elapsed; + public double FramesPerSecond => 1 / FRAME_INTERVAL; + public FrameTimeInfo TimeInfo { get; private set; } + } + + private class MockFrameStableClock : GameplayClock, IFrameStableClock + { + public MockFrameStableClock(MockFrameBasedClock underlyingClock) + : base(underlyingClock) + { + } + + public void Seek(double time) => (UnderlyingClock as MockFrameBasedClock)?.Seek(time); + + public IBindable IsCatchingUp => new Bindable(); + public IBindable WaitingOnFrames => new Bindable(); + } + + private class DrawableCookieziRuleset : DrawableRuleset + { + public DrawableCookieziRuleset(Ruleset ruleset, IFrameStableClock clock) + : base(ruleset) + { + FrameStableClock = clock; + } + +#pragma warning disable CS0067 + public override event Action? NewResult; + public override event Action? RevertResult; +#pragma warning restore CS0067 + public override Playfield? Playfield => null; + public override Container? Overlays => null; + public override Container? FrameStableComponents => null; + public override IFrameStableClock FrameStableClock { get; } + + internal override bool FrameStablePlayback { get; set; } + public override IReadOnlyList Mods => Array.Empty(); + public override IEnumerable Objects => Array.Empty(); + public override double GameplayStartTime => 0; + public override GameplayCursorContainer? Cursor => null; + + public override void SetReplayScore(Score replayScore) + { + } + + public override void SetRecordTarget(Score score) + { + } + + public override void RequestResume(Action continueResume) + { + } + + public override void CancelResume() + { + } + } + + #endregion + } +} diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneKeysPerSecondCounter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneKeysPerSecondCounter.cs deleted file mode 100644 index 8bc2eae1d4..0000000000 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneKeysPerSecondCounter.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.Tests.Visual.Gameplay -{ - public class TestSceneKeysPerSecondCounter : OsuManualInputManagerTestScene - { - } -} From 0e1efbd865c96a2aa53c98f80cc1b00d287f2190 Mon Sep 17 00:00:00 2001 From: Ryuki Date: Thu, 11 Aug 2022 01:03:47 +0200 Subject: [PATCH 082/420] Rename `DrawableCookieziRuleset` to `MockDrawableRuleset` --- osu.Game.Tests/Visual/Gameplay/TestSceneKeysPerSecond.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneKeysPerSecond.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneKeysPerSecond.cs index 4bda998c49..edfba7f154 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneKeysPerSecond.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneKeysPerSecond.cs @@ -48,7 +48,7 @@ namespace osu.Game.Tests.Visual.Gameplay CachedDependencies = new (Type, object)[] { (typeof(GameplayClock), mainClock = new MockFrameStableClock(new MockFrameBasedClock())), - (typeof(DrawableRuleset), new DrawableCookieziRuleset(ruleset, mainClock)) + (typeof(DrawableRuleset), new MockDrawableRuleset(ruleset, mainClock)) } }, }; @@ -261,9 +261,9 @@ namespace osu.Game.Tests.Visual.Gameplay public IBindable WaitingOnFrames => new Bindable(); } - private class DrawableCookieziRuleset : DrawableRuleset + private class MockDrawableRuleset : DrawableRuleset { - public DrawableCookieziRuleset(Ruleset ruleset, IFrameStableClock clock) + public MockDrawableRuleset(Ruleset ruleset, IFrameStableClock clock) : base(ruleset) { FrameStableClock = clock; From 3c6461b9e4383174278cf3b4ab7493a6c076473d Mon Sep 17 00:00:00 2001 From: Ryuki Date: Thu, 11 Aug 2022 09:36:35 +0200 Subject: [PATCH 083/420] Remove KPS acronym usage --- .../Visual/Gameplay/TestSceneKeysPerSecond.cs | 2 +- osu.Game/Rulesets/UI/DrawableRuleset.cs | 6 +++--- osu.Game/Rulesets/UI/RulesetInputManager.cs | 16 ++++++---------- .../KeysPerSecondCalculator.cs | 2 +- .../KeysPerSecondCounter.cs | 4 ++-- osu.Game/Screens/Play/HUDOverlay.cs | 5 ++--- 6 files changed, 15 insertions(+), 20 deletions(-) rename osu.Game/Screens/Play/HUD/{KPSCounter => KeysPerSecond}/KeysPerSecondCalculator.cs (98%) rename osu.Game/Screens/Play/HUD/{KPSCounter => KeysPerSecond}/KeysPerSecondCounter.cs (98%) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneKeysPerSecond.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneKeysPerSecond.cs index edfba7f154..5c1ca18dbc 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneKeysPerSecond.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneKeysPerSecond.cs @@ -19,7 +19,7 @@ using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.UI; using osu.Game.Scoring; using osu.Game.Screens.Play; -using osu.Game.Screens.Play.HUD.KPSCounter; +using osu.Game.Screens.Play.HUD.KeysPerSecond; using osuTK; namespace osu.Game.Tests.Visual.Gameplay diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index b28e3355a4..443e4392cf 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -30,7 +30,7 @@ using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; using osu.Game.Screens.Play; -using osu.Game.Screens.Play.HUD.KPSCounter; +using osu.Game.Screens.Play.HUD.KeysPerSecond; using osuTK; namespace osu.Game.Rulesets.UI @@ -39,7 +39,7 @@ namespace osu.Game.Rulesets.UI /// Displays an interactive ruleset gameplay instance. /// /// The type of HitObject contained by this DrawableRuleset. - public abstract class DrawableRuleset : DrawableRuleset, IProvideCursor, ICanAttachKeyCounter, ICanAttachKpsCalculator + public abstract class DrawableRuleset : DrawableRuleset, IProvideCursor, ICanAttachKeyCounter where TObject : HitObject { public override event Action NewResult; @@ -341,7 +341,7 @@ namespace osu.Game.Rulesets.UI public void Attach(KeyCounterDisplay keyCounter) => (KeyBindingInputManager as ICanAttachKeyCounter)?.Attach(keyCounter); - public void Attach(KeysPerSecondCalculator kps) => (KeyBindingInputManager as ICanAttachKpsCalculator)?.Attach(kps); + public void Attach(KeysPerSecondCalculator calculator) => (KeyBindingInputManager as ICanAttachKeyCounter)?.Attach(calculator); /// /// Creates a key conversion input manager. An exception will be thrown if a valid is not returned. diff --git a/osu.Game/Rulesets/UI/RulesetInputManager.cs b/osu.Game/Rulesets/UI/RulesetInputManager.cs index 23e64153eb..2e9986ada6 100644 --- a/osu.Game/Rulesets/UI/RulesetInputManager.cs +++ b/osu.Game/Rulesets/UI/RulesetInputManager.cs @@ -20,12 +20,12 @@ using osu.Game.Input.Bindings; using osu.Game.Input.Handlers; using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Play; -using osu.Game.Screens.Play.HUD.KPSCounter; +using osu.Game.Screens.Play.HUD.KeysPerSecond; using static osu.Game.Input.Handlers.ReplayInputHandler; namespace osu.Game.Rulesets.UI { - public abstract class RulesetInputManager : PassThroughInputManager, ICanAttachKeyCounter, IHasReplayHandler, IHasRecordingHandler, ICanAttachKpsCalculator + public abstract class RulesetInputManager : PassThroughInputManager, ICanAttachKeyCounter, IHasReplayHandler, IHasRecordingHandler where T : struct { public readonly KeyBindingContainer KeyBindingContainer; @@ -187,15 +187,15 @@ namespace osu.Game.Rulesets.UI #endregion - #region KPS Counter Attachment + #region Keys per second Counter Attachment - public void Attach(KeysPerSecondCalculator kps) + public void Attach(KeysPerSecondCalculator calculator) { var listener = new ActionListener(); KeyBindingContainer.Add(listener); - kps.Listener = listener; + calculator.Listener = listener; } public class ActionListener : KeysPerSecondCalculator.InputListener, IKeyBindingHandler @@ -257,11 +257,7 @@ namespace osu.Game.Rulesets.UI public interface ICanAttachKeyCounter { void Attach(KeyCounterDisplay keyCounter); - } - - public interface ICanAttachKpsCalculator - { - void Attach(KeysPerSecondCalculator keysPerSecondCalculator); + void Attach(KeysPerSecondCalculator calculator); } public class RulesetInputManagerInputState : InputState diff --git a/osu.Game/Screens/Play/HUD/KPSCounter/KeysPerSecondCalculator.cs b/osu.Game/Screens/Play/HUD/KeysPerSecond/KeysPerSecondCalculator.cs similarity index 98% rename from osu.Game/Screens/Play/HUD/KPSCounter/KeysPerSecondCalculator.cs rename to osu.Game/Screens/Play/HUD/KeysPerSecond/KeysPerSecondCalculator.cs index dddef9abaf..ecc9c6ef86 100644 --- a/osu.Game/Screens/Play/HUD/KPSCounter/KeysPerSecondCalculator.cs +++ b/osu.Game/Screens/Play/HUD/KeysPerSecond/KeysPerSecondCalculator.cs @@ -9,7 +9,7 @@ using osu.Framework.Graphics; using osu.Framework.Timing; using osu.Game.Rulesets.UI; -namespace osu.Game.Screens.Play.HUD.KPSCounter +namespace osu.Game.Screens.Play.HUD.KeysPerSecond { public class KeysPerSecondCalculator : Component { diff --git a/osu.Game/Screens/Play/HUD/KPSCounter/KeysPerSecondCounter.cs b/osu.Game/Screens/Play/HUD/KeysPerSecond/KeysPerSecondCounter.cs similarity index 98% rename from osu.Game/Screens/Play/HUD/KPSCounter/KeysPerSecondCounter.cs rename to osu.Game/Screens/Play/HUD/KeysPerSecond/KeysPerSecondCounter.cs index 47ebede623..6589dbb719 100644 --- a/osu.Game/Screens/Play/HUD/KPSCounter/KeysPerSecondCounter.cs +++ b/osu.Game/Screens/Play/HUD/KeysPerSecond/KeysPerSecondCounter.cs @@ -10,11 +10,11 @@ using osu.Framework.Localisation; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; -// using osu.Game.Rulesets.UI; using osu.Game.Skinning; using osuTK; +// using osu.Game.Rulesets.UI; -namespace osu.Game.Screens.Play.HUD.KPSCounter +namespace osu.Game.Screens.Play.HUD.KeysPerSecond { public class KeysPerSecondCounter : RollingCounter, ISkinnableDrawable { diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 1c28e04950..20a1a27f3d 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -22,7 +22,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.Screens.Play.HUD.KPSCounter; +using osu.Game.Screens.Play.HUD.KeysPerSecond; using osu.Game.Skinning; using osuTK; @@ -127,7 +127,6 @@ namespace osu.Game.Screens.Play HoldToQuit = CreateHoldForMenuButton(), } }, - keysPerSecondCalculator = new KeysPerSecondCalculator() }; } @@ -265,7 +264,7 @@ namespace osu.Game.Screens.Play protected virtual void BindDrawableRuleset(DrawableRuleset drawableRuleset) { (drawableRuleset as ICanAttachKeyCounter)?.Attach(KeyCounter); - (drawableRuleset as ICanAttachKpsCalculator)?.Attach(keysPerSecondCalculator); + (drawableRuleset as ICanAttachKeyCounter)?.Attach(keysPerSecondCalculator); replayLoaded.BindTo(drawableRuleset.HasReplayLoaded); } From 787dee249da553736e006485582a8eaddcf54ef5 Mon Sep 17 00:00:00 2001 From: Ryuki Date: Thu, 11 Aug 2022 10:37:06 +0200 Subject: [PATCH 084/420] Move `KeysPerSecondCalculator` instantiation from `HUDOverlay` to `Player` This prevents messing with *future* Skin (de)serialization --- osu.Game/Screens/Play/HUDOverlay.cs | 7 ++++--- osu.Game/Screens/Play/Player.cs | 7 +++++++ 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 20a1a27f3d..1cddbcac41 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -50,8 +50,7 @@ namespace osu.Game.Screens.Play public readonly HoldForMenuButton HoldToQuit; public readonly PlayerSettingsOverlay PlayerSettingsOverlay; - [Cached] - private readonly KeysPerSecondCalculator keysPerSecondCalculator; + private KeysPerSecondCalculator keysPerSecondCalculator; public Bindable ShowHealthBar = new Bindable(true); @@ -131,8 +130,10 @@ namespace osu.Game.Screens.Play } [BackgroundDependencyLoader(true)] - private void load(OsuConfigManager config, INotificationOverlay notificationOverlay) + private void load(OsuConfigManager config, INotificationOverlay notificationOverlay, KeysPerSecondCalculator calculator) { + keysPerSecondCalculator = calculator; + if (drawableRuleset != null) { BindDrawableRuleset(drawableRuleset); diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index e3844088e2..ba7e01a803 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -34,6 +34,7 @@ using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; using osu.Game.Scoring; using osu.Game.Scoring.Legacy; +using osu.Game.Screens.Play.HUD.KeysPerSecond; using osu.Game.Screens.Ranking; using osu.Game.Skinning; using osu.Game.Users; @@ -122,6 +123,8 @@ namespace osu.Game.Screens.Play private SkipOverlay skipIntroOverlay; private SkipOverlay skipOutroOverlay; + protected KeysPerSecondCalculator KeysPerSecondCalculator { get; private set; } + protected ScoreProcessor ScoreProcessor { get; private set; } protected HealthProcessor HealthProcessor { get; private set; } @@ -226,6 +229,9 @@ namespace osu.Game.Screens.Play dependencies.CacheAs(ScoreProcessor); + KeysPerSecondCalculator = new KeysPerSecondCalculator(); + dependencies.CacheAs(KeysPerSecondCalculator); + HealthProcessor = ruleset.CreateHealthProcessor(playableBeatmap.HitObjects[0].StartTime); HealthProcessor.ApplyBeatmap(playableBeatmap); @@ -442,6 +448,7 @@ namespace osu.Game.Screens.Play OnRetry = Restart, OnQuit = () => PerformExit(true), }, + KeysPerSecondCalculator }, }; From d29cba80e9705a2a28c4dff23c0dd55312923654 Mon Sep 17 00:00:00 2001 From: Ryuki Date: Thu, 11 Aug 2022 11:01:16 +0200 Subject: [PATCH 085/420] Remove useless comment in `KeysPerSecondCounter` --- osu.Game/Screens/Play/HUD/KeysPerSecond/KeysPerSecondCounter.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Screens/Play/HUD/KeysPerSecond/KeysPerSecondCounter.cs b/osu.Game/Screens/Play/HUD/KeysPerSecond/KeysPerSecondCounter.cs index 6589dbb719..7bd4d41242 100644 --- a/osu.Game/Screens/Play/HUD/KeysPerSecond/KeysPerSecondCounter.cs +++ b/osu.Game/Screens/Play/HUD/KeysPerSecond/KeysPerSecondCounter.cs @@ -12,7 +12,6 @@ using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Skinning; using osuTK; -// using osu.Game.Rulesets.UI; namespace osu.Game.Screens.Play.HUD.KeysPerSecond { From 9b252b1d81ce40307324e2a3197433dd874d464c Mon Sep 17 00:00:00 2001 From: Ryuki Date: Thu, 11 Aug 2022 11:58:30 +0200 Subject: [PATCH 086/420] Make `KeysPerSecondCalculator` dependency in `HUDOverlay` nullable --- osu.Game/Rulesets/UI/RulesetInputManager.cs | 2 ++ osu.Game/Screens/Play/HUDOverlay.cs | 7 +++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/osu.Game/Rulesets/UI/RulesetInputManager.cs b/osu.Game/Rulesets/UI/RulesetInputManager.cs index 2e9986ada6..590a305f77 100644 --- a/osu.Game/Rulesets/UI/RulesetInputManager.cs +++ b/osu.Game/Rulesets/UI/RulesetInputManager.cs @@ -191,6 +191,8 @@ namespace osu.Game.Rulesets.UI public void Attach(KeysPerSecondCalculator calculator) { + if (calculator == null) return; + var listener = new ActionListener(); KeyBindingContainer.Add(listener); diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 1cddbcac41..458e19826b 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -50,7 +50,8 @@ namespace osu.Game.Screens.Play public readonly HoldForMenuButton HoldToQuit; public readonly PlayerSettingsOverlay PlayerSettingsOverlay; - private KeysPerSecondCalculator keysPerSecondCalculator; + [Resolved(canBeNull: true)] + private KeysPerSecondCalculator keysPerSecondCalculator { get; set; } public Bindable ShowHealthBar = new Bindable(true); @@ -130,10 +131,8 @@ namespace osu.Game.Screens.Play } [BackgroundDependencyLoader(true)] - private void load(OsuConfigManager config, INotificationOverlay notificationOverlay, KeysPerSecondCalculator calculator) + private void load(OsuConfigManager config, INotificationOverlay notificationOverlay) { - keysPerSecondCalculator = calculator; - if (drawableRuleset != null) { BindDrawableRuleset(drawableRuleset); From 037f56077bdbcc993e46f23fd682e43fc2f86941 Mon Sep 17 00:00:00 2001 From: MBmasher Date: Fri, 12 Aug 2022 13:29:04 +1000 Subject: [PATCH 087/420] Apply Flashlight grid nerf --- .../Difficulty/Evaluators/FlashlightEvaluator.cs | 12 ++++++++++++ .../Difficulty/Skills/Flashlight.cs | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/FlashlightEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/FlashlightEvaluator.cs index fcf4179a3b..f1ae68ec73 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/FlashlightEvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/FlashlightEvaluator.cs @@ -18,11 +18,14 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators private const double min_velocity = 0.5; private const double slider_multiplier = 1.3; + private const double min_grid_multiplier = 0.35; + /// /// Evaluates the difficulty of memorising and hitting an object, based on: /// /// distance between a number of previous objects and the current object, /// the visual opacity of the current object, + /// the angle made by the current object, /// length and speed of the current object (for sliders), /// and whether the hidden mod is enabled. /// @@ -77,6 +80,15 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators if (hidden) result *= 1.0 + hidden_bonus; + // Nerf patterns with angles that are commonly used in grid maps. + // 0 deg, 60 deg, 120 deg and 180 deg are commonly used in hexgrid maps. + // 0 deg, 45 deg, 90 deg, 135 deg and 180 deg are commonly used in squaregrid maps. + if (osuCurrent.Angle != null) { + double hexgrid_multiplier = 1.0 - Math.Pow(Math.Cos((180 / 60.0) * (double)(osuCurrent.Angle)), 20.0); + double squaregrid_multiplier = 1.0 - Math.Pow(Math.Cos((180 / 45.0) * (double)(osuCurrent.Angle)), 20.0); + result *= (1.0 - min_grid_multiplier) * hexgrid_multiplier * squaregrid_multiplier + min_grid_multiplier; + } + double sliderBonus = 0.0; if (osuCurrent.BaseObject is Slider osuSlider) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs index 84ef109598..03130031ea 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs @@ -26,7 +26,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills hasHiddenMod = mods.Any(m => m is OsuModHidden); } - private double skillMultiplier => 0.05; + private double skillMultiplier => 0.06; private double strainDecayBase => 0.15; private double currentStrain; From f70588a423b50c20030ab621ad6dd2d549ad935b Mon Sep 17 00:00:00 2001 From: MBmasher Date: Fri, 12 Aug 2022 14:08:32 +1000 Subject: [PATCH 088/420] Add newline before brace --- .../Difficulty/Evaluators/FlashlightEvaluator.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/FlashlightEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/FlashlightEvaluator.cs index f1ae68ec73..29434dcf49 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/FlashlightEvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/FlashlightEvaluator.cs @@ -83,7 +83,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators // Nerf patterns with angles that are commonly used in grid maps. // 0 deg, 60 deg, 120 deg and 180 deg are commonly used in hexgrid maps. // 0 deg, 45 deg, 90 deg, 135 deg and 180 deg are commonly used in squaregrid maps. - if (osuCurrent.Angle != null) { + if (osuCurrent.Angle != null) + { double hexgrid_multiplier = 1.0 - Math.Pow(Math.Cos((180 / 60.0) * (double)(osuCurrent.Angle)), 20.0); double squaregrid_multiplier = 1.0 - Math.Pow(Math.Cos((180 / 45.0) * (double)(osuCurrent.Angle)), 20.0); result *= (1.0 - min_grid_multiplier) * hexgrid_multiplier * squaregrid_multiplier + min_grid_multiplier; From 21c5fed45f48830d24ec448840b22f4b38e7504e Mon Sep 17 00:00:00 2001 From: MBmasher Date: Fri, 12 Aug 2022 14:09:16 +1000 Subject: [PATCH 089/420] Adjust capitalisation --- .../Difficulty/Evaluators/FlashlightEvaluator.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/FlashlightEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/FlashlightEvaluator.cs index 29434dcf49..9d2696c978 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/FlashlightEvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/FlashlightEvaluator.cs @@ -85,9 +85,9 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators // 0 deg, 45 deg, 90 deg, 135 deg and 180 deg are commonly used in squaregrid maps. if (osuCurrent.Angle != null) { - double hexgrid_multiplier = 1.0 - Math.Pow(Math.Cos((180 / 60.0) * (double)(osuCurrent.Angle)), 20.0); - double squaregrid_multiplier = 1.0 - Math.Pow(Math.Cos((180 / 45.0) * (double)(osuCurrent.Angle)), 20.0); - result *= (1.0 - min_grid_multiplier) * hexgrid_multiplier * squaregrid_multiplier + min_grid_multiplier; + double hexgridMultiplier = 1.0 - Math.Pow(Math.Cos((180 / 60.0) * (double)(osuCurrent.Angle)), 20.0); + double squaregridMultiplier = 1.0 - Math.Pow(Math.Cos((180 / 45.0) * (double)(osuCurrent.Angle)), 20.0); + result *= (1.0 - min_grid_multiplier) * hexgridMultiplier * squaregridMultiplier + min_grid_multiplier; } double sliderBonus = 0.0; From 78fe72476de7ea3b591596480059741182259b45 Mon Sep 17 00:00:00 2001 From: Pasi4K5 Date: Sat, 13 Aug 2022 00:01:40 +0200 Subject: [PATCH 090/420] Adjust parameters --- osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs index 716fa990c9..3c0642ca15 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs @@ -46,9 +46,9 @@ namespace osu.Game.Rulesets.Osu.Mods if (i == 0 || (positionInfos[Math.Max(0, i - 2)].HitObject.IndexInCurrentCombo > 1 && positionInfos[i - 1].HitObject.NewCombo && rng.NextDouble() < 0.6) || OsuHitObjectGenerationUtils.IsHitObjectOnBeat(osuBeatmap, positionInfos[i - 1].HitObject, true) || - (OsuHitObjectGenerationUtils.IsHitObjectOnBeat(osuBeatmap, positionInfos[i - 1].HitObject) && rng.NextDouble() < 0.3)) + (OsuHitObjectGenerationUtils.IsHitObjectOnBeat(osuBeatmap, positionInfos[i - 1].HitObject) && rng.NextDouble() < 0.4)) { - sequenceOffset = OsuHitObjectGenerationUtils.RandomGaussian(rng, 0, 0.0012f); + sequenceOffset = OsuHitObjectGenerationUtils.RandomGaussian(rng, 0, 0.001f); flowDirection = !flowDirection; } From 8c624d3269902ea8f83e874fbfebd204c97b8206 Mon Sep 17 00:00:00 2001 From: Pasi4K5 Date: Sat, 13 Aug 2022 00:57:49 +0200 Subject: [PATCH 091/420] Add comments and improve code readability --- osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs | 62 +++++++++++++++++----- 1 file changed, 48 insertions(+), 14 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs index 3c0642ca15..0a89f3eb43 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.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.Utils; using osu.Game.Beatmaps; @@ -29,7 +30,7 @@ namespace osu.Game.Rulesets.Osu.Mods public void ApplyToBeatmap(IBeatmap beatmap) { - if (!(beatmap is OsuBeatmap osuBeatmap)) + if (beatmap is not OsuBeatmap osuBeatmap) return; Seed.Value ??= RNG.Next(); @@ -38,17 +39,17 @@ namespace osu.Game.Rulesets.Osu.Mods var positionInfos = OsuHitObjectGenerationUtils.GeneratePositionInfos(osuBeatmap.HitObjects); - float sequenceOffset = 0; + // Offsets the angles of all hit objects in a "section" by the same amount. + float sectionOffset = 0; + + // Whether the angles are positive or negative (clockwise or counter-clockwise flow). bool flowDirection = false; for (int i = 0; i < positionInfos.Count; i++) { - if (i == 0 || - (positionInfos[Math.Max(0, i - 2)].HitObject.IndexInCurrentCombo > 1 && positionInfos[i - 1].HitObject.NewCombo && rng.NextDouble() < 0.6) || - OsuHitObjectGenerationUtils.IsHitObjectOnBeat(osuBeatmap, positionInfos[i - 1].HitObject, true) || - (OsuHitObjectGenerationUtils.IsHitObjectOnBeat(osuBeatmap, positionInfos[i - 1].HitObject) && rng.NextDouble() < 0.4)) + if (shouldStartNewSection(osuBeatmap, positionInfos, i, 0.6f, 0.4f)) { - sequenceOffset = OsuHitObjectGenerationUtils.RandomGaussian(rng, 0, 0.001f); + sectionOffset = OsuHitObjectGenerationUtils.RandomGaussian(rng, 0, 0.001f); flowDirection = !flowDirection; } @@ -59,21 +60,25 @@ namespace osu.Game.Rulesets.Osu.Mods } else { + // Offsets only the angle of the current hit object if a flow change occurs. float flowChangeOffset = 0; + + // Offsets only the angle of the current hit object. float oneTimeOffset = OsuHitObjectGenerationUtils.RandomGaussian(rng, 0, 0.002f); - if (positionInfos[Math.Max(0, i - 2)].HitObject.IndexInCurrentCombo > 1 && positionInfos[i - 1].HitObject.NewCombo && rng.NextDouble() < 0.6) + if (shouldApplyFlowChange(positionInfos, i, 0.6f)) { flowChangeOffset = OsuHitObjectGenerationUtils.RandomGaussian(rng, 0, 0.002f); flowDirection = !flowDirection; } - positionInfos[i].RelativeAngle = getRelativeTargetAngle( - positionInfos[i].DistanceFromPrevious, - (sequenceOffset + oneTimeOffset) * positionInfos[i].DistanceFromPrevious + - flowChangeOffset * (playfield_diagonal - positionInfos[i].DistanceFromPrevious), - flowDirection - ); + float totalOffset = + // sectionOffset and oneTimeOffset should mainly affect patterns with large spacing. + (sectionOffset + oneTimeOffset) * positionInfos[i].DistanceFromPrevious + + // flowChangeOffset should mainly affect streams. + flowChangeOffset * (playfield_diagonal - positionInfos[i].DistanceFromPrevious); + + positionInfos[i].RelativeAngle = getRelativeTargetAngle(positionInfos[i].DistanceFromPrevious, totalOffset, flowDirection); } } @@ -89,5 +94,34 @@ namespace osu.Game.Rulesets.Osu.Mods float relativeAngle = (float)Math.PI - angle; return flowDirection ? -relativeAngle : relativeAngle; } + + /// + /// A new section should be started...
+ /// ...at the beginning of the .
+ /// ...on every combo start with a probability of (excluding new-combo-spam and 1-2-combos).
+ /// ...on every downbeat.
+ /// ...on every beat with a probability of .
+ ///
+ /// Whether a new section should be started at the current . + private bool shouldStartNewSection( + OsuBeatmap beatmap, + IReadOnlyList positionInfos, + int i, + float newComboProbability, + float beatProbability + ) => + i == 0 || + (positionInfos[Math.Max(0, i - 2)].HitObject.IndexInCurrentCombo > 1 && positionInfos[i - 1].HitObject.NewCombo && rng?.NextDouble() < newComboProbability) || + OsuHitObjectGenerationUtils.IsHitObjectOnBeat(beatmap, positionInfos[i - 1].HitObject, true) || + (OsuHitObjectGenerationUtils.IsHitObjectOnBeat(beatmap, positionInfos[i - 1].HitObject) && rng?.NextDouble() < beatProbability); + + /// + /// A flow change should occur on every combo start with a probability of (excluding new-combo-spam and 1-2-combos). + /// + /// Whether a flow change should be applied at the current . + private bool shouldApplyFlowChange(IReadOnlyList positionInfos, int i, float probability) => + positionInfos[Math.Max(0, i - 2)].HitObject.IndexInCurrentCombo > 1 && + positionInfos[i - 1].HitObject.NewCombo && + rng?.NextDouble() < probability; } } From 7a41b9f25a35c375670103baad777e15ea231e59 Mon Sep 17 00:00:00 2001 From: Pasi4K5 Date: Sat, 13 Aug 2022 03:11:58 +0200 Subject: [PATCH 092/420] Adjust angle and `sectionOffset` calculations --- osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs index 0a89f3eb43..2be1ad231a 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs @@ -49,7 +49,7 @@ namespace osu.Game.Rulesets.Osu.Mods { if (shouldStartNewSection(osuBeatmap, positionInfos, i, 0.6f, 0.4f)) { - sectionOffset = OsuHitObjectGenerationUtils.RandomGaussian(rng, 0, 0.001f); + sectionOffset = OsuHitObjectGenerationUtils.RandomGaussian(rng, 0, 0.0008f); flowDirection = !flowDirection; } @@ -90,7 +90,7 @@ namespace osu.Game.Rulesets.Osu.Mods /// Whether the relative angle should be positive or negative. private static float getRelativeTargetAngle(float targetDistance, float offset, bool flowDirection) { - float angle = (float)(2.16 / (1 + 200 * Math.Exp(0.036 * (targetDistance - 320))) + 0.5 + offset); + float angle = (float)(2.16 / (1 + 200 * Math.Exp(0.036 * (targetDistance - 310))) + 0.5 + offset); float relativeAngle = (float)Math.PI - angle; return flowDirection ? -relativeAngle : relativeAngle; } From e08f71797ec89bbcf41be2689a6f9d939623d4ba Mon Sep 17 00:00:00 2001 From: Ryuki Date: Sat, 13 Aug 2022 04:27:26 +0200 Subject: [PATCH 093/420] Change displayed metric from "KPS" to "clicks/s" --- .../HUD/KeysPerSecond/KeysPerSecondCounter.cs | 25 ++++++++++++++++--- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/KeysPerSecond/KeysPerSecondCounter.cs b/osu.Game/Screens/Play/HUD/KeysPerSecond/KeysPerSecondCounter.cs index 7bd4d41242..a5c122f5b1 100644 --- a/osu.Game/Screens/Play/HUD/KeysPerSecond/KeysPerSecondCounter.cs +++ b/osu.Game/Screens/Play/HUD/KeysPerSecond/KeysPerSecondCounter.cs @@ -80,13 +80,30 @@ namespace osu.Game.Screens.Play.HUD.KeysPerSecond Origin = Anchor.BottomLeft, Font = OsuFont.Numeric.With(size: 16, fixedWidth: true) }, - new OsuSpriteText + new FillFlowContainer { Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft, - Font = OsuFont.Numeric.With(size: 8, fixedWidth: true), - Text = @"KPS", - Padding = new MarginPadding { Bottom = 1.5f }, // align baseline better + Direction = FillDirection.Vertical, + AutoSizeAxes = Axes.Both, + Children = new Drawable[] + { + new OsuSpriteText + { + Anchor = Anchor.TopLeft, + Origin = Anchor.TopLeft, + Font = OsuFont.Numeric.With(size: 6, fixedWidth: false), + Text = @"clicks", + }, + new OsuSpriteText + { + Anchor = Anchor.TopLeft, + Origin = Anchor.TopLeft, + Font = OsuFont.Numeric.With(size: 6, fixedWidth: false), + Text = @"/sec", + Padding = new MarginPadding { Bottom = 3f }, // align baseline better + } + } } } }; From fa2ebe1d5f8fde264e4253f16195fb73f9db0b35 Mon Sep 17 00:00:00 2001 From: Josh Date: Sat, 13 Aug 2022 18:02:29 +0800 Subject: [PATCH 094/420] add basic touch functionality --- osu.Game.Rulesets.Catch/CatchInputManager.cs | 2 ++ osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs | 8 ++++++++ 2 files changed, 10 insertions(+) diff --git a/osu.Game.Rulesets.Catch/CatchInputManager.cs b/osu.Game.Rulesets.Catch/CatchInputManager.cs index 86f35c8cee..5b62154a34 100644 --- a/osu.Game.Rulesets.Catch/CatchInputManager.cs +++ b/osu.Game.Rulesets.Catch/CatchInputManager.cs @@ -4,11 +4,13 @@ #nullable disable using System.ComponentModel; +using osu.Framework.Allocation; using osu.Framework.Input.Bindings; using osu.Game.Rulesets.UI; namespace osu.Game.Rulesets.Catch { + [Cached] public class CatchInputManager : RulesetInputManager { public CatchInputManager(RulesetInfo ruleset) diff --git a/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs b/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs index b6cea92173..83dc5770a6 100644 --- a/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs @@ -4,6 +4,7 @@ #nullable disable using System.Collections.Generic; +using osu.Framework.Allocation; using osu.Framework.Input; using osu.Game.Beatmaps; using osu.Game.Configuration; @@ -32,6 +33,13 @@ namespace osu.Game.Rulesets.Catch.UI TimeRange.Value = IBeatmapDifficultyInfo.DifficultyRange(beatmap.Difficulty.ApproachRate, 1800, 1200, 450); } + [BackgroundDependencyLoader] + private void load() + { + KeyBindingInputManager.Add(new TouchInputField()); + } + + protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new CatchFramedReplayInputHandler(replay); protected override ReplayRecorder CreateReplayRecorder(Score score) => new CatchReplayRecorder(score, (CatchPlayfield)Playfield); From 757d236e14460ad5af6a1f3b7b901c713cd492d6 Mon Sep 17 00:00:00 2001 From: Josh Date: Sat, 13 Aug 2022 18:55:31 +0800 Subject: [PATCH 095/420] Add the UI file --- osu.Game.Rulesets.Catch/UI/TouchInputField.cs | 280 ++++++++++++++++++ 1 file changed, 280 insertions(+) create mode 100644 osu.Game.Rulesets.Catch/UI/TouchInputField.cs diff --git a/osu.Game.Rulesets.Catch/UI/TouchInputField.cs b/osu.Game.Rulesets.Catch/UI/TouchInputField.cs new file mode 100644 index 0000000000..1219ebd02d --- /dev/null +++ b/osu.Game.Rulesets.Catch/UI/TouchInputField.cs @@ -0,0 +1,280 @@ +// Copyright (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.Diagnostics; +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.Input.Events; +using osu.Game.Graphics; +using osuTK.Graphics; +using osuTK; +using System.Collections.Generic; + +namespace osu.Game.Rulesets.Catch.UI +{ + public class TouchInputField : VisibilityContainer + { + public enum TouchCatchAction + { + MoveLeft = 0, + MoveRight = 1, + DashLeft = 2, + DashRight = 3, + None = 4 + } + + private Dictionary trackedActions = new Dictionary(); + + private KeyBindingContainer keyBindingContainer = null!; + + private Container mainContent = null!; + + // Fill values with null because UI is not declared in constructor + private ArrowHitbox leftBox = null!; + private ArrowHitbox rightBox = null!; + private ArrowHitbox leftDashBox = null!; + private ArrowHitbox rightDashBox = null!; + + [BackgroundDependencyLoader] + private void load(CatchInputManager catchInputManager, OsuColour colours) + { + Show(); + Debug.Assert(catchInputManager.KeyBindingContainer != null); + + keyBindingContainer = catchInputManager.KeyBindingContainer; + + // Container should handle input everywhere. + RelativeSizeAxes = Axes.Both; + + + Children = new Drawable[] + { + mainContent = new Container + { + RelativeSizeAxes = Axes.Both, + RelativePositionAxes = Axes.Both, + Children = new Drawable[] + { + leftBox = new ArrowHitbox(TouchCatchAction.MoveLeft, ref trackedActions, colours.Blue) + { + Anchor = Anchor.TopLeft, + Origin = Anchor.CentreLeft, + X = 0, + RelativePositionAxes = Axes.Both, + Size = new Vector2(100.0f, 100.0f) + }, + rightBox = new ArrowHitbox(TouchCatchAction.MoveRight, ref trackedActions, colours.Blue) + { + Anchor = Anchor.TopRight, + Origin = Anchor.CentreRight, + X = 0, + RelativePositionAxes = Axes.Both, + Size = new Vector2(100.0f, 100.0f), + }, + leftDashBox = new ArrowHitbox(TouchCatchAction.DashLeft, ref trackedActions, colours.Pink) + { + Anchor = Anchor.TopLeft, + Origin = Anchor.CentreLeft, + X = 0.1f, + RelativePositionAxes = Axes.Both, + Size = new Vector2(100.0f, 100.0f), + }, + rightDashBox = new ArrowHitbox(TouchCatchAction.DashRight, ref trackedActions, colours.Pink) + { + Anchor = Anchor.TopRight, + Origin = Anchor.CentreRight, + X = -0.1f, + RelativePositionAxes = Axes.Both, + Size = new Vector2(100.0f, 100.0f), + }, + } + }, + }; + } + + protected override bool OnKeyDown(KeyDownEvent e) + { + // Hide whenever the keyboard is used. + Hide(); + return false; + } + + protected override bool OnMouseDown(MouseDownEvent e) + { + if (getTouchCatchActionFromInput(e.MousePosition) != TouchCatchAction.None) + return false; + + handleDown(e.Button, e.ScreenSpaceMousePosition); + return true; + } + + protected override void OnMouseUp(MouseUpEvent e) + { + if (getTouchCatchActionFromInput(e.MousePosition) != TouchCatchAction.None) + return; + + handleUp(e.Button); + base.OnMouseUp(e); + } + + protected override void OnTouchMove(TouchMoveEvent e) + { + // I'm not sure if this is posible but let's be safe + if (!trackedActions.ContainsKey(e.Touch.Source)) + trackedActions.Add(e.Touch.Source, TouchCatchAction.None); + + trackedActions[e.Touch.Source] = getTouchCatchActionFromInput(e.MousePosition); + + calculateActiveKeys(); + + base.OnTouchMove(e); + } + + protected override bool OnTouchDown(TouchDownEvent e) + { + handleDown(e.Touch.Source, e.ScreenSpaceTouchDownPosition); + return true; + } + + protected override void OnTouchUp(TouchUpEvent e) + { + handleUp(e.Touch.Source); + base.OnTouchUp(e); + } + + private CatchAction removeDashFromAction(TouchCatchAction touchCatchAction) + { + if (touchCatchAction == TouchCatchAction.DashLeft || touchCatchAction == TouchCatchAction.MoveLeft) + return CatchAction.MoveLeft; + return CatchAction.MoveRight; + } + + private void calculateActiveKeys() + { + if (trackedActions.ContainsValue(TouchCatchAction.DashLeft) || trackedActions.ContainsValue(TouchCatchAction.MoveLeft)) + keyBindingContainer.TriggerPressed(CatchAction.MoveLeft); + else + keyBindingContainer.TriggerReleased(CatchAction.MoveLeft); + + if (trackedActions.ContainsValue(TouchCatchAction.DashRight) || trackedActions.ContainsValue(TouchCatchAction.MoveRight)) + keyBindingContainer.TriggerPressed(CatchAction.MoveRight); + else + keyBindingContainer.TriggerReleased(CatchAction.MoveRight); + + if (trackedActions.ContainsValue(TouchCatchAction.DashRight) || trackedActions.ContainsValue(TouchCatchAction.DashLeft)) + keyBindingContainer.TriggerPressed(CatchAction.Dash); + else + keyBindingContainer.TriggerReleased(CatchAction.Dash); + } + + private void handleDown(object source, Vector2 position) + { + Show(); + + TouchCatchAction catchAction = getTouchCatchActionFromInput(position); + + // Not too sure how this can happen, but let's avoid throwing. + if (trackedActions.ContainsKey(source)) + return; + + trackedActions.Add(source, catchAction); + calculateActiveKeys(); + } + + private void handleUp(object source) + { + trackedActions.Remove(source); + + calculateActiveKeys(); + } + + private TouchCatchAction getTouchCatchActionFromInput(Vector2 inputPosition) + { + if (leftDashBox.Contains(inputPosition)) + return TouchCatchAction.DashLeft; + if (rightDashBox.Contains(inputPosition)) + return TouchCatchAction.DashRight; + if (leftBox.Contains(inputPosition)) + return TouchCatchAction.MoveLeft; + if (rightBox.Contains(inputPosition)) + return TouchCatchAction.MoveRight; + return TouchCatchAction.None; + } + + protected override void PopIn() + { + mainContent.FadeIn(500, Easing.OutQuint); + } + + protected override void PopOut() + { + mainContent.FadeOut(300); + } + + private class ArrowHitbox : CompositeDrawable, IKeyBindingHandler + { + private readonly TouchCatchAction handledAction; + + private readonly Box overlay; + + private readonly Dictionary trackedActions; + + private bool isHiglighted = false; + + public ArrowHitbox(TouchCatchAction handledAction, ref Dictionary trackedActions, Color4 colour) + { + this.handledAction = handledAction; + this.trackedActions = trackedActions; + + InternalChildren = new Drawable[] + { + new Container + { + Width = 1, + Height = 1, + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + overlay = new Box + { + Alpha = 0, + Colour = colour.Multiply(1.4f).Darken(2.8f), + Blending = BlendingParameters.Additive, + Size = new Vector2(100.0f, 100.0f), + }, + new Box + { + Alpha = 0.5f, + Colour = colour, + Size = new Vector2(100.0f, 100.0f), + } + } + } + }; + } + + public bool OnPressed(KeyBindingPressEvent _) + { + if (trackedActions.ContainsValue(handledAction)) + { + isHiglighted = true; + overlay.FadeTo(0.5f, 80, Easing.OutQuint); + } + return false; + } + + public void OnReleased(KeyBindingReleaseEvent _) + { + if (isHiglighted && !trackedActions.ContainsValue(handledAction)) + { + isHiglighted = false; + overlay.FadeOut(1000, Easing.Out); + } + } + } + } +} From 09e45f39b2aa8c1bb3f359133c3ca30b564629ea Mon Sep 17 00:00:00 2001 From: Josh Date: Sat, 13 Aug 2022 19:55:47 +0800 Subject: [PATCH 096/420] Add the touchinputfield file because it was untracked --- osu.Game.Rulesets.Catch/UI/TouchInputField.cs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/osu.Game.Rulesets.Catch/UI/TouchInputField.cs b/osu.Game.Rulesets.Catch/UI/TouchInputField.cs index 1219ebd02d..cb853d1abd 100644 --- a/osu.Game.Rulesets.Catch/UI/TouchInputField.cs +++ b/osu.Game.Rulesets.Catch/UI/TouchInputField.cs @@ -146,13 +146,6 @@ namespace osu.Game.Rulesets.Catch.UI base.OnTouchUp(e); } - private CatchAction removeDashFromAction(TouchCatchAction touchCatchAction) - { - if (touchCatchAction == TouchCatchAction.DashLeft || touchCatchAction == TouchCatchAction.MoveLeft) - return CatchAction.MoveLeft; - return CatchAction.MoveRight; - } - private void calculateActiveKeys() { if (trackedActions.ContainsValue(TouchCatchAction.DashLeft) || trackedActions.ContainsValue(TouchCatchAction.MoveLeft)) From d5f10cbb9d1396ab10c32981c436729fb4c2c639 Mon Sep 17 00:00:00 2001 From: Ryuki Date: Sun, 14 Aug 2022 18:53:00 +0200 Subject: [PATCH 097/420] Revert 787dee24 and initialize calculator in `HUDOverlay` --- osu.Game/Screens/Play/HUDOverlay.cs | 5 +++-- osu.Game/Screens/Play/Player.cs | 7 ------- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 458e19826b..20f7f7d6c2 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -50,8 +50,8 @@ namespace osu.Game.Screens.Play public readonly HoldForMenuButton HoldToQuit; public readonly PlayerSettingsOverlay PlayerSettingsOverlay; - [Resolved(canBeNull: true)] - private KeysPerSecondCalculator keysPerSecondCalculator { get; set; } + [Cached] + private readonly KeysPerSecondCalculator keysPerSecondCalculator; public Bindable ShowHealthBar = new Bindable(true); @@ -127,6 +127,7 @@ namespace osu.Game.Screens.Play HoldToQuit = CreateHoldForMenuButton(), } }, + keysPerSecondCalculator = new KeysPerSecondCalculator() }; } diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index ba7e01a803..e3844088e2 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -34,7 +34,6 @@ using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; using osu.Game.Scoring; using osu.Game.Scoring.Legacy; -using osu.Game.Screens.Play.HUD.KeysPerSecond; using osu.Game.Screens.Ranking; using osu.Game.Skinning; using osu.Game.Users; @@ -123,8 +122,6 @@ namespace osu.Game.Screens.Play private SkipOverlay skipIntroOverlay; private SkipOverlay skipOutroOverlay; - protected KeysPerSecondCalculator KeysPerSecondCalculator { get; private set; } - protected ScoreProcessor ScoreProcessor { get; private set; } protected HealthProcessor HealthProcessor { get; private set; } @@ -229,9 +226,6 @@ namespace osu.Game.Screens.Play dependencies.CacheAs(ScoreProcessor); - KeysPerSecondCalculator = new KeysPerSecondCalculator(); - dependencies.CacheAs(KeysPerSecondCalculator); - HealthProcessor = ruleset.CreateHealthProcessor(playableBeatmap.HitObjects[0].StartTime); HealthProcessor.ApplyBeatmap(playableBeatmap); @@ -448,7 +442,6 @@ namespace osu.Game.Screens.Play OnRetry = Restart, OnQuit = () => PerformExit(true), }, - KeysPerSecondCalculator }, }; From 5106c00a9c5010f751b428a7b0726d95bc5b7620 Mon Sep 17 00:00:00 2001 From: Pasi4K5 Date: Sun, 14 Aug 2022 19:02:29 +0200 Subject: [PATCH 098/420] Improve code quality --- osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs | 54 +++++++++++----------- 1 file changed, 28 insertions(+), 26 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs index 2be1ad231a..ce54d87dfa 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs @@ -47,7 +47,7 @@ namespace osu.Game.Rulesets.Osu.Mods for (int i = 0; i < positionInfos.Count; i++) { - if (shouldStartNewSection(osuBeatmap, positionInfos, i, 0.6f, 0.4f)) + if (shouldStartNewSection(osuBeatmap, positionInfos, i)) { sectionOffset = OsuHitObjectGenerationUtils.RandomGaussian(rng, 0, 0.0008f); flowDirection = !flowDirection; @@ -66,7 +66,7 @@ namespace osu.Game.Rulesets.Osu.Mods // Offsets only the angle of the current hit object. float oneTimeOffset = OsuHitObjectGenerationUtils.RandomGaussian(rng, 0, 0.002f); - if (shouldApplyFlowChange(positionInfos, i, 0.6f)) + if (shouldApplyFlowChange(positionInfos, i)) { flowChangeOffset = OsuHitObjectGenerationUtils.RandomGaussian(rng, 0, 0.002f); flowDirection = !flowDirection; @@ -95,33 +95,35 @@ namespace osu.Game.Rulesets.Osu.Mods return flowDirection ? -relativeAngle : relativeAngle; } - /// - /// A new section should be started...
- /// ...at the beginning of the .
- /// ...on every combo start with a probability of (excluding new-combo-spam and 1-2-combos).
- /// ...on every downbeat.
- /// ...on every beat with a probability of .
- ///
/// Whether a new section should be started at the current . - private bool shouldStartNewSection( - OsuBeatmap beatmap, - IReadOnlyList positionInfos, - int i, - float newComboProbability, - float beatProbability - ) => - i == 0 || - (positionInfos[Math.Max(0, i - 2)].HitObject.IndexInCurrentCombo > 1 && positionInfos[i - 1].HitObject.NewCombo && rng?.NextDouble() < newComboProbability) || - OsuHitObjectGenerationUtils.IsHitObjectOnBeat(beatmap, positionInfos[i - 1].HitObject, true) || - (OsuHitObjectGenerationUtils.IsHitObjectOnBeat(beatmap, positionInfos[i - 1].HitObject) && rng?.NextDouble() < beatProbability); + private bool shouldStartNewSection(OsuBeatmap beatmap, IReadOnlyList positionInfos, int i) + { + if (i == 0) + return true; + + // Exclude new-combo-spam and 1-2-combos. + bool previousObjectStartedCombo = positionInfos[Math.Max(0, i - 2)].HitObject.IndexInCurrentCombo > 1 && + positionInfos[i - 1].HitObject.NewCombo; + bool previousObjectWasOnDownbeat = OsuHitObjectGenerationUtils.IsHitObjectOnBeat(beatmap, positionInfos[i - 1].HitObject, true); + bool previousObjectWasOnBeat = OsuHitObjectGenerationUtils.IsHitObjectOnBeat(beatmap, positionInfos[i - 1].HitObject); + + return (previousObjectStartedCombo && randomBool(0.6f)) || + previousObjectWasOnDownbeat || + (previousObjectWasOnBeat && randomBool(0.4f)); + } - /// - /// A flow change should occur on every combo start with a probability of (excluding new-combo-spam and 1-2-combos). - /// /// Whether a flow change should be applied at the current . - private bool shouldApplyFlowChange(IReadOnlyList positionInfos, int i, float probability) => - positionInfos[Math.Max(0, i - 2)].HitObject.IndexInCurrentCombo > 1 && - positionInfos[i - 1].HitObject.NewCombo && + private bool shouldApplyFlowChange(IReadOnlyList positionInfos, int i) + { + // Exclude new-combo-spam and 1-2-combos. + bool previousObjectStartedCombo = positionInfos[Math.Max(0, i - 2)].HitObject.IndexInCurrentCombo > 1 && + positionInfos[i - 1].HitObject.NewCombo; + + return previousObjectStartedCombo && randomBool(0.6f); + } + + /// true with a probability of , false otherwise. + private bool randomBool(float probability) => rng?.NextDouble() < probability; } } From 9dc806506e55ca13b39241b50e71a1b7da7a332b Mon Sep 17 00:00:00 2001 From: Ryuki Date: Sun, 14 Aug 2022 19:09:34 +0200 Subject: [PATCH 099/420] Make `ActionListener` and `KeysPerSecondCalculator` not rely on events to add timestamps --- .../Visual/Gameplay/TestSceneKeysPerSecond.cs | 13 +++++----- osu.Game/Rulesets/UI/RulesetInputManager.cs | 11 +++++--- .../KeysPerSecond/KeysPerSecondCalculator.cs | 25 ++++++------------- 3 files changed, 21 insertions(+), 28 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneKeysPerSecond.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneKeysPerSecond.cs index 5c1ca18dbc..ac7b7521c9 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneKeysPerSecond.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneKeysPerSecond.cs @@ -61,10 +61,7 @@ namespace osu.Game.Tests.Visual.Gameplay { dependencyContainer!.Children = new Drawable[] { - calculator = new KeysPerSecondCalculator - { - Listener = listener = new ManualInputListener() - }, + calculator = new KeysPerSecondCalculator(), new DependencyProvidingContainer { RelativeSizeAxes = Axes.Both, @@ -77,6 +74,7 @@ namespace osu.Game.Tests.Visual.Gameplay } } }; + calculator!.Listener = listener = new ManualInputListener(calculator!); }); } @@ -208,9 +206,12 @@ namespace osu.Game.Tests.Visual.Gameplay private class ManualInputListener : KeysPerSecondCalculator.InputListener { - public override event Action? OnNewInput; + public void AddInput() => Calculator.AddTimestamp(); - public void AddInput() => OnNewInput?.Invoke(); + public ManualInputListener(KeysPerSecondCalculator calculator) + : base(calculator) + { + } } private class MockFrameBasedClock : ManualClock, IFrameBasedClock diff --git a/osu.Game/Rulesets/UI/RulesetInputManager.cs b/osu.Game/Rulesets/UI/RulesetInputManager.cs index 590a305f77..23580bc40a 100644 --- a/osu.Game/Rulesets/UI/RulesetInputManager.cs +++ b/osu.Game/Rulesets/UI/RulesetInputManager.cs @@ -193,7 +193,7 @@ namespace osu.Game.Rulesets.UI { if (calculator == null) return; - var listener = new ActionListener(); + var listener = new ActionListener(calculator); KeyBindingContainer.Add(listener); @@ -202,11 +202,9 @@ namespace osu.Game.Rulesets.UI public class ActionListener : KeysPerSecondCalculator.InputListener, IKeyBindingHandler { - public override event Action OnNewInput; - public bool OnPressed(KeyBindingPressEvent e) { - OnNewInput?.Invoke(); + Calculator.AddTimestamp(); return false; } @@ -214,6 +212,11 @@ namespace osu.Game.Rulesets.UI public void OnReleased(KeyBindingReleaseEvent e) { } + + public ActionListener(KeysPerSecondCalculator calculator) + : base(calculator) + { + } } #endregion diff --git a/osu.Game/Screens/Play/HUD/KeysPerSecond/KeysPerSecondCalculator.cs b/osu.Game/Screens/Play/HUD/KeysPerSecond/KeysPerSecondCalculator.cs index ecc9c6ef86..20ab09e9cc 100644 --- a/osu.Game/Screens/Play/HUD/KeysPerSecond/KeysPerSecondCalculator.cs +++ b/osu.Game/Screens/Play/HUD/KeysPerSecond/KeysPerSecondCalculator.cs @@ -29,7 +29,6 @@ namespace osu.Game.Screens.Play.HUD.KeysPerSecond { onResetRequested?.Invoke(); listener = value; - listener.OnNewInput += addTimestamp; } } @@ -43,12 +42,9 @@ namespace osu.Game.Screens.Play.HUD.KeysPerSecond { get { - if (gameplayClock != null) + if (gameplayClock?.TrueGameplayRate > 0) { - if (gameplayClock.TrueGameplayRate > 0) - { - baseRate = gameplayClock.TrueGameplayRate; - } + baseRate = gameplayClock.TrueGameplayRate; } return baseRate; @@ -71,12 +67,9 @@ namespace osu.Game.Screens.Play.HUD.KeysPerSecond { timestamps.Clear(); maxTime = double.NegativeInfinity; - - if (listener != null) - listener.OnNewInput -= addTimestamp; } - private void addTimestamp() + public void AddTimestamp() { if (workingClock == null) return; @@ -96,20 +89,16 @@ namespace osu.Game.Screens.Play.HUD.KeysPerSecond return relativeTime > 0 && relativeTime <= span; } - ~KeysPerSecondCalculator() - { - cleanUp(); - } - public abstract class InputListener : Component { - protected InputListener() + protected KeysPerSecondCalculator Calculator; + + protected InputListener(KeysPerSecondCalculator calculator) { RelativeSizeAxes = Axes.Both; Depth = float.MinValue; + Calculator = calculator; } - - public abstract event Action? OnNewInput; } } } From 2aa3a1b50d558b8c01097b7c601889848596d8f6 Mon Sep 17 00:00:00 2001 From: Ryuki Date: Sun, 14 Aug 2022 20:12:11 +0200 Subject: [PATCH 100/420] Rename all "KeysPerSecond" usages to "ClicksPerSecond" --- ...sPerSecond.cs => TestSceneClicksPerSecond.cs} | 16 ++++++++-------- osu.Game/Rulesets/UI/DrawableRuleset.cs | 4 ++-- osu.Game/Rulesets/UI/RulesetInputManager.cs | 10 +++++----- .../ClicksPerSecondCalculator.cs} | 10 +++++----- .../ClicksPerSecondCounter.cs} | 10 +++++----- osu.Game/Screens/Play/HUDOverlay.cs | 8 ++++---- 6 files changed, 29 insertions(+), 29 deletions(-) rename osu.Game.Tests/Visual/Gameplay/{TestSceneKeysPerSecond.cs => TestSceneClicksPerSecond.cs} (94%) rename osu.Game/Screens/Play/HUD/{KeysPerSecond/KeysPerSecondCalculator.cs => ClicksPerSecond/ClicksPerSecondCalculator.cs} (90%) rename osu.Game/Screens/Play/HUD/{KeysPerSecond/KeysPerSecondCounter.cs => ClicksPerSecond/ClicksPerSecondCounter.cs} (92%) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneKeysPerSecond.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneClicksPerSecond.cs similarity index 94% rename from osu.Game.Tests/Visual/Gameplay/TestSceneKeysPerSecond.cs rename to osu.Game.Tests/Visual/Gameplay/TestSceneClicksPerSecond.cs index ac7b7521c9..8a5bd1af0f 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneKeysPerSecond.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneClicksPerSecond.cs @@ -19,16 +19,16 @@ using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.UI; using osu.Game.Scoring; using osu.Game.Screens.Play; -using osu.Game.Screens.Play.HUD.KeysPerSecond; +using osu.Game.Screens.Play.HUD.ClicksPerSecond; using osuTK; namespace osu.Game.Tests.Visual.Gameplay { - public class TestSceneKeysPerSecond : OsuTestScene + public class TestSceneClicksPerSecond : OsuTestScene { private DependencyProvidingContainer? dependencyContainer; private MockFrameStableClock? mainClock; - private KeysPerSecondCalculator? calculator; + private ClicksPerSecondCalculator? calculator; private ManualInputListener? listener; [SetUpSteps] @@ -61,12 +61,12 @@ namespace osu.Game.Tests.Visual.Gameplay { dependencyContainer!.Children = new Drawable[] { - calculator = new KeysPerSecondCalculator(), + calculator = new ClicksPerSecondCalculator(), new DependencyProvidingContainer { RelativeSizeAxes = Axes.Both, - CachedDependencies = new (Type, object)[] { (typeof(KeysPerSecondCalculator), calculator) }, - Child = new KeysPerSecondCounter // For visual debugging, has no real purpose in the tests + CachedDependencies = new (Type, object)[] { (typeof(ClicksPerSecondCalculator), calculator) }, + Child = new ClicksPerSecondCounter // For visual debugging, has no real purpose in the tests { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -204,11 +204,11 @@ namespace osu.Game.Tests.Visual.Gameplay #region Mock classes - private class ManualInputListener : KeysPerSecondCalculator.InputListener + private class ManualInputListener : ClicksPerSecondCalculator.InputListener { public void AddInput() => Calculator.AddTimestamp(); - public ManualInputListener(KeysPerSecondCalculator calculator) + public ManualInputListener(ClicksPerSecondCalculator calculator) : base(calculator) { } diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index 443e4392cf..cb483bff81 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -30,7 +30,7 @@ using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; using osu.Game.Screens.Play; -using osu.Game.Screens.Play.HUD.KeysPerSecond; +using osu.Game.Screens.Play.HUD.ClicksPerSecond; using osuTK; namespace osu.Game.Rulesets.UI @@ -341,7 +341,7 @@ namespace osu.Game.Rulesets.UI public void Attach(KeyCounterDisplay keyCounter) => (KeyBindingInputManager as ICanAttachKeyCounter)?.Attach(keyCounter); - public void Attach(KeysPerSecondCalculator calculator) => (KeyBindingInputManager as ICanAttachKeyCounter)?.Attach(calculator); + public void Attach(ClicksPerSecondCalculator calculator) => (KeyBindingInputManager as ICanAttachKeyCounter)?.Attach(calculator); /// /// Creates a key conversion input manager. An exception will be thrown if a valid is not returned. diff --git a/osu.Game/Rulesets/UI/RulesetInputManager.cs b/osu.Game/Rulesets/UI/RulesetInputManager.cs index 23580bc40a..4b7ce22cfc 100644 --- a/osu.Game/Rulesets/UI/RulesetInputManager.cs +++ b/osu.Game/Rulesets/UI/RulesetInputManager.cs @@ -20,7 +20,7 @@ using osu.Game.Input.Bindings; using osu.Game.Input.Handlers; using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Play; -using osu.Game.Screens.Play.HUD.KeysPerSecond; +using osu.Game.Screens.Play.HUD.ClicksPerSecond; using static osu.Game.Input.Handlers.ReplayInputHandler; namespace osu.Game.Rulesets.UI @@ -189,7 +189,7 @@ namespace osu.Game.Rulesets.UI #region Keys per second Counter Attachment - public void Attach(KeysPerSecondCalculator calculator) + public void Attach(ClicksPerSecondCalculator calculator) { if (calculator == null) return; @@ -200,7 +200,7 @@ namespace osu.Game.Rulesets.UI calculator.Listener = listener; } - public class ActionListener : KeysPerSecondCalculator.InputListener, IKeyBindingHandler + public class ActionListener : ClicksPerSecondCalculator.InputListener, IKeyBindingHandler { public bool OnPressed(KeyBindingPressEvent e) { @@ -213,7 +213,7 @@ namespace osu.Game.Rulesets.UI { } - public ActionListener(KeysPerSecondCalculator calculator) + public ActionListener(ClicksPerSecondCalculator calculator) : base(calculator) { } @@ -262,7 +262,7 @@ namespace osu.Game.Rulesets.UI public interface ICanAttachKeyCounter { void Attach(KeyCounterDisplay keyCounter); - void Attach(KeysPerSecondCalculator calculator); + void Attach(ClicksPerSecondCalculator calculator); } public class RulesetInputManagerInputState : InputState diff --git a/osu.Game/Screens/Play/HUD/KeysPerSecond/KeysPerSecondCalculator.cs b/osu.Game/Screens/Play/HUD/ClicksPerSecond/ClicksPerSecondCalculator.cs similarity index 90% rename from osu.Game/Screens/Play/HUD/KeysPerSecond/KeysPerSecondCalculator.cs rename to osu.Game/Screens/Play/HUD/ClicksPerSecond/ClicksPerSecondCalculator.cs index 20ab09e9cc..9dc8615fb6 100644 --- a/osu.Game/Screens/Play/HUD/KeysPerSecond/KeysPerSecondCalculator.cs +++ b/osu.Game/Screens/Play/HUD/ClicksPerSecond/ClicksPerSecondCalculator.cs @@ -9,9 +9,9 @@ using osu.Framework.Graphics; using osu.Framework.Timing; using osu.Game.Rulesets.UI; -namespace osu.Game.Screens.Play.HUD.KeysPerSecond +namespace osu.Game.Screens.Play.HUD.ClicksPerSecond { - public class KeysPerSecondCalculator : Component + public class ClicksPerSecondCalculator : Component { private readonly List timestamps; @@ -56,7 +56,7 @@ namespace osu.Game.Screens.Play.HUD.KeysPerSecond public bool Ready => workingClock != null && gameplayClock != null && listener != null; public int Value => timestamps.Count(isTimestampWithinSpan); - public KeysPerSecondCalculator() + public ClicksPerSecondCalculator() { RelativeSizeAxes = Axes.Both; timestamps = new List(); @@ -91,9 +91,9 @@ namespace osu.Game.Screens.Play.HUD.KeysPerSecond public abstract class InputListener : Component { - protected KeysPerSecondCalculator Calculator; + protected ClicksPerSecondCalculator Calculator; - protected InputListener(KeysPerSecondCalculator calculator) + protected InputListener(ClicksPerSecondCalculator calculator) { RelativeSizeAxes = Axes.Both; Depth = float.MinValue; diff --git a/osu.Game/Screens/Play/HUD/KeysPerSecond/KeysPerSecondCounter.cs b/osu.Game/Screens/Play/HUD/ClicksPerSecond/ClicksPerSecondCounter.cs similarity index 92% rename from osu.Game/Screens/Play/HUD/KeysPerSecond/KeysPerSecondCounter.cs rename to osu.Game/Screens/Play/HUD/ClicksPerSecond/ClicksPerSecondCounter.cs index a5c122f5b1..049f38ba4c 100644 --- a/osu.Game/Screens/Play/HUD/KeysPerSecond/KeysPerSecondCounter.cs +++ b/osu.Game/Screens/Play/HUD/ClicksPerSecond/ClicksPerSecondCounter.cs @@ -13,27 +13,27 @@ using osu.Game.Graphics.UserInterface; using osu.Game.Skinning; using osuTK; -namespace osu.Game.Screens.Play.HUD.KeysPerSecond +namespace osu.Game.Screens.Play.HUD.ClicksPerSecond { - public class KeysPerSecondCounter : RollingCounter, ISkinnableDrawable + public class ClicksPerSecondCounter : RollingCounter, ISkinnableDrawable { private const float alpha_when_invalid = 0.3f; private readonly Bindable valid = new Bindable(); - private KeysPerSecondCalculator? calculator; + private ClicksPerSecondCalculator? calculator; protected override double RollingDuration => 350; public bool UsesFixedAnchor { get; set; } - public KeysPerSecondCounter() + public ClicksPerSecondCounter() { Current.Value = 0; } [BackgroundDependencyLoader] - private void load(OsuColour colours, KeysPerSecondCalculator calculator) + private void load(OsuColour colours, ClicksPerSecondCalculator calculator) { this.calculator = calculator; Colour = colours.BlueLighter; diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 20f7f7d6c2..b27efaf13a 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -22,7 +22,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.Screens.Play.HUD.KeysPerSecond; +using osu.Game.Screens.Play.HUD.ClicksPerSecond; using osu.Game.Skinning; using osuTK; @@ -51,7 +51,7 @@ namespace osu.Game.Screens.Play public readonly PlayerSettingsOverlay PlayerSettingsOverlay; [Cached] - private readonly KeysPerSecondCalculator keysPerSecondCalculator; + private readonly ClicksPerSecondCalculator clicksPerSecondCalculator; public Bindable ShowHealthBar = new Bindable(true); @@ -127,7 +127,7 @@ namespace osu.Game.Screens.Play HoldToQuit = CreateHoldForMenuButton(), } }, - keysPerSecondCalculator = new KeysPerSecondCalculator() + clicksPerSecondCalculator = new ClicksPerSecondCalculator() }; } @@ -265,7 +265,7 @@ namespace osu.Game.Screens.Play protected virtual void BindDrawableRuleset(DrawableRuleset drawableRuleset) { (drawableRuleset as ICanAttachKeyCounter)?.Attach(KeyCounter); - (drawableRuleset as ICanAttachKeyCounter)?.Attach(keysPerSecondCalculator); + (drawableRuleset as ICanAttachKeyCounter)?.Attach(clicksPerSecondCalculator); replayLoaded.BindTo(drawableRuleset.HasReplayLoaded); } From ff497c452fd48978b5f4b7021086f01368c4d62d Mon Sep 17 00:00:00 2001 From: Josh Date: Mon, 15 Aug 2022 17:23:29 +0800 Subject: [PATCH 101/420] Fix formatting + Add tests + fix touch UI --- .../TestSceneCatchTouchInput.cs | 45 ++++++++ .../UI/DrawableCatchRuleset.cs | 1 - osu.Game.Rulesets.Catch/UI/TouchInputField.cs | 104 +++++++++++------- 3 files changed, 110 insertions(+), 40 deletions(-) create mode 100644 osu.Game.Rulesets.Catch.Tests/TestSceneCatchTouchInput.cs diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatchTouchInput.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchTouchInput.cs new file mode 100644 index 0000000000..03d031a0a8 --- /dev/null +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchTouchInput.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. + +#nullable disable + +using NUnit.Framework; +using osu.Framework.Graphics; +using osu.Framework.Testing; +using osu.Game.Rulesets.Catch.UI; +using osu.Game.Tests.Visual; + +namespace osu.Game.Rulesets.Catch.Tests +{ + [TestFixture] + public class TestSceneCatchTouchInput : OsuTestScene + { + private TouchInputField touchInputField = null!; + + [SetUpSteps] + public void SetUpSteps() + { + AddStep("create inputfield", () => + { + Child = new CatchInputManager(new CatchRuleset().RulesetInfo) + { + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + touchInputField = new TouchInputField + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre + } + } + }; + }); + } + + [Test] + public void TestInputField() + { + AddStep("show inputfield", () => touchInputField.Show()); + } + } +} diff --git a/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs b/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs index 83dc5770a6..b84d0c60d5 100644 --- a/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs @@ -39,7 +39,6 @@ namespace osu.Game.Rulesets.Catch.UI KeyBindingInputManager.Add(new TouchInputField()); } - protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new CatchFramedReplayInputHandler(replay); protected override ReplayRecorder CreateReplayRecorder(Score score) => new CatchReplayRecorder(score, (CatchPlayfield)Playfield); diff --git a/osu.Game.Rulesets.Catch/UI/TouchInputField.cs b/osu.Game.Rulesets.Catch/UI/TouchInputField.cs index cb853d1abd..3b92ffa445 100644 --- a/osu.Game.Rulesets.Catch/UI/TouchInputField.cs +++ b/osu.Game.Rulesets.Catch/UI/TouchInputField.cs @@ -50,48 +50,70 @@ namespace osu.Game.Rulesets.Catch.UI // Container should handle input everywhere. RelativeSizeAxes = Axes.Both; - Children = new Drawable[] { mainContent = new Container { RelativeSizeAxes = Axes.Both, RelativePositionAxes = Axes.Both, + Alpha = 0, Children = new Drawable[] { - leftBox = new ArrowHitbox(TouchCatchAction.MoveLeft, ref trackedActions, colours.Blue) + new Container { - Anchor = Anchor.TopLeft, + RelativeSizeAxes = Axes.Both, + Width = 0.15f, + Height = 1, + Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, - X = 0, - RelativePositionAxes = Axes.Both, - Size = new Vector2(100.0f, 100.0f) + Children = new Drawable[] + { + leftBox = new ArrowHitbox(TouchCatchAction.MoveLeft, ref trackedActions, colours.Gray2) + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + RelativeSizeAxes = Axes.Both, + RelativePositionAxes = Axes.Both, + Width = 0.5f, + }, + leftDashBox = new ArrowHitbox(TouchCatchAction.DashLeft, ref trackedActions, colours.Gray3) + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + RelativeSizeAxes = Axes.Both, + RelativePositionAxes = Axes.Both, + Width = 0.5f, + } + } }, - rightBox = new ArrowHitbox(TouchCatchAction.MoveRight, ref trackedActions, colours.Blue) + new Container { - Anchor = Anchor.TopRight, + RelativeSizeAxes = Axes.Both, + Width = 0.15f, + Height = 1, + Anchor = Anchor.CentreRight, Origin = Anchor.CentreRight, - X = 0, - RelativePositionAxes = Axes.Both, - Size = new Vector2(100.0f, 100.0f), + Children = new Drawable[] + { + rightBox = new ArrowHitbox(TouchCatchAction.MoveRight, ref trackedActions, colours.Gray2) + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + RelativeSizeAxes = Axes.Both, + RelativePositionAxes = Axes.Both, + Width = 0.5f, + }, + rightDashBox = new ArrowHitbox(TouchCatchAction.DashRight, ref trackedActions, colours.Gray3) + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + RelativeSizeAxes = Axes.Both, + RelativePositionAxes = Axes.Both, + Width = 0.5f, + }, + } }, - leftDashBox = new ArrowHitbox(TouchCatchAction.DashLeft, ref trackedActions, colours.Pink) - { - Anchor = Anchor.TopLeft, - Origin = Anchor.CentreLeft, - X = 0.1f, - RelativePositionAxes = Axes.Both, - Size = new Vector2(100.0f, 100.0f), - }, - rightDashBox = new ArrowHitbox(TouchCatchAction.DashRight, ref trackedActions, colours.Pink) - { - Anchor = Anchor.TopRight, - Origin = Anchor.CentreRight, - X = -0.1f, - RelativePositionAxes = Axes.Both, - Size = new Vector2(100.0f, 100.0f), - }, - } + }, }, }; } @@ -99,13 +121,13 @@ namespace osu.Game.Rulesets.Catch.UI protected override bool OnKeyDown(KeyDownEvent e) { // Hide whenever the keyboard is used. - Hide(); + PopOut(); return false; } protected override bool OnMouseDown(MouseDownEvent e) { - if (getTouchCatchActionFromInput(e.MousePosition) != TouchCatchAction.None) + if (getTouchCatchActionFromInput(e.ScreenSpaceMousePosition) == TouchCatchAction.None) return false; handleDown(e.Button, e.ScreenSpaceMousePosition); @@ -114,14 +136,15 @@ namespace osu.Game.Rulesets.Catch.UI protected override void OnMouseUp(MouseUpEvent e) { - if (getTouchCatchActionFromInput(e.MousePosition) != TouchCatchAction.None) + if (getTouchCatchActionFromInput(e.ScreenSpaceMousePosition) == TouchCatchAction.None) return; handleUp(e.Button); base.OnMouseUp(e); } - protected override void OnTouchMove(TouchMoveEvent e) + /* I plan to come back to this code to add touch support + * protected override void OnTouchMove(TouchMoveEvent e) { // I'm not sure if this is posible but let's be safe if (!trackedActions.ContainsKey(e.Touch.Source)) @@ -132,7 +155,7 @@ namespace osu.Game.Rulesets.Catch.UI calculateActiveKeys(); base.OnTouchMove(e); - } + }*/ protected override bool OnTouchDown(TouchDownEvent e) { @@ -166,7 +189,7 @@ namespace osu.Game.Rulesets.Catch.UI private void handleDown(object source, Vector2 position) { - Show(); + PopIn(); TouchCatchAction catchAction = getTouchCatchActionFromInput(position); @@ -195,6 +218,7 @@ namespace osu.Game.Rulesets.Catch.UI return TouchCatchAction.MoveLeft; if (rightBox.Contains(inputPosition)) return TouchCatchAction.MoveRight; + return TouchCatchAction.None; } @@ -216,7 +240,7 @@ namespace osu.Game.Rulesets.Catch.UI private readonly Dictionary trackedActions; - private bool isHiglighted = false; + private bool isHiglighted; public ArrowHitbox(TouchCatchAction handledAction, ref Dictionary trackedActions, Color4 colour) { @@ -228,7 +252,6 @@ namespace osu.Game.Rulesets.Catch.UI new Container { Width = 1, - Height = 1, RelativeSizeAxes = Axes.Both, Children = new Drawable[] { @@ -237,13 +260,15 @@ namespace osu.Game.Rulesets.Catch.UI Alpha = 0, Colour = colour.Multiply(1.4f).Darken(2.8f), Blending = BlendingParameters.Additive, - Size = new Vector2(100.0f, 100.0f), + Width = 1, + RelativeSizeAxes = Axes.Both, }, new Box { - Alpha = 0.5f, + Alpha = 0.8f, Colour = colour, - Size = new Vector2(100.0f, 100.0f), + Width = 1, + RelativeSizeAxes = Axes.Both, } } } @@ -257,6 +282,7 @@ namespace osu.Game.Rulesets.Catch.UI isHiglighted = true; overlay.FadeTo(0.5f, 80, Easing.OutQuint); } + return false; } From be7367b90e5023004bd8352084054eaa8607911a Mon Sep 17 00:00:00 2001 From: Josh Date: Mon, 15 Aug 2022 17:52:26 +0800 Subject: [PATCH 102/420] Invert the dash and normal hits. --- osu.Game.Rulesets.Catch/UI/TouchInputField.cs | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/osu.Game.Rulesets.Catch/UI/TouchInputField.cs b/osu.Game.Rulesets.Catch/UI/TouchInputField.cs index 3b92ffa445..771902df65 100644 --- a/osu.Game.Rulesets.Catch/UI/TouchInputField.cs +++ b/osu.Game.Rulesets.Catch/UI/TouchInputField.cs @@ -68,18 +68,18 @@ namespace osu.Game.Rulesets.Catch.UI Origin = Anchor.CentreLeft, Children = new Drawable[] { - leftBox = new ArrowHitbox(TouchCatchAction.MoveLeft, ref trackedActions, colours.Gray2) + leftBox = new ArrowHitbox(TouchCatchAction.MoveLeft, ref trackedActions, colours.Gray3) { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, RelativeSizeAxes = Axes.Both, RelativePositionAxes = Axes.Both, Width = 0.5f, }, - leftDashBox = new ArrowHitbox(TouchCatchAction.DashLeft, ref trackedActions, colours.Gray3) + leftDashBox = new ArrowHitbox(TouchCatchAction.DashLeft, ref trackedActions, colours.Gray2) { - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, RelativeSizeAxes = Axes.Both, RelativePositionAxes = Axes.Both, Width = 0.5f, @@ -95,18 +95,18 @@ namespace osu.Game.Rulesets.Catch.UI Origin = Anchor.CentreRight, Children = new Drawable[] { - rightBox = new ArrowHitbox(TouchCatchAction.MoveRight, ref trackedActions, colours.Gray2) + rightBox = new ArrowHitbox(TouchCatchAction.MoveRight, ref trackedActions, colours.Gray3) { - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, RelativeSizeAxes = Axes.Both, RelativePositionAxes = Axes.Both, Width = 0.5f, }, - rightDashBox = new ArrowHitbox(TouchCatchAction.DashRight, ref trackedActions, colours.Gray3) + rightDashBox = new ArrowHitbox(TouchCatchAction.DashRight, ref trackedActions, colours.Gray2) { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, RelativeSizeAxes = Axes.Both, RelativePositionAxes = Axes.Both, Width = 0.5f, From 9d2c2b71cf07a172a0f83e246e79e6b06822e29a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 16 Aug 2022 16:21:35 +0900 Subject: [PATCH 103/420] Change conditional to check for insertions in addition to modifications It is possible that the import process itself marks the previous beatmaps as deleted due to an overlap in metadata or otherwise. --- osu.Game/Screens/Select/BeatmapCarousel.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index e9419e7156..e9f676d32f 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -273,11 +273,13 @@ namespace osu.Game.Screens.Select // Check if the current selection was potentially deleted by re-querying its validity. bool selectedSetMarkedDeleted = realm.Run(r => r.Find(SelectedBeatmapSet.ID))?.DeletePending != false; - if (selectedSetMarkedDeleted && changes.NewModifiedIndices.Any()) + int[] modifiedAndInserted = changes.NewModifiedIndices.Concat(changes.InsertedIndices).ToArray(); + + if (selectedSetMarkedDeleted && modifiedAndInserted.Any()) { // If it is no longer valid, make the bold assumption that an updated version will be available in the modified indices. // This relies on the full update operation being in a single transaction, so please don't change that. - foreach (int i in changes.NewModifiedIndices) + foreach (int i in modifiedAndInserted) { var beatmapSetInfo = sender[i]; From 3de35a15180fd37a72df6ae3bb72d589797f25ef Mon Sep 17 00:00:00 2001 From: Ryuki Date: Thu, 18 Aug 2022 18:40:02 +0200 Subject: [PATCH 104/420] Update calculator and tests to match changes on clocks --- .../Gameplay/TestSceneClicksPerSecond.cs | 22 ++++++++++++++++--- .../ClicksPerSecondCalculator.cs | 2 +- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneClicksPerSecond.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneClicksPerSecond.cs index 8a5bd1af0f..69868e07b4 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneClicksPerSecond.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneClicksPerSecond.cs @@ -47,7 +47,7 @@ namespace osu.Game.Tests.Visual.Gameplay RelativeSizeAxes = Axes.Both, CachedDependencies = new (Type, object)[] { - (typeof(GameplayClock), mainClock = new MockFrameStableClock(new MockFrameBasedClock())), + (typeof(IGameplayClock), mainClock = new MockFrameStableClock(new MockFrameBasedClock())), (typeof(DrawableRuleset), new MockDrawableRuleset(ruleset, mainClock)) } }, @@ -249,17 +249,33 @@ namespace osu.Game.Tests.Visual.Gameplay public FrameTimeInfo TimeInfo { get; private set; } } - private class MockFrameStableClock : GameplayClock, IFrameStableClock + private class MockFrameStableClock : IGameplayClock, IFrameStableClock { + internal readonly IFrameBasedClock UnderlyingClock; + + public readonly BindableBool IsPaused = new BindableBool(); + public MockFrameStableClock(MockFrameBasedClock underlyingClock) - : base(underlyingClock) { + UnderlyingClock = underlyingClock; } public void Seek(double time) => (UnderlyingClock as MockFrameBasedClock)?.Seek(time); public IBindable IsCatchingUp => new Bindable(); public IBindable WaitingOnFrames => new Bindable(); + public double CurrentTime => UnderlyingClock.CurrentTime; + public double Rate => UnderlyingClock.Rate; + public bool IsRunning => UnderlyingClock.IsRunning; + public void ProcessFrame() => UnderlyingClock.ProcessFrame(); + + public double ElapsedFrameTime => UnderlyingClock.ElapsedFrameTime; + public double FramesPerSecond => UnderlyingClock.FramesPerSecond; + public FrameTimeInfo TimeInfo => UnderlyingClock.TimeInfo; + public double TrueGameplayRate => UnderlyingClock.Rate; + public double? StartTime => 0; + public IEnumerable NonGameplayAdjustments => Enumerable.Empty(); + IBindable IGameplayClock.IsPaused => IsPaused; } private class MockDrawableRuleset : DrawableRuleset diff --git a/osu.Game/Screens/Play/HUD/ClicksPerSecond/ClicksPerSecondCalculator.cs b/osu.Game/Screens/Play/HUD/ClicksPerSecond/ClicksPerSecondCalculator.cs index 9dc8615fb6..d9c3c6ffec 100644 --- a/osu.Game/Screens/Play/HUD/ClicksPerSecond/ClicksPerSecondCalculator.cs +++ b/osu.Game/Screens/Play/HUD/ClicksPerSecond/ClicksPerSecondCalculator.cs @@ -18,7 +18,7 @@ namespace osu.Game.Screens.Play.HUD.ClicksPerSecond private InputListener? listener; [Resolved] - private GameplayClock? gameplayClock { get; set; } + private IGameplayClock? gameplayClock { get; set; } [Resolved(canBeNull: true)] private DrawableRuleset? drawableRuleset { get; set; } From 3ac6500423a8c7607989e855de3eda71484eca8f Mon Sep 17 00:00:00 2001 From: Ryuki Date: Fri, 19 Aug 2022 00:49:03 +0200 Subject: [PATCH 105/420] Add new test resources for CPS counter --- .../Archives/modified-default-20220818.osk | Bin 0 -> 1145 bytes osu.Game.Tests/Skins/SkinDeserialisationTest.cs | 4 +++- 2 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 osu.Game.Tests/Resources/Archives/modified-default-20220818.osk diff --git a/osu.Game.Tests/Resources/Archives/modified-default-20220818.osk b/osu.Game.Tests/Resources/Archives/modified-default-20220818.osk new file mode 100644 index 0000000000000000000000000000000000000000..92215cbf8655decffeedfabd902ed0e8923bab66 GIT binary patch literal 1145 zcmWIWW@Zs#U|`^2NLVTqs`|jDa|)2R0W8A6P@J8arxn6S=HYqNAcrbLkFt<8S zdHCG=Wxv;FdCR48&5>TQc#^zr@Z(j<8zY0+!z(-V9<9<3Kyi8A6BC6ZpsPE9m=}ma zF3-$M%h$^)&d*!@>}gOyz$foBzM-3f0#51cg@#E_w1`>3Opjh`99S~7VrLd3s^caHu3EB{k%8ea&}Cvk$N45^=6Qs= zIOpdUYt#t>`Il0=~M34Q~2k%>zY_|J@Sq?YP0NlkD**?k?0eZrEh$HT#c&P z9%mv}`qyo)_ritmZ>gHp6`p^0>BQEfRwWm`?gy+5d263H)tddC+uj3ECk}@!ESGR@ALFKi%qnSq2%um-l#P&Pw z=#<$j^rQXL#=;|O+LN{a9{(t3aqe3de_f6Z*MX%+dkuuRR`--M_xVM-9hRSy`v1+3 zDecZRMT{q9mY8*gr!_ZO|AU)E;K zooDlh4K<=Qcl+?N03(VK0p5&EA`G~*98eboG=eBt z21M6|p4_2&7#JFNL3P0sKe|@*#E8%u4osrhlP09~Q;lPaH>i}<7Hjn}qAp8WR J3xP@)7yzY8uFwDg literal 0 HcmV?d00001 diff --git a/osu.Game.Tests/Skins/SkinDeserialisationTest.cs b/osu.Game.Tests/Skins/SkinDeserialisationTest.cs index c7eb334f25..1b03f8ef6b 100644 --- a/osu.Game.Tests/Skins/SkinDeserialisationTest.cs +++ b/osu.Game.Tests/Skins/SkinDeserialisationTest.cs @@ -36,7 +36,9 @@ namespace osu.Game.Tests.Skins "Archives/modified-default-20220723.osk", "Archives/modified-classic-20220723.osk", // Covers legacy song progress, UR counter, colour hit error metre. - "Archives/modified-classic-20220801.osk" + "Archives/modified-classic-20220801.osk", + // Covers clicks/s counter + "Archives/modified-default-20220818.osk" }; /// From 5cf54a788adc5601a518b41409a91a9daca76534 Mon Sep 17 00:00:00 2001 From: Ryuki Date: Sun, 21 Aug 2022 03:15:30 +0200 Subject: [PATCH 106/420] Code cleanup for CPS counter --- .../ClicksPerSecond/ClicksPerSecondCounter.cs | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/ClicksPerSecond/ClicksPerSecondCounter.cs b/osu.Game/Screens/Play/HUD/ClicksPerSecond/ClicksPerSecondCounter.cs index 049f38ba4c..3ff06d5217 100644 --- a/osu.Game/Screens/Play/HUD/ClicksPerSecond/ClicksPerSecondCounter.cs +++ b/osu.Game/Screens/Play/HUD/ClicksPerSecond/ClicksPerSecondCounter.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; @@ -19,9 +18,8 @@ namespace osu.Game.Screens.Play.HUD.ClicksPerSecond { private const float alpha_when_invalid = 0.3f; - private readonly Bindable valid = new Bindable(); - - private ClicksPerSecondCalculator? calculator; + [Resolved(canBeNull: false)] + private ClicksPerSecondCalculator calculator { get; set; } = null!; protected override double RollingDuration => 350; @@ -33,26 +31,19 @@ namespace osu.Game.Screens.Play.HUD.ClicksPerSecond } [BackgroundDependencyLoader] - private void load(OsuColour colours, ClicksPerSecondCalculator calculator) + private void load(OsuColour colours) { - this.calculator = calculator; Colour = colours.BlueLighter; - valid.BindValueChanged(e => - DrawableCount.FadeTo(e.NewValue ? 1 : alpha_when_invalid, 1000, Easing.OutQuint)); } protected override void Update() { base.Update(); - valid.Value = calculator != null && calculator.Ready; - Current.Value = calculator != null ? calculator.Ready ? calculator.Value : 0 : 0; + Current.Value = calculator.Ready ? calculator.Value : 0; } - protected override IHasText CreateText() => new TextComponent - { - Alpha = alpha_when_invalid - }; + protected override IHasText CreateText() => new TextComponent(); private class TextComponent : CompositeDrawable, IHasText { From c56390cd7b4f7f4c442a321e9a71ce1293e72a3d Mon Sep 17 00:00:00 2001 From: Ryuki Date: Mon, 22 Aug 2022 00:03:24 +0200 Subject: [PATCH 107/420] Use less custom classes for CPS tests --- .../Gameplay/TestSceneClicksPerSecond.cs | 272 +++++++++--------- 1 file changed, 144 insertions(+), 128 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneClicksPerSecond.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneClicksPerSecond.cs index 69868e07b4..375726dd9a 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneClicksPerSecond.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneClicksPerSecond.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Linq; using NUnit.Framework; using osu.Framework.Bindables; @@ -13,9 +14,9 @@ using osu.Framework.Testing; using osu.Framework.Timing; using osu.Game.Rulesets; using osu.Game.Rulesets.Judgements; -using osu.Game.Rulesets.Mania; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.UI; using osu.Game.Scoring; using osu.Game.Screens.Play; @@ -27,9 +28,12 @@ namespace osu.Game.Tests.Visual.Gameplay public class TestSceneClicksPerSecond : OsuTestScene { private DependencyProvidingContainer? dependencyContainer; - private MockFrameStableClock? mainClock; private ClicksPerSecondCalculator? calculator; private ManualInputListener? listener; + private GameplayClockContainer? gameplayClockContainer; + private ManualClock? manualClock; + private DrawableRuleset? drawableRuleset; + private IFrameStableClock? frameStableClock; [SetUpSteps] public void SetUpSteps() @@ -40,41 +44,20 @@ namespace osu.Game.Tests.Visual.Gameplay Debug.Assert(ruleset != null); - Children = new Drawable[] + Child = gameplayClockContainer = new GameplayClockContainer(manualClock = new ManualClock()); + gameplayClockContainer.AddRange(new Drawable[] { + drawableRuleset = new TestDrawableRuleset(frameStableClock = new TestFrameStableClock(manualClock)), dependencyContainer = new DependencyProvidingContainer { RelativeSizeAxes = Axes.Both, CachedDependencies = new (Type, object)[] { - (typeof(IGameplayClock), mainClock = new MockFrameStableClock(new MockFrameBasedClock())), - (typeof(DrawableRuleset), new MockDrawableRuleset(ruleset, mainClock)) - } - }, - }; - }); - } - - private void createCalculator() - { - AddStep("create calculator", () => - { - dependencyContainer!.Children = new Drawable[] - { - calculator = new ClicksPerSecondCalculator(), - new DependencyProvidingContainer - { - RelativeSizeAxes = Axes.Both, - CachedDependencies = new (Type, object)[] { (typeof(ClicksPerSecondCalculator), calculator) }, - Child = new ClicksPerSecondCounter // For visual debugging, has no real purpose in the tests - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Scale = new Vector2(5), + (typeof(DrawableRuleset), drawableRuleset), + (typeof(IGameplayClock), gameplayClockContainer) } } - }; - calculator!.Listener = listener = new ManualInputListener(calculator!); + }); }); } @@ -82,6 +65,7 @@ namespace osu.Game.Tests.Visual.Gameplay public void TestBasicConsistency() { createCalculator(); + startClock(); AddStep("Create gradually increasing KPS inputs", () => { @@ -101,6 +85,7 @@ namespace osu.Game.Tests.Visual.Gameplay public void TestRateAdjustConsistency() { createCalculator(); + startClock(); AddStep("Create consistent KPS inputs", () => addInputs(generateConsistentKps(10))); @@ -125,6 +110,7 @@ namespace osu.Game.Tests.Visual.Gameplay public void TestInputsDiscardedOnRewind() { createCalculator(); + startClock(); AddStep("Create consistent KPS inputs", () => addInputs(generateConsistentKps(10))); seek(1000); @@ -136,38 +122,79 @@ namespace osu.Game.Tests.Visual.Gameplay AddAssert("KPS didn't changed", () => calculator!.Value == 10); } - private void seek(double time) => AddStep($"Seek main clock to {time}ms", () => mainClock?.Seek(time)); - - private void changeRate(double rate) => AddStep($"Change rate to x{rate}", () => - (mainClock?.UnderlyingClock as MockFrameBasedClock)!.Rate = rate); - - private void advanceForwards(int frames = 1) => AddStep($"Advance main clock {frames} frame(s) forward.", () => + private void seekAllClocks(double time) { - if (mainClock == null) return; + gameplayClockContainer?.Seek(time); + manualClock!.CurrentTime = time; + } - MockFrameBasedClock underlyingClock = (MockFrameBasedClock)mainClock.UnderlyingClock; - underlyingClock.Backwards = false; + protected override Ruleset CreateRuleset() => new OsuRuleset(); - for (int i = 0; i < frames; i++) + #region Quick steps methods + + private void createCalculator() + { + AddStep("create calculator", () => { - underlyingClock.ProcessFrame(); - } + Debug.Assert(dependencyContainer?.Dependencies.Get(typeof(DrawableRuleset)) is DrawableRuleset); + dependencyContainer!.Children = new Drawable[] + { + calculator = new ClicksPerSecondCalculator(), + new DependencyProvidingContainer + { + RelativeSizeAxes = Axes.Both, + CachedDependencies = new (Type, object)[] { (typeof(ClicksPerSecondCalculator), calculator) }, + Child = new ClicksPerSecondCounter // For visual debugging, has no real purpose in the tests + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Scale = new Vector2(5), + } + } + }; + calculator!.Listener = listener = new ManualInputListener(calculator!); + }); + } + + private void seek(double time) => AddStep($"Seek clocks to {time}ms", () => seekAllClocks(time)); + + private void changeRate(double rate) => AddStep($"Change rate to x{rate}", () => manualClock!.Rate = rate); + + private void advanceForwards(double time) => + AddStep($"Advance clocks {time} seconds forward.", () => + { + gameplayClockContainer!.Seek(gameplayClockContainer.CurrentTime + time * manualClock!.Rate); + + for (int i = 0; i < time; i++) + { + frameStableClock?.ProcessFrame(); + } + }); + + private void startClock() => AddStep("Start clocks", () => + { + gameplayClockContainer?.Start(); + manualClock!.Rate = 1; }); + #endregion + + #region Input generation + private void addInputs(IEnumerable inputs) { - Debug.Assert(mainClock != null && listener != null); + Debug.Assert(manualClock != null && listener != null && gameplayClockContainer != null); if (!inputs.Any()) return; - double baseTime = mainClock.CurrentTime; + double baseTime = gameplayClockContainer.CurrentTime; foreach (double timestamp in inputs) { - mainClock.Seek(timestamp); + seekAllClocks(timestamp); listener.AddInput(); } - mainClock.Seek(baseTime); + seekAllClocks(baseTime); } private IEnumerable generateGraduallyIncreasingKps() @@ -200,9 +227,52 @@ namespace osu.Game.Tests.Visual.Gameplay } } - protected override Ruleset CreateRuleset() => new ManiaRuleset(); + #endregion - #region Mock classes + #region Test classes + + private class TestFrameStableClock : IFrameStableClock + { + public TestFrameStableClock(IClock source, double startTime = 0) + { + this.source = source; + StartTime = startTime; + + if (source is ManualClock manualClock) + { + manualClock.CurrentTime = startTime; + } + } + + public double CurrentTime => source.CurrentTime; + public double Rate => source.Rate; + public bool IsRunning => source.IsRunning; + + private IClock source; + + public void ProcessFrame() + { + if (source is ManualClock manualClock) + { + manualClock.CurrentTime += 1000 * Rate; + } + + TimeInfo = new FrameTimeInfo + { + Elapsed = 1000 * Rate, + Current = CurrentTime + }; + } + + public double ElapsedFrameTime => TimeInfo.Elapsed; + public double FramesPerSecond => 1 / ElapsedFrameTime * 1000; + public FrameTimeInfo TimeInfo { get; private set; } + + public double? StartTime { get; } + public IEnumerable NonGameplayAdjustments => Enumerable.Empty(); + public IBindable IsCatchingUp => new Bindable(); + public IBindable WaitingOnFrames => new Bindable(); + } private class ManualInputListener : ClicksPerSecondCalculator.InputListener { @@ -214,108 +284,54 @@ namespace osu.Game.Tests.Visual.Gameplay } } - private class MockFrameBasedClock : ManualClock, IFrameBasedClock +#nullable disable + + [SuppressMessage("ReSharper", "UnassignedGetOnlyAutoProperty")] + private class TestDrawableRuleset : DrawableRuleset { - public const double FRAME_INTERVAL = 1000; - public bool Backwards; + public override IEnumerable Objects => Enumerable.Empty(); - public MockFrameBasedClock() + public override event Action NewResult { - Rate = 1; - IsRunning = true; + add => throw new InvalidOperationException($"{nameof(NewResult)} operations not supported in test context"); + remove => throw new InvalidOperationException($"{nameof(NewResult)} operations not supported in test context"); } - public void ProcessFrame() + public override event Action RevertResult { - CurrentTime += FRAME_INTERVAL * Rate * (Backwards ? -1 : 1); - TimeInfo = new FrameTimeInfo - { - Current = CurrentTime, - Elapsed = FRAME_INTERVAL * Rate * (Backwards ? -1 : 1) - }; + add => throw new InvalidOperationException($"{nameof(RevertResult)} operations not supported in test context"); + remove => throw new InvalidOperationException($"{nameof(RevertResult)} operations not supported in test context"); } - public void Seek(double time) - { - TimeInfo = new FrameTimeInfo - { - Elapsed = time - CurrentTime, - Current = CurrentTime = time - }; - } - - public double ElapsedFrameTime => TimeInfo.Elapsed; - public double FramesPerSecond => 1 / FRAME_INTERVAL; - public FrameTimeInfo TimeInfo { get; private set; } - } - - private class MockFrameStableClock : IGameplayClock, IFrameStableClock - { - internal readonly IFrameBasedClock UnderlyingClock; - - public readonly BindableBool IsPaused = new BindableBool(); - - public MockFrameStableClock(MockFrameBasedClock underlyingClock) - { - UnderlyingClock = underlyingClock; - } - - public void Seek(double time) => (UnderlyingClock as MockFrameBasedClock)?.Seek(time); - - public IBindable IsCatchingUp => new Bindable(); - public IBindable WaitingOnFrames => new Bindable(); - public double CurrentTime => UnderlyingClock.CurrentTime; - public double Rate => UnderlyingClock.Rate; - public bool IsRunning => UnderlyingClock.IsRunning; - public void ProcessFrame() => UnderlyingClock.ProcessFrame(); - - public double ElapsedFrameTime => UnderlyingClock.ElapsedFrameTime; - public double FramesPerSecond => UnderlyingClock.FramesPerSecond; - public FrameTimeInfo TimeInfo => UnderlyingClock.TimeInfo; - public double TrueGameplayRate => UnderlyingClock.Rate; - public double? StartTime => 0; - public IEnumerable NonGameplayAdjustments => Enumerable.Empty(); - IBindable IGameplayClock.IsPaused => IsPaused; - } - - private class MockDrawableRuleset : DrawableRuleset - { - public MockDrawableRuleset(Ruleset ruleset, IFrameStableClock clock) - : base(ruleset) - { - FrameStableClock = clock; - } - -#pragma warning disable CS0067 - public override event Action? NewResult; - public override event Action? RevertResult; -#pragma warning restore CS0067 - public override Playfield? Playfield => null; - public override Container? Overlays => null; - public override Container? FrameStableComponents => null; + public override Playfield Playfield => null; + public override Container Overlays => null; + public override Container FrameStableComponents => null; public override IFrameStableClock FrameStableClock { get; } internal override bool FrameStablePlayback { get; set; } public override IReadOnlyList Mods => Array.Empty(); - public override IEnumerable Objects => Array.Empty(); + public override double GameplayStartTime => 0; - public override GameplayCursorContainer? Cursor => null; + public override GameplayCursorContainer Cursor => null; - public override void SetReplayScore(Score replayScore) + public TestDrawableRuleset() + : base(new OsuRuleset()) { } - public override void SetRecordTarget(Score score) + public TestDrawableRuleset(IFrameStableClock frameStableClock) + : this() { + FrameStableClock = frameStableClock; } - public override void RequestResume(Action continueResume) - { - } + public override void SetReplayScore(Score replayScore) => throw new NotImplementedException(); - public override void CancelResume() - { - } + public override void SetRecordTarget(Score score) => throw new NotImplementedException(); + + public override void RequestResume(Action continueResume) => throw new NotImplementedException(); + + public override void CancelResume() => throw new NotImplementedException(); } #endregion From f5710d8000d3bc11c4a2065f411105c53e249914 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 22 Aug 2022 16:10:55 +0900 Subject: [PATCH 108/420] Add ruleset API versioning --- 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 | 17 +++++++++++++++++ 5 files changed, 25 insertions(+) diff --git a/osu.Game.Rulesets.Catch/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs index ed151855b1..f94bf276a0 100644 --- a/osu.Game.Rulesets.Catch/CatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs @@ -43,6 +43,8 @@ namespace osu.Game.Rulesets.Catch public const string SHORT_NAME = "fruits"; + public override string RulesetAPIVersionSupported => "internal"; + public override IEnumerable GetDefaultKeyBindings(int variant = 0) => new[] { new KeyBinding(InputKey.Z, CatchAction.MoveLeft), diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index ac6060ceed..b1fe4b30c4 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -60,6 +60,8 @@ namespace osu.Game.Rulesets.Mania public const string SHORT_NAME = "mania"; + public override string RulesetAPIVersionSupported => "internal"; + public override HitObjectComposer CreateHitObjectComposer() => new ManiaHitObjectComposer(this); public override ISkin CreateLegacySkinProvider(ISkin skin, IBeatmap beatmap) => new ManiaLegacySkinTransformer(skin, beatmap); diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index 7f58f29d4b..4400dfbb65 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -54,6 +54,8 @@ namespace osu.Game.Rulesets.Osu public const string SHORT_NAME = "osu"; + public override string RulesetAPIVersionSupported => "internal"; + public override IEnumerable GetDefaultKeyBindings(int variant = 0) => new[] { new KeyBinding(InputKey.Z, OsuAction.LeftButton), diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index 04bb08395b..275c7144a7 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -49,6 +49,8 @@ namespace osu.Game.Rulesets.Taiko public const string SHORT_NAME = "taiko"; + public override string RulesetAPIVersionSupported => "internal"; + public override IEnumerable GetDefaultKeyBindings(int variant = 0) => new[] { new KeyBinding(InputKey.MouseLeft, TaikoAction.LeftCentre), diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index 0968d78ed7..63f5906f46 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -44,6 +44,23 @@ namespace osu.Game.Rulesets private static readonly ConcurrentDictionary mod_reference_cache = new ConcurrentDictionary(); + /// + /// Version history: + /// 2022.205.0 FramedReplayInputHandler.CollectPendingInputs renamed to FramedReplayHandler.CollectReplayInputs. + /// 2022.822.0 All strings return values have been converted to LocalisableString to allow for localisation support. + /// + public const string CURRENT_RULESET_API_VERSION = "2022.822.0"; + + /// + /// Define the ruleset API version supported by this ruleset. + /// Ruleset implementations should be updated to support the latest version to ensure they can still be loaded. + /// + /// + /// When updating a ruleset to support the latest API, you should set this to . + /// See https://github.com/ppy/osu/wiki/Breaking-Changes for full details on required ongoing changes. + /// + public virtual string RulesetAPIVersionSupported => string.Empty; + /// /// A queryable source containing all available mods. /// Call for consumption purposes. From 758a554180fb61f21dbc8b9eae6d90d674358ded Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 22 Aug 2022 16:31:25 +0900 Subject: [PATCH 109/420] Add basic check for correct ruleset API version --- osu.Game/Rulesets/RealmRulesetStore.cs | 28 +++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/RealmRulesetStore.cs b/osu.Game/Rulesets/RealmRulesetStore.cs index dba7f47f2f..590f118b68 100644 --- a/osu.Game/Rulesets/RealmRulesetStore.cs +++ b/osu.Game/Rulesets/RealmRulesetStore.cs @@ -77,9 +77,16 @@ namespace osu.Game.Rulesets continue; } - var instanceInfo = (Activator.CreateInstance(resolvedType) as Ruleset)?.RulesetInfo + var instance = (Activator.CreateInstance(resolvedType) as Ruleset); + var instanceInfo = instance?.RulesetInfo ?? throw new RulesetLoadException(@"Instantiation failure"); + if (!checkRulesetUpToDate(instance)) + { + throw new ArgumentOutOfRangeException(nameof(instance.RulesetAPIVersionSupported), + $"Ruleset API version is too old (was {instance.RulesetAPIVersionSupported}, expected {Ruleset.CURRENT_RULESET_API_VERSION})"); + } + // If a ruleset isn't up-to-date with the API, it could cause a crash at an arbitrary point of execution. // To eagerly handle cases of missing implementations, enumerate all types here and mark as non-available on throw. resolvedType.Assembly.GetTypes(); @@ -104,6 +111,25 @@ namespace osu.Game.Rulesets }); } + private bool checkRulesetUpToDate(Ruleset instance) + { + switch (instance.RulesetAPIVersionSupported) + { + // The default `virtual` implementation leaves the version string empty. + // Consider rulesets which haven't override the version as up-to-date for now. + // At some point (once ruleset devs add versioning), we'll probably want to disallow this for deployed builds. + case @"": + // Rulesets which are bundled with the game. Saves having to update their versions each bump. + case @"internal": + // Ruleset is up-to-date, all good. + case Ruleset.CURRENT_RULESET_API_VERSION: + return true; + + default: + return false; + } + } + private void testRulesetCompatibility(RulesetInfo rulesetInfo) { // do various operations to ensure that we are in a good state. From b5970495242d9555fad30c52a33b1e66f3f3a4e9 Mon Sep 17 00:00:00 2001 From: Ryuki Date: Mon, 22 Aug 2022 10:47:37 +0200 Subject: [PATCH 110/420] Code cleanup for CPS tests - Remove null-forgiving operator usages - Fix code quality issues mentionned by NVika --- .../Gameplay/TestSceneClicksPerSecond.cs | 48 +++++++++---------- 1 file changed, 22 insertions(+), 26 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneClicksPerSecond.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneClicksPerSecond.cs index 375726dd9a..137ab7acdb 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneClicksPerSecond.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneClicksPerSecond.cs @@ -27,11 +27,11 @@ namespace osu.Game.Tests.Visual.Gameplay { public class TestSceneClicksPerSecond : OsuTestScene { - private DependencyProvidingContainer? dependencyContainer; - private ClicksPerSecondCalculator? calculator; + private DependencyProvidingContainer dependencyContainer = null!; + private ClicksPerSecondCalculator calculator = null!; private ManualInputListener? listener; - private GameplayClockContainer? gameplayClockContainer; - private ManualClock? manualClock; + private GameplayClockContainer gameplayClockContainer = null!; + private ManualClock manualClock = null!; private DrawableRuleset? drawableRuleset; private IFrameStableClock? frameStableClock; @@ -77,7 +77,7 @@ namespace osu.Game.Tests.Visual.Gameplay seek(i * 10000); advanceForwards(2); int kps = i + 1; - AddAssert($"{kps} KPS", () => calculator!.Value == kps); + AddAssert($"{kps} KPS", () => calculator.Value == kps); } } @@ -95,14 +95,14 @@ namespace osu.Game.Tests.Visual.Gameplay { changeRate(i); double rate = i; - AddAssert($"KPS approx. = {i}", () => MathHelper.ApproximatelyEquivalent(calculator!.Value, 10 * rate, 0.5)); + AddAssert($"KPS approx. = {i}", () => MathHelper.ApproximatelyEquivalent(calculator.Value, 10 * rate, 0.5)); } for (double i = 1; i >= 0.5; i -= 0.25) { changeRate(i); double rate = i; - AddAssert($"KPS approx. = {i}", () => MathHelper.ApproximatelyEquivalent(calculator!.Value, 10 * rate, 0.5)); + AddAssert($"KPS approx. = {i}", () => MathHelper.ApproximatelyEquivalent(calculator.Value, 10 * rate, 0.5)); } } @@ -115,17 +115,17 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("Create consistent KPS inputs", () => addInputs(generateConsistentKps(10))); seek(1000); - AddAssert("KPS = 10", () => calculator!.Value == 10); + AddAssert("KPS = 10", () => calculator.Value == 10); AddStep("Create delayed inputs", () => addInputs(generateConsistentKps(10, 50))); seek(1000); - AddAssert("KPS didn't changed", () => calculator!.Value == 10); + AddAssert("KPS didn't changed", () => calculator.Value == 10); } private void seekAllClocks(double time) { - gameplayClockContainer?.Seek(time); - manualClock!.CurrentTime = time; + gameplayClockContainer.Seek(time); + manualClock.CurrentTime = time; } protected override Ruleset CreateRuleset() => new OsuRuleset(); @@ -136,8 +136,7 @@ namespace osu.Game.Tests.Visual.Gameplay { AddStep("create calculator", () => { - Debug.Assert(dependencyContainer?.Dependencies.Get(typeof(DrawableRuleset)) is DrawableRuleset); - dependencyContainer!.Children = new Drawable[] + dependencyContainer.Children = new Drawable[] { calculator = new ClicksPerSecondCalculator(), new DependencyProvidingContainer @@ -152,18 +151,18 @@ namespace osu.Game.Tests.Visual.Gameplay } } }; - calculator!.Listener = listener = new ManualInputListener(calculator!); + calculator.Listener = listener = new ManualInputListener(calculator); }); } private void seek(double time) => AddStep($"Seek clocks to {time}ms", () => seekAllClocks(time)); - private void changeRate(double rate) => AddStep($"Change rate to x{rate}", () => manualClock!.Rate = rate); + private void changeRate(double rate) => AddStep($"Change rate to x{rate}", () => manualClock.Rate = rate); private void advanceForwards(double time) => AddStep($"Advance clocks {time} seconds forward.", () => { - gameplayClockContainer!.Seek(gameplayClockContainer.CurrentTime + time * manualClock!.Rate); + gameplayClockContainer.Seek(gameplayClockContainer.CurrentTime + time * manualClock.Rate); for (int i = 0; i < time; i++) { @@ -173,8 +172,8 @@ namespace osu.Game.Tests.Visual.Gameplay private void startClock() => AddStep("Start clocks", () => { - gameplayClockContainer?.Start(); - manualClock!.Rate = 1; + gameplayClockContainer.Start(); + manualClock.Rate = 1; }); #endregion @@ -183,7 +182,6 @@ namespace osu.Game.Tests.Visual.Gameplay private void addInputs(IEnumerable inputs) { - Debug.Assert(manualClock != null && listener != null && gameplayClockContainer != null); if (!inputs.Any()) return; double baseTime = gameplayClockContainer.CurrentTime; @@ -191,7 +189,7 @@ namespace osu.Game.Tests.Visual.Gameplay foreach (double timestamp in inputs) { seekAllClocks(timestamp); - listener.AddInput(); + listener?.AddInput(); } seekAllClocks(baseTime); @@ -199,7 +197,7 @@ namespace osu.Game.Tests.Visual.Gameplay private IEnumerable generateGraduallyIncreasingKps() { - IEnumerable? final = null; + IEnumerable final = null!; for (int i = 1; i <= 10; i++) { @@ -211,10 +209,10 @@ namespace osu.Game.Tests.Visual.Gameplay continue; } - final = final!.Concat(currentKps); + final = final.Concat(currentKps); } - return final!; + return final; } private IEnumerable generateConsistentKps(double kps, double start = 0, double duration = 10) @@ -236,7 +234,6 @@ namespace osu.Game.Tests.Visual.Gameplay public TestFrameStableClock(IClock source, double startTime = 0) { this.source = source; - StartTime = startTime; if (source is ManualClock manualClock) { @@ -248,7 +245,7 @@ namespace osu.Game.Tests.Visual.Gameplay public double Rate => source.Rate; public bool IsRunning => source.IsRunning; - private IClock source; + private readonly IClock source; public void ProcessFrame() { @@ -268,7 +265,6 @@ namespace osu.Game.Tests.Visual.Gameplay public double FramesPerSecond => 1 / ElapsedFrameTime * 1000; public FrameTimeInfo TimeInfo { get; private set; } - public double? StartTime { get; } public IEnumerable NonGameplayAdjustments => Enumerable.Empty(); public IBindable IsCatchingUp => new Bindable(); public IBindable WaitingOnFrames => new Bindable(); From 1098e24c40afef4ee684706d2050e0e9dae9bd61 Mon Sep 17 00:00:00 2001 From: HiddenNode Date: Mon, 22 Aug 2022 14:24:52 +0100 Subject: [PATCH 111/420] Improved UprightUnscaledContainer --- osu.Game/Extensions/DrawableExtensions.cs | 36 ---------- .../Graphics/Containers/GrowToFitContainer.cs | 21 ------ .../Containers/UprightUnscaledContainer.cs | 66 ++++++++++++++++++- osu.Game/Screens/Play/HUD/SongProgressInfo.cs | 33 +++------- 4 files changed, 73 insertions(+), 83 deletions(-) delete mode 100644 osu.Game/Graphics/Containers/GrowToFitContainer.cs diff --git a/osu.Game/Extensions/DrawableExtensions.cs b/osu.Game/Extensions/DrawableExtensions.cs index 320b9d7996..35f2d61437 100644 --- a/osu.Game/Extensions/DrawableExtensions.cs +++ b/osu.Game/Extensions/DrawableExtensions.cs @@ -8,7 +8,6 @@ using osu.Game.Configuration; using osu.Game.Screens.Play.HUD; using osu.Game.Skinning; using osuTK; -using System; namespace osu.Game.Extensions { @@ -80,40 +79,5 @@ namespace osu.Game.Extensions container.Add(child.CreateInstance()); } } - - /// - /// Keeps the drawable upright and unstretched preventing it from being rotated, sheared, scaled or flipped with its Parent. - /// - /// The drawable. - public static void KeepUprightAndUnscaled(this Drawable drawable) - { - // Decomposes the inverse of the parent FrawInfo.Matrix into rotation, shear and scale. - var parentMatrix = drawable.Parent.DrawInfo.Matrix; - parentMatrix.Transpose(); - - // Remove Translation. - parentMatrix.M13 = 0.0f; - parentMatrix.M23 = 0.0f; - - Matrix3 C = parentMatrix.Inverted(); - - // Extract the rotation. - float angle = MathF.Atan2(C.M21, C.M11); - drawable.Rotation = MathHelper.RadiansToDegrees(angle); - - // Remove rotation from the C matrix so that it only contains shear and scale. - Matrix3 m = Matrix3.CreateRotationZ(-angle); - m.Transpose(); - C = m * C; - - // Extract shear and scale. - float alpha, sx, sy; - sx = C.M11; - sy = C.M22; - alpha = C.M12 / C.M22; - - drawable.Scale = new Vector2(sx, sy); - drawable.Shear = new Vector2(-alpha, 0); - } } } diff --git a/osu.Game/Graphics/Containers/GrowToFitContainer.cs b/osu.Game/Graphics/Containers/GrowToFitContainer.cs deleted file mode 100644 index 9b4ad0dba9..0000000000 --- a/osu.Game/Graphics/Containers/GrowToFitContainer.cs +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using System; -using osu.Framework.Graphics.Containers; - -namespace osu.Game.Graphics.Containers -{ - /// - /// A container that grows in size to fit its child and retains its size when its child shrinks - /// - public class GrowToFitContainer : Container - { - protected override void Update() - { - base.Update(); - Height = Math.Max(Child.Height, Height); - Width = Math.Max(Child.Width, Width); - } - } -} diff --git a/osu.Game/Graphics/Containers/UprightUnscaledContainer.cs b/osu.Game/Graphics/Containers/UprightUnscaledContainer.cs index cdb839fdec..b6c3b56c4a 100644 --- a/osu.Game/Graphics/Containers/UprightUnscaledContainer.cs +++ b/osu.Game/Graphics/Containers/UprightUnscaledContainer.cs @@ -4,6 +4,8 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Layout; +using osuTK; +using System; namespace osu.Game.Graphics.Containers { @@ -12,21 +14,81 @@ namespace osu.Game.Graphics.Containers /// public class UprightUnscaledContainer : Container { + protected override Container Content => content; + private readonly Container content; + public UprightUnscaledContainer() { + InternalChild = content = new GrowToFitContainer(); AddLayout(layout); } - private LayoutValue layout = new LayoutValue(Invalidation.DrawInfo, InvalidationSource.Parent); + private readonly LayoutValue layout = new LayoutValue(Invalidation.DrawInfo, InvalidationSource.Parent); protected override void Update() { base.Update(); + if (!layout.IsValid) { - Extensions.DrawableExtensions.KeepUprightAndUnscaled(this); + keepUprightAndUnscaled(); layout.Validate(); } } + + /// + /// Keeps the drawable upright and unstretched preventing it from being rotated, sheared, scaled or flipped with its Parent. + /// + private void keepUprightAndUnscaled() + { + // Decomposes the inverse of the parent FrawInfo.Matrix into rotation, shear and scale. + var parentMatrix = Parent.DrawInfo.Matrix; + + // Remove Translation. + parentMatrix.M31 = 0.0f; + parentMatrix.M32 = 0.0f; + + Matrix3 reversedParrent = parentMatrix.Inverted(); + + // Extract the rotation. + float angle = MathF.Atan2(reversedParrent.M12, reversedParrent.M11); + Rotation = MathHelper.RadiansToDegrees(angle); + + // Remove rotation from the C matrix so that it only contains shear and scale. + Matrix3 m = Matrix3.CreateRotationZ(-angle); + reversedParrent *= m; + + // Extract shear and scale. + float sx = reversedParrent.M11; + float sy = reversedParrent.M22; + float alpha = reversedParrent.M21 / reversedParrent.M22; + + Scale = new Vector2(sx, sy); + Shear = new Vector2(-alpha, 0); + } + + /// + /// A container that grows in size to fit its children and retains its size when its children shrink + /// + private class GrowToFitContainer : Container + { + protected override Container Content => content; + private readonly Container content; + + public GrowToFitContainer() + { + InternalChild = content = new Container + { + AutoSizeAxes = Axes.Both, + }; + } + + protected override void Update() + { + base.Update(); + Height = Math.Max(content.Height, Height); + Width = Math.Max(content.Width, Width); + } + } } } diff --git a/osu.Game/Screens/Play/HUD/SongProgressInfo.cs b/osu.Game/Screens/Play/HUD/SongProgressInfo.cs index 069492fd80..c4cdc0cb29 100644 --- a/osu.Game/Screens/Play/HUD/SongProgressInfo.cs +++ b/osu.Game/Screens/Play/HUD/SongProgressInfo.cs @@ -60,17 +60,12 @@ namespace osu.Game.Screens.Play.HUD Origin = Anchor.Centre, Anchor = Anchor.Centre, AutoSizeAxes = Axes.Both, - Child = new GrowToFitContainer + Child = timeCurrent = new OsuSpriteText { Origin = Anchor.Centre, Anchor = Anchor.Centre, - Child = timeCurrent = new OsuSpriteText - { - Origin = Anchor.Centre, - Anchor = Anchor.Centre, - Colour = colours.BlueLighter, - Font = OsuFont.Numeric, - } + Colour = colours.BlueLighter, + Font = OsuFont.Numeric, } } }, @@ -84,17 +79,12 @@ namespace osu.Game.Screens.Play.HUD Origin = Anchor.Centre, Anchor = Anchor.Centre, AutoSizeAxes = Axes.Both, - Child = new GrowToFitContainer + Child = progress = new OsuSpriteText { Origin = Anchor.Centre, Anchor = Anchor.Centre, - Child = progress = new OsuSpriteText - { - Origin = Anchor.Centre, - Anchor = Anchor.Centre, - Colour = colours.BlueLighter, - Font = OsuFont.Numeric, - } + Colour = colours.BlueLighter, + Font = OsuFont.Numeric, } } }, @@ -108,17 +98,12 @@ namespace osu.Game.Screens.Play.HUD Origin = Anchor.Centre, Anchor = Anchor.Centre, AutoSizeAxes = Axes.Both, - Child = new GrowToFitContainer + Child = timeLeft = new OsuSpriteText { Origin = Anchor.Centre, Anchor = Anchor.Centre, - Child = timeLeft = new OsuSpriteText - { - Origin = Anchor.Centre, - Anchor = Anchor.Centre, - Colour = colours.BlueLighter, - Font = OsuFont.Numeric, - } + Colour = colours.BlueLighter, + Font = OsuFont.Numeric, } } } From e8d4bc4497c3fe3f7f2606db471a48fce87393cb Mon Sep 17 00:00:00 2001 From: Khang Date: Mon, 22 Aug 2022 21:04:26 -0400 Subject: [PATCH 112/420] Allow NaN during beatmap parsing if desired --- osu.Game.Tests/Beatmaps/Formats/ParsingTest.cs | 7 ++++++- osu.Game/Beatmaps/Formats/Parsing.cs | 8 ++++---- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/Formats/ParsingTest.cs b/osu.Game.Tests/Beatmaps/Formats/ParsingTest.cs index f3673b0e7f..339063633a 100644 --- a/osu.Game.Tests/Beatmaps/Formats/ParsingTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/ParsingTest.cs @@ -14,7 +14,12 @@ namespace osu.Game.Tests.Beatmaps.Formats public class ParsingTest { [Test] - public void TestNaNHandling() => allThrow("NaN"); + public void TestNaNHandling() + { + allThrow("NaN"); + Assert.That(Parsing.ParseFloat("NaN", allowNaN: true), Is.NaN); + Assert.That(Parsing.ParseDouble("NaN", allowNaN: true), Is.NaN); + } [Test] public void TestBadStringHandling() => allThrow("Random string 123"); diff --git a/osu.Game/Beatmaps/Formats/Parsing.cs b/osu.Game/Beatmaps/Formats/Parsing.cs index ce04f27020..9b0d200077 100644 --- a/osu.Game/Beatmaps/Formats/Parsing.cs +++ b/osu.Game/Beatmaps/Formats/Parsing.cs @@ -17,26 +17,26 @@ namespace osu.Game.Beatmaps.Formats public const double MAX_PARSE_VALUE = int.MaxValue; - public static float ParseFloat(string input, float parseLimit = (float)MAX_PARSE_VALUE) + public static float ParseFloat(string input, float parseLimit = (float)MAX_PARSE_VALUE, bool allowNaN = false) { float output = float.Parse(input, CultureInfo.InvariantCulture); if (output < -parseLimit) throw new OverflowException("Value is too low"); if (output > parseLimit) throw new OverflowException("Value is too high"); - if (float.IsNaN(output)) throw new FormatException("Not a number"); + if (!allowNaN && float.IsNaN(output)) throw new FormatException("Not a number"); return output; } - public static double ParseDouble(string input, double parseLimit = MAX_PARSE_VALUE) + public static double ParseDouble(string input, double parseLimit = MAX_PARSE_VALUE, bool allowNaN = false) { double output = double.Parse(input, CultureInfo.InvariantCulture); if (output < -parseLimit) throw new OverflowException("Value is too low"); if (output > parseLimit) throw new OverflowException("Value is too high"); - if (double.IsNaN(output)) throw new FormatException("Not a number"); + if (!allowNaN && double.IsNaN(output)) throw new FormatException("Not a number"); return output; } From 9f08c474caf57d8a84a510653c72a7f01ccf2b8d Mon Sep 17 00:00:00 2001 From: Khang Date: Mon, 22 Aug 2022 21:44:25 -0400 Subject: [PATCH 113/420] Treat NaN slider velocity timing points as 1.0x but without slider ticks --- .../Objects/JuiceStream.cs | 5 ++++- osu.Game.Rulesets.Mania/Objects/HoldNote.cs | 2 +- .../Mods/OsuModStrictTracking.cs | 2 +- osu.Game.Rulesets.Osu/Objects/Slider.cs | 8 ++++++- osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs | 2 +- .../Beatmaps/SliderEventGenerationTest.cs | 17 ++++++++++----- .../ControlPoints/DifficultyControlPoint.cs | 15 ++++++++++--- .../Beatmaps/Formats/LegacyBeatmapDecoder.cs | 8 ++++++- .../Rulesets/Objects/SliderEventGenerator.cs | 21 +++++++++++-------- 9 files changed, 57 insertions(+), 23 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs index 311e15116e..a9c7d938d2 100644 --- a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs +++ b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs @@ -40,6 +40,9 @@ namespace osu.Game.Rulesets.Catch.Objects [JsonIgnore] public double TickDistance => tickDistanceFactor * DifficultyControlPoint.SliderVelocity; + [JsonIgnore] + public bool GenerateTicks => DifficultyControlPoint.GenerateTicks; + /// /// The length of one span of this . /// @@ -64,7 +67,7 @@ namespace osu.Game.Rulesets.Catch.Objects int nodeIndex = 0; SliderEventDescriptor? lastEvent = null; - foreach (var e in SliderEventGenerator.Generate(StartTime, SpanDuration, Velocity, TickDistance, Path.Distance, this.SpanCount(), LegacyLastTickOffset, cancellationToken)) + foreach (var e in SliderEventGenerator.Generate(StartTime, SpanDuration, Velocity, TickDistance, Path.Distance, this.SpanCount(), LegacyLastTickOffset, GenerateTicks, cancellationToken)) { // generate tiny droplets since the last point if (lastEvent != null) diff --git a/osu.Game.Rulesets.Mania/Objects/HoldNote.cs b/osu.Game.Rulesets.Mania/Objects/HoldNote.cs index 22fab15c1b..4ec039c405 100644 --- a/osu.Game.Rulesets.Mania/Objects/HoldNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/HoldNote.cs @@ -117,7 +117,7 @@ namespace osu.Game.Rulesets.Mania.Objects private void createTicks(CancellationToken cancellationToken) { - if (tickSpacing == 0) + if (tickSpacing == 0 || !DifficultyControlPoint.GenerateTicks) return; for (double t = StartTime + tickSpacing; t <= EndTime - tickSpacing; t += tickSpacing) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModStrictTracking.cs b/osu.Game.Rulesets.Osu/Mods/OsuModStrictTracking.cs index 67b19124e1..eb59122686 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModStrictTracking.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModStrictTracking.cs @@ -102,7 +102,7 @@ namespace osu.Game.Rulesets.Osu.Mods protected override void CreateNestedHitObjects(CancellationToken cancellationToken) { - var sliderEvents = SliderEventGenerator.Generate(StartTime, SpanDuration, Velocity, TickDistance, Path.Distance, this.SpanCount(), LegacyLastTickOffset, cancellationToken); + var sliderEvents = SliderEventGenerator.Generate(StartTime, SpanDuration, Velocity, TickDistance, Path.Distance, this.SpanCount(), LegacyLastTickOffset, GenerateTicks, cancellationToken); foreach (var e in sliderEvents) { diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index a5468ff613..31117c0836 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -142,6 +142,11 @@ namespace osu.Game.Rulesets.Osu.Objects /// public double TickDistanceMultiplier = 1; + /// + /// Whether this should generate s. + /// + public bool GenerateTicks { get; private set; } + /// /// Whether this 's judgement is fully handled by its nested s. /// If false, this will be judged proportionally to the number of nested s hit. @@ -170,13 +175,14 @@ namespace osu.Game.Rulesets.Osu.Objects Velocity = scoringDistance / timingPoint.BeatLength; TickDistance = scoringDistance / difficulty.SliderTickRate * TickDistanceMultiplier; + GenerateTicks = DifficultyControlPoint.GenerateTicks; } protected override void CreateNestedHitObjects(CancellationToken cancellationToken) { base.CreateNestedHitObjects(cancellationToken); - var sliderEvents = SliderEventGenerator.Generate(StartTime, SpanDuration, Velocity, TickDistance, Path.Distance, this.SpanCount(), LegacyLastTickOffset, cancellationToken); + var sliderEvents = SliderEventGenerator.Generate(StartTime, SpanDuration, Velocity, TickDistance, Path.Distance, this.SpanCount(), LegacyLastTickOffset, GenerateTicks, cancellationToken); foreach (var e in sliderEvents) { diff --git a/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs b/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs index e1619e2857..09e465c37b 100644 --- a/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs +++ b/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs @@ -85,7 +85,7 @@ namespace osu.Game.Rulesets.Taiko.Objects private void createTicks(CancellationToken cancellationToken) { - if (tickSpacing == 0) + if (tickSpacing == 0 || !DifficultyControlPoint.GenerateTicks) return; bool first = true; diff --git a/osu.Game.Tests/Beatmaps/SliderEventGenerationTest.cs b/osu.Game.Tests/Beatmaps/SliderEventGenerationTest.cs index d30ab3dea1..077398f015 100644 --- a/osu.Game.Tests/Beatmaps/SliderEventGenerationTest.cs +++ b/osu.Game.Tests/Beatmaps/SliderEventGenerationTest.cs @@ -18,7 +18,7 @@ namespace osu.Game.Tests.Beatmaps [Test] public void TestSingleSpan() { - var events = SliderEventGenerator.Generate(start_time, span_duration, 1, span_duration / 2, span_duration, 1, null).ToArray(); + var events = SliderEventGenerator.Generate(start_time, span_duration, 1, span_duration / 2, span_duration, 1, null, true).ToArray(); Assert.That(events[0].Type, Is.EqualTo(SliderEventType.Head)); Assert.That(events[0].Time, Is.EqualTo(start_time)); @@ -33,7 +33,7 @@ namespace osu.Game.Tests.Beatmaps [Test] public void TestRepeat() { - var events = SliderEventGenerator.Generate(start_time, span_duration, 1, span_duration / 2, span_duration, 2, null).ToArray(); + var events = SliderEventGenerator.Generate(start_time, span_duration, 1, span_duration / 2, span_duration, 2, null, true).ToArray(); Assert.That(events[0].Type, Is.EqualTo(SliderEventType.Head)); Assert.That(events[0].Time, Is.EqualTo(start_time)); @@ -54,7 +54,7 @@ namespace osu.Game.Tests.Beatmaps [Test] public void TestNonEvenTicks() { - var events = SliderEventGenerator.Generate(start_time, span_duration, 1, 300, span_duration, 2, null).ToArray(); + var events = SliderEventGenerator.Generate(start_time, span_duration, 1, 300, span_duration, 2, null, true).ToArray(); Assert.That(events[0].Type, Is.EqualTo(SliderEventType.Head)); Assert.That(events[0].Time, Is.EqualTo(start_time)); @@ -87,7 +87,7 @@ namespace osu.Game.Tests.Beatmaps [Test] public void TestLegacyLastTickOffset() { - var events = SliderEventGenerator.Generate(start_time, span_duration, 1, span_duration / 2, span_duration, 1, 100).ToArray(); + var events = SliderEventGenerator.Generate(start_time, span_duration, 1, span_duration / 2, span_duration, 1, 100, true).ToArray(); Assert.That(events[2].Type, Is.EqualTo(SliderEventType.LegacyLastTick)); Assert.That(events[2].Time, Is.EqualTo(900)); @@ -99,7 +99,7 @@ namespace osu.Game.Tests.Beatmaps const double velocity = 5; const double min_distance = velocity * 10; - var events = SliderEventGenerator.Generate(start_time, span_duration, velocity, velocity, span_duration, 2, 0).ToArray(); + var events = SliderEventGenerator.Generate(start_time, span_duration, velocity, velocity, span_duration, 2, 0, true).ToArray(); Assert.Multiple(() => { @@ -114,5 +114,12 @@ namespace osu.Game.Tests.Beatmaps } }); } + + [Test] + public void TestNoTickGeneration() + { + var events = SliderEventGenerator.Generate(start_time, span_duration, 1, span_duration / 2, span_duration, 1, null, false).ToArray(); + Assert.That(events.Any(e => e.Type == SliderEventType.Tick), Is.False); + } } } diff --git a/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs index c199d1da59..dfa3552469 100644 --- a/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs @@ -40,13 +40,21 @@ namespace osu.Game.Beatmaps.ControlPoints set => SliderVelocityBindable.Value = value; } + /// + /// Whether or not slider ticks should be generated at this control point. + /// This exists for backwards compatibility with maps that abuse NaN slider velocity behavior on osu!stable (e.g. /b/2628991). + /// + public bool GenerateTicks { get; set; } = true; + public override bool IsRedundant(ControlPoint? existing) => existing is DifficultyControlPoint existingDifficulty - && SliderVelocity == existingDifficulty.SliderVelocity; + && SliderVelocity == existingDifficulty.SliderVelocity + && GenerateTicks == existingDifficulty.GenerateTicks; public override void CopyFrom(ControlPoint other) { SliderVelocity = ((DifficultyControlPoint)other).SliderVelocity; + GenerateTicks = ((DifficultyControlPoint)other).GenerateTicks; base.CopyFrom(other); } @@ -57,8 +65,9 @@ namespace osu.Game.Beatmaps.ControlPoints public bool Equals(DifficultyControlPoint? other) => base.Equals(other) - && SliderVelocity == other.SliderVelocity; + && SliderVelocity == other.SliderVelocity + && GenerateTicks == other.GenerateTicks; - public override int GetHashCode() => HashCode.Combine(base.GetHashCode(), SliderVelocity); + public override int GetHashCode() => HashCode.Combine(base.GetHashCode(), SliderVelocity, GenerateTicks); } } diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index 89d3465ab6..3d3661745a 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -373,7 +373,9 @@ namespace osu.Game.Beatmaps.Formats string[] split = line.Split(','); double time = getOffsetTime(Parsing.ParseDouble(split[0].Trim())); - double beatLength = Parsing.ParseDouble(split[1].Trim()); + double beatLength = Parsing.ParseDouble(split[1].Trim(), allowNaN: true); + + // If beatLength is NaN, speedMultiplier should still be 1 because all comparisons against NaN are false. double speedMultiplier = beatLength < 0 ? 100.0 / -beatLength : 1; TimeSignature timeSignature = TimeSignature.SimpleQuadruple; @@ -412,6 +414,9 @@ namespace osu.Game.Beatmaps.Formats if (timingChange) { + if (double.IsNaN(beatLength)) + throw new InvalidDataException("Beat length cannot be NaN in a timing control point"); + var controlPoint = CreateTimingControlPoint(); controlPoint.BeatLength = beatLength; @@ -425,6 +430,7 @@ namespace osu.Game.Beatmaps.Formats #pragma warning restore 618 { SliderVelocity = speedMultiplier, + GenerateTicks = !double.IsNaN(beatLength), }, timingChange); var effectPoint = new EffectControlPoint diff --git a/osu.Game/Rulesets/Objects/SliderEventGenerator.cs b/osu.Game/Rulesets/Objects/SliderEventGenerator.cs index d32a7cb16d..b77d15d565 100644 --- a/osu.Game/Rulesets/Objects/SliderEventGenerator.cs +++ b/osu.Game/Rulesets/Objects/SliderEventGenerator.cs @@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Objects { // ReSharper disable once MethodOverloadWithOptionalParameter public static IEnumerable Generate(double startTime, double spanDuration, double velocity, double tickDistance, double totalDistance, int spanCount, - double? legacyLastTickOffset, CancellationToken cancellationToken = default) + double? legacyLastTickOffset, bool shouldGenerateTicks, CancellationToken cancellationToken = default) { // A very lenient maximum length of a slider for ticks to be generated. // This exists for edge cases such as /b/1573664 where the beatmap has been edited by the user, and should never be reached in normal usage. @@ -41,16 +41,19 @@ namespace osu.Game.Rulesets.Objects double spanStartTime = startTime + span * spanDuration; bool reversed = span % 2 == 1; - var ticks = generateTicks(span, spanStartTime, spanDuration, reversed, length, tickDistance, minDistanceFromEnd, cancellationToken); - - if (reversed) + if (shouldGenerateTicks) { - // For repeat spans, ticks are returned in reverse-StartTime order, which is undesirable for some rulesets - ticks = ticks.Reverse(); - } + var ticks = generateTicks(span, spanStartTime, spanDuration, reversed, length, tickDistance, minDistanceFromEnd, cancellationToken); - foreach (var e in ticks) - yield return e; + if (reversed) + { + // For repeat spans, ticks are returned in reverse-StartTime order, which is undesirable for some rulesets + ticks = ticks.Reverse(); + } + + foreach (var e in ticks) + yield return e; + } if (span < spanCount - 1) { From 8f708c1dcf30e7300b8f0c71ab48989a234f5cda Mon Sep 17 00:00:00 2001 From: Khang Date: Mon, 22 Aug 2022 22:43:44 -0400 Subject: [PATCH 114/420] Turn GenerateTicks into a bindable to pass code quality tests --- .../ControlPoints/DifficultyControlPoint.cs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs index dfa3552469..ca49d316e0 100644 --- a/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs @@ -16,6 +16,7 @@ namespace osu.Game.Beatmaps.ControlPoints public static readonly DifficultyControlPoint DEFAULT = new DifficultyControlPoint { SliderVelocityBindable = { Disabled = true }, + GenerateTicksBindable = { Disabled = true }, }; /// @@ -29,6 +30,12 @@ namespace osu.Game.Beatmaps.ControlPoints MaxValue = 10 }; + /// + /// Whether or not slider ticks should be generated at this control point. + /// This exists for backwards compatibility with maps that abuse NaN slider velocity behavior on osu!stable (e.g. /b/2628991). + /// + public readonly BindableBool GenerateTicksBindable = new BindableBool(true); + public override Color4 GetRepresentingColour(OsuColour colours) => colours.Lime1; /// @@ -44,7 +51,11 @@ namespace osu.Game.Beatmaps.ControlPoints /// Whether or not slider ticks should be generated at this control point. /// This exists for backwards compatibility with maps that abuse NaN slider velocity behavior on osu!stable (e.g. /b/2628991). /// - public bool GenerateTicks { get; set; } = true; + public bool GenerateTicks + { + get => GenerateTicksBindable.Value; + set => GenerateTicksBindable.Value = value; + } public override bool IsRedundant(ControlPoint? existing) => existing is DifficultyControlPoint existingDifficulty From a81672f3dc9e885651f78852675fadb76940ace2 Mon Sep 17 00:00:00 2001 From: Khang Date: Mon, 22 Aug 2022 23:31:24 -0400 Subject: [PATCH 115/420] Use an infinite tick distance instead of directly disabling tick generation for SliderEventGenerator --- .../Objects/JuiceStream.cs | 5 +---- .../Mods/OsuModStrictTracking.cs | 2 +- osu.Game.Rulesets.Osu/Objects/Slider.cs | 10 ++------- .../Beatmaps/SliderEventGenerationTest.cs | 17 +++++---------- .../Rulesets/Objects/SliderEventGenerator.cs | 21 ++++++++----------- 5 files changed, 18 insertions(+), 37 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs index a9c7d938d2..311e15116e 100644 --- a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs +++ b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs @@ -40,9 +40,6 @@ namespace osu.Game.Rulesets.Catch.Objects [JsonIgnore] public double TickDistance => tickDistanceFactor * DifficultyControlPoint.SliderVelocity; - [JsonIgnore] - public bool GenerateTicks => DifficultyControlPoint.GenerateTicks; - /// /// The length of one span of this . /// @@ -67,7 +64,7 @@ namespace osu.Game.Rulesets.Catch.Objects int nodeIndex = 0; SliderEventDescriptor? lastEvent = null; - foreach (var e in SliderEventGenerator.Generate(StartTime, SpanDuration, Velocity, TickDistance, Path.Distance, this.SpanCount(), LegacyLastTickOffset, GenerateTicks, cancellationToken)) + foreach (var e in SliderEventGenerator.Generate(StartTime, SpanDuration, Velocity, TickDistance, Path.Distance, this.SpanCount(), LegacyLastTickOffset, cancellationToken)) { // generate tiny droplets since the last point if (lastEvent != null) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModStrictTracking.cs b/osu.Game.Rulesets.Osu/Mods/OsuModStrictTracking.cs index eb59122686..67b19124e1 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModStrictTracking.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModStrictTracking.cs @@ -102,7 +102,7 @@ namespace osu.Game.Rulesets.Osu.Mods protected override void CreateNestedHitObjects(CancellationToken cancellationToken) { - var sliderEvents = SliderEventGenerator.Generate(StartTime, SpanDuration, Velocity, TickDistance, Path.Distance, this.SpanCount(), LegacyLastTickOffset, GenerateTicks, cancellationToken); + var sliderEvents = SliderEventGenerator.Generate(StartTime, SpanDuration, Velocity, TickDistance, Path.Distance, this.SpanCount(), LegacyLastTickOffset, cancellationToken); foreach (var e in sliderEvents) { diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index 31117c0836..0689c49085 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -142,11 +142,6 @@ namespace osu.Game.Rulesets.Osu.Objects /// public double TickDistanceMultiplier = 1; - /// - /// Whether this should generate s. - /// - public bool GenerateTicks { get; private set; } - /// /// Whether this 's judgement is fully handled by its nested s. /// If false, this will be judged proportionally to the number of nested s hit. @@ -174,15 +169,14 @@ namespace osu.Game.Rulesets.Osu.Objects double scoringDistance = BASE_SCORING_DISTANCE * difficulty.SliderMultiplier * DifficultyControlPoint.SliderVelocity; Velocity = scoringDistance / timingPoint.BeatLength; - TickDistance = scoringDistance / difficulty.SliderTickRate * TickDistanceMultiplier; - GenerateTicks = DifficultyControlPoint.GenerateTicks; + TickDistance = DifficultyControlPoint.GenerateTicks ? (scoringDistance / difficulty.SliderTickRate * TickDistanceMultiplier) : double.PositiveInfinity; } protected override void CreateNestedHitObjects(CancellationToken cancellationToken) { base.CreateNestedHitObjects(cancellationToken); - var sliderEvents = SliderEventGenerator.Generate(StartTime, SpanDuration, Velocity, TickDistance, Path.Distance, this.SpanCount(), LegacyLastTickOffset, GenerateTicks, cancellationToken); + var sliderEvents = SliderEventGenerator.Generate(StartTime, SpanDuration, Velocity, TickDistance, Path.Distance, this.SpanCount(), LegacyLastTickOffset, cancellationToken); foreach (var e in sliderEvents) { diff --git a/osu.Game.Tests/Beatmaps/SliderEventGenerationTest.cs b/osu.Game.Tests/Beatmaps/SliderEventGenerationTest.cs index 077398f015..d30ab3dea1 100644 --- a/osu.Game.Tests/Beatmaps/SliderEventGenerationTest.cs +++ b/osu.Game.Tests/Beatmaps/SliderEventGenerationTest.cs @@ -18,7 +18,7 @@ namespace osu.Game.Tests.Beatmaps [Test] public void TestSingleSpan() { - var events = SliderEventGenerator.Generate(start_time, span_duration, 1, span_duration / 2, span_duration, 1, null, true).ToArray(); + var events = SliderEventGenerator.Generate(start_time, span_duration, 1, span_duration / 2, span_duration, 1, null).ToArray(); Assert.That(events[0].Type, Is.EqualTo(SliderEventType.Head)); Assert.That(events[0].Time, Is.EqualTo(start_time)); @@ -33,7 +33,7 @@ namespace osu.Game.Tests.Beatmaps [Test] public void TestRepeat() { - var events = SliderEventGenerator.Generate(start_time, span_duration, 1, span_duration / 2, span_duration, 2, null, true).ToArray(); + var events = SliderEventGenerator.Generate(start_time, span_duration, 1, span_duration / 2, span_duration, 2, null).ToArray(); Assert.That(events[0].Type, Is.EqualTo(SliderEventType.Head)); Assert.That(events[0].Time, Is.EqualTo(start_time)); @@ -54,7 +54,7 @@ namespace osu.Game.Tests.Beatmaps [Test] public void TestNonEvenTicks() { - var events = SliderEventGenerator.Generate(start_time, span_duration, 1, 300, span_duration, 2, null, true).ToArray(); + var events = SliderEventGenerator.Generate(start_time, span_duration, 1, 300, span_duration, 2, null).ToArray(); Assert.That(events[0].Type, Is.EqualTo(SliderEventType.Head)); Assert.That(events[0].Time, Is.EqualTo(start_time)); @@ -87,7 +87,7 @@ namespace osu.Game.Tests.Beatmaps [Test] public void TestLegacyLastTickOffset() { - var events = SliderEventGenerator.Generate(start_time, span_duration, 1, span_duration / 2, span_duration, 1, 100, true).ToArray(); + var events = SliderEventGenerator.Generate(start_time, span_duration, 1, span_duration / 2, span_duration, 1, 100).ToArray(); Assert.That(events[2].Type, Is.EqualTo(SliderEventType.LegacyLastTick)); Assert.That(events[2].Time, Is.EqualTo(900)); @@ -99,7 +99,7 @@ namespace osu.Game.Tests.Beatmaps const double velocity = 5; const double min_distance = velocity * 10; - var events = SliderEventGenerator.Generate(start_time, span_duration, velocity, velocity, span_duration, 2, 0, true).ToArray(); + var events = SliderEventGenerator.Generate(start_time, span_duration, velocity, velocity, span_duration, 2, 0).ToArray(); Assert.Multiple(() => { @@ -114,12 +114,5 @@ namespace osu.Game.Tests.Beatmaps } }); } - - [Test] - public void TestNoTickGeneration() - { - var events = SliderEventGenerator.Generate(start_time, span_duration, 1, span_duration / 2, span_duration, 1, null, false).ToArray(); - Assert.That(events.Any(e => e.Type == SliderEventType.Tick), Is.False); - } } } diff --git a/osu.Game/Rulesets/Objects/SliderEventGenerator.cs b/osu.Game/Rulesets/Objects/SliderEventGenerator.cs index b77d15d565..d32a7cb16d 100644 --- a/osu.Game/Rulesets/Objects/SliderEventGenerator.cs +++ b/osu.Game/Rulesets/Objects/SliderEventGenerator.cs @@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Objects { // ReSharper disable once MethodOverloadWithOptionalParameter public static IEnumerable Generate(double startTime, double spanDuration, double velocity, double tickDistance, double totalDistance, int spanCount, - double? legacyLastTickOffset, bool shouldGenerateTicks, CancellationToken cancellationToken = default) + double? legacyLastTickOffset, CancellationToken cancellationToken = default) { // A very lenient maximum length of a slider for ticks to be generated. // This exists for edge cases such as /b/1573664 where the beatmap has been edited by the user, and should never be reached in normal usage. @@ -41,20 +41,17 @@ namespace osu.Game.Rulesets.Objects double spanStartTime = startTime + span * spanDuration; bool reversed = span % 2 == 1; - if (shouldGenerateTicks) + var ticks = generateTicks(span, spanStartTime, spanDuration, reversed, length, tickDistance, minDistanceFromEnd, cancellationToken); + + if (reversed) { - var ticks = generateTicks(span, spanStartTime, spanDuration, reversed, length, tickDistance, minDistanceFromEnd, cancellationToken); - - if (reversed) - { - // For repeat spans, ticks are returned in reverse-StartTime order, which is undesirable for some rulesets - ticks = ticks.Reverse(); - } - - foreach (var e in ticks) - yield return e; + // For repeat spans, ticks are returned in reverse-StartTime order, which is undesirable for some rulesets + ticks = ticks.Reverse(); } + foreach (var e in ticks) + yield return e; + if (span < spanCount - 1) { yield return new SliderEventDescriptor From fbe8de2757ca2a752163bd6a2d61a4d788b55301 Mon Sep 17 00:00:00 2001 From: Khang Date: Tue, 23 Aug 2022 00:57:25 -0400 Subject: [PATCH 116/420] Disable the GetHashCode warning instead of using bindables --- .../ControlPoints/DifficultyControlPoint.cs | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs index ca49d316e0..ddb2b1e95c 100644 --- a/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs @@ -16,7 +16,6 @@ namespace osu.Game.Beatmaps.ControlPoints public static readonly DifficultyControlPoint DEFAULT = new DifficultyControlPoint { SliderVelocityBindable = { Disabled = true }, - GenerateTicksBindable = { Disabled = true }, }; /// @@ -30,12 +29,6 @@ namespace osu.Game.Beatmaps.ControlPoints MaxValue = 10 }; - /// - /// Whether or not slider ticks should be generated at this control point. - /// This exists for backwards compatibility with maps that abuse NaN slider velocity behavior on osu!stable (e.g. /b/2628991). - /// - public readonly BindableBool GenerateTicksBindable = new BindableBool(true); - public override Color4 GetRepresentingColour(OsuColour colours) => colours.Lime1; /// @@ -51,11 +44,7 @@ namespace osu.Game.Beatmaps.ControlPoints /// Whether or not slider ticks should be generated at this control point. /// This exists for backwards compatibility with maps that abuse NaN slider velocity behavior on osu!stable (e.g. /b/2628991). /// - public bool GenerateTicks - { - get => GenerateTicksBindable.Value; - set => GenerateTicksBindable.Value = value; - } + public bool GenerateTicks { get; set; } = true; public override bool IsRedundant(ControlPoint? existing) => existing is DifficultyControlPoint existingDifficulty @@ -79,6 +68,7 @@ namespace osu.Game.Beatmaps.ControlPoints && SliderVelocity == other.SliderVelocity && GenerateTicks == other.GenerateTicks; + // ReSharper disable once NonReadonlyMemberInGetHashCode public override int GetHashCode() => HashCode.Combine(base.GetHashCode(), SliderVelocity, GenerateTicks); } } From c1ced85b5e4fd5afe86e064774fda164be0ce88c Mon Sep 17 00:00:00 2001 From: Khang Date: Tue, 23 Aug 2022 14:07:18 -0400 Subject: [PATCH 117/420] Move GenerateTicks to LegacyDifficultyControlPoint and remove support for NaN slider velocity support for other rulesets (at least for now) --- .../Objects/JuiceStream.cs | 6 +++++- osu.Game.Rulesets.Mania/Objects/HoldNote.cs | 2 +- osu.Game.Rulesets.Osu/Objects/Slider.cs | 10 +++++++++- osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs | 2 +- .../ControlPoints/DifficultyControlPoint.cs | 16 +++------------ .../Beatmaps/Formats/LegacyBeatmapDecoder.cs | 1 - osu.Game/Beatmaps/Formats/LegacyDecoder.cs | 20 ++++++++++++++++--- 7 files changed, 36 insertions(+), 21 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs index 311e15116e..2a5564297c 100644 --- a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs +++ b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs @@ -11,6 +11,7 @@ using Newtonsoft.Json; using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Beatmaps.Formats; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; @@ -50,9 +51,12 @@ namespace osu.Game.Rulesets.Catch.Objects base.ApplyDefaultsToSelf(controlPointInfo, difficulty); TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime); +#pragma warning disable 618 + bool generateTicks = (DifficultyControlPoint as LegacyBeatmapDecoder.LegacyDifficultyControlPoint)?.GenerateTicks ?? true; +#pragma warning restore 618 velocityFactor = base_scoring_distance * difficulty.SliderMultiplier / timingPoint.BeatLength; - tickDistanceFactor = base_scoring_distance * difficulty.SliderMultiplier / difficulty.SliderTickRate; + tickDistanceFactor = generateTicks ? (base_scoring_distance * difficulty.SliderMultiplier / difficulty.SliderTickRate) : double.PositiveInfinity; } protected override void CreateNestedHitObjects(CancellationToken cancellationToken) diff --git a/osu.Game.Rulesets.Mania/Objects/HoldNote.cs b/osu.Game.Rulesets.Mania/Objects/HoldNote.cs index 4ec039c405..22fab15c1b 100644 --- a/osu.Game.Rulesets.Mania/Objects/HoldNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/HoldNote.cs @@ -117,7 +117,7 @@ namespace osu.Game.Rulesets.Mania.Objects private void createTicks(CancellationToken cancellationToken) { - if (tickSpacing == 0 || !DifficultyControlPoint.GenerateTicks) + if (tickSpacing == 0) return; for (double t = StartTime + tickSpacing; t <= EndTime - tickSpacing; t += tickSpacing) diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index 0689c49085..df1252c060 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -14,6 +14,8 @@ using osu.Framework.Caching; using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Beatmaps.Formats; +using osu.Game.Beatmaps.Legacy; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Osu.Judgements; using osu.Game.Rulesets.Scoring; @@ -166,10 +168,16 @@ namespace osu.Game.Rulesets.Osu.Objects TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime); + var legacyInfo = controlPointInfo as LegacyControlPointInfo; +#pragma warning disable 618 + var legacyDifficultyPoint = legacyInfo?.DifficultyPointAt(StartTime) as LegacyBeatmapDecoder.LegacyDifficultyControlPoint; +#pragma warning restore 618 + double scoringDistance = BASE_SCORING_DISTANCE * difficulty.SliderMultiplier * DifficultyControlPoint.SliderVelocity; + bool generateTicks = legacyDifficultyPoint?.GenerateTicks ?? true; Velocity = scoringDistance / timingPoint.BeatLength; - TickDistance = DifficultyControlPoint.GenerateTicks ? (scoringDistance / difficulty.SliderTickRate * TickDistanceMultiplier) : double.PositiveInfinity; + TickDistance = generateTicks ? (scoringDistance / difficulty.SliderTickRate * TickDistanceMultiplier) : double.PositiveInfinity; } protected override void CreateNestedHitObjects(CancellationToken cancellationToken) diff --git a/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs b/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs index 09e465c37b..e1619e2857 100644 --- a/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs +++ b/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs @@ -85,7 +85,7 @@ namespace osu.Game.Rulesets.Taiko.Objects private void createTicks(CancellationToken cancellationToken) { - if (tickSpacing == 0 || !DifficultyControlPoint.GenerateTicks) + if (tickSpacing == 0) return; bool first = true; diff --git a/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs index ddb2b1e95c..c199d1da59 100644 --- a/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs @@ -40,21 +40,13 @@ namespace osu.Game.Beatmaps.ControlPoints set => SliderVelocityBindable.Value = value; } - /// - /// Whether or not slider ticks should be generated at this control point. - /// This exists for backwards compatibility with maps that abuse NaN slider velocity behavior on osu!stable (e.g. /b/2628991). - /// - public bool GenerateTicks { get; set; } = true; - public override bool IsRedundant(ControlPoint? existing) => existing is DifficultyControlPoint existingDifficulty - && SliderVelocity == existingDifficulty.SliderVelocity - && GenerateTicks == existingDifficulty.GenerateTicks; + && SliderVelocity == existingDifficulty.SliderVelocity; public override void CopyFrom(ControlPoint other) { SliderVelocity = ((DifficultyControlPoint)other).SliderVelocity; - GenerateTicks = ((DifficultyControlPoint)other).GenerateTicks; base.CopyFrom(other); } @@ -65,10 +57,8 @@ namespace osu.Game.Beatmaps.ControlPoints public bool Equals(DifficultyControlPoint? other) => base.Equals(other) - && SliderVelocity == other.SliderVelocity - && GenerateTicks == other.GenerateTicks; + && SliderVelocity == other.SliderVelocity; - // ReSharper disable once NonReadonlyMemberInGetHashCode - public override int GetHashCode() => HashCode.Combine(base.GetHashCode(), SliderVelocity, GenerateTicks); + public override int GetHashCode() => HashCode.Combine(base.GetHashCode(), SliderVelocity); } } diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index 3d3661745a..f064d89e19 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -430,7 +430,6 @@ namespace osu.Game.Beatmaps.Formats #pragma warning restore 618 { SliderVelocity = speedMultiplier, - GenerateTicks = !double.IsNaN(beatLength), }, timingChange); var effectPoint = new EffectControlPoint diff --git a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs index 52e760a068..c3fd16e86f 100644 --- a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs @@ -168,11 +168,18 @@ namespace osu.Game.Beatmaps.Formats /// public double BpmMultiplier { get; private set; } + /// + /// Whether or not slider ticks should be generated at this control point. + /// This exists for backwards compatibility with maps that abuse NaN slider velocity behavior on osu!stable (e.g. /b/2628991). + /// + public bool GenerateTicks { get; private set; } = true; + public LegacyDifficultyControlPoint(double beatLength) : this() { // Note: In stable, the division occurs on floats, but with compiler optimisations turned on actually seems to occur on doubles via some .NET black magic (possibly inlining?). BpmMultiplier = beatLength < 0 ? Math.Clamp((float)-beatLength, 10, 10000) / 100.0 : 1; + GenerateTicks = !double.IsNaN(beatLength); } public LegacyDifficultyControlPoint() @@ -180,11 +187,17 @@ namespace osu.Game.Beatmaps.Formats SliderVelocityBindable.Precision = double.Epsilon; } + public override bool IsRedundant(ControlPoint? existing) + => existing is LegacyDifficultyControlPoint existingLegacyDifficulty + && base.IsRedundant(existing) + && GenerateTicks == existingLegacyDifficulty.GenerateTicks; + public override void CopyFrom(ControlPoint other) { base.CopyFrom(other); BpmMultiplier = ((LegacyDifficultyControlPoint)other).BpmMultiplier; + GenerateTicks = ((LegacyDifficultyControlPoint)other).GenerateTicks; } public override bool Equals(ControlPoint? other) @@ -193,10 +206,11 @@ namespace osu.Game.Beatmaps.Formats public bool Equals(LegacyDifficultyControlPoint? other) => base.Equals(other) - && BpmMultiplier == other.BpmMultiplier; + && BpmMultiplier == other.BpmMultiplier + && GenerateTicks == other.GenerateTicks; - // ReSharper disable once NonReadonlyMemberInGetHashCode - public override int GetHashCode() => HashCode.Combine(base.GetHashCode(), BpmMultiplier); + // ReSharper disable twice NonReadonlyMemberInGetHashCode + public override int GetHashCode() => HashCode.Combine(base.GetHashCode(), BpmMultiplier, GenerateTicks); } internal class LegacySampleControlPoint : SampleControlPoint, IEquatable From ada61e6fe7121960b2f531ff08bb29379ade63b1 Mon Sep 17 00:00:00 2001 From: Khang Date: Tue, 23 Aug 2022 14:10:26 -0400 Subject: [PATCH 118/420] Forgot to undo broken changes in JuiceStream --- osu.Game.Rulesets.Catch/Objects/JuiceStream.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs index 2a5564297c..311e15116e 100644 --- a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs +++ b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs @@ -11,7 +11,6 @@ using Newtonsoft.Json; using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Beatmaps.Formats; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; @@ -51,12 +50,9 @@ namespace osu.Game.Rulesets.Catch.Objects base.ApplyDefaultsToSelf(controlPointInfo, difficulty); TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime); -#pragma warning disable 618 - bool generateTicks = (DifficultyControlPoint as LegacyBeatmapDecoder.LegacyDifficultyControlPoint)?.GenerateTicks ?? true; -#pragma warning restore 618 velocityFactor = base_scoring_distance * difficulty.SliderMultiplier / timingPoint.BeatLength; - tickDistanceFactor = generateTicks ? (base_scoring_distance * difficulty.SliderMultiplier / difficulty.SliderTickRate) : double.PositiveInfinity; + tickDistanceFactor = base_scoring_distance * difficulty.SliderMultiplier / difficulty.SliderTickRate; } protected override void CreateNestedHitObjects(CancellationToken cancellationToken) From ec9daec93bcea47e7983564db3fb75b1c531733a Mon Sep 17 00:00:00 2001 From: Khang Date: Tue, 23 Aug 2022 14:18:40 -0400 Subject: [PATCH 119/420] Remove unnecessary workaround --- osu.Game.Rulesets.Osu/Objects/Slider.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index df1252c060..a7495a2809 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -167,10 +167,8 @@ namespace osu.Game.Rulesets.Osu.Objects base.ApplyDefaultsToSelf(controlPointInfo, difficulty); TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime); - - var legacyInfo = controlPointInfo as LegacyControlPointInfo; #pragma warning disable 618 - var legacyDifficultyPoint = legacyInfo?.DifficultyPointAt(StartTime) as LegacyBeatmapDecoder.LegacyDifficultyControlPoint; + var legacyDifficultyPoint = DifficultyControlPoint as LegacyBeatmapDecoder.LegacyDifficultyControlPoint; #pragma warning restore 618 double scoringDistance = BASE_SCORING_DISTANCE * difficulty.SliderMultiplier * DifficultyControlPoint.SliderVelocity; From c9f364d6a086c7582040c9b66a94eca09e8ce25e Mon Sep 17 00:00:00 2001 From: Khang Date: Wed, 24 Aug 2022 02:10:19 -0400 Subject: [PATCH 120/420] Document why beatLength can be NaN --- osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index f064d89e19..75500fbc4e 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -373,6 +373,8 @@ namespace osu.Game.Beatmaps.Formats string[] split = line.Split(','); double time = getOffsetTime(Parsing.ParseDouble(split[0].Trim())); + + // beatLength is allowed to be NaN to handle an edge case in which some beatmaps use NaN slider velocity to disable slider tick generation (see LegacyDifficultyControlPoint). double beatLength = Parsing.ParseDouble(split[1].Trim(), allowNaN: true); // If beatLength is NaN, speedMultiplier should still be 1 because all comparisons against NaN are false. From c7d4c739aa067825e89a97f11a8d3ae3e4965602 Mon Sep 17 00:00:00 2001 From: Khang Date: Wed, 24 Aug 2022 02:53:55 -0400 Subject: [PATCH 121/420] Add a basic NaN control point test for LegacyBeatmapDecoder --- .../Formats/LegacyBeatmapDecoderTest.cs | 27 +++++++++++++++++++ .../Resources/nan-control-points.osu | 15 +++++++++++ 2 files changed, 42 insertions(+) create mode 100644 osu.Game.Tests/Resources/nan-control-points.osu diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index 9acd3a6cab..7bd32eb5bd 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -919,5 +919,32 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.That(controlPoints[1].Position, Is.Not.EqualTo(Vector2.Zero)); } } + + [Test] + public void TestNaNControlPoints() + { + var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false }; + + using (var resStream = TestResources.OpenResource("nan-control-points.osu")) + using (var stream = new LineBufferedReader(resStream)) + { + var controlPoints = (LegacyControlPointInfo)decoder.Decode(stream).ControlPointInfo; + + Assert.That(controlPoints.TimingPoints.Count, Is.EqualTo(1)); + Assert.That(controlPoints.DifficultyPoints.Count, Is.EqualTo(3)); + + Assert.That(controlPoints.TimingPointAt(1000).BeatLength, Is.EqualTo(500)); + + Assert.That(controlPoints.DifficultyPointAt(1000).SliderVelocity, Is.EqualTo(1)); + Assert.That(controlPoints.DifficultyPointAt(2000).SliderVelocity, Is.EqualTo(1)); + Assert.That(controlPoints.DifficultyPointAt(3000).SliderVelocity, Is.EqualTo(1)); + +#pragma warning disable 618 + Assert.That(((LegacyBeatmapDecoder.LegacyDifficultyControlPoint)controlPoints.DifficultyPointAt(1000)).GenerateTicks, Is.True); + Assert.That(((LegacyBeatmapDecoder.LegacyDifficultyControlPoint)controlPoints.DifficultyPointAt(2000)).GenerateTicks, Is.False); + Assert.That(((LegacyBeatmapDecoder.LegacyDifficultyControlPoint)controlPoints.DifficultyPointAt(3000)).GenerateTicks, Is.True); +#pragma warning restore 618 + } + } } } diff --git a/osu.Game.Tests/Resources/nan-control-points.osu b/osu.Game.Tests/Resources/nan-control-points.osu new file mode 100644 index 0000000000..dcaa705116 --- /dev/null +++ b/osu.Game.Tests/Resources/nan-control-points.osu @@ -0,0 +1,15 @@ +osu file format v14 + +[TimingPoints] + +// NaN bpm (should be rejected) +0,NaN,4,2,0,100,1,0 + +// 120 bpm +1000,500,4,2,0,100,1,0 + +// NaN slider velocity +2000,NaN,4,3,0,100,0,1 + +// 1.0x slider velocity +3000,-100,4,3,0,100,0,1 \ No newline at end of file From 9c6968e96dfb322cbcc08a17dcd5ed0dbaf6ea00 Mon Sep 17 00:00:00 2001 From: Khang Date: Wed, 24 Aug 2022 02:54:40 -0400 Subject: [PATCH 122/420] Remove unused import in Slider --- osu.Game.Rulesets.Osu/Objects/Slider.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index a7495a2809..e3c1b1e168 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -15,7 +15,6 @@ using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.Formats; -using osu.Game.Beatmaps.Legacy; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Osu.Judgements; using osu.Game.Rulesets.Scoring; From adea29c10604fb50a91d680e751f744c84cfe559 Mon Sep 17 00:00:00 2001 From: Khang Date: Wed, 24 Aug 2022 03:37:33 -0400 Subject: [PATCH 123/420] Fix test failures --- osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs | 4 +--- osu.Game/Beatmaps/Formats/LegacyDecoder.cs | 5 ++--- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index 7bd32eb5bd..9fc1eb7650 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -931,16 +931,14 @@ namespace osu.Game.Tests.Beatmaps.Formats var controlPoints = (LegacyControlPointInfo)decoder.Decode(stream).ControlPointInfo; Assert.That(controlPoints.TimingPoints.Count, Is.EqualTo(1)); - Assert.That(controlPoints.DifficultyPoints.Count, Is.EqualTo(3)); + Assert.That(controlPoints.DifficultyPoints.Count, Is.EqualTo(2)); Assert.That(controlPoints.TimingPointAt(1000).BeatLength, Is.EqualTo(500)); - Assert.That(controlPoints.DifficultyPointAt(1000).SliderVelocity, Is.EqualTo(1)); Assert.That(controlPoints.DifficultyPointAt(2000).SliderVelocity, Is.EqualTo(1)); Assert.That(controlPoints.DifficultyPointAt(3000).SliderVelocity, Is.EqualTo(1)); #pragma warning disable 618 - Assert.That(((LegacyBeatmapDecoder.LegacyDifficultyControlPoint)controlPoints.DifficultyPointAt(1000)).GenerateTicks, Is.True); Assert.That(((LegacyBeatmapDecoder.LegacyDifficultyControlPoint)controlPoints.DifficultyPointAt(2000)).GenerateTicks, Is.False); Assert.That(((LegacyBeatmapDecoder.LegacyDifficultyControlPoint)controlPoints.DifficultyPointAt(3000)).GenerateTicks, Is.True); #pragma warning restore 618 diff --git a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs index c3fd16e86f..3d65ab8e0f 100644 --- a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs @@ -188,9 +188,8 @@ namespace osu.Game.Beatmaps.Formats } public override bool IsRedundant(ControlPoint? existing) - => existing is LegacyDifficultyControlPoint existingLegacyDifficulty - && base.IsRedundant(existing) - && GenerateTicks == existingLegacyDifficulty.GenerateTicks; + => base.IsRedundant(existing) + && GenerateTicks == ((existing as LegacyDifficultyControlPoint)?.GenerateTicks ?? true); public override void CopyFrom(ControlPoint other) { From f3847b90fddaa6418c457a68ddd2912f637b0443 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 24 Aug 2022 19:36:01 +0900 Subject: [PATCH 124/420] Tidy up attach logic --- osu.Game/Rulesets/UI/DrawableRuleset.cs | 7 ++++--- osu.Game/Rulesets/UI/RulesetInputManager.cs | 23 +++++++++------------ osu.Game/Screens/Play/HUDOverlay.cs | 7 +++++-- 3 files changed, 19 insertions(+), 18 deletions(-) diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index 02df4d8fb3..73acb1759f 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -39,7 +39,7 @@ namespace osu.Game.Rulesets.UI /// Displays an interactive ruleset gameplay instance. /// /// The type of HitObject contained by this DrawableRuleset. - public abstract class DrawableRuleset : DrawableRuleset, IProvideCursor, ICanAttachKeyCounter + public abstract class DrawableRuleset : DrawableRuleset, IProvideCursor, ICanAttachHUDPieces where TObject : HitObject { public override event Action NewResult; @@ -339,9 +339,10 @@ namespace osu.Game.Rulesets.UI public abstract DrawableHitObject CreateDrawableRepresentation(TObject h); public void Attach(KeyCounterDisplay keyCounter) => - (KeyBindingInputManager as ICanAttachKeyCounter)?.Attach(keyCounter); + (KeyBindingInputManager as ICanAttachHUDPieces)?.Attach(keyCounter); - public void Attach(ClicksPerSecondCalculator calculator) => (KeyBindingInputManager as ICanAttachKeyCounter)?.Attach(calculator); + public void Attach(ClicksPerSecondCalculator calculator) => + (KeyBindingInputManager as ICanAttachHUDPieces)?.Attach(calculator); /// /// Creates a key conversion input manager. An exception will be thrown if a valid is not returned. diff --git a/osu.Game/Rulesets/UI/RulesetInputManager.cs b/osu.Game/Rulesets/UI/RulesetInputManager.cs index 4b7ce22cfc..401ebbfd74 100644 --- a/osu.Game/Rulesets/UI/RulesetInputManager.cs +++ b/osu.Game/Rulesets/UI/RulesetInputManager.cs @@ -25,7 +25,7 @@ using static osu.Game.Input.Handlers.ReplayInputHandler; namespace osu.Game.Rulesets.UI { - public abstract class RulesetInputManager : PassThroughInputManager, ICanAttachKeyCounter, IHasReplayHandler, IHasRecordingHandler + public abstract class RulesetInputManager : PassThroughInputManager, ICanAttachHUDPieces, IHasReplayHandler, IHasRecordingHandler where T : struct { public readonly KeyBindingContainer KeyBindingContainer; @@ -169,7 +169,7 @@ namespace osu.Game.Rulesets.UI .Select(action => new KeyCounterAction(action))); } - public class ActionReceptor : KeyCounterDisplay.Receptor, IKeyBindingHandler + private class ActionReceptor : KeyCounterDisplay.Receptor, IKeyBindingHandler { public ActionReceptor(KeyCounterDisplay target) : base(target) @@ -191,8 +191,6 @@ namespace osu.Game.Rulesets.UI public void Attach(ClicksPerSecondCalculator calculator) { - if (calculator == null) return; - var listener = new ActionListener(calculator); KeyBindingContainer.Add(listener); @@ -200,23 +198,22 @@ namespace osu.Game.Rulesets.UI calculator.Listener = listener; } - public class ActionListener : ClicksPerSecondCalculator.InputListener, IKeyBindingHandler + private class ActionListener : ClicksPerSecondCalculator.InputListener, IKeyBindingHandler { + public ActionListener(ClicksPerSecondCalculator calculator) + : base(calculator) + { + } + public bool OnPressed(KeyBindingPressEvent e) { Calculator.AddTimestamp(); - return false; } public void OnReleased(KeyBindingReleaseEvent e) { } - - public ActionListener(ClicksPerSecondCalculator calculator) - : base(calculator) - { - } } #endregion @@ -256,10 +253,10 @@ namespace osu.Game.Rulesets.UI } /// - /// Supports attaching a . + /// Supports attaching various HUD pieces. /// Keys will be populated automatically and a receptor will be injected inside. /// - public interface ICanAttachKeyCounter + public interface ICanAttachHUDPieces { void Attach(KeyCounterDisplay keyCounter); void Attach(ClicksPerSecondCalculator calculator); diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index b27efaf13a..f9f3693385 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -264,8 +264,11 @@ namespace osu.Game.Screens.Play protected virtual void BindDrawableRuleset(DrawableRuleset drawableRuleset) { - (drawableRuleset as ICanAttachKeyCounter)?.Attach(KeyCounter); - (drawableRuleset as ICanAttachKeyCounter)?.Attach(clicksPerSecondCalculator); + if (drawableRuleset is ICanAttachHUDPieces attachTarget) + { + attachTarget.Attach(KeyCounter); + attachTarget.Attach(clicksPerSecondCalculator); + } replayLoaded.BindTo(drawableRuleset.HasReplayLoaded); } From 5cddc7ed1f8fadccedccc4b6efe0a836d1ec3037 Mon Sep 17 00:00:00 2001 From: Ryuki Date: Wed, 24 Aug 2022 17:12:52 +0200 Subject: [PATCH 125/420] Code cleanup (CPS) --- .../Gameplay/TestSceneClicksPerSecond.cs | 31 ++---- osu.Game/Rulesets/UI/RulesetInputManager.cs | 10 +- .../ClicksPerSecondCalculator.cs | 96 ++++++------------- .../ClicksPerSecond/ClicksPerSecondCounter.cs | 6 +- 4 files changed, 42 insertions(+), 101 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneClicksPerSecond.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneClicksPerSecond.cs index 137ab7acdb..a140460251 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneClicksPerSecond.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneClicksPerSecond.cs @@ -29,7 +29,6 @@ namespace osu.Game.Tests.Visual.Gameplay { private DependencyProvidingContainer dependencyContainer = null!; private ClicksPerSecondCalculator calculator = null!; - private ManualInputListener? listener; private GameplayClockContainer gameplayClockContainer = null!; private ManualClock manualClock = null!; private DrawableRuleset? drawableRuleset; @@ -151,7 +150,6 @@ namespace osu.Game.Tests.Visual.Gameplay } } }; - calculator.Listener = listener = new ManualInputListener(calculator); }); } @@ -189,7 +187,7 @@ namespace osu.Game.Tests.Visual.Gameplay foreach (double timestamp in inputs) { seekAllClocks(timestamp); - listener?.AddInput(); + calculator.AddTimestamp(); } seekAllClocks(baseTime); @@ -270,18 +268,6 @@ namespace osu.Game.Tests.Visual.Gameplay public IBindable WaitingOnFrames => new Bindable(); } - private class ManualInputListener : ClicksPerSecondCalculator.InputListener - { - public void AddInput() => Calculator.AddTimestamp(); - - public ManualInputListener(ClicksPerSecondCalculator calculator) - : base(calculator) - { - } - } - -#nullable disable - [SuppressMessage("ReSharper", "UnassignedGetOnlyAutoProperty")] private class TestDrawableRuleset : DrawableRuleset { @@ -299,24 +285,19 @@ namespace osu.Game.Tests.Visual.Gameplay remove => throw new InvalidOperationException($"{nameof(RevertResult)} operations not supported in test context"); } - public override Playfield Playfield => null; - public override Container Overlays => null; - public override Container FrameStableComponents => null; + public override Playfield Playfield => null!; + public override Container Overlays => null!; + public override Container FrameStableComponents => null!; public override IFrameStableClock FrameStableClock { get; } internal override bool FrameStablePlayback { get; set; } public override IReadOnlyList Mods => Array.Empty(); public override double GameplayStartTime => 0; - public override GameplayCursorContainer Cursor => null; - - public TestDrawableRuleset() - : base(new OsuRuleset()) - { - } + public override GameplayCursorContainer Cursor => null!; public TestDrawableRuleset(IFrameStableClock frameStableClock) - : this() + : base(new OsuRuleset()) { FrameStableClock = frameStableClock; } diff --git a/osu.Game/Rulesets/UI/RulesetInputManager.cs b/osu.Game/Rulesets/UI/RulesetInputManager.cs index 401ebbfd74..dcd2c4fb5d 100644 --- a/osu.Game/Rulesets/UI/RulesetInputManager.cs +++ b/osu.Game/Rulesets/UI/RulesetInputManager.cs @@ -194,20 +194,20 @@ namespace osu.Game.Rulesets.UI var listener = new ActionListener(calculator); KeyBindingContainer.Add(listener); - - calculator.Listener = listener; } - private class ActionListener : ClicksPerSecondCalculator.InputListener, IKeyBindingHandler + private class ActionListener : Component, IKeyBindingHandler { + private readonly ClicksPerSecondCalculator calculator; + public ActionListener(ClicksPerSecondCalculator calculator) - : base(calculator) { + this.calculator = calculator; } public bool OnPressed(KeyBindingPressEvent e) { - Calculator.AddTimestamp(); + calculator.AddTimestamp(); return false; } diff --git a/osu.Game/Screens/Play/HUD/ClicksPerSecond/ClicksPerSecondCalculator.cs b/osu.Game/Screens/Play/HUD/ClicksPerSecond/ClicksPerSecondCalculator.cs index d9c3c6ffec..20ac923b82 100644 --- a/osu.Game/Screens/Play/HUD/ClicksPerSecond/ClicksPerSecondCalculator.cs +++ b/osu.Game/Screens/Play/HUD/ClicksPerSecond/ClicksPerSecondCalculator.cs @@ -1,12 +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.Graphics; -using osu.Framework.Timing; using osu.Game.Rulesets.UI; namespace osu.Game.Screens.Play.HUD.ClicksPerSecond @@ -15,89 +13,53 @@ namespace osu.Game.Screens.Play.HUD.ClicksPerSecond { private readonly List timestamps; - private InputListener? listener; + [Resolved] + private IGameplayClock gameplayClock { get; set; } = null!; [Resolved] - private IGameplayClock? gameplayClock { get; set; } + private DrawableRuleset drawableRuleset { get; set; } = null!; - [Resolved(canBeNull: true)] - private DrawableRuleset? drawableRuleset { get; set; } + private double rate; - public InputListener Listener - { - set - { - onResetRequested?.Invoke(); - listener = value; - } - } + // The latest timestamp GC seeked. Does not affect normal gameplay + // but prevents duplicate inputs on replays. + private double latestTime = double.NegativeInfinity; - private event Action? onResetRequested; - - private IClock? workingClock => drawableRuleset?.FrameStableClock; - - private double baseRate; - - private double rate - { - get - { - if (gameplayClock?.TrueGameplayRate > 0) - { - baseRate = gameplayClock.TrueGameplayRate; - } - - return baseRate; - } - } - - private double maxTime = double.NegativeInfinity; - - public bool Ready => workingClock != null && gameplayClock != null && listener != null; - public int Value => timestamps.Count(isTimestampWithinSpan); + public int Value { get; private set; } public ClicksPerSecondCalculator() { RelativeSizeAxes = Axes.Both; timestamps = new List(); - onResetRequested += cleanUp; } - private void cleanUp() + protected override void Update() { - timestamps.Clear(); - maxTime = double.NegativeInfinity; + base.Update(); + + // When pausing in replays (using the space bar) GC.TrueGameplayRate returns 0 + // To prevent CPS value being 0, we store and use the last non-zero TrueGameplayRate + if (gameplayClock.TrueGameplayRate > 0) + { + rate = gameplayClock.TrueGameplayRate; + } + + Value = timestamps.Count(timestamp => + { + double window = 1000 * rate; + double relativeTime = drawableRuleset.FrameStableClock.CurrentTime - timestamp; + return relativeTime > 0 && relativeTime <= window; + }); } public void AddTimestamp() { - if (workingClock == null) return; - - if (workingClock.CurrentTime >= maxTime) + // Discard inputs if current gameplay time is not the latest + // to prevent duplicate inputs + if (drawableRuleset.FrameStableClock.CurrentTime >= latestTime) { - timestamps.Add(workingClock.CurrentTime); - maxTime = workingClock.CurrentTime; - } - } - - private bool isTimestampWithinSpan(double timestamp) - { - if (workingClock == null) return false; - - double span = 1000 * rate; - double relativeTime = workingClock.CurrentTime - timestamp; - return relativeTime > 0 && relativeTime <= span; - } - - public abstract class InputListener : Component - { - protected ClicksPerSecondCalculator Calculator; - - protected InputListener(ClicksPerSecondCalculator calculator) - { - RelativeSizeAxes = Axes.Both; - Depth = float.MinValue; - Calculator = calculator; + timestamps.Add(drawableRuleset.FrameStableClock.CurrentTime); + latestTime = drawableRuleset.FrameStableClock.CurrentTime; } } } diff --git a/osu.Game/Screens/Play/HUD/ClicksPerSecond/ClicksPerSecondCounter.cs b/osu.Game/Screens/Play/HUD/ClicksPerSecond/ClicksPerSecondCounter.cs index 3ff06d5217..243d8ed1e8 100644 --- a/osu.Game/Screens/Play/HUD/ClicksPerSecond/ClicksPerSecondCounter.cs +++ b/osu.Game/Screens/Play/HUD/ClicksPerSecond/ClicksPerSecondCounter.cs @@ -16,9 +16,7 @@ namespace osu.Game.Screens.Play.HUD.ClicksPerSecond { public class ClicksPerSecondCounter : RollingCounter, ISkinnableDrawable { - private const float alpha_when_invalid = 0.3f; - - [Resolved(canBeNull: false)] + [Resolved] private ClicksPerSecondCalculator calculator { get; set; } = null!; protected override double RollingDuration => 350; @@ -40,7 +38,7 @@ namespace osu.Game.Screens.Play.HUD.ClicksPerSecond { base.Update(); - Current.Value = calculator.Ready ? calculator.Value : 0; + Current.Value = calculator.Value; } protected override IHasText CreateText() => new TextComponent(); From 29f3724047ec5e457a89f797f20689ac2e87ef54 Mon Sep 17 00:00:00 2001 From: HiddenNode Date: Fri, 26 Aug 2022 01:28:57 +0100 Subject: [PATCH 126/420] Changed UprightUnscaledContainer to UprightUnstretchedContainer. --- .../Containers/UprightUnscaledContainer.cs | 66 ++++++++++++++++--- 1 file changed, 58 insertions(+), 8 deletions(-) diff --git a/osu.Game/Graphics/Containers/UprightUnscaledContainer.cs b/osu.Game/Graphics/Containers/UprightUnscaledContainer.cs index b6c3b56c4a..c64e4ee2b7 100644 --- a/osu.Game/Graphics/Containers/UprightUnscaledContainer.cs +++ b/osu.Game/Graphics/Containers/UprightUnscaledContainer.cs @@ -12,13 +12,24 @@ namespace osu.Game.Graphics.Containers /// /// A container that prevents itself and its children from getting rotated, scaled or flipped with its Parent. /// - public class UprightUnscaledContainer : Container + public class UprightUnstretchedContainer : Container { protected override Container Content => content; private readonly Container content; - public UprightUnscaledContainer() + /// + /// Controls how much this container scales compared to its parent (default is 1.0f). + /// + public float ScalingFactor { get; set; } + + /// + /// Controls the scaling of this container. + /// + public ScaleMode Scaling { get; set; } + + public UprightUnstretchedContainer() { + Scaling = ScaleMode.NoScaling; InternalChild = content = new GrowToFitContainer(); AddLayout(layout); } @@ -31,7 +42,7 @@ namespace osu.Game.Graphics.Containers if (!layout.IsValid) { - keepUprightAndUnscaled(); + keepUprightAndUnstretched(); layout.Validate(); } } @@ -39,7 +50,7 @@ namespace osu.Game.Graphics.Containers /// /// Keeps the drawable upright and unstretched preventing it from being rotated, sheared, scaled or flipped with its Parent. /// - private void keepUprightAndUnscaled() + private void keepUprightAndUnstretched() { // Decomposes the inverse of the parent FrawInfo.Matrix into rotation, shear and scale. var parentMatrix = Parent.DrawInfo.Matrix; @@ -58,13 +69,32 @@ namespace osu.Game.Graphics.Containers Matrix3 m = Matrix3.CreateRotationZ(-angle); reversedParrent *= m; - // Extract shear and scale. + // Extract shear. + float alpha = reversedParrent.M21 / reversedParrent.M22; + Shear = new Vector2(-alpha, 0); + + // Etract scale. float sx = reversedParrent.M11; float sy = reversedParrent.M22; - float alpha = reversedParrent.M21 / reversedParrent.M22; - Scale = new Vector2(sx, sy); - Shear = new Vector2(-alpha, 0); + Vector3 parentScale = parentMatrix.ExtractScale(); + + float usedScale = 1.0f; + + switch (Scaling) + { + case ScaleMode.Horizontal: + usedScale = parentScale.X; + break; + + case ScaleMode.Vertical: + usedScale = parentScale.Y; + break; + } + + usedScale = 1.0f + (usedScale - 1.0f) * ScalingFactor; + + Scale = new Vector2(sx * usedScale, sy * usedScale); } /// @@ -91,4 +121,24 @@ namespace osu.Game.Graphics.Containers } } } + + public enum ScaleMode + { + /// + /// Prevent this container from scaling. + /// + NoScaling, + + /// + /// Scale This container (vertically and horizontally) with the vertical axis of its parent + /// preserving the aspect ratio of the container. + /// + Vertical, + + /// + /// Scales This container (vertically and horizontally) with the horizontal axis of its parent + /// preserving the aspect ratio of the container. + /// + Horizontal, + } } From d98357aa5748d5a6ee400925684dc45a7bcdaec6 Mon Sep 17 00:00:00 2001 From: HiddenNode Date: Fri, 26 Aug 2022 01:30:44 +0100 Subject: [PATCH 127/420] Made text inside SongProgressInfo scale. --- osu.Game/Screens/Play/HUD/SongProgressInfo.cs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/SongProgressInfo.cs b/osu.Game/Screens/Play/HUD/SongProgressInfo.cs index c4cdc0cb29..83fe8b14c3 100644 --- a/osu.Game/Screens/Play/HUD/SongProgressInfo.cs +++ b/osu.Game/Screens/Play/HUD/SongProgressInfo.cs @@ -55,11 +55,13 @@ namespace osu.Game.Screens.Play.HUD Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, AutoSizeAxes = Axes.Both, - Child = new UprightUnscaledContainer + Child = new UprightUnstretchedContainer { Origin = Anchor.Centre, Anchor = Anchor.Centre, AutoSizeAxes = Axes.Both, + Scaling = ScaleMode.Vertical, + ScalingFactor = 0.5f, Child = timeCurrent = new OsuSpriteText { Origin = Anchor.Centre, @@ -74,11 +76,13 @@ namespace osu.Game.Screens.Play.HUD Origin = Anchor.Centre, Anchor = Anchor.Centre, AutoSizeAxes = Axes.Both, - Child = new UprightUnscaledContainer + Child = new UprightUnstretchedContainer { Origin = Anchor.Centre, Anchor = Anchor.Centre, AutoSizeAxes = Axes.Both, + Scaling = ScaleMode.Vertical, + ScalingFactor = 0.5f, Child = progress = new OsuSpriteText { Origin = Anchor.Centre, @@ -93,11 +97,13 @@ namespace osu.Game.Screens.Play.HUD Origin = Anchor.CentreRight, Anchor = Anchor.CentreRight, AutoSizeAxes = Axes.Both, - Child = new UprightUnscaledContainer + Child = new UprightUnstretchedContainer { Origin = Anchor.Centre, Anchor = Anchor.Centre, AutoSizeAxes = Axes.Both, + Scaling = ScaleMode.Vertical, + ScalingFactor = 0.5f, Child = timeLeft = new OsuSpriteText { Origin = Anchor.Centre, From b0e7f63361aa072841a0614b6454aeb5aa243158 Mon Sep 17 00:00:00 2001 From: MBmasher Date: Fri, 26 Aug 2022 12:34:33 +1000 Subject: [PATCH 128/420] Update angle multiplier to nerf repeated angles --- .../Evaluators/FlashlightEvaluator.cs | 28 ++++++++++++------- .../Difficulty/Skills/Flashlight.cs | 2 +- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/FlashlightEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/FlashlightEvaluator.cs index 9d2696c978..8cf8190554 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/FlashlightEvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/FlashlightEvaluator.cs @@ -7,6 +7,7 @@ using System; using osu.Game.Rulesets.Difficulty.Preprocessing; using osu.Game.Rulesets.Osu.Difficulty.Preprocessing; using osu.Game.Rulesets.Osu.Objects; +using osu.Framework.Utils; namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators { @@ -18,7 +19,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators private const double min_velocity = 0.5; private const double slider_multiplier = 1.3; - private const double min_grid_multiplier = 0.35; + private const double min_angle_multiplier = 0.2; /// /// Evaluates the difficulty of memorising and hitting an object, based on: @@ -46,6 +47,13 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators OsuDifficultyHitObject lastObj = osuCurrent; + int angleRepeatCount = 0; + + // We want to round angles to make abusing the nerf a bit harder. + double initialRoundedAngle = 0.0; + if (osuCurrent.Angle != null) + initialRoundedAngle = Math.Round(MathUtils.RadiansToDegrees(osuCurrent.Angle.Value) / 2.0) * 2.0; + // This is iterating backwards in time from the current object. for (int i = 0; i < Math.Min(current.Index, 10); i++) { @@ -69,6 +77,13 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators double opacityBonus = 1.0 + max_opacity_bonus * (1.0 - osuCurrent.OpacityAt(currentHitObject.StartTime, hidden)); result += stackNerf * opacityBonus * scalingFactor * jumpDistance / cumulativeStrainTime; + + if (currentObj.Angle != null && osuCurrent.Angle != null) { + double roundedAngle = Math.Round(MathUtils.RadiansToDegrees(currentObj.Angle.Value) / 2.0) * 2.0; + + if (roundedAngle == initialRoundedAngle) + angleRepeatCount++; + } } lastObj = currentObj; @@ -80,15 +95,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators if (hidden) result *= 1.0 + hidden_bonus; - // Nerf patterns with angles that are commonly used in grid maps. - // 0 deg, 60 deg, 120 deg and 180 deg are commonly used in hexgrid maps. - // 0 deg, 45 deg, 90 deg, 135 deg and 180 deg are commonly used in squaregrid maps. - if (osuCurrent.Angle != null) - { - double hexgridMultiplier = 1.0 - Math.Pow(Math.Cos((180 / 60.0) * (double)(osuCurrent.Angle)), 20.0); - double squaregridMultiplier = 1.0 - Math.Pow(Math.Cos((180 / 45.0) * (double)(osuCurrent.Angle)), 20.0); - result *= (1.0 - min_grid_multiplier) * hexgridMultiplier * squaregridMultiplier + min_grid_multiplier; - } + // Nerf patterns with repeated angles. + result *= min_angle_multiplier + (1.0 - min_angle_multiplier) / (angleRepeatCount + 1.0); double sliderBonus = 0.0; diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs index 03130031ea..84ef109598 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs @@ -26,7 +26,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills hasHiddenMod = mods.Any(m => m is OsuModHidden); } - private double skillMultiplier => 0.06; + private double skillMultiplier => 0.05; private double strainDecayBase => 0.15; private double currentStrain; From 6651e76e2e9215ebfe700d5fcb4720c2ae265308 Mon Sep 17 00:00:00 2001 From: MBmasher Date: Fri, 26 Aug 2022 12:37:56 +1000 Subject: [PATCH 129/420] Remove whitespace --- .../Difficulty/Evaluators/FlashlightEvaluator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/FlashlightEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/FlashlightEvaluator.cs index 8cf8190554..f3953bdb79 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/FlashlightEvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/FlashlightEvaluator.cs @@ -80,7 +80,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators if (currentObj.Angle != null && osuCurrent.Angle != null) { double roundedAngle = Math.Round(MathUtils.RadiansToDegrees(currentObj.Angle.Value) / 2.0) * 2.0; - + if (roundedAngle == initialRoundedAngle) angleRepeatCount++; } From d8854413cbbcf0607edd6093c9b0cfea8935a3ed Mon Sep 17 00:00:00 2001 From: MBmasher Date: Fri, 26 Aug 2022 12:38:36 +1000 Subject: [PATCH 130/420] Add newline --- .../Difficulty/Evaluators/FlashlightEvaluator.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/FlashlightEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/FlashlightEvaluator.cs index f3953bdb79..86b6170d13 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/FlashlightEvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/FlashlightEvaluator.cs @@ -78,7 +78,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators result += stackNerf * opacityBonus * scalingFactor * jumpDistance / cumulativeStrainTime; - if (currentObj.Angle != null && osuCurrent.Angle != null) { + if (currentObj.Angle != null && osuCurrent.Angle != null) + { double roundedAngle = Math.Round(MathUtils.RadiansToDegrees(currentObj.Angle.Value) / 2.0) * 2.0; if (roundedAngle == initialRoundedAngle) From 23efec65054946ca62f6b7f3b1473f9b55258c5d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 26 Aug 2022 15:56:47 +0900 Subject: [PATCH 131/420] Fix naming and comment typos --- .../Containers/UprightUnscaledContainer.cs | 19 ++++++++----------- osu.Game/Screens/Play/HUD/SongProgressInfo.cs | 6 +++--- 2 files changed, 11 insertions(+), 14 deletions(-) diff --git a/osu.Game/Graphics/Containers/UprightUnscaledContainer.cs b/osu.Game/Graphics/Containers/UprightUnscaledContainer.cs index c64e4ee2b7..4d9630052a 100644 --- a/osu.Game/Graphics/Containers/UprightUnscaledContainer.cs +++ b/osu.Game/Graphics/Containers/UprightUnscaledContainer.cs @@ -10,9 +10,9 @@ using System; namespace osu.Game.Graphics.Containers { /// - /// A container that prevents itself and its children from getting rotated, scaled or flipped with its Parent. + /// A container that reverts any rotation (and optionally scale) applied by its direct parent. /// - public class UprightUnstretchedContainer : Container + public class UprightAspectMaintainingContainer : Container { protected override Container Content => content; private readonly Container content; @@ -20,16 +20,15 @@ namespace osu.Game.Graphics.Containers /// /// Controls how much this container scales compared to its parent (default is 1.0f). /// - public float ScalingFactor { get; set; } + public float ScalingFactor { get; set; } = 1; /// /// Controls the scaling of this container. /// - public ScaleMode Scaling { get; set; } + public ScaleMode Scaling { get; set; } = ScaleMode.Vertical; - public UprightUnstretchedContainer() + public UprightAspectMaintainingContainer() { - Scaling = ScaleMode.NoScaling; InternalChild = content = new GrowToFitContainer(); AddLayout(layout); } @@ -52,7 +51,7 @@ namespace osu.Game.Graphics.Containers /// private void keepUprightAndUnstretched() { - // Decomposes the inverse of the parent FrawInfo.Matrix into rotation, shear and scale. + // Decomposes the inverse of the parent DrawInfo.Matrix into rotation, shear and scale. var parentMatrix = Parent.DrawInfo.Matrix; // Remove Translation. @@ -130,14 +129,12 @@ namespace osu.Game.Graphics.Containers NoScaling, /// - /// Scale This container (vertically and horizontally) with the vertical axis of its parent - /// preserving the aspect ratio of the container. + /// Scale uniformly (maintaining aspect ratio) based on the vertical scale of the parent. /// Vertical, /// - /// Scales This container (vertically and horizontally) with the horizontal axis of its parent - /// preserving the aspect ratio of the container. + /// Scale uniformly (maintaining aspect ratio) based on the horizontal scale of the parent. /// Horizontal, } diff --git a/osu.Game/Screens/Play/HUD/SongProgressInfo.cs b/osu.Game/Screens/Play/HUD/SongProgressInfo.cs index 83fe8b14c3..36ec16a607 100644 --- a/osu.Game/Screens/Play/HUD/SongProgressInfo.cs +++ b/osu.Game/Screens/Play/HUD/SongProgressInfo.cs @@ -55,7 +55,7 @@ namespace osu.Game.Screens.Play.HUD Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, AutoSizeAxes = Axes.Both, - Child = new UprightUnstretchedContainer + Child = new UprightAspectMaintainingContainer { Origin = Anchor.Centre, Anchor = Anchor.Centre, @@ -76,7 +76,7 @@ namespace osu.Game.Screens.Play.HUD Origin = Anchor.Centre, Anchor = Anchor.Centre, AutoSizeAxes = Axes.Both, - Child = new UprightUnstretchedContainer + Child = new UprightAspectMaintainingContainer { Origin = Anchor.Centre, Anchor = Anchor.Centre, @@ -97,7 +97,7 @@ namespace osu.Game.Screens.Play.HUD Origin = Anchor.CentreRight, Anchor = Anchor.CentreRight, AutoSizeAxes = Axes.Both, - Child = new UprightUnstretchedContainer + Child = new UprightAspectMaintainingContainer { Origin = Anchor.Centre, Anchor = Anchor.Centre, From a40355186a1c8434535a7ccf286ee92e82c2d39d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 26 Aug 2022 16:00:20 +0900 Subject: [PATCH 132/420] Tidy up constructor field initialisation --- osu.Game/Graphics/Containers/UprightUnscaledContainer.cs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/osu.Game/Graphics/Containers/UprightUnscaledContainer.cs b/osu.Game/Graphics/Containers/UprightUnscaledContainer.cs index 4d9630052a..d2cd7d3373 100644 --- a/osu.Game/Graphics/Containers/UprightUnscaledContainer.cs +++ b/osu.Game/Graphics/Containers/UprightUnscaledContainer.cs @@ -14,8 +14,7 @@ namespace osu.Game.Graphics.Containers /// public class UprightAspectMaintainingContainer : Container { - protected override Container Content => content; - private readonly Container content; + protected override Container Content { get; } /// /// Controls how much this container scales compared to its parent (default is 1.0f). @@ -27,14 +26,14 @@ namespace osu.Game.Graphics.Containers /// public ScaleMode Scaling { get; set; } = ScaleMode.Vertical; + private readonly LayoutValue layout = new LayoutValue(Invalidation.DrawInfo, InvalidationSource.Parent); + public UprightAspectMaintainingContainer() { - InternalChild = content = new GrowToFitContainer(); + AddInternal(Content = new GrowToFitContainer()); AddLayout(layout); } - private readonly LayoutValue layout = new LayoutValue(Invalidation.DrawInfo, InvalidationSource.Parent); - protected override void Update() { base.Update(); From 31e459364bd2eb6a5c6879447fbd596dda0f5b52 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 18 Aug 2022 14:11:42 +0900 Subject: [PATCH 133/420] Use `FramedBeatmapClock` in `EditorClock` --- osu.Game/Screens/Edit/EditorClock.cs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Edit/EditorClock.cs b/osu.Game/Screens/Edit/EditorClock.cs index c3b29afd30..d55f2c0bf6 100644 --- a/osu.Game/Screens/Edit/EditorClock.cs +++ b/osu.Game/Screens/Edit/EditorClock.cs @@ -4,10 +4,12 @@ #nullable disable using System; +using System.Diagnostics; using System.Linq; using osu.Framework.Audio.Track; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Transforms; using osu.Framework.Timing; using osu.Framework.Utils; @@ -19,7 +21,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 + public class EditorClock : CompositeComponent, IFrameBasedClock, IAdjustableClock, ISourceChangeableClock { public IBindable Track => track; @@ -33,7 +35,7 @@ namespace osu.Game.Screens.Edit private readonly BindableBeatDivisor beatDivisor; - private readonly DecoupleableInterpolatingFramedClock underlyingClock; + private readonly FramedBeatmapClock underlyingClock; private bool playbackFinished; @@ -52,7 +54,8 @@ namespace osu.Game.Screens.Edit this.beatDivisor = beatDivisor ?? new BindableBeatDivisor(); - underlyingClock = new DecoupleableInterpolatingFramedClock(); + underlyingClock = new FramedBeatmapClock(applyOffsets: true); + AddInternal(underlyingClock); } /// @@ -219,6 +222,9 @@ namespace osu.Game.Screens.Edit public void ProcessFrame() { + // EditorClock wasn't being added in many places. This gives us more certainty that it is. + Debug.Assert(underlyingClock.LoadState > LoadState.NotLoaded); + underlyingClock.ProcessFrame(); playbackFinished = CurrentTime >= TrackLength; From fec744a7fe5b4d44fdcd801ef63ad14d63ee334f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 26 Aug 2022 17:44:33 +0900 Subject: [PATCH 134/420] Add global `FramedBeatmapClock` for `BeatSyncProvider` components --- osu.Game/OsuGameBase.cs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 8d5c58d5f0..27ea3a76ae 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -185,6 +185,12 @@ namespace osu.Game private RealmAccess realm; + /// + /// For now, this is used as a source specifically for beat synced components. + /// Going forward, it could potentially be used as the single source-of-truth for beatmap timing. + /// + private readonly FramedBeatmapClock beatmapClock = new FramedBeatmapClock(true); + protected override Container Content => content; private Container content; @@ -368,10 +374,15 @@ namespace osu.Game AddInternal(MusicController = new MusicController()); dependencies.CacheAs(MusicController); + MusicController.TrackChanged += onTrackChanged; + AddInternal(beatmapClock); + Ruleset.BindValueChanged(onRulesetChanged); Beatmap.BindValueChanged(onBeatmapChanged); } + private void onTrackChanged(WorkingBeatmap beatmap, TrackChangeDirection direction) => beatmapClock.ChangeSource(beatmap.Track); + protected virtual void InitialiseFonts() { AddFont(Resources, @"Fonts/osuFont"); @@ -587,7 +598,7 @@ namespace osu.Game } ControlPointInfo IBeatSyncProvider.ControlPoints => Beatmap.Value.BeatmapLoaded ? Beatmap.Value.Beatmap.ControlPointInfo : null; - IClock IBeatSyncProvider.Clock => Beatmap.Value.TrackLoaded ? Beatmap.Value.Track : (IClock)null; + IClock IBeatSyncProvider.Clock => beatmapClock; ChannelAmplitudes IHasAmplitudes.CurrentAmplitudes => Beatmap.Value.TrackLoaded ? Beatmap.Value.Track.CurrentAmplitudes : ChannelAmplitudes.Empty; } } From 12d6d6793cd5eba002c0b39a976dc15c8c8955b6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 26 Aug 2022 18:08:39 +0900 Subject: [PATCH 135/420] Move `EditorClock` processing to `Update` and always decouple --- osu.Game/Screens/Edit/Editor.cs | 2 +- osu.Game/Screens/Edit/EditorClock.cs | 38 ++++++++++++---------------- 2 files changed, 17 insertions(+), 23 deletions(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index a7cbe1f1ad..ad8fda7ad0 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -220,7 +220,7 @@ namespace osu.Game.Screens.Edit } // Todo: should probably be done at a DrawableRuleset level to share logic with Player. - clock = new EditorClock(playableBeatmap, beatDivisor) { IsCoupled = false }; + clock = new EditorClock(playableBeatmap, beatDivisor); clock.ChangeSource(loadableBeatmap.Track); dependencies.CacheAs(clock); diff --git a/osu.Game/Screens/Edit/EditorClock.cs b/osu.Game/Screens/Edit/EditorClock.cs index d55f2c0bf6..7a00a74530 100644 --- a/osu.Game/Screens/Edit/EditorClock.cs +++ b/osu.Game/Screens/Edit/EditorClock.cs @@ -54,7 +54,7 @@ namespace osu.Game.Screens.Edit this.beatDivisor = beatDivisor ?? new BindableBeatDivisor(); - underlyingClock = new FramedBeatmapClock(applyOffsets: true); + underlyingClock = new FramedBeatmapClock(applyOffsets: true) { IsCoupled = false }; AddInternal(underlyingClock); } @@ -222,21 +222,7 @@ namespace osu.Game.Screens.Edit public void ProcessFrame() { - // EditorClock wasn't being added in many places. This gives us more certainty that it is. - Debug.Assert(underlyingClock.LoadState > LoadState.NotLoaded); - - underlyingClock.ProcessFrame(); - - playbackFinished = CurrentTime >= TrackLength; - - if (playbackFinished) - { - if (IsRunning) - underlyingClock.Stop(); - - if (CurrentTime > TrackLength) - underlyingClock.Seek(TrackLength); - } + // Noop to ensure an external consumer doesn't process the internal clock an extra time. } public double ElapsedFrameTime => underlyingClock.ElapsedFrameTime; @@ -253,18 +239,26 @@ namespace osu.Game.Screens.Edit public IClock Source => underlyingClock.Source; - public bool IsCoupled - { - get => underlyingClock.IsCoupled; - set => underlyingClock.IsCoupled = value; - } - private const double transform_time = 300; protected override void Update() { base.Update(); + // EditorClock wasn't being added in many places. This gives us more certainty that it is. + Debug.Assert(underlyingClock.LoadState > LoadState.NotLoaded); + + playbackFinished = CurrentTime >= TrackLength; + + if (playbackFinished) + { + if (IsRunning) + underlyingClock.Stop(); + + if (CurrentTime > TrackLength) + underlyingClock.Seek(TrackLength); + } + updateSeekingState(); } From 4b72e557701bf8358d771a710950d9126d593b93 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 17 Aug 2022 15:40:01 +0900 Subject: [PATCH 136/420] Fix various test scenes not adding `EditorClock` to the draw hierarchy --- .../CatchSelectionBlueprintTestScene.cs | 9 +- .../Editor/TestSceneManiaHitObjectComposer.cs | 8 +- .../Editor/TestSceneOsuDistanceSnapGrid.cs | 3 +- .../Editor/TestSceneTaikoHitObjectComposer.cs | 2 +- ...tSceneHitObjectComposerDistanceSnapping.cs | 18 +- .../Visual/Editing/TestSceneEditorClock.cs | 48 +++--- .../Editing/TestSceneEditorSeekSnapping.cs | 159 +++++++++--------- .../Editing/TestSceneTapTimingControl.cs | 4 +- .../TestSceneTimelineBlueprintContainer.cs | 2 +- .../Visual/Editing/TestSceneTimingScreen.cs | 12 +- .../Visual/Editing/TimelineTestScene.cs | 2 +- osu.Game/Tests/Visual/EditorClockTestScene.cs | 44 ++--- .../Visual/PlacementBlueprintTestScene.cs | 11 +- .../Visual/SelectionBlueprintTestScene.cs | 17 +- 14 files changed, 174 insertions(+), 165 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/Editor/CatchSelectionBlueprintTestScene.cs b/osu.Game.Rulesets.Catch.Tests/Editor/CatchSelectionBlueprintTestScene.cs index ea17fa400c..60fb31d1e0 100644 --- a/osu.Game.Rulesets.Catch.Tests/Editor/CatchSelectionBlueprintTestScene.cs +++ b/osu.Game.Rulesets.Catch.Tests/Editor/CatchSelectionBlueprintTestScene.cs @@ -70,10 +70,17 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor [Cached] private readonly BindableBeatDivisor beatDivisor; + protected override Container Content { get; } = new Container { RelativeSizeAxes = Axes.Both }; + public EditorBeatmapDependencyContainer(IBeatmap beatmap, BindableBeatDivisor beatDivisor) { - editorClock = new EditorClock(beatmap, beatDivisor); this.beatDivisor = beatDivisor; + + InternalChildren = new Drawable[] + { + editorClock = new EditorClock(beatmap, beatDivisor), + Content, + }; } } } diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaHitObjectComposer.cs index 4ad27348fc..fcc9e2e6c3 100644 --- a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaHitObjectComposer.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaHitObjectComposer.cs @@ -35,7 +35,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor public void Setup() => Schedule(() => { BeatDivisor.Value = 8; - Clock.Seek(0); + EditorClock.Seek(0); Child = composer = new TestComposer { RelativeSizeAxes = Axes.Both }; }); @@ -53,7 +53,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor { lastObject = this.ChildrenOfType().Single(d => d.HitObject == composer.EditorBeatmap.HitObjects.Last()); originalTime = lastObject.HitObject.StartTime; - Clock.Seek(composer.EditorBeatmap.HitObjects.Last().StartTime); + EditorClock.Seek(composer.EditorBeatmap.HitObjects.Last().StartTime); }); AddStep("select all objects", () => composer.EditorBeatmap.SelectedHitObjects.AddRange(composer.EditorBeatmap.HitObjects)); @@ -90,7 +90,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor { lastObject = this.ChildrenOfType().Single(d => d.HitObject == composer.EditorBeatmap.HitObjects.Last()); originalTime = lastObject.HitObject.StartTime; - Clock.Seek(composer.EditorBeatmap.HitObjects.Last().StartTime); + EditorClock.Seek(composer.EditorBeatmap.HitObjects.Last().StartTime); }); AddStep("select all objects", () => composer.EditorBeatmap.SelectedHitObjects.AddRange(composer.EditorBeatmap.HitObjects)); @@ -125,7 +125,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor AddStep("seek to last object", () => { lastObject = this.ChildrenOfType().Single(d => d.HitObject == composer.EditorBeatmap.HitObjects.Last()); - Clock.Seek(composer.EditorBeatmap.HitObjects.Last().StartTime); + EditorClock.Seek(composer.EditorBeatmap.HitObjects.Last().StartTime); }); AddStep("select all objects", () => composer.EditorBeatmap.SelectedHitObjects.AddRange(composer.EditorBeatmap.HitObjects)); diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuDistanceSnapGrid.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuDistanceSnapGrid.cs index e54ce45ccc..c102678e00 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuDistanceSnapGrid.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuDistanceSnapGrid.cs @@ -59,10 +59,9 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor } }); - editorClock = new EditorClock(editorBeatmap); - base.Content.Children = new Drawable[] { + editorClock = new EditorClock(editorBeatmap), snapProvider, Content }; diff --git a/osu.Game.Rulesets.Taiko.Tests/Editor/TestSceneTaikoHitObjectComposer.cs b/osu.Game.Rulesets.Taiko.Tests/Editor/TestSceneTaikoHitObjectComposer.cs index 16f17f4131..8d17918a92 100644 --- a/osu.Game.Rulesets.Taiko.Tests/Editor/TestSceneTaikoHitObjectComposer.cs +++ b/osu.Game.Rulesets.Taiko.Tests/Editor/TestSceneTaikoHitObjectComposer.cs @@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Editor public void Setup() => Schedule(() => { BeatDivisor.Value = 8; - Clock.Seek(0); + EditorClock.Seek(0); Child = new TestComposer { RelativeSizeAxes = Axes.Both }; }); diff --git a/osu.Game.Tests/Editing/TestSceneHitObjectComposerDistanceSnapping.cs b/osu.Game.Tests/Editing/TestSceneHitObjectComposerDistanceSnapping.cs index 0e80f8f699..1e87ed27df 100644 --- a/osu.Game.Tests/Editing/TestSceneHitObjectComposerDistanceSnapping.cs +++ b/osu.Game.Tests/Editing/TestSceneHitObjectComposerDistanceSnapping.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. -#nullable disable - using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -23,13 +21,13 @@ namespace osu.Game.Tests.Editing [HeadlessTest] public class TestSceneHitObjectComposerDistanceSnapping : EditorClockTestScene { - private TestHitObjectComposer composer; + private TestHitObjectComposer composer = null!; [Cached(typeof(EditorBeatmap))] [Cached(typeof(IBeatSnapProvider))] private readonly EditorBeatmap editorBeatmap; - protected override Container Content { get; } + protected override Container Content { get; } = new Container { RelativeSizeAxes = Axes.Both }; public TestSceneHitObjectComposerDistanceSnapping() { @@ -40,15 +38,9 @@ namespace osu.Game.Tests.Editing { editorBeatmap = new EditorBeatmap(new OsuBeatmap { - BeatmapInfo = - { - Ruleset = new OsuRuleset().RulesetInfo, - }, + BeatmapInfo = { Ruleset = new OsuRuleset().RulesetInfo }, }), - Content = new Container - { - RelativeSizeAxes = Axes.Both, - } + Content }, }); } @@ -205,7 +197,7 @@ namespace osu.Game.Tests.Editing assertSnappedDistance(400, 400); } - private void assertSnapDistance(float expectedDistance, HitObject hitObject = null) + private void assertSnapDistance(float expectedDistance, HitObject? hitObject = null) => AddAssert($"distance is {expectedDistance}", () => composer.GetBeatSnapDistanceAt(hitObject ?? new HitObject()), () => Is.EqualTo(expectedDistance)); private void assertDurationToDistance(double duration, float expectedDistance) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorClock.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorClock.cs index 3c6820e49b..d598ebafa9 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorClock.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorClock.cs @@ -55,51 +55,51 @@ namespace osu.Game.Tests.Visual.Editing [Test] public void TestStopAtTrackEnd() { - AddStep("reset clock", () => Clock.Seek(0)); + AddStep("reset clock", () => EditorClock.Seek(0)); - AddStep("start clock", () => Clock.Start()); - AddAssert("clock running", () => Clock.IsRunning); + AddStep("start clock", () => EditorClock.Start()); + AddAssert("clock running", () => EditorClock.IsRunning); - AddStep("seek near end", () => Clock.Seek(Clock.TrackLength - 250)); - AddUntilStep("clock stops", () => !Clock.IsRunning); + AddStep("seek near end", () => EditorClock.Seek(EditorClock.TrackLength - 250)); + AddUntilStep("clock stops", () => !EditorClock.IsRunning); - AddUntilStep("clock stopped at end", () => Clock.CurrentTime, () => Is.EqualTo(Clock.TrackLength)); + AddUntilStep("clock stopped at end", () => EditorClock.CurrentTime, () => Is.EqualTo(EditorClock.TrackLength)); - AddStep("start clock again", () => Clock.Start()); - AddAssert("clock looped to start", () => Clock.IsRunning && Clock.CurrentTime < 500); + AddStep("start clock again", () => EditorClock.Start()); + AddAssert("clock looped to start", () => EditorClock.IsRunning && EditorClock.CurrentTime < 500); } [Test] public void TestWrapWhenStoppedAtTrackEnd() { - AddStep("reset clock", () => Clock.Seek(0)); + AddStep("reset clock", () => EditorClock.Seek(0)); - AddStep("stop clock", () => Clock.Stop()); - AddAssert("clock stopped", () => !Clock.IsRunning); + AddStep("stop clock", () => EditorClock.Stop()); + AddAssert("clock stopped", () => !EditorClock.IsRunning); - AddStep("seek exactly to end", () => Clock.Seek(Clock.TrackLength)); - AddAssert("clock stopped at end", () => Clock.CurrentTime, () => Is.EqualTo(Clock.TrackLength)); + AddStep("seek exactly to end", () => EditorClock.Seek(EditorClock.TrackLength)); + AddAssert("clock stopped at end", () => EditorClock.CurrentTime, () => Is.EqualTo(EditorClock.TrackLength)); - AddStep("start clock again", () => Clock.Start()); - AddAssert("clock looped to start", () => Clock.IsRunning && Clock.CurrentTime < 500); + AddStep("start clock again", () => EditorClock.Start()); + AddAssert("clock looped to start", () => EditorClock.IsRunning && EditorClock.CurrentTime < 500); } [Test] public void TestClampWhenSeekOutsideBeatmapBounds() { - AddStep("stop clock", () => Clock.Stop()); + AddStep("stop clock", () => EditorClock.Stop()); - AddStep("seek before start time", () => Clock.Seek(-1000)); - AddAssert("time is clamped to 0", () => Clock.CurrentTime, () => Is.EqualTo(0)); + AddStep("seek before start time", () => EditorClock.Seek(-1000)); + AddAssert("time is clamped to 0", () => EditorClock.CurrentTime, () => Is.EqualTo(0)); - AddStep("seek beyond track length", () => Clock.Seek(Clock.TrackLength + 1000)); - AddAssert("time is clamped to track length", () => Clock.CurrentTime, () => Is.EqualTo(Clock.TrackLength)); + AddStep("seek beyond track length", () => EditorClock.Seek(EditorClock.TrackLength + 1000)); + AddAssert("time is clamped to track length", () => EditorClock.CurrentTime, () => Is.EqualTo(EditorClock.TrackLength)); - AddStep("seek smoothly before start time", () => Clock.SeekSmoothlyTo(-1000)); - AddUntilStep("time is clamped to 0", () => Clock.CurrentTime, () => Is.EqualTo(0)); + AddStep("seek smoothly before start time", () => EditorClock.SeekSmoothlyTo(-1000)); + AddUntilStep("time is clamped to 0", () => EditorClock.CurrentTime, () => Is.EqualTo(0)); - AddStep("seek smoothly beyond track length", () => Clock.SeekSmoothlyTo(Clock.TrackLength + 1000)); - AddUntilStep("time is clamped to track length", () => Clock.CurrentTime, () => Is.EqualTo(Clock.TrackLength)); + AddStep("seek smoothly beyond track length", () => EditorClock.SeekSmoothlyTo(EditorClock.TrackLength + 1000)); + AddUntilStep("time is clamped to track length", () => EditorClock.CurrentTime, () => Is.EqualTo(EditorClock.TrackLength)); } protected override void Dispose(bool isDisposing) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorSeekSnapping.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorSeekSnapping.cs index 2465512dae..aa4bccd728 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorSeekSnapping.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorSeekSnapping.cs @@ -28,6 +28,11 @@ namespace osu.Game.Tests.Visual.Editing { base.LoadComplete(); + Child = new TimingPointVisualiser(Beatmap.Value.Beatmap, 5000) { Clock = EditorClock }; + } + + protected override Beatmap CreateEditorClockBeatmap() + { var testBeatmap = new Beatmap { ControlPointInfo = new ControlPointInfo(), @@ -45,9 +50,7 @@ namespace osu.Game.Tests.Visual.Editing testBeatmap.ControlPointInfo.Add(450, new TimingControlPoint { BeatLength = 100 }); testBeatmap.ControlPointInfo.Add(500, new TimingControlPoint { BeatLength = 307.69230769230802 }); - Beatmap.Value = CreateWorkingBeatmap(testBeatmap); - - Child = new TimingPointVisualiser(testBeatmap, 5000) { Clock = Clock }; + return testBeatmap; } /// @@ -59,17 +62,17 @@ namespace osu.Game.Tests.Visual.Editing reset(); // Forwards - AddStep("Seek(0)", () => Clock.Seek(0)); + AddStep("Seek(0)", () => EditorClock.Seek(0)); checkTime(0); - AddStep("Seek(33)", () => Clock.Seek(33)); + AddStep("Seek(33)", () => EditorClock.Seek(33)); checkTime(33); - AddStep("Seek(89)", () => Clock.Seek(89)); + AddStep("Seek(89)", () => EditorClock.Seek(89)); checkTime(89); // Backwards - AddStep("Seek(25)", () => Clock.Seek(25)); + AddStep("Seek(25)", () => EditorClock.Seek(25)); checkTime(25); - AddStep("Seek(0)", () => Clock.Seek(0)); + AddStep("Seek(0)", () => EditorClock.Seek(0)); checkTime(0); } @@ -82,19 +85,19 @@ namespace osu.Game.Tests.Visual.Editing { reset(); - AddStep("Seek(0), Snap", () => Clock.SeekSnapped(0)); + AddStep("Seek(0), Snap", () => EditorClock.SeekSnapped(0)); checkTime(0); - AddStep("Seek(50), Snap", () => Clock.SeekSnapped(50)); + AddStep("Seek(50), Snap", () => EditorClock.SeekSnapped(50)); checkTime(50); - AddStep("Seek(100), Snap", () => Clock.SeekSnapped(100)); + AddStep("Seek(100), Snap", () => EditorClock.SeekSnapped(100)); checkTime(100); - AddStep("Seek(175), Snap", () => Clock.SeekSnapped(175)); + AddStep("Seek(175), Snap", () => EditorClock.SeekSnapped(175)); checkTime(175); - AddStep("Seek(350), Snap", () => Clock.SeekSnapped(350)); + AddStep("Seek(350), Snap", () => EditorClock.SeekSnapped(350)); checkTime(350); - AddStep("Seek(400), Snap", () => Clock.SeekSnapped(400)); + AddStep("Seek(400), Snap", () => EditorClock.SeekSnapped(400)); checkTime(400); - AddStep("Seek(450), Snap", () => Clock.SeekSnapped(450)); + AddStep("Seek(450), Snap", () => EditorClock.SeekSnapped(450)); checkTime(450); } @@ -107,17 +110,17 @@ namespace osu.Game.Tests.Visual.Editing { reset(); - AddStep("Seek(24), Snap", () => Clock.SeekSnapped(24)); + AddStep("Seek(24), Snap", () => EditorClock.SeekSnapped(24)); checkTime(0); - AddStep("Seek(26), Snap", () => Clock.SeekSnapped(26)); + AddStep("Seek(26), Snap", () => EditorClock.SeekSnapped(26)); checkTime(50); - AddStep("Seek(150), Snap", () => Clock.SeekSnapped(150)); + AddStep("Seek(150), Snap", () => EditorClock.SeekSnapped(150)); checkTime(100); - AddStep("Seek(170), Snap", () => Clock.SeekSnapped(170)); + AddStep("Seek(170), Snap", () => EditorClock.SeekSnapped(170)); checkTime(175); - AddStep("Seek(274), Snap", () => Clock.SeekSnapped(274)); + AddStep("Seek(274), Snap", () => EditorClock.SeekSnapped(274)); checkTime(175); - AddStep("Seek(276), Snap", () => Clock.SeekSnapped(276)); + AddStep("Seek(276), Snap", () => EditorClock.SeekSnapped(276)); checkTime(350); } @@ -129,15 +132,15 @@ namespace osu.Game.Tests.Visual.Editing { reset(); - AddStep("SeekForward", () => Clock.SeekForward()); + AddStep("SeekForward", () => EditorClock.SeekForward()); checkTime(50); - AddStep("SeekForward", () => Clock.SeekForward()); + AddStep("SeekForward", () => EditorClock.SeekForward()); checkTime(100); - AddStep("SeekForward", () => Clock.SeekForward()); + AddStep("SeekForward", () => EditorClock.SeekForward()); checkTime(200); - AddStep("SeekForward", () => Clock.SeekForward()); + AddStep("SeekForward", () => EditorClock.SeekForward()); checkTime(400); - AddStep("SeekForward", () => Clock.SeekForward()); + AddStep("SeekForward", () => EditorClock.SeekForward()); checkTime(450); } @@ -149,17 +152,17 @@ namespace osu.Game.Tests.Visual.Editing { reset(); - AddStep("SeekForward, Snap", () => Clock.SeekForward(true)); + AddStep("SeekForward, Snap", () => EditorClock.SeekForward(true)); checkTime(50); - AddStep("SeekForward, Snap", () => Clock.SeekForward(true)); + AddStep("SeekForward, Snap", () => EditorClock.SeekForward(true)); checkTime(100); - AddStep("SeekForward, Snap", () => Clock.SeekForward(true)); + AddStep("SeekForward, Snap", () => EditorClock.SeekForward(true)); checkTime(175); - AddStep("SeekForward, Snap", () => Clock.SeekForward(true)); + AddStep("SeekForward, Snap", () => EditorClock.SeekForward(true)); checkTime(350); - AddStep("SeekForward, Snap", () => Clock.SeekForward(true)); + AddStep("SeekForward, Snap", () => EditorClock.SeekForward(true)); checkTime(400); - AddStep("SeekForward, Snap", () => Clock.SeekForward(true)); + AddStep("SeekForward, Snap", () => EditorClock.SeekForward(true)); checkTime(450); } @@ -172,30 +175,30 @@ namespace osu.Game.Tests.Visual.Editing { reset(); - AddStep("Seek(49)", () => Clock.Seek(49)); + AddStep("Seek(49)", () => EditorClock.Seek(49)); checkTime(49); - AddStep("SeekForward, Snap", () => Clock.SeekForward(true)); + AddStep("SeekForward, Snap", () => EditorClock.SeekForward(true)); checkTime(50); - AddStep("Seek(49.999)", () => Clock.Seek(49.999)); - AddStep("SeekForward, Snap", () => Clock.SeekForward(true)); + AddStep("Seek(49.999)", () => EditorClock.Seek(49.999)); + AddStep("SeekForward, Snap", () => EditorClock.SeekForward(true)); checkTime(100); - AddStep("Seek(99)", () => Clock.Seek(99)); - AddStep("SeekForward, Snap", () => Clock.SeekForward(true)); + AddStep("Seek(99)", () => EditorClock.Seek(99)); + AddStep("SeekForward, Snap", () => EditorClock.SeekForward(true)); checkTime(100); - AddStep("Seek(99.999)", () => Clock.Seek(99.999)); - AddStep("SeekForward, Snap", () => Clock.SeekForward(true)); + AddStep("Seek(99.999)", () => EditorClock.Seek(99.999)); + AddStep("SeekForward, Snap", () => EditorClock.SeekForward(true)); checkTime(150); - AddStep("Seek(174)", () => Clock.Seek(174)); - AddStep("SeekForward, Snap", () => Clock.SeekForward(true)); + AddStep("Seek(174)", () => EditorClock.Seek(174)); + AddStep("SeekForward, Snap", () => EditorClock.SeekForward(true)); checkTime(175); - AddStep("Seek(349)", () => Clock.Seek(349)); - AddStep("SeekForward, Snap", () => Clock.SeekForward(true)); + AddStep("Seek(349)", () => EditorClock.Seek(349)); + AddStep("SeekForward, Snap", () => EditorClock.SeekForward(true)); checkTime(350); - AddStep("Seek(399)", () => Clock.Seek(399)); - AddStep("SeekForward, Snap", () => Clock.SeekForward(true)); + AddStep("Seek(399)", () => EditorClock.Seek(399)); + AddStep("SeekForward, Snap", () => EditorClock.SeekForward(true)); checkTime(400); - AddStep("Seek(449)", () => Clock.Seek(449)); - AddStep("SeekForward, Snap", () => Clock.SeekForward(true)); + AddStep("Seek(449)", () => EditorClock.Seek(449)); + AddStep("SeekForward, Snap", () => EditorClock.SeekForward(true)); checkTime(450); } @@ -207,17 +210,17 @@ namespace osu.Game.Tests.Visual.Editing { reset(); - AddStep("Seek(450)", () => Clock.Seek(450)); + AddStep("Seek(450)", () => EditorClock.Seek(450)); checkTime(450); - AddStep("SeekBackward", () => Clock.SeekBackward()); + AddStep("SeekBackward", () => EditorClock.SeekBackward()); checkTime(400); - AddStep("SeekBackward", () => Clock.SeekBackward()); + AddStep("SeekBackward", () => EditorClock.SeekBackward()); checkTime(350); - AddStep("SeekBackward", () => Clock.SeekBackward()); + AddStep("SeekBackward", () => EditorClock.SeekBackward()); checkTime(150); - AddStep("SeekBackward", () => Clock.SeekBackward()); + AddStep("SeekBackward", () => EditorClock.SeekBackward()); checkTime(50); - AddStep("SeekBackward", () => Clock.SeekBackward()); + AddStep("SeekBackward", () => EditorClock.SeekBackward()); checkTime(0); } @@ -229,19 +232,19 @@ namespace osu.Game.Tests.Visual.Editing { reset(); - AddStep("Seek(450)", () => Clock.Seek(450)); + AddStep("Seek(450)", () => EditorClock.Seek(450)); checkTime(450); - AddStep("SeekBackward, Snap", () => Clock.SeekBackward(true)); + AddStep("SeekBackward, Snap", () => EditorClock.SeekBackward(true)); checkTime(400); - AddStep("SeekBackward, Snap", () => Clock.SeekBackward(true)); + AddStep("SeekBackward, Snap", () => EditorClock.SeekBackward(true)); checkTime(350); - AddStep("SeekBackward, Snap", () => Clock.SeekBackward(true)); + AddStep("SeekBackward, Snap", () => EditorClock.SeekBackward(true)); checkTime(175); - AddStep("SeekBackward, Snap", () => Clock.SeekBackward(true)); + AddStep("SeekBackward, Snap", () => EditorClock.SeekBackward(true)); checkTime(100); - AddStep("SeekBackward, Snap", () => Clock.SeekBackward(true)); + AddStep("SeekBackward, Snap", () => EditorClock.SeekBackward(true)); checkTime(50); - AddStep("SeekBackward, Snap", () => Clock.SeekBackward(true)); + AddStep("SeekBackward, Snap", () => EditorClock.SeekBackward(true)); checkTime(0); } @@ -254,18 +257,18 @@ namespace osu.Game.Tests.Visual.Editing { reset(); - AddStep("Seek(451)", () => Clock.Seek(451)); + AddStep("Seek(451)", () => EditorClock.Seek(451)); checkTime(451); - AddStep("SeekBackward, Snap", () => Clock.SeekBackward(true)); + AddStep("SeekBackward, Snap", () => EditorClock.SeekBackward(true)); checkTime(450); - AddStep("Seek(450.999)", () => Clock.Seek(450.999)); - AddStep("SeekBackward, Snap", () => Clock.SeekBackward(true)); + AddStep("Seek(450.999)", () => EditorClock.Seek(450.999)); + AddStep("SeekBackward, Snap", () => EditorClock.SeekBackward(true)); checkTime(450); - AddStep("Seek(401)", () => Clock.Seek(401)); - AddStep("SeekBackward, Snap", () => Clock.SeekBackward(true)); + AddStep("Seek(401)", () => EditorClock.Seek(401)); + AddStep("SeekBackward, Snap", () => EditorClock.SeekBackward(true)); checkTime(400); - AddStep("Seek(401.999)", () => Clock.Seek(401.999)); - AddStep("SeekBackward, Snap", () => Clock.SeekBackward(true)); + AddStep("Seek(401.999)", () => EditorClock.Seek(401.999)); + AddStep("SeekBackward, Snap", () => EditorClock.SeekBackward(true)); checkTime(400); } @@ -279,37 +282,37 @@ namespace osu.Game.Tests.Visual.Editing double lastTime = 0; - AddStep("Seek(0)", () => Clock.Seek(0)); + AddStep("Seek(0)", () => EditorClock.Seek(0)); checkTime(0); for (int i = 0; i < 9; i++) { AddStep("SeekForward, Snap", () => { - lastTime = Clock.CurrentTime; - Clock.SeekForward(true); + lastTime = EditorClock.CurrentTime; + EditorClock.SeekForward(true); }); - AddAssert("Time > lastTime", () => Clock.CurrentTime > lastTime); + AddAssert("Time > lastTime", () => EditorClock.CurrentTime > lastTime); } for (int i = 0; i < 9; i++) { AddStep("SeekBackward, Snap", () => { - lastTime = Clock.CurrentTime; - Clock.SeekBackward(true); + lastTime = EditorClock.CurrentTime; + EditorClock.SeekBackward(true); }); - AddAssert("Time < lastTime", () => Clock.CurrentTime < lastTime); + AddAssert("Time < lastTime", () => EditorClock.CurrentTime < lastTime); } checkTime(0); } - private void checkTime(double expectedTime) => AddAssert($"Current time is {expectedTime}", () => Clock.CurrentTime, () => Is.EqualTo(expectedTime)); + private void checkTime(double expectedTime) => AddUntilStep($"Current time is {expectedTime}", () => EditorClock.CurrentTime, () => Is.EqualTo(expectedTime)); private void reset() { - AddStep("Reset", () => Clock.Seek(0)); + AddStep("Reset", () => EditorClock.Seek(0)); } private class TimingPointVisualiser : CompositeDrawable diff --git a/osu.Game.Tests/Visual/Editing/TestSceneTapTimingControl.cs b/osu.Game.Tests/Visual/Editing/TestSceneTapTimingControl.cs index 7d881bc259..10e1206b53 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneTapTimingControl.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneTapTimingControl.cs @@ -113,7 +113,7 @@ namespace osu.Game.Tests.Visual.Editing .TriggerClick(); }); - AddUntilStep("wait for track playing", () => Clock.IsRunning); + AddUntilStep("wait for track playing", () => EditorClock.IsRunning); AddStep("click reset button", () => { @@ -122,7 +122,7 @@ namespace osu.Game.Tests.Visual.Editing .TriggerClick(); }); - AddUntilStep("wait for track stopped", () => !Clock.IsRunning); + AddUntilStep("wait for track stopped", () => !EditorClock.IsRunning); } protected override void Dispose(bool isDisposing) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneTimelineBlueprintContainer.cs b/osu.Game.Tests/Visual/Editing/TestSceneTimelineBlueprintContainer.cs index d2984728b0..c098b683a6 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneTimelineBlueprintContainer.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneTimelineBlueprintContainer.cs @@ -17,7 +17,7 @@ namespace osu.Game.Tests.Visual.Editing protected override void LoadComplete() { base.LoadComplete(); - Clock.Seek(10000); + EditorClock.Seek(10000); } } } diff --git a/osu.Game.Tests/Visual/Editing/TestSceneTimingScreen.cs b/osu.Game.Tests/Visual/Editing/TestSceneTimingScreen.cs index e262bd756a..03c184c27d 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneTimingScreen.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneTimingScreen.cs @@ -53,7 +53,7 @@ namespace osu.Game.Tests.Visual.Editing [SetUpSteps] public void SetUpSteps() { - AddStep("Stop clock", () => Clock.Stop()); + AddStep("Stop clock", () => EditorClock.Stop()); AddUntilStep("wait for rows to load", () => Child.ChildrenOfType().Any()); } @@ -68,10 +68,10 @@ namespace osu.Game.Tests.Visual.Editing }); AddUntilStep("Selection changed", () => timingScreen.SelectedGroup.Value.Time == 54670); - AddUntilStep("Ensure seeked to correct time", () => Clock.CurrentTimeAccurate == 54670); + AddUntilStep("Ensure seeked to correct time", () => EditorClock.CurrentTimeAccurate == 54670); - AddStep("Seek to just before next point", () => Clock.Seek(69000)); - AddStep("Start clock", () => Clock.Start()); + AddStep("Seek to just before next point", () => EditorClock.Seek(69000)); + AddStep("Start clock", () => EditorClock.Start()); AddUntilStep("Selection changed", () => timingScreen.SelectedGroup.Value.Time == 69670); } @@ -86,9 +86,9 @@ namespace osu.Game.Tests.Visual.Editing }); AddUntilStep("Selection changed", () => timingScreen.SelectedGroup.Value.Time == 54670); - AddUntilStep("Ensure seeked to correct time", () => Clock.CurrentTimeAccurate == 54670); + AddUntilStep("Ensure seeked to correct time", () => EditorClock.CurrentTimeAccurate == 54670); - AddStep("Seek to later", () => Clock.Seek(80000)); + AddStep("Seek to later", () => EditorClock.Seek(80000)); AddUntilStep("Selection changed", () => timingScreen.SelectedGroup.Value.Time == 69670); } diff --git a/osu.Game.Tests/Visual/Editing/TimelineTestScene.cs b/osu.Game.Tests/Visual/Editing/TimelineTestScene.cs index 437f06c47f..d830f8d488 100644 --- a/osu.Game.Tests/Visual/Editing/TimelineTestScene.cs +++ b/osu.Game.Tests/Visual/Editing/TimelineTestScene.cs @@ -72,7 +72,7 @@ namespace osu.Game.Tests.Visual.Editing { base.LoadComplete(); - Clock.Seek(2500); + EditorClock.Seek(2500); } public abstract Drawable CreateTestComponent(); diff --git a/osu.Game/Tests/Visual/EditorClockTestScene.cs b/osu.Game/Tests/Visual/EditorClockTestScene.cs index 91284ae499..8f1e7abd9e 100644 --- a/osu.Game/Tests/Visual/EditorClockTestScene.cs +++ b/osu.Game/Tests/Visual/EditorClockTestScene.cs @@ -6,6 +6,8 @@ using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Input.Events; using osu.Game.Beatmaps; using osu.Game.Overlays; @@ -24,30 +26,39 @@ namespace osu.Game.Tests.Visual protected readonly BindableBeatDivisor BeatDivisor = new BindableBeatDivisor(); - [Cached] - protected new readonly EditorClock Clock; + protected EditorClock EditorClock; private readonly Bindable frequencyAdjustment = new BindableDouble(1); + private IBeatmap editorClockBeatmap; protected virtual bool ScrollUsingMouseWheel => true; - protected EditorClockTestScene() - { - Clock = new EditorClock(new Beatmap(), BeatDivisor) { IsCoupled = false }; - } + protected override Container Content => content; + + private readonly Container content = new Container { RelativeSizeAxes = Axes.Both }; protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) { var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); + editorClockBeatmap = CreateEditorClockBeatmap(); + + base.Content.AddRange(new Drawable[] + { + EditorClock = new EditorClock(editorClockBeatmap, BeatDivisor), + content + }); + dependencies.Cache(BeatDivisor); - dependencies.CacheAs(Clock); + dependencies.CacheAs(EditorClock); return dependencies; } protected override void LoadComplete() { + Beatmap.Value = CreateWorkingBeatmap(editorClockBeatmap); + base.LoadComplete(); Beatmap.BindValueChanged(beatmapChanged, true); @@ -55,22 +66,13 @@ namespace osu.Game.Tests.Visual AddSliderStep("editor clock rate", 0.0, 2.0, 1.0, v => frequencyAdjustment.Value = v); } + protected virtual IBeatmap CreateEditorClockBeatmap() => new Beatmap(); + private void beatmapChanged(ValueChangedEvent e) { e.OldValue?.Track.RemoveAdjustment(AdjustableProperty.Frequency, frequencyAdjustment); - - Clock.Beatmap = e.NewValue.Beatmap; - Clock.ChangeSource(e.NewValue.Track); - Clock.ProcessFrame(); - e.NewValue.Track.AddAdjustment(AdjustableProperty.Frequency, frequencyAdjustment); - } - - protected override void Update() - { - base.Update(); - - Clock.ProcessFrame(); + EditorClock.ChangeSource(e.NewValue.Track); } protected override bool OnScroll(ScrollEvent e) @@ -79,9 +81,9 @@ namespace osu.Game.Tests.Visual return false; if (e.ScrollDelta.Y > 0) - Clock.SeekBackward(true); + EditorClock.SeekBackward(true); else - Clock.SeekForward(true); + EditorClock.SeekForward(true); return true; } diff --git a/osu.Game/Tests/Visual/PlacementBlueprintTestScene.cs b/osu.Game/Tests/Visual/PlacementBlueprintTestScene.cs index 797e8363c3..176b181e73 100644 --- a/osu.Game/Tests/Visual/PlacementBlueprintTestScene.cs +++ b/osu.Game/Tests/Visual/PlacementBlueprintTestScene.cs @@ -30,10 +30,15 @@ namespace osu.Game.Tests.Visual { var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); - dependencies.CacheAs(new EditorClock()); - var playable = GetPlayableBeatmap(); - dependencies.CacheAs(new EditorBeatmap(playable)); + + var editorClock = new EditorClock(); + base.Content.Add(editorClock); + dependencies.CacheAs(editorClock); + + var editorBeatmap = new EditorBeatmap(playable); + // Not adding to hierarchy as we don't satisfy its dependencies. Probably not good. + dependencies.CacheAs(editorBeatmap); return dependencies; } diff --git a/osu.Game/Tests/Visual/SelectionBlueprintTestScene.cs b/osu.Game/Tests/Visual/SelectionBlueprintTestScene.cs index 66d79cad1c..ac0d1cd366 100644 --- a/osu.Game/Tests/Visual/SelectionBlueprintTestScene.cs +++ b/osu.Game/Tests/Visual/SelectionBlueprintTestScene.cs @@ -1,9 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - -using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -19,19 +16,23 @@ namespace osu.Game.Tests.Visual [Cached] private readonly EditorClock editorClock = new EditorClock(); - protected override Container Content => content ?? base.Content; + protected override Container Content => content; private readonly Container content; protected SelectionBlueprintTestScene() { - base.Content.Add(content = new Container + base.Content.AddRange(new Drawable[] { - Clock = new FramedClock(new StopwatchClock()), - RelativeSizeAxes = Axes.Both + editorClock, + content = new Container + { + Clock = new FramedClock(new StopwatchClock()), + RelativeSizeAxes = Axes.Both + } }); } - protected void AddBlueprint(HitObjectSelectionBlueprint blueprint, [CanBeNull] DrawableHitObject drawableObject = null) + protected void AddBlueprint(HitObjectSelectionBlueprint blueprint, DrawableHitObject? drawableObject = null) { Add(blueprint.With(d => { From cd90536e4bbb881b1787390d4469a57a5e6299d5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 26 Aug 2022 18:25:48 +0900 Subject: [PATCH 137/420] Remove `Track` access in `Timeline` --- .../Compose/Components/Timeline/Timeline.cs | 48 ++++++++----------- 1 file changed, 20 insertions(+), 28 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs index 54f2d13707..9e96a7386d 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs @@ -5,7 +5,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; @@ -64,8 +63,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline /// private bool trackWasPlaying; - private Track track; - /// /// The timeline zoom level at a 1x zoom scale. /// @@ -93,6 +90,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private Bindable waveformOpacity; + private double trackLengthForZoom; + [BackgroundDependencyLoader] private void load(IBindable beatmap, OsuColour colours, OsuConfigManager config) { @@ -144,9 +143,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline Beatmap.BindValueChanged(b => { waveform.Waveform = b.NewValue.Waveform; - track = b.NewValue.Track; - - setupTimelineZoom(); }, true); Zoom = (float)(defaultTimelineZoom * editorBeatmap.BeatmapInfo.TimelineZoom); @@ -185,8 +181,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private void updateWaveformOpacity() => waveform.FadeTo(WaveformVisible.Value ? waveformOpacity.Value : 0, 200, Easing.OutQuint); - private float getZoomLevelForVisibleMilliseconds(double milliseconds) => Math.Max(1, (float)(track.Length / milliseconds)); - protected override void Update() { base.Update(); @@ -197,20 +191,21 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline // This needs to happen after transforms are updated, but before the scroll position is updated in base.UpdateAfterChildren if (editorClock.IsRunning) scrollToTrackTime(); - } - private void setupTimelineZoom() - { - if (!track.IsLoaded) + if (editorClock.TrackLength != trackLengthForZoom) { - Scheduler.AddOnce(setupTimelineZoom); - return; + defaultTimelineZoom = getZoomLevelForVisibleMilliseconds(6000); + + float initialZoom = (float)(defaultTimelineZoom * editorBeatmap.BeatmapInfo.TimelineZoom); + float minimumZoom = getZoomLevelForVisibleMilliseconds(10000); + float maximumZoom = getZoomLevelForVisibleMilliseconds(500); + + SetupZoom(initialZoom, minimumZoom, maximumZoom); + + float getZoomLevelForVisibleMilliseconds(double milliseconds) => Math.Max(1, (float)(editorClock.TrackLength / milliseconds)); + + trackLengthForZoom = editorClock.TrackLength; } - - defaultTimelineZoom = getZoomLevelForVisibleMilliseconds(6000); - - float initialZoom = (float)(defaultTimelineZoom * editorBeatmap.BeatmapInfo.TimelineZoom); - SetupZoom(initialZoom, getZoomLevelForVisibleMilliseconds(10000), getZoomLevelForVisibleMilliseconds(500)); } protected override bool OnScroll(ScrollEvent e) @@ -255,16 +250,13 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private void seekTrackToCurrent() { - if (!track.IsLoaded) - return; - - double target = Current / Content.DrawWidth * track.Length; - editorClock.Seek(Math.Min(track.Length, target)); + double target = Current / Content.DrawWidth * editorClock.TrackLength; + editorClock.Seek(Math.Min(editorClock.TrackLength, target)); } private void scrollToTrackTime() { - if (!track.IsLoaded || track.Length == 0) + if (editorClock.TrackLength == 0) return; // covers the case where the user starts playback after a drag is in progress. @@ -272,7 +264,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline if (handlingDragInput) editorClock.Stop(); - ScrollTo((float)(editorClock.CurrentTime / track.Length) * Content.DrawWidth, false); + ScrollTo((float)(editorClock.CurrentTime / editorClock.TrackLength) * Content.DrawWidth, false); } protected override bool OnMouseDown(MouseDownEvent e) @@ -310,12 +302,12 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline /// /// The total amount of time visible on the timeline. /// - public double VisibleRange => track.Length / Zoom; + public double VisibleRange => editorClock.TrackLength / Zoom; public SnapResult FindSnappedPositionAndTime(Vector2 screenSpacePosition, SnapType snapType = SnapType.All) => new SnapResult(screenSpacePosition, beatSnapProvider.SnapTime(getTimeFromPosition(Content.ToLocalSpace(screenSpacePosition)))); private double getTimeFromPosition(Vector2 localPosition) => - (localPosition.X / Content.DrawWidth) * track.Length; + (localPosition.X / Content.DrawWidth) * editorClock.TrackLength; } } From 9c9238d6e8686017d522752c3044156906393674 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 26 Aug 2022 18:38:52 +0900 Subject: [PATCH 138/420] Fix `TimelineTestScene`'s beatmap getting overwritten by `EditorClockTestScene` --- .../Visual/Editing/TestSceneTimelineZoom.cs | 4 ---- .../Visual/Editing/TimelineTestScene.cs | 20 ++++++++++++------- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneTimelineZoom.cs b/osu.Game.Tests/Visual/Editing/TestSceneTimelineZoom.cs index 630d048867..11ac102814 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneTimelineZoom.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneTimelineZoom.cs @@ -17,8 +17,6 @@ namespace osu.Game.Tests.Visual.Editing { double initialVisibleRange = 0; - AddUntilStep("wait for load", () => MusicController.TrackLoaded); - AddStep("reset zoom", () => TimelineArea.Timeline.Zoom = 100); AddStep("get initial range", () => initialVisibleRange = TimelineArea.Timeline.VisibleRange); @@ -36,8 +34,6 @@ namespace osu.Game.Tests.Visual.Editing { double initialVisibleRange = 0; - AddUntilStep("wait for load", () => MusicController.TrackLoaded); - AddStep("reset timeline size", () => TimelineArea.Timeline.Width = 1); AddStep("get initial range", () => initialVisibleRange = TimelineArea.Timeline.VisibleRange); diff --git a/osu.Game.Tests/Visual/Editing/TimelineTestScene.cs b/osu.Game.Tests/Visual/Editing/TimelineTestScene.cs index d830f8d488..19e297a08d 100644 --- a/osu.Game.Tests/Visual/Editing/TimelineTestScene.cs +++ b/osu.Game.Tests/Visual/Editing/TimelineTestScene.cs @@ -9,12 +9,14 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Graphics.Cursor; using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Edit; using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Compose.Components.Timeline; +using osu.Game.Storyboards; using osuTK; using osuTK.Graphics; @@ -28,10 +30,14 @@ namespace osu.Game.Tests.Visual.Editing protected EditorBeatmap EditorBeatmap { get; private set; } - [BackgroundDependencyLoader] - private void load(AudioManager audio) + [Resolved] + private AudioManager audio { get; set; } + + protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null) => new WaveformTestBeatmap(audio); + + protected override void LoadComplete() { - Beatmap.Value = new WaveformTestBeatmap(audio); + base.LoadComplete(); var playable = Beatmap.Value.GetPlayableBeatmap(Beatmap.Value.BeatmapInfo.Ruleset); EditorBeatmap = new EditorBeatmap(playable); @@ -68,11 +74,11 @@ namespace osu.Game.Tests.Visual.Editing }); } - protected override void LoadComplete() + [SetUpSteps] + public void SetUpSteps() { - base.LoadComplete(); - - EditorClock.Seek(2500); + AddUntilStep("wait for track loaded", () => MusicController.TrackLoaded); + AddStep("seek forward", () => EditorClock.Seek(2500)); } public abstract Drawable CreateTestComponent(); From d6359b00ad5022062f9469cb2103d4229ce38d88 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 26 Aug 2022 19:20:36 +0900 Subject: [PATCH 139/420] Fix filename mismatch --- .../UprightAspectMaintainingContainer.cs | 122 ++++++++++++++++++ .../Containers/UprightUnscaledContainer.cs | 117 ----------------- 2 files changed, 122 insertions(+), 117 deletions(-) create mode 100644 osu.Game/Graphics/Containers/UprightAspectMaintainingContainer.cs diff --git a/osu.Game/Graphics/Containers/UprightAspectMaintainingContainer.cs b/osu.Game/Graphics/Containers/UprightAspectMaintainingContainer.cs new file mode 100644 index 0000000000..e556117bc0 --- /dev/null +++ b/osu.Game/Graphics/Containers/UprightAspectMaintainingContainer.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 osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Layout; +using osuTK; + +namespace osu.Game.Graphics.Containers +{ + /// + /// A container that reverts any rotation (and optionally scale) applied by its direct parent. + /// + public class UprightAspectMaintainingContainer : Container + { + protected override Container Content { get; } + + /// + /// Controls how much this container scales compared to its parent (default is 1.0f). + /// + public float ScalingFactor { get; set; } = 1; + + /// + /// Controls the scaling of this container. + /// + public ScaleMode Scaling { get; set; } = ScaleMode.Vertical; + + private readonly LayoutValue layout = new LayoutValue(Invalidation.DrawInfo, InvalidationSource.Parent); + + public UprightAspectMaintainingContainer() + { + AddInternal(Content = new GrowToFitContainer()); + AddLayout(layout); + } + + protected override void Update() + { + base.Update(); + + if (!layout.IsValid) + { + keepUprightAndUnstretched(); + layout.Validate(); + } + } + + /// + /// Keeps the drawable upright and unstretched preventing it from being rotated, sheared, scaled or flipped with its Parent. + /// + private void keepUprightAndUnstretched() + { + // Decomposes the inverse of the parent DrawInfo.Matrix into rotation, shear and scale. + var parentMatrix = Parent.DrawInfo.Matrix; + + // Remove Translation. + parentMatrix.M31 = 0.0f; + parentMatrix.M32 = 0.0f; + + Matrix3 reversedParrent = parentMatrix.Inverted(); + + // Extract the rotation. + float angle = MathF.Atan2(reversedParrent.M12, reversedParrent.M11); + Rotation = MathHelper.RadiansToDegrees(angle); + + // Remove rotation from the C matrix so that it only contains shear and scale. + Matrix3 m = Matrix3.CreateRotationZ(-angle); + reversedParrent *= m; + + // Extract shear. + float alpha = reversedParrent.M21 / reversedParrent.M22; + Shear = new Vector2(-alpha, 0); + + // Etract scale. + float sx = reversedParrent.M11; + float sy = reversedParrent.M22; + + Vector3 parentScale = parentMatrix.ExtractScale(); + + float usedScale = 1.0f; + + switch (Scaling) + { + case ScaleMode.Horizontal: + usedScale = parentScale.X; + break; + + case ScaleMode.Vertical: + usedScale = parentScale.Y; + break; + } + + usedScale = 1.0f + (usedScale - 1.0f) * ScalingFactor; + + Scale = new Vector2(sx * usedScale, sy * usedScale); + } + + /// + /// A container that grows in size to fit its children and retains its size when its children shrink + /// + private class GrowToFitContainer : Container + { + protected override Container Content => content; + private readonly Container content; + + public GrowToFitContainer() + { + InternalChild = content = new Container + { + AutoSizeAxes = Axes.Both, + }; + } + + protected override void Update() + { + base.Update(); + Height = Math.Max(content.Height, Height); + Width = Math.Max(content.Width, Width); + } + } + } +} diff --git a/osu.Game/Graphics/Containers/UprightUnscaledContainer.cs b/osu.Game/Graphics/Containers/UprightUnscaledContainer.cs index d2cd7d3373..d801f4a215 100644 --- a/osu.Game/Graphics/Containers/UprightUnscaledContainer.cs +++ b/osu.Game/Graphics/Containers/UprightUnscaledContainer.cs @@ -1,125 +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.Framework.Graphics.Containers; -using osu.Framework.Layout; -using osuTK; -using System; - namespace osu.Game.Graphics.Containers { - /// - /// A container that reverts any rotation (and optionally scale) applied by its direct parent. - /// - public class UprightAspectMaintainingContainer : Container - { - protected override Container Content { get; } - - /// - /// Controls how much this container scales compared to its parent (default is 1.0f). - /// - public float ScalingFactor { get; set; } = 1; - - /// - /// Controls the scaling of this container. - /// - public ScaleMode Scaling { get; set; } = ScaleMode.Vertical; - - private readonly LayoutValue layout = new LayoutValue(Invalidation.DrawInfo, InvalidationSource.Parent); - - public UprightAspectMaintainingContainer() - { - AddInternal(Content = new GrowToFitContainer()); - AddLayout(layout); - } - - protected override void Update() - { - base.Update(); - - if (!layout.IsValid) - { - keepUprightAndUnstretched(); - layout.Validate(); - } - } - - /// - /// Keeps the drawable upright and unstretched preventing it from being rotated, sheared, scaled or flipped with its Parent. - /// - private void keepUprightAndUnstretched() - { - // Decomposes the inverse of the parent DrawInfo.Matrix into rotation, shear and scale. - var parentMatrix = Parent.DrawInfo.Matrix; - - // Remove Translation. - parentMatrix.M31 = 0.0f; - parentMatrix.M32 = 0.0f; - - Matrix3 reversedParrent = parentMatrix.Inverted(); - - // Extract the rotation. - float angle = MathF.Atan2(reversedParrent.M12, reversedParrent.M11); - Rotation = MathHelper.RadiansToDegrees(angle); - - // Remove rotation from the C matrix so that it only contains shear and scale. - Matrix3 m = Matrix3.CreateRotationZ(-angle); - reversedParrent *= m; - - // Extract shear. - float alpha = reversedParrent.M21 / reversedParrent.M22; - Shear = new Vector2(-alpha, 0); - - // Etract scale. - float sx = reversedParrent.M11; - float sy = reversedParrent.M22; - - Vector3 parentScale = parentMatrix.ExtractScale(); - - float usedScale = 1.0f; - - switch (Scaling) - { - case ScaleMode.Horizontal: - usedScale = parentScale.X; - break; - - case ScaleMode.Vertical: - usedScale = parentScale.Y; - break; - } - - usedScale = 1.0f + (usedScale - 1.0f) * ScalingFactor; - - Scale = new Vector2(sx * usedScale, sy * usedScale); - } - - /// - /// A container that grows in size to fit its children and retains its size when its children shrink - /// - private class GrowToFitContainer : Container - { - protected override Container Content => content; - private readonly Container content; - - public GrowToFitContainer() - { - InternalChild = content = new Container - { - AutoSizeAxes = Axes.Both, - }; - } - - protected override void Update() - { - base.Update(); - Height = Math.Max(content.Height, Height); - Width = Math.Max(content.Width, Width); - } - } - } - public enum ScaleMode { /// From 08cb70b0931eac8edcfc3939c0341166dff54ea1 Mon Sep 17 00:00:00 2001 From: MBmasher Date: Fri, 26 Aug 2022 20:27:31 +1000 Subject: [PATCH 140/420] Lessen repeated angle nerf for objects further back in time --- .../Difficulty/Evaluators/FlashlightEvaluator.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/FlashlightEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/FlashlightEvaluator.cs index 86b6170d13..a6e76db902 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/FlashlightEvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/FlashlightEvaluator.cs @@ -47,7 +47,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators OsuDifficultyHitObject lastObj = osuCurrent; - int angleRepeatCount = 0; + double angleRepeatCount = 0.0; // We want to round angles to make abusing the nerf a bit harder. double initialRoundedAngle = 0.0; @@ -82,8 +82,9 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators { double roundedAngle = Math.Round(MathUtils.RadiansToDegrees(currentObj.Angle.Value) / 2.0) * 2.0; + // Objects further back in time should count less for the nerf. if (roundedAngle == initialRoundedAngle) - angleRepeatCount++; + angleRepeatCount += 1.0 - 0.1 * i; } } From 5082ee26cfeb62b7ce8560f9253e92f8434f606b Mon Sep 17 00:00:00 2001 From: MBmasher Date: Fri, 26 Aug 2022 20:30:14 +1000 Subject: [PATCH 141/420] Ensure a negative value cannot be added to angleRepeatCount --- .../Difficulty/Evaluators/FlashlightEvaluator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/FlashlightEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/FlashlightEvaluator.cs index a6e76db902..2733217b64 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/FlashlightEvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/FlashlightEvaluator.cs @@ -84,7 +84,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators // Objects further back in time should count less for the nerf. if (roundedAngle == initialRoundedAngle) - angleRepeatCount += 1.0 - 0.1 * i; + angleRepeatCount += Math.Max(1.0 - 0.1 * i, 0.0); } } From 249c3f868f6c426985a3631d2ad4f3773bbffb7d Mon Sep 17 00:00:00 2001 From: MBmasher Date: Fri, 26 Aug 2022 20:40:18 +1000 Subject: [PATCH 142/420] Compare raw angle values instead of rounding angles --- .../Difficulty/Evaluators/FlashlightEvaluator.cs | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/FlashlightEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/FlashlightEvaluator.cs index 2733217b64..9630da5a01 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/FlashlightEvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/FlashlightEvaluator.cs @@ -49,11 +49,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators double angleRepeatCount = 0.0; - // We want to round angles to make abusing the nerf a bit harder. - double initialRoundedAngle = 0.0; - if (osuCurrent.Angle != null) - initialRoundedAngle = Math.Round(MathUtils.RadiansToDegrees(osuCurrent.Angle.Value) / 2.0) * 2.0; - // This is iterating backwards in time from the current object. for (int i = 0; i < Math.Min(current.Index, 10); i++) { @@ -80,10 +75,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators if (currentObj.Angle != null && osuCurrent.Angle != null) { - double roundedAngle = Math.Round(MathUtils.RadiansToDegrees(currentObj.Angle.Value) / 2.0) * 2.0; - // Objects further back in time should count less for the nerf. - if (roundedAngle == initialRoundedAngle) + if (Math.Abs(currentObj.Angle.Value - osuCurrent.Angle.Value) < 0.02) angleRepeatCount += Math.Max(1.0 - 0.1 * i, 0.0); } } From 454d868dd598148f7ea141f83de2e9dbdb73a97c Mon Sep 17 00:00:00 2001 From: MBmasher Date: Fri, 26 Aug 2022 20:42:02 +1000 Subject: [PATCH 143/420] Remove unnecessary using call --- .../Difficulty/Evaluators/FlashlightEvaluator.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/FlashlightEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/FlashlightEvaluator.cs index 9630da5a01..2ba856d014 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/FlashlightEvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/FlashlightEvaluator.cs @@ -7,7 +7,6 @@ using System; using osu.Game.Rulesets.Difficulty.Preprocessing; using osu.Game.Rulesets.Osu.Difficulty.Preprocessing; using osu.Game.Rulesets.Osu.Objects; -using osu.Framework.Utils; namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators { From 24edffcbc47628e49df7e25159d1dbb437f2ddd5 Mon Sep 17 00:00:00 2001 From: HiddenNode Date: Fri, 26 Aug 2022 12:47:12 +0100 Subject: [PATCH 144/420] Moved ScaleMode to UprightAspectMaintainingContainer.cs --- .../UprightAspectMaintainingContainer.cs | 18 +++++++++++++++ .../Containers/UprightUnscaledContainer.cs | 23 ------------------- 2 files changed, 18 insertions(+), 23 deletions(-) delete mode 100644 osu.Game/Graphics/Containers/UprightUnscaledContainer.cs diff --git a/osu.Game/Graphics/Containers/UprightAspectMaintainingContainer.cs b/osu.Game/Graphics/Containers/UprightAspectMaintainingContainer.cs index e556117bc0..9736cba206 100644 --- a/osu.Game/Graphics/Containers/UprightAspectMaintainingContainer.cs +++ b/osu.Game/Graphics/Containers/UprightAspectMaintainingContainer.cs @@ -119,4 +119,22 @@ namespace osu.Game.Graphics.Containers } } } + + public enum ScaleMode + { + /// + /// Prevent this container from scaling. + /// + NoScaling, + + /// + /// Scale uniformly (maintaining aspect ratio) based on the vertical scale of the parent. + /// + Vertical, + + /// + /// Scale uniformly (maintaining aspect ratio) based on the horizontal scale of the parent. + /// + Horizontal, + } } diff --git a/osu.Game/Graphics/Containers/UprightUnscaledContainer.cs b/osu.Game/Graphics/Containers/UprightUnscaledContainer.cs deleted file mode 100644 index d801f4a215..0000000000 --- a/osu.Game/Graphics/Containers/UprightUnscaledContainer.cs +++ /dev/null @@ -1,23 +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.Graphics.Containers -{ - public enum ScaleMode - { - /// - /// Prevent this container from scaling. - /// - NoScaling, - - /// - /// Scale uniformly (maintaining aspect ratio) based on the vertical scale of the parent. - /// - Vertical, - - /// - /// Scale uniformly (maintaining aspect ratio) based on the horizontal scale of the parent. - /// - Horizontal, - } -} From 81c0a641b4f596ca4c9b0a7d9c19b97c389f2ee1 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 26 Aug 2022 14:51:08 +0300 Subject: [PATCH 145/420] Fix selection fallback path not updated to check inserted indices --- osu.Game/Screens/Select/BeatmapCarousel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index e9f676d32f..0cbc17c67a 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -299,7 +299,7 @@ namespace osu.Game.Screens.Select // If a direct selection couldn't be made, it's feasible that the difficulty name (or beatmap metadata) changed. // Let's attempt to follow set-level selection anyway. - SelectBeatmap(sender[changes.NewModifiedIndices.First()].Beatmaps.First()); + SelectBeatmap(sender[modifiedAndInserted.First()].Beatmaps.First()); } } } From a3e595a9aac215987a6cb5ee5cad82cfad615a5c Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 26 Aug 2022 14:51:19 +0300 Subject: [PATCH 146/420] Update comment to include inserted indices --- osu.Game/Screens/Select/BeatmapCarousel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 0cbc17c67a..0f130714f1 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -277,7 +277,7 @@ namespace osu.Game.Screens.Select if (selectedSetMarkedDeleted && modifiedAndInserted.Any()) { - // If it is no longer valid, make the bold assumption that an updated version will be available in the modified indices. + // If it is no longer valid, make the bold assumption that an updated version will be available in the modified/inserted indices. // This relies on the full update operation being in a single transaction, so please don't change that. foreach (int i in modifiedAndInserted) { From b082dc1fe47f30662d7f2f81a3ac5766371a1bf9 Mon Sep 17 00:00:00 2001 From: MBmasher Date: Sat, 27 Aug 2022 18:31:07 +1000 Subject: [PATCH 147/420] Slightly buff flashlight multiplier --- osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs index 84ef109598..40448c444c 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs @@ -26,7 +26,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills hasHiddenMod = mods.Any(m => m is OsuModHidden); } - private double skillMultiplier => 0.05; + private double skillMultiplier => 0.052; private double strainDecayBase => 0.15; private double currentStrain; From 16e0ec2f88f7924c4a9f965a735c5a635981c300 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sat, 27 Aug 2022 13:53:50 +0200 Subject: [PATCH 148/420] Fixed 0 length merge being allowed --- .../Editor/TestSceneObjectMerging.cs | 43 +++++++++++++++++++ .../Edit/OsuSelectionHandler.cs | 7 ++- 2 files changed, 48 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectMerging.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectMerging.cs index cdb2a7fe77..4673c3d1d9 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectMerging.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectMerging.cs @@ -3,9 +3,11 @@ using System.Linq; using NUnit.Framework; +using osu.Framework.Testing; using osu.Framework.Utils; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; +using osu.Game.Rulesets.Osu.Edit; using osu.Game.Rulesets.Osu.Objects; using osuTK; using osuTK.Input; @@ -14,6 +16,8 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor { public class TestSceneObjectMerging : TestSceneOsuEditor { + private OsuSelectionHandler selectionHandler => Editor.ChildrenOfType().First(); + [Test] public void TestSimpleMerge() { @@ -29,6 +33,9 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor EditorBeatmap.SelectedHitObjects.Add(circle2); }); + moveMouseToHitObject(1); + AddAssert("merge option available", () => selectionHandler.ContextMenuItems.Any(o => o.Text.Value == "Merge selection")); + mergeSelection(); AddAssert("slider created", () => circle1 is not null && circle2 is not null && sliderCreatedFor( @@ -174,6 +181,30 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor AddAssert("spinner not merged", () => EditorBeatmap.HitObjects.Contains(spinner)); } + [Test] + public void TestIllegalMerge() + { + HitCircle? circle1 = null; + HitCircle? circle2 = null; + + AddStep("add two circles on the same position", () => + { + circle1 = new HitCircle(); + circle2 = new HitCircle { Position = circle1.Position + Vector2.UnitX, StartTime = 1 }; + EditorClock.Seek(0); + EditorBeatmap.Add(circle1); + EditorBeatmap.Add(circle2); + EditorBeatmap.SelectedHitObjects.Add(circle1); + EditorBeatmap.SelectedHitObjects.Add(circle2); + }); + + moveMouseToHitObject(1); + AddAssert("merge option not available", () => selectionHandler.ContextMenuItems.Length > 0 && selectionHandler.ContextMenuItems.All(o => o.Text.Value != "Merge selection")); + mergeSelection(); + AddAssert("circles not merged", () => circle1 is not null && circle2 is not null + && EditorBeatmap.HitObjects.Contains(circle1) && EditorBeatmap.HitObjects.Contains(circle2)); + } + private void mergeSelection() { AddStep("merge selection", () => @@ -225,5 +256,17 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor return mergedSlider.Samples[0] is not null; } + + private void moveMouseToHitObject(int index) + { + AddStep($"hover mouse over hit object {index}", () => + { + if (EditorBeatmap.HitObjects.Count <= index) + return; + + Vector2 position = ((OsuHitObject)EditorBeatmap.HitObjects[index]).Position; + InputManager.MoveMouseTo(selectionHandler.ToScreenSpace(position)); + }); + } } } diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs index 2c5bbdb279..8b67c0dcc9 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs @@ -358,7 +358,8 @@ namespace osu.Game.Rulesets.Osu.Edit { var mergeableObjects = selectedMergeableObjects; - if (mergeableObjects.Length < 2) + if (mergeableObjects.Length < 2 || (mergeableObjects.All(h => h is not Slider) + && Precision.AlmostBigger(1, Vector2.DistanceSquared(mergeableObjects[0].Position, mergeableObjects[1].Position)))) return; ChangeHandler?.BeginChange(); @@ -445,7 +446,9 @@ namespace osu.Game.Rulesets.Osu.Edit foreach (var item in base.GetContextMenuItemsForSelection(selection)) yield return item; - if (selectedMergeableObjects.Length > 1) + var mergeableObjects = selectedMergeableObjects; + if (mergeableObjects.Length > 1 && (mergeableObjects.Any(h => h is Slider) + || Precision.DefinitelyBigger(Vector2.DistanceSquared(mergeableObjects[0].Position, mergeableObjects[1].Position), 1))) yield return new OsuMenuItem("Merge selection", MenuItemType.Destructive, mergeSelection); } } From ff2eac79d14934b5d6e179d38792d196484b77bd Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sat, 27 Aug 2022 17:43:32 +0200 Subject: [PATCH 149/420] fix same time merge causing crash --- .../Editor/TestSceneObjectMerging.cs | 29 ++++++++++++++++++- .../Edit/OsuSelectionHandler.cs | 2 ++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectMerging.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectMerging.cs index 4673c3d1d9..5c5384e0b7 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectMerging.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectMerging.cs @@ -190,7 +190,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor AddStep("add two circles on the same position", () => { circle1 = new HitCircle(); - circle2 = new HitCircle { Position = circle1.Position + Vector2.UnitX, StartTime = 1 }; + circle2 = new HitCircle { Position = circle1.Position + Vector2.UnitX }; EditorClock.Seek(0); EditorBeatmap.Add(circle1); EditorBeatmap.Add(circle2); @@ -205,6 +205,33 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor && EditorBeatmap.HitObjects.Contains(circle1) && EditorBeatmap.HitObjects.Contains(circle2)); } + [Test] + public void TestSameStartTimeMerge() + { + HitCircle? circle1 = null; + HitCircle? circle2 = null; + + AddStep("add two circles at the same time", () => + { + circle1 = new HitCircle(); + circle2 = new HitCircle { Position = circle1.Position + 100 * Vector2.UnitX }; + EditorClock.Seek(0); + EditorBeatmap.Add(circle1); + EditorBeatmap.Add(circle2); + EditorBeatmap.SelectedHitObjects.Add(circle1); + EditorBeatmap.SelectedHitObjects.Add(circle2); + }); + + moveMouseToHitObject(1); + AddAssert("merge option available", () => selectionHandler.ContextMenuItems.Any(o => o.Text.Value == "Merge selection")); + + mergeSelection(); + + AddAssert("slider created", () => circle1 is not null && circle2 is not null && sliderCreatedFor( + (pos: circle1.Position, pathType: PathType.Linear), + (pos: circle2.Position, pathType: null))); + } + private void mergeSelection() { AddStep("merge selection", () => diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs index 8b67c0dcc9..3d425afe9e 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs @@ -363,6 +363,7 @@ namespace osu.Game.Rulesets.Osu.Edit return; ChangeHandler?.BeginChange(); + EditorBeatmap.BeginChange(); // Have an initial slider object. var firstHitObject = mergeableObjects[0]; @@ -438,6 +439,7 @@ namespace osu.Game.Rulesets.Osu.Edit SelectedItems.Clear(); SelectedItems.Add(mergedHitObject); + EditorBeatmap.EndChange(); ChangeHandler?.EndChange(); } From 0cc6a76c17f6f5e9c0b36a747bfa349369f4e82c Mon Sep 17 00:00:00 2001 From: its5Q Date: Sun, 28 Aug 2022 14:13:38 +1000 Subject: [PATCH 150/420] Fix crash with legacy import from incomplete installs --- osu.Game/Database/LegacyBeatmapImporter.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Database/LegacyBeatmapImporter.cs b/osu.Game/Database/LegacyBeatmapImporter.cs index 6805cb36b8..0955461609 100644 --- a/osu.Game/Database/LegacyBeatmapImporter.cs +++ b/osu.Game/Database/LegacyBeatmapImporter.cs @@ -20,6 +20,10 @@ namespace osu.Game.Database protected override IEnumerable GetStableImportPaths(Storage storage) { + // make sure the directory exists + if (!storage.ExistsDirectory(string.Empty)) + yield break; + foreach (string directory in storage.GetDirectories(string.Empty)) { var directoryStorage = storage.GetStorageForDirectory(directory); From d4a52baa56f0eee37c6a50ba65c29d57dc8bc3f9 Mon Sep 17 00:00:00 2001 From: HiddenNode Date: Mon, 29 Aug 2022 00:07:42 +0100 Subject: [PATCH 151/420] Added visual test for UprightAspectMaintainingContainer --- ...tSceneUprightAspectMaintainingContainer.cs | 245 ++++++++++++++++++ 1 file changed, 245 insertions(+) create mode 100644 osu.Game.Tests/Visual/UserInterface/TestSceneUprightAspectMaintainingContainer.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneUprightAspectMaintainingContainer.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneUprightAspectMaintainingContainer.cs new file mode 100644 index 0000000000..8b87e4030e --- /dev/null +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneUprightAspectMaintainingContainer.cs @@ -0,0 +1,245 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable disable + +using System; +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics; +using osuTK.Graphics; +using osuTK; + +namespace osu.Game.Tests.Visual.UserInterface +{ + public class TestSceneUprightAspectMaintainingContainer : OsuGridTestScene + { + private const int rows = 3; + private const int columns = 4; + + private readonly ScaleMode[] scaleModeValues = { ScaleMode.NoScaling, ScaleMode.Horizontal, ScaleMode.Vertical }; + private readonly float[] scalingFactorValues = { 1.0f / 3, 1.0f / 2, 1.0f, 1.5f }; + + private readonly List> parentContainers = new List>(rows); + private readonly List> childContainers = new List>(rows); + + // Preferably should be set to (4 * 2^n) + private const int rotation_step_count = 8; + + private readonly List flipStates = new List(); + private readonly List rotationSteps = new List(); + private readonly List scaleSteps = new List(); + + public TestSceneUprightAspectMaintainingContainer() + : base(rows, columns) + { + for (int i = 0; i < rows; i++) + { + parentContainers.Add(new List()); + childContainers.Add(new List()); + + for (int j = 0; j < columns; j++) + { + UprightAspectMaintainingContainer child; + Container parent = new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Height = 80, + Width = 80, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = new Color4(255, 0, 0, 160), + }, + new OsuSpriteText + { + Text = "Parent", + }, + child = new UprightAspectMaintainingContainer + { + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + AutoSizeAxes = Axes.Both, + + // These are the parameters being Tested + Scaling = scaleModeValues[i], + ScalingFactor = scalingFactorValues[j], + + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = new Color4(0, 0, 255, 160), + }, + new OsuSpriteText + { + Text = "Text", + Font = OsuFont.Numeric, + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + Padding = new MarginPadding + { + Horizontal = 4, + Vertical = 4, + } + }, + } + } + } + }; + + Container cellInfo = new Container + { + Children = new Drawable[] + { + new OsuSpriteText + { + Text = "Scaling: " + scaleModeValues[i].ToString(), + }, + new OsuSpriteText + { + Text = "ScalingFactor: " + scalingFactorValues[j].ToString("0.00"), + Margin = new MarginPadding + { + Top = 15, + }, + }, + }, + }; + + Cell(i * columns + j).Add(cellInfo); + Cell(i * columns + j).Add(parent); + parentContainers[i].Add(parent); + childContainers[i].Add(child); + } + } + + flipStates.AddRange(new[] { 1, -1 }); + rotationSteps.AddRange(Enumerable.Range(0, rotation_step_count).Select(x => 360f * ((float)x / rotation_step_count))); + scaleSteps.AddRange(new[] { 1, 0.5f, 0.3f, 1.5f, 2.0f }); + } + + [Test] + public void ExplicitlySizedParent() + { + var parentStates = from xFlip in flipStates + from yFlip in flipStates + from xScale in scaleSteps + from yScale in scaleSteps + from rotation in rotationSteps + select new { xFlip, yFlip, xScale, yScale, rotation }; + + foreach (var state in parentStates) + { + Vector2 parentScale = new Vector2(state.xFlip * state.xScale, state.yFlip * state.yScale); + float parentRotation = state.rotation; + + AddStep("S: (" + parentScale.X.ToString("0.00") + ", " + parentScale.Y.ToString("0.00") + "), R: " + parentRotation.ToString("0.00"), () => + { + foreach (List list in parentContainers) + { + foreach (Container container in list) + { + container.Scale = parentScale; + container.Rotation = parentRotation; + } + } + }); + + AddAssert("Check if state is valid", () => + { + foreach (int i in Enumerable.Range(0, parentContainers.Count)) + { + foreach (int j in Enumerable.Range(0, parentContainers[i].Count)) + { + if (!uprightAspectMaintainingContainerStateIsValid(parentContainers[i][j], childContainers[i][j])) + return false; + } + } + + return true; + }); + } + } + + private bool uprightAspectMaintainingContainerStateIsValid(Container parent, UprightAspectMaintainingContainer child) + { + Matrix3 parentMatrix = parent.DrawInfo.Matrix; + Matrix3 childMatrix = child.DrawInfo.Matrix; + Vector3 childScale = childMatrix.ExtractScale(); + Vector3 parentScale = parentMatrix.ExtractScale(); + + // Orientation check + if (!(isNearlyZero(MathF.Abs(childMatrix.M21)) && isNearlyZero(MathF.Abs(childMatrix.M12)))) + return false; + + // flip check + if (!(childMatrix.M11 * childMatrix.M22 > 0)) + return false; + + // Aspect ratio check + if (!isNearlyZero(childScale.X - childScale.Y, 0.0001f)) + return false; + + // ScalingMode check + switch (child.Scaling) + { + case ScaleMode.NoScaling: + if (!(isNearlyZero(childMatrix.M11 - 1.0f) && isNearlyZero(childMatrix.M22 - 1.0f))) + return false; + + break; + + case ScaleMode.Vertical: + if (!(checkScaling(child.ScalingFactor, parentScale.Y, childScale.Y))) + return false; + + break; + + case ScaleMode.Horizontal: + if (!(checkScaling(child.ScalingFactor, parentScale.X, childScale.X))) + return false; + + break; + } + + return true; + } + + private bool checkScaling(float scalingFactor, float parentScale, float childScale) + { + if (scalingFactor <= 1.0f) + { + if (!isNearlyZero(1.0f + (parentScale - 1.0f) * scalingFactor - childScale)) + return false; + } + else if (scalingFactor > 1.0f) + { + if (parentScale < 1.0f) + { + if (!isNearlyZero((parentScale * (1.0f / scalingFactor)) - childScale)) + return false; + } + else if (!isNearlyZero(parentScale * scalingFactor - childScale)) + return false; + } + + return true; + } + + private bool isNearlyZero(float f, float epsilon = 0.00001f) + { + return f < epsilon; + } + } +} From 62210bce4ee3c97c212e9f064ec6631ab271dd10 Mon Sep 17 00:00:00 2001 From: HiddenNode Date: Mon, 29 Aug 2022 00:08:19 +0100 Subject: [PATCH 152/420] Fixed issues found in UprightAspectMaintainingContainer --- .../UprightAspectMaintainingContainer.cs | 59 ++++++++++--------- 1 file changed, 31 insertions(+), 28 deletions(-) diff --git a/osu.Game/Graphics/Containers/UprightAspectMaintainingContainer.cs b/osu.Game/Graphics/Containers/UprightAspectMaintainingContainer.cs index 9736cba206..64b9eb409b 100644 --- a/osu.Game/Graphics/Containers/UprightAspectMaintainingContainer.cs +++ b/osu.Game/Graphics/Containers/UprightAspectMaintainingContainer.cs @@ -14,8 +14,6 @@ namespace osu.Game.Graphics.Containers /// public class UprightAspectMaintainingContainer : Container { - protected override Container Content { get; } - /// /// Controls how much this container scales compared to its parent (default is 1.0f). /// @@ -30,7 +28,6 @@ namespace osu.Game.Graphics.Containers public UprightAspectMaintainingContainer() { - AddInternal(Content = new GrowToFitContainer()); AddLayout(layout); } @@ -45,6 +42,14 @@ namespace osu.Game.Graphics.Containers } } + public override void Add(Drawable drawable) + { + base.Add(new GrowToFitContainer + { + Child = drawable + }); + } + /// /// Keeps the drawable upright and unstretched preventing it from being rotated, sheared, scaled or flipped with its Parent. /// @@ -57,23 +62,23 @@ namespace osu.Game.Graphics.Containers parentMatrix.M31 = 0.0f; parentMatrix.M32 = 0.0f; - Matrix3 reversedParrent = parentMatrix.Inverted(); + Matrix3 reversedParent = parentMatrix.Inverted(); // Extract the rotation. - float angle = MathF.Atan2(reversedParrent.M12, reversedParrent.M11); + float angle = MathF.Atan2(reversedParent.M12, reversedParent.M11); Rotation = MathHelper.RadiansToDegrees(angle); // Remove rotation from the C matrix so that it only contains shear and scale. Matrix3 m = Matrix3.CreateRotationZ(-angle); - reversedParrent *= m; + reversedParent *= m; // Extract shear. - float alpha = reversedParrent.M21 / reversedParrent.M22; + float alpha = reversedParent.M21 / reversedParent.M22; Shear = new Vector2(-alpha, 0); // Etract scale. - float sx = reversedParrent.M11; - float sy = reversedParrent.M22; + float sx = reversedParent.M11; + float sy = reversedParent.M22; Vector3 parentScale = parentMatrix.ExtractScale(); @@ -90,32 +95,30 @@ namespace osu.Game.Graphics.Containers break; } - usedScale = 1.0f + (usedScale - 1.0f) * ScalingFactor; + if (Scaling != ScaleMode.NoScaling) + { + if (ScalingFactor < 1.0f) + usedScale = 1.0f + (usedScale - 1.0f) * ScalingFactor; + if (ScalingFactor > 1.0f) + usedScale = (usedScale < 1.0f) ? usedScale * (1.0f / ScalingFactor) : usedScale * ScalingFactor; + } Scale = new Vector2(sx * usedScale, sy * usedScale); } - /// - /// A container that grows in size to fit its children and retains its size when its children shrink - /// - private class GrowToFitContainer : Container + public class GrowToFitContainer : Container { - protected override Container Content => content; - private readonly Container content; - - public GrowToFitContainer() - { - InternalChild = content = new Container - { - AutoSizeAxes = Axes.Both, - }; - } - protected override void Update() { - base.Update(); - Height = Math.Max(content.Height, Height); - Width = Math.Max(content.Width, Width); + if ((Child.RelativeSizeAxes & Axes.X) != 0) + RelativeSizeAxes |= Axes.X; + else + Width = Math.Max(Child.Width, Width); + + if ((Child.RelativeSizeAxes & Axes.Y) != 0) + RelativeSizeAxes |= Axes.Y; + else + Height = Math.Max(Child.Height, Height); } } } From c0b13c7e1fa33da7681957dd62326632cba564e1 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 22 Aug 2022 19:39:12 +0900 Subject: [PATCH 153/420] Refactor ScoreProcessor ComputeScore() methods --- .../Rulesets/Scoring/ScoreProcessorTest.cs | 4 +- .../PerformanceBreakdownCalculator.cs | 2 +- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 60 +------------------ osu.Game/Scoring/ScoreManager.cs | 2 +- .../OnlinePlay/Playlists/PlaylistsPlayer.cs | 2 +- 5 files changed, 8 insertions(+), 62 deletions(-) diff --git a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs index 44ebdad2e4..7a45d1e7cf 100644 --- a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs +++ b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs @@ -307,7 +307,7 @@ namespace osu.Game.Tests.Rulesets.Scoring HitObjects = { new TestHitObject(result) } }); - Assert.That(scoreProcessor.ComputeFinalScore(ScoringMode.Standardised, new ScoreInfo + Assert.That(scoreProcessor.ComputeScore(ScoringMode.Standardised, new ScoreInfo { Ruleset = new TestRuleset().RulesetInfo, MaxCombo = result.AffectsCombo() ? 1 : 0, @@ -350,7 +350,7 @@ namespace osu.Game.Tests.Rulesets.Scoring } }; - double totalScore = new TestScoreProcessor().ComputeFinalScore(ScoringMode.Standardised, testScore); + double totalScore = new TestScoreProcessor().ComputeScore(ScoringMode.Standardised, testScore); Assert.That(totalScore, Is.EqualTo(750_000)); // 500K from accuracy (100%), and 250K from combo (50%). } #pragma warning restore CS0618 diff --git a/osu.Game/Rulesets/Difficulty/PerformanceBreakdownCalculator.cs b/osu.Game/Rulesets/Difficulty/PerformanceBreakdownCalculator.cs index 4465f1a328..3fb12041d1 100644 --- a/osu.Game/Rulesets/Difficulty/PerformanceBreakdownCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/PerformanceBreakdownCalculator.cs @@ -66,7 +66,7 @@ namespace osu.Game.Rulesets.Difficulty // calculate total score ScoreProcessor scoreProcessor = ruleset.CreateScoreProcessor(); scoreProcessor.Mods.Value = perfectPlay.Mods; - perfectPlay.TotalScore = (long)scoreProcessor.ComputeFinalScore(ScoringMode.Standardised, perfectPlay); + perfectPlay.TotalScore = (long)scoreProcessor.ComputeScore(ScoringMode.Standardised, perfectPlay); // compute rank achieved // default to SS, then adjust the rank with mods diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 5b21caee84..f13e3e6de6 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -121,7 +121,7 @@ namespace osu.Game.Rulesets.Scoring /// /// The maximum of a basic (non-tick and non-bonus) hitobject. - /// Only populated via or . + /// Only populated via or . /// private HitResult? maxBasicResult; @@ -281,7 +281,7 @@ namespace osu.Game.Rulesets.Scoring /// The to compute the total score of. /// The total score in the given . [Pure] - public double ComputeFinalScore(ScoringMode mode, ScoreInfo scoreInfo) + public double ComputeScore(ScoringMode mode, ScoreInfo scoreInfo) { if (!ruleset.RulesetInfo.Equals(scoreInfo.Ruleset)) throw new ArgumentException($"Unexpected score ruleset. Expected \"{ruleset.RulesetInfo.ShortName}\" but was \"{scoreInfo.Ruleset.ShortName}\"."); @@ -291,60 +291,6 @@ namespace osu.Game.Rulesets.Scoring return ComputeScore(mode, current, maximum); } - /// - /// Computes the total score of a partially-completed . This should be used when it is unknown whether a score is complete. - /// - /// - /// Requires to have been called before use. - /// - /// The to represent the score as. - /// The to compute the total score of. - /// The total score in the given . - [Pure] - public double ComputePartialScore(ScoringMode mode, ScoreInfo scoreInfo) - { - if (!ruleset.RulesetInfo.Equals(scoreInfo.Ruleset)) - throw new ArgumentException($"Unexpected score ruleset. Expected \"{ruleset.RulesetInfo.ShortName}\" but was \"{scoreInfo.Ruleset.ShortName}\"."); - - if (!beatmapApplied) - throw new InvalidOperationException($"Cannot compute partial score without calling {nameof(ApplyBeatmap)}."); - - ExtractScoringValues(scoreInfo, out var current, out _); - - return ComputeScore(mode, current, MaximumScoringValues); - } - - /// - /// Computes the total score of a given with a given custom max achievable combo. - /// - /// - /// This is useful for processing legacy scores in which the maximum achievable combo can be more accurately determined via external means (e.g. database values or difficulty calculation). - ///

Does not require to have been called before use.

- ///
- /// The to represent the score as. - /// The to compute the total score of. - /// The maximum achievable combo for the provided beatmap. - /// The total score in the given . - [Pure] - public double ComputeFinalLegacyScore(ScoringMode mode, ScoreInfo scoreInfo, int maxAchievableCombo) - { - if (!ruleset.RulesetInfo.Equals(scoreInfo.Ruleset)) - throw new ArgumentException($"Unexpected score ruleset. Expected \"{ruleset.RulesetInfo.ShortName}\" but was \"{scoreInfo.Ruleset.ShortName}\"."); - - double accuracyRatio = scoreInfo.Accuracy; - double comboRatio = maxAchievableCombo > 0 ? (double)scoreInfo.MaxCombo / maxAchievableCombo : 1; - - ExtractScoringValues(scoreInfo, out var current, out var maximum); - - // For legacy osu!mania scores, a full-GREAT score has 100% accuracy. If combined with a full-combo, the score becomes indistinguishable from a full-PERFECT score. - // To get around this, the accuracy ratio is always recalculated based on the hit statistics rather than trusting the score. - // Note: This cannot be applied universally to all legacy scores, as some rulesets (e.g. catch) group multiple judgements together. - if (scoreInfo.IsLegacyScore && scoreInfo.Ruleset.OnlineID == 3 && maximum.BaseScore > 0) - accuracyRatio = current.BaseScore / maximum.BaseScore; - - return ComputeScore(mode, accuracyRatio, comboRatio, current.BonusScore, maximum.CountBasicHitObjects); - } - /// /// Computes the total score from scoring values. /// @@ -454,7 +400,7 @@ namespace osu.Game.Rulesets.Scoring score.MaximumStatistics[result] = maximumResultCounts.GetValueOrDefault(result); // Populate total score after everything else. - score.TotalScore = (long)Math.Round(ComputeFinalScore(ScoringMode.Standardised, score)); + score.TotalScore = (long)Math.Round(ComputeScore(ScoringMode.Standardised, score)); } /// diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index 7367a1ef77..ecd37c761c 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -148,7 +148,7 @@ namespace osu.Game.Scoring var scoreProcessor = ruleset.CreateScoreProcessor(); scoreProcessor.Mods.Value = score.Mods; - return (long)Math.Round(scoreProcessor.ComputeFinalLegacyScore(mode, score, beatmapMaxCombo.Value)); + return (long)Math.Round(scoreProcessor.ComputeScore(mode, score)); } /// diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs index 282c1db585..1c4d02bb11 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs @@ -67,7 +67,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists { await base.PrepareScoreForResultsAsync(score).ConfigureAwait(false); - Score.ScoreInfo.TotalScore = (int)Math.Round(ScoreProcessor.ComputeFinalScore(ScoringMode.Standardised, Score.ScoreInfo)); + Score.ScoreInfo.TotalScore = (int)Math.Round(ScoreProcessor.ComputeScore(ScoringMode.Standardised, Score.ScoreInfo)); } protected override void Dispose(bool isDisposing) From 90b9c02ac64ca410fd8440d8d30c4a607aaeffeb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 29 Aug 2022 14:01:04 +0900 Subject: [PATCH 154/420] Remove `"internal"` identifier as unnecessary --- osu.Game.Rulesets.Catch/CatchRuleset.cs | 30 +++++++++---------- osu.Game.Rulesets.Mania/ManiaRuleset.cs | 20 ++++++------- osu.Game.Rulesets.Osu/OsuRuleset.cs | 40 ++++++++++++------------- osu.Game.Rulesets.Taiko/TaikoRuleset.cs | 32 ++++++++++---------- osu.Game/Rulesets/RealmRulesetStore.cs | 2 -- osu.Game/Rulesets/Ruleset.cs | 37 ++++++++++++----------- 6 files changed, 80 insertions(+), 81 deletions(-) diff --git a/osu.Game.Rulesets.Catch/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs index f94bf276a0..321399c597 100644 --- a/osu.Game.Rulesets.Catch/CatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs @@ -3,30 +3,30 @@ #nullable disable -using osu.Game.Beatmaps; -using osu.Game.Graphics; -using osu.Game.Rulesets.Catch.Mods; -using osu.Game.Rulesets.Catch.UI; -using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.UI; +using System; using System.Collections.Generic; +using osu.Framework.Extensions.EnumExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Bindings; -using osu.Game.Rulesets.Catch.Replays; -using osu.Game.Rulesets.Replays.Types; +using osu.Framework.Localisation; +using osu.Game.Beatmaps; using osu.Game.Beatmaps.Legacy; +using osu.Game.Graphics; using osu.Game.Rulesets.Catch.Beatmaps; using osu.Game.Rulesets.Catch.Difficulty; -using osu.Game.Rulesets.Catch.Scoring; -using osu.Game.Rulesets.Difficulty; -using osu.Game.Rulesets.Scoring; -using System; -using osu.Framework.Extensions.EnumExtensions; -using osu.Framework.Localisation; using osu.Game.Rulesets.Catch.Edit; +using osu.Game.Rulesets.Catch.Mods; +using osu.Game.Rulesets.Catch.Replays; +using osu.Game.Rulesets.Catch.Scoring; using osu.Game.Rulesets.Catch.Skinning.Legacy; +using osu.Game.Rulesets.Catch.UI; +using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Replays.Types; +using osu.Game.Rulesets.Scoring; +using osu.Game.Rulesets.UI; using osu.Game.Skinning; namespace osu.Game.Rulesets.Catch @@ -43,7 +43,7 @@ namespace osu.Game.Rulesets.Catch public const string SHORT_NAME = "fruits"; - public override string RulesetAPIVersionSupported => "internal"; + public override string RulesetAPIVersionSupported => CURRENT_RULESET_API_VERSION; public override IEnumerable GetDefaultKeyBindings(int variant = 0) => new[] { diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index b1fe4b30c4..813e2c461a 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -4,11 +4,6 @@ #nullable disable using System; -using osu.Game.Beatmaps; -using osu.Game.Rulesets.Mania.Mods; -using osu.Game.Rulesets.Mania.UI; -using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.UI; using System.Collections.Generic; using System.Linq; using osu.Framework.Extensions.EnumExtensions; @@ -16,11 +11,10 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Bindings; using osu.Framework.Localisation; -using osu.Game.Graphics; -using osu.Game.Rulesets.Mania.Replays; -using osu.Game.Rulesets.Replays.Types; +using osu.Game.Beatmaps; using osu.Game.Beatmaps.Legacy; using osu.Game.Configuration; +using osu.Game.Graphics; using osu.Game.Overlays.Settings; using osu.Game.Rulesets.Configuration; using osu.Game.Rulesets.Difficulty; @@ -31,13 +25,19 @@ using osu.Game.Rulesets.Mania.Configuration; using osu.Game.Rulesets.Mania.Difficulty; using osu.Game.Rulesets.Mania.Edit; using osu.Game.Rulesets.Mania.Edit.Setup; +using osu.Game.Rulesets.Mania.Mods; +using osu.Game.Rulesets.Mania.Replays; using osu.Game.Rulesets.Mania.Scoring; using osu.Game.Rulesets.Mania.Skinning.Legacy; +using osu.Game.Rulesets.Mania.UI; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Replays.Types; using osu.Game.Rulesets.Scoring; -using osu.Game.Skinning; +using osu.Game.Rulesets.UI; using osu.Game.Scoring; using osu.Game.Screens.Edit.Setup; using osu.Game.Screens.Ranking.Statistics; +using osu.Game.Skinning; namespace osu.Game.Rulesets.Mania { @@ -60,7 +60,7 @@ namespace osu.Game.Rulesets.Mania public const string SHORT_NAME = "mania"; - public override string RulesetAPIVersionSupported => "internal"; + public override string RulesetAPIVersionSupported => CURRENT_RULESET_API_VERSION; public override HitObjectComposer CreateHitObjectComposer() => new ManiaHitObjectComposer(this); diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index 4400dfbb65..226299d168 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -3,42 +3,42 @@ #nullable disable -using osu.Game.Beatmaps; -using osu.Game.Graphics; -using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Osu.Mods; -using osu.Game.Rulesets.Osu.UI; -using osu.Game.Rulesets.UI; +using System; using System.Collections.Generic; +using System.Linq; +using osu.Framework.Extensions.EnumExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; -using osu.Game.Overlays.Settings; using osu.Framework.Input.Bindings; -using osu.Game.Rulesets.Osu.Edit; -using osu.Game.Rulesets.Edit; -using osu.Game.Rulesets.Osu.Replays; -using osu.Game.Rulesets.Replays.Types; +using osu.Framework.Localisation; +using osu.Game.Beatmaps; using osu.Game.Beatmaps.Legacy; using osu.Game.Configuration; +using osu.Game.Graphics; +using osu.Game.Overlays.Settings; using osu.Game.Rulesets.Configuration; using osu.Game.Rulesets.Difficulty; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Beatmaps; using osu.Game.Rulesets.Osu.Configuration; using osu.Game.Rulesets.Osu.Difficulty; -using osu.Game.Rulesets.Osu.Scoring; -using osu.Game.Rulesets.Scoring; -using osu.Game.Scoring; -using osu.Game.Skinning; -using System; -using System.Linq; -using osu.Framework.Extensions.EnumExtensions; -using osu.Framework.Localisation; +using osu.Game.Rulesets.Osu.Edit; using osu.Game.Rulesets.Osu.Edit.Setup; +using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Replays; +using osu.Game.Rulesets.Osu.Scoring; using osu.Game.Rulesets.Osu.Skinning.Legacy; using osu.Game.Rulesets.Osu.Statistics; +using osu.Game.Rulesets.Osu.UI; +using osu.Game.Rulesets.Replays.Types; +using osu.Game.Rulesets.Scoring; +using osu.Game.Rulesets.UI; +using osu.Game.Scoring; using osu.Game.Screens.Edit.Setup; using osu.Game.Screens.Ranking.Statistics; +using osu.Game.Skinning; namespace osu.Game.Rulesets.Osu { @@ -54,7 +54,7 @@ namespace osu.Game.Rulesets.Osu public const string SHORT_NAME = "osu"; - public override string RulesetAPIVersionSupported => "internal"; + public override string RulesetAPIVersionSupported => CURRENT_RULESET_API_VERSION; public override IEnumerable GetDefaultKeyBindings(int variant = 0) => new[] { diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index 275c7144a7..f4eb1c68b3 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -3,33 +3,33 @@ #nullable disable -using osu.Game.Beatmaps; -using osu.Game.Graphics; -using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Taiko.Mods; -using osu.Game.Rulesets.Taiko.UI; -using osu.Game.Rulesets.UI; +using System; using System.Collections.Generic; +using System.Linq; +using osu.Framework.Extensions.EnumExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Bindings; -using osu.Game.Rulesets.Replays.Types; -using osu.Game.Rulesets.Taiko.Replays; +using osu.Framework.Localisation; +using osu.Game.Beatmaps; using osu.Game.Beatmaps.Legacy; +using osu.Game.Graphics; using osu.Game.Rulesets.Difficulty; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Replays.Types; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Taiko.Beatmaps; using osu.Game.Rulesets.Taiko.Difficulty; -using osu.Game.Rulesets.Taiko.Scoring; -using osu.Game.Scoring; -using System; -using System.Linq; -using osu.Framework.Extensions.EnumExtensions; -using osu.Framework.Localisation; -using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Taiko.Edit; +using osu.Game.Rulesets.Taiko.Mods; using osu.Game.Rulesets.Taiko.Objects; +using osu.Game.Rulesets.Taiko.Replays; +using osu.Game.Rulesets.Taiko.Scoring; using osu.Game.Rulesets.Taiko.Skinning.Legacy; +using osu.Game.Rulesets.Taiko.UI; +using osu.Game.Rulesets.UI; +using osu.Game.Scoring; using osu.Game.Screens.Ranking.Statistics; using osu.Game.Skinning; @@ -49,7 +49,7 @@ namespace osu.Game.Rulesets.Taiko public const string SHORT_NAME = "taiko"; - public override string RulesetAPIVersionSupported => "internal"; + public override string RulesetAPIVersionSupported => CURRENT_RULESET_API_VERSION; public override IEnumerable GetDefaultKeyBindings(int variant = 0) => new[] { diff --git a/osu.Game/Rulesets/RealmRulesetStore.cs b/osu.Game/Rulesets/RealmRulesetStore.cs index 590f118b68..456f6e399b 100644 --- a/osu.Game/Rulesets/RealmRulesetStore.cs +++ b/osu.Game/Rulesets/RealmRulesetStore.cs @@ -119,8 +119,6 @@ namespace osu.Game.Rulesets // Consider rulesets which haven't override the version as up-to-date for now. // At some point (once ruleset devs add versioning), we'll probably want to disallow this for deployed builds. case @"": - // Rulesets which are bundled with the game. Saves having to update their versions each bump. - case @"internal": // Ruleset is up-to-date, all good. case Ruleset.CURRENT_RULESET_API_VERSION: return true; diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index 63f5906f46..cb72a1f20f 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -7,33 +7,33 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; +using JetBrains.Annotations; +using osu.Framework.Extensions; +using osu.Framework.Extensions.EnumExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Bindings; using osu.Framework.IO.Stores; -using osu.Game.Beatmaps; -using osu.Game.Overlays.Settings; -using osu.Game.Rulesets.Edit; -using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Replays.Types; -using osu.Game.Rulesets.UI; -using osu.Game.Beatmaps.Legacy; -using osu.Game.Configuration; -using osu.Game.Rulesets.Configuration; -using osu.Game.Rulesets.Difficulty; -using osu.Game.Rulesets.Scoring; -using osu.Game.Scoring; -using osu.Game.Skinning; -using osu.Game.Users; -using JetBrains.Annotations; -using osu.Framework.Extensions; -using osu.Framework.Extensions.EnumExtensions; using osu.Framework.Localisation; using osu.Framework.Testing; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.Legacy; +using osu.Game.Configuration; using osu.Game.Extensions; +using osu.Game.Overlays.Settings; +using osu.Game.Rulesets.Configuration; +using osu.Game.Rulesets.Difficulty; +using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Filter; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Replays.Types; +using osu.Game.Rulesets.Scoring; +using osu.Game.Rulesets.UI; +using osu.Game.Scoring; using osu.Game.Screens.Edit.Setup; using osu.Game.Screens.Ranking.Statistics; +using osu.Game.Skinning; +using osu.Game.Users; namespace osu.Game.Rulesets { @@ -56,7 +56,8 @@ namespace osu.Game.Rulesets /// Ruleset implementations should be updated to support the latest version to ensure they can still be loaded. /// /// - /// When updating a ruleset to support the latest API, you should set this to . + /// Generally, all ruleset implementations should point this directly to . + /// This will ensure that each time you compile a new release, it will pull in the most recent version. /// See https://github.com/ppy/osu/wiki/Breaking-Changes for full details on required ongoing changes. /// public virtual string RulesetAPIVersionSupported => string.Empty; From 5ff4e6a4fe657b6c2f420bc1df3eddb028401638 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 29 Aug 2022 14:26:09 +0900 Subject: [PATCH 155/420] Add test coverage for outdated ruleset --- osu.Game.Tests/Database/RulesetStoreTests.cs | 87 +++++++++++++++++++- 1 file changed, 85 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Database/RulesetStoreTests.cs b/osu.Game.Tests/Database/RulesetStoreTests.cs index 795e90f543..dedec6dc83 100644 --- a/osu.Game.Tests/Database/RulesetStoreTests.cs +++ b/osu.Game.Tests/Database/RulesetStoreTests.cs @@ -1,11 +1,19 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - +using System; +using System.Collections.Generic; using System.Linq; using NUnit.Framework; +using osu.Game.Beatmaps; using osu.Game.Rulesets; +using osu.Game.Rulesets.Difficulty; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Osu.Beatmaps; +using osu.Game.Rulesets.Osu.Difficulty; +using osu.Game.Rulesets.Osu.UI; +using osu.Game.Rulesets.UI; namespace osu.Game.Tests.Database { @@ -51,5 +59,80 @@ namespace osu.Game.Tests.Database Assert.IsFalse(rulesets.GetRuleset("mania")?.IsManaged); }); } + + [Test] + public void TestOutdatedRulesetNotAvailable() + { + RunTestWithRealm((realm, storage) => + { + OutdatedRuleset.Version = "2021.101.0"; + OutdatedRuleset.HasImplementations = true; + + var ruleset = new OutdatedRuleset(); + string rulesetShortName = ruleset.RulesetInfo.ShortName; + + realm.Write(r => r.Add(new RulesetInfo(rulesetShortName, ruleset.RulesetInfo.Name, ruleset.RulesetInfo.InstantiationInfo, ruleset.RulesetInfo.OnlineID) + { + Available = true, + })); + + Assert.That(realm.Run(r => r.Find(rulesetShortName).Available), Is.True); + + // Availability is updated on construction of a RealmRulesetStore + var _ = new RealmRulesetStore(realm, storage); + + Assert.That(realm.Run(r => r.Find(rulesetShortName).Available), Is.False); + + // Simulate the ruleset getting updated + OutdatedRuleset.Version = Ruleset.CURRENT_RULESET_API_VERSION; + var __ = new RealmRulesetStore(realm, storage); + + Assert.That(realm.Run(r => r.Find(rulesetShortName).Available), Is.True); + }); + } + + private class OutdatedRuleset : Ruleset + { + public override string RulesetAPIVersionSupported => Version; + + public static bool HasImplementations = true; + + public static string Version { get; set; } = CURRENT_RULESET_API_VERSION; + + public override IEnumerable GetModsFor(ModType type) + { + if (!HasImplementations) + throw new NotImplementedException(); + + return Array.Empty(); + } + + public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList? mods = null) + { + if (!HasImplementations) + throw new NotImplementedException(); + + return new DrawableOsuRuleset(new OsuRuleset(), beatmap, mods); + } + + public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) + { + if (!HasImplementations) + throw new NotImplementedException(); + + return new OsuBeatmapConverter(beatmap, new OsuRuleset()); + } + + public override DifficultyCalculator CreateDifficultyCalculator(IWorkingBeatmap beatmap) + { + if (!HasImplementations) + throw new NotImplementedException(); + + return new OsuDifficultyCalculator(new OsuRuleset().RulesetInfo, beatmap); + } + + public override string Description => "outdated ruleset"; + public override string ShortName => "ruleset-outdated"; + } } } From 892f43da433a96d408f8b8d723d03c677d1d3653 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 29 Aug 2022 14:28:55 +0900 Subject: [PATCH 156/420] Add test coverage of ruleset being marked unavailable if methods are throwing --- osu.Game.Tests/Database/RulesetStoreTests.cs | 35 +++++++++++++++++--- 1 file changed, 30 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Database/RulesetStoreTests.cs b/osu.Game.Tests/Database/RulesetStoreTests.cs index dedec6dc83..a5662fa121 100644 --- a/osu.Game.Tests/Database/RulesetStoreTests.cs +++ b/osu.Game.Tests/Database/RulesetStoreTests.cs @@ -60,15 +60,40 @@ namespace osu.Game.Tests.Database }); } + [Test] + public void TestRulesetThrowingOnMethods() + { + RunTestWithRealm((realm, storage) => + { + LoadTestRuleset.Version = Ruleset.CURRENT_RULESET_API_VERSION; + LoadTestRuleset.HasImplementations = false; + + var ruleset = new LoadTestRuleset(); + string rulesetShortName = ruleset.RulesetInfo.ShortName; + + realm.Write(r => r.Add(new RulesetInfo(rulesetShortName, ruleset.RulesetInfo.Name, ruleset.RulesetInfo.InstantiationInfo, ruleset.RulesetInfo.OnlineID) + { + Available = true, + })); + + Assert.That(realm.Run(r => r.Find(rulesetShortName).Available), Is.True); + + // Availability is updated on construction of a RealmRulesetStore + var _ = new RealmRulesetStore(realm, storage); + + Assert.That(realm.Run(r => r.Find(rulesetShortName).Available), Is.False); + }); + } + [Test] public void TestOutdatedRulesetNotAvailable() { RunTestWithRealm((realm, storage) => { - OutdatedRuleset.Version = "2021.101.0"; - OutdatedRuleset.HasImplementations = true; + LoadTestRuleset.Version = "2021.101.0"; + LoadTestRuleset.HasImplementations = true; - var ruleset = new OutdatedRuleset(); + var ruleset = new LoadTestRuleset(); string rulesetShortName = ruleset.RulesetInfo.ShortName; realm.Write(r => r.Add(new RulesetInfo(rulesetShortName, ruleset.RulesetInfo.Name, ruleset.RulesetInfo.InstantiationInfo, ruleset.RulesetInfo.OnlineID) @@ -84,14 +109,14 @@ namespace osu.Game.Tests.Database Assert.That(realm.Run(r => r.Find(rulesetShortName).Available), Is.False); // Simulate the ruleset getting updated - OutdatedRuleset.Version = Ruleset.CURRENT_RULESET_API_VERSION; + LoadTestRuleset.Version = Ruleset.CURRENT_RULESET_API_VERSION; var __ = new RealmRulesetStore(realm, storage); Assert.That(realm.Run(r => r.Find(rulesetShortName).Available), Is.True); }); } - private class OutdatedRuleset : Ruleset + private class LoadTestRuleset : Ruleset { public override string RulesetAPIVersionSupported => Version; From e8ae6840ea6da67cb206e55955c1040951378769 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 29 Aug 2022 15:23:34 +0900 Subject: [PATCH 157/420] Add test coverage of selection being retained --- .../SongSelect/TestScenePlaySongSelect.cs | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs index 3d8f496c9a..5db46e3097 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs @@ -6,15 +6,18 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading.Tasks; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Bindables; +using osu.Framework.Extensions; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics.Containers; using osu.Framework.Platform; using osu.Framework.Screens; using osu.Framework.Testing; +using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Database; @@ -24,6 +27,7 @@ using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Chat; using osu.Game.Overlays; using osu.Game.Overlays.Mods; +using osu.Game.Overlays.Notifications; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu; @@ -413,6 +417,55 @@ namespace osu.Game.Tests.Visual.SongSelect AddUntilStep("no selection", () => songSelect.Carousel.SelectedBeatmapInfo == null); } + [Test] + public void TestSelectionRetainedOnBeatmapUpdate() + { + createSongSelect(); + changeRuleset(0); + + Live original = null!; + int originalOnlineSetID = 0; + + AddStep(@"Sort by artist", () => config.SetValue(OsuSetting.SongSelectSortingMode, SortMode.Artist)); + + AddStep("import original", () => + { + original = manager.Import(new ImportTask(TestResources.GetQuickTestBeatmapForImport())).GetResultSafely(); + originalOnlineSetID = original!.Value.OnlineID; + }); + + // This will move the beatmap set to a different location in the carousel. + AddStep("Update original with bogus info", () => + { + original.PerformWrite(set => + { + foreach (var beatmap in set.Beatmaps) + { + beatmap.Metadata.Artist = "ZZZZZ"; + beatmap.OnlineID = 12804; + } + }); + }); + + AddRepeatStep("import other beatmaps", () => + { + var testBeatmapSetInfo = TestResources.CreateTestBeatmapSetInfo(); + + foreach (var beatmap in testBeatmapSetInfo.Beatmaps) + beatmap.Metadata.Artist = ((char)RNG.Next('A', 'Z')).ToString(); + + manager.Import(testBeatmapSetInfo); + }, 10); + + AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmapInfo?.BeatmapSet?.OnlineID == originalOnlineSetID); + + Task> updateTask = null!; + AddStep("update beatmap", () => updateTask = manager.ImportAsUpdate(new ProgressNotification(), new ImportTask(TestResources.GetQuickTestBeatmapForImport()), original.Value)); + AddUntilStep("wait for update completion", () => updateTask.IsCompleted); + + AddUntilStep("retained selection", () => songSelect.Carousel.SelectedBeatmapInfo?.BeatmapSet?.OnlineID == originalOnlineSetID); + } + [Test] public void TestPresentNewRulesetNewBeatmap() { From 3ff2058975a72aa69b2d2a7d39fa0ec4be2fe7d0 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 29 Aug 2022 09:23:53 +0300 Subject: [PATCH 158/420] Fix back-to-front fallback comparison in `HitObjectOrderedSelectionContainer` --- .../Compose/Components/HitObjectOrderedSelectionContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/HitObjectOrderedSelectionContainer.cs b/osu.Game/Screens/Edit/Compose/Components/HitObjectOrderedSelectionContainer.cs index 06af232111..e3934025e2 100644 --- a/osu.Game/Screens/Edit/Compose/Components/HitObjectOrderedSelectionContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/HitObjectOrderedSelectionContainer.cs @@ -64,7 +64,7 @@ namespace osu.Game.Screens.Edit.Compose.Components if (result != 0) return result; } - return CompareReverseChildID(y, x); + return CompareReverseChildID(x, y); } protected override void Dispose(bool isDisposing) From b2e80ca7f0314f7478b340309632a900f5119ace Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 29 Aug 2022 15:27:19 +0900 Subject: [PATCH 159/420] Don't include misses in failed score statistics --- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 5b21caee84..547b132345 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -468,22 +468,6 @@ namespace osu.Game.Rulesets.Scoring score.Passed = false; Rank.Value = ScoreRank.F; - Debug.Assert(maximumResultCounts != null); - - if (maximumResultCounts.TryGetValue(HitResult.LargeTickHit, out int maximumLargeTick)) - scoreResultCounts[HitResult.LargeTickMiss] = maximumLargeTick - scoreResultCounts.GetValueOrDefault(HitResult.LargeTickHit); - - if (maximumResultCounts.TryGetValue(HitResult.SmallTickHit, out int maximumSmallTick)) - scoreResultCounts[HitResult.SmallTickMiss] = maximumSmallTick - scoreResultCounts.GetValueOrDefault(HitResult.SmallTickHit); - - int maximumBonusOrIgnore = maximumResultCounts.Where(kvp => kvp.Key.IsBonus() || kvp.Key == HitResult.IgnoreHit).Sum(kvp => kvp.Value); - int currentBonusOrIgnore = scoreResultCounts.Where(kvp => kvp.Key.IsBonus() || kvp.Key == HitResult.IgnoreHit).Sum(kvp => kvp.Value); - scoreResultCounts[HitResult.IgnoreMiss] = maximumBonusOrIgnore - currentBonusOrIgnore; - - int maximumBasic = maximumResultCounts.SingleOrDefault(kvp => kvp.Key.IsBasic()).Value; - int currentBasic = scoreResultCounts.Where(kvp => kvp.Key.IsBasic() && kvp.Key != HitResult.Miss).Sum(kvp => kvp.Value); - scoreResultCounts[HitResult.Miss] = maximumBasic - currentBasic; - PopulateScore(score); } From 423f6f90f2d28bf81ff5096b4b3a8156e7ad9caa Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 22 Aug 2022 21:31:30 +0900 Subject: [PATCH 160/420] Remove async calls from ScoreManager --- .../TestScenePlayerLocalScoreImport.cs | 2 +- .../SongSelect/TestSceneBeatmapLeaderboard.cs | 2 +- .../SongSelect/TestSceneTopLocalRank.cs | 2 +- .../TestSceneDeleteLocalScore.cs | 2 +- osu.Game/OsuGameBase.cs | 2 +- .../BeatmapSet/Scores/ScoresContainer.cs | 28 ++--- osu.Game/Scoring/ScoreManager.cs | 101 ++---------------- .../Playlists/PlaylistsResultsScreen.cs | 33 +++--- .../Expanded/ExpandedPanelMiddleContent.cs | 4 +- osu.Game/Screens/Ranking/ScorePanelList.cs | 45 ++++---- .../Select/Leaderboards/BeatmapLeaderboard.cs | 28 ++--- 11 files changed, 62 insertions(+), 187 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs index ddb585a73c..f3e436e31f 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs @@ -37,7 +37,7 @@ namespace osu.Game.Tests.Visual.Gameplay { Dependencies.Cache(rulesets = new RealmRulesetStore(Realm)); Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, null, audio, Resources, host, Beatmap.Default)); - Dependencies.Cache(new ScoreManager(rulesets, () => beatmaps, LocalStorage, Realm, Scheduler, API)); + Dependencies.Cache(new ScoreManager(rulesets, () => beatmaps, LocalStorage, Realm, API)); Dependencies.Cache(Realm); } diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs index aeb30c94e1..07da1790c8 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs @@ -47,7 +47,7 @@ namespace osu.Game.Tests.Visual.SongSelect dependencies.Cache(rulesetStore = new RealmRulesetStore(Realm)); dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, Realm, null, dependencies.Get(), Resources, dependencies.Get(), Beatmap.Default)); - dependencies.Cache(scoreManager = new ScoreManager(rulesetStore, () => beatmapManager, LocalStorage, Realm, Scheduler, API)); + dependencies.Cache(scoreManager = new ScoreManager(rulesetStore, () => beatmapManager, LocalStorage, Realm, API)); Dependencies.Cache(Realm); return dependencies; diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneTopLocalRank.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneTopLocalRank.cs index 72d78ededb..086af3084d 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneTopLocalRank.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneTopLocalRank.cs @@ -32,7 +32,7 @@ namespace osu.Game.Tests.Visual.SongSelect { Dependencies.Cache(rulesets = new RealmRulesetStore(Realm)); Dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, Realm, null, audio, Resources, host, Beatmap.Default)); - Dependencies.Cache(scoreManager = new ScoreManager(rulesets, () => beatmapManager, LocalStorage, Realm, Scheduler, API)); + Dependencies.Cache(scoreManager = new ScoreManager(rulesets, () => beatmapManager, LocalStorage, Realm, API)); Dependencies.Cache(Realm); beatmapManager.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely(); diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs index 3beade9d4f..db380cfdb7 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs @@ -73,7 +73,7 @@ namespace osu.Game.Tests.Visual.UserInterface dependencies.Cache(new RealmRulesetStore(Realm)); dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, Realm, null, dependencies.Get(), Resources, dependencies.Get(), Beatmap.Default)); - dependencies.Cache(scoreManager = new ScoreManager(dependencies.Get(), () => beatmapManager, LocalStorage, Realm, Scheduler, API)); + dependencies.Cache(scoreManager = new ScoreManager(dependencies.Get(), () => beatmapManager, LocalStorage, Realm, API)); Dependencies.Cache(Realm); return dependencies; diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 8d5c58d5f0..f87f95efd5 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -273,7 +273,7 @@ namespace osu.Game dependencies.Cache(difficultyCache = new BeatmapDifficultyCache()); // ordering is important here to ensure foreign keys rules are not broken in ModelStore.Cleanup() - dependencies.Cache(ScoreManager = new ScoreManager(RulesetStore, () => BeatmapManager, Storage, realm, Scheduler, API, difficultyCache, LocalConfig)); + dependencies.Cache(ScoreManager = new ScoreManager(RulesetStore, () => BeatmapManager, Storage, realm, API, LocalConfig)); dependencies.Cache(BeatmapManager = new BeatmapManager(Storage, realm, API, Audio, Resources, Host, defaultBeatmap, difficultyCache, performOnlineLookups: true)); diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs index e50fc356eb..53818bbee3 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs @@ -6,10 +6,8 @@ using System.Diagnostics; using System.Linq; using System.Threading; -using System.Threading.Tasks; 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; @@ -87,27 +85,19 @@ namespace osu.Game.Overlays.BeatmapSet.Scores MD5Hash = apiBeatmap.MD5Hash }; - scoreManager.OrderByTotalScoreAsync(value.Scores.Select(s => s.ToScoreInfo(rulesets, beatmapInfo)).ToArray(), loadCancellationSource.Token) - .ContinueWith(task => Schedule(() => - { - if (loadCancellationSource.IsCancellationRequested) - return; + var scores = scoreManager.OrderByTotalScore(value.Scores.Select(s => s.ToScoreInfo(rulesets, beatmapInfo))).ToArray(); + var topScore = scores.First(); - var scores = task.GetResultSafely(); + scoreTable.DisplayScores(scores, apiBeatmap.Status.GrantsPerformancePoints()); + scoreTable.Show(); - var topScore = scores.First(); + var userScore = value.UserScore; + var userScoreInfo = userScore?.Score.ToScoreInfo(rulesets, beatmapInfo); - scoreTable.DisplayScores(scores, apiBeatmap.Status.GrantsPerformancePoints()); - scoreTable.Show(); + topScoresContainer.Add(new DrawableTopScore(topScore)); - var userScore = value.UserScore; - var userScoreInfo = userScore?.Score.ToScoreInfo(rulesets, beatmapInfo); - - topScoresContainer.Add(new DrawableTopScore(topScore)); - - if (userScoreInfo != null && userScoreInfo.OnlineID != topScore.OnlineID) - topScoresContainer.Add(new DrawableTopScore(userScoreInfo, userScore.Position)); - }), TaskContinuationOptions.OnlyOnRanToCompletion); + if (userScoreInfo != null && userScoreInfo.OnlineID != topScore.OnlineID) + topScoresContainer.Add(new DrawableTopScore(userScoreInfo, userScore.Position)); }); } diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index ecd37c761c..7204b5a281 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -11,10 +11,7 @@ using System.Threading; using System.Threading.Tasks; using JetBrains.Annotations; using osu.Framework.Bindables; -using osu.Framework.Extensions; -using osu.Framework.Logging; using osu.Framework.Platform; -using osu.Framework.Threading; using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Database; @@ -28,17 +25,12 @@ namespace osu.Game.Scoring { public class ScoreManager : ModelManager, IModelImporter { - private readonly Scheduler scheduler; - private readonly BeatmapDifficultyCache difficultyCache; private readonly OsuConfigManager configManager; private readonly ScoreImporter scoreImporter; - public ScoreManager(RulesetStore rulesets, Func beatmaps, Storage storage, RealmAccess realm, Scheduler scheduler, IAPIProvider api, - BeatmapDifficultyCache difficultyCache = null, OsuConfigManager configManager = null) + public ScoreManager(RulesetStore rulesets, Func beatmaps, Storage storage, RealmAccess realm, IAPIProvider api, OsuConfigManager configManager = null) : base(storage, realm) { - this.scheduler = scheduler; - this.difficultyCache = difficultyCache; this.configManager = configManager; scoreImporter = new ScoreImporter(rulesets, beatmaps, storage, realm, api) @@ -63,27 +55,13 @@ namespace osu.Game.Scoring /// Orders an array of s by total score. /// /// The array of s to reorder. - /// A to cancel the process. /// The given ordered by decreasing total score. - public async Task OrderByTotalScoreAsync(ScoreInfo[] scores, CancellationToken cancellationToken = default) + public IEnumerable OrderByTotalScore(IEnumerable scores) { - if (difficultyCache != null) - { - // Compute difficulties asynchronously first to prevent blocking via the GetTotalScore() call below. - foreach (var s in scores) - { - await difficultyCache.GetDifficultyAsync(s.BeatmapInfo, s.Ruleset, s.Mods, cancellationToken).ConfigureAwait(false); - cancellationToken.ThrowIfCancellationRequested(); - } - } - - long[] totalScores = await Task.WhenAll(scores.Select(s => GetTotalScoreAsync(s, cancellationToken: cancellationToken))).ConfigureAwait(false); - - return scores.Select((score, index) => (score, totalScore: totalScores[index])) + return scores.Select((score, index) => (score, totalScore: GetTotalScore(score))) .OrderByDescending(g => g.totalScore) .ThenBy(g => g.score.OnlineID) - .Select(g => g.score) - .ToArray(); + .Select(g => g.score); } /// @@ -106,44 +84,18 @@ namespace osu.Game.Scoring /// The bindable containing the formatted total score string. public Bindable GetBindableTotalScoreString([NotNull] ScoreInfo score) => new TotalScoreStringBindable(GetBindableTotalScore(score)); - /// - /// Retrieves the total score of a in the given . - /// The score is returned in a callback that is run on the update thread. - /// - /// The to calculate the total score of. - /// The callback to be invoked with the total score. - /// The to return the total score as. - /// A to cancel the process. - public void GetTotalScore([NotNull] ScoreInfo score, [NotNull] Action callback, ScoringMode mode = ScoringMode.Standardised, CancellationToken cancellationToken = default) - { - GetTotalScoreAsync(score, mode, cancellationToken) - .ContinueWith(task => scheduler.Add(() => - { - if (!cancellationToken.IsCancellationRequested) - callback(task.GetResultSafely()); - }), TaskContinuationOptions.OnlyOnRanToCompletion); - } - /// /// Retrieves the total score of a in the given . /// /// The to calculate the total score of. /// The to return the total score as. - /// A to cancel the process. /// The total score. - public async Task GetTotalScoreAsync([NotNull] ScoreInfo score, ScoringMode mode = ScoringMode.Standardised, CancellationToken cancellationToken = default) + public long GetTotalScore([NotNull] ScoreInfo score, ScoringMode mode = ScoringMode.Standardised) { // TODO: This is required for playlist aggregate scores. They should likely not be getting here in the first place. if (string.IsNullOrEmpty(score.BeatmapInfo.MD5Hash)) return score.TotalScore; - int? beatmapMaxCombo = await GetMaximumAchievableComboAsync(score, cancellationToken).ConfigureAwait(false); - if (beatmapMaxCombo == null) - return score.TotalScore; - - if (beatmapMaxCombo == 0) - return 0; - var ruleset = score.Ruleset.CreateInstance(); var scoreProcessor = ruleset.CreateScoreProcessor(); scoreProcessor.Mods.Value = score.Mods; @@ -155,33 +107,9 @@ namespace osu.Game.Scoring /// Retrieves the maximum achievable combo for the provided score. /// /// The to compute the maximum achievable combo for. - /// A to cancel the process. /// The maximum achievable combo. A return value indicates the difficulty cache has failed to retrieve the combo. - public async Task GetMaximumAchievableComboAsync([NotNull] ScoreInfo score, CancellationToken cancellationToken = default) + public int GetMaximumAchievableCombo([NotNull] ScoreInfo score) { - if (score.IsLegacyScore) - { - // 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. -#pragma warning disable CS0618 - if (score.BeatmapInfo.MaxCombo != null) - return score.BeatmapInfo.MaxCombo.Value; -#pragma warning restore CS0618 - - if (difficultyCache == null) - return null; - - // We can compute the max combo locally after the async beatmap difficulty computation. - var difficulty = await difficultyCache.GetDifficultyAsync(score.BeatmapInfo, score.Ruleset, score.Mods, cancellationToken).ConfigureAwait(false); - - if (difficulty == null) - Logger.Log($"Couldn't get beatmap difficulty for beatmap {score.BeatmapInfo.OnlineID}"); - - return difficulty?.MaxCombo; - } - - // This is guaranteed to be a non-legacy 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. return Enum.GetValues(typeof(HitResult)).OfType().Where(r => r.AffectsCombo()).Select(r => score.Statistics.GetValueOrDefault(r)).Sum(); } @@ -191,10 +119,6 @@ namespace osu.Game.Scoring private class TotalScoreBindable : Bindable { private readonly Bindable scoringMode = new Bindable(); - private readonly ScoreInfo score; - private readonly ScoreManager scoreManager; - - private CancellationTokenSource difficultyCalculationCancellationSource; /// /// Creates a new . @@ -204,19 +128,8 @@ namespace osu.Game.Scoring /// The config. public TotalScoreBindable(ScoreInfo score, ScoreManager scoreManager, OsuConfigManager configManager) { - this.score = score; - this.scoreManager = scoreManager; - configManager?.BindWith(OsuSetting.ScoreDisplayMode, scoringMode); - scoringMode.BindValueChanged(onScoringModeChanged, true); - } - - private void onScoringModeChanged(ValueChangedEvent mode) - { - difficultyCalculationCancellationSource?.Cancel(); - difficultyCalculationCancellationSource = new CancellationTokenSource(); - - scoreManager.GetTotalScore(score, s => Value = s, mode.NewValue, difficultyCalculationCancellationSource.Token); + scoringMode.BindValueChanged(mode => Value = scoreManager.GetTotalScore(score, mode.NewValue), true); } } diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsResultsScreen.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsResultsScreen.cs index 53b38962ac..41633c34ce 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsResultsScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsResultsScreen.cs @@ -180,31 +180,26 @@ namespace osu.Game.Screens.OnlinePlay.Playlists /// 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) + private void performSuccessCallback([NotNull] Action> callback, [NotNull] List scores, [CanBeNull] MultiplayerScores pivot = null) => Schedule(() => { - var scoreInfos = scores.Select(s => s.CreateScoreInfo(rulesets, playlistItem, Beatmap.Value.BeatmapInfo)).ToArray(); + var scoreInfos = scoreManager.OrderByTotalScore(scores.Select(s => s.CreateScoreInfo(rulesets, playlistItem, Beatmap.Value.BeatmapInfo))).ToArray(); - // Score panels calculate total score before displaying, which can take some time. In order to count that calculation as part of the loading spinner display duration, - // calculate the total scores locally before invoking the success callback. - scoreManager.OrderByTotalScoreAsync(scoreInfos).ContinueWith(_ => Schedule(() => + // 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) { - // 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(() => { - Schedule(() => - { - // Prefer selecting the local user's score, or otherwise default to the first visible score. - SelectedScore.Value = scoreInfos.FirstOrDefault(s => s.User.OnlineID == api.LocalUser.Value.Id) ?? scoreInfos.FirstOrDefault(); - }); - } + // Prefer selecting the local user's score, or otherwise default to the first visible score. + SelectedScore.Value = scoreInfos.FirstOrDefault(s => s.User.OnlineID == 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.OnlineID != Score?.OnlineID)); + // Invoke callback to add the scores. Exclude the user's current score which was added previously. + callback.Invoke(scoreInfos.Where(s => s.OnlineID != Score?.OnlineID)); - hideLoadingSpinners(pivot); - })); - } + hideLoadingSpinners(pivot); + }); private void hideLoadingSpinners([CanBeNull] MultiplayerScores pivot = null) { diff --git a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs index 0f202e5e08..b496f4242d 100644 --- a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs +++ b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs @@ -67,12 +67,10 @@ namespace osu.Game.Screens.Ranking.Expanded var metadata = beatmap.BeatmapSet?.Metadata ?? beatmap.Metadata; string creator = metadata.Author.Username; - int? beatmapMaxCombo = scoreManager.GetMaximumAchievableComboAsync(score).GetResultSafely(); - var topStatistics = new List { new AccuracyStatistic(score.Accuracy), - new ComboStatistic(score.MaxCombo, beatmapMaxCombo), + new ComboStatistic(score.MaxCombo, scoreManager.GetMaximumAchievableCombo(score)), new PerformanceStatistic(score), }; diff --git a/osu.Game/Screens/Ranking/ScorePanelList.cs b/osu.Game/Screens/Ranking/ScorePanelList.cs index 4f9e61a4a1..46f9efd126 100644 --- a/osu.Game/Screens/Ranking/ScorePanelList.cs +++ b/osu.Game/Screens/Ranking/ScorePanelList.cs @@ -8,11 +8,9 @@ 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.Bindables; -using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Events; @@ -151,32 +149,27 @@ namespace osu.Game.Screens.Ranking var score = trackingContainer.Panel.Score; - // Calculating score can take a while in extreme scenarios, so only display scores after the process completes. - scoreManager.GetTotalScoreAsync(score) - .ContinueWith(task => Schedule(() => - { - flow.SetLayoutPosition(trackingContainer, task.GetResultSafely()); + flow.SetLayoutPosition(trackingContainer, scoreManager.GetTotalScore(score)); - trackingContainer.Show(); + trackingContainer.Show(); - if (SelectedScore.Value?.Equals(score) == true) - { - SelectedScore.TriggerChange(); - } - else - { - // We want the scroll position to remain relative to the expanded panel. When a new panel is added after the expanded panel, nothing needs to be done. - // But when a panel is added before the expanded panel, we need to offset the scroll position by the width of the new panel. - if (expandedPanel != null && flow.GetPanelIndex(score) < flow.GetPanelIndex(expandedPanel.Score)) - { - // A somewhat hacky property is used here because we need to: - // 1) Scroll after the scroll container's visible range is updated. - // 2) Scroll before the scroll container's scroll position is updated. - // Without this, we would have a 1-frame positioning error which looks very jarring. - scroll.InstantScrollTarget = (scroll.InstantScrollTarget ?? scroll.Target) + ScorePanel.CONTRACTED_WIDTH + panel_spacing; - } - } - }), TaskContinuationOptions.OnlyOnRanToCompletion); + if (SelectedScore.Value?.Equals(score) == true) + { + SelectedScore.TriggerChange(); + } + else + { + // We want the scroll position to remain relative to the expanded panel. When a new panel is added after the expanded panel, nothing needs to be done. + // But when a panel is added before the expanded panel, we need to offset the scroll position by the width of the new panel. + if (expandedPanel != null && flow.GetPanelIndex(score) < flow.GetPanelIndex(expandedPanel.Score)) + { + // A somewhat hacky property is used here because we need to: + // 1) Scroll after the scroll container's visible range is updated. + // 2) Scroll before the scroll container's scroll position is updated. + // Without this, we would have a 1-frame positioning error which looks very jarring. + scroll.InstantScrollTarget = (scroll.InstantScrollTarget ?? scroll.Target) + ScorePanel.CONTRACTED_WIDTH + panel_spacing; + } + } } /// diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs index b497943dfa..343b815e9f 100644 --- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs @@ -8,10 +8,8 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Threading; -using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Bindables; -using osu.Framework.Extensions; using osu.Game.Beatmaps; using osu.Game.Database; using osu.Game.Extensions; @@ -150,17 +148,12 @@ namespace osu.Game.Screens.Select.Leaderboards var req = new GetScoresRequest(fetchBeatmapInfo, fetchRuleset, Scope, requestMods); - req.Success += r => + req.Success += r => Schedule(() => { - scoreManager.OrderByTotalScoreAsync(r.Scores.Select(s => s.ToScoreInfo(rulesets, fetchBeatmapInfo)).ToArray(), cancellationToken) - .ContinueWith(task => Schedule(() => - { - if (cancellationToken.IsCancellationRequested) - return; - - SetScores(task.GetResultSafely(), r.UserScore?.CreateScoreInfo(rulesets, fetchBeatmapInfo)); - }), TaskContinuationOptions.OnlyOnRanToCompletion); - }; + SetScores( + scoreManager.OrderByTotalScore(r.Scores.Select(s => s.ToScoreInfo(rulesets, fetchBeatmapInfo))), + r.UserScore?.CreateScoreInfo(rulesets, fetchBeatmapInfo)); + }); return req; } @@ -213,16 +206,9 @@ namespace osu.Game.Screens.Select.Leaderboards scores = scores.Where(s => s.Mods.Any(m => selectedMods.Contains(m.Acronym))); } - scores = scores.Detach(); + scores = scoreManager.OrderByTotalScore(scores.Detach()); - scoreManager.OrderByTotalScoreAsync(scores.ToArray(), cancellationToken) - .ContinueWith(ordered => Schedule(() => - { - if (cancellationToken.IsCancellationRequested) - return; - - SetScores(ordered.GetResultSafely()); - }), TaskContinuationOptions.OnlyOnRanToCompletion); + Schedule(() => SetScores(scores)); } } From d75543ad68acc74546d9e4ab4d1c2066efb52d44 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 29 Aug 2022 15:36:10 +0900 Subject: [PATCH 161/420] Simplify GetMaximumAchievableCombo further --- osu.Game/Scoring/ScoreManager.cs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index 7204b5a281..fd600f4864 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -107,11 +107,8 @@ namespace osu.Game.Scoring /// Retrieves the maximum achievable combo for the provided score. /// /// The to compute the maximum achievable combo for. - /// The maximum achievable combo. A return value indicates the difficulty cache has failed to retrieve the combo. - public int GetMaximumAchievableCombo([NotNull] ScoreInfo score) - { - return Enum.GetValues(typeof(HitResult)).OfType().Where(r => r.AffectsCombo()).Select(r => score.Statistics.GetValueOrDefault(r)).Sum(); - } + /// The maximum achievable combo. + public int GetMaximumAchievableCombo([NotNull] ScoreInfo score) => score.MaximumStatistics.Where(kvp => kvp.Key.AffectsCombo()).Sum(kvp => kvp.Value); /// /// Provides the total score of a . Responds to changes in the currently-selected . From 81ac0daba8cd9574fd08821cbf8fe767d0969a1b Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 29 Aug 2022 15:51:12 +0900 Subject: [PATCH 162/420] Update xmldoc --- 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 547b132345..cd01ae7eff 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -458,7 +458,7 @@ namespace osu.Game.Rulesets.Scoring } /// - /// Populates the given score with remaining statistics as "missed" and marks it with rank. + /// Populates a failed score, marking it with the rank. /// public void FailScore(ScoreInfo score) { From a215d009fed8bae6f3566e08f19f3de753c098e1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 26 Aug 2022 15:19:05 +0900 Subject: [PATCH 163/420] Update `Remove`/`RemoveRange`/`RemoveAll` calls in line with framework changes --- osu.Game.Rulesets.Catch/UI/Catcher.cs | 6 +++--- osu.Game.Rulesets.Catch/UI/CatcherTrailDisplay.cs | 6 +++--- osu.Game.Rulesets.Mania/Mods/ManiaModPlayfieldCover.cs | 2 +- osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyBodyPiece.cs | 2 +- osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs | 4 ++-- .../Objects/Drawables/DrawableOsuHitObject.cs | 2 +- .../Objects/Drawables/DrawableTaikoHitObject.cs | 6 +++--- .../Visual/Background/TestSceneSeasonalBackgroundLoader.cs | 2 +- .../Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs | 2 +- osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorHost.cs | 2 +- osu.Game.Tests/Visual/Gameplay/TestSceneStoryboard.cs | 4 ++-- osu.Game.Tests/Visual/Menus/TestSceneToolbar.cs | 4 ++-- osu.Game.Tests/Visual/Navigation/TestSettingsMigration.cs | 2 +- .../Visual/Online/TestSceneLeaderboardModSelector.cs | 2 +- .../Visual/SongSelect/TestSceneBeatmapMetadataDisplay.cs | 2 +- .../Visual/UserInterface/TestSceneLogoTrackingContainer.cs | 2 +- .../Screens/TestSceneGameplayScreen.cs | 2 +- osu.Game.Tournament/Screens/Drawings/Components/Group.cs | 2 +- .../Screens/Drawings/Components/ScrollingTeamContainer.cs | 4 ++-- .../Screens/Drawings/Components/VisualiserContainer.cs | 2 +- .../Screens/Editors/TournamentEditorScreen.cs | 2 +- osu.Game/Graphics/Containers/SectionsContainer.cs | 2 +- .../Graphics/Containers/SelectionCycleFillFlowContainer.cs | 2 +- osu.Game/Graphics/UserInterface/BarGraph.cs | 2 +- osu.Game/Graphics/UserInterface/TwoLayerButton.cs | 4 ++-- osu.Game/Overlays/Chat/ChannelList/ChannelList.cs | 2 +- osu.Game/Overlays/Chat/DrawableChannel.cs | 2 +- osu.Game/Rulesets/UI/JudgementContainer.cs | 2 +- .../Screens/Edit/Compose/Components/BlueprintContainer.cs | 2 +- .../Components/HitObjectOrderedSelectionContainer.cs | 4 ++-- .../Components/Timeline/TimelineBlueprintContainer.cs | 2 +- osu.Game/Screens/Menu/IntroTriangles.cs | 3 +-- .../Lounge/Components/DrawableRoomParticipantsList.cs | 4 ++-- .../Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs | 2 +- osu.Game/Screens/Ranking/ResultsScreen.cs | 2 +- osu.Game/Skinning/SkinnableSound.cs | 2 +- osu.Game/Skinning/SkinnableTargetContainer.cs | 2 +- osu.Game/Tests/Visual/OsuGameTestScene.cs | 5 +---- osu.Game/Tests/Visual/PlacementBlueprintTestScene.cs | 2 +- 39 files changed, 52 insertions(+), 56 deletions(-) diff --git a/osu.Game.Rulesets.Catch/UI/Catcher.cs b/osu.Game.Rulesets.Catch/UI/Catcher.cs index efc841dfac..61fefcfd99 100644 --- a/osu.Game.Rulesets.Catch/UI/Catcher.cs +++ b/osu.Game.Rulesets.Catch/UI/Catcher.cs @@ -271,8 +271,8 @@ namespace osu.Game.Rulesets.Catch.UI SetHyperDashState(); } - caughtObjectContainer.RemoveAll(d => d.HitObject == drawableObject.HitObject); - droppedObjectTarget.RemoveAll(d => d.HitObject == drawableObject.HitObject); + caughtObjectContainer.RemoveAll(d => d.HitObject == drawableObject.HitObject, false); + droppedObjectTarget.RemoveAll(d => d.HitObject == drawableObject.HitObject, false); } /// @@ -430,7 +430,7 @@ namespace osu.Game.Rulesets.Catch.UI { var droppedObject = getDroppedObject(caughtObject); - caughtObjectContainer.Remove(caughtObject); + caughtObjectContainer.Remove(caughtObject, false); droppedObjectTarget.Add(droppedObject); diff --git a/osu.Game.Rulesets.Catch/UI/CatcherTrailDisplay.cs b/osu.Game.Rulesets.Catch/UI/CatcherTrailDisplay.cs index 6ab6a59293..d255937fed 100644 --- a/osu.Game.Rulesets.Catch/UI/CatcherTrailDisplay.cs +++ b/osu.Game.Rulesets.Catch/UI/CatcherTrailDisplay.cs @@ -93,15 +93,15 @@ namespace osu.Game.Rulesets.Catch.UI switch (entry.Animation) { case CatcherTrailAnimation.Dashing: - dashTrails.Remove(drawable); + dashTrails.Remove(drawable, false); break; case CatcherTrailAnimation.HyperDashing: - hyperDashTrails.Remove(drawable); + hyperDashTrails.Remove(drawable, false); break; case CatcherTrailAnimation.HyperDashAfterImage: - hyperDashAfterImages.Remove(drawable); + hyperDashAfterImages.Remove(drawable, false); break; } } diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModPlayfieldCover.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModPlayfieldCover.cs index 3c24e91d54..6a94e5d371 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModPlayfieldCover.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModPlayfieldCover.cs @@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Mania.Mods HitObjectContainer hoc = column.HitObjectArea.HitObjectContainer; Container hocParent = (Container)hoc.Parent; - hocParent.Remove(hoc); + hocParent.Remove(hoc, false); hocParent.Add(new PlayfieldCoveringWrapper(hoc).With(c => { c.RelativeSizeAxes = Axes.Both; diff --git a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyBodyPiece.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyBodyPiece.cs index 4b6f364831..49ba503cb5 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyBodyPiece.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyBodyPiece.cs @@ -155,7 +155,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy else { lightContainer.FadeOut(120) - .OnComplete(d => Column.TopLevelContainer.Remove(d)); + .OnComplete(d => Column.TopLevelContainer.Remove(d, false)); } } diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs index 305678bb62..1e625cd4e6 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs @@ -195,7 +195,7 @@ namespace osu.Game.Rulesets.Osu.Tests { var drawableObject = getFunc.Invoke(); - hitObjectContainer.Remove(drawableObject); + hitObjectContainer.Remove(drawableObject, false); followPointRenderer.RemoveFollowPoints(drawableObject.HitObject); }); } @@ -212,7 +212,7 @@ namespace osu.Game.Rulesets.Osu.Tests else targetTime = getObject(hitObjectContainer.Count - 1).HitObject.StartTime + 1; - hitObjectContainer.Remove(toReorder); + hitObjectContainer.Remove(toReorder, false); toReorder.HitObject.StartTime = targetTime; hitObjectContainer.Add(toReorder); }); diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs index 360b28f69f..aedb62dd24 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs @@ -77,7 +77,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables // This is a bit ugly but we don't have the concept of InternalContent so it'll have to do for now. (https://github.com/ppy/osu-framework/issues/1690) protected override void AddInternal(Drawable drawable) => shakeContainer.Add(drawable); protected override void ClearInternal(bool disposeChildren = true) => shakeContainer.Clear(disposeChildren); - protected override bool RemoveInternal(Drawable drawable) => shakeContainer.Remove(drawable); + protected override bool RemoveInternal(Drawable drawable) => shakeContainer.Remove(drawable, true); protected sealed override double InitialLifetimeOffset => HitObject.TimePreempt; diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs index 9bbd3670fa..c0c80eaa4a 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs @@ -56,7 +56,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables isProxied = true; - nonProxiedContent.Remove(Content); + nonProxiedContent.Remove(Content, false); proxiedContent.Add(Content); } @@ -70,7 +70,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables isProxied = false; - proxiedContent.Remove(Content); + proxiedContent.Remove(Content, false); nonProxiedContent.Add(Content); } @@ -141,7 +141,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables Size = BaseSize = new Vector2(TaikoHitObject.DEFAULT_SIZE); if (MainPiece != null) - Content.Remove(MainPiece); + Content.Remove(MainPiece, true); Content.Add(MainPiece = CreateMainPiece()); } diff --git a/osu.Game.Tests/Visual/Background/TestSceneSeasonalBackgroundLoader.cs b/osu.Game.Tests/Visual/Background/TestSceneSeasonalBackgroundLoader.cs index cce7ae1922..c9920cd01f 100644 --- a/osu.Game.Tests/Visual/Background/TestSceneSeasonalBackgroundLoader.cs +++ b/osu.Game.Tests/Visual/Background/TestSceneSeasonalBackgroundLoader.cs @@ -171,7 +171,7 @@ namespace osu.Game.Tests.Visual.Background => AddStep("create loader", () => { if (backgroundLoader != null) - Remove(backgroundLoader); + Remove(backgroundLoader, true); Add(backgroundLoader = new SeasonalBackgroundLoader()); }); diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs index d6c49b026e..5c7321fb24 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs @@ -87,7 +87,7 @@ namespace osu.Game.Tests.Visual.Gameplay Add(expectedComponentsAdjustmentContainer); expectedComponentsAdjustmentContainer.UpdateSubTree(); var expectedInfo = expectedComponentsContainer.CreateSkinnableInfo(); - Remove(expectedComponentsAdjustmentContainer); + Remove(expectedComponentsAdjustmentContainer, true); return almostEqual(actualInfo, expectedInfo); } diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorHost.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorHost.cs index 9ad8ac086c..083be3539d 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorHost.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorHost.cs @@ -63,7 +63,7 @@ namespace osu.Game.Tests.Visual.Gameplay { base.TearDownSteps(); AddStep("stop watching user", () => spectatorClient.StopWatchingUser(dummy_user_id)); - AddStep("remove test spectator client", () => Remove(spectatorClient)); + AddStep("remove test spectator client", () => Remove(spectatorClient, false)); } } } diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboard.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboard.cs index 30c2790fb4..2ec675874b 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboard.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboard.cs @@ -91,7 +91,7 @@ namespace osu.Game.Tests.Visual.Gameplay private void loadStoryboard(IWorkingBeatmap working) { if (storyboard != null) - storyboardContainer.Remove(storyboard); + storyboardContainer.Remove(storyboard, true); var decoupledClock = new DecoupleableInterpolatingFramedClock { IsCoupled = true }; storyboardContainer.Clock = decoupledClock; @@ -106,7 +106,7 @@ namespace osu.Game.Tests.Visual.Gameplay private void loadStoryboardNoVideo() { if (storyboard != null) - storyboardContainer.Remove(storyboard); + storyboardContainer.Remove(storyboard, true); var decoupledClock = new DecoupleableInterpolatingFramedClock { IsCoupled = true }; storyboardContainer.Clock = decoupledClock; diff --git a/osu.Game.Tests/Visual/Menus/TestSceneToolbar.cs b/osu.Game.Tests/Visual/Menus/TestSceneToolbar.cs index 90e218675c..4473f315b9 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneToolbar.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneToolbar.cs @@ -62,8 +62,8 @@ namespace osu.Game.Tests.Visual.Menus [SetUp] public void SetUp() => Schedule(() => { - Remove(nowPlayingOverlay); - Remove(volumeOverlay); + Remove(nowPlayingOverlay, false); + Remove(volumeOverlay, false); Children = new Drawable[] { diff --git a/osu.Game.Tests/Visual/Navigation/TestSettingsMigration.cs b/osu.Game.Tests/Visual/Navigation/TestSettingsMigration.cs index f5c7ee2f19..2879536034 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSettingsMigration.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSettingsMigration.cs @@ -34,7 +34,7 @@ namespace osu.Game.Tests.Visual.Navigation AddStep("force save config", () => Game.LocalConfig.Save()); - AddStep("remove game", () => Remove(Game)); + AddStep("remove game", () => Remove(Game, true)); AddStep("create game again", CreateGame); diff --git a/osu.Game.Tests/Visual/Online/TestSceneLeaderboardModSelector.cs b/osu.Game.Tests/Visual/Online/TestSceneLeaderboardModSelector.cs index 10d9a5664e..5579ecedbd 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneLeaderboardModSelector.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneLeaderboardModSelector.cs @@ -60,7 +60,7 @@ namespace osu.Game.Tests.Visual.Online { if (selected.Text == mod.Acronym) { - selectedMods.Remove(selected); + selectedMods.Remove(selected, true); break; } } diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapMetadataDisplay.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapMetadataDisplay.cs index 27b485156c..c42b51c1a6 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapMetadataDisplay.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapMetadataDisplay.cs @@ -98,7 +98,7 @@ namespace osu.Game.Tests.Visual.SongSelect OsuLogo logo = new OsuLogo { Scale = new Vector2(0.15f) }; - Remove(testDifficultyCache); + Remove(testDifficultyCache, false); Children = new Drawable[] { diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneLogoTrackingContainer.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneLogoTrackingContainer.cs index 558ea01a49..d069e742dd 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneLogoTrackingContainer.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneLogoTrackingContainer.cs @@ -259,7 +259,7 @@ namespace osu.Game.Tests.Visual.UserInterface private void removeFacade() { - trackingContainer.Remove(logoFacade); + trackingContainer.Remove(logoFacade, false); visualBox.Colour = Color4.White; moveLogoFacade(); } diff --git a/osu.Game.Tournament.Tests/Screens/TestSceneGameplayScreen.cs b/osu.Game.Tournament.Tests/Screens/TestSceneGameplayScreen.cs index 842324d03d..4fc15c365f 100644 --- a/osu.Game.Tournament.Tests/Screens/TestSceneGameplayScreen.cs +++ b/osu.Game.Tournament.Tests/Screens/TestSceneGameplayScreen.cs @@ -53,7 +53,7 @@ namespace osu.Game.Tournament.Tests.Screens { AddStep("setup screen", () => { - Remove(chat); + Remove(chat, false); Children = new Drawable[] { diff --git a/osu.Game.Tournament/Screens/Drawings/Components/Group.cs b/osu.Game.Tournament/Screens/Drawings/Components/Group.cs index f50abd6e58..0b1a5328ab 100644 --- a/osu.Game.Tournament/Screens/Drawings/Components/Group.cs +++ b/osu.Game.Tournament/Screens/Drawings/Components/Group.cs @@ -93,7 +93,7 @@ namespace osu.Game.Tournament.Screens.Drawings.Components { allTeams.RemoveAll(gt => gt.Team == team); - if (teams.RemoveAll(gt => gt.Team == team) > 0) + if (teams.RemoveAll(gt => gt.Team == team, true) > 0) { TeamsCount--; return true; diff --git a/osu.Game.Tournament/Screens/Drawings/Components/ScrollingTeamContainer.cs b/osu.Game.Tournament/Screens/Drawings/Components/ScrollingTeamContainer.cs index 52c611d323..55555adb80 100644 --- a/osu.Game.Tournament/Screens/Drawings/Components/ScrollingTeamContainer.cs +++ b/osu.Game.Tournament/Screens/Drawings/Components/ScrollingTeamContainer.cs @@ -170,7 +170,7 @@ namespace osu.Game.Tournament.Screens.Drawings.Components availableTeams.Add(team); - RemoveAll(c => c is ScrollingTeam); + RemoveAll(c => c is ScrollingTeam, false); setScrollState(ScrollState.Idle); } @@ -186,7 +186,7 @@ namespace osu.Game.Tournament.Screens.Drawings.Components public void ClearTeams() { availableTeams.Clear(); - RemoveAll(c => c is ScrollingTeam); + RemoveAll(c => c is ScrollingTeam, true); setScrollState(ScrollState.Idle); } diff --git a/osu.Game.Tournament/Screens/Drawings/Components/VisualiserContainer.cs b/osu.Game.Tournament/Screens/Drawings/Components/VisualiserContainer.cs index 15edbb76c1..663162d1ca 100644 --- a/osu.Game.Tournament/Screens/Drawings/Components/VisualiserContainer.cs +++ b/osu.Game.Tournament/Screens/Drawings/Components/VisualiserContainer.cs @@ -57,7 +57,7 @@ namespace osu.Game.Tournament.Screens.Drawings.Components if (allLines.Count == 0) return; - Remove(allLines.First()); + Remove(allLines.First(), true); allLines.Remove(allLines.First()); } diff --git a/osu.Game.Tournament/Screens/Editors/TournamentEditorScreen.cs b/osu.Game.Tournament/Screens/Editors/TournamentEditorScreen.cs index 0fefe6f780..8c55026c67 100644 --- a/osu.Game.Tournament/Screens/Editors/TournamentEditorScreen.cs +++ b/osu.Game.Tournament/Screens/Editors/TournamentEditorScreen.cs @@ -106,7 +106,7 @@ namespace osu.Game.Tournament.Screens.Editors break; case NotifyCollectionChangedAction.Remove: - args.OldItems.Cast().ForEach(i => flow.RemoveAll(d => d.Model == i)); + args.OldItems.Cast().ForEach(i => flow.RemoveAll(d => d.Model == i, true)); break; } }; diff --git a/osu.Game/Graphics/Containers/SectionsContainer.cs b/osu.Game/Graphics/Containers/SectionsContainer.cs index c04a5add89..448ecc6b3d 100644 --- a/osu.Game/Graphics/Containers/SectionsContainer.cs +++ b/osu.Game/Graphics/Containers/SectionsContainer.cs @@ -70,7 +70,7 @@ namespace osu.Game.Graphics.Containers if (value == footer) return; if (footer != null) - scrollContainer.Remove(footer); + scrollContainer.Remove(footer, true); footer = value; if (value == null) return; diff --git a/osu.Game/Graphics/Containers/SelectionCycleFillFlowContainer.cs b/osu.Game/Graphics/Containers/SelectionCycleFillFlowContainer.cs index 94505d2310..2667b8b8e0 100644 --- a/osu.Game/Graphics/Containers/SelectionCycleFillFlowContainer.cs +++ b/osu.Game/Graphics/Containers/SelectionCycleFillFlowContainer.cs @@ -59,7 +59,7 @@ namespace osu.Game.Graphics.Containers drawable.StateChanged += state => selectionChanged(drawable, state); } - public override bool Remove(T drawable) + public override bool Remove(T drawable, bool disposeImmediately) => throw new NotSupportedException($"Cannot remove drawables from {nameof(SelectionCycleFillFlowContainer)}"); private void setSelected(int? value) diff --git a/osu.Game/Graphics/UserInterface/BarGraph.cs b/osu.Game/Graphics/UserInterface/BarGraph.cs index f55875ac58..2e9fd6734f 100644 --- a/osu.Game/Graphics/UserInterface/BarGraph.cs +++ b/osu.Game/Graphics/UserInterface/BarGraph.cs @@ -74,7 +74,7 @@ namespace osu.Game.Graphics.UserInterface } //I'm using ToList() here because Where() returns an Enumerable which can change it's elements afterwards - RemoveRange(Children.Where((_, index) => index >= value.Count()).ToList()); + RemoveRange(Children.Where((_, index) => index >= value.Count()).ToList(), true); } } } diff --git a/osu.Game/Graphics/UserInterface/TwoLayerButton.cs b/osu.Game/Graphics/UserInterface/TwoLayerButton.cs index 144b51d3e8..1b8848f3d5 100644 --- a/osu.Game/Graphics/UserInterface/TwoLayerButton.cs +++ b/osu.Game/Graphics/UserInterface/TwoLayerButton.cs @@ -64,8 +64,8 @@ namespace osu.Game.Graphics.UserInterface X = value.HasFlagFast(Anchor.x2) ? SIZE_RETRACTED.X * shear.X * 0.5f : 0; - Remove(c1); - Remove(c2); + Remove(c1, false); + Remove(c2, false); c1.Depth = value.HasFlagFast(Anchor.x2) ? 0 : 1; c2.Depth = value.HasFlagFast(Anchor.x2) ? 1 : 0; Add(c1); diff --git a/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs b/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs index b3814ca90c..d9f962ca97 100644 --- a/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs +++ b/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs @@ -99,7 +99,7 @@ namespace osu.Game.Overlays.Chat.ChannelList FillFlowContainer flow = getFlowForChannel(channel); channelMap.Remove(channel); - flow.Remove(item); + flow.Remove(item, true); updateVisibility(); } diff --git a/osu.Game/Overlays/Chat/DrawableChannel.cs b/osu.Game/Overlays/Chat/DrawableChannel.cs index c05f456a96..544daf7d2c 100644 --- a/osu.Game/Overlays/Chat/DrawableChannel.cs +++ b/osu.Game/Overlays/Chat/DrawableChannel.cs @@ -181,7 +181,7 @@ namespace osu.Game.Overlays.Chat { Trace.Assert(updated.Id.HasValue, "An updated message was returned with no ID."); - ChatLineFlow.Remove(found); + ChatLineFlow.Remove(found, false); found.Message = updated; ChatLineFlow.Add(found); } diff --git a/osu.Game/Rulesets/UI/JudgementContainer.cs b/osu.Game/Rulesets/UI/JudgementContainer.cs index 4336977aa8..471a62cab3 100644 --- a/osu.Game/Rulesets/UI/JudgementContainer.cs +++ b/osu.Game/Rulesets/UI/JudgementContainer.cs @@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.UI // remove any existing judgements for the judged object. // this can be the case when rewinding. - RemoveAll(c => c.JudgedObject == judgement.JudgedObject); + RemoveAll(c => c.JudgedObject == judgement.JudgedObject, false); base.Add(judgement); } diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index 540fbf9a72..8b38d9c612 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -292,7 +292,7 @@ namespace osu.Game.Screens.Edit.Compose.Components blueprint.Selected -= OnBlueprintSelected; blueprint.Deselected -= OnBlueprintDeselected; - SelectionBlueprints.Remove(blueprint); + SelectionBlueprints.Remove(blueprint, true); if (movementBlueprints?.Contains(blueprint) == true) finishSelectionMovement(); diff --git a/osu.Game/Screens/Edit/Compose/Components/HitObjectOrderedSelectionContainer.cs b/osu.Game/Screens/Edit/Compose/Components/HitObjectOrderedSelectionContainer.cs index 06af232111..3be1c27129 100644 --- a/osu.Game/Screens/Edit/Compose/Components/HitObjectOrderedSelectionContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/HitObjectOrderedSelectionContainer.cs @@ -35,10 +35,10 @@ namespace osu.Game.Screens.Edit.Compose.Components base.Add(drawable); } - public override bool Remove(SelectionBlueprint drawable) + public override bool Remove(SelectionBlueprint drawable, bool disposeImmediately) { SortInternal(); - return base.Remove(drawable); + return base.Remove(drawable, disposeImmediately); } protected override int Compare(Drawable x, Drawable y) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs index 742e16d5a9..590f92d281 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs @@ -77,7 +77,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { if (placementBlueprint != null) { - SelectionBlueprints.Remove(placementBlueprint); + SelectionBlueprints.Remove(placementBlueprint, true); placementBlueprint = null; } } diff --git a/osu.Game/Screens/Menu/IntroTriangles.cs b/osu.Game/Screens/Menu/IntroTriangles.cs index d777f78df2..05a6d25303 100644 --- a/osu.Game/Screens/Menu/IntroTriangles.cs +++ b/osu.Game/Screens/Menu/IntroTriangles.cs @@ -255,8 +255,7 @@ namespace osu.Game.Screens.Menu { lazerLogo.FadeOut().OnComplete(_ => { - logoContainerSecondary.Remove(lazerLogo); - lazerLogo.Dispose(); // explicit disposal as we are pushing a new screen and the expire may not get run. + logoContainerSecondary.Remove(lazerLogo, true); logo.FadeIn(); diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoomParticipantsList.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoomParticipantsList.cs index 995a0d3397..9e2bd41fd0 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoomParticipantsList.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoomParticipantsList.cs @@ -232,7 +232,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components private void removeUser(APIUser user) { - avatarFlow.RemoveAll(a => a.User == user); + avatarFlow.RemoveAll(a => a.User == user, true); } private void clearUsers() @@ -250,7 +250,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components hiddenUsers.Count = hiddenCount; if (displayedCircles > NumberOfCircles) - avatarFlow.Remove(avatarFlow.Last()); + avatarFlow.Remove(avatarFlow.Last(), true); else if (displayedCircles < NumberOfCircles) { var nextUser = RecentParticipants.FirstOrDefault(u => avatarFlow.All(a => a.User != u)); diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs index 6142fc78a8..e6b1942506 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs @@ -138,7 +138,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components { foreach (var r in rooms) { - roomFlow.RemoveAll(d => d.Room == r); + roomFlow.RemoveAll(d => d.Room == r, true); // selection may have a lease due to being in a sub screen. if (!SelectedRoom.Disabled) diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index 226216b0f0..486df8653f 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -317,7 +317,7 @@ namespace osu.Game.Screens.Ranking var screenSpacePos = detachedPanel.ScreenSpaceDrawQuad.TopLeft; // Remove from the local container and re-attach. - detachedPanelContainer.Remove(detachedPanel); + detachedPanelContainer.Remove(detachedPanel, false); ScorePanelList.Attach(detachedPanel); // Move into its original location in the attached container first, then to the final location. diff --git a/osu.Game/Skinning/SkinnableSound.cs b/osu.Game/Skinning/SkinnableSound.cs index c2696c56f3..8f71b40801 100644 --- a/osu.Game/Skinning/SkinnableSound.cs +++ b/osu.Game/Skinning/SkinnableSound.cs @@ -151,7 +151,7 @@ namespace osu.Game.Skinning bool wasPlaying = IsPlaying; // Remove all pooled samples (return them to the pool), and dispose the rest. - samplesContainer.RemoveAll(s => s.IsInPool); + samplesContainer.RemoveAll(s => s.IsInPool, false); samplesContainer.Clear(); foreach (var s in samples) diff --git a/osu.Game/Skinning/SkinnableTargetContainer.cs b/osu.Game/Skinning/SkinnableTargetContainer.cs index 341a881789..2faaa9a905 100644 --- a/osu.Game/Skinning/SkinnableTargetContainer.cs +++ b/osu.Game/Skinning/SkinnableTargetContainer.cs @@ -85,7 +85,7 @@ namespace osu.Game.Skinning if (!(component is Drawable drawable)) throw new ArgumentException($"Provided argument must be of type {nameof(Drawable)}.", nameof(component)); - content.Remove(drawable); + content.Remove(drawable, true); components.Remove(component); } diff --git a/osu.Game/Tests/Visual/OsuGameTestScene.cs b/osu.Game/Tests/Visual/OsuGameTestScene.cs index 69a945db34..e47d19fba6 100644 --- a/osu.Game/Tests/Visual/OsuGameTestScene.cs +++ b/osu.Game/Tests/Visual/OsuGameTestScene.cs @@ -58,10 +58,7 @@ namespace osu.Game.Tests.Visual AddStep("Create new game instance", () => { if (Game?.Parent != null) - { - Remove(Game); - Game.Dispose(); - } + Remove(Game, true); RecycleLocalStorage(false); diff --git a/osu.Game/Tests/Visual/PlacementBlueprintTestScene.cs b/osu.Game/Tests/Visual/PlacementBlueprintTestScene.cs index 797e8363c3..a12459bc4e 100644 --- a/osu.Game/Tests/Visual/PlacementBlueprintTestScene.cs +++ b/osu.Game/Tests/Visual/PlacementBlueprintTestScene.cs @@ -67,7 +67,7 @@ namespace osu.Game.Tests.Visual protected void ResetPlacement() { if (CurrentBlueprint != null) - Remove(CurrentBlueprint); + Remove(CurrentBlueprint, true); Add(CurrentBlueprint = CreateBlueprint()); } From 105aa01e7d414d5e1837d00fd7eb4990895d98c6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 29 Aug 2022 15:49:28 +0900 Subject: [PATCH 164/420] Update usages of `RemoveInternal` --- .../Edit/Blueprints/Components/EditablePath.cs | 2 +- .../Edit/Blueprints/Components/NestedOutlineContainer.cs | 2 +- .../Objects/Drawables/DrawableOsuHitObject.cs | 2 +- osu.Game/Graphics/Backgrounds/Background.cs | 2 +- osu.Game/Graphics/Containers/SectionsContainer.cs | 2 +- osu.Game/Rulesets/Judgements/DrawableJudgement.cs | 2 +- .../Objects/Pooling/PooledDrawableWithLifetimeContainer.cs | 2 +- osu.Game/Rulesets/UI/HitObjectContainer.cs | 2 +- osu.Game/Screens/Play/FailAnimation.cs | 3 +-- osu.Game/Screens/Ranking/ScorePanelTrackingContainer.cs | 2 +- 10 files changed, 10 insertions(+), 11 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/EditablePath.cs b/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/EditablePath.cs index 44d14ec330..8473eda663 100644 --- a/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/EditablePath.cs +++ b/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/EditablePath.cs @@ -56,7 +56,7 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components public void UpdateFrom(ScrollingHitObjectContainer hitObjectContainer, JuiceStream hitObject) { while (path.Vertices.Count < InternalChildren.Count) - RemoveInternal(InternalChildren[^1]); + RemoveInternal(InternalChildren[^1], true); while (InternalChildren.Count < path.Vertices.Count) AddInternal(new VertexPiece()); diff --git a/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/NestedOutlineContainer.cs b/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/NestedOutlineContainer.cs index 431ba331ac..a6f1732bc1 100644 --- a/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/NestedOutlineContainer.cs +++ b/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/NestedOutlineContainer.cs @@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components .Where(h => !(h is TinyDroplet))); while (nestedHitObjects.Count < InternalChildren.Count) - RemoveInternal(InternalChildren[^1]); + RemoveInternal(InternalChildren[^1], true); while (InternalChildren.Count < nestedHitObjects.Count) AddInternal(new FruitOutline()); diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs index aedb62dd24..6e525071ca 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs @@ -77,7 +77,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables // This is a bit ugly but we don't have the concept of InternalContent so it'll have to do for now. (https://github.com/ppy/osu-framework/issues/1690) protected override void AddInternal(Drawable drawable) => shakeContainer.Add(drawable); protected override void ClearInternal(bool disposeChildren = true) => shakeContainer.Clear(disposeChildren); - protected override bool RemoveInternal(Drawable drawable) => shakeContainer.Remove(drawable, true); + protected override bool RemoveInternal(Drawable drawable, bool disposeImmediately) => shakeContainer.Remove(drawable, disposeImmediately); protected sealed override double InitialLifetimeOffset => HitObject.TimePreempt; diff --git a/osu.Game/Graphics/Backgrounds/Background.cs b/osu.Game/Graphics/Backgrounds/Background.cs index 2dd96bcb09..0899c0706d 100644 --- a/osu.Game/Graphics/Backgrounds/Background.cs +++ b/osu.Game/Graphics/Backgrounds/Background.cs @@ -57,7 +57,7 @@ namespace osu.Game.Graphics.Backgrounds { if (bufferedContainer == null && newBlurSigma != Vector2.Zero) { - RemoveInternal(Sprite); + RemoveInternal(Sprite, false); AddInternal(bufferedContainer = new BufferedContainer(cachedFrameBuffer: true) { diff --git a/osu.Game/Graphics/Containers/SectionsContainer.cs b/osu.Game/Graphics/Containers/SectionsContainer.cs index 448ecc6b3d..b8a8ea79cc 100644 --- a/osu.Game/Graphics/Containers/SectionsContainer.cs +++ b/osu.Game/Graphics/Containers/SectionsContainer.cs @@ -35,7 +35,7 @@ namespace osu.Game.Graphics.Containers if (value == expandableHeader) return; if (expandableHeader != null) - RemoveInternal(expandableHeader); + RemoveInternal(expandableHeader, false); expandableHeader = value; diff --git a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs index 0d8b8429d8..7e1196d4ca 100644 --- a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs +++ b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs @@ -165,7 +165,7 @@ namespace osu.Game.Rulesets.Judgements // sub-classes might have added their own children that would be removed here if .InternalChild was used. if (JudgementBody != null) - RemoveInternal(JudgementBody); + RemoveInternal(JudgementBody, true); AddInternal(JudgementBody = new SkinnableDrawable(new GameplaySkinComponent(type), _ => CreateDefaultJudgement(type), confineMode: ConfineMode.NoScaling) diff --git a/osu.Game/Rulesets/Objects/Pooling/PooledDrawableWithLifetimeContainer.cs b/osu.Game/Rulesets/Objects/Pooling/PooledDrawableWithLifetimeContainer.cs index 07a80895e6..d5b4390ce8 100644 --- a/osu.Game/Rulesets/Objects/Pooling/PooledDrawableWithLifetimeContainer.cs +++ b/osu.Game/Rulesets/Objects/Pooling/PooledDrawableWithLifetimeContainer.cs @@ -132,7 +132,7 @@ namespace osu.Game.Rulesets.Objects.Pooling /// /// Invoked when the entry became dead. /// - protected virtual void RemoveDrawable(TEntry entry, TDrawable drawable) => RemoveInternal(drawable); + protected virtual void RemoveDrawable(TEntry entry, TDrawable drawable) => RemoveInternal(drawable, false); private void entryCrossedBoundary(LifetimeEntry lifetimeEntry, LifetimeBoundaryKind kind, LifetimeBoundaryCrossingDirection direction) { diff --git a/osu.Game/Rulesets/UI/HitObjectContainer.cs b/osu.Game/Rulesets/UI/HitObjectContainer.cs index 589b585643..bbced9e58c 100644 --- a/osu.Game/Rulesets/UI/HitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/HitObjectContainer.cs @@ -124,7 +124,7 @@ namespace osu.Game.Rulesets.UI unbindStartTime(drawable); - RemoveInternal(drawable); + RemoveInternal(drawable, false); } #endregion diff --git a/osu.Game/Screens/Play/FailAnimation.cs b/osu.Game/Screens/Play/FailAnimation.cs index 7275b369c3..a4b4bf4d2b 100644 --- a/osu.Game/Screens/Play/FailAnimation.cs +++ b/osu.Game/Screens/Play/FailAnimation.cs @@ -166,8 +166,7 @@ namespace osu.Game.Screens.Play if (filters.Parent == null) return; - RemoveInternal(filters); - filters.Dispose(); + RemoveInternal(filters, true); } protected override void Update() diff --git a/osu.Game/Screens/Ranking/ScorePanelTrackingContainer.cs b/osu.Game/Screens/Ranking/ScorePanelTrackingContainer.cs index e55c4530b4..b4d6d481ef 100644 --- a/osu.Game/Screens/Ranking/ScorePanelTrackingContainer.cs +++ b/osu.Game/Screens/Ranking/ScorePanelTrackingContainer.cs @@ -34,7 +34,7 @@ namespace osu.Game.Screens.Ranking if (InternalChildren.Count == 0) throw new InvalidOperationException("Score panel container is not attached."); - RemoveInternal(Panel); + RemoveInternal(Panel, false); } /// From d4a37725c43d6582b89c2eba96d5732ee265785a Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 29 Aug 2022 15:59:57 +0900 Subject: [PATCH 165/420] Adjust test --- .../Gameplay/TestSceneScoreProcessor.cs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs b/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs index 4123412ab6..fb9d841d99 100644 --- a/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs +++ b/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs @@ -124,14 +124,19 @@ namespace osu.Game.Tests.Gameplay Assert.That(score.Rank, Is.EqualTo(ScoreRank.F)); Assert.That(score.Passed, Is.False); - Assert.That(score.Statistics.Count(kvp => kvp.Value > 0), Is.EqualTo(7)); + Assert.That(score.Statistics.Sum(kvp => kvp.Value), Is.EqualTo(4)); + Assert.That(score.MaximumStatistics.Sum(kvp => kvp.Value), Is.EqualTo(8)); + Assert.That(score.Statistics[HitResult.Ok], Is.EqualTo(1)); - Assert.That(score.Statistics[HitResult.Miss], Is.EqualTo(1)); Assert.That(score.Statistics[HitResult.LargeTickHit], Is.EqualTo(1)); - Assert.That(score.Statistics[HitResult.LargeTickMiss], Is.EqualTo(1)); - Assert.That(score.Statistics[HitResult.SmallTickMiss], Is.EqualTo(2)); + Assert.That(score.Statistics[HitResult.SmallTickMiss], Is.EqualTo(1)); Assert.That(score.Statistics[HitResult.SmallBonus], Is.EqualTo(1)); - Assert.That(score.Statistics[HitResult.IgnoreMiss], Is.EqualTo(1)); + + Assert.That(score.MaximumStatistics[HitResult.Perfect], Is.EqualTo(2)); + Assert.That(score.MaximumStatistics[HitResult.LargeTickHit], Is.EqualTo(2)); + Assert.That(score.MaximumStatistics[HitResult.SmallTickHit], Is.EqualTo(2)); + Assert.That(score.MaximumStatistics[HitResult.SmallBonus], Is.EqualTo(1)); + Assert.That(score.MaximumStatistics[HitResult.LargeBonus], Is.EqualTo(1)); } private class TestJudgement : Judgement From be5c6232e82ea1fa9115fda102a6259ec5bb2444 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 29 Aug 2022 16:29:19 +0900 Subject: [PATCH 166/420] Encapsulate `Track` inside a `FramedClock` to avoid mutating operations --- osu.Game/OsuGameBase.cs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 27ea3a76ae..0b158d5e08 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -381,7 +381,16 @@ namespace osu.Game Beatmap.BindValueChanged(onBeatmapChanged); } - private void onTrackChanged(WorkingBeatmap beatmap, TrackChangeDirection direction) => beatmapClock.ChangeSource(beatmap.Track); + private void onTrackChanged(WorkingBeatmap beatmap, TrackChangeDirection direction) + { + // FramedBeatmapClock uses a decoupled clock internally which will mutate the source if it is an `IAdjustableClock`. + // We don't want this for now, as the intention of beatmapClock is to be a read-only source for beat sync components. + // + // Encapsulating in a FramedClock will avoid any mutations. + var framedClock = new FramedClock(beatmap.Track); + + beatmapClock.ChangeSource(framedClock); + } protected virtual void InitialiseFonts() { From cf8fad045d94a010d96c7764fc66879ae2f22561 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 29 Aug 2022 16:32:41 +0900 Subject: [PATCH 167/420] Update template rulesets to include baked value --- .../osu.Game.Rulesets.EmptyFreeform/EmptyFreeformRuleset.cs | 3 +++ .../osu.Game.Rulesets.Pippidon/PippidonRuleset.cs | 3 +++ .../osu.Game.Rulesets.EmptyScrolling/EmptyScrollingRuleset.cs | 3 +++ .../osu.Game.Rulesets.Pippidon/PippidonRuleset.cs | 3 +++ 4 files changed, 12 insertions(+) diff --git a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/EmptyFreeformRuleset.cs b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/EmptyFreeformRuleset.cs index 00754c6346..1e88f87f09 100644 --- a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/EmptyFreeformRuleset.cs +++ b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/EmptyFreeformRuleset.cs @@ -77,5 +77,8 @@ namespace osu.Game.Rulesets.EmptyFreeform }; } } + + // Leave this line intact. It will bake the correct version into the ruleset on each build/release. + public override string RulesetAPIVersionSupported => CURRENT_RULESET_API_VERSION; } } diff --git a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/PippidonRuleset.cs b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/PippidonRuleset.cs index 0522840e9e..2f6ba0dda6 100644 --- a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/PippidonRuleset.cs +++ b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/PippidonRuleset.cs @@ -49,5 +49,8 @@ namespace osu.Game.Rulesets.Pippidon }; public override Drawable CreateIcon() => new PippidonRulesetIcon(this); + + // Leave this line intact. It will bake the correct version into the ruleset on each build/release. + public override string RulesetAPIVersionSupported => CURRENT_RULESET_API_VERSION; } } diff --git a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/EmptyScrollingRuleset.cs b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/EmptyScrollingRuleset.cs index c8f0c07724..a32586c414 100644 --- a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/EmptyScrollingRuleset.cs +++ b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/EmptyScrollingRuleset.cs @@ -54,5 +54,8 @@ namespace osu.Game.Rulesets.EmptyScrolling Text = ShortName[0].ToString(), Font = OsuFont.Default.With(size: 18), }; + + // Leave this line intact. It will bake the correct version into the ruleset on each build/release. + public override string RulesetAPIVersionSupported => CURRENT_RULESET_API_VERSION; } } diff --git a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/PippidonRuleset.cs b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/PippidonRuleset.cs index 89246373ee..bde530feb8 100644 --- a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/PippidonRuleset.cs +++ b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/PippidonRuleset.cs @@ -46,5 +46,8 @@ namespace osu.Game.Rulesets.Pippidon }; public override Drawable CreateIcon() => new PippidonRulesetIcon(this); + + // Leave this line intact. It will bake the correct version into the ruleset on each build/release. + public override string RulesetAPIVersionSupported => CURRENT_RULESET_API_VERSION; } } From 2dafa041a7e0a732159691f2107977555378e836 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 29 Aug 2022 16:42:50 +0900 Subject: [PATCH 168/420] Account for offset being applied to editor clock time in `TestSceneEditorClock` --- osu.Game.Tests/Visual/Editing/TestSceneEditorClock.cs | 2 +- osu.Game/Beatmaps/FramedBeatmapClock.cs | 4 ++-- osu.Game/Screens/Edit/EditorClock.cs | 2 ++ 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorClock.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorClock.cs index d598ebafa9..319f8ab9dc 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorClock.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorClock.cs @@ -63,7 +63,7 @@ namespace osu.Game.Tests.Visual.Editing AddStep("seek near end", () => EditorClock.Seek(EditorClock.TrackLength - 250)); AddUntilStep("clock stops", () => !EditorClock.IsRunning); - AddUntilStep("clock stopped at end", () => EditorClock.CurrentTime, () => Is.EqualTo(EditorClock.TrackLength)); + AddUntilStep("clock stopped at end", () => EditorClock.CurrentTime - EditorClock.TotalAppliedOffset, () => Is.EqualTo(EditorClock.TrackLength)); AddStep("start clock again", () => EditorClock.Start()); AddAssert("clock looped to start", () => EditorClock.IsRunning && EditorClock.CurrentTime < 500); diff --git a/osu.Game/Beatmaps/FramedBeatmapClock.cs b/osu.Game/Beatmaps/FramedBeatmapClock.cs index c86f25640f..a4787a34e8 100644 --- a/osu.Game/Beatmaps/FramedBeatmapClock.cs +++ b/osu.Game/Beatmaps/FramedBeatmapClock.cs @@ -124,7 +124,7 @@ namespace osu.Game.Beatmaps finalClockSource.ProcessFrame(); } - private double totalAppliedOffset + public double TotalAppliedOffset { get { @@ -169,7 +169,7 @@ namespace osu.Game.Beatmaps public bool Seek(double position) { - bool success = decoupledClock.Seek(position - totalAppliedOffset); + bool success = decoupledClock.Seek(position - TotalAppliedOffset); finalClockSource.ProcessFrame(); return success; diff --git a/osu.Game/Screens/Edit/EditorClock.cs b/osu.Game/Screens/Edit/EditorClock.cs index 7a00a74530..6485f683ad 100644 --- a/osu.Game/Screens/Edit/EditorClock.cs +++ b/osu.Game/Screens/Edit/EditorClock.cs @@ -158,6 +158,8 @@ namespace osu.Game.Screens.Edit public double CurrentTime => underlyingClock.CurrentTime; + public double TotalAppliedOffset => underlyingClock.TotalAppliedOffset; + public void Reset() { ClearTransforms(); From 780121eeee359a433aba22dba057fcc2a55ef8d5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 29 Aug 2022 17:12:09 +0900 Subject: [PATCH 169/420] Add setting to toggle metronome in "Target" mod As mentioned in https://github.com/ppy/osu/discussions/20006#discussioncomment-3496732. --- osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs index 82260db818..b48f0b4ccd 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs @@ -59,6 +59,9 @@ namespace osu.Game.Rulesets.Osu.Mods Value = null }; + [SettingSource("Metronome ticks", "Whether a metronome beat should play in the background")] + public BindableBool Metronome { get; } = new BindableBool(true); + #region Constants /// @@ -337,7 +340,8 @@ namespace osu.Game.Rulesets.Osu.Mods public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) { - drawableRuleset.Overlays.Add(new MetronomeBeat(drawableRuleset.Beatmap.HitObjects.First().StartTime)); + if (Metronome.Value) + drawableRuleset.Overlays.Add(new MetronomeBeat(drawableRuleset.Beatmap.HitObjects.First().StartTime)); } #endregion From 07b502f69af5bc73b84ac0df689fec121edc39f7 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 29 Aug 2022 17:58:57 +0900 Subject: [PATCH 170/420] Simplify OrderByTotalScore implementation --- osu.Game/Scoring/ScoreManager.cs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index fd600f4864..782590114f 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -57,12 +57,7 @@ namespace osu.Game.Scoring /// The array of s to reorder. /// The given ordered by decreasing total score. public IEnumerable OrderByTotalScore(IEnumerable scores) - { - return scores.Select((score, index) => (score, totalScore: GetTotalScore(score))) - .OrderByDescending(g => g.totalScore) - .ThenBy(g => g.score.OnlineID) - .Select(g => g.score); - } + => scores.OrderByDescending(s => GetTotalScore(s)).ThenBy(s => s.OnlineID); /// /// Retrieves a bindable that represents the total score of a . From 3eda284b03b5abcabe78b03d743078313f8f52a0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 29 Aug 2022 18:17:41 +0900 Subject: [PATCH 171/420] Always reprocess beatmaps after a user update request This covers the rare case where metadata may have changed server-side but not the beatmap itself. Tested with the provided user database to resolve the issue. Closes #19976. --- osu.Game/Beatmaps/BeatmapImporter.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/osu.Game/Beatmaps/BeatmapImporter.cs b/osu.Game/Beatmaps/BeatmapImporter.cs index 0fa30cf5e7..292caa4397 100644 --- a/osu.Game/Beatmaps/BeatmapImporter.cs +++ b/osu.Game/Beatmaps/BeatmapImporter.cs @@ -55,7 +55,14 @@ namespace osu.Game.Beatmaps // If there were no changes, ensure we don't accidentally nuke ourselves. if (first.ID == original.ID) + { + first.PerformRead(s => + { + // Re-run processing even in this case. We might have outdated metadata. + ProcessBeatmap?.Invoke((s, false)); + }); return first; + } first.PerformWrite(updated => { From ad5ef529227d3687e8dd94573ea62b9e45b24f5d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 29 Aug 2022 19:02:01 +0900 Subject: [PATCH 172/420] Add test coverage of resuming after pause not skipping forward in time --- .../Visual/Gameplay/TestScenePause.cs | 34 ++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs index cad8c62233..61b59747ea 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs @@ -90,6 +90,9 @@ namespace osu.Game.Tests.Visual.Gameplay AddAssert("player not playing", () => !Player.LocalUserPlaying.Value); resumeAndConfirm(); + + AddAssert("Resumed without seeking forward", () => Player.LastResumeTime, () => Is.LessThanOrEqualTo(Player.LastPauseTime)); + AddUntilStep("player playing", () => Player.LocalUserPlaying.Value); } @@ -378,7 +381,16 @@ namespace osu.Game.Tests.Visual.Gameplay AddAssert("pause overlay " + (isShown ? "shown" : "hidden"), () => Player.PauseOverlayVisible == isShown); private void confirmClockRunning(bool isRunning) => - AddUntilStep("clock " + (isRunning ? "running" : "stopped"), () => Player.GameplayClockContainer.IsRunning == isRunning); + AddUntilStep("clock " + (isRunning ? "running" : "stopped"), () => + { + bool completed = Player.GameplayClockContainer.IsRunning == isRunning; + + if (completed) + { + } + + return completed; + }); protected override bool AllowFail => true; @@ -386,6 +398,9 @@ namespace osu.Game.Tests.Visual.Gameplay protected class PausePlayer : TestPlayer { + public double LastPauseTime { get; private set; } + public double LastResumeTime { get; private set; } + public bool FailOverlayVisible => FailOverlay.State.Value == Visibility.Visible; public bool PauseOverlayVisible => PauseOverlay.State.Value == Visibility.Visible; @@ -399,6 +414,23 @@ namespace osu.Game.Tests.Visual.Gameplay base.OnEntering(e); GameplayClockContainer.Stop(); } + + private bool? isRunning; + + protected override void UpdateAfterChildren() + { + base.UpdateAfterChildren(); + + if (GameplayClockContainer.IsRunning != isRunning) + { + isRunning = GameplayClockContainer.IsRunning; + + if (isRunning.Value) + LastResumeTime = GameplayClockContainer.CurrentTime; + else + LastPauseTime = GameplayClockContainer.CurrentTime; + } + } } } } From 75531d2d62d3f7e9f7c2fd04dd5ba53d884bcefc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 29 Aug 2022 19:51:16 +0900 Subject: [PATCH 173/420] Fix gameplay skipping forward during resume operation --- .../Screens/Play/GameplayClockContainer.cs | 17 +++++-- .../Play/MasterGameplayClockContainer.cs | 45 ++++++++++++++++++- 2 files changed, 56 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index 1ae393d06a..ff82fb96ec 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -88,9 +88,7 @@ namespace osu.Game.Screens.Play ensureSourceClockSet(); - // Seeking the decoupled clock to its current time ensures that its source clock will be seeked to the same time - // This accounts for the clock source potentially taking time to enter a completely stopped state - Seek(GameplayClock.CurrentTime); + PrepareStart(); // The case which caused this to be added is FrameStabilityContainer, which manages its own current and elapsed time. // Because we generally update our own current time quicker than children can query it (via Start/Seek/Update), @@ -111,11 +109,22 @@ namespace osu.Game.Screens.Play }); } + /// + /// When is called, this will be run to give an opportunity to prepare the clock at the correct + /// start location. + /// + protected virtual void PrepareStart() + { + // Seeking the decoupled clock to its current time ensures that its source clock will be seeked to the same time + // This accounts for the clock source potentially taking time to enter a completely stopped state + Seek(GameplayClock.CurrentTime); + } + /// /// Seek to a specific time in gameplay. /// /// The destination time to seek to. - public void Seek(double time) + public virtual void Seek(double time) { Logger.Log($"{nameof(GameplayClockContainer)} seeking to {time}"); diff --git a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs index 238817ad05..f0f5daf64d 100644 --- a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs +++ b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs @@ -8,6 +8,7 @@ using osu.Framework.Audio; using osu.Framework.Audio.Track; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Logging; using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; @@ -45,6 +46,17 @@ namespace osu.Game.Screens.Play private readonly List> nonGameplayAdjustments = new List>(); + /// + /// Stores the time at which the last call was triggered. + /// This is used to ensure we resume from that precise point in time, ignoring the proceeding frequency ramp. + /// + /// Optimally, we'd have gameplay ramp down with the frequency, but I believe this was intentionally disabled + /// to avoid fails occurring after the pause screen has been shown. + /// + /// In the future I want to change this. + /// + private double? actualStopTime; + public override IEnumerable NonGameplayAdjustments => nonGameplayAdjustments.Select(b => b.Value); /// @@ -86,10 +98,12 @@ namespace osu.Game.Screens.Play protected override void StopGameplayClock() { + actualStopTime = GameplayClock.CurrentTime; + if (IsLoaded) { // During normal operation, the source is stopped after performing a frequency ramp. - this.TransformBindableTo(GameplayClock.ExternalPauseFrequencyAdjust, 0, 200, Easing.Out).OnComplete(_ => + this.TransformBindableTo(GameplayClock.ExternalPauseFrequencyAdjust, 0, 2000, Easing.Out).OnComplete(_ => { if (IsPaused.Value) base.StopGameplayClock(); @@ -108,6 +122,25 @@ namespace osu.Game.Screens.Play } } + public override void Seek(double time) + { + // Safety in case the clock is seeked while stopped. + actualStopTime = null; + + base.Seek(time); + } + + protected override void PrepareStart() + { + if (actualStopTime != null) + { + Seek(actualStopTime.Value); + actualStopTime = null; + } + else + base.PrepareStart(); + } + protected override void StartGameplayClock() { addSourceClockAdjustments(); @@ -116,7 +149,7 @@ namespace osu.Game.Screens.Play if (IsLoaded) { - this.TransformBindableTo(GameplayClock.ExternalPauseFrequencyAdjust, 1, 200, Easing.In); + this.TransformBindableTo(GameplayClock.ExternalPauseFrequencyAdjust, 1, 2000, Easing.In); } else { @@ -158,6 +191,14 @@ namespace osu.Game.Screens.Play private bool speedAdjustmentsApplied; + protected override void Update() + { + base.Update(); + + if (GameplayClock.ExternalPauseFrequencyAdjust.Value < 1) + Logger.Log($"{GameplayClock.CurrentTime}"); + } + private void addSourceClockAdjustments() { if (speedAdjustmentsApplied) From 1bff540381f8c7aaea4d4be68fab8bc69ffafa03 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 29 Aug 2022 22:04:37 +0900 Subject: [PATCH 174/420] Remove debug changes --- .../Screens/Play/MasterGameplayClockContainer.cs | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs index f0f5daf64d..2f1ffa126f 100644 --- a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs +++ b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs @@ -8,7 +8,6 @@ using osu.Framework.Audio; using osu.Framework.Audio.Track; using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Framework.Logging; using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; @@ -103,7 +102,7 @@ namespace osu.Game.Screens.Play if (IsLoaded) { // During normal operation, the source is stopped after performing a frequency ramp. - this.TransformBindableTo(GameplayClock.ExternalPauseFrequencyAdjust, 0, 2000, Easing.Out).OnComplete(_ => + this.TransformBindableTo(GameplayClock.ExternalPauseFrequencyAdjust, 0, 200, Easing.Out).OnComplete(_ => { if (IsPaused.Value) base.StopGameplayClock(); @@ -149,7 +148,7 @@ namespace osu.Game.Screens.Play if (IsLoaded) { - this.TransformBindableTo(GameplayClock.ExternalPauseFrequencyAdjust, 1, 2000, Easing.In); + this.TransformBindableTo(GameplayClock.ExternalPauseFrequencyAdjust, 1, 200, Easing.In); } else { @@ -191,14 +190,6 @@ namespace osu.Game.Screens.Play private bool speedAdjustmentsApplied; - protected override void Update() - { - base.Update(); - - if (GameplayClock.ExternalPauseFrequencyAdjust.Value < 1) - Logger.Log($"{GameplayClock.CurrentTime}"); - } - private void addSourceClockAdjustments() { if (speedAdjustmentsApplied) From a296c1ec81e6980927d8d8dc9338001758ae40e0 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Mon, 29 Aug 2022 16:05:35 +0200 Subject: [PATCH 175/420] remove call to changeHandler BeginChange --- osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs index 3d425afe9e..09f28b5b14 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs @@ -362,7 +362,6 @@ namespace osu.Game.Rulesets.Osu.Edit && Precision.AlmostBigger(1, Vector2.DistanceSquared(mergeableObjects[0].Position, mergeableObjects[1].Position)))) return; - ChangeHandler?.BeginChange(); EditorBeatmap.BeginChange(); // Have an initial slider object. @@ -440,7 +439,6 @@ namespace osu.Game.Rulesets.Osu.Edit SelectedItems.Add(mergedHitObject); EditorBeatmap.EndChange(); - ChangeHandler?.EndChange(); } protected override IEnumerable GetContextMenuItemsForSelection(IEnumerable> selection) From 27ad224f13591fb35309d4612b074dd47b22f6cf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 30 Aug 2022 01:21:44 +0900 Subject: [PATCH 176/420] Remove probably unnecessary `Seek` on start --- osu.Game/Screens/Play/GameplayClockContainer.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index ff82fb96ec..6de88d7ad0 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -115,9 +115,6 @@ namespace osu.Game.Screens.Play /// protected virtual void PrepareStart() { - // Seeking the decoupled clock to its current time ensures that its source clock will be seeked to the same time - // This accounts for the clock source potentially taking time to enter a completely stopped state - Seek(GameplayClock.CurrentTime); } /// From 062a6fcc181da7e3ca597a9d6201491b16fc7898 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 30 Aug 2022 01:21:53 +0900 Subject: [PATCH 177/420] Fix failing large offset test If we are going to continue to let the underlying clock process frames, there needs to be a bit of lenience to allow the backwards seek on resume (to play back over the freq ramp period). The test is meant to be ensuring we don't skip the full offset amount, so div10 seems pretty safe. --- osu.Game.Tests/Visual/Gameplay/TestScenePause.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs index 61b59747ea..a6abdd7ee1 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs @@ -66,7 +66,7 @@ namespace osu.Game.Tests.Visual.Gameplay Player.OnUpdate += _ => { double currentTime = Player.GameplayClockContainer.CurrentTime; - alwaysGoingForward &= currentTime >= lastTime; + alwaysGoingForward &= currentTime >= lastTime - 500; lastTime = currentTime; }; }); @@ -77,7 +77,7 @@ namespace osu.Game.Tests.Visual.Gameplay resumeAndConfirm(); - AddAssert("time didn't go backwards", () => alwaysGoingForward); + AddAssert("time didn't go too far backwards", () => alwaysGoingForward); AddStep("reset offset", () => LocalConfig.SetValue(OsuSetting.AudioOffset, 0.0)); } From d50e9caa11a5930d49b39aa4ce18e11827730f7f Mon Sep 17 00:00:00 2001 From: OliBomby Date: Mon, 29 Aug 2022 18:58:29 +0200 Subject: [PATCH 178/420] Moved guards to separate canMerge method --- osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs index 8b67c0dcc9..dd9d1614b8 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs @@ -354,12 +354,14 @@ namespace osu.Game.Rulesets.Osu.Edit .OrderBy(h => h.StartTime) .ToArray(); + private bool canMerge(IReadOnlyList objects) => + objects.Count > 1 && (objects.Any(h => h is Slider) || Precision.DefinitelyBigger(Vector2.DistanceSquared(objects[0].Position, objects[1].Position), 1)); + private void mergeSelection() { var mergeableObjects = selectedMergeableObjects; - if (mergeableObjects.Length < 2 || (mergeableObjects.All(h => h is not Slider) - && Precision.AlmostBigger(1, Vector2.DistanceSquared(mergeableObjects[0].Position, mergeableObjects[1].Position)))) + if (!canMerge(mergeableObjects)) return; ChangeHandler?.BeginChange(); @@ -446,9 +448,7 @@ namespace osu.Game.Rulesets.Osu.Edit foreach (var item in base.GetContextMenuItemsForSelection(selection)) yield return item; - var mergeableObjects = selectedMergeableObjects; - if (mergeableObjects.Length > 1 && (mergeableObjects.Any(h => h is Slider) - || Precision.DefinitelyBigger(Vector2.DistanceSquared(mergeableObjects[0].Position, mergeableObjects[1].Position), 1))) + if (canMerge(selectedMergeableObjects)) yield return new OsuMenuItem("Merge selection", MenuItemType.Destructive, mergeSelection); } } From 2e5770be4e14623f61327063f62019112d15e316 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 30 Aug 2022 02:51:42 +0900 Subject: [PATCH 179/420] Move helper method to bottom of class --- osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs index dd9d1614b8..048fd4ed7e 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs @@ -354,9 +354,6 @@ namespace osu.Game.Rulesets.Osu.Edit .OrderBy(h => h.StartTime) .ToArray(); - private bool canMerge(IReadOnlyList objects) => - objects.Count > 1 && (objects.Any(h => h is Slider) || Precision.DefinitelyBigger(Vector2.DistanceSquared(objects[0].Position, objects[1].Position), 1)); - private void mergeSelection() { var mergeableObjects = selectedMergeableObjects; @@ -451,5 +448,10 @@ namespace osu.Game.Rulesets.Osu.Edit if (canMerge(selectedMergeableObjects)) yield return new OsuMenuItem("Merge selection", MenuItemType.Destructive, mergeSelection); } + + private bool canMerge(IReadOnlyList objects) => + objects.Count > 1 + && (objects.Any(h => h is Slider) + || Precision.DefinitelyBigger(Vector2.DistanceSquared(objects[0].Position, objects[1].Position), 1)); } } From d20e7da2d98365e6e2e75fc239132069964d5344 Mon Sep 17 00:00:00 2001 From: HiddenNode Date: Mon, 29 Aug 2022 21:03:22 +0100 Subject: [PATCH 180/420] Changed epsilon --- .../TestSceneUprightAspectMaintainingContainer.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneUprightAspectMaintainingContainer.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneUprightAspectMaintainingContainer.cs index 8b87e4030e..f81f8e8d85 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneUprightAspectMaintainingContainer.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneUprightAspectMaintainingContainer.cs @@ -10,6 +10,7 @@ using NUnit.Framework; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics; +using osu.Framework.Utils; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Graphics; @@ -188,7 +189,7 @@ namespace osu.Game.Tests.Visual.UserInterface return false; // Aspect ratio check - if (!isNearlyZero(childScale.X - childScale.Y, 0.0001f)) + if (!isNearlyZero(childScale.X - childScale.Y)) return false; // ScalingMode check @@ -237,7 +238,7 @@ namespace osu.Game.Tests.Visual.UserInterface return true; } - private bool isNearlyZero(float f, float epsilon = 0.00001f) + private bool isNearlyZero(float f, float epsilon = Precision.FLOAT_EPSILON) { return f < epsilon; } From 43f2ba659697370d5e885d2adf784852447f1535 Mon Sep 17 00:00:00 2001 From: HiddenNode Date: Mon, 29 Aug 2022 22:00:33 +0100 Subject: [PATCH 181/420] Added test scene --- .../TestSceneGrowToFitContent.cs | 175 ++++++++++++++++++ 1 file changed, 175 insertions(+) create mode 100644 osu.Game.Tests/Visual/UserInterface/TestSceneGrowToFitContent.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneGrowToFitContent.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneGrowToFitContent.cs new file mode 100644 index 0000000000..bcff992f9d --- /dev/null +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneGrowToFitContent.cs @@ -0,0 +1,175 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable disable + +using System.Collections.Generic; +using NUnit.Framework; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics; +using System.Linq; +using osuTK; + +namespace osu.Game.Tests.Visual.UserInterface +{ + public class TestSceneGrowToFitContent : OsuGridTestScene + { + private readonly List parentContainers = new List(); + private readonly List childContainers = new List(); + private readonly List texts = new List(); + + public TestSceneGrowToFitContent() + : base(1, 2) + { + for (int i = 0; i < 2; i++) + { + OsuSpriteText text; + UprightAspectMaintainingContainer childContainer; + Container parentContainer = new Container + { + Origin = Anchor.BottomRight, + Anchor = Anchor.BottomCentre, + AutoSizeAxes = Axes.Both, + Rotation = 45, + Y = -200, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Colour4.Red, + }, + childContainer = new UprightAspectMaintainingContainer + { + AutoSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Colour4.Blue, + }, + text = new OsuSpriteText + { + Text = "Text", + Font = OsuFont.GetFont(Typeface.Venera, weight: FontWeight.Bold, size: 40), + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + }, + } + }, + } + }; + + Container cellInfo = new Container + { + Origin = Anchor.TopCentre, + Anchor = Anchor.TopCentre, + Margin = new MarginPadding + { + Top = 100, + }, + Child = new OsuSpriteText + { + Text = (i == 0) ? "GrowToFitContent == true" : "GrowToFitContent == false", + Font = OsuFont.GetFont(Typeface.Inter, weight: FontWeight.Bold, size: 40), + Origin = Anchor.TopCentre, + Anchor = Anchor.TopCentre, + }, + }; + + parentContainers.Add(parentContainer); + childContainers.Add(childContainer); + texts.Add(text); + Cell(i).Add(cellInfo); + Cell(i).Add(parentContainer); + } + } + + [Test] + public void TestResizeText() + { + AddStep("reset...", () => + { + childContainers[0].GrowToFitContent = false; + childContainers[1].GrowToFitContent = false; + }); + + AddStep("setup...", () => + { + childContainers[0].GrowToFitContent = true; + childContainers[1].GrowToFitContent = false; + }); + + for (int i = 0; i < 10; i++) + { + AddStep("Add Character", () => + { + foreach (int j in Enumerable.Range(0, parentContainers.Count)) + { + texts[j].Text += "."; + } + }); + } + + for (int i = 0; i < 10; i++) + { + AddStep("Remove Character", () => + { + foreach (int j in Enumerable.Range(0, parentContainers.Count)) + { + string text = texts[j].Text.ToString(); + texts[j].Text = text.Remove(text.Length - 1, 1); + } + }); + } + } + + [Test] + public void TestScaleText() + { + AddStep("reset...", () => + { + childContainers[0].GrowToFitContent = false; + childContainers[1].GrowToFitContent = false; + }); + + AddStep("setup...", () => + { + childContainers[0].GrowToFitContent = true; + childContainers[1].GrowToFitContent = false; + }); + + for (int i = 0; i < 1; i++) + { + AddStep("Big text", scaleUp); + + AddWaitStep("wait...", 5); + + AddStep("Small text", scaleDown); + } + } + + private void scaleUp() + { + foreach (int j in Enumerable.Range(0, parentContainers.Count)) + { + texts[j].ScaleTo(new Vector2(2, 2), 1000); + } + } + + private void scaleDown() + { + foreach (int j in Enumerable.Range(0, parentContainers.Count)) + { + texts[j].ScaleTo(new Vector2(1, 1), 1000); + } + } + } +} From cda7faecf798e9ad9242c44b0edcf544fb5d1801 Mon Sep 17 00:00:00 2001 From: HiddenNode Date: Mon, 29 Aug 2022 22:01:24 +0100 Subject: [PATCH 182/420] Added GrowToFitContent Parameter. --- .../UprightAspectMaintainingContainer.cs | 86 ++++++++++++++++--- 1 file changed, 73 insertions(+), 13 deletions(-) diff --git a/osu.Game/Graphics/Containers/UprightAspectMaintainingContainer.cs b/osu.Game/Graphics/Containers/UprightAspectMaintainingContainer.cs index 64b9eb409b..8efc29c83c 100644 --- a/osu.Game/Graphics/Containers/UprightAspectMaintainingContainer.cs +++ b/osu.Game/Graphics/Containers/UprightAspectMaintainingContainer.cs @@ -24,6 +24,27 @@ namespace osu.Game.Graphics.Containers /// public ScaleMode Scaling { get; set; } = ScaleMode.Vertical; + /// + /// If this is true, all wrapper containers will be set to grow with their content + /// and not shrink back, this is used to fix the position of children that change + /// in size when using AutoSizeAxes. + /// + public bool GrowToFitContent + { + get => growToFitContent; + set + { + if (growToFitContent != value) + { + foreach (GrowToFitContainer c in Children) + c.GrowToFitContentUpdated = true; + growToFitContent = value; + } + } + } + + private bool growToFitContent = true; + private readonly LayoutValue layout = new LayoutValue(Invalidation.DrawInfo, InvalidationSource.Parent); public UprightAspectMaintainingContainer() @@ -44,10 +65,11 @@ namespace osu.Game.Graphics.Containers public override void Add(Drawable drawable) { - base.Add(new GrowToFitContainer - { - Child = drawable - }); + var wrapper = new GrowToFitContainer(); + wrapper.Wrap(drawable); + wrapper.AutoSizeAxes = Axes.None; + drawable.Origin = drawable.Anchor = Anchor.Centre; + base.Add(wrapper); } /// @@ -58,7 +80,7 @@ namespace osu.Game.Graphics.Containers // Decomposes the inverse of the parent DrawInfo.Matrix into rotation, shear and scale. var parentMatrix = Parent.DrawInfo.Matrix; - // Remove Translation. + // Remove Translation.> parentMatrix.M31 = 0.0f; parentMatrix.M32 = 0.0f; @@ -108,17 +130,55 @@ namespace osu.Game.Graphics.Containers public class GrowToFitContainer : Container { + private readonly LayoutValue layout = new LayoutValue(Invalidation.RequiredParentSizeToFit, InvalidationSource.Child); + + private Vector2 maxChildSize; + + public bool GrowToFitContentUpdated { get; set; } + + public GrowToFitContainer() + { + AddLayout(layout); + } + protected override void Update() { - if ((Child.RelativeSizeAxes & Axes.X) != 0) - RelativeSizeAxes |= Axes.X; - else - Width = Math.Max(Child.Width, Width); + UprightAspectMaintainingContainer parent = (UprightAspectMaintainingContainer)Parent; - if ((Child.RelativeSizeAxes & Axes.Y) != 0) - RelativeSizeAxes |= Axes.Y; - else - Height = Math.Max(Child.Height, Height); + if (!layout.IsValid || GrowToFitContentUpdated) + { + if ((Child.RelativeSizeAxes & Axes.X) != 0) + RelativeSizeAxes |= Axes.X; + else + { + if (parent.GrowToFitContent) + Width = Math.Max(Child.Width * Child.Scale.X, maxChildSize.X); + else + Width = Child.Width * Child.Scale.X; + } + + if ((Child.RelativeSizeAxes & Axes.Y) != 0) + RelativeSizeAxes |= Axes.Y; + else + { + if (parent.GrowToFitContent) + Height = Math.Max(Child.Height * Child.Scale.Y, maxChildSize.Y); + else + Height = Child.Height * Child.Scale.Y; + } + + // reset max_child_size or update it + if (!parent.GrowToFitContent) + maxChildSize = Child.Size; + else + { + maxChildSize.X = MathF.Max(maxChildSize.X, Child.Size.X); + maxChildSize.Y = MathF.Max(maxChildSize.Y, Child.Size.Y); + } + + GrowToFitContentUpdated = false; + layout.Validate(); + } } } } From 44916c51d73580ab42c5a18f33705da9ffae9a16 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Tue, 30 Aug 2022 00:18:55 +0200 Subject: [PATCH 183/420] Updated canMerge check to be totally accurate --- osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs index 048fd4ed7e..ac5fad54a4 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs @@ -452,6 +452,6 @@ namespace osu.Game.Rulesets.Osu.Edit private bool canMerge(IReadOnlyList objects) => objects.Count > 1 && (objects.Any(h => h is Slider) - || Precision.DefinitelyBigger(Vector2.DistanceSquared(objects[0].Position, objects[1].Position), 1)); + || objects.Zip(objects.Skip(1), (h1, h2) => Precision.DefinitelyBigger(Vector2.DistanceSquared(h1.Position, h2.Position), 1)).Any(x => x)); } } From 5d41fdfc890acd4d9dc1d45e3e96f890ec12f1b6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 30 Aug 2022 15:05:21 +0900 Subject: [PATCH 184/420] Remove unnecessary usage of `DrawableAudioMixer` in `ScorePanel` --- 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 0bcfa0da1f..cb777de144 100644 --- a/osu.Game/Screens/Ranking/ScorePanel.cs +++ b/osu.Game/Screens/Ranking/ScorePanel.cs @@ -99,7 +99,7 @@ namespace osu.Game.Screens.Ranking [Resolved] private OsuGameBase game { get; set; } - private DrawableAudioMixer mixer; + private AudioContainer audioContent; private bool displayWithFlair; @@ -130,7 +130,7 @@ namespace osu.Game.Screens.Ranking // 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 = mixer = new DrawableAudioMixer + InternalChild = audioContent = new AudioContainer { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -225,7 +225,7 @@ namespace osu.Game.Screens.Ranking protected override void Update() { base.Update(); - mixer.Balance.Value = (ScreenSpaceDrawQuad.Centre.X / game.ScreenSpaceDrawQuad.Width) * 2 - 1; + audioContent.Balance.Value = (ScreenSpaceDrawQuad.Centre.X / game.ScreenSpaceDrawQuad.Width) * 2 - 1; } private void playAppearSample() @@ -274,7 +274,7 @@ namespace osu.Game.Screens.Ranking break; } - mixer.ResizeTo(Size, RESIZE_DURATION, Easing.OutQuint); + audioContent.ResizeTo(Size, RESIZE_DURATION, Easing.OutQuint); bool topLayerExpanded = topLayerContainer.Y < 0; From 5202c15a0e3edc10e3667805c0db15fe059e441b Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 30 Aug 2022 15:11:39 +0900 Subject: [PATCH 185/420] Populate MaximumStatistics for test scores --- osu.Game.Tests/Resources/TestResources.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Resources/TestResources.cs b/osu.Game.Tests/Resources/TestResources.cs index 6bce03869d..9c85f61330 100644 --- a/osu.Game.Tests/Resources/TestResources.cs +++ b/osu.Game.Tests/Resources/TestResources.cs @@ -194,8 +194,16 @@ namespace osu.Game.Tests.Resources [HitResult.LargeTickHit] = 100, [HitResult.LargeTickMiss] = 50, [HitResult.SmallBonus] = 10, - [HitResult.SmallBonus] = 50 + [HitResult.LargeBonus] = 50 }, + MaximumStatistics = new Dictionary + { + [HitResult.Perfect] = 971, + [HitResult.SmallTickHit] = 75, + [HitResult.LargeTickHit] = 150, + [HitResult.SmallBonus] = 10, + [HitResult.LargeBonus] = 50, + } }; private class TestModHardRock : ModHardRock From 8b3742188fcb9c4c80c7515546091b2ec714ec3e Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 30 Aug 2022 15:42:33 +0900 Subject: [PATCH 186/420] Fix test by also clearing out maximum statistics --- .../Visual/Playlists/TestScenePlaylistsResultsScreen.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs index 8a04cd96fe..26fa740159 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs @@ -61,6 +61,7 @@ namespace osu.Game.Tests.Visual.Playlists userScore = TestResources.CreateTestScoreInfo(); userScore.TotalScore = 0; userScore.Statistics = new Dictionary(); + userScore.MaximumStatistics = new Dictionary(); bindHandler(); From 799c015bff82460ffb1c1392610d47984281a443 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 30 Aug 2022 15:50:19 +0900 Subject: [PATCH 187/420] Add LegacyTotalScore to SoloScoreInfo --- osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs b/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs index 16aa800cb0..a8cedabd48 100644 --- a/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs +++ b/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs @@ -77,6 +77,12 @@ namespace osu.Game.Online.API.Requests.Responses [JsonProperty("maximum_statistics")] public Dictionary MaximumStatistics { get; set; } = new Dictionary(); + /// + /// Used to preserve the total score for legacy scores. + /// + [JsonProperty("legacy_total_score")] + public int? LegacyTotalScore { get; set; } + #region osu-web API additions (not stored to database). [JsonProperty("id")] From b8fda1a16f3668e244086ba5958f9b818c95c02b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 30 Aug 2022 15:51:46 +0900 Subject: [PATCH 188/420] Apply NRT to notification classes and tidy things up a bit. --- .../TestSceneNotificationOverlay.cs | 10 +++--- .../Database/ImportProgressNotification.cs | 2 -- osu.Game/Overlays/NotificationOverlay.cs | 12 ++----- .../Overlays/Notifications/Notification.cs | 8 ++--- .../Notifications/NotificationSection.cs | 14 ++++---- .../Notifications/ProgressNotification.cs | 32 +++++++++---------- .../Notifications/SimpleNotification.cs | 2 -- 7 files changed, 32 insertions(+), 48 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs index cf069a9e34..c196b204b9 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.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. -#nullable disable - using System.Collections.Generic; using System.Linq; using NUnit.Framework; @@ -19,11 +17,11 @@ namespace osu.Game.Tests.Visual.UserInterface [TestFixture] public class TestSceneNotificationOverlay : OsuTestScene { - private NotificationOverlay notificationOverlay; + private NotificationOverlay notificationOverlay = null!; private readonly List progressingNotifications = new List(); - private SpriteText displayedCount; + private SpriteText displayedCount = null!; [SetUp] public void SetUp() => Schedule(() => @@ -46,7 +44,7 @@ namespace osu.Game.Tests.Visual.UserInterface [Test] public void TestCompleteProgress() { - ProgressNotification notification = null; + ProgressNotification notification = null!; AddStep("add progress notification", () => { notification = new ProgressNotification @@ -64,7 +62,7 @@ namespace osu.Game.Tests.Visual.UserInterface [Test] public void TestCancelProgress() { - ProgressNotification notification = null; + ProgressNotification notification = null!; AddStep("add progress notification", () => { notification = new ProgressNotification diff --git a/osu.Game/Database/ImportProgressNotification.cs b/osu.Game/Database/ImportProgressNotification.cs index 46f9936bc2..aaee3e117f 100644 --- a/osu.Game/Database/ImportProgressNotification.cs +++ b/osu.Game/Database/ImportProgressNotification.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. -#nullable disable - using osu.Game.Overlays.Notifications; namespace osu.Game.Database diff --git a/osu.Game/Overlays/NotificationOverlay.cs b/osu.Game/Overlays/NotificationOverlay.cs index 281077bcf6..cbcc7b6886 100644 --- a/osu.Game/Overlays/NotificationOverlay.cs +++ b/osu.Game/Overlays/NotificationOverlay.cs @@ -64,14 +64,8 @@ namespace osu.Game.Overlays RelativeSizeAxes = Axes.X, Children = new[] { - new NotificationSection(AccountsStrings.NotificationsTitle, "Clear All") - { - AcceptTypes = new[] { typeof(SimpleNotification) } - }, - new NotificationSection(@"Running Tasks", @"Cancel All") - { - AcceptTypes = new[] { typeof(ProgressNotification) } - } + new NotificationSection(AccountsStrings.NotificationsTitle, new[] { typeof(SimpleNotification) }, "Clear All"), + new NotificationSection(@"Running Tasks", new[] { typeof(ProgressNotification) }, @"Cancel All"), } } } @@ -133,7 +127,7 @@ namespace osu.Game.Overlays var ourType = notification.GetType(); - var section = sections.Children.FirstOrDefault(s => s.AcceptTypes.Any(accept => accept.IsAssignableFrom(ourType))); + var section = sections.Children.FirstOrDefault(s => s.AcceptedNotificationTypes.Any(accept => accept.IsAssignableFrom(ourType))); section?.Add(notification, notification.DisplayOnTop ? -runningDepth : runningDepth); if (notification.IsImportant) diff --git a/osu.Game/Overlays/Notifications/Notification.cs b/osu.Game/Overlays/Notifications/Notification.cs index 64bf3693c8..fbb906e637 100644 --- a/osu.Game/Overlays/Notifications/Notification.cs +++ b/osu.Game/Overlays/Notifications/Notification.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. -#nullable disable - using System; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; @@ -26,7 +24,7 @@ namespace osu.Game.Overlays.Notifications /// /// User requested close. /// - public event Action Closed; + public event Action? Closed; public abstract LocalisableString Text { get; set; } @@ -38,7 +36,7 @@ namespace osu.Game.Overlays.Notifications /// /// Run on user activating the notification. Return true to close. /// - public Func Activated; + public Func? Activated; /// /// Should we show at the top of our section on display? @@ -212,7 +210,7 @@ namespace osu.Game.Overlays.Notifications public class NotificationLight : Container { private bool pulsate; - private Container pulsateLayer; + private Container pulsateLayer = null!; public bool Pulsate { diff --git a/osu.Game/Overlays/Notifications/NotificationSection.cs b/osu.Game/Overlays/Notifications/NotificationSection.cs index b7e21822fa..d2e18a0cee 100644 --- a/osu.Game/Overlays/Notifications/NotificationSection.cs +++ b/osu.Game/Overlays/Notifications/NotificationSection.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. -#nullable disable - using System; using System.Collections.Generic; using System.Linq; @@ -21,9 +19,9 @@ namespace osu.Game.Overlays.Notifications { public class NotificationSection : AlwaysUpdateFillFlowContainer { - private OsuSpriteText countDrawable; + private OsuSpriteText countDrawable = null!; - private FlowContainer notifications; + private FlowContainer notifications = null!; public int DisplayedCount => notifications.Count(n => !n.WasClosed); public int UnreadCount => notifications.Count(n => !n.WasClosed && !n.Read); @@ -33,14 +31,16 @@ namespace osu.Game.Overlays.Notifications notifications.Insert((int)position, notification); } - public IEnumerable AcceptTypes; + public IEnumerable AcceptedNotificationTypes { get; } private readonly string clearButtonText; private readonly LocalisableString titleText; - public NotificationSection(LocalisableString title, string clearButtonText) + public NotificationSection(LocalisableString title, IEnumerable acceptedNotificationTypes, string clearButtonText) { + AcceptedNotificationTypes = acceptedNotificationTypes.ToArray(); + this.clearButtonText = clearButtonText.ToUpperInvariant(); titleText = title; } @@ -159,7 +159,7 @@ namespace osu.Game.Overlays.Notifications public void MarkAllRead() { - notifications?.Children.ForEach(n => n.Read = true); + notifications.Children.ForEach(n => n.Read = true); } } diff --git a/osu.Game/Overlays/Notifications/ProgressNotification.cs b/osu.Game/Overlays/Notifications/ProgressNotification.cs index 719e77db83..15346930a3 100644 --- a/osu.Game/Overlays/Notifications/ProgressNotification.cs +++ b/osu.Game/Overlays/Notifications/ProgressNotification.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. -#nullable disable - using System; using System.Threading; using osu.Framework.Allocation; @@ -25,6 +23,18 @@ namespace osu.Game.Overlays.Notifications { private const float loading_spinner_size = 22; + public Func? CancelRequested { get; set; } + + /// + /// The function to post completion notifications back to. + /// + public Action? CompletionTarget { get; set; } + + /// + /// An action to complete when the completion notification is clicked. Return true to close. + /// + public Func? CompletionClickAction { get; set; } + private LocalisableString text; public override LocalisableString Text @@ -142,7 +152,7 @@ namespace osu.Game.Overlays.Notifications Text = CompletionText }; - protected virtual void Completed() + protected void Completed() { CompletionTarget?.Invoke(CreateCompletionNotification()); base.Close(); @@ -155,8 +165,8 @@ namespace osu.Game.Overlays.Notifications private Color4 colourActive; private Color4 colourCancelled; - private Box iconBackground; - private LoadingSpinner loadingSpinner; + private Box iconBackground = null!; + private LoadingSpinner loadingSpinner = null!; private readonly TextFlowContainer textDrawable; @@ -222,18 +232,6 @@ namespace osu.Game.Overlays.Notifications } } - public Func CancelRequested { get; set; } - - /// - /// The function to post completion notifications back to. - /// - public Action CompletionTarget { get; set; } - - /// - /// An action to complete when the completion notification is clicked. Return true to close. - /// - public Func CompletionClickAction; - private class ProgressBar : Container { private readonly Box box; diff --git a/osu.Game/Overlays/Notifications/SimpleNotification.cs b/osu.Game/Overlays/Notifications/SimpleNotification.cs index fc594902cf..b9a1cc6d90 100644 --- a/osu.Game/Overlays/Notifications/SimpleNotification.cs +++ b/osu.Game/Overlays/Notifications/SimpleNotification.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. -#nullable disable - using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; From 1484ae19f0a7a3f4a069bf8628dd5c8805d923db Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 30 Aug 2022 17:33:08 +0900 Subject: [PATCH 189/420] Initial design update pass --- osu.Game/Overlays/NotificationOverlay.cs | 6 +- .../Overlays/Notifications/Notification.cs | 147 ++++++++++-------- .../Notifications/ProgressNotification.cs | 3 +- .../Notifications/SimpleNotification.cs | 32 ++-- 4 files changed, 100 insertions(+), 88 deletions(-) diff --git a/osu.Game/Overlays/NotificationOverlay.cs b/osu.Game/Overlays/NotificationOverlay.cs index cbcc7b6886..bd483e073c 100644 --- a/osu.Game/Overlays/NotificationOverlay.cs +++ b/osu.Game/Overlays/NotificationOverlay.cs @@ -12,7 +12,6 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Localisation; using osu.Framework.Logging; using osu.Framework.Threading; -using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Overlays.Notifications; using osu.Game.Resources.Localisation.Web; @@ -35,6 +34,9 @@ namespace osu.Game.Overlays [Resolved] private AudioManager audio { get; set; } = null!; + [Cached] + private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple); + private readonly IBindable firstRunSetupVisibility = new Bindable(); [BackgroundDependencyLoader] @@ -49,7 +51,7 @@ namespace osu.Game.Overlays new Box { RelativeSizeAxes = Axes.Both, - Colour = OsuColour.Gray(0.05f), + Colour = colourProvider.Background4, }, new OsuScrollContainer { diff --git a/osu.Game/Overlays/Notifications/Notification.cs b/osu.Game/Overlays/Notifications/Notification.cs index fbb906e637..5cd0db628d 100644 --- a/osu.Game/Overlays/Notifications/Notification.cs +++ b/osu.Game/Overlays/Notifications/Notification.cs @@ -12,7 +12,6 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; using osu.Framework.Localisation; -using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osuTK; using osuTK.Graphics; @@ -46,8 +45,9 @@ namespace osu.Game.Overlays.Notifications public virtual string PopInSampleName => "UI/notification-pop-in"; protected NotificationLight Light; - private readonly CloseButton closeButton; + protected Container IconContent; + private readonly Container content; protected override Container Content => content; @@ -61,7 +61,7 @@ namespace osu.Game.Overlays.Notifications RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; - AddRangeInternal(new Drawable[] + InternalChildren = new Drawable[] { Light = new NotificationLight { @@ -79,64 +79,68 @@ namespace osu.Game.Overlays.Notifications AutoSizeEasing = Easing.OutQuint, Children = new Drawable[] { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.White, - }, - new Container + new GridContainer { RelativeSizeAxes = Axes.X, - Padding = new MarginPadding(5), AutoSizeAxes = Axes.Y, - Children = new Drawable[] + RowDimensions = new[] { - IconContent = new Container - { - Size = new Vector2(40), - Masking = true, - CornerRadius = 5, - }, - content = new Container - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Padding = new MarginPadding - { - Left = 45, - Right = 30 - }, - } - } - }, - closeButton = new CloseButton - { - Alpha = 0, - Action = Close, - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, - Margin = new MarginPadding - { - Right = 5 + new Dimension(GridSizeMode.AutoSize, minSize: 60) }, - } + ColumnDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize), + new Dimension(), + new Dimension(GridSizeMode.AutoSize), + }, + Content = new[] + { + new Drawable[] + { + IconContent = new Container + { + Width = 40, + RelativeSizeAxes = Axes.Y, + }, + new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding(10), + Children = new Drawable[] + { + content = new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + }, + } + }, + new CloseButton + { + Action = Close, + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + } + } + }, + }, } } + }; + } + + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider) + { + NotificationContent.Add(new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colourProvider.Background3, + Depth = float.MaxValue }); } - protected override bool OnHover(HoverEvent e) - { - closeButton.FadeIn(75); - return base.OnHover(e); - } - - protected override void OnHoverLost(HoverLostEvent e) - { - closeButton.FadeOut(75); - base.OnHoverLost(e); - } - protected override bool OnClick(ClickEvent e) { if (Activated?.Invoke() ?? true) @@ -150,6 +154,7 @@ namespace osu.Game.Overlays.Notifications base.LoadComplete(); this.FadeInFromZero(200); + NotificationContent.MoveToX(DrawSize.X); NotificationContent.MoveToX(0, 500, Easing.OutQuint); } @@ -169,40 +174,48 @@ namespace osu.Game.Overlays.Notifications private class CloseButton : OsuClickableContainer { - private Color4 hoverColour; + private SpriteIcon icon = null!; + private Box background = null!; - public CloseButton() + [Resolved] + private OverlayColourProvider colourProvider { get; set; } = null!; + + [BackgroundDependencyLoader] + private void load() { - Colour = OsuColour.Gray(0.2f); - AutoSizeAxes = Axes.Both; + RelativeSizeAxes = Axes.Y; + Width = 24; - Children = new[] + Children = new Drawable[] { - new SpriteIcon + background = new Box + { + Colour = colourProvider.Background4, + Alpha = 0, + RelativeSizeAxes = Axes.Both, + }, + icon = new SpriteIcon { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Icon = FontAwesome.Solid.TimesCircle, - Size = new Vector2(20), + Icon = FontAwesome.Solid.Check, + Size = new Vector2(12), + Colour = colourProvider.Foreground1, } }; } - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - hoverColour = colours.Yellow; - } - protected override bool OnHover(HoverEvent e) { - this.FadeColour(hoverColour, 200); + background.FadeIn(200); + icon.FadeColour(colourProvider.Content1, 200); return base.OnHover(e); } protected override void OnHoverLost(HoverLostEvent e) { - this.FadeColour(OsuColour.Gray(0.2f), 200); + background.FadeOut(200); + icon.FadeColour(colourProvider.Foreground1, 200); base.OnHoverLost(e); } } diff --git a/osu.Game/Overlays/Notifications/ProgressNotification.cs b/osu.Game/Overlays/Notifications/ProgressNotification.cs index 15346930a3..6390d9a54f 100644 --- a/osu.Game/Overlays/Notifications/ProgressNotification.cs +++ b/osu.Game/Overlays/Notifications/ProgressNotification.cs @@ -57,7 +57,7 @@ namespace osu.Game.Overlays.Notifications set { progress = value; - Scheduler.AddOnce(updateProgress, progress); + Scheduler.AddOnce(p => progressBar.Progress = p, progress); } } @@ -174,7 +174,6 @@ namespace osu.Game.Overlays.Notifications { Content.Add(textDrawable = new OsuTextFlowContainer { - Colour = OsuColour.Gray(128), AutoSizeAxes = Axes.Y, RelativeSizeAxes = Axes.X, }); diff --git a/osu.Game/Overlays/Notifications/SimpleNotification.cs b/osu.Game/Overlays/Notifications/SimpleNotification.cs index b9a1cc6d90..ae4d183258 100644 --- a/osu.Game/Overlays/Notifications/SimpleNotification.cs +++ b/osu.Game/Overlays/Notifications/SimpleNotification.cs @@ -3,7 +3,6 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; -using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; @@ -24,7 +23,8 @@ namespace osu.Game.Overlays.Notifications set { text = value; - textDrawable.Text = text; + if (textDrawable != null) + textDrawable.Text = text; } } @@ -36,48 +36,46 @@ namespace osu.Game.Overlays.Notifications set { icon = value; - iconDrawable.Icon = icon; + if (iconDrawable != null) + iconDrawable.Icon = icon; } } - private readonly TextFlowContainer textDrawable; - private readonly SpriteIcon iconDrawable; + protected Box IconBackground = null!; - protected Box IconBackground; + private TextFlowContainer? textDrawable; - public SimpleNotification() + private SpriteIcon? iconDrawable; + + [BackgroundDependencyLoader] + private void load(OsuColour colours, OverlayColourProvider colourProvider) { + Light.Colour = colours.Green; + IconContent.AddRange(new Drawable[] { IconBackground = new Box { RelativeSizeAxes = Axes.Both, - Colour = ColourInfo.GradientVertical(OsuColour.Gray(0.2f), OsuColour.Gray(0.6f)) + Colour = colourProvider.Background5, }, iconDrawable = new SpriteIcon { Anchor = Anchor.Centre, Origin = Anchor.Centre, Icon = icon, - Size = new Vector2(20), + Size = new Vector2(16), } }); - Content.Add(textDrawable = new OsuTextFlowContainer(t => t.Font = t.Font.With(size: 14)) + Content.Add(textDrawable = new OsuTextFlowContainer(t => t.Font = t.Font.With(size: 14, weight: FontWeight.Medium)) { - Colour = OsuColour.Gray(128), AutoSizeAxes = Axes.Y, RelativeSizeAxes = Axes.X, Text = text }); } - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - Light.Colour = colours.Green; - } - public override bool Read { get => base.Read; From 0f203531d938fc39e0b8c99274cf35ccf844b5a3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 30 Aug 2022 17:37:43 +0900 Subject: [PATCH 190/420] Allow customising the "close" button icon --- osu.Game/Overlays/Notifications/Notification.cs | 13 +++++++++++-- .../Overlays/Notifications/ProgressNotification.cs | 5 ++++- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Notifications/Notification.cs b/osu.Game/Overlays/Notifications/Notification.cs index 5cd0db628d..2b57423305 100644 --- a/osu.Game/Overlays/Notifications/Notification.cs +++ b/osu.Game/Overlays/Notifications/Notification.cs @@ -56,6 +56,8 @@ namespace osu.Game.Overlays.Notifications public virtual bool Read { get; set; } + protected virtual IconUsage CloseButtonIcon => FontAwesome.Solid.Check; + protected Notification() { RelativeSizeAxes = Axes.X; @@ -116,7 +118,7 @@ namespace osu.Game.Overlays.Notifications }, } }, - new CloseButton + new CloseButton(CloseButtonIcon) { Action = Close, Anchor = Anchor.TopRight, @@ -177,9 +179,16 @@ namespace osu.Game.Overlays.Notifications private SpriteIcon icon = null!; private Box background = null!; + private readonly IconUsage iconUsage; + [Resolved] private OverlayColourProvider colourProvider { get; set; } = null!; + public CloseButton(IconUsage iconUsage) + { + this.iconUsage = iconUsage; + } + [BackgroundDependencyLoader] private void load() { @@ -198,7 +207,7 @@ namespace osu.Game.Overlays.Notifications { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Icon = FontAwesome.Solid.Check, + Icon = iconUsage, Size = new Vector2(12), Colour = colourProvider.Foreground1, } diff --git a/osu.Game/Overlays/Notifications/ProgressNotification.cs b/osu.Game/Overlays/Notifications/ProgressNotification.cs index 6390d9a54f..594537bce7 100644 --- a/osu.Game/Overlays/Notifications/ProgressNotification.cs +++ b/osu.Game/Overlays/Notifications/ProgressNotification.cs @@ -61,7 +61,10 @@ namespace osu.Game.Overlays.Notifications } } - private void updateProgress(float progress) => progressBar.Progress = progress; + protected override IconUsage CloseButtonIcon => FontAwesome.Solid.Times; + + [Resolved] + private OverlayColourProvider colourProvider { get; set; } = null!; protected override void LoadComplete() { From 09aa3e065d790ad2f1cb44024dc7b06bd9cc8b73 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 30 Aug 2022 17:40:35 +0900 Subject: [PATCH 191/420] Move colouring to full icon content rather than background --- osu.Desktop/Security/ElevatedPrivilegesChecker.cs | 2 +- osu.Game/Database/TooManyDownloadsNotification.cs | 2 +- osu.Game/Online/Chat/MessageNotifier.cs | 2 +- .../Notifications/ProgressCompletionNotification.cs | 2 +- .../Overlays/Notifications/ProgressNotification.cs | 11 +++++------ osu.Game/Overlays/Notifications/SimpleNotification.cs | 4 +--- osu.Game/Screens/Play/PlayerLoader.cs | 4 ++-- osu.Game/Updater/UpdateManager.cs | 2 +- 8 files changed, 13 insertions(+), 16 deletions(-) diff --git a/osu.Desktop/Security/ElevatedPrivilegesChecker.cs b/osu.Desktop/Security/ElevatedPrivilegesChecker.cs index 9959b24b35..cc4337fb02 100644 --- a/osu.Desktop/Security/ElevatedPrivilegesChecker.cs +++ b/osu.Desktop/Security/ElevatedPrivilegesChecker.cs @@ -76,7 +76,7 @@ namespace osu.Desktop.Security private void load(OsuColour colours) { Icon = FontAwesome.Solid.ShieldAlt; - IconBackground.Colour = colours.YellowDark; + IconContent.Colour = colours.YellowDark; } } } diff --git a/osu.Game/Database/TooManyDownloadsNotification.cs b/osu.Game/Database/TooManyDownloadsNotification.cs index aa88fed43c..14012e1d34 100644 --- a/osu.Game/Database/TooManyDownloadsNotification.cs +++ b/osu.Game/Database/TooManyDownloadsNotification.cs @@ -20,7 +20,7 @@ namespace osu.Game.Database [BackgroundDependencyLoader] private void load(OsuColour colours) { - IconBackground.Colour = colours.RedDark; + IconContent.Colour = colours.RedDark; } } } diff --git a/osu.Game/Online/Chat/MessageNotifier.cs b/osu.Game/Online/Chat/MessageNotifier.cs index 3fc6a008e8..22c2b4690e 100644 --- a/osu.Game/Online/Chat/MessageNotifier.cs +++ b/osu.Game/Online/Chat/MessageNotifier.cs @@ -174,7 +174,7 @@ namespace osu.Game.Online.Chat [BackgroundDependencyLoader] private void load(OsuColour colours, ChatOverlay chatOverlay, INotificationOverlay notificationOverlay) { - IconBackground.Colour = colours.PurpleDark; + IconContent.Colour = colours.PurpleDark; Activated = delegate { diff --git a/osu.Game/Overlays/Notifications/ProgressCompletionNotification.cs b/osu.Game/Overlays/Notifications/ProgressCompletionNotification.cs index cb9d54c14c..49d558285c 100644 --- a/osu.Game/Overlays/Notifications/ProgressCompletionNotification.cs +++ b/osu.Game/Overlays/Notifications/ProgressCompletionNotification.cs @@ -20,7 +20,7 @@ namespace osu.Game.Overlays.Notifications [BackgroundDependencyLoader] private void load(OsuColour colours) { - IconBackground.Colour = ColourInfo.GradientVertical(colours.GreenDark, colours.GreenLight); + IconContent.Colour = ColourInfo.GradientVertical(colours.GreenDark, colours.GreenLight); } } } diff --git a/osu.Game/Overlays/Notifications/ProgressNotification.cs b/osu.Game/Overlays/Notifications/ProgressNotification.cs index 594537bce7..c0342f1c2a 100644 --- a/osu.Game/Overlays/Notifications/ProgressNotification.cs +++ b/osu.Game/Overlays/Notifications/ProgressNotification.cs @@ -103,7 +103,7 @@ namespace osu.Game.Overlays.Notifications Light.Pulsate = false; progressBar.Active = false; - iconBackground.FadeColour(ColourInfo.GradientVertical(colourQueued, colourQueued.Lighten(0.5f)), colour_fade_duration); + IconContent.FadeColour(ColourInfo.GradientVertical(colourQueued, colourQueued.Lighten(0.5f)), colour_fade_duration); loadingSpinner.Show(); break; @@ -112,14 +112,14 @@ namespace osu.Game.Overlays.Notifications Light.Pulsate = true; progressBar.Active = true; - iconBackground.FadeColour(ColourInfo.GradientVertical(colourActive, colourActive.Lighten(0.5f)), colour_fade_duration); + IconContent.FadeColour(ColourInfo.GradientVertical(colourActive, colourActive.Lighten(0.5f)), colour_fade_duration); loadingSpinner.Show(); break; case ProgressNotificationState.Cancelled: cancellationTokenSource.Cancel(); - iconBackground.FadeColour(ColourInfo.GradientVertical(Color4.Gray, Color4.Gray.Lighten(0.5f)), colour_fade_duration); + IconContent.FadeColour(ColourInfo.GradientVertical(Color4.Gray, Color4.Gray.Lighten(0.5f)), colour_fade_duration); loadingSpinner.Hide(); var icon = new SpriteIcon @@ -168,7 +168,6 @@ namespace osu.Game.Overlays.Notifications private Color4 colourActive; private Color4 colourCancelled; - private Box iconBackground = null!; private LoadingSpinner loadingSpinner = null!; private readonly TextFlowContainer textDrawable; @@ -206,10 +205,10 @@ namespace osu.Game.Overlays.Notifications IconContent.AddRange(new Drawable[] { - iconBackground = new Box + new Box { RelativeSizeAxes = Axes.Both, - Colour = Color4.White, + Colour = colourProvider.Background5, }, loadingSpinner = new LoadingSpinner { diff --git a/osu.Game/Overlays/Notifications/SimpleNotification.cs b/osu.Game/Overlays/Notifications/SimpleNotification.cs index ae4d183258..1dba60fb5f 100644 --- a/osu.Game/Overlays/Notifications/SimpleNotification.cs +++ b/osu.Game/Overlays/Notifications/SimpleNotification.cs @@ -41,8 +41,6 @@ namespace osu.Game.Overlays.Notifications } } - protected Box IconBackground = null!; - private TextFlowContainer? textDrawable; private SpriteIcon? iconDrawable; @@ -54,7 +52,7 @@ namespace osu.Game.Overlays.Notifications IconContent.AddRange(new Drawable[] { - IconBackground = new Box + new Box { RelativeSizeAxes = Axes.Both, Colour = colourProvider.Background5, diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index e6bd1367ef..a9fcab063c 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -530,7 +530,7 @@ namespace osu.Game.Screens.Play private void load(OsuColour colours, AudioManager audioManager, INotificationOverlay notificationOverlay, VolumeOverlay volumeOverlay) { Icon = FontAwesome.Solid.VolumeMute; - IconBackground.Colour = colours.RedDark; + IconContent.Colour = colours.RedDark; Activated = delegate { @@ -584,7 +584,7 @@ namespace osu.Game.Screens.Play private void load(OsuColour colours, INotificationOverlay notificationOverlay) { Icon = FontAwesome.Solid.BatteryQuarter; - IconBackground.Colour = colours.RedDark; + IconContent.Colour = colours.RedDark; Activated = delegate { diff --git a/osu.Game/Updater/UpdateManager.cs b/osu.Game/Updater/UpdateManager.cs index d60f2e4a4b..4790055cd1 100644 --- a/osu.Game/Updater/UpdateManager.cs +++ b/osu.Game/Updater/UpdateManager.cs @@ -99,7 +99,7 @@ namespace osu.Game.Updater private void load(OsuColour colours, ChangelogOverlay changelog, INotificationOverlay notificationOverlay) { Icon = FontAwesome.Solid.CheckSquare; - IconBackground.Colour = colours.BlueDark; + IconContent.Colour = colours.BlueDark; Activated = delegate { From bea12ab3c262bb777dbba82b4fbc74ce1dd38c60 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 30 Aug 2022 17:49:29 +0900 Subject: [PATCH 192/420] Rename `NotificationContent` to `MainContent` --- osu.Game/Overlays/Notifications/Notification.cs | 10 +++++----- .../Overlays/Notifications/ProgressNotification.cs | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game/Overlays/Notifications/Notification.cs b/osu.Game/Overlays/Notifications/Notification.cs index 2b57423305..b04e732f92 100644 --- a/osu.Game/Overlays/Notifications/Notification.cs +++ b/osu.Game/Overlays/Notifications/Notification.cs @@ -52,7 +52,7 @@ namespace osu.Game.Overlays.Notifications protected override Container Content => content; - protected Container NotificationContent; + protected Container MainContent; public virtual bool Read { get; set; } @@ -71,7 +71,7 @@ namespace osu.Game.Overlays.Notifications Anchor = Anchor.CentreLeft, Origin = Anchor.CentreRight, }, - NotificationContent = new Container + MainContent = new Container { CornerRadius = 8, Masking = true, @@ -135,7 +135,7 @@ namespace osu.Game.Overlays.Notifications [BackgroundDependencyLoader] private void load(OverlayColourProvider colourProvider) { - NotificationContent.Add(new Box + MainContent.Add(new Box { RelativeSizeAxes = Axes.Both, Colour = colourProvider.Background3, @@ -157,8 +157,8 @@ namespace osu.Game.Overlays.Notifications this.FadeInFromZero(200); - NotificationContent.MoveToX(DrawSize.X); - NotificationContent.MoveToX(0, 500, Easing.OutQuint); + MainContent.MoveToX(DrawSize.X); + MainContent.MoveToX(0, 500, Easing.OutQuint); } public bool WasClosed; diff --git a/osu.Game/Overlays/Notifications/ProgressNotification.cs b/osu.Game/Overlays/Notifications/ProgressNotification.cs index c0342f1c2a..2f813b4ad5 100644 --- a/osu.Game/Overlays/Notifications/ProgressNotification.cs +++ b/osu.Game/Overlays/Notifications/ProgressNotification.cs @@ -141,7 +141,7 @@ namespace osu.Game.Overlays.Notifications case ProgressNotificationState.Completed: loadingSpinner.Hide(); - NotificationContent.MoveToY(-DrawSize.Y / 2, 200, Easing.OutQuint); + MainContent.MoveToY(-DrawSize.Y / 2, 200, Easing.OutQuint); this.FadeOut(200).Finally(_ => Completed()); break; } @@ -180,7 +180,7 @@ namespace osu.Game.Overlays.Notifications RelativeSizeAxes = Axes.X, }); - NotificationContent.Add(progressBar = new ProgressBar + MainContent.Add(progressBar = new ProgressBar { Origin = Anchor.BottomLeft, Anchor = Anchor.BottomLeft, From c846bf20a76ffb9adf5c698f64f3c668b04f90aa Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 30 Aug 2022 17:58:32 +0900 Subject: [PATCH 193/420] Add background hover and adjust remaining metrics --- .../Overlays/Notifications/Notification.cs | 33 ++++++++++++++----- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/osu.Game/Overlays/Notifications/Notification.cs b/osu.Game/Overlays/Notifications/Notification.cs index b04e732f92..a759e2efd1 100644 --- a/osu.Game/Overlays/Notifications/Notification.cs +++ b/osu.Game/Overlays/Notifications/Notification.cs @@ -58,6 +58,11 @@ namespace osu.Game.Overlays.Notifications protected virtual IconUsage CloseButtonIcon => FontAwesome.Solid.Check; + [Resolved] + private OverlayColourProvider colourProvider { get; set; } = null!; + + private Box background = null!; + protected Notification() { RelativeSizeAxes = Axes.X; @@ -73,7 +78,7 @@ namespace osu.Game.Overlays.Notifications }, MainContent = new Container { - CornerRadius = 8, + CornerRadius = 6, Masking = true, RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, @@ -133,9 +138,9 @@ namespace osu.Game.Overlays.Notifications } [BackgroundDependencyLoader] - private void load(OverlayColourProvider colourProvider) + private void load() { - MainContent.Add(new Box + MainContent.Add(background = new Box { RelativeSizeAxes = Axes.Both, Colour = colourProvider.Background3, @@ -143,6 +148,18 @@ namespace osu.Game.Overlays.Notifications }); } + protected override bool OnHover(HoverEvent e) + { + background.FadeColour(colourProvider.Background2, 200, Easing.OutQuint); + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + background.FadeColour(colourProvider.Background3, 200, Easing.OutQuint); + base.OnHoverLost(e); + } + protected override bool OnClick(ClickEvent e) { if (Activated?.Invoke() ?? true) @@ -193,7 +210,7 @@ namespace osu.Game.Overlays.Notifications private void load() { RelativeSizeAxes = Axes.Y; - Width = 24; + Width = 28; Children = new Drawable[] { @@ -216,15 +233,15 @@ namespace osu.Game.Overlays.Notifications protected override bool OnHover(HoverEvent e) { - background.FadeIn(200); - icon.FadeColour(colourProvider.Content1, 200); + background.FadeIn(200, Easing.OutQuint); + icon.FadeColour(colourProvider.Content1, 200, Easing.OutQuint); return base.OnHover(e); } protected override void OnHoverLost(HoverLostEvent e) { - background.FadeOut(200); - icon.FadeColour(colourProvider.Foreground1, 200); + background.FadeOut(200, Easing.OutQuint); + icon.FadeColour(colourProvider.Foreground1, 200, Easing.OutQuint); base.OnHoverLost(e); } } From d600058c98419657c7f4b9d630b34ff8b5d2a613 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 30 Aug 2022 18:09:10 +0900 Subject: [PATCH 194/420] Assert non-null in `ProfileHeader` to appease r# --- osu.Game/Overlays/Profile/ProfileHeader.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game/Overlays/Profile/ProfileHeader.cs b/osu.Game/Overlays/Profile/ProfileHeader.cs index f84f829f7a..1eca6a81cf 100644 --- a/osu.Game/Overlays/Profile/ProfileHeader.cs +++ b/osu.Game/Overlays/Profile/ProfileHeader.cs @@ -3,6 +3,7 @@ #nullable disable +using System.Diagnostics; using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; @@ -37,6 +38,10 @@ namespace osu.Game.Overlays.Profile // todo: pending implementation. // TabControl.AddItem(LayoutStrings.HeaderUsersModding); + // Haphazardly guaranteed by OverlayHeader constructor (see CreateBackground / CreateContent). + Debug.Assert(centreHeaderContainer != null); + Debug.Assert(detailHeaderContainer != null); + centreHeaderContainer.DetailsVisible.BindValueChanged(visible => detailHeaderContainer.Expanded = visible.NewValue, true); } From 928bce8fcdee3daf785539cd068b48274eae30e0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 30 Aug 2022 18:18:46 +0900 Subject: [PATCH 195/420] Fix crash when attempting to watch a replay when the storage file doesn't exist --- osu.Game/Scoring/LegacyDatabasedScore.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game/Scoring/LegacyDatabasedScore.cs b/osu.Game/Scoring/LegacyDatabasedScore.cs index de35cb5faf..a7641c7999 100644 --- a/osu.Game/Scoring/LegacyDatabasedScore.cs +++ b/osu.Game/Scoring/LegacyDatabasedScore.cs @@ -25,7 +25,12 @@ namespace osu.Game.Scoring return; using (var stream = store.GetStream(replayFilename)) + { + if (stream == null) + return; + Replay = new DatabasedLegacyScoreDecoder(rulesets, beatmaps).Parse(stream).Replay; + } } } } From 6b71b4656d6a10e085d9ab9119110293c7250cbd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 30 Aug 2022 19:16:59 +0900 Subject: [PATCH 196/420] Remove `ProgressNotification` vertical movement and delay --- osu.Game/Overlays/Notifications/ProgressNotification.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Notifications/ProgressNotification.cs b/osu.Game/Overlays/Notifications/ProgressNotification.cs index 2f813b4ad5..36b8bac873 100644 --- a/osu.Game/Overlays/Notifications/ProgressNotification.cs +++ b/osu.Game/Overlays/Notifications/ProgressNotification.cs @@ -141,8 +141,7 @@ namespace osu.Game.Overlays.Notifications case ProgressNotificationState.Completed: loadingSpinner.Hide(); - MainContent.MoveToY(-DrawSize.Y / 2, 200, Easing.OutQuint); - this.FadeOut(200).Finally(_ => Completed()); + Completed(); break; } } From 60413e3e7bac003d6d7ceab82b704d4efd236bb5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 30 Aug 2022 19:17:40 +0900 Subject: [PATCH 197/420] Enable masking for main content to avoid underlap with close button on word wrap failure. --- osu.Game/Overlays/Notifications/Notification.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Overlays/Notifications/Notification.cs b/osu.Game/Overlays/Notifications/Notification.cs index a759e2efd1..90d9b88b79 100644 --- a/osu.Game/Overlays/Notifications/Notification.cs +++ b/osu.Game/Overlays/Notifications/Notification.cs @@ -118,6 +118,7 @@ namespace osu.Game.Overlays.Notifications { content = new Container { + Masking = true, RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, }, From 7b006f1f2249278d67a073bbd747e0957aede85a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 30 Aug 2022 20:34:27 +0900 Subject: [PATCH 198/420] Add flash when a new notification is displayed to draw attention --- osu.Game/Overlays/Notifications/Notification.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/osu.Game/Overlays/Notifications/Notification.cs b/osu.Game/Overlays/Notifications/Notification.cs index 90d9b88b79..3407995502 100644 --- a/osu.Game/Overlays/Notifications/Notification.cs +++ b/osu.Game/Overlays/Notifications/Notification.cs @@ -61,6 +61,8 @@ namespace osu.Game.Overlays.Notifications [Resolved] private OverlayColourProvider colourProvider { get; set; } = null!; + private readonly Box initialFlash; + private Box background = null!; protected Notification() @@ -133,6 +135,12 @@ namespace osu.Game.Overlays.Notifications } }, }, + initialFlash = new Box + { + Colour = Color4.White.Opacity(0.8f), + RelativeSizeAxes = Axes.Both, + Blending = BlendingParameters.Additive, + }, } } }; @@ -177,6 +185,8 @@ namespace osu.Game.Overlays.Notifications MainContent.MoveToX(DrawSize.X); MainContent.MoveToX(0, 500, Easing.OutQuint); + + initialFlash.FadeOutFromOne(2000, Easing.OutQuart); } public bool WasClosed; From b8300ae60a29216e63790857e486614f3c295eac Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 30 Aug 2022 20:57:12 +0900 Subject: [PATCH 199/420] Add toast notification tray --- osu.Game/Overlays/NotificationOverlay.cs | 81 +++++++++--- .../Overlays/NotificationOverlayToastTray.cs | 124 ++++++++++++++++++ 2 files changed, 184 insertions(+), 21 deletions(-) create mode 100644 osu.Game/Overlays/NotificationOverlayToastTray.cs diff --git a/osu.Game/Overlays/NotificationOverlay.cs b/osu.Game/Overlays/NotificationOverlay.cs index bd483e073c..096de558dd 100644 --- a/osu.Game/Overlays/NotificationOverlay.cs +++ b/osu.Game/Overlays/NotificationOverlay.cs @@ -15,6 +15,7 @@ using osu.Framework.Threading; using osu.Game.Graphics.Containers; using osu.Game.Overlays.Notifications; using osu.Game.Resources.Localisation.Web; +using osuTK; using NotificationsStrings = osu.Game.Localisation.NotificationsStrings; namespace osu.Game.Overlays @@ -39,6 +40,23 @@ namespace osu.Game.Overlays private readonly IBindable firstRunSetupVisibility = new Bindable(); + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) + { + if (State.Value == Visibility.Visible) + return base.ReceivePositionalInputAt(screenSpacePos); + + if (toastTray.IsDisplayingToasts) + return toastTray.ReceivePositionalInputAt(screenSpacePos); + + return false; + } + + public override bool PropagatePositionalInputSubTree => base.PropagatePositionalInputSubTree || toastTray.IsDisplayingToasts; + + private NotificationOverlayToastTray toastTray = null!; + + private Container mainContent = null!; + [BackgroundDependencyLoader] private void load(FirstRunSetupOverlay? firstRunSetup) { @@ -48,30 +66,41 @@ namespace osu.Game.Overlays Children = new Drawable[] { - new Box + toastTray = new NotificationOverlayToastTray { - RelativeSizeAxes = Axes.Both, - Colour = colourProvider.Background4, + Origin = Anchor.TopRight, }, - new OsuScrollContainer + mainContent = new Container { - Masking = true, RelativeSizeAxes = Axes.Both, - Children = new[] + Children = new Drawable[] { - sections = new FillFlowContainer + new Box { - Direction = FillDirection.Vertical, - AutoSizeAxes = Axes.Y, - RelativeSizeAxes = Axes.X, + RelativeSizeAxes = Axes.Both, + Colour = colourProvider.Background4, + }, + new OsuScrollContainer + { + Masking = true, + RelativeSizeAxes = Axes.Both, Children = new[] { - new NotificationSection(AccountsStrings.NotificationsTitle, new[] { typeof(SimpleNotification) }, "Clear All"), - new NotificationSection(@"Running Tasks", new[] { typeof(ProgressNotification) }, @"Cancel All"), + sections = new FillFlowContainer + { + Direction = FillDirection.Vertical, + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + Children = new[] + { + new NotificationSection(AccountsStrings.NotificationsTitle, new[] { typeof(SimpleNotification) }, "Clear All"), + new NotificationSection(@"Running Tasks", new[] { typeof(ProgressNotification) }, @"Cancel All"), + } + } } } } - } + }, }; if (firstRunSetup != null) @@ -129,14 +158,22 @@ namespace osu.Game.Overlays var ourType = notification.GetType(); - var section = sections.Children.FirstOrDefault(s => s.AcceptedNotificationTypes.Any(accept => accept.IsAssignableFrom(ourType))); - section?.Add(notification, notification.DisplayOnTop ? -runningDepth : runningDepth); + int depth = notification.DisplayOnTop ? -runningDepth : runningDepth; - if (notification.IsImportant) - Show(); + if (State.Value == Visibility.Hidden) + toastTray.Post(notification, addPermanently); + else + addPermanently(); - updateCounts(); - playDebouncedSample(notification.PopInSampleName); + void addPermanently() + { + var section = sections.Children.First(s => s.AcceptedNotificationTypes.Any(accept => accept.IsAssignableFrom(ourType))); + + section.Add(notification, depth); + + updateCounts(); + playDebouncedSample(notification.PopInSampleName); + } }); protected override void Update() @@ -152,7 +189,9 @@ namespace osu.Game.Overlays base.PopIn(); this.MoveToX(0, TRANSITION_LENGTH, Easing.OutQuint); - this.FadeTo(1, TRANSITION_LENGTH, Easing.OutQuint); + mainContent.FadeTo(1, TRANSITION_LENGTH, Easing.OutQuint); + + toastTray.FlushAllToasts(); } protected override void PopOut() @@ -162,7 +201,7 @@ namespace osu.Game.Overlays markAllRead(); this.MoveToX(WIDTH, TRANSITION_LENGTH, Easing.OutQuint); - this.FadeTo(0, TRANSITION_LENGTH, Easing.OutQuint); + mainContent.FadeTo(0, TRANSITION_LENGTH, Easing.OutQuint); } private void notificationClosed() diff --git a/osu.Game/Overlays/NotificationOverlayToastTray.cs b/osu.Game/Overlays/NotificationOverlayToastTray.cs new file mode 100644 index 0000000000..338e84c472 --- /dev/null +++ b/osu.Game/Overlays/NotificationOverlayToastTray.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 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.Effects; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Threading; +using osu.Framework.Utils; +using osu.Game.Overlays.Notifications; +using osuTK; + +namespace osu.Game.Overlays +{ + /// + /// A tray which attaches to the left of to show temporary toasts. + /// + public class NotificationOverlayToastTray : CompositeDrawable + { + public bool IsDisplayingToasts => toastFlow.Count > 0; + + private FillFlowContainer toastFlow = null!; + private BufferedContainer toastContentBackground = null!; + + [Resolved] + private OverlayColourProvider colourProvider { get; set; } = null!; + + private readonly List pendingToastOperations = new List(); + + private int runningDepth; + + [BackgroundDependencyLoader] + private void load() + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + Padding = new MarginPadding(20); + + InternalChildren = new Drawable[] + { + toastContentBackground = (new Box + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + Colour = ColourInfo.GradientVertical( + colourProvider.Background6.Opacity(0.7f), + colourProvider.Background6.Opacity(0.5f)), + RelativeSizeAxes = Axes.Both, + }.WithEffect(new BlurEffect + { + PadExtent = true, + Sigma = new Vector2(20), + }).With(postEffectDrawable => + { + postEffectDrawable.Scale = new Vector2(1.5f, 1); + postEffectDrawable.Position += new Vector2(70, -50); + postEffectDrawable.AutoSizeAxes = Axes.None; + postEffectDrawable.RelativeSizeAxes = Axes.X; + })), + toastFlow = new FillFlowContainer + { + LayoutDuration = 150, + LayoutEasing = Easing.OutQuart, + Spacing = new Vector2(3), + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + }, + }; + } + + public void FlushAllToasts() + { + foreach (var d in pendingToastOperations.Where(d => !d.Completed)) + d.RunTask(); + + pendingToastOperations.Clear(); + } + + public void Post(Notification notification, Action addPermanently) + { + ++runningDepth; + + int depth = notification.DisplayOnTop ? -runningDepth : runningDepth; + + toastFlow.Insert(depth, notification); + + pendingToastOperations.Add(Scheduler.AddDelayed(() => + { + // add notification to permanent overlay unless it was already dismissed by the user. + if (notification.WasClosed) + return; + + toastFlow.Remove(notification); + AddInternal(notification); + + notification.MoveToOffset(new Vector2(400, 0), NotificationOverlay.TRANSITION_LENGTH, Easing.OutQuint); + notification.FadeOut(NotificationOverlay.TRANSITION_LENGTH, Easing.OutQuint).OnComplete(_ => + { + RemoveInternal(notification); + addPermanently(); + + notification.FadeIn(300, Easing.OutQuint); + }); + }, notification.IsImportant ? 15000 : 3000)); + } + + protected override void Update() + { + base.Update(); + + float height = toastFlow.DrawHeight + 120; + float alpha = IsDisplayingToasts ? MathHelper.Clamp(toastFlow.DrawHeight / 40, 0, 1) * toastFlow.Children.Max(n => n.Alpha) : 0; + + toastContentBackground.Height = (float)Interpolation.DampContinuously(toastContentBackground.Height, height, 10, Clock.ElapsedFrameTime); + toastContentBackground.Alpha = (float)Interpolation.DampContinuously(toastContentBackground.Alpha, alpha, 10, Clock.ElapsedFrameTime); + } + } +} From e9cfaa76c954bf2375f3f0c44b24354a2ead697d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 30 Aug 2022 21:04:28 +0900 Subject: [PATCH 200/420] Change global overlay ordering so notification toasts display above settings --- 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 f7747c5d64..108153fd9d 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -804,8 +804,8 @@ namespace osu.Game Children = new Drawable[] { overlayContent = new Container { RelativeSizeAxes = Axes.Both }, - rightFloatingOverlayContent = new Container { RelativeSizeAxes = Axes.Both }, leftFloatingOverlayContent = new Container { RelativeSizeAxes = Axes.Both }, + rightFloatingOverlayContent = new Container { RelativeSizeAxes = Axes.Both }, } }, topMostOverlayContent = new Container { RelativeSizeAxes = Axes.Both }, From 95ce78a50c72df36eaaa2c63101ea9939f930a28 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 30 Aug 2022 21:04:38 +0900 Subject: [PATCH 201/420] Reduce notification post delay now that it's less important --- osu.Game/Overlays/NotificationOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/NotificationOverlay.cs b/osu.Game/Overlays/NotificationOverlay.cs index 096de558dd..b6354179e0 100644 --- a/osu.Game/Overlays/NotificationOverlay.cs +++ b/osu.Game/Overlays/NotificationOverlay.cs @@ -117,7 +117,7 @@ namespace osu.Game.Overlays if (enabled) // we want a slight delay before toggling notifications on to avoid the user becoming overwhelmed. - notificationsEnabler = Scheduler.AddDelayed(() => processingPosts = true, State.Value == Visibility.Visible ? 0 : 1000); + notificationsEnabler = Scheduler.AddDelayed(() => processingPosts = true, State.Value == Visibility.Visible ? 0 : 100); else processingPosts = false; } From a7110666a0c43b214b3f83d48587eadc3067d0ad Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 30 Aug 2022 21:13:27 +0900 Subject: [PATCH 202/420] Play notification appear sample immediately --- osu.Game/Overlays/NotificationOverlay.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/NotificationOverlay.cs b/osu.Game/Overlays/NotificationOverlay.cs index b6354179e0..b0ebf7e666 100644 --- a/osu.Game/Overlays/NotificationOverlay.cs +++ b/osu.Game/Overlays/NotificationOverlay.cs @@ -160,6 +160,8 @@ namespace osu.Game.Overlays int depth = notification.DisplayOnTop ? -runningDepth : runningDepth; + playDebouncedSample(notification.PopInSampleName); + if (State.Value == Visibility.Hidden) toastTray.Post(notification, addPermanently); else @@ -172,7 +174,6 @@ namespace osu.Game.Overlays section.Add(notification, depth); updateCounts(); - playDebouncedSample(notification.PopInSampleName); } }); From 403fc18976e8593e93e7724a9069604fd70a242d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 30 Aug 2022 21:13:37 +0900 Subject: [PATCH 203/420] Fix notification completion events not being run when overlay not visible --- osu.Game/Overlays/NotificationOverlay.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Overlays/NotificationOverlay.cs b/osu.Game/Overlays/NotificationOverlay.cs index b0ebf7e666..80071db37f 100644 --- a/osu.Game/Overlays/NotificationOverlay.cs +++ b/osu.Game/Overlays/NotificationOverlay.cs @@ -72,6 +72,7 @@ namespace osu.Game.Overlays }, mainContent = new Container { + AlwaysPresent = true, RelativeSizeAxes = Axes.Both, Children = new Drawable[] { From 224ab29ef405428d9ee4634879fbd6f10658d507 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 30 Aug 2022 21:19:51 +0900 Subject: [PATCH 204/420] Don't dismiss toasts while hovered (and adjust timings slightly) --- osu.Game/Overlays/NotificationOverlayToastTray.cs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/NotificationOverlayToastTray.cs b/osu.Game/Overlays/NotificationOverlayToastTray.cs index 338e84c472..38f790e88e 100644 --- a/osu.Game/Overlays/NotificationOverlayToastTray.cs +++ b/osu.Game/Overlays/NotificationOverlayToastTray.cs @@ -90,12 +90,20 @@ namespace osu.Game.Overlays toastFlow.Insert(depth, notification); - pendingToastOperations.Add(Scheduler.AddDelayed(() => + pendingToastOperations.Add(scheduleDismissal()); + + ScheduledDelegate scheduleDismissal() => Scheduler.AddDelayed(() => { // add notification to permanent overlay unless it was already dismissed by the user. if (notification.WasClosed) return; + if (notification.IsHovered) + { + pendingToastOperations.Add(scheduleDismissal()); + return; + } + toastFlow.Remove(notification); AddInternal(notification); @@ -107,7 +115,7 @@ namespace osu.Game.Overlays notification.FadeIn(300, Easing.OutQuint); }); - }, notification.IsImportant ? 15000 : 3000)); + }, notification.IsImportant ? 12000 : 2500); } protected override void Update() From b185194d07f0ff1e6ebe7eafb796a85491fab118 Mon Sep 17 00:00:00 2001 From: sw1tchbl4d3 Date: Tue, 30 Aug 2022 14:44:44 +0200 Subject: [PATCH 205/420] Apply comments by smoogi --- .../Skinning/TestSceneDrawableTaikoMascot.cs | 1 - .../TestSceneDrumRollJudgements.cs | 38 ------------------- .../Judgements/TaikoDrumRollJudgement.cs | 16 -------- .../Objects/Drawables/DrawableDrumRoll.cs | 2 +- .../Objects/Drawables/DrawableSwell.cs | 3 +- osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs | 2 +- 6 files changed, 3 insertions(+), 59 deletions(-) delete mode 100644 osu.Game.Rulesets.Taiko.Tests/TestSceneDrumRollJudgements.cs delete mode 100644 osu.Game.Rulesets.Taiko/Judgements/TaikoDrumRollJudgement.cs diff --git a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableTaikoMascot.cs b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableTaikoMascot.cs index ef95358d34..637e355d97 100644 --- a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableTaikoMascot.cs +++ b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableTaikoMascot.cs @@ -118,7 +118,6 @@ 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.Ok }, TaikoMascotAnimationState.Idle); } diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneDrumRollJudgements.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneDrumRollJudgements.cs deleted file mode 100644 index 82dfaecaa4..0000000000 --- a/osu.Game.Rulesets.Taiko.Tests/TestSceneDrumRollJudgements.cs +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -#nullable disable - -using System.Linq; -using NUnit.Framework; -using osu.Game.Beatmaps; -using osu.Game.Rulesets.Taiko.Objects; - -namespace osu.Game.Rulesets.Taiko.Tests -{ - public class TestSceneDrumRollJudgements : TestSceneTaikoPlayer - { - [Test] - public void TestStrongDrumRollFullyJudgedOnKilled() - { - AddUntilStep("gameplay finished", () => Player.ScoreProcessor.HasCompleted.Value); - AddAssert("all judgements are misses", () => Player.Results.All(r => r.Type == r.Judgement.MinResult)); - } - - protected override bool Autoplay => false; - - protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new Beatmap - { - BeatmapInfo = { Ruleset = new TaikoRuleset().RulesetInfo }, - HitObjects = - { - new DrumRoll - { - StartTime = 1000, - Duration = 1000, - IsStrong = true - } - } - }; - } -} diff --git a/osu.Game.Rulesets.Taiko/Judgements/TaikoDrumRollJudgement.cs b/osu.Game.Rulesets.Taiko/Judgements/TaikoDrumRollJudgement.cs deleted file mode 100644 index f7f923e76e..0000000000 --- a/osu.Game.Rulesets.Taiko/Judgements/TaikoDrumRollJudgement.cs +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -#nullable disable - -using osu.Game.Rulesets.Scoring; - -namespace osu.Game.Rulesets.Taiko.Judgements -{ - public class TaikoDrumRollJudgement : TaikoJudgement - { - public override HitResult MaxResult => HitResult.IgnoreHit; - - protected override double HealthIncreaseFor(HitResult result) => 0; - } -} diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs index 418c4673e2..259add81e0 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs @@ -140,7 +140,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables if (timeOffset < 0) return; - ApplyResult(r => r.Type = r.Judgement.MinResult); + ApplyResult(r => r.Type = r.Judgement.MaxResult); } protected override void UpdateHitStateTransforms(ArmedState state) diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs index 84edb30890..a6f6edba09 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs @@ -16,7 +16,6 @@ using osuTK.Graphics; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; using osu.Game.Rulesets.Objects; -using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Taiko.Skinning.Default; using osu.Game.Skinning; @@ -224,7 +223,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables tick.TriggerResult(false); } - ApplyResult(r => r.Type = numHits > HitObject.RequiredHits / 2 ? HitResult.SmallBonus : r.Judgement.MinResult); + ApplyResult(r => r.Type = numHits == HitObject.RequiredHits ? r.Judgement.MaxResult : r.Judgement.MinResult); } } diff --git a/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs b/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs index db752c6691..bd6f10b1a4 100644 --- a/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs +++ b/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs @@ -89,7 +89,7 @@ namespace osu.Game.Rulesets.Taiko.Objects } } - public override Judgement CreateJudgement() => new TaikoDrumRollJudgement(); + public override Judgement CreateJudgement() => new IgnoreJudgement(); protected override HitWindows CreateHitWindows() => HitWindows.Empty; From a9aa928ce6b50162b17ca8bf0b889e4e4e0a0586 Mon Sep 17 00:00:00 2001 From: sw1tchbl4d3 Date: Tue, 30 Aug 2022 15:00:46 +0200 Subject: [PATCH 206/420] Fix test, make strong hits have LargeBonus judgement --- osu.Game.Rulesets.Taiko/Judgements/TaikoStrongJudgement.cs | 2 +- osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Judgements/TaikoStrongJudgement.cs b/osu.Game.Rulesets.Taiko/Judgements/TaikoStrongJudgement.cs index f2397aba22..bafe7dfbaf 100644 --- a/osu.Game.Rulesets.Taiko/Judgements/TaikoStrongJudgement.cs +++ b/osu.Game.Rulesets.Taiko/Judgements/TaikoStrongJudgement.cs @@ -9,7 +9,7 @@ namespace osu.Game.Rulesets.Taiko.Judgements { public class TaikoStrongJudgement : TaikoJudgement { - public override HitResult MaxResult => HitResult.SmallBonus; + public override HitResult MaxResult => HitResult.LargeBonus; // MainObject already changes the HP protected override double HealthIncreaseFor(HitResult result) => 0; diff --git a/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs b/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs index bd6f10b1a4..3dab8d2e13 100644 --- a/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs +++ b/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs @@ -11,7 +11,6 @@ using osu.Game.Beatmaps.Formats; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Scoring; -using osu.Game.Rulesets.Taiko.Judgements; using osuTK; namespace osu.Game.Rulesets.Taiko.Objects From ed11b1ba6f57063527ec2fb96965c64671365b8e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 30 Aug 2022 21:31:06 +0900 Subject: [PATCH 207/420] Improve forwarding flow to not use piling delegates --- osu.Game/Overlays/NotificationOverlay.cs | 30 ++++----- .../Overlays/NotificationOverlayToastTray.cs | 62 +++++++++++-------- 2 files changed, 52 insertions(+), 40 deletions(-) diff --git a/osu.Game/Overlays/NotificationOverlay.cs b/osu.Game/Overlays/NotificationOverlay.cs index 80071db37f..593618ab51 100644 --- a/osu.Game/Overlays/NotificationOverlay.cs +++ b/osu.Game/Overlays/NotificationOverlay.cs @@ -68,6 +68,7 @@ namespace osu.Game.Overlays { toastTray = new NotificationOverlayToastTray { + ForwardNotificationToPermanentStore = addPermanently, Origin = Anchor.TopRight, }, mainContent = new Container @@ -157,27 +158,26 @@ namespace osu.Game.Overlays if (notification is IHasCompletionTarget hasCompletionTarget) hasCompletionTarget.CompletionTarget = Post; - var ourType = notification.GetType(); - - int depth = notification.DisplayOnTop ? -runningDepth : runningDepth; - playDebouncedSample(notification.PopInSampleName); if (State.Value == Visibility.Hidden) - toastTray.Post(notification, addPermanently); + toastTray.Post(notification); else - addPermanently(); - - void addPermanently() - { - var section = sections.Children.First(s => s.AcceptedNotificationTypes.Any(accept => accept.IsAssignableFrom(ourType))); - - section.Add(notification, depth); - - updateCounts(); - } + addPermanently(notification); }); + private void addPermanently(Notification notification) + { + var ourType = notification.GetType(); + int depth = notification.DisplayOnTop ? -runningDepth : runningDepth; + + var section = sections.Children.First(s => s.AcceptedNotificationTypes.Any(accept => accept.IsAssignableFrom(ourType))); + + section.Add(notification, depth); + + updateCounts(); + } + protected override void Update() { base.Update(); diff --git a/osu.Game/Overlays/NotificationOverlayToastTray.cs b/osu.Game/Overlays/NotificationOverlayToastTray.cs index 38f790e88e..7017365dbd 100644 --- a/osu.Game/Overlays/NotificationOverlayToastTray.cs +++ b/osu.Game/Overlays/NotificationOverlayToastTray.cs @@ -2,7 +2,7 @@ // 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 osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; @@ -11,7 +11,6 @@ using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; -using osu.Framework.Threading; using osu.Framework.Utils; using osu.Game.Overlays.Notifications; using osuTK; @@ -25,13 +24,13 @@ namespace osu.Game.Overlays { public bool IsDisplayingToasts => toastFlow.Count > 0; - private FillFlowContainer toastFlow = null!; + private FillFlowContainer toastFlow = null!; private BufferedContainer toastContentBackground = null!; [Resolved] private OverlayColourProvider colourProvider { get; set; } = null!; - private readonly List pendingToastOperations = new List(); + public Action? ForwardNotificationToPermanentStore { get; set; } private int runningDepth; @@ -63,7 +62,7 @@ namespace osu.Game.Overlays postEffectDrawable.AutoSizeAxes = Axes.None; postEffectDrawable.RelativeSizeAxes = Axes.X; })), - toastFlow = new FillFlowContainer + toastFlow = new FillFlowContainer { LayoutDuration = 150, LayoutEasing = Easing.OutQuart, @@ -76,13 +75,13 @@ namespace osu.Game.Overlays public void FlushAllToasts() { - foreach (var d in pendingToastOperations.Where(d => !d.Completed)) - d.RunTask(); - - pendingToastOperations.Clear(); + foreach (var notification in toastFlow.ToArray()) + { + forwardNotification(notification); + } } - public void Post(Notification notification, Action addPermanently) + public void Post(Notification notification) { ++runningDepth; @@ -90,34 +89,47 @@ namespace osu.Game.Overlays toastFlow.Insert(depth, notification); - pendingToastOperations.Add(scheduleDismissal()); + scheduleDismissal(); - ScheduledDelegate scheduleDismissal() => Scheduler.AddDelayed(() => + void scheduleDismissal() => Scheduler.AddDelayed(() => { - // add notification to permanent overlay unless it was already dismissed by the user. + // Notification dismissed by user. if (notification.WasClosed) return; + // Notification forwarded away. + if (notification.Parent != toastFlow) + return; + + // Notification hovered; delay dismissal. if (notification.IsHovered) { - pendingToastOperations.Add(scheduleDismissal()); + scheduleDismissal(); return; } - toastFlow.Remove(notification); - AddInternal(notification); - - notification.MoveToOffset(new Vector2(400, 0), NotificationOverlay.TRANSITION_LENGTH, Easing.OutQuint); - notification.FadeOut(NotificationOverlay.TRANSITION_LENGTH, Easing.OutQuint).OnComplete(_ => - { - RemoveInternal(notification); - addPermanently(); - - notification.FadeIn(300, Easing.OutQuint); - }); + // All looks good, forward away! + forwardNotification(notification); }, notification.IsImportant ? 12000 : 2500); } + private void forwardNotification(Notification notification) + { + Debug.Assert(notification.Parent == toastFlow); + + toastFlow.Remove(notification); + AddInternal(notification); + + notification.MoveToOffset(new Vector2(400, 0), NotificationOverlay.TRANSITION_LENGTH, Easing.OutQuint); + notification.FadeOut(NotificationOverlay.TRANSITION_LENGTH, Easing.OutQuint).OnComplete(_ => + { + RemoveInternal(notification); + ForwardNotificationToPermanentStore?.Invoke(notification); + + notification.FadeIn(300, Easing.OutQuint); + }); + } + protected override void Update() { base.Update(); From a62ba9e0d9bda715dabc408df5b0b1f85ed76cd2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 31 Aug 2022 00:57:18 +0900 Subject: [PATCH 208/420] Remove notification blocking behaviour of first run setup --- .../Visual/Navigation/TestSceneFirstRunGame.cs | 9 --------- osu.Game/Overlays/NotificationOverlay.cs | 10 ++-------- 2 files changed, 2 insertions(+), 17 deletions(-) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneFirstRunGame.cs b/osu.Game.Tests/Visual/Navigation/TestSceneFirstRunGame.cs index f4078676c9..fe26d59812 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneFirstRunGame.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneFirstRunGame.cs @@ -26,16 +26,7 @@ namespace osu.Game.Tests.Visual.Navigation public void TestImportantNotificationDoesntInterruptSetup() { AddStep("post important notification", () => Game.Notifications.Post(new SimpleNotification { Text = "Important notification" })); - AddAssert("no notification posted", () => Game.Notifications.UnreadCount.Value == 0); AddAssert("first-run setup still visible", () => Game.FirstRunOverlay.State.Value == Visibility.Visible); - - AddUntilStep("finish first-run setup", () => - { - Game.FirstRunOverlay.NextButton.TriggerClick(); - return Game.FirstRunOverlay.State.Value == Visibility.Hidden; - }); - AddWaitStep("wait for post delay", 5); - AddAssert("notifications shown", () => Game.Notifications.State.Value == Visibility.Visible); AddAssert("notification posted", () => Game.Notifications.UnreadCount.Value == 1); } diff --git a/osu.Game/Overlays/NotificationOverlay.cs b/osu.Game/Overlays/NotificationOverlay.cs index 593618ab51..bb0beeef41 100644 --- a/osu.Game/Overlays/NotificationOverlay.cs +++ b/osu.Game/Overlays/NotificationOverlay.cs @@ -38,8 +38,6 @@ namespace osu.Game.Overlays [Cached] private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple); - private readonly IBindable firstRunSetupVisibility = new Bindable(); - public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) { if (State.Value == Visibility.Visible) @@ -58,7 +56,7 @@ namespace osu.Game.Overlays private Container mainContent = null!; [BackgroundDependencyLoader] - private void load(FirstRunSetupOverlay? firstRunSetup) + private void load() { X = WIDTH; Width = WIDTH; @@ -104,16 +102,13 @@ namespace osu.Game.Overlays } }, }; - - if (firstRunSetup != null) - firstRunSetupVisibility.BindTo(firstRunSetup.State); } private ScheduledDelegate? notificationsEnabler; private void updateProcessingMode() { - bool enabled = (OverlayActivationMode.Value == OverlayActivation.All && firstRunSetupVisibility.Value != Visibility.Visible) || State.Value == Visibility.Visible; + bool enabled = OverlayActivationMode.Value == OverlayActivation.All || State.Value == Visibility.Visible; notificationsEnabler?.Cancel(); @@ -129,7 +124,6 @@ namespace osu.Game.Overlays base.LoadComplete(); State.BindValueChanged(_ => updateProcessingMode()); - firstRunSetupVisibility.BindValueChanged(_ => updateProcessingMode()); OverlayActivationMode.BindValueChanged(_ => updateProcessingMode(), true); } From 31a9980686a620c5e434a0ea55851e112fe183a0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 31 Aug 2022 00:57:30 +0900 Subject: [PATCH 209/420] Update remaining test expectations with new behaviour --- .../Visual/UserInterface/TestSceneNotificationOverlay.cs | 5 +++-- osu.Game/Overlays/NotificationOverlay.cs | 7 ++++++- osu.Game/Overlays/NotificationOverlayToastTray.cs | 2 ++ 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs index c196b204b9..38eecaa052 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs @@ -110,7 +110,8 @@ namespace osu.Game.Tests.Visual.UserInterface { AddStep(@"simple #1", sendHelloNotification); - AddAssert("Is visible", () => notificationOverlay.State.Value == Visibility.Visible); + AddAssert("toast displayed", () => notificationOverlay.ToastCount == 1); + AddAssert("is not visible", () => notificationOverlay.State.Value == Visibility.Hidden); checkDisplayedCount(1); @@ -183,7 +184,7 @@ namespace osu.Game.Tests.Visual.UserInterface } private void checkDisplayedCount(int expected) => - AddAssert($"Displayed count is {expected}", () => notificationOverlay.UnreadCount.Value == expected); + AddUntilStep($"Displayed count is {expected}", () => notificationOverlay.UnreadCount.Value == expected); private void sendDownloadProgress() { diff --git a/osu.Game/Overlays/NotificationOverlay.cs b/osu.Game/Overlays/NotificationOverlay.cs index bb0beeef41..3769a7080f 100644 --- a/osu.Game/Overlays/NotificationOverlay.cs +++ b/osu.Game/Overlays/NotificationOverlay.cs @@ -129,6 +129,8 @@ namespace osu.Game.Overlays public IBindable UnreadCount => unreadCount; + public int ToastCount => toastTray.UnreadCount; + private readonly BindableInt unreadCount = new BindableInt(); private int runningDepth; @@ -155,7 +157,10 @@ namespace osu.Game.Overlays playDebouncedSample(notification.PopInSampleName); if (State.Value == Visibility.Hidden) + { toastTray.Post(notification); + updateCounts(); + } else addPermanently(notification); }); @@ -220,7 +225,7 @@ namespace osu.Game.Overlays private void updateCounts() { - unreadCount.Value = sections.Select(c => c.UnreadCount).Sum(); + unreadCount.Value = sections.Select(c => c.UnreadCount).Sum() + toastTray.UnreadCount; } private void markAllRead() diff --git a/osu.Game/Overlays/NotificationOverlayToastTray.cs b/osu.Game/Overlays/NotificationOverlayToastTray.cs index 7017365dbd..368996f395 100644 --- a/osu.Game/Overlays/NotificationOverlayToastTray.cs +++ b/osu.Game/Overlays/NotificationOverlayToastTray.cs @@ -32,6 +32,8 @@ namespace osu.Game.Overlays public Action? ForwardNotificationToPermanentStore { get; set; } + public int UnreadCount => toastFlow.Count(n => !n.WasClosed && !n.Read); + private int runningDepth; [BackgroundDependencyLoader] From 9eb615f9421202ebec908f97c5a1524005e0f3dd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 31 Aug 2022 01:40:27 +0900 Subject: [PATCH 210/420] Fix remaining test failures by strengthening `PlayerLoader` tests - Click using `TriggerClick` as notifications move around quite a bit. - Ensure any notifications from a previous test method are cleaned up. --- .../Visual/Gameplay/TestScenePlayerLoader.cs | 44 ++++++++++--------- osu.Game/Overlays/NotificationOverlay.cs | 6 +-- 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs index 922529cf19..b6c17fbaca 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs @@ -14,11 +14,10 @@ using osu.Framework.Audio; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Localisation; -using osu.Framework.Utils; using osu.Framework.Screens; using osu.Framework.Testing; +using osu.Framework.Utils; using osu.Game.Configuration; -using osu.Game.Graphics.Containers; using osu.Game.Overlays; using osu.Game.Overlays.Notifications; using osu.Game.Rulesets.Mods; @@ -30,7 +29,6 @@ using osu.Game.Screens.Play; using osu.Game.Screens.Play.PlayerSettings; using osu.Game.Utils; using osuTK.Input; -using SkipOverlay = osu.Game.Screens.Play.SkipOverlay; namespace osu.Game.Tests.Visual.Gameplay { @@ -83,6 +81,20 @@ namespace osu.Game.Tests.Visual.Gameplay [SetUp] public void Setup() => Schedule(() => player = null); + [SetUpSteps] + public override void SetUpSteps() + { + base.SetUpSteps(); + + AddStep("read all notifications", () => + { + notificationOverlay.Show(); + notificationOverlay.Hide(); + }); + + AddUntilStep("wait for no notifications", () => notificationOverlay.UnreadCount.Value, () => Is.EqualTo(0)); + } + /// /// Sets the input manager child to a new test player loader container instance. /// @@ -287,16 +299,9 @@ namespace osu.Game.Tests.Visual.Gameplay saveVolumes(); - AddAssert("check for notification", () => notificationOverlay.UnreadCount.Value == 1); - AddStep("click notification", () => - { - var scrollContainer = (OsuScrollContainer)notificationOverlay.Children.Last(); - var flowContainer = scrollContainer.Children.OfType>().First(); - var notification = flowContainer.First(); + AddAssert("check for notification", () => notificationOverlay.UnreadCount.Value, () => Is.EqualTo(1)); - InputManager.MoveMouseTo(notification); - InputManager.Click(MouseButton.Left); - }); + clickNotificationIfAny(); AddAssert("check " + volumeName, assert); @@ -366,15 +371,7 @@ namespace osu.Game.Tests.Visual.Gameplay })); AddUntilStep("wait for player", () => player?.LoadState == LoadState.Ready); AddAssert($"notification {(shouldWarn ? "triggered" : "not triggered")}", () => notificationOverlay.UnreadCount.Value == (shouldWarn ? 1 : 0)); - AddStep("click notification", () => - { - var scrollContainer = (OsuScrollContainer)notificationOverlay.Children.Last(); - var flowContainer = scrollContainer.Children.OfType>().First(); - var notification = flowContainer.First(); - - InputManager.MoveMouseTo(notification); - InputManager.Click(MouseButton.Left); - }); + clickNotificationIfAny(); AddUntilStep("wait for player load", () => player.IsLoaded); } @@ -439,6 +436,11 @@ namespace osu.Game.Tests.Visual.Gameplay AddUntilStep("skip button not visible", () => !checkSkipButtonVisible()); } + private void clickNotificationIfAny() + { + AddStep("click notification", () => notificationOverlay.ChildrenOfType().FirstOrDefault()?.TriggerClick()); + } + private EpilepsyWarning getWarning() => loader.ChildrenOfType().SingleOrDefault(); private class TestPlayerLoader : PlayerLoader diff --git a/osu.Game/Overlays/NotificationOverlay.cs b/osu.Game/Overlays/NotificationOverlay.cs index 3769a7080f..488f3eab0d 100644 --- a/osu.Game/Overlays/NotificationOverlay.cs +++ b/osu.Game/Overlays/NotificationOverlay.cs @@ -157,12 +157,11 @@ namespace osu.Game.Overlays playDebouncedSample(notification.PopInSampleName); if (State.Value == Visibility.Hidden) - { toastTray.Post(notification); - updateCounts(); - } else addPermanently(notification); + + updateCounts(); }); private void addPermanently(Notification notification) @@ -231,7 +230,6 @@ namespace osu.Game.Overlays private void markAllRead() { sections.Children.ForEach(s => s.MarkAllRead()); - updateCounts(); } } From ad650adab0b9ea2b226b10523193f96439265c6c Mon Sep 17 00:00:00 2001 From: apollo-dw <83023433+apollo-dw@users.noreply.github.com> Date: Tue, 30 Aug 2022 18:03:44 +0100 Subject: [PATCH 211/420] Fix speed note count sum --- osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs index a156726f94..c39d61020c 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs @@ -64,7 +64,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills if (maxStrain == 0) return 0; - return objectStrains.Aggregate((total, next) => total + (1.0 / (1.0 + Math.Exp(-(next / maxStrain * 12.0 - 6.0))))); + return objectStrains.Sum(strain => 1.0 / (1.0 + Math.Exp(-(strain / maxStrain * 12.0 - 6.0)))); } } } From 0558dae917899a7cafb5dd14adaf016ff7701ce0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 31 Aug 2022 12:46:03 +0900 Subject: [PATCH 212/420] Mark toasts as read when closing the overlay for added safety I'm not sure how the read status will play out going forward so I'm just adding this to keep things conforming for now. --- osu.Game/Overlays/NotificationOverlay.cs | 1 + osu.Game/Overlays/NotificationOverlayToastTray.cs | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/osu.Game/Overlays/NotificationOverlay.cs b/osu.Game/Overlays/NotificationOverlay.cs index 488f3eab0d..6eb327cbce 100644 --- a/osu.Game/Overlays/NotificationOverlay.cs +++ b/osu.Game/Overlays/NotificationOverlay.cs @@ -230,6 +230,7 @@ namespace osu.Game.Overlays private void markAllRead() { sections.Children.ForEach(s => s.MarkAllRead()); + toastTray.MarkAllRead(); updateCounts(); } } diff --git a/osu.Game/Overlays/NotificationOverlayToastTray.cs b/osu.Game/Overlays/NotificationOverlayToastTray.cs index 368996f395..800751072c 100644 --- a/osu.Game/Overlays/NotificationOverlayToastTray.cs +++ b/osu.Game/Overlays/NotificationOverlayToastTray.cs @@ -75,6 +75,12 @@ namespace osu.Game.Overlays }; } + public void MarkAllRead() + { + toastFlow.Children.ForEach(n => n.Read = true); + InternalChildren.OfType().ForEach(n => n.Read = true); + } + public void FlushAllToasts() { foreach (var notification in toastFlow.ToArray()) From 7c72c6b43faef9a78400a8b0f5807fc8f927885f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 31 Aug 2022 12:46:43 +0900 Subject: [PATCH 213/420] Fix unread count potentially missing notifications in a transforming state --- osu.Game/Overlays/NotificationOverlay.cs | 10 +++++----- osu.Game/Overlays/NotificationOverlayToastTray.cs | 9 +++++---- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/osu.Game/Overlays/NotificationOverlay.cs b/osu.Game/Overlays/NotificationOverlay.cs index 6eb327cbce..2c39ebcc87 100644 --- a/osu.Game/Overlays/NotificationOverlay.cs +++ b/osu.Game/Overlays/NotificationOverlay.cs @@ -222,16 +222,16 @@ namespace osu.Game.Overlays } } - private void updateCounts() - { - unreadCount.Value = sections.Select(c => c.UnreadCount).Sum() + toastTray.UnreadCount; - } - private void markAllRead() { sections.Children.ForEach(s => s.MarkAllRead()); toastTray.MarkAllRead(); updateCounts(); } + + private void updateCounts() + { + unreadCount.Value = sections.Select(c => c.UnreadCount).Sum() + toastTray.UnreadCount; + } } } diff --git a/osu.Game/Overlays/NotificationOverlayToastTray.cs b/osu.Game/Overlays/NotificationOverlayToastTray.cs index 800751072c..4417b5e0d0 100644 --- a/osu.Game/Overlays/NotificationOverlayToastTray.cs +++ b/osu.Game/Overlays/NotificationOverlayToastTray.cs @@ -6,6 +6,7 @@ using System.Diagnostics; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; @@ -32,7 +33,8 @@ namespace osu.Game.Overlays public Action? ForwardNotificationToPermanentStore { get; set; } - public int UnreadCount => toastFlow.Count(n => !n.WasClosed && !n.Read); + public int UnreadCount => toastFlow.Count(n => !n.WasClosed && !n.Read) + + InternalChildren.OfType().Count(n => !n.WasClosed && !n.Read); private int runningDepth; @@ -64,7 +66,7 @@ namespace osu.Game.Overlays postEffectDrawable.AutoSizeAxes = Axes.None; postEffectDrawable.RelativeSizeAxes = Axes.X; })), - toastFlow = new FillFlowContainer + toastFlow = new AlwaysUpdateFillFlowContainer { LayoutDuration = 150, LayoutEasing = Easing.OutQuart, @@ -84,9 +86,7 @@ namespace osu.Game.Overlays public void FlushAllToasts() { foreach (var notification in toastFlow.ToArray()) - { forwardNotification(notification); - } } public void Post(Notification notification) @@ -125,6 +125,7 @@ namespace osu.Game.Overlays { Debug.Assert(notification.Parent == toastFlow); + // Temporarily remove from flow so we can animate the position off to the right. toastFlow.Remove(notification); AddInternal(notification); From c573396ab6f5c1ff4d66d89cc3e73eaaa0aefdc4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 31 Aug 2022 12:46:54 +0900 Subject: [PATCH 214/420] Fix `IntroTestScene` not clearing previous notifications hard enough --- osu.Game.Tests/Visual/Menus/IntroTestScene.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Menus/IntroTestScene.cs b/osu.Game.Tests/Visual/Menus/IntroTestScene.cs index 66e3612113..e0f0df0554 100644 --- a/osu.Game.Tests/Visual/Menus/IntroTestScene.cs +++ b/osu.Game.Tests/Visual/Menus/IntroTestScene.cs @@ -52,6 +52,7 @@ namespace osu.Game.Tests.Visual.Menus }, notifications = new NotificationOverlay { + Depth = float.MinValue, Anchor = Anchor.TopRight, Origin = Anchor.TopRight, } @@ -82,7 +83,14 @@ namespace osu.Game.Tests.Visual.Menus [Test] public virtual void TestPlayIntroWithFailingAudioDevice() { - AddStep("hide notifications", () => notifications.Hide()); + AddStep("reset notifications", () => + { + notifications.Show(); + notifications.Hide(); + }); + + AddUntilStep("wait for no notifications", () => notifications.UnreadCount.Value, () => Is.EqualTo(0)); + AddStep("restart sequence", () => { logo.FinishTransforms(); From 85442fe0328395bb76209b7478bbf69f0a85a5cf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 31 Aug 2022 12:48:30 +0900 Subject: [PATCH 215/420] Adjust dismiss button background colour to avoid conflict with background --- osu.Game/Overlays/Notifications/Notification.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Notifications/Notification.cs b/osu.Game/Overlays/Notifications/Notification.cs index 90d9b88b79..a2ffdbe208 100644 --- a/osu.Game/Overlays/Notifications/Notification.cs +++ b/osu.Game/Overlays/Notifications/Notification.cs @@ -12,6 +12,7 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; using osu.Framework.Localisation; +using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osuTK; using osuTK.Graphics; @@ -217,7 +218,7 @@ namespace osu.Game.Overlays.Notifications { background = new Box { - Colour = colourProvider.Background4, + Colour = OsuColour.Gray(0).Opacity(0.15f), Alpha = 0, RelativeSizeAxes = Axes.Both, }, From 8b9ccc66b7b9bd8c01385083b9b55fd9943f5e68 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 31 Aug 2022 12:49:28 +0900 Subject: [PATCH 216/420] Update `ProgressNotification` font spec to match other notifications --- osu.Game/Overlays/Notifications/ProgressNotification.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Notifications/ProgressNotification.cs b/osu.Game/Overlays/Notifications/ProgressNotification.cs index 36b8bac873..14cf6b3013 100644 --- a/osu.Game/Overlays/Notifications/ProgressNotification.cs +++ b/osu.Game/Overlays/Notifications/ProgressNotification.cs @@ -173,7 +173,7 @@ namespace osu.Game.Overlays.Notifications public ProgressNotification() { - Content.Add(textDrawable = new OsuTextFlowContainer + Content.Add(textDrawable = new OsuTextFlowContainer(t => t.Font = t.Font.With(size: 14, weight: FontWeight.Medium)) { AutoSizeAxes = Axes.Y, RelativeSizeAxes = Axes.X, From 7ce1cf7560d00b9c92df6a6b18eda8a5999470d3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 31 Aug 2022 13:19:58 +0900 Subject: [PATCH 217/420] Add test coverage of skip button failure with equal time --- .../Visual/Gameplay/TestSceneSkipOverlay.cs | 29 +++++++++++++++++-- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs index b6b3650c83..6b02449aa3 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs @@ -27,8 +27,7 @@ namespace osu.Game.Tests.Visual.Gameplay private const double skip_time = 6000; - [SetUp] - public void SetUp() => Schedule(() => + private void createTest(double skipTime = skip_time) => AddStep("create test", () => { requestCount = 0; increment = skip_time; @@ -40,7 +39,7 @@ namespace osu.Game.Tests.Visual.Gameplay RelativeSizeAxes = Axes.Both, Children = new Drawable[] { - skip = new TestSkipOverlay(skip_time) + skip = new TestSkipOverlay(skipTime) { RequestSkip = () => { @@ -55,9 +54,25 @@ namespace osu.Game.Tests.Visual.Gameplay gameplayClock = gameplayClockContainer; }); + [Test] + public void TestSkipTimeZero() + { + createTest(0); + AddUntilStep("wait for skip overlay expired", () => !skip.IsAlive); + } + + [Test] + public void TestSkipTimeEqualToSkip() + { + createTest(MasterGameplayClockContainer.MINIMUM_SKIP_TIME); + AddUntilStep("wait for skip overlay expired", () => !skip.IsAlive); + } + [Test] public void TestFadeOnIdle() { + createTest(); + AddStep("move mouse", () => InputManager.MoveMouseTo(Vector2.Zero)); AddUntilStep("fully visible", () => skip.FadingContent.Alpha == 1); AddUntilStep("wait for fade", () => skip.FadingContent.Alpha < 1); @@ -70,6 +85,8 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestClickableAfterFade() { + createTest(); + AddStep("move mouse", () => InputManager.MoveMouseTo(skip.ScreenSpaceDrawQuad.Centre)); AddUntilStep("wait for fade", () => skip.FadingContent.Alpha == 0); AddStep("click", () => InputManager.Click(MouseButton.Left)); @@ -79,6 +96,8 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestClickOnlyActuatesOnce() { + createTest(); + AddStep("move mouse", () => InputManager.MoveMouseTo(skip.ScreenSpaceDrawQuad.Centre)); AddStep("click", () => { @@ -94,6 +113,8 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestClickOnlyActuatesMultipleTimes() { + createTest(); + AddStep("set increment lower", () => increment = 3000); AddStep("move mouse", () => InputManager.MoveMouseTo(skip.ScreenSpaceDrawQuad.Centre)); AddStep("click", () => InputManager.Click(MouseButton.Left)); @@ -106,6 +127,8 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestDoesntFadeOnMouseDown() { + createTest(); + AddStep("move mouse", () => InputManager.MoveMouseTo(skip.ScreenSpaceDrawQuad.Centre)); AddStep("button down", () => InputManager.PressButton(MouseButton.Left)); AddUntilStep("wait for overlay disappear", () => !skip.OverlayContent.IsPresent); From 51346e015417f634928c169d641a332f9bae0590 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 31 Aug 2022 13:05:57 +0900 Subject: [PATCH 218/420] Fix skip button getting stuck on screen for certain beatmaps Closes #20034. --- osu.Game/Screens/Play/SkipOverlay.cs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Play/SkipOverlay.cs b/osu.Game/Screens/Play/SkipOverlay.cs index 5c9a706549..974d40b538 100644 --- a/osu.Game/Screens/Play/SkipOverlay.cs +++ b/osu.Game/Screens/Play/SkipOverlay.cs @@ -114,16 +114,17 @@ namespace osu.Game.Screens.Play { base.LoadComplete(); + displayTime = gameplayClock.CurrentTime; + // skip is not required if there is no extra "empty" time to skip. // we may need to remove this if rewinding before the initial player load position becomes a thing. - if (fadeOutBeginTime < gameplayClock.CurrentTime) + if (fadeOutBeginTime <= displayTime) { Expire(); return; } button.Action = () => RequestSkip?.Invoke(); - displayTime = gameplayClock.CurrentTime; fadeContainer.TriggerShow(); @@ -146,7 +147,12 @@ namespace osu.Game.Screens.Play { base.Update(); - double progress = fadeOutBeginTime <= displayTime ? 1 : Math.Max(0, 1 - (gameplayClock.CurrentTime - displayTime) / (fadeOutBeginTime - displayTime)); + // This case causes an immediate expire in `LoadComplete`, but `Update` may run once after that. + // Avoid div-by-zero below. + if (fadeOutBeginTime <= displayTime) + return; + + double progress = Math.Max(0, 1 - (gameplayClock.CurrentTime - displayTime) / (fadeOutBeginTime - displayTime)); remainingTimeBox.Width = (float)Interpolation.Lerp(remainingTimeBox.Width, progress, Math.Clamp(Time.Elapsed / 40, 0, 1)); From 93bc4b9294cf08d32d6ba6af00ccf72200a79cc3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 31 Aug 2022 13:44:06 +0900 Subject: [PATCH 219/420] Add toggle for tournament client "auto progression" behaviour Addresses https://github.com/ppy/osu/discussions/20038. --- osu.Game.Tournament/Models/LadderInfo.cs | 2 ++ .../Screens/Gameplay/GameplayScreen.cs | 21 +++++++++++-------- .../Screens/MapPool/MapPoolScreen.cs | 9 +++++--- .../Screens/Setup/SetupScreen.cs | 6 ++++++ 4 files changed, 26 insertions(+), 12 deletions(-) diff --git a/osu.Game.Tournament/Models/LadderInfo.cs b/osu.Game.Tournament/Models/LadderInfo.cs index 0f73b60774..6b64a1156e 100644 --- a/osu.Game.Tournament/Models/LadderInfo.cs +++ b/osu.Game.Tournament/Models/LadderInfo.cs @@ -40,5 +40,7 @@ namespace osu.Game.Tournament.Models MinValue = 3, MaxValue = 4, }; + + public Bindable AutoProgressScreens = new BindableBool(true); } } diff --git a/osu.Game.Tournament/Screens/Gameplay/GameplayScreen.cs b/osu.Game.Tournament/Screens/Gameplay/GameplayScreen.cs index 54ae4c0366..8a23ee65da 100644 --- a/osu.Game.Tournament/Screens/Gameplay/GameplayScreen.cs +++ b/osu.Game.Tournament/Screens/Gameplay/GameplayScreen.cs @@ -199,16 +199,19 @@ namespace osu.Game.Tournament.Screens.Gameplay case TourneyState.Idle: contract(); - const float delay_before_progression = 4000; - - // if we've returned to idle and the last screen was ranking - // we should automatically proceed after a short delay - if (lastState == TourneyState.Ranking && !warmup.Value) + if (LadderInfo.AutoProgressScreens.Value) { - if (CurrentMatch.Value?.Completed.Value == true) - scheduledOperation = Scheduler.AddDelayed(() => { sceneManager?.SetScreen(typeof(TeamWinScreen)); }, delay_before_progression); - else if (CurrentMatch.Value?.Completed.Value == false) - scheduledOperation = Scheduler.AddDelayed(() => { sceneManager?.SetScreen(typeof(MapPoolScreen)); }, delay_before_progression); + const float delay_before_progression = 4000; + + // if we've returned to idle and the last screen was ranking + // we should automatically proceed after a short delay + if (lastState == TourneyState.Ranking && !warmup.Value) + { + if (CurrentMatch.Value?.Completed.Value == true) + scheduledOperation = Scheduler.AddDelayed(() => { sceneManager?.SetScreen(typeof(TeamWinScreen)); }, delay_before_progression); + else if (CurrentMatch.Value?.Completed.Value == false) + scheduledOperation = Scheduler.AddDelayed(() => { sceneManager?.SetScreen(typeof(MapPoolScreen)); }, delay_before_progression); + } } break; diff --git a/osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs b/osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs index 5eb2142fae..4d36515316 100644 --- a/osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs +++ b/osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs @@ -197,10 +197,13 @@ namespace osu.Game.Tournament.Screens.MapPool setNextMode(); - if (pickType == ChoiceType.Pick && CurrentMatch.Value.PicksBans.Any(i => i.Type == ChoiceType.Pick)) + if (LadderInfo.AutoProgressScreens.Value) { - scheduledChange?.Cancel(); - scheduledChange = Scheduler.AddDelayed(() => { sceneManager?.SetScreen(typeof(GameplayScreen)); }, 10000); + if (pickType == ChoiceType.Pick && CurrentMatch.Value.PicksBans.Any(i => i.Type == ChoiceType.Pick)) + { + scheduledChange?.Cancel(); + scheduledChange = Scheduler.AddDelayed(() => { sceneManager?.SetScreen(typeof(GameplayScreen)); }, 10000); + } } } diff --git a/osu.Game.Tournament/Screens/Setup/SetupScreen.cs b/osu.Game.Tournament/Screens/Setup/SetupScreen.cs index 2b2dce3664..ff781dec80 100644 --- a/osu.Game.Tournament/Screens/Setup/SetupScreen.cs +++ b/osu.Game.Tournament/Screens/Setup/SetupScreen.cs @@ -131,6 +131,12 @@ namespace osu.Game.Tournament.Screens.Setup windowSize.Value = new Size((int)(height * aspect_ratio / TournamentSceneManager.STREAM_AREA_WIDTH * TournamentSceneManager.REQUIRED_WIDTH), height); } }, + new LabelledSwitchButton + { + Label = "Auto advance screens", + Description = "Screens will progress automatically from gameplay -> results -> map pool", + Current = LadderInfo.AutoProgressScreens, + }, }; } From e9463f3c192aa635dc73c891e61ceb58206572e7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 31 Aug 2022 14:07:55 +0900 Subject: [PATCH 220/420] Test editor `ComposeScreen` tests not adding beatmap to hierarchy Makes it hard to test anything because `EditorBeatmap`'s `Update` method updates whether a beatmap has timing or not (enabling the placement controls). Also adds a basic timing point to allow for better testing. --- .../Editor/TestSceneManiaComposeScreen.cs | 15 ++++++++++++--- .../Visual/Editing/TestSceneComposeScreen.cs | 11 +++++++++-- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaComposeScreen.cs b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaComposeScreen.cs index 15b38b39ba..0354228cca 100644 --- a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaComposeScreen.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaComposeScreen.cs @@ -10,6 +10,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Testing; +using osu.Game.Beatmaps.ControlPoints; using osu.Game.Database; using osu.Game.Overlays; using osu.Game.Rulesets.Edit; @@ -34,10 +35,14 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor { AddStep("setup compose screen", () => { - var editorBeatmap = new EditorBeatmap(new ManiaBeatmap(new StageDefinition { Columns = 4 }) + var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 4 }) { BeatmapInfo = { Ruleset = new ManiaRuleset().RulesetInfo }, - }); + }; + + beatmap.ControlPointInfo.Add(0, new TimingControlPoint()); + + var editorBeatmap = new EditorBeatmap(beatmap, new LegacyBeatmapSkin(beatmap.BeatmapInfo, null)); Beatmap.Value = CreateWorkingBeatmap(editorBeatmap.PlayableBeatmap); @@ -50,7 +55,11 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor (typeof(IBeatSnapProvider), editorBeatmap), (typeof(OverlayColourProvider), new OverlayColourProvider(OverlayColourScheme.Green)), }, - Child = new ComposeScreen { State = { Value = Visibility.Visible } }, + Children = new Drawable[] + { + editorBeatmap, + new ComposeScreen { State = { Value = Visibility.Visible } }, + } }; }); diff --git a/osu.Game.Tests/Visual/Editing/TestSceneComposeScreen.cs b/osu.Game.Tests/Visual/Editing/TestSceneComposeScreen.cs index 291630fa3a..0df44b9ac4 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneComposeScreen.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneComposeScreen.cs @@ -9,6 +9,7 @@ using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Testing; +using osu.Game.Beatmaps.ControlPoints; using osu.Game.Overlays; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Osu; @@ -34,9 +35,11 @@ namespace osu.Game.Tests.Visual.Editing { var beatmap = new OsuBeatmap { - BeatmapInfo = { Ruleset = new OsuRuleset().RulesetInfo } + BeatmapInfo = { Ruleset = new OsuRuleset().RulesetInfo }, }; + beatmap.ControlPointInfo.Add(0, new TimingControlPoint()); + editorBeatmap = new EditorBeatmap(beatmap, new LegacyBeatmapSkin(beatmap.BeatmapInfo, null)); Beatmap.Value = CreateWorkingBeatmap(editorBeatmap.PlayableBeatmap); @@ -50,7 +53,11 @@ namespace osu.Game.Tests.Visual.Editing (typeof(IBeatSnapProvider), editorBeatmap), (typeof(OverlayColourProvider), new OverlayColourProvider(OverlayColourScheme.Green)), }, - Child = new ComposeScreen { State = { Value = Visibility.Visible } }, + Children = new Drawable[] + { + editorBeatmap, + new ComposeScreen { State = { Value = Visibility.Visible } }, + } }; }); From cc9dc604a069506f443aa8d3f77bcc35d9d4025a Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Wed, 31 Aug 2022 17:21:58 +0900 Subject: [PATCH 221/420] Refactor feedback sample playback logic --- osu.Game/Graphics/UserInterface/OsuTextBox.cs | 124 +++++++++--------- 1 file changed, 59 insertions(+), 65 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/OsuTextBox.cs b/osu.Game/Graphics/UserInterface/OsuTextBox.cs index 1984840553..a5f133506e 100644 --- a/osu.Game/Graphics/UserInterface/OsuTextBox.cs +++ b/osu.Game/Graphics/UserInterface/OsuTextBox.cs @@ -1,6 +1,8 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; +using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Audio; @@ -40,30 +42,26 @@ namespace osu.Game.Graphics.UserInterface Margin = new MarginPadding { Left = 2 }, }; - private readonly Sample?[] textAddedSamples = new Sample[4]; - private Sample? capsTextAddedSample; - private Sample? textRemovedSample; - private Sample? textCommittedSample; - private Sample? caretMovedSample; - - private Sample? selectCharSample; - private Sample? selectWordSample; - private Sample? selectAllSample; - private Sample? deselectSample; - private OsuCaret? caret; private bool selectionStarted; private double sampleLastPlaybackTime; - private enum SelectionSampleType + private enum FeedbackSampleType { - Character, - Word, - All, + TextAdd, + TextAddCaps, + TextRemove, + TextConfirm, + CaretMove, + SelectCharacter, + SelectWord, + SelectAll, Deselect } + private Dictionary sampleMap = new Dictionary(); + public OsuTextBox() { Height = 40; @@ -87,18 +85,22 @@ namespace osu.Game.Graphics.UserInterface Placeholder.Colour = colourProvider?.Foreground1 ?? new Color4(180, 180, 180, 255); + var textAddedSamples = new Sample?[4]; for (int i = 0; i < textAddedSamples.Length; i++) textAddedSamples[i] = audio.Samples.Get($@"Keyboard/key-press-{1 + i}"); - capsTextAddedSample = audio.Samples.Get(@"Keyboard/key-caps"); - textRemovedSample = audio.Samples.Get(@"Keyboard/key-delete"); - textCommittedSample = audio.Samples.Get(@"Keyboard/key-confirm"); - caretMovedSample = audio.Samples.Get(@"Keyboard/key-movement"); - - selectCharSample = audio.Samples.Get(@"Keyboard/select-char"); - selectWordSample = audio.Samples.Get(@"Keyboard/select-word"); - selectAllSample = audio.Samples.Get(@"Keyboard/select-all"); - deselectSample = audio.Samples.Get(@"Keyboard/deselect"); + sampleMap = new Dictionary + { + { FeedbackSampleType.TextAdd, textAddedSamples }, + { FeedbackSampleType.TextAddCaps, new[] { audio.Samples.Get(@"Keyboard/key-caps") } }, + { FeedbackSampleType.TextRemove, new[] { audio.Samples.Get(@"Keyboard/key-delete") } }, + { FeedbackSampleType.TextConfirm, new[] { audio.Samples.Get(@"Keyboard/key-confirm") } }, + { FeedbackSampleType.CaretMove, new[] { audio.Samples.Get(@"Keyboard/key-movement") } }, + { FeedbackSampleType.SelectCharacter, new[] { audio.Samples.Get(@"Keyboard/select-char") } }, + { FeedbackSampleType.SelectWord, new[] { audio.Samples.Get(@"Keyboard/select-word") } }, + { FeedbackSampleType.SelectAll, new[] { audio.Samples.Get(@"Keyboard/select-all") } }, + { FeedbackSampleType.Deselect, new[] { audio.Samples.Get(@"Keyboard/deselect") } } + }; } private Color4 selectionColour; @@ -110,23 +112,23 @@ namespace osu.Game.Graphics.UserInterface base.OnUserTextAdded(added); if (added.Any(char.IsUpper) && AllowUniqueCharacterSamples) - capsTextAddedSample?.Play(); + playSample(FeedbackSampleType.TextAddCaps); else - playTextAddedSample(); + playSample(FeedbackSampleType.TextAdd); } protected override void OnUserTextRemoved(string removed) { base.OnUserTextRemoved(removed); - textRemovedSample?.Play(); + playSample(FeedbackSampleType.TextRemove); } protected override void OnTextCommitted(bool textChanged) { base.OnTextCommitted(textChanged); - textCommittedSample?.Play(); + playSample(FeedbackSampleType.TextConfirm); } protected override void OnCaretMoved(bool selecting) @@ -134,7 +136,7 @@ namespace osu.Game.Graphics.UserInterface base.OnCaretMoved(selecting); if (!selecting) - caretMovedSample?.Play(); + playSample(FeedbackSampleType.CaretMove); } protected override void OnTextSelectionChanged(TextSelectionType selectionType) @@ -144,15 +146,15 @@ namespace osu.Game.Graphics.UserInterface switch (selectionType) { case TextSelectionType.Character: - playSelectSample(SelectionSampleType.Character); + playSample(FeedbackSampleType.SelectCharacter); break; case TextSelectionType.Word: - playSelectSample(selectionStarted ? SelectionSampleType.Character : SelectionSampleType.Word); + playSample(selectionStarted ? FeedbackSampleType.SelectCharacter : FeedbackSampleType.SelectWord); break; case TextSelectionType.All: - playSelectSample(SelectionSampleType.All); + playSample(FeedbackSampleType.SelectAll); break; } @@ -165,7 +167,7 @@ namespace osu.Game.Graphics.UserInterface if (!selectionStarted) return; - playSelectSample(SelectionSampleType.Deselect); + playSample(FeedbackSampleType.Deselect); selectionStarted = false; } @@ -184,13 +186,13 @@ namespace osu.Game.Graphics.UserInterface case 1: // composition probably ended by pressing backspace, or was cancelled. - textRemovedSample?.Play(); + playSample(FeedbackSampleType.TextRemove); return; default: // longer text removed, composition ended because it was cancelled. // could be a different sample if desired. - textRemovedSample?.Play(); + playSample(FeedbackSampleType.TextRemove); return; } } @@ -198,7 +200,7 @@ namespace osu.Game.Graphics.UserInterface if (addedTextLength > 0) { // some text was added, probably due to typing new text or by changing the candidate. - playTextAddedSample(); + playSample(FeedbackSampleType.TextAdd); return; } @@ -206,14 +208,14 @@ namespace osu.Game.Graphics.UserInterface { // text was probably removed by backspacing. // it's also possible that a candidate that only removed text was changed to. - textRemovedSample?.Play(); + playSample(FeedbackSampleType.TextRemove); return; } if (caretMoved) { // only the caret/selection was moved. - caretMovedSample?.Play(); + playSample(FeedbackSampleType.CaretMove); } } @@ -224,13 +226,13 @@ namespace osu.Game.Graphics.UserInterface if (successful) { // composition was successfully completed, usually by pressing the enter key. - textCommittedSample?.Play(); + playSample(FeedbackSampleType.TextConfirm); } else { // composition was prematurely ended, eg. by clicking inside the textbox. // could be a different sample if desired. - textCommittedSample?.Play(); + playSample(FeedbackSampleType.TextConfirm); } } @@ -259,43 +261,35 @@ namespace osu.Game.Graphics.UserInterface SelectionColour = SelectionColour, }; - private void playSelectSample(SelectionSampleType selectionType) + private SampleChannel? getSampleChannel(FeedbackSampleType feedbackSampleType) + { + var samples = sampleMap[feedbackSampleType]; + + if (samples == null || samples.Length == 0) + return null; + + return samples[RNG.Next(0, samples.Length)]?.GetChannel(); + } + + private void playSample(FeedbackSampleType feedbackSample) { if (Time.Current < sampleLastPlaybackTime + 15) return; - SampleChannel? channel; - double pitch = 0.98 + RNG.NextDouble(0.04); - - switch (selectionType) - { - case SelectionSampleType.All: - channel = selectAllSample?.GetChannel(); - break; - - case SelectionSampleType.Word: - channel = selectWordSample?.GetChannel(); - break; - - case SelectionSampleType.Deselect: - channel = deselectSample?.GetChannel(); - break; - - default: - channel = selectCharSample?.GetChannel(); - pitch += (SelectedText.Length / (double)Text.Length) * 0.15f; - break; - } + SampleChannel? channel = getSampleChannel(feedbackSample); if (channel == null) return; + double pitch = 0.98 + RNG.NextDouble(0.04); + + if (feedbackSample == FeedbackSampleType.SelectCharacter) + pitch += ((double)SelectedText.Length / Math.Max(1, Text.Length)) * 0.15f; + channel.Frequency.Value = pitch; channel.Play(); sampleLastPlaybackTime = Time.Current; } - private void playTextAddedSample() => textAddedSamples[RNG.Next(0, textAddedSamples.Length)]?.Play(); - private class OsuCaret : Caret { private const float caret_move_time = 60; From 212d76a11f3b3bbb6e8bafdcc9c935076a34c61c Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Wed, 31 Aug 2022 17:23:22 +0900 Subject: [PATCH 222/420] Add audio feedback for invalid textbox input --- osu.Game/Graphics/UserInterface/OsuTextBox.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/osu.Game/Graphics/UserInterface/OsuTextBox.cs b/osu.Game/Graphics/UserInterface/OsuTextBox.cs index a5f133506e..e5341cfd4b 100644 --- a/osu.Game/Graphics/UserInterface/OsuTextBox.cs +++ b/osu.Game/Graphics/UserInterface/OsuTextBox.cs @@ -53,6 +53,7 @@ namespace osu.Game.Graphics.UserInterface TextAddCaps, TextRemove, TextConfirm, + TextInvalid, CaretMove, SelectCharacter, SelectWord, @@ -95,6 +96,7 @@ namespace osu.Game.Graphics.UserInterface { FeedbackSampleType.TextAddCaps, new[] { audio.Samples.Get(@"Keyboard/key-caps") } }, { FeedbackSampleType.TextRemove, new[] { audio.Samples.Get(@"Keyboard/key-delete") } }, { FeedbackSampleType.TextConfirm, new[] { audio.Samples.Get(@"Keyboard/key-confirm") } }, + { FeedbackSampleType.TextInvalid, new[] { audio.Samples.Get(@"Keyboard/key-invalid") } }, { FeedbackSampleType.CaretMove, new[] { audio.Samples.Get(@"Keyboard/key-movement") } }, { FeedbackSampleType.SelectCharacter, new[] { audio.Samples.Get(@"Keyboard/select-char") } }, { FeedbackSampleType.SelectWord, new[] { audio.Samples.Get(@"Keyboard/select-word") } }, @@ -111,6 +113,9 @@ namespace osu.Game.Graphics.UserInterface { base.OnUserTextAdded(added); + if (!added.Any(CanAddCharacter)) + return; + if (added.Any(char.IsUpper) && AllowUniqueCharacterSamples) playSample(FeedbackSampleType.TextAddCaps); else @@ -124,6 +129,13 @@ namespace osu.Game.Graphics.UserInterface playSample(FeedbackSampleType.TextRemove); } + protected override void NotifyInputError() + { + base.NotifyInputError(); + + playSample(FeedbackSampleType.TextInvalid); + } + protected override void OnTextCommitted(bool textChanged) { base.OnTextCommitted(textChanged); From b5ec7d06dda29ec8463d9aac8cbe3987c7e9e93b Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 31 Aug 2022 19:49:04 +0900 Subject: [PATCH 223/420] Add auto-skip setting Default to auto skip --- osu.Game/Online/Multiplayer/MultiplayerClient.cs | 5 ++++- .../Online/Multiplayer/MultiplayerRoomSettings.cs | 9 +++++++-- osu.Game/Online/Rooms/Room.cs | 5 +++++ .../Match/MultiplayerMatchSettingsOverlay.cs | 13 ++++++++++++- osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs | 3 +++ 5 files changed, 31 insertions(+), 4 deletions(-) diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index 04b87c19da..364309ffe4 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -265,8 +265,9 @@ namespace osu.Game.Online.Multiplayer /// The type of the match, if any. /// The new queue mode, if any. /// The new auto-start countdown duration, if any. + /// The new auto-skip setting. public Task ChangeSettings(Optional name = default, Optional password = default, Optional matchType = default, Optional queueMode = default, - Optional autoStartDuration = default) + Optional autoStartDuration = default, Optional autoSkip = default) { if (Room == null) throw new InvalidOperationException("Must be joined to a match to change settings."); @@ -278,6 +279,7 @@ namespace osu.Game.Online.Multiplayer MatchType = matchType.GetOr(Room.Settings.MatchType), QueueMode = queueMode.GetOr(Room.Settings.QueueMode), AutoStartDuration = autoStartDuration.GetOr(Room.Settings.AutoStartDuration), + AutoSkip = autoSkip.GetOr(Room.Settings.AutoSkip) }); } @@ -739,6 +741,7 @@ namespace osu.Game.Online.Multiplayer APIRoom.QueueMode.Value = Room.Settings.QueueMode; APIRoom.AutoStartDuration.Value = Room.Settings.AutoStartDuration; APIRoom.CurrentPlaylistItem.Value = APIRoom.Playlist.Single(item => item.ID == settings.PlaylistItemId); + APIRoom.AutoSkip.Value = Room.Settings.AutoSkip; RoomUpdated?.Invoke(); } diff --git a/osu.Game/Online/Multiplayer/MultiplayerRoomSettings.cs b/osu.Game/Online/Multiplayer/MultiplayerRoomSettings.cs index 356acb427c..c73b02874e 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerRoomSettings.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerRoomSettings.cs @@ -29,6 +29,9 @@ namespace osu.Game.Online.Multiplayer [Key(5)] public TimeSpan AutoStartDuration { get; set; } + [Key(6)] + public bool AutoSkip { get; set; } + [IgnoreMember] public bool AutoStartEnabled => AutoStartDuration != TimeSpan.Zero; @@ -42,7 +45,8 @@ namespace osu.Game.Online.Multiplayer && PlaylistItemId == other.PlaylistItemId && MatchType == other.MatchType && QueueMode == other.QueueMode - && AutoStartDuration == other.AutoStartDuration; + && AutoStartDuration == other.AutoStartDuration + && AutoSkip == other.AutoSkip; } public override string ToString() => $"Name:{Name}" @@ -50,6 +54,7 @@ namespace osu.Game.Online.Multiplayer + $" Type:{MatchType}" + $" Item:{PlaylistItemId}" + $" Queue:{QueueMode}" - + $" Start:{AutoStartDuration}"; + + $" Start:{AutoStartDuration}" + + $" AutoSkip:{AutoSkip}"; } } diff --git a/osu.Game/Online/Rooms/Room.cs b/osu.Game/Online/Rooms/Room.cs index 3a24fa02a8..33397a237f 100644 --- a/osu.Game/Online/Rooms/Room.cs +++ b/osu.Game/Online/Rooms/Room.cs @@ -159,6 +159,10 @@ namespace osu.Game.Online.Rooms set => MaxAttempts.Value = value; } + [Cached] + [JsonProperty("auto_skip")] + public readonly Bindable AutoSkip = new Bindable(true); + public Room() { Password.BindValueChanged(p => HasPassword.Value = !string.IsNullOrEmpty(p.NewValue)); @@ -195,6 +199,7 @@ namespace osu.Game.Online.Rooms DifficultyRange.Value = other.DifficultyRange.Value; PlaylistItemStats.Value = other.PlaylistItemStats.Value; CurrentPlaylistItem.Value = other.CurrentPlaylistItem.Value; + AutoSkip.Value = other.AutoSkip.Value; if (EndDate.Value != null && DateTimeOffset.Now >= EndDate.Value) Status.Value = new RoomStatusEnded(); diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs index 3aa879dde0..3d6127e8e7 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs @@ -63,6 +63,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match public MatchTypePicker TypePicker; public OsuEnumDropdown QueueModeDropdown; public OsuTextBox PasswordTextBox; + public OsuCheckbox AutoSkipCheckbox; public TriangleButton ApplyButton; public OsuSpriteText ErrorText; @@ -249,6 +250,13 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match LengthLimit = 255, }, }, + new Section("Other") + { + Child = AutoSkipCheckbox = new OsuCheckbox + { + LabelText = "Automatically skip the beatmap intro" + } + } } } }, @@ -343,6 +351,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match Password.BindValueChanged(password => PasswordTextBox.Text = password.NewValue ?? string.Empty, true); QueueMode.BindValueChanged(mode => QueueModeDropdown.Current.Value = mode.NewValue, true); AutoStartDuration.BindValueChanged(duration => startModeDropdown.Current.Value = (StartMode)(int)duration.NewValue.TotalSeconds, true); + AutoSkip.BindValueChanged(autoSkip => AutoSkipCheckbox.Current.Value = autoSkip.NewValue, true); operationInProgress.BindTo(ongoingOperationTracker.InProgress); operationInProgress.BindValueChanged(v => @@ -390,7 +399,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match password: PasswordTextBox.Text, matchType: TypePicker.Current.Value, queueMode: QueueModeDropdown.Current.Value, - autoStartDuration: autoStartDuration) + autoStartDuration: autoStartDuration, + autoSkip: AutoSkipCheckbox.Current.Value) .ContinueWith(t => Schedule(() => { if (t.IsCompletedSuccessfully) @@ -406,6 +416,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match room.Password.Value = PasswordTextBox.Current.Value; room.QueueMode.Value = QueueModeDropdown.Current.Value; room.AutoStartDuration.Value = autoStartDuration; + room.AutoSkip.Value = AutoSkipCheckbox.Current.Value; if (int.TryParse(MaxParticipantsField.Text, out int max)) room.MaxParticipants.Value = max; diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs index 0bf5f2604c..50ad3228e5 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs @@ -86,6 +86,9 @@ namespace osu.Game.Screens.OnlinePlay [Resolved(typeof(Room))] protected Bindable AutoStartDuration { get; private set; } + [Resolved(typeof(Room))] + protected Bindable AutoSkip { get; private set; } + [Resolved(CanBeNull = true)] private IBindable subScreenSelectedItem { get; set; } From c852c54055c17537cf91242f0ab970f41ba76f71 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 31 Aug 2022 19:50:16 +0900 Subject: [PATCH 224/420] Consume auto skip setting during play --- osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs | 3 ++- osu.Game/Screens/Play/PlayerLoader.cs | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs index a82da7b185..773e68162e 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs @@ -61,7 +61,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer { AllowPause = false, AllowRestart = false, - AllowSkipping = false, + AllowSkipping = room.AutoSkip.Value, + AutomaticallySkipIntro = room.AutoSkip.Value }) { this.users = users; diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index a9fcab063c..6373633b5a 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -363,7 +363,7 @@ namespace osu.Game.Screens.Play return; CurrentPlayer = createPlayer(); - CurrentPlayer.Configuration.AutomaticallySkipIntro = quickRestart; + CurrentPlayer.Configuration.AutomaticallySkipIntro |= quickRestart; CurrentPlayer.RestartCount = restartCount++; CurrentPlayer.RestartRequested = restartRequested; From 50e8052f0706d00cb9cb8fae11d140566acb1e92 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 31 Aug 2022 22:08:20 +0900 Subject: [PATCH 225/420] 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 bff3627af7..85857771a5 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 5fc69e475f..f757fd77b9 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -37,7 +37,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index f763e411be..9fcc3753eb 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -62,7 +62,7 @@ - + From 6af8143c8caed96fbfb18fcd02c226cb311a18bc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 31 Aug 2022 22:34:07 +0900 Subject: [PATCH 226/420] Fix typing of new setting to allow it to be visible to tools export --- osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs index b48f0b4ccd..5e2a92e5e9 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs @@ -60,7 +60,7 @@ namespace osu.Game.Rulesets.Osu.Mods }; [SettingSource("Metronome ticks", "Whether a metronome beat should play in the background")] - public BindableBool Metronome { get; } = new BindableBool(true); + public Bindable Metronome { get; } = new BindableBool(true); #region Constants From ba20044af499ea5555e1cfc61e91ab64630a0cad Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 31 Aug 2022 23:24:39 +0900 Subject: [PATCH 227/420] Fix missing nullability consideraition --- .../Visual/UserInterface/TestSceneFirstRunSetupOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneFirstRunSetupOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneFirstRunSetupOverlay.cs index b845b85e1f..16d564f0ee 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneFirstRunSetupOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneFirstRunSetupOverlay.cs @@ -194,7 +194,7 @@ namespace osu.Game.Tests.Visual.UserInterface AddStep("notification arrived", () => notificationOverlay.Verify(n => n.Post(It.IsAny()), Times.Once)); - AddStep("run notification action", () => lastNotification.Activated()); + AddStep("run notification action", () => lastNotification.Activated?.Invoke()); AddAssert("overlay shown", () => overlay.State.Value == Visibility.Visible); AddAssert("is resumed", () => overlay.CurrentScreen is ScreenUIScale); From eb02a9a14442d970b2b710b69e749abaa7cd93eb Mon Sep 17 00:00:00 2001 From: HiddenNode Date: Wed, 31 Aug 2022 22:04:28 +0100 Subject: [PATCH 228/420] Removed GrowToFItContainer --- .../UprightAspectMaintainingContainer.cs | 84 ------------------- 1 file changed, 84 deletions(-) diff --git a/osu.Game/Graphics/Containers/UprightAspectMaintainingContainer.cs b/osu.Game/Graphics/Containers/UprightAspectMaintainingContainer.cs index 8efc29c83c..300b5bd4b4 100644 --- a/osu.Game/Graphics/Containers/UprightAspectMaintainingContainer.cs +++ b/osu.Game/Graphics/Containers/UprightAspectMaintainingContainer.cs @@ -24,27 +24,6 @@ namespace osu.Game.Graphics.Containers /// public ScaleMode Scaling { get; set; } = ScaleMode.Vertical; - /// - /// If this is true, all wrapper containers will be set to grow with their content - /// and not shrink back, this is used to fix the position of children that change - /// in size when using AutoSizeAxes. - /// - public bool GrowToFitContent - { - get => growToFitContent; - set - { - if (growToFitContent != value) - { - foreach (GrowToFitContainer c in Children) - c.GrowToFitContentUpdated = true; - growToFitContent = value; - } - } - } - - private bool growToFitContent = true; - private readonly LayoutValue layout = new LayoutValue(Invalidation.DrawInfo, InvalidationSource.Parent); public UprightAspectMaintainingContainer() @@ -63,15 +42,6 @@ namespace osu.Game.Graphics.Containers } } - public override void Add(Drawable drawable) - { - var wrapper = new GrowToFitContainer(); - wrapper.Wrap(drawable); - wrapper.AutoSizeAxes = Axes.None; - drawable.Origin = drawable.Anchor = Anchor.Centre; - base.Add(wrapper); - } - /// /// Keeps the drawable upright and unstretched preventing it from being rotated, sheared, scaled or flipped with its Parent. /// @@ -127,60 +97,6 @@ namespace osu.Game.Graphics.Containers Scale = new Vector2(sx * usedScale, sy * usedScale); } - - public class GrowToFitContainer : Container - { - private readonly LayoutValue layout = new LayoutValue(Invalidation.RequiredParentSizeToFit, InvalidationSource.Child); - - private Vector2 maxChildSize; - - public bool GrowToFitContentUpdated { get; set; } - - public GrowToFitContainer() - { - AddLayout(layout); - } - - protected override void Update() - { - UprightAspectMaintainingContainer parent = (UprightAspectMaintainingContainer)Parent; - - if (!layout.IsValid || GrowToFitContentUpdated) - { - if ((Child.RelativeSizeAxes & Axes.X) != 0) - RelativeSizeAxes |= Axes.X; - else - { - if (parent.GrowToFitContent) - Width = Math.Max(Child.Width * Child.Scale.X, maxChildSize.X); - else - Width = Child.Width * Child.Scale.X; - } - - if ((Child.RelativeSizeAxes & Axes.Y) != 0) - RelativeSizeAxes |= Axes.Y; - else - { - if (parent.GrowToFitContent) - Height = Math.Max(Child.Height * Child.Scale.Y, maxChildSize.Y); - else - Height = Child.Height * Child.Scale.Y; - } - - // reset max_child_size or update it - if (!parent.GrowToFitContent) - maxChildSize = Child.Size; - else - { - maxChildSize.X = MathF.Max(maxChildSize.X, Child.Size.X); - maxChildSize.Y = MathF.Max(maxChildSize.Y, Child.Size.Y); - } - - GrowToFitContentUpdated = false; - layout.Validate(); - } - } - } } public enum ScaleMode From 4a630b53846f64d1f1d5fd96569991fdd316b246 Mon Sep 17 00:00:00 2001 From: HiddenNode Date: Wed, 31 Aug 2022 22:05:06 +0100 Subject: [PATCH 229/420] Implemented SizePreservingSpriteText --- .../Sprites/SizePreservingTextSprite.cs | 107 ++++++++++++++++++ osu.Game/Screens/Play/HUD/SongProgressInfo.cs | 12 +- 2 files changed, 113 insertions(+), 6 deletions(-) create mode 100644 osu.Game/Graphics/Sprites/SizePreservingTextSprite.cs diff --git a/osu.Game/Graphics/Sprites/SizePreservingTextSprite.cs b/osu.Game/Graphics/Sprites/SizePreservingTextSprite.cs new file mode 100644 index 0000000000..11b81b26fd --- /dev/null +++ b/osu.Game/Graphics/Sprites/SizePreservingTextSprite.cs @@ -0,0 +1,107 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable disable + +using System; +using System.Collections.Generic; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Localisation; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Graphics.Sprites +{ + public class SizePreservingSpriteText : CompositeDrawable + { + private readonly OsuSpriteText text = new OsuSpriteText(); + + private Vector2 maximumSize; + + public SizePreservingSpriteText(Vector2? minimumSize = null) + { + text.Origin = Anchor.Centre; + text.Anchor = Anchor.Centre; + + AddInternal(text); + maximumSize = minimumSize ?? Vector2.Zero; + } + + protected override void Update() + { + Width = maximumSize.X = MathF.Max(maximumSize.X, text.Width); + Height = maximumSize.Y = MathF.Max(maximumSize.Y, text.Height); + } + + public new Axes AutoSizeAxes + { + get => Axes.None; + set => throw new InvalidOperationException("You can't set AutoSizeAxes of this container"); + } + + /// + /// Gets or sets the text to be displayed. + /// + public LocalisableString Text + { + get => text.Text; + set => text.Text = value; + } + + /// + /// Contains information on the font used to display the text. + /// + public FontUsage Font + { + get => text.Font; + set => text.Font = value; + } + + /// + /// True if a shadow should be displayed around the text. + /// + public bool Shadow + { + get => text.Shadow; + set => text.Shadow = value; + } + + /// + /// The colour of the shadow displayed around the text. A shadow will only be displayed if the property is set to true. + /// + public Color4 ShadowColour + { + get => text.ShadowColour; + set => text.ShadowColour = value; + } + + /// + /// The offset of the shadow displayed around the text. A shadow will only be displayed if the property is set to true. + /// + public Vector2 ShadowOffset + { + get => text.ShadowOffset; + set => text.ShadowOffset = value; + } + + /// + /// True if the 's vertical size should be equal to (the full height) or precisely the size of used characters. + /// Set to false to allow better centering of individual characters/numerals/etc. + /// + public bool UseFullGlyphHeight + { + get => text.UseFullGlyphHeight; + set => text.UseFullGlyphHeight = value; + } + + public override bool IsPresent => text.IsPresent; + + public override string ToString() => text.ToString(); + + public float LineBaseHeight => text.LineBaseHeight; + + public IEnumerable FilterTerms => text.FilterTerms; + } +} diff --git a/osu.Game/Screens/Play/HUD/SongProgressInfo.cs b/osu.Game/Screens/Play/HUD/SongProgressInfo.cs index 81abfce0c7..d0eb8f8ca1 100644 --- a/osu.Game/Screens/Play/HUD/SongProgressInfo.cs +++ b/osu.Game/Screens/Play/HUD/SongProgressInfo.cs @@ -15,9 +15,9 @@ namespace osu.Game.Screens.Play.HUD { public class SongProgressInfo : Container { - private OsuSpriteText timeCurrent; - private OsuSpriteText timeLeft; - private OsuSpriteText progress; + private SizePreservingSpriteText timeCurrent; + private SizePreservingSpriteText timeLeft; + private SizePreservingSpriteText progress; private double startTime; private double endTime; @@ -62,7 +62,7 @@ namespace osu.Game.Screens.Play.HUD AutoSizeAxes = Axes.Both, Scaling = ScaleMode.Vertical, ScalingFactor = 0.5f, - Child = timeCurrent = new OsuSpriteText + Child = timeCurrent = new SizePreservingSpriteText { Origin = Anchor.Centre, Anchor = Anchor.Centre, @@ -83,7 +83,7 @@ namespace osu.Game.Screens.Play.HUD AutoSizeAxes = Axes.Both, Scaling = ScaleMode.Vertical, ScalingFactor = 0.5f, - Child = progress = new OsuSpriteText + Child = progress = new SizePreservingSpriteText { Origin = Anchor.Centre, Anchor = Anchor.Centre, @@ -104,7 +104,7 @@ namespace osu.Game.Screens.Play.HUD AutoSizeAxes = Axes.Both, Scaling = ScaleMode.Vertical, ScalingFactor = 0.5f, - Child = timeLeft = new OsuSpriteText + Child = timeLeft = new SizePreservingSpriteText { Origin = Anchor.Centre, Anchor = Anchor.Centre, From a548b28158c018d738d29fd2a2e94b70c00673f7 Mon Sep 17 00:00:00 2001 From: HiddenNode Date: Wed, 31 Aug 2022 22:05:46 +0100 Subject: [PATCH 230/420] Added test scene for SizePreservingSpriteText --- .../TestSceneGrowToFitContent.cs | 175 ------------------ .../TestSceneSizePreservingTextSprite.cs | 96 ++++++++++ 2 files changed, 96 insertions(+), 175 deletions(-) delete mode 100644 osu.Game.Tests/Visual/UserInterface/TestSceneGrowToFitContent.cs create mode 100644 osu.Game.Tests/Visual/UserInterface/TestSceneSizePreservingTextSprite.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneGrowToFitContent.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneGrowToFitContent.cs deleted file mode 100644 index bcff992f9d..0000000000 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneGrowToFitContent.cs +++ /dev/null @@ -1,175 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -#nullable disable - -using System.Collections.Generic; -using NUnit.Framework; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics; -using osu.Game.Graphics.Containers; -using osu.Game.Graphics.Sprites; -using osu.Game.Graphics; -using System.Linq; -using osuTK; - -namespace osu.Game.Tests.Visual.UserInterface -{ - public class TestSceneGrowToFitContent : OsuGridTestScene - { - private readonly List parentContainers = new List(); - private readonly List childContainers = new List(); - private readonly List texts = new List(); - - public TestSceneGrowToFitContent() - : base(1, 2) - { - for (int i = 0; i < 2; i++) - { - OsuSpriteText text; - UprightAspectMaintainingContainer childContainer; - Container parentContainer = new Container - { - Origin = Anchor.BottomRight, - Anchor = Anchor.BottomCentre, - AutoSizeAxes = Axes.Both, - Rotation = 45, - Y = -200, - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Colour4.Red, - }, - childContainer = new UprightAspectMaintainingContainer - { - AutoSizeAxes = Axes.Both, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Colour4.Blue, - }, - text = new OsuSpriteText - { - Text = "Text", - Font = OsuFont.GetFont(Typeface.Venera, weight: FontWeight.Bold, size: 40), - Origin = Anchor.Centre, - Anchor = Anchor.Centre, - }, - } - }, - } - }; - - Container cellInfo = new Container - { - Origin = Anchor.TopCentre, - Anchor = Anchor.TopCentre, - Margin = new MarginPadding - { - Top = 100, - }, - Child = new OsuSpriteText - { - Text = (i == 0) ? "GrowToFitContent == true" : "GrowToFitContent == false", - Font = OsuFont.GetFont(Typeface.Inter, weight: FontWeight.Bold, size: 40), - Origin = Anchor.TopCentre, - Anchor = Anchor.TopCentre, - }, - }; - - parentContainers.Add(parentContainer); - childContainers.Add(childContainer); - texts.Add(text); - Cell(i).Add(cellInfo); - Cell(i).Add(parentContainer); - } - } - - [Test] - public void TestResizeText() - { - AddStep("reset...", () => - { - childContainers[0].GrowToFitContent = false; - childContainers[1].GrowToFitContent = false; - }); - - AddStep("setup...", () => - { - childContainers[0].GrowToFitContent = true; - childContainers[1].GrowToFitContent = false; - }); - - for (int i = 0; i < 10; i++) - { - AddStep("Add Character", () => - { - foreach (int j in Enumerable.Range(0, parentContainers.Count)) - { - texts[j].Text += "."; - } - }); - } - - for (int i = 0; i < 10; i++) - { - AddStep("Remove Character", () => - { - foreach (int j in Enumerable.Range(0, parentContainers.Count)) - { - string text = texts[j].Text.ToString(); - texts[j].Text = text.Remove(text.Length - 1, 1); - } - }); - } - } - - [Test] - public void TestScaleText() - { - AddStep("reset...", () => - { - childContainers[0].GrowToFitContent = false; - childContainers[1].GrowToFitContent = false; - }); - - AddStep("setup...", () => - { - childContainers[0].GrowToFitContent = true; - childContainers[1].GrowToFitContent = false; - }); - - for (int i = 0; i < 1; i++) - { - AddStep("Big text", scaleUp); - - AddWaitStep("wait...", 5); - - AddStep("Small text", scaleDown); - } - } - - private void scaleUp() - { - foreach (int j in Enumerable.Range(0, parentContainers.Count)) - { - texts[j].ScaleTo(new Vector2(2, 2), 1000); - } - } - - private void scaleDown() - { - foreach (int j in Enumerable.Range(0, parentContainers.Count)) - { - texts[j].ScaleTo(new Vector2(1, 1), 1000); - } - } - } -} diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneSizePreservingTextSprite.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneSizePreservingTextSprite.cs new file mode 100644 index 0000000000..a5e70a54f8 --- /dev/null +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneSizePreservingTextSprite.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. + +#nullable disable + +using System; +using System.Collections.Generic; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics; + +namespace osu.Game.Tests.Visual.UserInterface +{ + public class TestSceneSizePreservingSpriteText : OsuGridTestScene + { + private readonly List parentContainers = new List(); + private readonly List childContainers = new List(); + private readonly OsuSpriteText osuSpriteText = new OsuSpriteText(); + private readonly SizePreservingSpriteText sizePreservingSpriteText = new SizePreservingSpriteText(); + + + public TestSceneSizePreservingSpriteText() + : base(1, 2) + { + for (int i = 0; i < 2; i++) + { + UprightAspectMaintainingContainer childContainer; + Container parentContainer = new Container + { + Origin = Anchor.BottomRight, + Anchor = Anchor.BottomCentre, + AutoSizeAxes = Axes.Both, + Rotation = 45, + Y = -200, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Colour4.Red, + }, + childContainer = new UprightAspectMaintainingContainer + { + AutoSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Colour4.Blue, + }, + } + }, + } + }; + + Container cellInfo = new Container + { + Origin = Anchor.TopCentre, + Anchor = Anchor.TopCentre, + Margin = new MarginPadding + { + Top = 100, + }, + Child = new OsuSpriteText + { + Text = (i == 0) ? "OsuSpriteText" : "SizePreservingSpriteText", + Font = OsuFont.GetFont(Typeface.Inter, weight: FontWeight.Bold, size: 40), + Origin = Anchor.TopCentre, + Anchor = Anchor.TopCentre, + }, + }; + + parentContainers.Add(parentContainer); + childContainers.Add(childContainer); + Cell(i).Add(cellInfo); + Cell(i).Add(parentContainer); + } + + childContainers[0].Add(osuSpriteText); + childContainers[1].Add(sizePreservingSpriteText); + osuSpriteText.Font = sizePreservingSpriteText.Font = OsuFont.GetFont(Typeface.Venera, weight: FontWeight.Bold, size: 20); + } + + protected override void Update() + { + base.Update(); + osuSpriteText.Text = sizePreservingSpriteText.Text = DateTime.Now.ToString(); + } + } +} From 921a9ef895f4e23f81775bb3f252c85916d9e670 Mon Sep 17 00:00:00 2001 From: HiddenNode Date: Wed, 31 Aug 2022 22:18:52 +0100 Subject: [PATCH 231/420] clean up --- .../Visual/UserInterface/TestSceneSizePreservingTextSprite.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneSizePreservingTextSprite.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneSizePreservingTextSprite.cs index a5e70a54f8..69f5015af6 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneSizePreservingTextSprite.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneSizePreservingTextSprite.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; +using System.Globalization; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics; @@ -21,7 +22,6 @@ namespace osu.Game.Tests.Visual.UserInterface private readonly OsuSpriteText osuSpriteText = new OsuSpriteText(); private readonly SizePreservingSpriteText sizePreservingSpriteText = new SizePreservingSpriteText(); - public TestSceneSizePreservingSpriteText() : base(1, 2) { @@ -90,7 +90,7 @@ namespace osu.Game.Tests.Visual.UserInterface protected override void Update() { base.Update(); - osuSpriteText.Text = sizePreservingSpriteText.Text = DateTime.Now.ToString(); + osuSpriteText.Text = sizePreservingSpriteText.Text = DateTime.Now.ToString(CultureInfo.InvariantCulture); } } } From d70208fcf189506ccefcce7617e9534cfc5b9e5c Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 1 Sep 2022 14:14:22 +0900 Subject: [PATCH 232/420] Default to off --- osu.Game/Online/Rooms/Room.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/Rooms/Room.cs b/osu.Game/Online/Rooms/Room.cs index 33397a237f..adfd4c226a 100644 --- a/osu.Game/Online/Rooms/Room.cs +++ b/osu.Game/Online/Rooms/Room.cs @@ -161,7 +161,7 @@ namespace osu.Game.Online.Rooms [Cached] [JsonProperty("auto_skip")] - public readonly Bindable AutoSkip = new Bindable(true); + public readonly Bindable AutoSkip = new Bindable(); public Room() { From 148e487c025421ea8596fd6e817519ddc6f90b58 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 1 Sep 2022 19:59:20 +0900 Subject: [PATCH 233/420] Add failing test of date submitted search failing --- .../SongSelect/TestSceneBeatmapCarousel.cs | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs index c3e485d56b..c9e63fa621 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs @@ -494,6 +494,43 @@ namespace osu.Game.Tests.Visual.SongSelect AddAssert("Something is selected", () => carousel.SelectedBeatmapInfo != null); } + [Test] + public void TestSortingDateSubmitted() + { + var sets = new List(); + const string zzz_string = "zzzzz"; + + AddStep("Populuate beatmap sets", () => + { + sets.Clear(); + + for (int i = 0; i < 20; i++) + { + var set = TestResources.CreateTestBeatmapSetInfo(5); + + if (i >= 2 && i < 10) + set.DateSubmitted = DateTimeOffset.Now.AddMinutes(i); + if (i < 5) + set.Beatmaps.ForEach(b => b.Metadata.Artist = zzz_string); + + sets.Add(set); + } + }); + + loadBeatmaps(sets); + + AddStep("Sort by date submitted", () => carousel.Filter(new FilterCriteria { Sort = SortMode.DateSubmitted }, false)); + checkVisibleItemCount(diff: false, count: 8); + checkVisibleItemCount(diff: true, count: 5); + AddStep("Sort by date submitted and string", () => carousel.Filter(new FilterCriteria + { + Sort = SortMode.DateSubmitted, + SearchText = zzz_string + }, false)); + checkVisibleItemCount(diff: false, count: 3); + checkVisibleItemCount(diff: true, count: 5); + } + [Test] public void TestSorting() { From 15246236242e14304a6ae6df5a84f36403b67e81 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 1 Sep 2022 19:27:33 +0900 Subject: [PATCH 234/420] Fix back-to-front filter logic Was copied across from a place which was checking for `match` and applied verbatim to a place that was `filter`. Which are polar opposites. --- osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs index 6c134a4ab8..9a4319c6b2 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs @@ -129,12 +129,13 @@ namespace osu.Game.Screens.Select.Carousel public override void Filter(FilterCriteria criteria) { base.Filter(criteria); - bool match = Items.All(i => i.Filtered.Value); - match &= criteria.Sort != SortMode.DateRanked || BeatmapSet?.DateRanked != null; - match &= criteria.Sort != SortMode.DateSubmitted || BeatmapSet?.DateSubmitted != null; + bool filtered = Items.All(i => i.Filtered.Value); - Filtered.Value = match; + filtered |= criteria.Sort == SortMode.DateRanked && BeatmapSet?.DateRanked == null; + filtered |= criteria.Sort == SortMode.DateSubmitted && BeatmapSet?.DateSubmitted == null; + + Filtered.Value = filtered; } public override string ToString() => BeatmapSet.ToString(); From a27743126639d51619c29b68654e5f50ab053a03 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 1 Sep 2022 21:10:36 +0900 Subject: [PATCH 235/420] Add has_replay and legacy_score_id to SoloScoreInfo --- osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs b/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs index a8cedabd48..659b661ef0 100644 --- a/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs +++ b/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs @@ -18,7 +18,7 @@ namespace osu.Game.Online.API.Requests.Responses [Serializable] public class SoloScoreInfo : IHasOnlineID { - [JsonProperty("replay")] + [JsonProperty("has_replay")] public bool HasReplay { get; set; } [JsonProperty("beatmap_id")] @@ -83,6 +83,9 @@ namespace osu.Game.Online.API.Requests.Responses [JsonProperty("legacy_total_score")] public int? LegacyTotalScore { get; set; } + [JsonProperty("legacy_score_id")] + public uint? LegacyScoreId { get; set; } + #region osu-web API additions (not stored to database). [JsonProperty("id")] @@ -117,7 +120,6 @@ namespace osu.Game.Online.API.Requests.Responses public bool ShouldSerializeBeatmapSet() => false; public bool ShouldSerializePP() => false; public bool ShouldSerializeOnlineID() => false; - public bool ShouldSerializeHasReplay() => false; #endregion From 8866250cff5742c2604ab7b3532044ae3b6adcef Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 1 Sep 2022 21:42:38 +0900 Subject: [PATCH 236/420] Fix seasonal background not being unloaded when changing setting to "Never" Closes #20065. --- .../Backgrounds/SeasonalBackgroundLoader.cs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/osu.Game/Graphics/Backgrounds/SeasonalBackgroundLoader.cs b/osu.Game/Graphics/Backgrounds/SeasonalBackgroundLoader.cs index 99af95b5fe..5c98e22818 100644 --- a/osu.Game/Graphics/Backgrounds/SeasonalBackgroundLoader.cs +++ b/osu.Game/Graphics/Backgrounds/SeasonalBackgroundLoader.cs @@ -38,21 +38,19 @@ namespace osu.Game.Graphics.Backgrounds private void load(OsuConfigManager config, SessionStatics sessionStatics) { seasonalBackgroundMode = config.GetBindable(OsuSetting.SeasonalBackgroundMode); - seasonalBackgroundMode.BindValueChanged(_ => triggerSeasonalBackgroundChanged()); + seasonalBackgroundMode.BindValueChanged(_ => SeasonalBackgroundChanged?.Invoke()); seasonalBackgrounds = sessionStatics.GetBindable(Static.SeasonalBackgrounds); - seasonalBackgrounds.BindValueChanged(_ => triggerSeasonalBackgroundChanged()); + seasonalBackgrounds.BindValueChanged(_ => + { + if (shouldShowSeasonal) + SeasonalBackgroundChanged?.Invoke(); + }); apiState.BindTo(api.State); apiState.BindValueChanged(fetchSeasonalBackgrounds, true); } - private void triggerSeasonalBackgroundChanged() - { - if (shouldShowSeasonal) - SeasonalBackgroundChanged?.Invoke(); - } - private void fetchSeasonalBackgrounds(ValueChangedEvent stateChanged) { if (seasonalBackgrounds.Value != null || stateChanged.NewValue != APIState.Online) From d3ae60ec6d38ce3dd55d588833a4edbfc8a7e274 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 1 Sep 2022 22:02:58 +0900 Subject: [PATCH 237/420] Fix tournament population failure when beatmap is not found on server --- osu.Game.Tournament/Models/TournamentBeatmap.cs | 3 +-- osu.Game/Beatmaps/BeatmapSetOnlineCovers.cs | 2 -- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/osu.Game.Tournament/Models/TournamentBeatmap.cs b/osu.Game.Tournament/Models/TournamentBeatmap.cs index 274fddc490..7f57b6a151 100644 --- a/osu.Game.Tournament/Models/TournamentBeatmap.cs +++ b/osu.Game.Tournament/Models/TournamentBeatmap.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using osu.Framework.Extensions.ObjectExtensions; using osu.Game.Beatmaps; using osu.Game.Extensions; using osu.Game.Online.API.Requests.Responses; @@ -41,7 +40,7 @@ namespace osu.Game.Tournament.Models StarRating = beatmap.StarRating; Metadata = beatmap.Metadata; Difficulty = beatmap.Difficulty; - Covers = beatmap.BeatmapSet.AsNonNull().Covers; + Covers = beatmap.BeatmapSet?.Covers ?? new BeatmapSetOnlineCovers(); } public bool Equals(IBeatmapInfo? other) => other is TournamentBeatmap b && this.MatchesOnlineID(b); diff --git a/osu.Game/Beatmaps/BeatmapSetOnlineCovers.cs b/osu.Game/Beatmaps/BeatmapSetOnlineCovers.cs index b76496b145..aad31befa8 100644 --- a/osu.Game/Beatmaps/BeatmapSetOnlineCovers.cs +++ b/osu.Game/Beatmaps/BeatmapSetOnlineCovers.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. -#nullable disable - using Newtonsoft.Json; namespace osu.Game.Beatmaps From 22c18d9a81cfdb1e777b09ff4bfb6fa42170be67 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 1 Sep 2022 23:03:28 +0900 Subject: [PATCH 238/420] 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 85857771a5..219912425f 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,7 +52,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index f757fd77b9..43e3076f5c 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -36,7 +36,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/osu.iOS.props b/osu.iOS.props index 9fcc3753eb..0f0bf2848c 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -61,7 +61,7 @@ - + @@ -84,7 +84,7 @@ - + From c9dec473d3e4ef96d093da510b2b4f666512ed05 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 1 Sep 2022 23:05:07 +0900 Subject: [PATCH 239/420] Update virtual track logic to match framework changes --- osu.Game/Tests/Visual/OsuTestScene.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game/Tests/Visual/OsuTestScene.cs b/osu.Game/Tests/Visual/OsuTestScene.cs index 5a297fd109..5055153691 100644 --- a/osu.Game/Tests/Visual/OsuTestScene.cs +++ b/osu.Game/Tests/Visual/OsuTestScene.cs @@ -404,9 +404,9 @@ namespace osu.Game.Tests.Visual public IEnumerable GetAvailableResources() => throw new NotImplementedException(); - public Track GetVirtual(double length = double.PositiveInfinity) + public Track GetVirtual(double length = double.PositiveInfinity, string name = "virtual") { - var track = new TrackVirtualManual(referenceClock) { Length = length }; + var track = new TrackVirtualManual(referenceClock, name) { Length = length }; AddItem(track); return track; } @@ -421,7 +421,8 @@ namespace osu.Game.Tests.Visual private bool running; - public TrackVirtualManual(IFrameBasedClock referenceClock) + public TrackVirtualManual(IFrameBasedClock referenceClock, string name = "virtual") + : base(name) { this.referenceClock = referenceClock; Length = double.PositiveInfinity; From 7eaa4c5ccd89ff88c691f9bddf7cc30eea8a64f9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 1 Sep 2022 23:05:34 +0900 Subject: [PATCH 240/420] Update new usages of `Remove` / `RemoveInternal` --- osu.Game/Overlays/NotificationOverlayToastTray.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/NotificationOverlayToastTray.cs b/osu.Game/Overlays/NotificationOverlayToastTray.cs index 4417b5e0d0..c47a61eac1 100644 --- a/osu.Game/Overlays/NotificationOverlayToastTray.cs +++ b/osu.Game/Overlays/NotificationOverlayToastTray.cs @@ -126,13 +126,13 @@ namespace osu.Game.Overlays Debug.Assert(notification.Parent == toastFlow); // Temporarily remove from flow so we can animate the position off to the right. - toastFlow.Remove(notification); + toastFlow.Remove(notification, false); AddInternal(notification); notification.MoveToOffset(new Vector2(400, 0), NotificationOverlay.TRANSITION_LENGTH, Easing.OutQuint); notification.FadeOut(NotificationOverlay.TRANSITION_LENGTH, Easing.OutQuint).OnComplete(_ => { - RemoveInternal(notification); + RemoveInternal(notification, false); ForwardNotificationToPermanentStore?.Invoke(notification); notification.FadeIn(300, Easing.OutQuint); From 23d5e8b286af37ec6dd4c79e4b837a9df5d2c136 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 1 Sep 2022 23:45:59 +0900 Subject: [PATCH 241/420] Fix beat sync components stopping after beatmap change Not an amazing fix, but it seems to work and would rather get this in ASAP rather than trying to fix at a framework level. Closes #20059. --- osu.Game/OsuGameBase.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 97142d5472..c95a281f09 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -390,6 +390,11 @@ namespace osu.Game var framedClock = new FramedClock(beatmap.Track); beatmapClock.ChangeSource(framedClock); + + // Normally the internal decoupled clock will seek the *track* to the decoupled time, but we blocked this. + // It won't behave nicely unless we also set it to the track's time. + // Probably another thing which should be fixed in the decoupled mess (or just replaced). + beatmapClock.Seek(beatmap.Track.CurrentTime); } protected virtual void InitialiseFonts() From d13e353a534a8ee93493f019d9640da8e2bb7183 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Sep 2022 03:02:27 +0900 Subject: [PATCH 242/420] Fix double colour application in update progress notification I'd like to restore it to yellow, but let's clean the slate first. --- osu.Desktop/Updater/SquirrelUpdateManager.cs | 9 --------- 1 file changed, 9 deletions(-) diff --git a/osu.Desktop/Updater/SquirrelUpdateManager.cs b/osu.Desktop/Updater/SquirrelUpdateManager.cs index d53db6c516..6f45237522 100644 --- a/osu.Desktop/Updater/SquirrelUpdateManager.cs +++ b/osu.Desktop/Updater/SquirrelUpdateManager.cs @@ -6,8 +6,6 @@ using System.Runtime.Versioning; using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Graphics; -using osu.Framework.Graphics.Colour; -using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Logging; using osu.Game; @@ -15,7 +13,6 @@ using osu.Game.Graphics; using osu.Game.Overlays; using osu.Game.Overlays.Notifications; using osuTK; -using osuTK.Graphics; using Squirrel; using Squirrel.SimpleSplat; @@ -177,17 +174,11 @@ namespace osu.Desktop.Updater { IconContent.AddRange(new Drawable[] { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = ColourInfo.GradientVertical(colours.YellowDark, colours.Yellow) - }, new SpriteIcon { Anchor = Anchor.Centre, Origin = Anchor.Centre, Icon = FontAwesome.Solid.Upload, - Colour = Color4.White, Size = new Vector2(20), } }); From 9645bfe7085fccfd6c79156bf1e9378576545f82 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 2 Sep 2022 16:27:25 +0900 Subject: [PATCH 243/420] Bump difficulty calculator versions --- osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs | 2 +- osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs | 2 +- osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs index 5d30d33190..63e61f17e3 100644 --- a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs @@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty private readonly bool isForCurrentRuleset; private readonly double originalOverallDifficulty; - public override int Version => 20220701; + public override int Version => 20220902; public ManiaDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatmap) : base(ruleset, beatmap) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs index 0ebfb9a283..6ef17d47c0 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs @@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty private const double difficulty_multiplier = 0.0675; private double hitWindowGreat; - public override int Version => 20220701; + public override int Version => 20220902; public OsuDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatmap) : base(ruleset, beatmap) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs index ea2f04a3d9..2b0b563323 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs @@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty { private const double difficulty_multiplier = 1.35; - public override int Version => 20220701; + public override int Version => 20220902; public TaikoDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatmap) : base(ruleset, beatmap) From a70fc10d06ffb1f03ee0aaf895a5eeb978da5333 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Sep 2022 16:30:04 +0900 Subject: [PATCH 244/420] Fix mock track construction failure --- osu.Game.Tests/Editing/Checks/CheckAudioQualityTest.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Editing/Checks/CheckAudioQualityTest.cs b/osu.Game.Tests/Editing/Checks/CheckAudioQualityTest.cs index 9fdd49823e..50e6087526 100644 --- a/osu.Game.Tests/Editing/Checks/CheckAudioQualityTest.cs +++ b/osu.Game.Tests/Editing/Checks/CheckAudioQualityTest.cs @@ -7,10 +7,12 @@ using System.Linq; using Moq; using NUnit.Framework; using osu.Framework.Audio.Track; +using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Checks; using osu.Game.Rulesets.Objects; +using osu.Game.Tests.Visual; namespace osu.Game.Tests.Editing.Checks { @@ -109,7 +111,7 @@ namespace osu.Game.Tests.Editing.Checks /// The bitrate of the audio file the beatmap uses. private Mock getMockWorkingBeatmap(int? audioBitrate) { - var mockTrack = new Mock(); + var mockTrack = new Mock(new FramedClock(), "virtual"); mockTrack.SetupGet(t => t.Bitrate).Returns(audioBitrate); var mockWorkingBeatmap = new Mock(); From 8c50ccc48e973e86c2a3381d7fae69ae5e8c401a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Sep 2022 16:37:39 +0900 Subject: [PATCH 245/420] Fix incorrect specification in `SectionsContainer` --- osu.Game/Graphics/Containers/SectionsContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Graphics/Containers/SectionsContainer.cs b/osu.Game/Graphics/Containers/SectionsContainer.cs index b8a8ea79cc..583f1385c1 100644 --- a/osu.Game/Graphics/Containers/SectionsContainer.cs +++ b/osu.Game/Graphics/Containers/SectionsContainer.cs @@ -35,7 +35,7 @@ namespace osu.Game.Graphics.Containers if (value == expandableHeader) return; if (expandableHeader != null) - RemoveInternal(expandableHeader, false); + RemoveInternal(expandableHeader, true); expandableHeader = value; From b10026993ab4a9f2b55db30fc479a63c1a2f760d Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 2 Sep 2022 16:40:32 +0900 Subject: [PATCH 246/420] Don't serialise has_replay --- osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs b/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs index 659b661ef0..d7b97cdddf 100644 --- a/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs +++ b/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs @@ -18,9 +18,6 @@ namespace osu.Game.Online.API.Requests.Responses [Serializable] public class SoloScoreInfo : IHasOnlineID { - [JsonProperty("has_replay")] - public bool HasReplay { get; set; } - [JsonProperty("beatmap_id")] public int BeatmapID { get; set; } @@ -114,12 +111,16 @@ namespace osu.Game.Online.API.Requests.Responses [JsonProperty("pp")] public double? PP { get; set; } + [JsonProperty("has_replay")] + public bool HasReplay { get; set; } + public bool ShouldSerializeID() => false; public bool ShouldSerializeUser() => false; public bool ShouldSerializeBeatmap() => false; public bool ShouldSerializeBeatmapSet() => false; public bool ShouldSerializePP() => false; public bool ShouldSerializeOnlineID() => false; + public bool ShouldSerializeHasReplay() => false; #endregion From 65baf73d97b3e4b4bfcc22ffb5a1971054c306f4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Sep 2022 16:50:28 +0900 Subject: [PATCH 247/420] Add test scene --- .../Editing/TestSceneDifficultyDelete.cs | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 osu.Game.Tests/Visual/Editing/TestSceneDifficultyDelete.cs diff --git a/osu.Game.Tests/Visual/Editing/TestSceneDifficultyDelete.cs b/osu.Game.Tests/Visual/Editing/TestSceneDifficultyDelete.cs new file mode 100644 index 0000000000..ef0c91cd90 --- /dev/null +++ b/osu.Game.Tests/Visual/Editing/TestSceneDifficultyDelete.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; +using System.Linq; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Extensions; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Testing; +using osu.Game.Beatmaps; +using osu.Game.Graphics.UserInterface; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Osu; +using osu.Game.Storyboards; +using osu.Game.Tests.Beatmaps.IO; +using osuTK.Input; + +namespace osu.Game.Tests.Visual.Editing +{ + public class TestSceneDifficultyDelete : EditorTestScene + { + protected override Ruleset CreateEditorRuleset() => new OsuRuleset(); + protected override bool IsolateSavingFromDatabase => false; + + [Resolved] + private OsuGameBase game { get; set; } = null!; + + [Resolved] + private BeatmapManager beatmaps { get; set; } = null!; + + private BeatmapSetInfo importedBeatmapSet = null!; + + protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null!) + => beatmaps.GetWorkingBeatmap(importedBeatmapSet.Beatmaps.First()); + + public override void SetUpSteps() + { + AddStep("import test beatmap", () => importedBeatmapSet = BeatmapImportHelper.LoadOszIntoOsu(game, virtualTrack: true).GetResultSafely()); + base.SetUpSteps(); + } + + [Test] + public void TestDifficultyDelete() + { + string lastDiff = null!; + AddStep("remember selected difficulty", () => lastDiff = EditorBeatmap.BeatmapInfo.DifficultyName); + + AddStep("click File", () => this.ChildrenOfType().First().TriggerClick()); + AddStep("click Delete", () => this.ChildrenOfType().Single(deleteMenuItemPredicate).TriggerClick()); + AddStep("confirm", () => InputManager.Key(Key.Number2)); + + AddAssert("difficulty is deleted", () => + { + if (lastDiff == null!) throw new NullReferenceException(); + + var newSet = beatmaps.GetWorkingBeatmap(importedBeatmapSet.Beatmaps.First(), true).BeatmapSetInfo; + return newSet.Beatmaps.All(x => x.DifficultyName != lastDiff); + }); + } + + private bool deleteMenuItemPredicate(DrawableOsuMenuItem item) + { + return item.ChildrenOfType().Any(text => text.Text.ToString().StartsWith("Delete", StringComparison.Ordinal)); + } + } +} From 605108c938486b276a6b4c6806ccda2140771b81 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Sep 2022 16:58:46 +0900 Subject: [PATCH 248/420] Refactor/rename deletion method to read better --- osu.Game/Beatmaps/BeatmapManager.cs | 31 ++++++++++++++--------------- osu.Game/Screens/Edit/Editor.cs | 2 +- 2 files changed, 16 insertions(+), 17 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 2baac05d8b..d784f1e627 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -364,27 +364,26 @@ namespace osu.Game.Beatmaps } /// - /// Hard-Delete a beatmap difficulty locally. + /// Delete a beatmap difficulty immediately. /// - /// is generally preferred as it keeps the local beatmap set in sync with the server. - /// The beatmap difficulty to hide. - public void DeleteLocal(BeatmapInfo beatmapInfo) + /// + /// There's no undoing this operation, as we don't have a soft-deletion flag on . + /// This may be a future consideration if there's a user requirement for undeleting support. + /// + public void DeleteDifficultyImmediately(BeatmapInfo beatmapInfo) { - Realm.Run(r => + Realm.Write(r => { - using (var transaction = r.BeginWrite()) - { - if (!beatmapInfo.IsManaged) - beatmapInfo = r.Find(beatmapInfo.ID); + if (!beatmapInfo.IsManaged) + beatmapInfo = r.Find(beatmapInfo.ID); - var setInfo = beatmapInfo.BeatmapSet!; - DeleteFile(setInfo, beatmapInfo.File!); - setInfo.Beatmaps.Remove(beatmapInfo); + Debug.Assert(beatmapInfo.BeatmapSet != null); + Debug.Assert(beatmapInfo.File != null); - var liveSetInfo = r.Find(setInfo.ID); - setInfo.CopyChangesToRealm(liveSetInfo); - transaction.Commit(); - } + var setInfo = beatmapInfo.BeatmapSet; + + DeleteFile(setInfo, beatmapInfo.File); + setInfo.Beatmaps.Remove(beatmapInfo); }); } diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 10a86cdca3..24a89d124d 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -904,7 +904,7 @@ namespace osu.Game.Screens.Edit var current = playableBeatmap.BeatmapInfo; if (current is null) return; - beatmapManager.DeleteLocal(current); + beatmapManager.DeleteDifficultyImmediately(current); switchBeatmapOrExit(current.BeatmapSet); } From 9fd8067e11d4d21d39dc9e33b1e3b9df39647519 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Sep 2022 17:07:09 +0900 Subject: [PATCH 249/420] Tidy up dialog and deletion flow --- .../DeleteDifficultyConfirmationDialog.cs | 18 +++++++ osu.Game/Screens/Edit/Editor.cs | 52 ++++++++----------- .../Edit/PromptForDifficultyDeleteDialog.cs | 38 -------------- 3 files changed, 41 insertions(+), 67 deletions(-) create mode 100644 osu.Game/Screens/Edit/DeleteDifficultyConfirmationDialog.cs delete mode 100644 osu.Game/Screens/Edit/PromptForDifficultyDeleteDialog.cs diff --git a/osu.Game/Screens/Edit/DeleteDifficultyConfirmationDialog.cs b/osu.Game/Screens/Edit/DeleteDifficultyConfirmationDialog.cs new file mode 100644 index 0000000000..594042b426 --- /dev/null +++ b/osu.Game/Screens/Edit/DeleteDifficultyConfirmationDialog.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; +using osu.Game.Beatmaps; +using osu.Game.Overlays.Dialog; + +namespace osu.Game.Screens.Edit +{ + public class DeleteDifficultyConfirmationDialog : DeleteConfirmationDialog + { + public DeleteDifficultyConfirmationDialog(BeatmapInfo beatmapInfo, Action deleteAction) + { + BodyText = $"\"{beatmapInfo.DifficultyName}\" difficulty"; + DeleteAction = deleteAction; + } + } +} diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 24a89d124d..2e09b9a865 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -879,35 +879,6 @@ namespace osu.Game.Screens.Edit clock.SeekForward(!trackPlaying, amount); } - private void exportBeatmap() - { - Save(); - new LegacyBeatmapExporter(storage).Export(Beatmap.Value.BeatmapSetInfo); - } - - private void deleteDifficulty() - { - dialogOverlay?.Push(new PromptForDifficultyDeleteDialog(confirmDifficultyHide, confirmDifficultyDelete, () => { })); - } - - private void confirmDifficultyHide() - { - var current = playableBeatmap.BeatmapInfo; - if (current is null) return; - - beatmapManager.Hide(current); - switchBeatmapOrExit(current.BeatmapSet); - } - - private void confirmDifficultyDelete() - { - var current = playableBeatmap.BeatmapInfo; - if (current is null) return; - - beatmapManager.DeleteDifficultyImmediately(current); - switchBeatmapOrExit(current.BeatmapSet); - } - private void switchBeatmapOrExit([CanBeNull] BeatmapSetInfo setInfo) { if (setInfo is null || setInfo.Beatmaps.Count() <= 1) @@ -948,6 +919,29 @@ namespace osu.Game.Screens.Edit return fileMenuItems; } + private void exportBeatmap() + { + Save(); + new LegacyBeatmapExporter(storage).Export(Beatmap.Value.BeatmapSetInfo); + } + + private void deleteDifficulty() + { + if (dialogOverlay == null) + delete(); + else + dialogOverlay.Push(new DeleteDifficultyConfirmationDialog(Beatmap.Value.BeatmapInfo, delete)); + + void delete() + { + var current = playableBeatmap.BeatmapInfo; + if (current is null) return; + + beatmapManager.DeleteDifficultyImmediately(current); + switchBeatmapOrExit(current.BeatmapSet); + } + } + private EditorMenuItem createDifficultyCreationMenu() { var rulesetItems = new List(); diff --git a/osu.Game/Screens/Edit/PromptForDifficultyDeleteDialog.cs b/osu.Game/Screens/Edit/PromptForDifficultyDeleteDialog.cs deleted file mode 100644 index dcea9f1210..0000000000 --- a/osu.Game/Screens/Edit/PromptForDifficultyDeleteDialog.cs +++ /dev/null @@ -1,38 +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.Sprites; -using osu.Game.Overlays.Dialog; - -namespace osu.Game.Screens.Edit -{ - public class PromptForDifficultyDeleteDialog : PopupDialog - { - public PromptForDifficultyDeleteDialog(Action hide, Action delete, Action cancel) - { - HeaderText = "Are you sure you want to delete this difficulty?"; - - Icon = FontAwesome.Regular.TrashAlt; - - Buttons = new PopupDialogButton[] - { - new PopupDialogOkButton - { - Text = @"Hide this difficulty instead (recommended)", - Action = hide - }, - new PopupDialogDangerousButton - { - Text = @"Yes, DELETE this difficulty!", - Action = delete - }, - new PopupDialogCancelButton - { - Text = @"Oops, continue editing", - Action = cancel - }, - }; - } - } -} From 840d1c4cd59aefa26e0d3cb8e6aa8b0961e0aa51 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Sep 2022 17:14:48 +0900 Subject: [PATCH 250/420] Disable delete difficulty menu item when only one difficulty is present --- osu.Game/Screens/Edit/Editor.cs | 33 +++++++++++---------------------- 1 file changed, 11 insertions(+), 22 deletions(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 2e09b9a865..5df403e643 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -895,29 +895,18 @@ namespace osu.Game.Screens.Edit lastSavedHash = changeHandler?.CurrentStateHash; } - private List createFileMenuItems() + private List createFileMenuItems() => new List { - var fileMenuItems = new List - { - new EditorMenuItem("Save", MenuItemType.Standard, () => Save()) - }; - - if (RuntimeInfo.IsDesktop) - fileMenuItems.Add(new EditorMenuItem("Export package", MenuItemType.Standard, exportBeatmap)); - - fileMenuItems.Add(new EditorMenuItemSpacer()); - - fileMenuItems.Add(createDifficultyCreationMenu()); - fileMenuItems.Add(createDifficultySwitchMenu()); - - fileMenuItems.Add(new EditorMenuItemSpacer()); - - fileMenuItems.Add(new EditorMenuItem("Delete difficulty", MenuItemType.Standard, deleteDifficulty)); - - fileMenuItems.Add(new EditorMenuItemSpacer()); - fileMenuItems.Add(new EditorMenuItem("Exit", MenuItemType.Standard, this.Exit)); - return fileMenuItems; - } + new EditorMenuItem("Save", MenuItemType.Standard, () => Save()), + new EditorMenuItem("Export package", MenuItemType.Standard, exportBeatmap) { Action = { Disabled = !RuntimeInfo.IsDesktop } }, + new EditorMenuItemSpacer(), + createDifficultyCreationMenu(), + createDifficultySwitchMenu(), + new EditorMenuItemSpacer(), + new EditorMenuItem("Delete difficulty", MenuItemType.Standard, deleteDifficulty) { Action = { Disabled = Beatmap.Value.BeatmapSetInfo.Beatmaps.Count < 2 } }, + new EditorMenuItemSpacer(), + new EditorMenuItem("Exit", MenuItemType.Standard, this.Exit) + }; private void exportBeatmap() { From 7a8fa5c2e4364f3c50523f155674119c98ad6394 Mon Sep 17 00:00:00 2001 From: HiddenNode Date: Fri, 2 Sep 2022 09:56:00 +0100 Subject: [PATCH 251/420] Fix filenames mismatch --- ...eservingTextSprite.cs => TestSceneSizePreservingSpriteText.cs} | 0 .../{SizePreservingTextSprite.cs => SizePreservingSpriteText.cs} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename osu.Game.Tests/Visual/UserInterface/{TestSceneSizePreservingTextSprite.cs => TestSceneSizePreservingSpriteText.cs} (100%) rename osu.Game/Graphics/Sprites/{SizePreservingTextSprite.cs => SizePreservingSpriteText.cs} (100%) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneSizePreservingTextSprite.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneSizePreservingSpriteText.cs similarity index 100% rename from osu.Game.Tests/Visual/UserInterface/TestSceneSizePreservingTextSprite.cs rename to osu.Game.Tests/Visual/UserInterface/TestSceneSizePreservingSpriteText.cs diff --git a/osu.Game/Graphics/Sprites/SizePreservingTextSprite.cs b/osu.Game/Graphics/Sprites/SizePreservingSpriteText.cs similarity index 100% rename from osu.Game/Graphics/Sprites/SizePreservingTextSprite.cs rename to osu.Game/Graphics/Sprites/SizePreservingSpriteText.cs From 4f18105e9d9045a1cd1c4ae98d30997de73b4588 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Sep 2022 17:39:14 +0900 Subject: [PATCH 252/420] Ensure next beatmap selected matches the menu ordering --- osu.Game/Screens/Edit/Editor.cs | 44 ++++++++++++++++----------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 5df403e643..6e06683e38 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.Linq; using JetBrains.Annotations; using osu.Framework; @@ -879,17 +878,6 @@ namespace osu.Game.Screens.Edit clock.SeekForward(!trackPlaying, amount); } - private void switchBeatmapOrExit([CanBeNull] BeatmapSetInfo setInfo) - { - if (setInfo is null || setInfo.Beatmaps.Count() <= 1) - this.Exit(); - var nextBeatmap = setInfo!.Beatmaps.First(); - - // Force a refresh of the beatmap (and beatmap set) so the `Change difficulty` list is also updated. - Beatmap.Value = beatmapManager.GetWorkingBeatmap(nextBeatmap, true); - SwitchToDifficulty(Beatmap.Value.BeatmapInfo); - } - private void updateLastSavedHash() { lastSavedHash = changeHandler?.CurrentStateHash; @@ -914,6 +902,14 @@ namespace osu.Game.Screens.Edit new LegacyBeatmapExporter(storage).Export(Beatmap.Value.BeatmapSetInfo); } + /// + /// Beatmaps of the currently edited set, grouped by ruleset and ordered by difficulty. + /// + private IOrderedEnumerable> groupedOrderedBeatmaps => Beatmap.Value.BeatmapSetInfo.Beatmaps + .OrderBy(b => b.StarRating) + .GroupBy(b => b.Ruleset) + .OrderBy(group => group.Key); + private void deleteDifficulty() { if (dialogOverlay == null) @@ -923,11 +919,19 @@ namespace osu.Game.Screens.Edit void delete() { - var current = playableBeatmap.BeatmapInfo; - if (current is null) return; + BeatmapInfo difficultyToDelete = playableBeatmap.BeatmapInfo; - beatmapManager.DeleteDifficultyImmediately(current); - switchBeatmapOrExit(current.BeatmapSet); + var difficultiesBeforeDeletion = groupedOrderedBeatmaps.SelectMany(g => g).ToList(); + + beatmapManager.DeleteDifficultyImmediately(difficultyToDelete); + + int deletedIndex = difficultiesBeforeDeletion.IndexOf(difficultyToDelete); + // of note, we're still working with the cloned version, so indices are all prior to deletion. + BeatmapInfo nextToShow = difficultiesBeforeDeletion[deletedIndex == 0 ? 1 : deletedIndex - 1]; + + Beatmap.Value = beatmapManager.GetWorkingBeatmap(nextToShow, true); + + SwitchToDifficulty(nextToShow); } } @@ -960,18 +964,14 @@ namespace osu.Game.Screens.Edit private EditorMenuItem createDifficultySwitchMenu() { - var beatmapSet = playableBeatmap.BeatmapInfo.BeatmapSet; - - Debug.Assert(beatmapSet != null); - var difficultyItems = new List(); - foreach (var rulesetBeatmaps in beatmapSet.Beatmaps.GroupBy(b => b.Ruleset).OrderBy(group => group.Key)) + foreach (var rulesetBeatmaps in groupedOrderedBeatmaps) { if (difficultyItems.Count > 0) difficultyItems.Add(new EditorMenuItemSpacer()); - foreach (var beatmap in rulesetBeatmaps.OrderBy(b => b.StarRating)) + foreach (var beatmap in rulesetBeatmaps) { bool isCurrentDifficulty = playableBeatmap.BeatmapInfo.Equals(beatmap); difficultyItems.Add(new DifficultyMenuItem(beatmap, isCurrentDifficulty, SwitchToDifficulty)); From dc02b59a05cd76f224bc9c96535741c72a3342a1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Sep 2022 17:57:19 +0900 Subject: [PATCH 253/420] Add extra coverage to tests Also fixes a potential failure due to test beatmap having two difficulties with same name. --- .../Editing/TestSceneDifficultyDelete.cs | 47 ++++++++++++------- 1 file changed, 31 insertions(+), 16 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneDifficultyDelete.cs b/osu.Game.Tests/Visual/Editing/TestSceneDifficultyDelete.cs index ef0c91cd90..1520f64ec0 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneDifficultyDelete.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneDifficultyDelete.cs @@ -41,27 +41,42 @@ namespace osu.Game.Tests.Visual.Editing } [Test] - public void TestDifficultyDelete() + public void TestDeleteDifficulties() { - string lastDiff = null!; - AddStep("remember selected difficulty", () => lastDiff = EditorBeatmap.BeatmapInfo.DifficultyName); + Guid deletedDifficultyID = Guid.Empty; + int countBeforeDeletion = 0; - AddStep("click File", () => this.ChildrenOfType().First().TriggerClick()); - AddStep("click Delete", () => this.ChildrenOfType().Single(deleteMenuItemPredicate).TriggerClick()); - AddStep("confirm", () => InputManager.Key(Key.Number2)); - - AddAssert("difficulty is deleted", () => + for (int i = 0; i < 12; i++) { - if (lastDiff == null!) throw new NullReferenceException(); + // Will be reloaded after each deletion. + AddUntilStep("wait for editor to load", () => Editor?.ReadyForUse == true); - var newSet = beatmaps.GetWorkingBeatmap(importedBeatmapSet.Beatmaps.First(), true).BeatmapSetInfo; - return newSet.Beatmaps.All(x => x.DifficultyName != lastDiff); - }); + AddStep("store selected difficulty", () => + { + deletedDifficultyID = EditorBeatmap.BeatmapInfo.ID; + countBeforeDeletion = Beatmap.Value.BeatmapSetInfo.Beatmaps.Count; + }); + + AddStep("click File", () => this.ChildrenOfType().First().TriggerClick()); + + if (i == 11) + { + // last difficulty shouldn't be able to be deleted. + AddAssert("Delete menu item disabled", () => getDeleteMenuItem().Item.Action.Disabled); + } + else + { + AddStep("click delete", () => getDeleteMenuItem().TriggerClick()); + AddUntilStep("wait for dialog", () => DialogOverlay.CurrentDialog != null); + AddStep("confirm", () => InputManager.Key(Key.Number1)); + + AddAssert($"difficulty {i} is deleted", () => Beatmap.Value.BeatmapSetInfo.Beatmaps.Select(b => b.ID), () => Does.Not.Contain(deletedDifficultyID)); + AddAssert("count decreased by one", () => Beatmap.Value.BeatmapSetInfo.Beatmaps.Count, () => Is.EqualTo(countBeforeDeletion - 1)); + } + } } - private bool deleteMenuItemPredicate(DrawableOsuMenuItem item) - { - return item.ChildrenOfType().Any(text => text.Text.ToString().StartsWith("Delete", StringComparison.Ordinal)); - } + private DrawableOsuMenuItem getDeleteMenuItem() => this.ChildrenOfType() + .Single(item => item.ChildrenOfType().Any(text => text.Text.ToString().StartsWith("Delete", StringComparison.Ordinal))); } } From 8bfaa2a51f09e21a64db332b5de1716aa12e9dd0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Sep 2022 18:14:47 +0900 Subject: [PATCH 254/420] Fix tournament match handling right click to select itself, dismissing the context menu --- .../Screens/Ladder/Components/DrawableTournamentMatch.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tournament/Screens/Ladder/Components/DrawableTournamentMatch.cs b/osu.Game.Tournament/Screens/Ladder/Components/DrawableTournamentMatch.cs index 5204edf3be..ed8b789387 100644 --- a/osu.Game.Tournament/Screens/Ladder/Components/DrawableTournamentMatch.cs +++ b/osu.Game.Tournament/Screens/Ladder/Components/DrawableTournamentMatch.cs @@ -280,7 +280,7 @@ namespace osu.Game.Tournament.Screens.Ladder.Components protected override bool OnClick(ClickEvent e) { - if (editorInfo == null || Match is ConditionalTournamentMatch) + if (editorInfo == null || Match is ConditionalTournamentMatch || e.Button != MouseButton.Left) return false; Selected = true; From 8d6739ae73b5eb13b07ee29c02a11a7a20547267 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Sep 2022 18:20:23 +0900 Subject: [PATCH 255/420] Show team scores at the tournament map pool screen --- .../Screens/Gameplay/Components/TeamScoreDisplay.cs | 5 +++++ osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs | 5 ++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tournament/Screens/Gameplay/Components/TeamScoreDisplay.cs b/osu.Game.Tournament/Screens/Gameplay/Components/TeamScoreDisplay.cs index 5ee57e9271..0fa5884603 100644 --- a/osu.Game.Tournament/Screens/Gameplay/Components/TeamScoreDisplay.cs +++ b/osu.Game.Tournament/Screens/Gameplay/Components/TeamScoreDisplay.cs @@ -25,6 +25,7 @@ namespace osu.Game.Tournament.Screens.Gameplay.Components public bool ShowScore { + get => teamDisplay.ShowScore; set => teamDisplay.ShowScore = value; } @@ -92,10 +93,14 @@ namespace osu.Game.Tournament.Screens.Gameplay.Components private void teamChanged(ValueChangedEvent team) { + bool wasShowingScores = teamDisplay?.ShowScore ?? false; + InternalChildren = new Drawable[] { teamDisplay = new TeamDisplay(team.NewValue, teamColour, currentTeamScore, currentMatch.Value?.PointsToWin ?? 0), }; + + teamDisplay.ShowScore = wasShowingScores; } } } diff --git a/osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs b/osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs index 4d36515316..decd723814 100644 --- a/osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs +++ b/osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs @@ -46,7 +46,10 @@ namespace osu.Game.Tournament.Screens.MapPool Loop = true, RelativeSizeAxes = Axes.Both, }, - new MatchHeader(), + new MatchHeader + { + ShowScores = true, + }, mapFlows = new FillFlowContainer> { Y = 160, From 40ff2d50dd6e51feefc17fb1a7bfbce0e18c2be1 Mon Sep 17 00:00:00 2001 From: Josh <43808099+josh-codes@users.noreply.github.com> Date: Sat, 3 Sep 2022 02:31:58 +0800 Subject: [PATCH 256/420] Refactor UI and add drag support --- .../TestSceneCatchTouchInput.cs | 2 - osu.Game.Rulesets.Catch/UI/TouchInputField.cs | 58 ++++++++++++++----- 2 files changed, 42 insertions(+), 18 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatchTouchInput.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchTouchInput.cs index 03d031a0a8..24afda4cfa 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatchTouchInput.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchTouchInput.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. -#nullable disable - using NUnit.Framework; using osu.Framework.Graphics; using osu.Framework.Testing; diff --git a/osu.Game.Rulesets.Catch/UI/TouchInputField.cs b/osu.Game.Rulesets.Catch/UI/TouchInputField.cs index 771902df65..dbc8a51d9c 100644 --- a/osu.Game.Rulesets.Catch/UI/TouchInputField.cs +++ b/osu.Game.Rulesets.Catch/UI/TouchInputField.cs @@ -13,6 +13,7 @@ using osu.Game.Graphics; using osuTK.Graphics; using osuTK; using System.Collections.Generic; +using osu.Framework.Logging; namespace osu.Game.Rulesets.Catch.UI { @@ -39,10 +40,13 @@ namespace osu.Game.Rulesets.Catch.UI private ArrowHitbox leftDashBox = null!; private ArrowHitbox rightDashBox = null!; + // Force input to be prossed even when hidden. + public override bool PropagatePositionalInputSubTree => true; + public override bool PropagateNonPositionalInputSubTree => true; + [BackgroundDependencyLoader] private void load(CatchInputManager catchInputManager, OsuColour colours) { - Show(); Debug.Assert(catchInputManager.KeyBindingContainer != null); keyBindingContainer = catchInputManager.KeyBindingContainer; @@ -121,7 +125,7 @@ namespace osu.Game.Rulesets.Catch.UI protected override bool OnKeyDown(KeyDownEvent e) { // Hide whenever the keyboard is used. - PopOut(); + Hide(); return false; } @@ -143,19 +147,39 @@ namespace osu.Game.Rulesets.Catch.UI base.OnMouseUp(e); } - /* I plan to come back to this code to add touch support - * protected override void OnTouchMove(TouchMoveEvent e) + protected override bool OnDragStart(DragStartEvent e) + { + return true; + } + + protected override void OnDragEnd(DragEndEvent e) + { + base.OnDragEnd(e); + } + + protected override void OnDrag(DragEvent e) + { + // I'm not sure if this is posible but let's be safe + if (!trackedActions.ContainsKey(e.Button)) + trackedActions.Add(e.Button, TouchCatchAction.None); + + trackedActions[e.Button] = getTouchCatchActionFromInput(e.ScreenSpaceMousePosition); + calculateActiveKeys(); + + base.OnDrag(e); + } + protected override void OnTouchMove(TouchMoveEvent e) { // I'm not sure if this is posible but let's be safe if (!trackedActions.ContainsKey(e.Touch.Source)) trackedActions.Add(e.Touch.Source, TouchCatchAction.None); - trackedActions[e.Touch.Source] = getTouchCatchActionFromInput(e.MousePosition); + trackedActions[e.Touch.Source] = getTouchCatchActionFromInput(e.ScreenSpaceTouchDownPosition); calculateActiveKeys(); base.OnTouchMove(e); - }*/ + } protected override bool OnTouchDown(TouchDownEvent e) { @@ -189,7 +213,7 @@ namespace osu.Game.Rulesets.Catch.UI private void handleDown(object source, Vector2 position) { - PopIn(); + Show(); TouchCatchAction catchAction = getTouchCatchActionFromInput(position); @@ -217,8 +241,10 @@ namespace osu.Game.Rulesets.Catch.UI if (leftBox.Contains(inputPosition)) return TouchCatchAction.MoveLeft; if (rightBox.Contains(inputPosition)) + { + Logger.Log(inputPosition.ToString()); return TouchCatchAction.MoveRight; - + } return TouchCatchAction.None; } @@ -255,20 +281,20 @@ namespace osu.Game.Rulesets.Catch.UI RelativeSizeAxes = Axes.Both, Children = new Drawable[] { - overlay = new Box - { - Alpha = 0, - Colour = colour.Multiply(1.4f).Darken(2.8f), - Blending = BlendingParameters.Additive, - Width = 1, - RelativeSizeAxes = Axes.Both, - }, new Box { Alpha = 0.8f, Colour = colour, Width = 1, RelativeSizeAxes = Axes.Both, + }, + overlay = new Box + { + Alpha = 0, + Colour = colour.Multiply(1.4f), + Blending = BlendingParameters.Additive, + Width = 1, + RelativeSizeAxes = Axes.Both, } } } From 161c54df1cf981fe000a35e8412153de0d9cc95a Mon Sep 17 00:00:00 2001 From: Josh <43808099+josh-codes@users.noreply.github.com> Date: Sat, 3 Sep 2022 14:14:34 +0800 Subject: [PATCH 257/420] Refactor UI and add drag support --- osu.Game.Rulesets.Catch/UI/TouchInputField.cs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Catch/UI/TouchInputField.cs b/osu.Game.Rulesets.Catch/UI/TouchInputField.cs index dbc8a51d9c..80453d6aa3 100644 --- a/osu.Game.Rulesets.Catch/UI/TouchInputField.cs +++ b/osu.Game.Rulesets.Catch/UI/TouchInputField.cs @@ -13,7 +13,6 @@ using osu.Game.Graphics; using osuTK.Graphics; using osuTK; using System.Collections.Generic; -using osu.Framework.Logging; namespace osu.Game.Rulesets.Catch.UI { @@ -174,8 +173,7 @@ namespace osu.Game.Rulesets.Catch.UI if (!trackedActions.ContainsKey(e.Touch.Source)) trackedActions.Add(e.Touch.Source, TouchCatchAction.None); - trackedActions[e.Touch.Source] = getTouchCatchActionFromInput(e.ScreenSpaceTouchDownPosition); - + trackedActions[e.Touch.Source] = getTouchCatchActionFromInput(e.ScreenSpaceTouch.Position); calculateActiveKeys(); base.OnTouchMove(e); @@ -183,7 +181,7 @@ namespace osu.Game.Rulesets.Catch.UI protected override bool OnTouchDown(TouchDownEvent e) { - handleDown(e.Touch.Source, e.ScreenSpaceTouchDownPosition); + handleDown(e.Touch.Source, e.ScreenSpaceTouch.Position); return true; } @@ -241,10 +239,7 @@ namespace osu.Game.Rulesets.Catch.UI if (leftBox.Contains(inputPosition)) return TouchCatchAction.MoveLeft; if (rightBox.Contains(inputPosition)) - { - Logger.Log(inputPosition.ToString()); return TouchCatchAction.MoveRight; - } return TouchCatchAction.None; } From 778d767a1288fe8daaa1c814f9b42bad62283dae Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 3 Sep 2022 15:02:56 +0300 Subject: [PATCH 258/420] Revert disposal on `SectionsContainer` properties --- osu.Game/Graphics/Containers/SectionsContainer.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/Containers/SectionsContainer.cs b/osu.Game/Graphics/Containers/SectionsContainer.cs index 583f1385c1..97e9ff88b5 100644 --- a/osu.Game/Graphics/Containers/SectionsContainer.cs +++ b/osu.Game/Graphics/Containers/SectionsContainer.cs @@ -35,7 +35,7 @@ namespace osu.Game.Graphics.Containers if (value == expandableHeader) return; if (expandableHeader != null) - RemoveInternal(expandableHeader, true); + RemoveInternal(expandableHeader, false); expandableHeader = value; @@ -55,6 +55,7 @@ namespace osu.Game.Graphics.Containers fixedHeader?.Expire(); fixedHeader = value; + if (value == null) return; AddInternal(fixedHeader); @@ -70,8 +71,10 @@ namespace osu.Game.Graphics.Containers if (value == footer) return; if (footer != null) - scrollContainer.Remove(footer, true); + scrollContainer.Remove(footer, false); + footer = value; + if (value == null) return; footer.Anchor |= Anchor.y2; From b43995269ad724ecf2ee352bb147fa3b363872d8 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 3 Sep 2022 15:17:46 +0300 Subject: [PATCH 259/420] Dispose `ScrollingTeam`s on removal --- .../Screens/Drawings/Components/ScrollingTeamContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tournament/Screens/Drawings/Components/ScrollingTeamContainer.cs b/osu.Game.Tournament/Screens/Drawings/Components/ScrollingTeamContainer.cs index 55555adb80..8092c24ccb 100644 --- a/osu.Game.Tournament/Screens/Drawings/Components/ScrollingTeamContainer.cs +++ b/osu.Game.Tournament/Screens/Drawings/Components/ScrollingTeamContainer.cs @@ -170,7 +170,7 @@ namespace osu.Game.Tournament.Screens.Drawings.Components availableTeams.Add(team); - RemoveAll(c => c is ScrollingTeam, false); + RemoveAll(c => c is ScrollingTeam, true); setScrollState(ScrollState.Idle); } From e8fa872f61e08ddd88488a3bd7efd1e0b3fab6bb Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 3 Sep 2022 16:14:21 +0300 Subject: [PATCH 260/420] Fix room status dropdown position inconsistent on online-play screens --- .../OnlinePlay/Multiplayer/MultiplayerLoungeSubScreen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerLoungeSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerLoungeSubScreen.cs index 37b977cff7..8206d4b64d 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerLoungeSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerLoungeSubScreen.cs @@ -56,7 +56,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer roomAccessTypeDropdown.Current.BindValueChanged(_ => UpdateFilter()); - return base.CreateFilterControls().Prepend(roomAccessTypeDropdown); + return base.CreateFilterControls().Append(roomAccessTypeDropdown); } protected override FilterCriteria CreateFilterCriteria() From 5f0832ead72c9964cdc40fa00b01f260ffa5a1ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Acid=20Chicken=20=28=E7=A1=AB=E9=85=B8=E9=B6=8F=29?= Date: Mon, 5 Sep 2022 01:58:57 +0900 Subject: [PATCH 261/420] refactor(osu.Game): separate `OsuColour.ForHitResult` by usage --- osu.Game/Graphics/OsuColour.cs | 30 ++++++++++++++++++- .../Leaderboards/LeaderboardScoreTooltip.cs | 2 +- .../Judgements/DefaultJudgementPiece.cs | 2 +- .../Play/HUD/HitErrorMeters/HitErrorMeter.cs | 25 +--------------- .../Expanded/Statistics/HitResultStatistic.cs | 2 +- 5 files changed, 33 insertions(+), 28 deletions(-) diff --git a/osu.Game/Graphics/OsuColour.cs b/osu.Game/Graphics/OsuColour.cs index 6e2f460930..022b1a363f 100644 --- a/osu.Game/Graphics/OsuColour.cs +++ b/osu.Game/Graphics/OsuColour.cs @@ -102,7 +102,7 @@ namespace osu.Game.Graphics /// /// Retrieves the colour for a . /// - public Color4 ForHitResult(HitResult judgement) + public Color4 TextForHitResult(HitResult judgement) { switch (judgement) { @@ -125,6 +125,34 @@ namespace osu.Game.Graphics } } + public Color4 DrawForHitResult(HitResult result) + { + switch (result) + { + case HitResult.SmallTickMiss: + case HitResult.LargeTickMiss: + case HitResult.Miss: + return Red; + + case HitResult.Meh: + return Yellow; + + case HitResult.Ok: + return Green; + + case HitResult.Good: + return GreenLight; + + case HitResult.SmallTickHit: + case HitResult.LargeTickHit: + case HitResult.Great: + return Blue; + + default: + return BlueLight; + } + } + /// /// Retrieves a colour for the given . /// A value indicates that a "background" shade from the local diff --git a/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs b/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs index 2f3ece0e3b..23d4e64191 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs @@ -156,7 +156,7 @@ namespace osu.Game.Online.Leaderboards { Font = OsuFont.Torus.With(size: 12, weight: FontWeight.SemiBold), Text = displayName.ToUpper(), - Colour = colours.ForHitResult(result), + Colour = colours.TextForHitResult(result), }, new OsuSpriteText { diff --git a/osu.Game/Rulesets/Judgements/DefaultJudgementPiece.cs b/osu.Game/Rulesets/Judgements/DefaultJudgementPiece.cs index c2b27d4ce8..a854bf37f5 100644 --- a/osu.Game/Rulesets/Judgements/DefaultJudgementPiece.cs +++ b/osu.Game/Rulesets/Judgements/DefaultJudgementPiece.cs @@ -42,7 +42,7 @@ namespace osu.Game.Rulesets.Judgements Anchor = Anchor.Centre, Origin = Anchor.Centre, Text = Result.GetDescription().ToUpperInvariant(), - Colour = colours.ForHitResult(Result), + Colour = colours.TextForHitResult(Result), Font = OsuFont.Numeric.With(size: 20), Scale = new Vector2(0.85f, 1), } diff --git a/osu.Game/Screens/Play/HUD/HitErrorMeters/HitErrorMeter.cs b/osu.Game/Screens/Play/HUD/HitErrorMeters/HitErrorMeter.cs index 26befd659c..35d28b8e98 100644 --- a/osu.Game/Screens/Play/HUD/HitErrorMeters/HitErrorMeter.cs +++ b/osu.Game/Screens/Play/HUD/HitErrorMeters/HitErrorMeter.cs @@ -59,30 +59,7 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters protected Color4 GetColourForHitResult(HitResult result) { - switch (result) - { - case HitResult.SmallTickMiss: - case HitResult.LargeTickMiss: - case HitResult.Miss: - return colours.Red; - - case HitResult.Meh: - return colours.Yellow; - - case HitResult.Ok: - return colours.Green; - - case HitResult.Good: - return colours.GreenLight; - - case HitResult.SmallTickHit: - case HitResult.LargeTickHit: - case HitResult.Great: - return colours.Blue; - - default: - return colours.BlueLight; - } + return colours.DrawForHitResult(result); } /// diff --git a/osu.Game/Screens/Ranking/Expanded/Statistics/HitResultStatistic.cs b/osu.Game/Screens/Ranking/Expanded/Statistics/HitResultStatistic.cs index c23a5e668d..429b72c07c 100644 --- a/osu.Game/Screens/Ranking/Expanded/Statistics/HitResultStatistic.cs +++ b/osu.Game/Screens/Ranking/Expanded/Statistics/HitResultStatistic.cs @@ -23,7 +23,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics [BackgroundDependencyLoader] private void load(OsuColour colours) { - HeaderText.Colour = colours.ForHitResult(Result); + HeaderText.Colour = colours.TextForHitResult(Result); } } } From 074d2a7a3a64cb09b566e51399450da57bbc455e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Acid=20Chicken=20=28=E7=A1=AB=E9=85=B8=E9=B6=8F=29?= Date: Mon, 5 Sep 2022 02:01:44 +0900 Subject: [PATCH 262/420] chore(osu.Game): provide ordering index for `HitResult` --- osu.Game/Rulesets/Scoring/HitResult.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/osu.Game/Rulesets/Scoring/HitResult.cs b/osu.Game/Rulesets/Scoring/HitResult.cs index 5047fdea82..8078363212 100644 --- a/osu.Game/Rulesets/Scoring/HitResult.cs +++ b/osu.Game/Rulesets/Scoring/HitResult.cs @@ -4,10 +4,12 @@ #nullable disable using System; +using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; using System.Linq; using System.Runtime.Serialization; +using osu.Framework.Extensions.EnumExtensions; using osu.Framework.Utils; namespace osu.Game.Rulesets.Scoring @@ -135,6 +137,8 @@ namespace osu.Game.Rulesets.Scoring #pragma warning disable CS0618 public static class HitResultExtensions { + private static readonly IList order = EnumExtensions.GetValuesInOrder().ToList(); + /// /// Whether a increases the combo. /// @@ -282,6 +286,16 @@ namespace osu.Game.Rulesets.Scoring Debug.Assert(minResult <= maxResult); return result > minResult && result < maxResult; } + + /// + /// Ordered index of a . Used for sorting. + /// + /// The to get the index of. + /// The index of . + public static int OrderingIndex(this HitResult result) + { + return order.IndexOf(result); + } } #pragma warning restore CS0618 } From 0af6b3dc0fa41e65480c670c7a8dbc53f11f6de9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Acid=20Chicken=20=28=E7=A1=AB=E9=85=B8=E9=B6=8F=29?= Date: Mon, 5 Sep 2022 02:02:38 +0900 Subject: [PATCH 263/420] chore(osu.Game): colorize bars by OD on `HitEventTimingDistributionGraph` --- .../HitEventTimingDistributionGraph.cs | 109 ++++++++++++++---- 1 file changed, 84 insertions(+), 25 deletions(-) diff --git a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs index db69e270f6..3a696290b9 100644 --- a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs +++ b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs @@ -7,7 +7,6 @@ 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; @@ -48,6 +47,9 @@ namespace osu.Game.Screens.Ranking.Statistics /// private readonly IReadOnlyList hitEvents; + [Resolved] + private OsuColour colours { get; set; } + /// /// Creates a new . /// @@ -57,7 +59,7 @@ namespace osu.Game.Screens.Ranking.Statistics this.hitEvents = hitEvents.Where(e => !(e.HitObject.HitWindows is HitWindows.EmptyHitWindows) && e.Result.IsHit()).ToList(); } - private int[] bins; + private IDictionary[] bins; private double binSize; private double hitOffset; @@ -69,7 +71,7 @@ namespace osu.Game.Screens.Ranking.Statistics if (hitEvents == null || hitEvents.Count == 0) return; - bins = new int[total_timing_distribution_bins]; + bins = Enumerable.Range(0, total_timing_distribution_bins).Select(_ => new Dictionary()).ToArray>(); binSize = Math.Ceiling(hitEvents.Max(e => Math.Abs(e.TimeOffset)) / timing_distribution_bins); @@ -89,7 +91,8 @@ namespace osu.Game.Screens.Ranking.Statistics { bool roundUp = true; - Array.Clear(bins, 0, bins.Length); + foreach (var bin in bins) + bin.Clear(); foreach (var e in hitEvents) { @@ -110,23 +113,29 @@ namespace osu.Game.Screens.Ranking.Statistics // may be out of range when applying an offset. for such cases we can just drop the results. if (index >= 0 && index < bins.Length) - bins[index]++; + { + bins[index].TryGetValue(e.Result, out int value); + bins[index][e.Result] = ++value; + } } if (barDrawables != null) { for (int i = 0; i < barDrawables.Length; i++) { - barDrawables[i].UpdateOffset(bins[i]); + barDrawables[i].UpdateOffset(bins[i].Sum(b => b.Value)); } } else { - int maxCount = bins.Max(); + int maxCount = bins.Max(b => b.Values.Sum()); barDrawables = new Bar[total_timing_distribution_bins]; for (int i = 0; i < barDrawables.Length; i++) - barDrawables[i] = new Bar(bins[i], maxCount, i == timing_distribution_centre_bin_index); + { + IReadOnlyList values = bins[i].Select(b => new BarValue(b.Key.OrderingIndex(), b.Value, colours.DrawForHitResult(b.Key))).OrderBy(b => b.Index).ToList(); + barDrawables[i] = new Bar(values, maxCount, i == timing_distribution_centre_bin_index); + } Container axisFlow; @@ -207,52 +216,102 @@ namespace osu.Game.Screens.Ranking.Statistics } } + private readonly struct BarValue + { + public readonly int Index; + public readonly float Value; + public readonly Color4 Colour; + + public BarValue(int index, float value, Color4 colour) + { + Index = index; + Value = value; + Colour = colour; + } + } + private class Bar : CompositeDrawable { - private readonly float value; + private float totalValue => values.Sum(v => v.Value); + private float basalHeight => BoundingBox.Width / BoundingBox.Height; + private float availableHeight => 1 - basalHeight; + + private readonly IReadOnlyList values; private readonly float maxValue; - private readonly Circle boxOriginal; + private readonly Circle[] boxOriginals; private Circle boxAdjustment; - private const float minimum_height = 0.05f; - - public Bar(float value, float maxValue, bool isCentre) + public Bar(IReadOnlyList values, float maxValue, bool isCentre) { - this.value = value; + this.values = values; this.maxValue = maxValue; RelativeSizeAxes = Axes.Both; Masking = true; - InternalChildren = new Drawable[] + if (values.Any()) { - boxOriginal = new Circle + boxOriginals = values.Select(v => new Circle { RelativeSizeAxes = Axes.Both, Anchor = Anchor.BottomCentre, Origin = Anchor.BottomCentre, - Colour = isCentre ? Color4.White : Color4Extensions.FromHex("#66FFCC"), - Height = minimum_height, - }, - }; + Colour = isCentre ? Color4.White : v.Colour, + Height = 0, + }).ToArray(); + InternalChildren = boxOriginals.Reverse().ToArray(); + } + else + { + InternalChildren = boxOriginals = new[] + { + new Circle + { + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + Colour = isCentre ? Color4.White : Color4.Gray, + Height = 0, + }, + }; + } } private const double duration = 300; + private float offsetForValue(float value) + { + return availableHeight * value / maxValue; + } + + private float heightForValue(float value) + { + return basalHeight + offsetForValue(value); + } + protected override void LoadComplete() { base.LoadComplete(); - float height = Math.Clamp(value / maxValue, minimum_height, 1); + float offsetValue = 0; - if (height > minimum_height) - boxOriginal.ResizeHeightTo(height, duration, Easing.OutQuint); + if (values.Any()) + { + for (int i = 0; i < values.Count; i++) + { + boxOriginals[i].Y = BoundingBox.Height * offsetForValue(offsetValue); + boxOriginals[i].Delay(duration * i).ResizeHeightTo(heightForValue(values[i].Value), duration, Easing.OutQuint); + offsetValue -= values[i].Value; + } + } + else + boxOriginals.Single().ResizeHeightTo(basalHeight, duration, Easing.OutQuint); } public void UpdateOffset(float adjustment) { - bool hasAdjustment = adjustment != value && adjustment / maxValue >= minimum_height; + bool hasAdjustment = adjustment != totalValue; if (boxAdjustment == null) { @@ -271,7 +330,7 @@ namespace osu.Game.Screens.Ranking.Statistics }); } - boxAdjustment.ResizeHeightTo(Math.Clamp(adjustment / maxValue, minimum_height, 1), duration, Easing.OutQuint); + boxAdjustment.ResizeHeightTo(heightForValue(adjustment), duration, Easing.OutQuint); boxAdjustment.FadeTo(!hasAdjustment ? 0 : 1, duration, Easing.OutQuint); } } From b67fd3d8804a6461943057fc0cf6fb0adf5fbcbc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Acid=20Chicken=20=28=E7=A1=AB=E9=85=B8=E9=B6=8F=29?= Date: Mon, 5 Sep 2022 03:45:51 +0900 Subject: [PATCH 264/420] chore(osu.Game): split transform duration of bars on `HitTimingDistributionGraph` --- .../Ranking/Statistics/HitEventTimingDistributionGraph.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs index 3a696290b9..900fd2c907 100644 --- a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs +++ b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs @@ -278,7 +278,9 @@ namespace osu.Game.Screens.Ranking.Statistics } } - private const double duration = 300; + private const double total_duration = 300; + + private double duration => total_duration / Math.Max(values.Count, 1); private float offsetForValue(float value) { From 19ab1433c67b06b1a9f08bf5b64c14abcee5647d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Acid=20Chicken=20=28=E7=A1=AB=E9=85=B8=E9=B6=8F=29?= Date: Mon, 5 Sep 2022 03:46:23 +0900 Subject: [PATCH 265/420] test(osu.Game): add more test cases for `HitTimingDistributionGraph` --- ...estSceneHitEventTimingDistributionGraph.cs | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneHitEventTimingDistributionGraph.cs b/osu.Game.Tests/Visual/Ranking/TestSceneHitEventTimingDistributionGraph.cs index 44cb438a6b..9264ed7030 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneHitEventTimingDistributionGraph.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneHitEventTimingDistributionGraph.cs @@ -43,6 +43,50 @@ namespace osu.Game.Tests.Visual.Ranking createTest(Enumerable.Range(-150, 300).Select(i => new HitEvent(i / 50f, HitResult.Perfect, placeholder_object, placeholder_object, null)).ToList()); } + [Test] + public void TestSparse() + { + createTest(new List + { + new HitEvent(-7, HitResult.Perfect, placeholder_object, placeholder_object, null), + new HitEvent(-6, HitResult.Perfect, placeholder_object, placeholder_object, null), + new HitEvent(-5, HitResult.Perfect, placeholder_object, placeholder_object, null), + new HitEvent(5, HitResult.Perfect, placeholder_object, placeholder_object, null), + new HitEvent(6, HitResult.Perfect, placeholder_object, placeholder_object, null), + new HitEvent(7, HitResult.Perfect, placeholder_object, placeholder_object, null), + }); + } + + [Test] + public void TestVariousTypesOfHitResult() + { + createTest(CreateDistributedHitEvents(0, 50).Select(h => + { + var offset = Math.Abs(h.TimeOffset); + var result = offset > 36 ? HitResult.Miss : offset > 32 ? HitResult.Meh : offset > 24 ? HitResult.Ok : offset > 16 ? HitResult.Good : offset > 8 ? HitResult.Great : HitResult.Perfect; + return new HitEvent(h.TimeOffset, result, placeholder_object, placeholder_object, null); + }).ToList()); + } + + [Test] + public void TestMultipleWindowsOfHitResult() + { + var wide = CreateDistributedHitEvents(0, 50).Select(h => + { + var offset = Math.Abs(h.TimeOffset); + var result = offset > 36 ? HitResult.Miss : offset > 32 ? HitResult.Meh : offset > 24 ? HitResult.Ok : offset > 16 ? HitResult.Good : offset > 8 ? HitResult.Great : HitResult.Perfect; + return new HitEvent(h.TimeOffset, result, placeholder_object, placeholder_object, null); + }); + var narrow = CreateDistributedHitEvents(0, 50).Select(h => + { + var offset = Math.Abs(h.TimeOffset); + var result = offset > 25 ? HitResult.Miss : offset > 20 ? HitResult.Meh : offset > 15 ? HitResult.Ok : offset > 10 ? HitResult.Good : offset > 5 ? HitResult.Great : HitResult.Perfect; + return new HitEvent(h.TimeOffset, result, placeholder_object, placeholder_object, null); + }); + createTest(wide.Concat(narrow).ToList()); + } + + [Test] public void TestZeroTimeOffset() { From 7e77c9e8b47e87fa181db6912bfbc9a541f278d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Acid=20Chicken=20=28=E7=A1=AB=E9=85=B8=E9=B6=8F=29?= Date: Mon, 5 Sep 2022 04:44:27 +0900 Subject: [PATCH 266/420] chore(osu.Game): only the first result should be white at zero position on `HitEventTimingDistributionGraph` --- .../Ranking/Statistics/HitEventTimingDistributionGraph.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs index 900fd2c907..1be32a94af 100644 --- a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs +++ b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs @@ -252,12 +252,12 @@ namespace osu.Game.Screens.Ranking.Statistics if (values.Any()) { - boxOriginals = values.Select(v => new Circle + boxOriginals = values.Select((v, i) => new Circle { RelativeSizeAxes = Axes.Both, Anchor = Anchor.BottomCentre, Origin = Anchor.BottomCentre, - Colour = isCentre ? Color4.White : v.Colour, + Colour = isCentre && i == 0 ? Color4.White : v.Colour, Height = 0, }).ToArray(); InternalChildren = boxOriginals.Reverse().ToArray(); From 4ea7ca4c0748f1cead8465b6448510136255c289 Mon Sep 17 00:00:00 2001 From: Exanc <43091560+Exanc@users.noreply.github.com> Date: Mon, 5 Sep 2022 00:09:20 +0200 Subject: [PATCH 267/420] Slight tweak to the StarsSlider - MinimumStarsSlider now shows "0" when at it's minimum - Modified and NoResultsPlaceholder the tooltip to stay consistent with the changes --- .../Select/DifficultyRangeFilterControl.cs | 15 ++++++++++++++- osu.Game/Screens/Select/NoResultsPlaceholder.cs | 4 ++-- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Select/DifficultyRangeFilterControl.cs b/osu.Game/Screens/Select/DifficultyRangeFilterControl.cs index a82c969805..eb5a797ac0 100644 --- a/osu.Game/Screens/Select/DifficultyRangeFilterControl.cs +++ b/osu.Game/Screens/Select/DifficultyRangeFilterControl.cs @@ -65,6 +65,10 @@ namespace osu.Game.Screens.Select private class MinimumStarsSlider : StarsSlider { + public MinimumStarsSlider() : base("0") { } + + public override LocalisableString TooltipText => Current.Value.ToString(@"0.## stars"); + protected override void LoadComplete() { base.LoadComplete(); @@ -82,6 +86,8 @@ namespace osu.Game.Screens.Select private class MaximumStarsSlider : StarsSlider { + public MaximumStarsSlider() : base("∞") { } + protected override void LoadComplete() { base.LoadComplete(); @@ -100,6 +106,13 @@ namespace osu.Game.Screens.Select ? UserInterfaceStrings.NoLimit : Current.Value.ToString(@"0.## stars"); + protected readonly string DefaultValue; + + public StarsSlider(string defaultValue) + { + DefaultValue = defaultValue; + } + protected override bool OnHover(HoverEvent e) { base.OnHover(e); @@ -125,7 +138,7 @@ namespace osu.Game.Screens.Select Current.BindValueChanged(current => { - currentDisplay.Text = current.NewValue != Current.Default ? current.NewValue.ToString("N1") : "∞"; + currentDisplay.Text = current.NewValue != Current.Default ? current.NewValue.ToString("N1") : DefaultValue; }, true); } } diff --git a/osu.Game/Screens/Select/NoResultsPlaceholder.cs b/osu.Game/Screens/Select/NoResultsPlaceholder.cs index f3c3fb4d87..f44aa01588 100644 --- a/osu.Game/Screens/Select/NoResultsPlaceholder.cs +++ b/osu.Game/Screens/Select/NoResultsPlaceholder.cs @@ -127,10 +127,10 @@ namespace osu.Game.Screens.Select config.SetValue(OsuSetting.DisplayStarsMaximum, 10.1); }); - string lowerStar = filter.UserStarDifficulty.Min == null ? "∞" : $"{filter.UserStarDifficulty.Min:N1}"; + string lowerStar = filter.UserStarDifficulty.Min == null ? "0,0" : $"{filter.UserStarDifficulty.Min:N1}"; string upperStar = filter.UserStarDifficulty.Max == null ? "∞" : $"{filter.UserStarDifficulty.Max:N1}"; - textFlow.AddText($" the {lowerStar}-{upperStar} star difficulty filter."); + textFlow.AddText($" the {lowerStar} - {upperStar} star difficulty filter."); } // TODO: Add realm queries to hint at which ruleset results are available in (and allow clicking to switch). From 8cbd3443308f08941e7b670516ed25355665e948 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 5 Sep 2022 11:28:12 +0900 Subject: [PATCH 268/420] Improve performance when cancelling import with debugger attached --- .../Database/RealmArchiveModelImporter.cs | 53 +++++++++---------- 1 file changed, 24 insertions(+), 29 deletions(-) diff --git a/osu.Game/Database/RealmArchiveModelImporter.cs b/osu.Game/Database/RealmArchiveModelImporter.cs index b340d0ee4b..f1bc0bfe0e 100644 --- a/osu.Game/Database/RealmArchiveModelImporter.cs +++ b/osu.Game/Database/RealmArchiveModelImporter.cs @@ -108,47 +108,42 @@ namespace osu.Game.Database bool isBatchImport = tasks.Length >= minimum_items_considered_batch_import; - try + await Task.WhenAll(tasks.Select(async task => { - await Task.WhenAll(tasks.Select(async task => + if (notification.CancellationToken.IsCancellationRequested) + return; + + try { - notification.CancellationToken.ThrowIfCancellationRequested(); + var model = await Import(task, isBatchImport, notification.CancellationToken).ConfigureAwait(false); - try + lock (imported) { - var model = await Import(task, isBatchImport, notification.CancellationToken).ConfigureAwait(false); + if (model != null) + imported.Add(model); + current++; - lock (imported) - { - if (model != null) - imported.Add(model); - current++; + notification.Text = $"Imported {current} of {tasks.Length} {HumanisedModelName}s"; + notification.Progress = (float)current / tasks.Length; + } + } + catch (OperationCanceledException) + { + } + catch (Exception e) + { + Logger.Error(e, $@"Could not import ({task})", LoggingTarget.Database); + } + })).ConfigureAwait(false); - notification.Text = $"Imported {current} of {tasks.Length} {HumanisedModelName}s"; - notification.Progress = (float)current / tasks.Length; - } - } - catch (TaskCanceledException) - { - throw; - } - catch (Exception e) - { - Logger.Error(e, $@"Could not import ({task})", LoggingTarget.Database); - } - })).ConfigureAwait(false); - } - catch (OperationCanceledException) + if (imported.Count == 0) { - if (imported.Count == 0) + if (notification.CancellationToken.IsCancellationRequested) { notification.State = ProgressNotificationState.Cancelled; return imported; } - } - if (imported.Count == 0) - { notification.Text = $"{HumanisedModelName.Humanize(LetterCasing.Title)} import failed!"; notification.State = ProgressNotificationState.Cancelled; } From 57954bb8f51382476d7d1dfbbcb0943431a5ec63 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 5 Sep 2022 14:50:43 +0900 Subject: [PATCH 269/420] Enable nullability on `TimelineHitObjectBlueprint` --- .../Timeline/TimelineHitObjectBlueprint.cs | 61 +++++++++---------- 1 file changed, 30 insertions(+), 31 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs index 32ebc9c3c1..974e240552 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.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. -#nullable disable - using System; using JetBrains.Annotations; using osu.Framework.Allocation; @@ -33,19 +31,19 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { private const float circle_size = 38; - private Container repeatsContainer; + private Container? repeatsContainer; - public Action OnDragHandled; + public Action? OnDragHandled = null!; [UsedImplicitly] private readonly Bindable startTime; - private Bindable indexInCurrentComboBindable; + private Bindable? indexInCurrentComboBindable; - private Bindable comboIndexBindable; - private Bindable comboIndexWithOffsetsBindable; + private Bindable? comboIndexBindable; + private Bindable? comboIndexWithOffsetsBindable; - private Bindable displayColourBindable; + private Bindable displayColourBindable = null!; private readonly ExtendableCircle circle; private readonly Border border; @@ -54,7 +52,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private readonly OsuSpriteText comboIndexText; [Resolved] - private ISkinSource skin { get; set; } + private ISkinSource skin { get; set; } = null!; public TimelineHitObjectBlueprint(HitObject item) : base(item) @@ -124,7 +122,10 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline case IHasComboInformation comboInfo: indexInCurrentComboBindable = comboInfo.IndexInCurrentComboBindable.GetBoundCopy(); - indexInCurrentComboBindable.BindValueChanged(_ => updateComboIndex(), true); + indexInCurrentComboBindable.BindValueChanged(_ => + { + comboIndexText.Text = (indexInCurrentComboBindable.Value + 1).ToString(); + }, true); comboIndexBindable = comboInfo.ComboIndexBindable.GetBoundCopy(); comboIndexWithOffsetsBindable = comboInfo.ComboIndexWithOffsetsBindable.GetBoundCopy(); @@ -149,8 +150,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline updateColour(); } - private void updateComboIndex() => comboIndexText.Text = (indexInCurrentComboBindable.Value + 1).ToString(); - private void updateColour() { Color4 colour; @@ -183,11 +182,11 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline colouredComponents.Colour = OsuColour.ForegroundTextColourFor(averageColour); } - private SamplePointPiece sampleOverrideDisplay; - private DifficultyPointPiece difficultyOverrideDisplay; + private SamplePointPiece? sampleOverrideDisplay; + private DifficultyPointPiece? difficultyOverrideDisplay; - private DifficultyControlPoint difficultyControlPoint; - private SampleControlPoint sampleControlPoint; + private DifficultyControlPoint difficultyControlPoint = null!; + private SampleControlPoint sampleControlPoint = null!; protected override void Update() { @@ -276,16 +275,27 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline public class DragArea : Circle { - private readonly HitObject hitObject; + private readonly HitObject? hitObject; [Resolved] - private Timeline timeline { get; set; } + private EditorBeatmap beatmap { get; set; } = null!; - public Action OnDragHandled; + [Resolved] + private IBeatSnapProvider beatSnapProvider { get; set; } = null!; + + [Resolved] + private Timeline timeline { get; set; } = null!; + + [Resolved(CanBeNull = true)] + private IEditorChangeHandler? changeHandler { get; set; } + + private ScheduledDelegate? dragOperation; + + public Action? OnDragHandled; public override bool HandlePositionalInput => hitObject != null; - public DragArea(HitObject hitObject) + public DragArea(HitObject? hitObject) { this.hitObject = hitObject; @@ -356,23 +366,12 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline this.FadeTo(IsHovered || hasMouseDown ? 1f : 0.9f, 200, Easing.OutQuint); } - [Resolved] - private EditorBeatmap beatmap { get; set; } - - [Resolved] - private IBeatSnapProvider beatSnapProvider { get; set; } - - [Resolved(CanBeNull = true)] - private IEditorChangeHandler changeHandler { get; set; } - protected override bool OnDragStart(DragStartEvent e) { changeHandler?.BeginChange(); return true; } - private ScheduledDelegate dragOperation; - protected override void OnDrag(DragEvent e) { base.OnDrag(e); From 8af8adf22d1359673e4992d81cef7604e518685c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 5 Sep 2022 16:17:26 +0900 Subject: [PATCH 270/420] Fix incorrect slider length in timeline when non-default velocity is inherited from previous object --- .../Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs index 59be93530c..4857f13a64 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -89,6 +89,11 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders HitObject.DifficultyControlPoint = nearestDifficultyPoint ?? new DifficultyControlPoint(); HitObject.Position = ToLocalSpace(result.ScreenSpacePosition); + + // Replacing the DifficultyControlPoint above doesn't trigger any kind of invalidation. + // Without re-applying defaults, velocity won't be updated. + // If this causes further issues, it may be better to copy the velocity p + ApplyDefaultsToHitObject(); break; case SliderPlacementState.Body: From 2bec8b82b355ac78be961b235e620d341a63bdc9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 5 Sep 2022 16:26:41 +0900 Subject: [PATCH 271/420] Fix textbox sample playback potentially crashing if called before load --- osu.Game/Graphics/UserInterface/OsuTextBox.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/OsuTextBox.cs b/osu.Game/Graphics/UserInterface/OsuTextBox.cs index e5341cfd4b..4c2e00d6e0 100644 --- a/osu.Game/Graphics/UserInterface/OsuTextBox.cs +++ b/osu.Game/Graphics/UserInterface/OsuTextBox.cs @@ -283,7 +283,7 @@ namespace osu.Game.Graphics.UserInterface return samples[RNG.Next(0, samples.Length)]?.GetChannel(); } - private void playSample(FeedbackSampleType feedbackSample) + private void playSample(FeedbackSampleType feedbackSample) => Schedule(() => { if (Time.Current < sampleLastPlaybackTime + 15) return; @@ -300,7 +300,7 @@ namespace osu.Game.Graphics.UserInterface channel.Play(); sampleLastPlaybackTime = Time.Current; - } + }); private class OsuCaret : Caret { From f1d9b225a7ddc43427ecf825910e2e24e18a5f79 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 5 Sep 2022 16:40:10 +0900 Subject: [PATCH 272/420] Remove probably pointless comment --- .../Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs index 4857f13a64..e2f98c273e 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -92,7 +92,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders // Replacing the DifficultyControlPoint above doesn't trigger any kind of invalidation. // Without re-applying defaults, velocity won't be updated. - // If this causes further issues, it may be better to copy the velocity p ApplyDefaultsToHitObject(); break; From 6946015d17fe0f6feddcc5e5e3a974cc39bc54d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Acid=20Chicken=20=28=E7=A1=AB=E9=85=B8=E9=B6=8F=29?= Date: Mon, 5 Sep 2022 07:49:29 +0000 Subject: [PATCH 273/420] style(osu.Game): fix multiple blank lines --- .../Visual/Ranking/TestSceneHitEventTimingDistributionGraph.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneHitEventTimingDistributionGraph.cs b/osu.Game.Tests/Visual/Ranking/TestSceneHitEventTimingDistributionGraph.cs index 9264ed7030..fe4aba1317 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneHitEventTimingDistributionGraph.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneHitEventTimingDistributionGraph.cs @@ -86,7 +86,6 @@ namespace osu.Game.Tests.Visual.Ranking createTest(wide.Concat(narrow).ToList()); } - [Test] public void TestZeroTimeOffset() { From 4fa6707bf08c6f347c732c1de9b322e1fd0e0020 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Sep 2022 00:58:10 +0900 Subject: [PATCH 274/420] Set all progress notifications to non-important --- osu.Game/Overlays/Notifications/ProgressNotification.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Overlays/Notifications/ProgressNotification.cs b/osu.Game/Overlays/Notifications/ProgressNotification.cs index 14cf6b3013..6821c25ac0 100644 --- a/osu.Game/Overlays/Notifications/ProgressNotification.cs +++ b/osu.Game/Overlays/Notifications/ProgressNotification.cs @@ -162,6 +162,8 @@ namespace osu.Game.Overlays.Notifications public override bool DisplayOnTop => false; + public override bool IsImportant => false; + private readonly ProgressBar progressBar; private Color4 colourQueued; private Color4 colourActive; From 56886fed09f87ad71c7c4bbd9b110d8cfed97e7a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Sep 2022 00:58:27 +0900 Subject: [PATCH 275/420] Add test coverage of progress notification completions showing --- .../TestSceneNotificationOverlay.cs | 30 ++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs index 38eecaa052..288f1f78bc 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs @@ -23,9 +23,12 @@ namespace osu.Game.Tests.Visual.UserInterface private SpriteText displayedCount = null!; + public double TimeToCompleteProgress { get; set; } = 2000; + [SetUp] public void SetUp() => Schedule(() => { + TimeToCompleteProgress = 2000; progressingNotifications.Clear(); Content.Children = new Drawable[] @@ -45,6 +48,7 @@ namespace osu.Game.Tests.Visual.UserInterface public void TestCompleteProgress() { ProgressNotification notification = null!; + AddStep("add progress notification", () => { notification = new ProgressNotification @@ -57,6 +61,30 @@ namespace osu.Game.Tests.Visual.UserInterface }); AddUntilStep("wait completion", () => notification.State == ProgressNotificationState.Completed); + + AddAssert("Completion toast shown", () => notificationOverlay.ToastCount == 1); + } + + [Test] + public void TestCompleteProgressSlow() + { + ProgressNotification notification = null!; + + AddStep("Set progress slow", () => TimeToCompleteProgress *= 2); + AddStep("add progress notification", () => + { + notification = new ProgressNotification + { + Text = @"Uploading to BSS...", + CompletionText = "Uploaded to BSS!", + }; + notificationOverlay.Post(notification); + progressingNotifications.Add(notification); + }); + + AddUntilStep("wait completion", () => notification.State == ProgressNotificationState.Completed); + + AddAssert("Completion toast shown", () => notificationOverlay.ToastCount == 1); } [Test] @@ -177,7 +205,7 @@ namespace osu.Game.Tests.Visual.UserInterface foreach (var n in progressingNotifications.FindAll(n => n.State == ProgressNotificationState.Active)) { if (n.Progress < 1) - n.Progress += (float)(Time.Elapsed / 2000); + n.Progress += (float)(Time.Elapsed / TimeToCompleteProgress); else n.State = ProgressNotificationState.Completed; } From eca7b8f9884284d53d506c28b1ceadfedbffdfd8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Sep 2022 00:51:59 +0900 Subject: [PATCH 276/420] Fix completion notifications not always showing as toasts --- osu.Game/Overlays/NotificationOverlay.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/NotificationOverlay.cs b/osu.Game/Overlays/NotificationOverlay.cs index 2c39ebcc87..fca43e65a5 100644 --- a/osu.Game/Overlays/NotificationOverlay.cs +++ b/osu.Game/Overlays/NotificationOverlay.cs @@ -137,7 +137,9 @@ namespace osu.Game.Overlays private readonly Scheduler postScheduler = new Scheduler(); - public override bool IsPresent => base.IsPresent || postScheduler.HasPendingTasks; + public override bool IsPresent => base.IsPresent + || postScheduler.HasPendingTasks + || toastTray.IsDisplayingToasts; private bool processingPosts = true; From 0d4ee6bd80ca8e8004de73f26b56f5695297b906 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 5 Sep 2022 18:35:02 +0900 Subject: [PATCH 277/420] Centralise ability to fetch all toast tray notifications (including animating ones) --- osu.Game/Overlays/NotificationOverlayToastTray.cs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/NotificationOverlayToastTray.cs b/osu.Game/Overlays/NotificationOverlayToastTray.cs index c47a61eac1..aa61e20a02 100644 --- a/osu.Game/Overlays/NotificationOverlayToastTray.cs +++ b/osu.Game/Overlays/NotificationOverlayToastTray.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; using System.Linq; using osu.Framework.Allocation; @@ -23,7 +24,7 @@ namespace osu.Game.Overlays /// public class NotificationOverlayToastTray : CompositeDrawable { - public bool IsDisplayingToasts => toastFlow.Count > 0; + public bool IsDisplayingToasts => displayedCount > 0; private FillFlowContainer toastFlow = null!; private BufferedContainer toastContentBackground = null!; @@ -33,11 +34,14 @@ namespace osu.Game.Overlays public Action? ForwardNotificationToPermanentStore { get; set; } - public int UnreadCount => toastFlow.Count(n => !n.WasClosed && !n.Read) - + InternalChildren.OfType().Count(n => !n.WasClosed && !n.Read); + public int UnreadCount => allNotifications.Count(n => !n.WasClosed && !n.Read); + + private IEnumerable allNotifications => toastFlow.Concat(InternalChildren.OfType()); private int runningDepth; + private int displayedCount; + [BackgroundDependencyLoader] private void load() { @@ -92,6 +96,7 @@ namespace osu.Game.Overlays public void Post(Notification notification) { ++runningDepth; + displayedCount++; int depth = notification.DisplayOnTop ? -runningDepth : runningDepth; @@ -133,6 +138,7 @@ namespace osu.Game.Overlays notification.FadeOut(NotificationOverlay.TRANSITION_LENGTH, Easing.OutQuint).OnComplete(_ => { RemoveInternal(notification, false); + displayedCount--; ForwardNotificationToPermanentStore?.Invoke(notification); notification.FadeIn(300, Easing.OutQuint); @@ -144,7 +150,7 @@ namespace osu.Game.Overlays base.Update(); float height = toastFlow.DrawHeight + 120; - float alpha = IsDisplayingToasts ? MathHelper.Clamp(toastFlow.DrawHeight / 40, 0, 1) * toastFlow.Children.Max(n => n.Alpha) : 0; + float alpha = toastFlow.Count > 0 ? MathHelper.Clamp(toastFlow.DrawHeight / 41, 0, 1) * toastFlow.Children.Max(n => n.Alpha) : 0; toastContentBackground.Height = (float)Interpolation.DampContinuously(toastContentBackground.Height, height, 10, Clock.ElapsedFrameTime); toastContentBackground.Alpha = (float)Interpolation.DampContinuously(toastContentBackground.Alpha, alpha, 10, Clock.ElapsedFrameTime); From 2923c10cd808f555375c081b008a4558460ec4e9 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 1 Sep 2022 18:53:35 +0900 Subject: [PATCH 278/420] Rewrite rooms to store multiple active countdowns Update test to the new structure --- .../Multiplayer/TestSceneMatchStartControl.cs | 15 ++++++---- .../Countdown/CountdownChangedEvent.cs | 20 ------------- .../Countdown/CountdownStartedEvent.cs | 28 +++++++++++++++++++ .../Countdown/CountdownStoppedEvent.cs | 28 +++++++++++++++++++ .../Countdown/StopCountdownRequest.cs | 10 +++++++ .../Online/Multiplayer/MatchServerEvent.cs | 3 +- .../Online/Multiplayer/MultiplayerClient.cs | 10 +++++-- .../Multiplayer/MultiplayerCountdown.cs | 10 +++++-- .../Online/Multiplayer/MultiplayerRoom.cs | 4 +-- osu.Game/Online/SignalRWorkaroundTypes.cs | 3 +- .../Multiplayer/Match/MatchStartControl.cs | 10 +++++-- .../Match/MultiplayerCountdownButton.cs | 5 ++-- .../Match/MultiplayerReadyButton.cs | 25 ++++++----------- 13 files changed, 117 insertions(+), 54 deletions(-) delete mode 100644 osu.Game/Online/Multiplayer/Countdown/CountdownChangedEvent.cs create mode 100644 osu.Game/Online/Multiplayer/Countdown/CountdownStartedEvent.cs create mode 100644 osu.Game/Online/Multiplayer/Countdown/CountdownStoppedEvent.cs diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchStartControl.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchStartControl.cs index a800b21bc9..12e7394c93 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchStartControl.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchStartControl.cs @@ -91,8 +91,7 @@ namespace osu.Game.Tests.Visual.Multiplayer break; case StopCountdownRequest: - multiplayerRoom.Countdown = null; - raiseRoomUpdated(); + clearRoomCountdown(); break; } }); @@ -244,14 +243,14 @@ namespace osu.Game.Tests.Visual.Multiplayer }); AddStep("start countdown", () => multiplayerClient.Object.SendMatchRequest(new StartMatchCountdownRequest { Duration = TimeSpan.FromMinutes(1) }).WaitSafely()); - AddUntilStep("countdown started", () => multiplayerRoom.Countdown != null); + AddUntilStep("countdown started", () => multiplayerRoom.ActiveCountdowns.Any()); AddStep("transfer host to local user", () => transferHost(localUser)); AddUntilStep("local user is host", () => multiplayerRoom.Host?.Equals(multiplayerClient.Object.LocalUser) == true); ClickButtonWhenEnabled(); checkLocalUserState(MultiplayerUserState.Ready); - AddAssert("countdown still active", () => multiplayerRoom.Countdown != null); + AddAssert("countdown still active", () => multiplayerRoom.ActiveCountdowns.Any()); } [Test] @@ -392,7 +391,13 @@ namespace osu.Game.Tests.Visual.Multiplayer private void setRoomCountdown(TimeSpan duration) { - multiplayerRoom.Countdown = new MatchStartCountdown { TimeRemaining = duration }; + multiplayerRoom.ActiveCountdowns.Add(new MatchStartCountdown { TimeRemaining = duration }); + raiseRoomUpdated(); + } + + private void clearRoomCountdown() + { + multiplayerRoom.ActiveCountdowns.Clear(); raiseRoomUpdated(); } diff --git a/osu.Game/Online/Multiplayer/Countdown/CountdownChangedEvent.cs b/osu.Game/Online/Multiplayer/Countdown/CountdownChangedEvent.cs deleted file mode 100644 index 649e3c8389..0000000000 --- a/osu.Game/Online/Multiplayer/Countdown/CountdownChangedEvent.cs +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using MessagePack; - -namespace osu.Game.Online.Multiplayer.Countdown -{ - /// - /// Indicates a change to the 's countdown. - /// - [MessagePackObject] - public class CountdownChangedEvent : MatchServerEvent - { - /// - /// The new countdown. - /// - [Key(0)] - public MultiplayerCountdown? Countdown { get; set; } - } -} diff --git a/osu.Game/Online/Multiplayer/Countdown/CountdownStartedEvent.cs b/osu.Game/Online/Multiplayer/Countdown/CountdownStartedEvent.cs new file mode 100644 index 0000000000..1dbb27bce6 --- /dev/null +++ b/osu.Game/Online/Multiplayer/Countdown/CountdownStartedEvent.cs @@ -0,0 +1,28 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using MessagePack; +using Newtonsoft.Json; + +namespace osu.Game.Online.Multiplayer.Countdown +{ + /// + /// Indicates that a countdown started in the . + /// + [MessagePackObject] + public class CountdownStartedEvent : MatchServerEvent + { + /// + /// The countdown that was started. + /// + [Key(0)] + public readonly MultiplayerCountdown Countdown; + + [JsonConstructor] + [SerializationConstructor] + public CountdownStartedEvent(MultiplayerCountdown countdown) + { + Countdown = countdown; + } + } +} diff --git a/osu.Game/Online/Multiplayer/Countdown/CountdownStoppedEvent.cs b/osu.Game/Online/Multiplayer/Countdown/CountdownStoppedEvent.cs new file mode 100644 index 0000000000..b46ed0e5e0 --- /dev/null +++ b/osu.Game/Online/Multiplayer/Countdown/CountdownStoppedEvent.cs @@ -0,0 +1,28 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using MessagePack; +using Newtonsoft.Json; + +namespace osu.Game.Online.Multiplayer.Countdown +{ + /// + /// Indicates that a countdown was stopped in the . + /// + [MessagePackObject] + public class CountdownStoppedEvent : MatchServerEvent + { + /// + /// The identifier of the countdown that was stopped. + /// + [Key(0)] + public readonly int ID; + + [JsonConstructor] + [SerializationConstructor] + public CountdownStoppedEvent(int id) + { + ID = id; + } + } +} diff --git a/osu.Game/Online/Multiplayer/Countdown/StopCountdownRequest.cs b/osu.Game/Online/Multiplayer/Countdown/StopCountdownRequest.cs index bd0c381c0b..495252c044 100644 --- a/osu.Game/Online/Multiplayer/Countdown/StopCountdownRequest.cs +++ b/osu.Game/Online/Multiplayer/Countdown/StopCountdownRequest.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using MessagePack; +using Newtonsoft.Json; namespace osu.Game.Online.Multiplayer.Countdown { @@ -11,5 +12,14 @@ namespace osu.Game.Online.Multiplayer.Countdown [MessagePackObject] public class StopCountdownRequest : MatchUserRequest { + [Key(0)] + public readonly int ID; + + [JsonConstructor] + [SerializationConstructor] + public StopCountdownRequest(int id) + { + ID = id; + } } } diff --git a/osu.Game/Online/Multiplayer/MatchServerEvent.cs b/osu.Game/Online/Multiplayer/MatchServerEvent.cs index 20bf9e5141..376ff4d261 100644 --- a/osu.Game/Online/Multiplayer/MatchServerEvent.cs +++ b/osu.Game/Online/Multiplayer/MatchServerEvent.cs @@ -13,7 +13,8 @@ namespace osu.Game.Online.Multiplayer [Serializable] [MessagePackObject] // IMPORTANT: Add rules to SignalRUnionWorkaroundResolver for new derived types. - [Union(0, typeof(CountdownChangedEvent))] + [Union(0, typeof(CountdownStartedEvent))] + [Union(1, typeof(CountdownStoppedEvent))] public abstract class MatchServerEvent { } diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index 04b87c19da..5575113a87 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -550,8 +550,14 @@ namespace osu.Game.Online.Multiplayer switch (e) { - case CountdownChangedEvent countdownChangedEvent: - Room.Countdown = countdownChangedEvent.Countdown; + case CountdownStartedEvent countdownStartedEvent: + Room.ActiveCountdowns.Add(countdownStartedEvent.Countdown); + break; + + case CountdownStoppedEvent countdownStoppedEvent: + MultiplayerCountdown? countdown = Room.ActiveCountdowns.FirstOrDefault(countdown => countdown.ID == countdownStoppedEvent.ID); + if (countdown != null) + Room.ActiveCountdowns.Remove(countdown); break; } diff --git a/osu.Game/Online/Multiplayer/MultiplayerCountdown.cs b/osu.Game/Online/Multiplayer/MultiplayerCountdown.cs index 621f9236fd..00bfa8919a 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerCountdown.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerCountdown.cs @@ -15,13 +15,19 @@ namespace osu.Game.Online.Multiplayer [Union(1, typeof(ForceGameplayStartCountdown))] public abstract class MultiplayerCountdown { + /// + /// A unique identifier for this countdown. + /// + [Key(0)] + public int ID { get; set; } + /// /// The amount of time remaining in the countdown. /// /// - /// This is only sent once from the server upon initial retrieval of the or via a . + /// This is only sent once from the server upon initial retrieval of the or via a . /// - [Key(0)] + [Key(1)] public TimeSpan TimeRemaining { get; set; } } } diff --git a/osu.Game/Online/Multiplayer/MultiplayerRoom.cs b/osu.Game/Online/Multiplayer/MultiplayerRoom.cs index fb05c03256..00048fa931 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerRoom.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerRoom.cs @@ -53,10 +53,10 @@ namespace osu.Game.Online.Multiplayer public IList Playlist { get; set; } = new List(); /// - /// The currently-running countdown. + /// The currently running countdowns. /// [Key(7)] - public MultiplayerCountdown? Countdown { get; set; } + public IList ActiveCountdowns { get; set; } = new List(); [JsonConstructor] [SerializationConstructor] diff --git a/osu.Game/Online/SignalRWorkaroundTypes.cs b/osu.Game/Online/SignalRWorkaroundTypes.cs index 29e01e13ae..3518fbb4fe 100644 --- a/osu.Game/Online/SignalRWorkaroundTypes.cs +++ b/osu.Game/Online/SignalRWorkaroundTypes.cs @@ -23,7 +23,8 @@ namespace osu.Game.Online (typeof(ChangeTeamRequest), typeof(MatchUserRequest)), (typeof(StartMatchCountdownRequest), typeof(MatchUserRequest)), (typeof(StopCountdownRequest), typeof(MatchUserRequest)), - (typeof(CountdownChangedEvent), typeof(MatchServerEvent)), + (typeof(CountdownStartedEvent), typeof(MatchServerEvent)), + (typeof(CountdownStoppedEvent), typeof(MatchServerEvent)), (typeof(TeamVersusRoomState), typeof(MatchRoomState)), (typeof(TeamVersusUserState), typeof(MatchUserState)), (typeof(MatchStartCountdown), typeof(MultiplayerCountdown)), diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MatchStartControl.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MatchStartControl.cs index 501c76f65b..f048ae59cd 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MatchStartControl.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MatchStartControl.cs @@ -109,7 +109,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match Debug.Assert(clickOperation == null); clickOperation = ongoingOperationTracker.BeginOperation(); - if (isReady() && Client.IsHost && Room.Countdown == null) + if (isReady() && Client.IsHost && !Room.ActiveCountdowns.Any(c => c is MatchStartCountdown)) startMatch(); else toggleReady(); @@ -140,10 +140,14 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match private void cancelCountdown() { + if (Client.Room == null) + return; + Debug.Assert(clickOperation == null); clickOperation = ongoingOperationTracker.BeginOperation(); - Client.SendMatchRequest(new StopCountdownRequest()).ContinueWith(_ => endOperation()); + MultiplayerCountdown countdown = Client.Room.ActiveCountdowns.Single(c => c is MatchStartCountdown); + Client.SendMatchRequest(new StopCountdownRequest(countdown.ID)).ContinueWith(_ => endOperation()); } private void endOperation() @@ -192,7 +196,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match // When the local user is the host and spectating the match, the ready button should be enabled only if any users are ready. if (localUser?.State == MultiplayerUserState.Spectating) - readyButton.Enabled.Value &= Client.IsHost && newCountReady > 0 && Room.Countdown == null; + readyButton.Enabled.Value &= Client.IsHost && newCountReady > 0 && !Room.ActiveCountdowns.Any(c => c is MatchStartCountdown); if (newCountReady == countReady) return; diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerCountdownButton.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerCountdownButton.cs index 14bf1a8375..cd94b47d9e 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerCountdownButton.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerCountdownButton.cs @@ -4,6 +4,7 @@ #nullable disable using System; +using System.Linq; using Humanizer; using osu.Framework.Allocation; using osu.Framework.Extensions; @@ -79,7 +80,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match private void onRoomUpdated() => Scheduler.AddOnce(() => { - bool countdownActive = multiplayerClient.Room?.Countdown is MatchStartCountdown; + bool countdownActive = multiplayerClient.Room?.ActiveCountdowns.Any(c => c is MatchStartCountdown) == true; if (countdownActive) { @@ -121,7 +122,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match }); } - if (multiplayerClient.Room?.Countdown != null && multiplayerClient.IsHost) + if (multiplayerClient.Room?.ActiveCountdowns.Any(c => c is MatchStartCountdown) == true && multiplayerClient.IsHost) { flow.Add(new OsuButton { diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs index c5d6da1ebc..b4ff34cbc2 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs @@ -57,23 +57,11 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match private void onRoomUpdated() => Scheduler.AddOnce(() => { - MultiplayerCountdown newCountdown; - - switch (room?.Countdown) - { - case MatchStartCountdown: - newCountdown = room.Countdown; - break; - - // Clear the countdown with any other (including non-null) countdown values. - default: - newCountdown = null; - break; - } + MultiplayerCountdown newCountdown = room?.ActiveCountdowns.SingleOrDefault(c => c is MatchStartCountdown); if (newCountdown != countdown) { - countdown = room?.Countdown; + countdown = newCountdown; countdownChangeTime = Time.Current; } @@ -213,7 +201,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match case MultiplayerUserState.Spectating: case MultiplayerUserState.Ready: - if (room?.Host?.Equals(localUser) == true && room.Countdown == null) + if (room?.Host?.Equals(localUser) == true && !room.ActiveCountdowns.Any(c => c is MatchStartCountdown)) setGreen(); else setYellow(); @@ -248,8 +236,13 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match { get { - if (room?.Countdown != null && multiplayerClient.IsHost && multiplayerClient.LocalUser?.State == MultiplayerUserState.Ready && !room.Settings.AutoStartEnabled) + if (room?.ActiveCountdowns.Any(c => c is MatchStartCountdown) == true + && multiplayerClient.IsHost + && multiplayerClient.LocalUser?.State == MultiplayerUserState.Ready + && !room.Settings.AutoStartEnabled) + { return "Cancel countdown"; + } return base.TooltipText; } From f754686521f4262c7194e2503857b351b796fb64 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 5 Sep 2022 18:57:33 +0900 Subject: [PATCH 279/420] Remove necessity of `AlwaysPresent` for `ProgressUpdate` completion posting --- osu.Game/Overlays/NotificationOverlay.cs | 7 +++---- osu.Game/Overlays/NotificationOverlayToastTray.cs | 2 +- .../Overlays/Notifications/ProgressNotification.cs | 13 ++++++------- 3 files changed, 10 insertions(+), 12 deletions(-) diff --git a/osu.Game/Overlays/NotificationOverlay.cs b/osu.Game/Overlays/NotificationOverlay.cs index fca43e65a5..b170ea5dfa 100644 --- a/osu.Game/Overlays/NotificationOverlay.cs +++ b/osu.Game/Overlays/NotificationOverlay.cs @@ -71,7 +71,6 @@ namespace osu.Game.Overlays }, mainContent = new Container { - AlwaysPresent = true, RelativeSizeAxes = Axes.Both, Children = new Drawable[] { @@ -137,9 +136,9 @@ namespace osu.Game.Overlays private readonly Scheduler postScheduler = new Scheduler(); - public override bool IsPresent => base.IsPresent - || postScheduler.HasPendingTasks - || toastTray.IsDisplayingToasts; + public override bool IsPresent => + // Delegate presence as we need to consider the toast tray in addition to the main overlay. + State.Value == Visibility.Visible || mainContent.IsPresent || toastTray.IsPresent || postScheduler.HasPendingTasks; private bool processingPosts = true; diff --git a/osu.Game/Overlays/NotificationOverlayToastTray.cs b/osu.Game/Overlays/NotificationOverlayToastTray.cs index aa61e20a02..ebf8fb32a9 100644 --- a/osu.Game/Overlays/NotificationOverlayToastTray.cs +++ b/osu.Game/Overlays/NotificationOverlayToastTray.cs @@ -70,7 +70,7 @@ namespace osu.Game.Overlays postEffectDrawable.AutoSizeAxes = Axes.None; postEffectDrawable.RelativeSizeAxes = Axes.X; })), - toastFlow = new AlwaysUpdateFillFlowContainer + toastFlow = new FillFlowContainer { LayoutDuration = 150, LayoutEasing = Easing.OutQuart, diff --git a/osu.Game/Overlays/Notifications/ProgressNotification.cs b/osu.Game/Overlays/Notifications/ProgressNotification.cs index 6821c25ac0..b5af3a56a1 100644 --- a/osu.Game/Overlays/Notifications/ProgressNotification.cs +++ b/osu.Game/Overlays/Notifications/ProgressNotification.cs @@ -88,7 +88,12 @@ namespace osu.Game.Overlays.Notifications state = value; if (IsLoaded) + { Schedule(updateState); + + if (state == ProgressNotificationState.Completed) + CompletionTarget?.Invoke(CreateCompletionNotification()); + } } } @@ -141,7 +146,7 @@ namespace osu.Game.Overlays.Notifications case ProgressNotificationState.Completed: loadingSpinner.Hide(); - Completed(); + base.Close(); break; } } @@ -154,12 +159,6 @@ namespace osu.Game.Overlays.Notifications Text = CompletionText }; - protected void Completed() - { - CompletionTarget?.Invoke(CreateCompletionNotification()); - base.Close(); - } - public override bool DisplayOnTop => false; public override bool IsImportant => false; From 229e1a8ef7a97639dd328c90ca96afaf6f5ee424 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 5 Sep 2022 18:42:37 +0900 Subject: [PATCH 280/420] Fix notification overlay being present when it doesn't need to --- .../UserInterface/TestSceneNotificationOverlay.cs | 14 ++++++++++++++ osu.Game/Overlays/NotificationOverlayToastTray.cs | 2 ++ 2 files changed, 16 insertions(+) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs index 288f1f78bc..8c7ad5f061 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs @@ -7,6 +7,7 @@ using NUnit.Framework; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; +using osu.Framework.Testing; using osu.Framework.Utils; using osu.Game.Graphics.Sprites; using osu.Game.Overlays; @@ -44,6 +45,18 @@ namespace osu.Game.Tests.Visual.UserInterface notificationOverlay.UnreadCount.ValueChanged += count => { displayedCount.Text = $"displayed count: {count.NewValue}"; }; }); + [Test] + public void TestPresence() + { + AddAssert("tray not present", () => !notificationOverlay.ChildrenOfType().Single().IsPresent); + AddAssert("overlay not present", () => !notificationOverlay.IsPresent); + + AddStep(@"post notification", sendBackgroundNotification); + + AddUntilStep("wait tray not present", () => !notificationOverlay.ChildrenOfType().Single().IsPresent); + AddUntilStep("wait overlay not present", () => !notificationOverlay.IsPresent); + } + [Test] public void TestCompleteProgress() { @@ -63,6 +76,7 @@ namespace osu.Game.Tests.Visual.UserInterface AddUntilStep("wait completion", () => notification.State == ProgressNotificationState.Completed); AddAssert("Completion toast shown", () => notificationOverlay.ToastCount == 1); + AddUntilStep("wait forwarded", () => notificationOverlay.ToastCount == 0); } [Test] diff --git a/osu.Game/Overlays/NotificationOverlayToastTray.cs b/osu.Game/Overlays/NotificationOverlayToastTray.cs index ebf8fb32a9..df9bbac887 100644 --- a/osu.Game/Overlays/NotificationOverlayToastTray.cs +++ b/osu.Game/Overlays/NotificationOverlayToastTray.cs @@ -24,6 +24,8 @@ namespace osu.Game.Overlays /// public class NotificationOverlayToastTray : CompositeDrawable { + public override bool IsPresent => IsDisplayingToasts; + public bool IsDisplayingToasts => displayedCount > 0; private FillFlowContainer toastFlow = null!; From 0514c961915a250c22a32bf3d466ee5d8239b072 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 5 Sep 2022 19:20:01 +0900 Subject: [PATCH 281/420] Fix incorrect count tracking when notification is manually disposed --- .../UserInterface/TestSceneNotificationOverlay.cs | 13 +++++++++++++ osu.Game/Overlays/NotificationOverlayToastTray.cs | 13 ++++++++++++- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs index 8c7ad5f061..699b8f7d89 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs @@ -57,6 +57,19 @@ namespace osu.Game.Tests.Visual.UserInterface AddUntilStep("wait overlay not present", () => !notificationOverlay.IsPresent); } + [Test] + public void TestPresenceWithManualDismiss() + { + AddAssert("tray not present", () => !notificationOverlay.ChildrenOfType().Single().IsPresent); + AddAssert("overlay not present", () => !notificationOverlay.IsPresent); + + AddStep(@"post notification", sendBackgroundNotification); + AddStep("click notification", () => notificationOverlay.ChildrenOfType().Single().TriggerClick()); + + AddUntilStep("wait tray not present", () => !notificationOverlay.ChildrenOfType().Single().IsPresent); + AddUntilStep("wait overlay not present", () => !notificationOverlay.IsPresent); + } + [Test] public void TestCompleteProgress() { diff --git a/osu.Game/Overlays/NotificationOverlayToastTray.cs b/osu.Game/Overlays/NotificationOverlayToastTray.cs index df9bbac887..4f98b9f40c 100644 --- a/osu.Game/Overlays/NotificationOverlayToastTray.cs +++ b/osu.Game/Overlays/NotificationOverlayToastTray.cs @@ -97,6 +97,8 @@ namespace osu.Game.Overlays public void Post(Notification notification) { + notification.Closed += stopTrackingNotification; + ++runningDepth; displayedCount++; @@ -139,14 +141,23 @@ namespace osu.Game.Overlays notification.MoveToOffset(new Vector2(400, 0), NotificationOverlay.TRANSITION_LENGTH, Easing.OutQuint); notification.FadeOut(NotificationOverlay.TRANSITION_LENGTH, Easing.OutQuint).OnComplete(_ => { + notification.Closed -= stopTrackingNotification; + if (!notification.WasClosed) + stopTrackingNotification(); + RemoveInternal(notification, false); - displayedCount--; ForwardNotificationToPermanentStore?.Invoke(notification); notification.FadeIn(300, Easing.OutQuint); }); } + private void stopTrackingNotification() + { + Debug.Assert(displayedCount > 0); + displayedCount--; + } + protected override void Update() { base.Update(); From 510972e3add24d7aebe619d68a56017a70fcbf67 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 5 Sep 2022 19:35:15 +0900 Subject: [PATCH 282/420] Avoid reference counting by using height calculation for `IsPresent` instead The reference counting was to guarantee performance (zero allocations) when the notification overlay was not required, but adds extra complexity. Instead, the toast tray now uses its ongoing height calculation as a metric for presence. --- .../Overlays/NotificationOverlayToastTray.cs | 22 ++++--------------- 1 file changed, 4 insertions(+), 18 deletions(-) diff --git a/osu.Game/Overlays/NotificationOverlayToastTray.cs b/osu.Game/Overlays/NotificationOverlayToastTray.cs index 4f98b9f40c..f9c4f657eb 100644 --- a/osu.Game/Overlays/NotificationOverlayToastTray.cs +++ b/osu.Game/Overlays/NotificationOverlayToastTray.cs @@ -24,9 +24,9 @@ namespace osu.Game.Overlays /// public class NotificationOverlayToastTray : CompositeDrawable { - public override bool IsPresent => IsDisplayingToasts; + public override bool IsPresent => toastContentBackground.Height > 0 || toastFlow.Count > 0; - public bool IsDisplayingToasts => displayedCount > 0; + public bool IsDisplayingToasts => allNotifications.Any(); private FillFlowContainer toastFlow = null!; private BufferedContainer toastContentBackground = null!; @@ -42,8 +42,6 @@ namespace osu.Game.Overlays private int runningDepth; - private int displayedCount; - [BackgroundDependencyLoader] private void load() { @@ -61,6 +59,7 @@ namespace osu.Game.Overlays colourProvider.Background6.Opacity(0.7f), colourProvider.Background6.Opacity(0.5f)), RelativeSizeAxes = Axes.Both, + Height = 0, }.WithEffect(new BlurEffect { PadExtent = true, @@ -97,10 +96,7 @@ namespace osu.Game.Overlays public void Post(Notification notification) { - notification.Closed += stopTrackingNotification; - ++runningDepth; - displayedCount++; int depth = notification.DisplayOnTop ? -runningDepth : runningDepth; @@ -141,10 +137,6 @@ namespace osu.Game.Overlays notification.MoveToOffset(new Vector2(400, 0), NotificationOverlay.TRANSITION_LENGTH, Easing.OutQuint); notification.FadeOut(NotificationOverlay.TRANSITION_LENGTH, Easing.OutQuint).OnComplete(_ => { - notification.Closed -= stopTrackingNotification; - if (!notification.WasClosed) - stopTrackingNotification(); - RemoveInternal(notification, false); ForwardNotificationToPermanentStore?.Invoke(notification); @@ -152,17 +144,11 @@ namespace osu.Game.Overlays }); } - private void stopTrackingNotification() - { - Debug.Assert(displayedCount > 0); - displayedCount--; - } - protected override void Update() { base.Update(); - float height = toastFlow.DrawHeight + 120; + float height = toastFlow.Count > 0 ? toastFlow.DrawHeight + 120 : 0; float alpha = toastFlow.Count > 0 ? MathHelper.Clamp(toastFlow.DrawHeight / 41, 0, 1) * toastFlow.Children.Max(n => n.Alpha) : 0; toastContentBackground.Height = (float)Interpolation.DampContinuously(toastContentBackground.Height, height, 10, Clock.ElapsedFrameTime); From b2f30fbf8cb7e76d02d8cb957d6a2eb1328f2b1e Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 5 Sep 2022 20:13:23 +0900 Subject: [PATCH 283/420] Add countdown exclusivity --- osu.Game/Online/Multiplayer/ForceGameplayStartCountdown.cs | 3 ++- osu.Game/Online/Multiplayer/MatchStartCountdown.cs | 3 ++- osu.Game/Online/Multiplayer/MultiplayerCountdown.cs | 5 +++++ 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/osu.Game/Online/Multiplayer/ForceGameplayStartCountdown.cs b/osu.Game/Online/Multiplayer/ForceGameplayStartCountdown.cs index 7f5c0f0a05..81ba56f35c 100644 --- a/osu.Game/Online/Multiplayer/ForceGameplayStartCountdown.cs +++ b/osu.Game/Online/Multiplayer/ForceGameplayStartCountdown.cs @@ -13,7 +13,8 @@ namespace osu.Game.Online.Multiplayer /// and forcing progression of any clients that are blocking load due to user interaction. /// [MessagePackObject] - public class ForceGameplayStartCountdown : MultiplayerCountdown + public sealed class ForceGameplayStartCountdown : MultiplayerCountdown { + public override bool IsExclusive => true; } } diff --git a/osu.Game/Online/Multiplayer/MatchStartCountdown.cs b/osu.Game/Online/Multiplayer/MatchStartCountdown.cs index 5d3365c947..b4c66e6f5b 100644 --- a/osu.Game/Online/Multiplayer/MatchStartCountdown.cs +++ b/osu.Game/Online/Multiplayer/MatchStartCountdown.cs @@ -9,7 +9,8 @@ namespace osu.Game.Online.Multiplayer /// A which will start the match after ending. /// [MessagePackObject] - public class MatchStartCountdown : MultiplayerCountdown + public sealed class MatchStartCountdown : MultiplayerCountdown { + public override bool IsExclusive => true; } } diff --git a/osu.Game/Online/Multiplayer/MultiplayerCountdown.cs b/osu.Game/Online/Multiplayer/MultiplayerCountdown.cs index 00bfa8919a..e8e2365f7b 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerCountdown.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerCountdown.cs @@ -29,5 +29,10 @@ namespace osu.Game.Online.Multiplayer /// [Key(1)] public TimeSpan TimeRemaining { get; set; } + + /// + /// Whether only a single instance of this type may be active at any one time. + /// + public virtual bool IsExclusive => false; } } From e33486a766044c17c2f254f5e8df6d72b29c341e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 5 Sep 2022 23:20:02 +0900 Subject: [PATCH 284/420] Implement `IAdjustableAudioComponent` in `MasterGameplayClockContainer` --- .../Play/MasterGameplayClockContainer.cs | 35 +++++++++++++++---- 1 file changed, 28 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs index 2f1ffa126f..57e67e6e26 100644 --- a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs +++ b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs @@ -24,7 +24,7 @@ namespace osu.Game.Screens.Play /// /// This is intended to be used as a single controller for gameplay, or as a reference source for other s. /// - public class MasterGameplayClockContainer : GameplayClockContainer, IBeatSyncProvider + public class MasterGameplayClockContainer : GameplayClockContainer, IBeatSyncProvider, IAdjustableAudioComponent { /// /// Duration before gameplay start time required before skip button displays. @@ -41,6 +41,8 @@ namespace osu.Game.Screens.Play private readonly WorkingBeatmap beatmap; + private readonly Track track; + private readonly double skipTargetTime; private readonly List> nonGameplayAdjustments = new List>(); @@ -66,6 +68,7 @@ namespace osu.Game.Screens.Play public MasterGameplayClockContainer(WorkingBeatmap beatmap, double skipTargetTime) : base(beatmap.Track, true) { + track = beatmap.Track; this.beatmap = beatmap; this.skipTargetTime = skipTargetTime; @@ -195,9 +198,6 @@ namespace osu.Game.Screens.Play if (speedAdjustmentsApplied) return; - if (SourceClock is not Track track) - return; - track.AddAdjustment(AdjustableProperty.Frequency, GameplayClock.ExternalPauseFrequencyAdjust); track.AddAdjustment(AdjustableProperty.Tempo, UserPlaybackRate); @@ -212,9 +212,6 @@ namespace osu.Game.Screens.Play if (!speedAdjustmentsApplied) return; - if (SourceClock is not Track track) - return; - track.RemoveAdjustment(AdjustableProperty.Frequency, GameplayClock.ExternalPauseFrequencyAdjust); track.RemoveAdjustment(AdjustableProperty.Tempo, UserPlaybackRate); @@ -234,5 +231,29 @@ namespace osu.Game.Screens.Play IClock IBeatSyncProvider.Clock => this; ChannelAmplitudes IHasAmplitudes.CurrentAmplitudes => beatmap.TrackLoaded ? beatmap.Track.CurrentAmplitudes : ChannelAmplitudes.Empty; + + void IAdjustableAudioComponent.AddAdjustment(AdjustableProperty type, IBindable adjustBindable) => + track.AddAdjustment(type, adjustBindable); + + void IAdjustableAudioComponent.RemoveAdjustment(AdjustableProperty type, IBindable adjustBindable) => + track.RemoveAdjustment(type, adjustBindable); + + public void RemoveAllAdjustments(AdjustableProperty type) + { + throw new NotImplementedException(); + } + + public void BindAdjustments(IAggregateAudioAdjustment component) => throw new NotImplementedException(); + public void UnbindAdjustments(IAggregateAudioAdjustment component) => 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 IBindable AggregateVolume => throw new NotImplementedException(); + public IBindable AggregateBalance => throw new NotImplementedException(); + public IBindable AggregateFrequency => throw new NotImplementedException(); + public IBindable AggregateTempo => throw new NotImplementedException(); } } From 7084aeee0523af04c380ef1ee2cee143d18f2fcc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 5 Sep 2022 23:22:38 +0900 Subject: [PATCH 285/420] Add method flow to reset applied adjustments --- osu.Game/Screens/Play/GameplayClockContainer.cs | 4 +++- osu.Game/Screens/Play/MasterGameplayClockContainer.cs | 7 +++++-- osu.Game/Screens/Play/Player.cs | 9 ++++++--- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index 6de88d7ad0..490c2329ca 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -196,7 +196,9 @@ namespace osu.Game.Screens.Play void IAdjustableClock.Reset() => Reset(); - public void ResetSpeedAdjustments() => throw new NotImplementedException(); + public virtual void ResetSpeedAdjustments() + { + } double IAdjustableClock.Rate { diff --git a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs index 57e67e6e26..15e9a40fd3 100644 --- a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs +++ b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs @@ -238,11 +238,14 @@ namespace osu.Game.Screens.Play void IAdjustableAudioComponent.RemoveAdjustment(AdjustableProperty type, IBindable adjustBindable) => track.RemoveAdjustment(type, adjustBindable); - public void RemoveAllAdjustments(AdjustableProperty type) + public override void ResetSpeedAdjustments() { - throw new NotImplementedException(); + track.RemoveAllAdjustments(AdjustableProperty.Frequency); + track.RemoveAllAdjustments(AdjustableProperty.Tempo); } + public void RemoveAllAdjustments(AdjustableProperty type) => throw new NotImplementedException(); + public void BindAdjustments(IAggregateAudioAdjustment component) => throw new NotImplementedException(); public void UnbindAdjustments(IAggregateAudioAdjustment component) => throw new NotImplementedException(); diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 4afd04c335..21a02fbe0b 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -999,9 +999,12 @@ namespace osu.Game.Screens.Play // 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 GameplayState.Mods.OfType()) - mod.ApplyToTrack(musicController.CurrentTrack); + if (GameplayClockContainer is IAdjustableAudioComponent adjustableClock) + { + GameplayClockContainer.ResetSpeedAdjustments(); + foreach (var mod in GameplayState.Mods.OfType()) + mod.ApplyToTrack(adjustableClock); + } updateGameplayState(); From 266eb758aa2471d1cdb54253ab564e02a27764a3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 5 Sep 2022 23:37:41 +0900 Subject: [PATCH 286/420] Use new flow to calcaulate `TrueGameplayRate` --- .../Screens/Play/GameplayClockContainer.cs | 19 +--------- .../Play/MasterGameplayClockContainer.cs | 35 ++++++++++++------- 2 files changed, 23 insertions(+), 31 deletions(-) diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index 490c2329ca..4d48675f94 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -10,7 +10,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Logging; using osu.Framework.Timing; -using osu.Framework.Utils; using osu.Game.Beatmaps; namespace osu.Game.Screens.Play @@ -225,22 +224,6 @@ namespace osu.Game.Screens.Play public FrameTimeInfo TimeInfo => GameplayClock.TimeInfo; - public double TrueGameplayRate - { - get - { - double baseRate = Rate; - - foreach (double adjustment in NonGameplayAdjustments) - { - if (Precision.AlmostEquals(adjustment, 0)) - return 0; - - baseRate /= adjustment; - } - - return baseRate; - } - } + public virtual double TrueGameplayRate => Rate; } } diff --git a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs index 15e9a40fd3..bda8718dc6 100644 --- a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs +++ b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs @@ -45,8 +45,6 @@ namespace osu.Game.Screens.Play private readonly double skipTargetTime; - private readonly List> nonGameplayAdjustments = new List>(); - /// /// Stores the time at which the last call was triggered. /// This is used to ensure we resume from that precise point in time, ignoring the proceeding frequency ramp. @@ -58,8 +56,6 @@ namespace osu.Game.Screens.Play /// private double? actualStopTime; - public override IEnumerable NonGameplayAdjustments => nonGameplayAdjustments.Select(b => b.Value); - /// /// Create a new master gameplay clock container. /// @@ -201,9 +197,6 @@ namespace osu.Game.Screens.Play track.AddAdjustment(AdjustableProperty.Frequency, GameplayClock.ExternalPauseFrequencyAdjust); track.AddAdjustment(AdjustableProperty.Tempo, UserPlaybackRate); - nonGameplayAdjustments.Add(GameplayClock.ExternalPauseFrequencyAdjust); - nonGameplayAdjustments.Add(UserPlaybackRate); - speedAdjustmentsApplied = true; } @@ -215,9 +208,6 @@ namespace osu.Game.Screens.Play track.RemoveAdjustment(AdjustableProperty.Frequency, GameplayClock.ExternalPauseFrequencyAdjust); track.RemoveAdjustment(AdjustableProperty.Tempo, UserPlaybackRate); - nonGameplayAdjustments.Remove(GameplayClock.ExternalPauseFrequencyAdjust); - nonGameplayAdjustments.Remove(UserPlaybackRate); - speedAdjustmentsApplied = false; } @@ -232,11 +222,30 @@ namespace osu.Game.Screens.Play ChannelAmplitudes IHasAmplitudes.CurrentAmplitudes => beatmap.TrackLoaded ? beatmap.Track.CurrentAmplitudes : ChannelAmplitudes.Empty; - void IAdjustableAudioComponent.AddAdjustment(AdjustableProperty type, IBindable adjustBindable) => - track.AddAdjustment(type, adjustBindable); + private readonly List> speedAdjustments = new List>(); - void IAdjustableAudioComponent.RemoveAdjustment(AdjustableProperty type, IBindable adjustBindable) => + public override double TrueGameplayRate + { + get + { + double rate = Rate; + foreach (var a in speedAdjustments) + rate *= a.Value; + return rate; + } + } + + void IAdjustableAudioComponent.AddAdjustment(AdjustableProperty type, IBindable adjustBindable) + { + speedAdjustments.Add(adjustBindable); + track.AddAdjustment(type, adjustBindable); + } + + void IAdjustableAudioComponent.RemoveAdjustment(AdjustableProperty type, IBindable adjustBindable) + { + speedAdjustments.Remove(adjustBindable); track.RemoveAdjustment(type, adjustBindable); + } public override void ResetSpeedAdjustments() { From 44b456e216741a814bc61018aa08c2642465ea74 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 5 Sep 2022 23:38:22 +0900 Subject: [PATCH 287/420] Use gameplay clock's `TrueGameplayRate` in `FrameStabilityContainer`? --- .../Rulesets/UI/FrameStabilityContainer.cs | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) diff --git a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs index 18d0ff0bed..4d817cd973 100644 --- a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs +++ b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs @@ -263,23 +263,7 @@ namespace osu.Game.Rulesets.UI public FrameTimeInfo TimeInfo => framedClock.TimeInfo; - public double TrueGameplayRate - { - get - { - double baseRate = Rate; - - foreach (double adjustment in NonGameplayAdjustments) - { - if (Precision.AlmostEquals(adjustment, 0)) - return 0; - - baseRate /= adjustment; - } - - return baseRate; - } - } + public double TrueGameplayRate => parentGameplayClock?.TrueGameplayRate ?? Rate; public double StartTime => parentGameplayClock?.StartTime ?? 0; From b109e5de6ce537916252487ccd183ef870012afb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Acid=20Chicken=20=28=E7=A1=AB=E9=85=B8=E9=B6=8F=29?= Date: Tue, 6 Sep 2022 00:04:10 +0900 Subject: [PATCH 288/420] chore(osu.Game): align height of bars on timing distribution graph to `basalHeight` first and combine their transitions into each one --- .../Statistics/HitEventTimingDistributionGraph.cs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs index 1be32a94af..5fbc07921a 100644 --- a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs +++ b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs @@ -278,9 +278,7 @@ namespace osu.Game.Screens.Ranking.Statistics } } - private const double total_duration = 300; - - private double duration => total_duration / Math.Max(values.Count, 1); + private const double duration = 300; private float offsetForValue(float value) { @@ -296,19 +294,20 @@ namespace osu.Game.Screens.Ranking.Statistics { base.LoadComplete(); + foreach (var boxOriginal in boxOriginals) + boxOriginal.Height = basalHeight; + float offsetValue = 0; if (values.Any()) { for (int i = 0; i < values.Count; i++) { - boxOriginals[i].Y = BoundingBox.Height * offsetForValue(offsetValue); - boxOriginals[i].Delay(duration * i).ResizeHeightTo(heightForValue(values[i].Value), duration, Easing.OutQuint); + boxOriginals[i].MoveToY(offsetForValue(offsetValue) * BoundingBox.Height, duration, Easing.OutQuint); + boxOriginals[i].ResizeHeightTo(heightForValue(values[i].Value), duration, Easing.OutQuint); offsetValue -= values[i].Value; } } - else - boxOriginals.Single().ResizeHeightTo(basalHeight, duration, Easing.OutQuint); } public void UpdateOffset(float adjustment) From 9e3228aa651181b5a531ccf8fc6b5da15962d7ef Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 6 Sep 2022 04:07:49 +0900 Subject: [PATCH 289/420] Fix completion notification not being posted if completion occurs during `NotificationOverlay` load --- .../Navigation/TestSceneStartupImport.cs | 4 +-- .../ProgressCompletionNotification.cs | 2 -- .../Notifications/ProgressNotification.cs | 33 ++++++++++++++----- 3 files changed, 26 insertions(+), 13 deletions(-) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneStartupImport.cs b/osu.Game.Tests/Visual/Navigation/TestSceneStartupImport.cs index cd53bf3af5..552eb82419 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneStartupImport.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneStartupImport.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. -#nullable disable - using System.Linq; using NUnit.Framework; using osu.Framework.Testing; @@ -13,7 +11,7 @@ namespace osu.Game.Tests.Visual.Navigation { public class TestSceneStartupImport : OsuGameTestScene { - private string importFilename; + private string? importFilename; protected override TestOsuGame CreateTestGame() => new TestOsuGame(LocalStorage, API, new[] { importFilename }); diff --git a/osu.Game/Overlays/Notifications/ProgressCompletionNotification.cs b/osu.Game/Overlays/Notifications/ProgressCompletionNotification.cs index 49d558285c..3cbdf7edf7 100644 --- a/osu.Game/Overlays/Notifications/ProgressCompletionNotification.cs +++ b/osu.Game/Overlays/Notifications/ProgressCompletionNotification.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. -#nullable disable - using osu.Framework.Allocation; using osu.Game.Graphics; using osu.Framework.Graphics.Colour; diff --git a/osu.Game/Overlays/Notifications/ProgressNotification.cs b/osu.Game/Overlays/Notifications/ProgressNotification.cs index b5af3a56a1..64ad69adf3 100644 --- a/osu.Game/Overlays/Notifications/ProgressNotification.cs +++ b/osu.Game/Overlays/Notifications/ProgressNotification.cs @@ -71,7 +71,7 @@ namespace osu.Game.Overlays.Notifications base.LoadComplete(); // we may have received changes before we were displayed. - updateState(); + Scheduler.AddOnce(updateState); } private readonly CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); @@ -87,13 +87,8 @@ namespace osu.Game.Overlays.Notifications state = value; - if (IsLoaded) - { - Schedule(updateState); - - if (state == ProgressNotificationState.Completed) - CompletionTarget?.Invoke(CreateCompletionNotification()); - } + Scheduler.AddOnce(updateState); + attemptPostCompletion(); } } @@ -146,11 +141,33 @@ namespace osu.Game.Overlays.Notifications case ProgressNotificationState.Completed: loadingSpinner.Hide(); + attemptPostCompletion(); base.Close(); break; } } + private bool completionSent; + + /// + /// Attempt to post a completion notification. + /// + private void attemptPostCompletion() + { + if (state != ProgressNotificationState.Completed) return; + + // This notification may not have been posted yet (and thus may not have a target to post the completion to). + // Completion posting will be re-attempted in a scheduled invocation. + if (CompletionTarget == null) + return; + + if (completionSent) + return; + + CompletionTarget.Invoke(CreateCompletionNotification()); + completionSent = true; + } + private ProgressNotificationState state; protected virtual Notification CreateCompletionNotification() => new ProgressCompletionNotification From abf02426862da854f93a257b9d2cd520f601ab4e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 6 Sep 2022 16:16:54 +0900 Subject: [PATCH 290/420] Add failing test for incorrect start time of storyboard elements --- .../Formats/LegacyStoryboardDecoderTest.cs | 17 +++++++++++++++++ .../loop-containing-earlier-non-zero-fade.osb | 8 ++++++++ 2 files changed, 25 insertions(+) create mode 100644 osu.Game.Tests/Resources/loop-containing-earlier-non-zero-fade.osb diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs index 57dded8ee4..8dcbdb8435 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs @@ -117,6 +117,23 @@ namespace osu.Game.Tests.Beatmaps.Formats } } + [Test] + public void TestEarliestStartTimeWithLoopAlphas() + { + var decoder = new LegacyStoryboardDecoder(); + + using (var resStream = TestResources.OpenResource("loop-containing-earlier-non-zero-fade.osb")) + using (var stream = new LineBufferedReader(resStream)) + { + var storyboard = decoder.Decode(stream); + + StoryboardLayer background = storyboard.Layers.Single(l => l.Depth == 3); + Assert.AreEqual(1, background.Elements.Count); + Assert.AreEqual(1000, background.Elements[0].StartTime); + Assert.AreEqual(1000, storyboard.EarliestEventTime); + } + } + [Test] public void TestDecodeVariableWithSuffix() { diff --git a/osu.Game.Tests/Resources/loop-containing-earlier-non-zero-fade.osb b/osu.Game.Tests/Resources/loop-containing-earlier-non-zero-fade.osb new file mode 100644 index 0000000000..5c80bc620c --- /dev/null +++ b/osu.Game.Tests/Resources/loop-containing-earlier-non-zero-fade.osb @@ -0,0 +1,8 @@ +osu file format v14 + +[Events] +//Storyboard Layer 0 (Background) +Sprite,Background,TopCentre,"img.jpg",320,240 + L,1000,1 + F,0,0,,1 // fade inside a loop with non-zero alpha and an earlier start time should be the true start time.. + F,0,2000,,0 // ..not a zero alpha fade with a later start time From a5e57b083c09a136a0248c07266b9838bd147bf7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 6 Sep 2022 15:48:54 +0900 Subject: [PATCH 291/420] Remove `EarliestDisplayTime`'s input to `CommandStartTime` --- osu.Game/Storyboards/CommandTimelineGroup.cs | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/osu.Game/Storyboards/CommandTimelineGroup.cs b/osu.Game/Storyboards/CommandTimelineGroup.cs index 6fc9f60177..de5da3118a 100644 --- a/osu.Game/Storyboards/CommandTimelineGroup.cs +++ b/osu.Game/Storyboards/CommandTimelineGroup.cs @@ -47,30 +47,11 @@ namespace osu.Game.Storyboards }; } - /// - /// Returns the earliest visible time. Will be null unless this group's first command has a start value of zero. - /// - public double? EarliestDisplayedTime - { - get - { - var first = Alpha.Commands.FirstOrDefault(); - - return first?.StartValue == 0 ? first.StartTime : null; - } - } - [JsonIgnore] public double CommandsStartTime { get { - // if the first alpha command starts at zero it should be given priority over anything else. - // this is due to it creating a state where the target is not present before that time, causing any other events to not be visible. - double? earliestDisplay = EarliestDisplayedTime; - if (earliestDisplay != null) - return earliestDisplay.Value; - double min = double.MaxValue; for (int i = 0; i < timelines.Length; i++) From bea42d286264e4d866dae8b9d58e0771b519f222 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 6 Sep 2022 16:09:16 +0900 Subject: [PATCH 292/420] Handle earliest-alpha-start-time logic in `StoryboardSprite` itself --- osu.Game/Storyboards/StoryboardSprite.cs | 50 ++++++++++++++++++------ 1 file changed, 39 insertions(+), 11 deletions(-) diff --git a/osu.Game/Storyboards/StoryboardSprite.cs b/osu.Game/Storyboards/StoryboardSprite.cs index a759482b5d..249be39b65 100644 --- a/osu.Game/Storyboards/StoryboardSprite.cs +++ b/osu.Game/Storyboards/StoryboardSprite.cs @@ -30,24 +30,52 @@ namespace osu.Game.Storyboards { get { - // check for presence affecting commands as an initial pass. - double earliestStartTime = TimelineGroup.EarliestDisplayedTime ?? double.MaxValue; + // To get the initial start time, we need to check whether the first alpha command to exist has an initial value of zero. + // A start value of zero on an alpha governs, above all else, the first valid display time of a sprite. + // + // To make this valid, we need to aggregate all alpha commands across all loops before checking for the first, time-wise, else we could run into a scenario where: + // L,1000,1 + // F,0,0,,1 # this NEEDS TO BE considered the StartTime... but has a StartValue greater than zero so won't in the initial pass. + // F,0,2000,,0 # ..this will be treated as earliest start due to a StartValue of zero. - foreach (var l in loops) + double zeroAlphaStartTimeOverride = double.MaxValue; + + var topLevelFirstAlphaCommand = TimelineGroup.Alpha.Commands.FirstOrDefault(); + if (topLevelFirstAlphaCommand?.StartValue == 0) + zeroAlphaStartTimeOverride = topLevelFirstAlphaCommand.StartTime; + + foreach (var loop in loops) { - if (l.EarliestDisplayedTime is double loopEarliestDisplayTime) - earliestStartTime = Math.Min(earliestStartTime, l.LoopStartTime + loopEarliestDisplayTime); + var loopFirstAlphaCommand = loop.Alpha.Commands.FirstOrDefault(); + + if (loopFirstAlphaCommand == null) + continue; + + double loopFirstAlphaStartTime = loopFirstAlphaCommand.StartTime + loop.LoopStartTime; + + if (loopFirstAlphaCommand.StartValue == 0) + { + // Found a loop containing a zero StartValue earlier than previous. + zeroAlphaStartTimeOverride = loopFirstAlphaStartTime; + } + else if (loopFirstAlphaStartTime < zeroAlphaStartTimeOverride) + { + // If a loop's first alpha command's StartValue is non-zero, we need to check whether the command's StartTime is less than any previously found zero alpha command. + // If this is the case, we want to abort zero alpha override logic and use the earliest command time instead. + // This is because if the first alpha command is non-zero, the sprite will be shown at that alpha from the time of the first command (of any type, not just alpha). + zeroAlphaStartTimeOverride = double.MaxValue; + break; + } } - if (earliestStartTime < double.MaxValue) - return earliestStartTime; - - // if an alpha-affecting command was not found, use the earliest of any command. - earliestStartTime = TimelineGroup.StartTime; + if (zeroAlphaStartTimeOverride < double.MaxValue) + return zeroAlphaStartTimeOverride; + // if we got to this point, either no alpha commands were present, or the earliest had a non-zero start value. + // the sprite's StartTime will be determined by the earliest command, of any type. + double earliestStartTime = TimelineGroup.StartTime; foreach (var l in loops) earliestStartTime = Math.Min(earliestStartTime, l.StartTime); - return earliestStartTime; } } From fa0a4614f8d90f18604349aef659ee521ad72ef0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 6 Sep 2022 16:40:59 +0900 Subject: [PATCH 293/420] Add failing test for second incorrect case of start time handling --- .../Beatmaps/Formats/LegacyStoryboardDecoderTest.cs | 5 ++++- .../Resources/loop-containing-earlier-non-zero-fade.osb | 6 ++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs index 8dcbdb8435..6e41043b0b 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs @@ -128,8 +128,11 @@ namespace osu.Game.Tests.Beatmaps.Formats var storyboard = decoder.Decode(stream); StoryboardLayer background = storyboard.Layers.Single(l => l.Depth == 3); - Assert.AreEqual(1, background.Elements.Count); + Assert.AreEqual(2, background.Elements.Count); + Assert.AreEqual(1000, background.Elements[0].StartTime); + Assert.AreEqual(1000, background.Elements[1].StartTime); + Assert.AreEqual(1000, storyboard.EarliestEventTime); } } diff --git a/osu.Game.Tests/Resources/loop-containing-earlier-non-zero-fade.osb b/osu.Game.Tests/Resources/loop-containing-earlier-non-zero-fade.osb index 5c80bc620c..2ff7f9f56f 100644 --- a/osu.Game.Tests/Resources/loop-containing-earlier-non-zero-fade.osb +++ b/osu.Game.Tests/Resources/loop-containing-earlier-non-zero-fade.osb @@ -6,3 +6,9 @@ Sprite,Background,TopCentre,"img.jpg",320,240 L,1000,1 F,0,0,,1 // fade inside a loop with non-zero alpha and an earlier start time should be the true start time.. F,0,2000,,0 // ..not a zero alpha fade with a later start time + +Sprite,Background,TopCentre,"img.jpg",320,240 + L,2000,1 + F,0,0,24,0 // fade inside a loop with zero alpha but later start time than the top-level zero alpha start time. + F,0,24,48,1 + F,0,1000,,1 // ..so this should be the true start time From d667f4683050c7e2bcebf6488f887c2ff421462a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 6 Sep 2022 16:41:32 +0900 Subject: [PATCH 294/420] Refactor alpha check to not overwrite sourced overrides with values from later commands --- osu.Game/Storyboards/StoryboardSprite.cs | 45 ++++++++++++++---------- 1 file changed, 26 insertions(+), 19 deletions(-) diff --git a/osu.Game/Storyboards/StoryboardSprite.cs b/osu.Game/Storyboards/StoryboardSprite.cs index 249be39b65..0764969d02 100644 --- a/osu.Game/Storyboards/StoryboardSprite.cs +++ b/osu.Game/Storyboards/StoryboardSprite.cs @@ -32,17 +32,18 @@ namespace osu.Game.Storyboards { // To get the initial start time, we need to check whether the first alpha command to exist has an initial value of zero. // A start value of zero on an alpha governs, above all else, the first valid display time of a sprite. - // - // To make this valid, we need to aggregate all alpha commands across all loops before checking for the first, time-wise, else we could run into a scenario where: - // L,1000,1 - // F,0,0,,1 # this NEEDS TO BE considered the StartTime... but has a StartValue greater than zero so won't in the initial pass. - // F,0,2000,,0 # ..this will be treated as earliest start due to a StartValue of zero. - double zeroAlphaStartTimeOverride = double.MaxValue; + double earliestAlphaTime = double.MaxValue; var topLevelFirstAlphaCommand = TimelineGroup.Alpha.Commands.FirstOrDefault(); - if (topLevelFirstAlphaCommand?.StartValue == 0) - zeroAlphaStartTimeOverride = topLevelFirstAlphaCommand.StartTime; + + if (topLevelFirstAlphaCommand != null) + { + earliestAlphaTime = topLevelFirstAlphaCommand.StartTime; + if (topLevelFirstAlphaCommand.StartValue == 0) + // The found alpha command has a zero StartValue, so should be used as the new override. + zeroAlphaStartTimeOverride = topLevelFirstAlphaCommand.StartTime; + } foreach (var loop in loops) { @@ -53,18 +54,24 @@ namespace osu.Game.Storyboards double loopFirstAlphaStartTime = loopFirstAlphaCommand.StartTime + loop.LoopStartTime; - if (loopFirstAlphaCommand.StartValue == 0) + // Found an alpha command with an earlier start time than an override. + if (loopFirstAlphaStartTime < earliestAlphaTime) { - // Found a loop containing a zero StartValue earlier than previous. - zeroAlphaStartTimeOverride = loopFirstAlphaStartTime; - } - else if (loopFirstAlphaStartTime < zeroAlphaStartTimeOverride) - { - // If a loop's first alpha command's StartValue is non-zero, we need to check whether the command's StartTime is less than any previously found zero alpha command. - // If this is the case, we want to abort zero alpha override logic and use the earliest command time instead. - // This is because if the first alpha command is non-zero, the sprite will be shown at that alpha from the time of the first command (of any type, not just alpha). - zeroAlphaStartTimeOverride = double.MaxValue; - break; + earliestAlphaTime = loopFirstAlphaStartTime; + + if (loopFirstAlphaCommand.StartValue == 0) + { + // The found alpha command has a zero StartValue, so should be used as the new override. + zeroAlphaStartTimeOverride = loopFirstAlphaStartTime; + } + else if (loopFirstAlphaStartTime < zeroAlphaStartTimeOverride) + { + // The found alpha command's StartValue is non-zero. + // In this case, we want to reset any found alpha override (so if this is the new earliest alpha, + // we will fall through to the earlier command time logic below). + zeroAlphaStartTimeOverride = double.MaxValue; + break; + } } } From 677708c5e4b0bad1b112ed6402cfa9be05d5ab73 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 6 Sep 2022 16:58:51 +0900 Subject: [PATCH 295/420] Rewrite logic using a list --- osu.Game/Storyboards/StoryboardSprite.cs | 62 ++++++++---------------- 1 file changed, 19 insertions(+), 43 deletions(-) diff --git a/osu.Game/Storyboards/StoryboardSprite.cs b/osu.Game/Storyboards/StoryboardSprite.cs index 0764969d02..f77b3a812e 100644 --- a/osu.Game/Storyboards/StoryboardSprite.cs +++ b/osu.Game/Storyboards/StoryboardSprite.cs @@ -30,56 +30,32 @@ namespace osu.Game.Storyboards { get { - // To get the initial start time, we need to check whether the first alpha command to exist has an initial value of zero. - // A start value of zero on an alpha governs, above all else, the first valid display time of a sprite. - double zeroAlphaStartTimeOverride = double.MaxValue; - double earliestAlphaTime = double.MaxValue; + // To get the initial start time, we need to check whether the first alpha command to exist (across all loops) has a StartValue of zero. + // A StartValue of zero governs, above all else, the first valid display time of a sprite. + // + // You can imagine that the first command of each type decides that type's start value, so if the initial alpha is zero, + // anything before that point can be ignored (the sprite is not visible after all). + var alphaCommands = new List<(double startTime, bool isZeroStartValue)>(); - var topLevelFirstAlphaCommand = TimelineGroup.Alpha.Commands.FirstOrDefault(); - - if (topLevelFirstAlphaCommand != null) - { - earliestAlphaTime = topLevelFirstAlphaCommand.StartTime; - if (topLevelFirstAlphaCommand.StartValue == 0) - // The found alpha command has a zero StartValue, so should be used as the new override. - zeroAlphaStartTimeOverride = topLevelFirstAlphaCommand.StartTime; - } + var command = TimelineGroup.Alpha.Commands.FirstOrDefault(); + if (command != null) alphaCommands.Add((command.StartTime, command.StartValue == 0)); foreach (var loop in loops) { - var loopFirstAlphaCommand = loop.Alpha.Commands.FirstOrDefault(); - - if (loopFirstAlphaCommand == null) - continue; - - double loopFirstAlphaStartTime = loopFirstAlphaCommand.StartTime + loop.LoopStartTime; - - // Found an alpha command with an earlier start time than an override. - if (loopFirstAlphaStartTime < earliestAlphaTime) - { - earliestAlphaTime = loopFirstAlphaStartTime; - - if (loopFirstAlphaCommand.StartValue == 0) - { - // The found alpha command has a zero StartValue, so should be used as the new override. - zeroAlphaStartTimeOverride = loopFirstAlphaStartTime; - } - else if (loopFirstAlphaStartTime < zeroAlphaStartTimeOverride) - { - // The found alpha command's StartValue is non-zero. - // In this case, we want to reset any found alpha override (so if this is the new earliest alpha, - // we will fall through to the earlier command time logic below). - zeroAlphaStartTimeOverride = double.MaxValue; - break; - } - } + command = loop.Alpha.Commands.FirstOrDefault(); + if (command != null) alphaCommands.Add((command.StartTime + loop.StartTime, command.StartValue == 0)); } - if (zeroAlphaStartTimeOverride < double.MaxValue) - return zeroAlphaStartTimeOverride; + if (alphaCommands.Count > 0) + { + var firstAlpha = alphaCommands.OrderBy(t => t.startTime).First(); - // if we got to this point, either no alpha commands were present, or the earliest had a non-zero start value. - // the sprite's StartTime will be determined by the earliest command, of any type. + if (firstAlpha.isZeroStartValue) + return firstAlpha.startTime; + } + + // If we got to this point, either no alpha commands were present, or the earliest had a non-zero start value. + // The sprite's StartTime will be determined by the earliest command, regardless of type. double earliestStartTime = TimelineGroup.StartTime; foreach (var l in loops) earliestStartTime = Math.Min(earliestStartTime, l.StartTime); From 6e52dbb2668768eb8654251a8a4bd3d13190443a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 6 Sep 2022 17:30:24 +0900 Subject: [PATCH 296/420] Update `IsDisplayingToasts` to check the flow count directly --- osu.Game/Overlays/NotificationOverlayToastTray.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/NotificationOverlayToastTray.cs b/osu.Game/Overlays/NotificationOverlayToastTray.cs index f9c4f657eb..40324963fc 100644 --- a/osu.Game/Overlays/NotificationOverlayToastTray.cs +++ b/osu.Game/Overlays/NotificationOverlayToastTray.cs @@ -26,7 +26,7 @@ namespace osu.Game.Overlays { public override bool IsPresent => toastContentBackground.Height > 0 || toastFlow.Count > 0; - public bool IsDisplayingToasts => allNotifications.Any(); + public bool IsDisplayingToasts => toastFlow.Count > 0; private FillFlowContainer toastFlow = null!; private BufferedContainer toastContentBackground = null!; @@ -36,9 +36,12 @@ namespace osu.Game.Overlays public Action? ForwardNotificationToPermanentStore { get; set; } - public int UnreadCount => allNotifications.Count(n => !n.WasClosed && !n.Read); + public int UnreadCount => allDisplayedNotifications.Count(n => !n.WasClosed && !n.Read); - private IEnumerable allNotifications => toastFlow.Concat(InternalChildren.OfType()); + /// + /// Notifications contained in the toast flow, or in a detached state while they animate during forwarding to the main overlay. + /// + private IEnumerable allDisplayedNotifications => toastFlow.Concat(InternalChildren.OfType()); private int runningDepth; From 9f2ea54e40c993a9b5091228f455a88bbb33d4fd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 6 Sep 2022 17:45:53 +0900 Subject: [PATCH 297/420] Tidy up `TestSceneLeadIn` constant for loop offset to read better --- osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs index c066f1417c..c18a78fe3c 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs @@ -66,18 +66,20 @@ namespace osu.Game.Tests.Visual.Gameplay [TestCase(-10000, -10000, true)] public void TestStoryboardProducesCorrectStartTimeFadeInAfterOtherEvents(double firstStoryboardEvent, double expectedStartTime, bool addEventToLoop) { + const double loop_start_time = -20000; + var storyboard = new Storyboard(); var sprite = new StoryboardSprite("unknown", Anchor.TopLeft, Vector2.Zero); // these should be ignored as we have an alpha visibility blocker proceeding this command. - sprite.TimelineGroup.Scale.Add(Easing.None, -20000, -18000, 0, 1); - var loopGroup = sprite.AddLoop(-20000, 50); - loopGroup.Scale.Add(Easing.None, -20000, -18000, 0, 1); + sprite.TimelineGroup.Scale.Add(Easing.None, loop_start_time, -18000, 0, 1); + var loopGroup = sprite.AddLoop(loop_start_time, 50); + loopGroup.Scale.Add(Easing.None, loop_start_time, -18000, 0, 1); var target = addEventToLoop ? loopGroup : sprite.TimelineGroup; - double targetTime = addEventToLoop ? 20000 : 0; - target.Alpha.Add(Easing.None, targetTime + firstStoryboardEvent, targetTime + firstStoryboardEvent + 500, 0, 1); + double loopRelativeOffset = addEventToLoop ? -loop_start_time : 0; + target.Alpha.Add(Easing.None, loopRelativeOffset + firstStoryboardEvent, loopRelativeOffset + firstStoryboardEvent + 500, 0, 1); // these should be ignored due to being in the future. sprite.TimelineGroup.Alpha.Add(Easing.None, 18000, 20000, 0, 1); From a3de5f808e39885505e061bdcab0716f226d8e22 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 6 Sep 2022 17:46:03 +0900 Subject: [PATCH 298/420] Fix typo in `LoopStartTime` addition --- osu.Game/Storyboards/StoryboardSprite.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Storyboards/StoryboardSprite.cs b/osu.Game/Storyboards/StoryboardSprite.cs index f77b3a812e..1eeaa0f084 100644 --- a/osu.Game/Storyboards/StoryboardSprite.cs +++ b/osu.Game/Storyboards/StoryboardSprite.cs @@ -43,7 +43,7 @@ namespace osu.Game.Storyboards foreach (var loop in loops) { command = loop.Alpha.Commands.FirstOrDefault(); - if (command != null) alphaCommands.Add((command.StartTime + loop.StartTime, command.StartValue == 0)); + if (command != null) alphaCommands.Add((command.StartTime + loop.LoopStartTime, command.StartValue == 0)); } if (alphaCommands.Count > 0) From 579e7e1f174a6db56827b80bbfffb1baaf7cd727 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 6 Sep 2022 18:08:51 +0900 Subject: [PATCH 299/420] Fix deleting a difficulty not updating the beatmap set hash --- .../Visual/Editing/TestSceneDifficultyDelete.cs | 3 +++ osu.Game/Beatmaps/BeatmapManager.cs | 11 +++++++++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneDifficultyDelete.cs b/osu.Game.Tests/Visual/Editing/TestSceneDifficultyDelete.cs index 1520f64ec0..4366b1c0bb 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneDifficultyDelete.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneDifficultyDelete.cs @@ -45,6 +45,7 @@ namespace osu.Game.Tests.Visual.Editing { Guid deletedDifficultyID = Guid.Empty; int countBeforeDeletion = 0; + string beatmapSetHashBefore = string.Empty; for (int i = 0; i < 12; i++) { @@ -55,6 +56,7 @@ namespace osu.Game.Tests.Visual.Editing { deletedDifficultyID = EditorBeatmap.BeatmapInfo.ID; countBeforeDeletion = Beatmap.Value.BeatmapSetInfo.Beatmaps.Count; + beatmapSetHashBefore = Beatmap.Value.BeatmapSetInfo.Hash; }); AddStep("click File", () => this.ChildrenOfType().First().TriggerClick()); @@ -72,6 +74,7 @@ namespace osu.Game.Tests.Visual.Editing AddAssert($"difficulty {i} is deleted", () => Beatmap.Value.BeatmapSetInfo.Beatmaps.Select(b => b.ID), () => Does.Not.Contain(deletedDifficultyID)); AddAssert("count decreased by one", () => Beatmap.Value.BeatmapSetInfo.Beatmaps.Count, () => Is.EqualTo(countBeforeDeletion - 1)); + AddAssert("set hash changed", () => Beatmap.Value.BeatmapSetInfo.Hash, () => Is.Not.EqualTo(beatmapSetHashBefore)); } } } diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index d784f1e627..d55ccb4b41 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -319,8 +319,7 @@ namespace osu.Game.Beatmaps AddFile(setInfo, stream, createBeatmapFilenameFromMetadata(beatmapInfo)); - setInfo.Hash = beatmapImporter.ComputeHash(setInfo); - setInfo.Status = BeatmapOnlineStatus.LocallyModified; + updateHashAndMarkDirty(setInfo); Realm.Write(r => { @@ -384,6 +383,8 @@ namespace osu.Game.Beatmaps DeleteFile(setInfo, beatmapInfo.File); setInfo.Beatmaps.Remove(beatmapInfo); + + updateHashAndMarkDirty(setInfo); }); } @@ -440,6 +441,12 @@ namespace osu.Game.Beatmaps public Task?> ImportAsUpdate(ProgressNotification notification, ImportTask importTask, BeatmapSetInfo original) => beatmapImporter.ImportAsUpdate(notification, importTask, original); + private void updateHashAndMarkDirty(BeatmapSetInfo setInfo) + { + setInfo.Hash = beatmapImporter.ComputeHash(setInfo); + setInfo.Status = BeatmapOnlineStatus.LocallyModified; + } + #region Implementation of ICanAcceptFiles public Task Import(params string[] paths) => beatmapImporter.Import(paths); From db15bd56e87630d0a6bce7e3f31422b82e81b933 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 6 Sep 2022 18:10:59 +0900 Subject: [PATCH 300/420] Invalidate working beatmap cache when calling `DeleteDifficultyImmediately` rather than in editor code --- osu.Game/Beatmaps/BeatmapManager.cs | 1 + osu.Game/Screens/Edit/Editor.cs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index d55ccb4b41..2c6edb64f8 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -385,6 +385,7 @@ namespace osu.Game.Beatmaps setInfo.Beatmaps.Remove(beatmapInfo); updateHashAndMarkDirty(setInfo); + workingBeatmapCache.Invalidate(setInfo); }); } diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 6e06683e38..3dfc7010f3 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -929,7 +929,7 @@ namespace osu.Game.Screens.Edit // of note, we're still working with the cloned version, so indices are all prior to deletion. BeatmapInfo nextToShow = difficultiesBeforeDeletion[deletedIndex == 0 ? 1 : deletedIndex - 1]; - Beatmap.Value = beatmapManager.GetWorkingBeatmap(nextToShow, true); + Beatmap.Value = beatmapManager.GetWorkingBeatmap(nextToShow); SwitchToDifficulty(nextToShow); } From bc1212f4e6785670a9249aaf1fa5ccf501fd6f5b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 6 Sep 2022 20:06:30 +0900 Subject: [PATCH 301/420] Change `NonGameplayAdjustments` to `GameplayAdjustments` and convert `TrueGameplayRate` to extension method --- .../Default/SpinnerRotationTracker.cs | 2 +- .../NonVisual/GameplayClockContainerTest.cs | 6 ++--- .../Visual/Gameplay/TestScenePause.cs | 2 +- .../Rulesets/UI/FrameStabilityContainer.cs | 5 +---- .../Screens/Play/GameplayClockContainer.cs | 4 +--- .../Screens/Play/GameplayClockExtensions.cs | 22 +++++++++++++++++++ osu.Game/Screens/Play/IGameplayClock.cs | 8 +------ .../Play/MasterGameplayClockContainer.cs | 11 +--------- 8 files changed, 31 insertions(+), 29 deletions(-) create mode 100644 osu.Game/Screens/Play/GameplayClockExtensions.cs diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/SpinnerRotationTracker.cs b/osu.Game.Rulesets.Osu/Skinning/Default/SpinnerRotationTracker.cs index 554ea3ac90..97cebc3123 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/SpinnerRotationTracker.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/SpinnerRotationTracker.cs @@ -110,7 +110,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default currentRotation += angle; // rate has to be applied each frame, because it's not guaranteed to be constant throughout playback // (see: ModTimeRamp) - drawableSpinner.Result.RateAdjustedRotation += (float)(Math.Abs(angle) * (gameplayClock?.TrueGameplayRate ?? Clock.Rate)); + drawableSpinner.Result.RateAdjustedRotation += (float)(Math.Abs(angle) * (gameplayClock?.GetTrueGameplayRate() ?? Clock.Rate)); } private void resetState(DrawableHitObject obj) diff --git a/osu.Game.Tests/NonVisual/GameplayClockContainerTest.cs b/osu.Game.Tests/NonVisual/GameplayClockContainerTest.cs index f9f4ead644..95bf1ab354 100644 --- a/osu.Game.Tests/NonVisual/GameplayClockContainerTest.cs +++ b/osu.Game.Tests/NonVisual/GameplayClockContainerTest.cs @@ -13,17 +13,17 @@ namespace osu.Game.Tests.NonVisual { [TestCase(0)] [TestCase(1)] - public void TestTrueGameplayRateWithZeroAdjustment(double underlyingClockRate) + public void TestTrueGameplayRateWithGameplayAdjustment(double underlyingClockRate) { var framedClock = new FramedClock(new ManualClock { Rate = underlyingClockRate }); var gameplayClock = new TestGameplayClockContainer(framedClock); - Assert.That(gameplayClock.TrueGameplayRate, Is.EqualTo(0)); + Assert.That(gameplayClock.GetTrueGameplayRate(), Is.EqualTo(2)); } private class TestGameplayClockContainer : GameplayClockContainer { - public override IEnumerable NonGameplayAdjustments => new[] { 0.0 }; + public override IEnumerable GameplayAdjustments => new[] { 2.0 }; public TestGameplayClockContainer(IFrameBasedClock underlyingClock) : base(underlyingClock) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs index a6abdd7ee1..d0371acce7 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs @@ -370,7 +370,7 @@ namespace osu.Game.Tests.Visual.Gameplay private void confirmNoTrackAdjustments() { - AddAssert("track has no adjustments", () => Beatmap.Value.Track.AggregateFrequency.Value == 1); + AddUntilStep("track has no adjustments", () => Beatmap.Value.Track.AggregateFrequency.Value, () => Is.EqualTo(1)); } private void restart() => AddStep("restart", () => Player.Restart()); diff --git a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs index 4d817cd973..4f4a2d908d 100644 --- a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs +++ b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs @@ -10,7 +10,6 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Timing; -using osu.Framework.Utils; using osu.Game.Input.Handlers; using osu.Game.Screens.Play; @@ -263,11 +262,9 @@ namespace osu.Game.Rulesets.UI public FrameTimeInfo TimeInfo => framedClock.TimeInfo; - public double TrueGameplayRate => parentGameplayClock?.TrueGameplayRate ?? Rate; - public double StartTime => parentGameplayClock?.StartTime ?? 0; - public IEnumerable NonGameplayAdjustments => parentGameplayClock?.NonGameplayAdjustments ?? Enumerable.Empty(); + public IEnumerable GameplayAdjustments => parentGameplayClock?.GameplayAdjustments ?? Enumerable.Empty(); #endregion diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index 4d48675f94..5dfaf2d584 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -45,7 +45,7 @@ namespace osu.Game.Screens.Play /// public double StartTime { get; protected set; } - public virtual IEnumerable NonGameplayAdjustments => Enumerable.Empty(); + public virtual IEnumerable GameplayAdjustments => Enumerable.Empty(); private readonly BindableBool isPaused = new BindableBool(true); @@ -223,7 +223,5 @@ namespace osu.Game.Screens.Play public double FramesPerSecond => GameplayClock.FramesPerSecond; public FrameTimeInfo TimeInfo => GameplayClock.TimeInfo; - - public virtual double TrueGameplayRate => Rate; } } diff --git a/osu.Game/Screens/Play/GameplayClockExtensions.cs b/osu.Game/Screens/Play/GameplayClockExtensions.cs new file mode 100644 index 0000000000..b683c61f63 --- /dev/null +++ b/osu.Game/Screens/Play/GameplayClockExtensions.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; + +namespace osu.Game.Screens.Play +{ + public static class GameplayClockExtensions + { + /// + /// The rate of gameplay when playback is at 100%. + /// This excludes any seeking / user adjustments. + /// + public static double GetTrueGameplayRate(this IGameplayClock clock) + { + double rate = Math.Sign(clock.Rate); + foreach (double a in clock.GameplayAdjustments) + rate *= a; + return rate; + } + } +} diff --git a/osu.Game/Screens/Play/IGameplayClock.cs b/osu.Game/Screens/Play/IGameplayClock.cs index ea567090ad..7c50b9d407 100644 --- a/osu.Game/Screens/Play/IGameplayClock.cs +++ b/osu.Game/Screens/Play/IGameplayClock.cs @@ -9,12 +9,6 @@ namespace osu.Game.Screens.Play { public interface IGameplayClock : IFrameBasedClock { - /// - /// The rate of gameplay when playback is at 100%. - /// This excludes any seeking / user adjustments. - /// - double TrueGameplayRate { get; } - /// /// The time from which the clock should start. Will be seeked to on calling . /// @@ -27,7 +21,7 @@ namespace osu.Game.Screens.Play /// /// All adjustments applied to this clock which don't come from gameplay or mods. /// - IEnumerable NonGameplayAdjustments { get; } + IEnumerable GameplayAdjustments { get; } IBindable IsPaused { get; } } diff --git a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs index bda8718dc6..f1ee65dcd3 100644 --- a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs +++ b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs @@ -224,16 +224,7 @@ namespace osu.Game.Screens.Play private readonly List> speedAdjustments = new List>(); - public override double TrueGameplayRate - { - get - { - double rate = Rate; - foreach (var a in speedAdjustments) - rate *= a.Value; - return rate; - } - } + public override IEnumerable GameplayAdjustments => speedAdjustments.Select(bindable => bindable.Value); void IAdjustableAudioComponent.AddAdjustment(AdjustableProperty type, IBindable adjustBindable) { From 66c44f5913d24c95116d1a4605ed4f6e3d3522d9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 6 Sep 2022 21:40:04 +0900 Subject: [PATCH 302/420] Delegate interface to valid target --- .../Play/MasterGameplayClockContainer.cs | 37 +++++++++++++------ 1 file changed, 26 insertions(+), 11 deletions(-) diff --git a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs index f1ee65dcd3..7c30f86125 100644 --- a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs +++ b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs @@ -8,6 +8,7 @@ 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.Beatmaps.ControlPoints; @@ -56,6 +57,11 @@ namespace osu.Game.Screens.Play /// private double? actualStopTime; + /// + /// Maintained solely to delegate pieces to (to maintain parent lookups). + /// + private readonly AudioContainer audioContainer; + /// /// Create a new master gameplay clock container. /// @@ -69,6 +75,8 @@ namespace osu.Game.Screens.Play this.skipTargetTime = skipTargetTime; StartTime = findEarliestStartTime(); + + AddInternal(audioContainer = new AudioContainer()); } private double findEarliestStartTime() @@ -238,25 +246,32 @@ namespace osu.Game.Screens.Play track.RemoveAdjustment(type, adjustBindable); } + void IAdjustableAudioComponent.RemoveAllAdjustments(AdjustableProperty type) => audioContainer.RemoveAllAdjustments(type); + + void IAdjustableAudioComponent.BindAdjustments(IAggregateAudioAdjustment component) => audioContainer.BindAdjustments(component); + + void IAdjustableAudioComponent.UnbindAdjustments(IAggregateAudioAdjustment component) => audioContainer.UnbindAdjustments(component); + + BindableNumber IAdjustableAudioComponent.Volume => audioContainer.Volume; + + BindableNumber IAdjustableAudioComponent.Balance => audioContainer.Balance; + + BindableNumber IAdjustableAudioComponent.Frequency => audioContainer.Frequency; + + BindableNumber IAdjustableAudioComponent.Tempo => audioContainer.Tempo; + public override void ResetSpeedAdjustments() { track.RemoveAllAdjustments(AdjustableProperty.Frequency); track.RemoveAllAdjustments(AdjustableProperty.Tempo); } - public void RemoveAllAdjustments(AdjustableProperty type) => throw new NotImplementedException(); + IBindable IAggregateAudioAdjustment.AggregateVolume => audioContainer.AggregateVolume; - public void BindAdjustments(IAggregateAudioAdjustment component) => throw new NotImplementedException(); - public void UnbindAdjustments(IAggregateAudioAdjustment component) => throw new NotImplementedException(); + IBindable IAggregateAudioAdjustment.AggregateBalance => audioContainer.AggregateBalance; - public BindableNumber Volume => throw new NotImplementedException(); - public BindableNumber Balance => throw new NotImplementedException(); - public BindableNumber Frequency => throw new NotImplementedException(); - public BindableNumber Tempo => throw new NotImplementedException(); + IBindable IAggregateAudioAdjustment.AggregateFrequency => audioContainer.AggregateFrequency; - public IBindable AggregateVolume => throw new NotImplementedException(); - public IBindable AggregateBalance => throw new NotImplementedException(); - public IBindable AggregateFrequency => throw new NotImplementedException(); - public IBindable AggregateTempo => throw new NotImplementedException(); + IBindable IAggregateAudioAdjustment.AggregateTempo => audioContainer.AggregateTempo; } } From a2f8ff825e88bc02c3a2e257d5d0f78a1af71a13 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 6 Sep 2022 17:27:29 +0900 Subject: [PATCH 303/420] Also ignore drum roll strong judgement --- osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs | 2 ++ osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs | 3 +++ 2 files changed, 5 insertions(+) diff --git a/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs b/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs index 3dab8d2e13..3325eda7cf 100644 --- a/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs +++ b/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs @@ -96,6 +96,8 @@ namespace osu.Game.Rulesets.Taiko.Objects public class StrongNestedHit : StrongNestedHitObject { + // The strong hit of the drum roll doesn't actually provide any score. + public override Judgement CreateJudgement() => new IgnoreJudgement(); } #region LegacyBeatmapEncoder diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs index ccca5587b7..cc71ba5401 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs @@ -317,6 +317,9 @@ namespace osu.Game.Rulesets.Taiko.UI break; default: + if (!result.Type.IsScorable()) + break; + judgementContainer.Add(judgementPools[result.Type].Get(j => { j.Apply(result, judgedObject); From 2ca63b50309b8d839dffaced3bb9d59ab51766fc Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 6 Sep 2022 18:02:17 +0900 Subject: [PATCH 304/420] Add tests for all taiko judgements --- .../Judgements/JudgementTest.cs | 96 +++++++++ .../Judgements/TestSceneDrumRollJudgements.cs | 201 ++++++++++++++++++ .../Judgements/TestSceneHitJudgements.cs | 133 ++++++++++++ .../Judgements/TestSceneSwellJudgements.cs | 118 ++++++++++ .../TestSceneSwellJudgements.cs | 42 ---- 5 files changed, 548 insertions(+), 42 deletions(-) create mode 100644 osu.Game.Rulesets.Taiko.Tests/Judgements/JudgementTest.cs create mode 100644 osu.Game.Rulesets.Taiko.Tests/Judgements/TestSceneDrumRollJudgements.cs create mode 100644 osu.Game.Rulesets.Taiko.Tests/Judgements/TestSceneHitJudgements.cs create mode 100644 osu.Game.Rulesets.Taiko.Tests/Judgements/TestSceneSwellJudgements.cs delete mode 100644 osu.Game.Rulesets.Taiko.Tests/TestSceneSwellJudgements.cs diff --git a/osu.Game.Rulesets.Taiko.Tests/Judgements/JudgementTest.cs b/osu.Game.Rulesets.Taiko.Tests/Judgements/JudgementTest.cs new file mode 100644 index 0000000000..c9e8fefead --- /dev/null +++ b/osu.Game.Rulesets.Taiko.Tests/Judgements/JudgementTest.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.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using osu.Framework.Extensions.TypeExtensions; +using osu.Framework.Screens; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Replays; +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Replays; +using osu.Game.Rulesets.Scoring; +using osu.Game.Rulesets.Taiko.Objects; +using osu.Game.Scoring; +using osu.Game.Screens.Play; +using osu.Game.Tests.Visual; + +namespace osu.Game.Rulesets.Taiko.Tests +{ + public class JudgementTest : RateAdjustedBeatmapTestScene + { + private ScoreAccessibleReplayPlayer currentPlayer = null!; + protected List JudgementResults { get; private set; } = null!; + + protected void AssertJudgementCount(int count) + { + AddAssert($"{count} judgement{(count > 0 ? "s" : "")}", () => JudgementResults, () => Has.Count.EqualTo(count)); + } + + protected void AssertResult(int index, HitResult expectedResult) + { + AddAssert($"{typeof(T).ReadableName()} ({index}) judged as {expectedResult}", + () => JudgementResults.Where(j => j.HitObject is T).OrderBy(j => j.HitObject.StartTime).ElementAt(index).Type, + () => Is.EqualTo(expectedResult)); + } + + protected void PerformTest(List frames, Beatmap? beatmap = null) + { + AddStep("load player", () => + { + Beatmap.Value = CreateWorkingBeatmap(beatmap); + + 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); + } + + protected Beatmap CreateBeatmap(params TaikoHitObject[] hitObjects) + { + var beatmap = new Beatmap + { + HitObjects = hitObjects.ToList(), + BeatmapInfo = + { + Difficulty = new BeatmapDifficulty { SliderTickRate = 4 }, + Ruleset = new TaikoRuleset().RulesetInfo + }, + }; + + beatmap.ControlPointInfo.Add(0, new EffectControlPoint { ScrollSpeed = 0.1f }); + return beatmap; + } + + private class ScoreAccessibleReplayPlayer : ReplayPlayer + { + public new ScoreProcessor ScoreProcessor => base.ScoreProcessor; + + protected override bool PauseOnFocusLost => false; + + public ScoreAccessibleReplayPlayer(Score score) + : base(score, new PlayerConfiguration + { + AllowPause = false, + ShowResults = false, + }) + { + } + } + } +} diff --git a/osu.Game.Rulesets.Taiko.Tests/Judgements/TestSceneDrumRollJudgements.cs b/osu.Game.Rulesets.Taiko.Tests/Judgements/TestSceneDrumRollJudgements.cs new file mode 100644 index 0000000000..2c28c3dad5 --- /dev/null +++ b/osu.Game.Rulesets.Taiko.Tests/Judgements/TestSceneDrumRollJudgements.cs @@ -0,0 +1,201 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using NUnit.Framework; +using osu.Game.Rulesets.Replays; +using osu.Game.Rulesets.Scoring; +using osu.Game.Rulesets.Taiko.Objects; +using osu.Game.Rulesets.Taiko.Replays; + +namespace osu.Game.Rulesets.Taiko.Tests.Judgements +{ + public class TestSceneDrumRollJudgements : JudgementTest + { + [Test] + public void TestHitAllDrumRoll() + { + const double hit_time = 1000; + + PerformTest(new List + { + new TaikoReplayFrame(0), + new TaikoReplayFrame(1000, TaikoAction.LeftCentre), + new TaikoReplayFrame(1001), + new TaikoReplayFrame(2000, TaikoAction.LeftCentre), + new TaikoReplayFrame(2001), + }, CreateBeatmap(new DrumRoll + { + StartTime = hit_time, + Duration = 1000 + })); + + AssertJudgementCount(3); + AssertResult(0, HitResult.SmallBonus); + AssertResult(1, HitResult.SmallBonus); + AssertResult(0, HitResult.IgnoreHit); + } + + [Test] + public void TestHitSomeDrumRoll() + { + const double hit_time = 1000; + + PerformTest(new List + { + new TaikoReplayFrame(0), + new TaikoReplayFrame(2000, TaikoAction.LeftCentre), + new TaikoReplayFrame(2001), + }, CreateBeatmap(new DrumRoll + { + StartTime = hit_time, + Duration = 1000 + })); + + AssertJudgementCount(3); + AssertResult(0, HitResult.IgnoreMiss); + AssertResult(1, HitResult.SmallBonus); + AssertResult(0, HitResult.IgnoreHit); + } + + [Test] + public void TestHitNoneDrumRoll() + { + const double hit_time = 1000; + + PerformTest(new List + { + new TaikoReplayFrame(0), + }, CreateBeatmap(new DrumRoll + { + StartTime = hit_time, + Duration = 1000 + })); + + AssertJudgementCount(3); + AssertResult(0, HitResult.IgnoreMiss); + AssertResult(1, HitResult.IgnoreMiss); + AssertResult(0, HitResult.IgnoreHit); + } + + [Test] + public void TestHitAllStrongDrumRollWithOneKey() + { + const double hit_time = 1000; + + PerformTest(new List + { + new TaikoReplayFrame(0), + new TaikoReplayFrame(1000, TaikoAction.LeftCentre), + new TaikoReplayFrame(1001), + new TaikoReplayFrame(2000, TaikoAction.LeftCentre), + new TaikoReplayFrame(2001), + }, CreateBeatmap(new DrumRoll + { + StartTime = hit_time, + Duration = 1000, + IsStrong = true + })); + + AssertJudgementCount(6); + + AssertResult(0, HitResult.SmallBonus); + AssertResult(0, HitResult.LargeBonus); + + AssertResult(1, HitResult.SmallBonus); + AssertResult(1, HitResult.LargeBonus); + + AssertResult(0, HitResult.IgnoreHit); + AssertResult(2, HitResult.IgnoreHit); + } + + [Test] + public void TestHitSomeStrongDrumRollWithOneKey() + { + const double hit_time = 1000; + + PerformTest(new List + { + new TaikoReplayFrame(0), + new TaikoReplayFrame(2000, TaikoAction.LeftCentre), + new TaikoReplayFrame(2001), + }, CreateBeatmap(new DrumRoll + { + StartTime = hit_time, + Duration = 1000, + IsStrong = true + })); + + AssertJudgementCount(6); + + AssertResult(0, HitResult.IgnoreMiss); + AssertResult(0, HitResult.IgnoreMiss); + + AssertResult(1, HitResult.SmallBonus); + AssertResult(1, HitResult.LargeBonus); + + AssertResult(0, HitResult.IgnoreHit); + AssertResult(2, HitResult.IgnoreHit); + } + + [Test] + public void TestHitAllStrongDrumRollWithBothKeys() + { + const double hit_time = 1000; + + PerformTest(new List + { + new TaikoReplayFrame(0), + new TaikoReplayFrame(1000, TaikoAction.LeftCentre, TaikoAction.RightCentre), + new TaikoReplayFrame(1001), + new TaikoReplayFrame(2000, TaikoAction.LeftCentre, TaikoAction.RightCentre), + new TaikoReplayFrame(2001), + }, CreateBeatmap(new DrumRoll + { + StartTime = hit_time, + Duration = 1000, + IsStrong = true + })); + + AssertJudgementCount(6); + + AssertResult(0, HitResult.SmallBonus); + AssertResult(0, HitResult.LargeBonus); + + AssertResult(1, HitResult.SmallBonus); + AssertResult(1, HitResult.LargeBonus); + + AssertResult(0, HitResult.IgnoreHit); + AssertResult(2, HitResult.IgnoreHit); + } + + [Test] + public void TestHitSomeStrongDrumRollWithBothKeys() + { + const double hit_time = 1000; + + PerformTest(new List + { + new TaikoReplayFrame(0), + new TaikoReplayFrame(2000, TaikoAction.LeftCentre, TaikoAction.RightCentre), + new TaikoReplayFrame(2001), + }, CreateBeatmap(new DrumRoll + { + StartTime = hit_time, + Duration = 1000, + IsStrong = true + })); + + AssertJudgementCount(6); + + AssertResult(0, HitResult.IgnoreMiss); + AssertResult(0, HitResult.IgnoreMiss); + + AssertResult(1, HitResult.SmallBonus); + AssertResult(1, HitResult.LargeBonus); + + AssertResult(0, HitResult.IgnoreHit); + AssertResult(2, HitResult.IgnoreHit); + } + } +} diff --git a/osu.Game.Rulesets.Taiko.Tests/Judgements/TestSceneHitJudgements.cs b/osu.Game.Rulesets.Taiko.Tests/Judgements/TestSceneHitJudgements.cs new file mode 100644 index 0000000000..a405f0e8ba --- /dev/null +++ b/osu.Game.Rulesets.Taiko.Tests/Judgements/TestSceneHitJudgements.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.Collections.Generic; +using NUnit.Framework; +using osu.Game.Rulesets.Replays; +using osu.Game.Rulesets.Scoring; +using osu.Game.Rulesets.Taiko.Objects; +using osu.Game.Rulesets.Taiko.Replays; + +namespace osu.Game.Rulesets.Taiko.Tests.Judgements +{ + public class TestSceneHitJudgements : JudgementTest + { + [Test] + public void TestHitCentreHit() + { + const double hit_time = 1000; + + PerformTest(new List + { + new TaikoReplayFrame(0), + new TaikoReplayFrame(hit_time, TaikoAction.LeftCentre), + }, CreateBeatmap(new Hit + { + Type = HitType.Centre, + StartTime = hit_time + })); + + AssertJudgementCount(1); + AssertResult(0, HitResult.Great); + } + + [Test] + public void TestHitRimHit() + { + const double hit_time = 1000; + + PerformTest(new List + { + new TaikoReplayFrame(0), + new TaikoReplayFrame(hit_time, TaikoAction.LeftRim), + }, CreateBeatmap(new Hit + { + Type = HitType.Rim, + StartTime = hit_time + })); + + AssertJudgementCount(1); + AssertResult(0, HitResult.Great); + } + + [Test] + public void TestMissHit() + { + const double hit_time = 1000; + + PerformTest(new List + { + new TaikoReplayFrame(0) + }, CreateBeatmap(new Hit + { + Type = HitType.Centre, + StartTime = hit_time + })); + + AssertJudgementCount(1); + AssertResult(0, HitResult.Miss); + } + + [Test] + public void TestHitStrongHitWithOneKey() + { + const double hit_time = 1000; + + PerformTest(new List + { + new TaikoReplayFrame(0), + new TaikoReplayFrame(hit_time, TaikoAction.LeftCentre), + }, CreateBeatmap(new Hit + { + Type = HitType.Centre, + StartTime = hit_time, + IsStrong = true + })); + + AssertJudgementCount(2); + AssertResult(0, HitResult.Great); + AssertResult(0, HitResult.IgnoreMiss); + } + + [Test] + public void TestHitStrongHitWithBothKeys() + { + const double hit_time = 1000; + + PerformTest(new List + { + new TaikoReplayFrame(0), + new TaikoReplayFrame(hit_time, TaikoAction.LeftCentre, TaikoAction.RightCentre), + }, CreateBeatmap(new Hit + { + Type = HitType.Centre, + StartTime = hit_time, + IsStrong = true + })); + + AssertJudgementCount(2); + AssertResult(0, HitResult.Great); + AssertResult(0, HitResult.LargeBonus); + } + + [Test] + public void TestMissStrongHit() + { + const double hit_time = 1000; + + PerformTest(new List + { + new TaikoReplayFrame(0), + }, CreateBeatmap(new Hit + { + Type = HitType.Centre, + StartTime = hit_time, + IsStrong = true + })); + + AssertJudgementCount(2); + AssertResult(0, HitResult.Miss); + AssertResult(0, HitResult.IgnoreMiss); + } + } +} diff --git a/osu.Game.Rulesets.Taiko.Tests/Judgements/TestSceneSwellJudgements.cs b/osu.Game.Rulesets.Taiko.Tests/Judgements/TestSceneSwellJudgements.cs new file mode 100644 index 0000000000..7bdfcf0b07 --- /dev/null +++ b/osu.Game.Rulesets.Taiko.Tests/Judgements/TestSceneSwellJudgements.cs @@ -0,0 +1,118 @@ +// 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.Rulesets.Replays; +using osu.Game.Rulesets.Scoring; +using osu.Game.Rulesets.Taiko.Objects; +using osu.Game.Rulesets.Taiko.Replays; + +namespace osu.Game.Rulesets.Taiko.Tests.Judgements +{ + public class TestSceneSwellJudgements : JudgementTest + { + [Test] + public void TestHitAllSwell() + { + const double hit_time = 1000; + + Swell swell = new Swell + { + StartTime = hit_time, + Duration = 1000, + RequiredHits = 10 + }; + + List frames = new List + { + new TaikoReplayFrame(0), + new TaikoReplayFrame(2001), + }; + + for (int i = 0; i < swell.RequiredHits; i++) + { + double frameTime = 1000 + i * 50; + frames.Add(new TaikoReplayFrame(frameTime, i % 2 == 0 ? TaikoAction.LeftCentre : TaikoAction.LeftRim)); + frames.Add(new TaikoReplayFrame(frameTime + 10)); + } + + PerformTest(frames, CreateBeatmap(swell)); + + AssertJudgementCount(11); + + for (int i = 0; i < swell.RequiredHits; i++) + AssertResult(i, HitResult.IgnoreHit); + + AssertResult(0, HitResult.LargeBonus); + } + + [Test] + public void TestHitSomeSwell() + { + const double hit_time = 1000; + + Swell swell = new Swell + { + StartTime = hit_time, + Duration = 1000, + RequiredHits = 10 + }; + + List frames = new List + { + new TaikoReplayFrame(0), + new TaikoReplayFrame(2001), + }; + + for (int i = 0; i < swell.RequiredHits / 2; i++) + { + double frameTime = 1000 + i * 50; + frames.Add(new TaikoReplayFrame(frameTime, i % 2 == 0 ? TaikoAction.LeftCentre : TaikoAction.LeftRim)); + frames.Add(new TaikoReplayFrame(frameTime + 10)); + } + + PerformTest(frames, CreateBeatmap(swell)); + + AssertJudgementCount(11); + + for (int i = 0; i < swell.RequiredHits / 2; i++) + AssertResult(i, HitResult.IgnoreHit); + for (int i = swell.RequiredHits / 2; i < swell.RequiredHits; i++) + AssertResult(i, HitResult.IgnoreMiss); + + AssertResult(0, HitResult.IgnoreMiss); + } + + [Test] + public void TestHitNoneSwell() + { + const double hit_time = 1000; + + Swell swell = new Swell + { + StartTime = hit_time, + Duration = 1000, + RequiredHits = 10 + }; + + List frames = new List + { + new TaikoReplayFrame(0), + new TaikoReplayFrame(2001), + }; + + PerformTest(frames, CreateBeatmap(swell)); + + AssertJudgementCount(11); + + for (int i = 0; i < swell.RequiredHits; i++) + AssertResult(i, HitResult.IgnoreMiss); + + AssertResult(0, HitResult.IgnoreMiss); + + AddAssert("all tick offsets are 0", () => JudgementResults.Where(r => r.HitObject is SwellTick).All(r => r.TimeOffset == 0)); + } + } +} diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneSwellJudgements.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneSwellJudgements.cs deleted file mode 100644 index bd546b16f2..0000000000 --- a/osu.Game.Rulesets.Taiko.Tests/TestSceneSwellJudgements.cs +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -#nullable disable - -using System.Linq; -using NUnit.Framework; -using osu.Game.Beatmaps; -using osu.Game.Rulesets.Taiko.Objects; - -namespace osu.Game.Rulesets.Taiko.Tests -{ - public class TestSceneSwellJudgements : TestSceneTaikoPlayer - { - [Test] - public void TestZeroTickTimeOffsets() - { - AddUntilStep("gameplay finished", () => Player.ScoreProcessor.HasCompleted.Value); - AddAssert("all tick offsets are 0", () => Player.Results.Where(r => r.HitObject is SwellTick).All(r => r.TimeOffset == 0)); - } - - protected override bool Autoplay => true; - - protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) - { - var beatmap = new Beatmap - { - BeatmapInfo = { Ruleset = new TaikoRuleset().RulesetInfo }, - HitObjects = - { - new Swell - { - StartTime = 1000, - Duration = 1000, - } - } - }; - - return beatmap; - } - } -} From c2107bd32277ae7c40e5cf3bb513a44f189be80f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 6 Sep 2022 23:36:27 +0900 Subject: [PATCH 305/420] Fix test failures due to notifications being forwarded before player finishes loading --- .../Visual/Gameplay/TestScenePlayerLoader.cs | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs index b6c17fbaca..1d101383cc 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs @@ -301,7 +301,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddAssert("check for notification", () => notificationOverlay.UnreadCount.Value, () => Is.EqualTo(1)); - clickNotificationIfAny(); + clickNotification(); AddAssert("check " + volumeName, assert); @@ -370,8 +370,12 @@ namespace osu.Game.Tests.Visual.Gameplay batteryInfo.SetChargeLevel(chargeLevel); })); AddUntilStep("wait for player", () => player?.LoadState == LoadState.Ready); - AddAssert($"notification {(shouldWarn ? "triggered" : "not triggered")}", () => notificationOverlay.UnreadCount.Value == (shouldWarn ? 1 : 0)); - clickNotificationIfAny(); + + if (shouldWarn) + clickNotification(); + else + AddAssert("notification not triggered", () => notificationOverlay.UnreadCount.Value == 0); + AddUntilStep("wait for player load", () => player.IsLoaded); } @@ -436,9 +440,13 @@ namespace osu.Game.Tests.Visual.Gameplay AddUntilStep("skip button not visible", () => !checkSkipButtonVisible()); } - private void clickNotificationIfAny() + private void clickNotification() { - AddStep("click notification", () => notificationOverlay.ChildrenOfType().FirstOrDefault()?.TriggerClick()); + Notification notification = null; + + AddUntilStep("wait for notification", () => (notification = notificationOverlay.ChildrenOfType().FirstOrDefault()) != null); + AddStep("open notification overlay", () => notificationOverlay.Show()); + AddStep("click notification", () => notification.TriggerClick()); } private EpilepsyWarning getWarning() => loader.ChildrenOfType().SingleOrDefault(); From 6a371eba5fcc910b627ca299ba09475198c34b04 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 7 Sep 2022 00:12:25 +0900 Subject: [PATCH 306/420] Fix namespace --- osu.Game.Rulesets.Taiko.Tests/Judgements/JudgementTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko.Tests/Judgements/JudgementTest.cs b/osu.Game.Rulesets.Taiko.Tests/Judgements/JudgementTest.cs index c9e8fefead..7f2f27b2b8 100644 --- a/osu.Game.Rulesets.Taiko.Tests/Judgements/JudgementTest.cs +++ b/osu.Game.Rulesets.Taiko.Tests/Judgements/JudgementTest.cs @@ -17,7 +17,7 @@ using osu.Game.Scoring; using osu.Game.Screens.Play; using osu.Game.Tests.Visual; -namespace osu.Game.Rulesets.Taiko.Tests +namespace osu.Game.Rulesets.Taiko.Tests.Judgements { public class JudgementTest : RateAdjustedBeatmapTestScene { From 7c0e99c5a86c0c28e1399e36c8cdf5a32271c143 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 7 Sep 2022 00:11:51 +0900 Subject: [PATCH 307/420] Decode Geki/Katu from legacy taiko scores into LargeBonus --- .../Formats/LegacyScoreDecoderTest.cs | 18 +++++++++++ .../Resources/Replays/taiko-replay.osr | Bin 0 -> 237 bytes .../Scoring/Legacy/ScoreInfoExtensions.cs | 28 +++++++++++++++--- 3 files changed, 42 insertions(+), 4 deletions(-) create mode 100644 osu.Game.Tests/Resources/Replays/taiko-replay.osr diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs index aa6fc1f309..cd6e5e7919 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs @@ -67,6 +67,24 @@ namespace osu.Game.Tests.Beatmaps.Formats } } + [Test] + public void TestDecodeTaikoReplay() + { + var decoder = new TestLegacyScoreDecoder(); + + using (var resourceStream = TestResources.OpenResource("Replays/taiko-replay.osr")) + { + var score = decoder.Parse(resourceStream); + + Assert.AreEqual(1, score.ScoreInfo.Ruleset.OnlineID); + Assert.AreEqual(4, score.ScoreInfo.Statistics[HitResult.Great]); + Assert.AreEqual(2, score.ScoreInfo.Statistics[HitResult.LargeBonus]); + Assert.AreEqual(4, score.ScoreInfo.MaxCombo); + + Assert.That(score.Replay.Frames, Is.Not.Empty); + } + } + [TestCase(3, true)] [TestCase(6, false)] [TestCase(LegacyBeatmapDecoder.LATEST_VERSION, false)] diff --git a/osu.Game.Tests/Resources/Replays/taiko-replay.osr b/osu.Game.Tests/Resources/Replays/taiko-replay.osr new file mode 100644 index 0000000000000000000000000000000000000000..986b3116ab25f62dcb67514b8f52941cddab9b75 GIT binary patch literal 237 zcmZSN=rUpCRxmU*PqQ>jN;EJ_HcqxoN;FS4NKLXxNl8jJvox|WwB+W>P0h|uOvx`U zRpeGMNi{Y$H#9XgPc=7Av^2LcO*A!6v@}XiG&3+yPE9gmVE_RpF!@23fdRzlU|`@D zG%_?ct})OvHr6pUG%%?#)JZt7C`od{EsiLlidY5)1qLgKE(Qj%9;dkG`r7Op-o=T* z*CL8GF^jI?N!zpE@#WrB&!a^VpByr7JWzVO{9Crhvo#`$>L1#cZ&WQ%Oui9fe{Qpf Vr?FkmyS Date: Wed, 7 Sep 2022 02:29:15 +0900 Subject: [PATCH 308/420] refactor(osu.Game): improve code quality --- .../TestSceneHitEventTimingDistributionGraph.cs | 6 +++--- .../Statistics/HitEventTimingDistributionGraph.cs | 11 ++++------- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneHitEventTimingDistributionGraph.cs b/osu.Game.Tests/Visual/Ranking/TestSceneHitEventTimingDistributionGraph.cs index fe4aba1317..4825cb7f80 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneHitEventTimingDistributionGraph.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneHitEventTimingDistributionGraph.cs @@ -62,7 +62,7 @@ namespace osu.Game.Tests.Visual.Ranking { createTest(CreateDistributedHitEvents(0, 50).Select(h => { - var offset = Math.Abs(h.TimeOffset); + double offset = Math.Abs(h.TimeOffset); var result = offset > 36 ? HitResult.Miss : offset > 32 ? HitResult.Meh : offset > 24 ? HitResult.Ok : offset > 16 ? HitResult.Good : offset > 8 ? HitResult.Great : HitResult.Perfect; return new HitEvent(h.TimeOffset, result, placeholder_object, placeholder_object, null); }).ToList()); @@ -73,13 +73,13 @@ namespace osu.Game.Tests.Visual.Ranking { var wide = CreateDistributedHitEvents(0, 50).Select(h => { - var offset = Math.Abs(h.TimeOffset); + double offset = Math.Abs(h.TimeOffset); var result = offset > 36 ? HitResult.Miss : offset > 32 ? HitResult.Meh : offset > 24 ? HitResult.Ok : offset > 16 ? HitResult.Good : offset > 8 ? HitResult.Great : HitResult.Perfect; return new HitEvent(h.TimeOffset, result, placeholder_object, placeholder_object, null); }); var narrow = CreateDistributedHitEvents(0, 50).Select(h => { - var offset = Math.Abs(h.TimeOffset); + double offset = Math.Abs(h.TimeOffset); var result = offset > 25 ? HitResult.Miss : offset > 20 ? HitResult.Meh : offset > 15 ? HitResult.Ok : offset > 10 ? HitResult.Good : offset > 5 ? HitResult.Great : HitResult.Perfect; return new HitEvent(h.TimeOffset, result, placeholder_object, placeholder_object, null); }); diff --git a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs index 5fbc07921a..d3c2716f8b 100644 --- a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs +++ b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs @@ -299,14 +299,11 @@ namespace osu.Game.Screens.Ranking.Statistics float offsetValue = 0; - if (values.Any()) + for (int i = 0; i < values.Count; i++) { - for (int i = 0; i < values.Count; i++) - { - boxOriginals[i].MoveToY(offsetForValue(offsetValue) * BoundingBox.Height, duration, Easing.OutQuint); - boxOriginals[i].ResizeHeightTo(heightForValue(values[i].Value), duration, Easing.OutQuint); - offsetValue -= values[i].Value; - } + boxOriginals[i].MoveToY(offsetForValue(offsetValue) * BoundingBox.Height, duration, Easing.OutQuint); + boxOriginals[i].ResizeHeightTo(heightForValue(values[i].Value), duration, Easing.OutQuint); + offsetValue -= values[i].Value; } } From cb1d886c9cdbe05b7e47ed11cff15f999023f09c Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Wed, 7 Sep 2022 13:17:04 +0900 Subject: [PATCH 309/420] Add audio feedback for Esc/Back clearing text from a FocusedTextBox --- .../Graphics/UserInterface/FocusedTextBox.cs | 1 + osu.Game/Graphics/UserInterface/OsuTextBox.cs | 38 +++++++++---------- 2 files changed, 20 insertions(+), 19 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/FocusedTextBox.cs b/osu.Game/Graphics/UserInterface/FocusedTextBox.cs index 230d921c68..0c18fd36fc 100644 --- a/osu.Game/Graphics/UserInterface/FocusedTextBox.cs +++ b/osu.Game/Graphics/UserInterface/FocusedTextBox.cs @@ -88,6 +88,7 @@ namespace osu.Game.Graphics.UserInterface if (Text.Length > 0) { Text = string.Empty; + PlayFeedbackSample(FeedbackSampleType.TextRemove); return true; } } diff --git a/osu.Game/Graphics/UserInterface/OsuTextBox.cs b/osu.Game/Graphics/UserInterface/OsuTextBox.cs index 4c2e00d6e0..18977638f3 100644 --- a/osu.Game/Graphics/UserInterface/OsuTextBox.cs +++ b/osu.Game/Graphics/UserInterface/OsuTextBox.cs @@ -47,7 +47,7 @@ namespace osu.Game.Graphics.UserInterface private bool selectionStarted; private double sampleLastPlaybackTime; - private enum FeedbackSampleType + protected enum FeedbackSampleType { TextAdd, TextAddCaps, @@ -117,30 +117,30 @@ namespace osu.Game.Graphics.UserInterface return; if (added.Any(char.IsUpper) && AllowUniqueCharacterSamples) - playSample(FeedbackSampleType.TextAddCaps); + PlayFeedbackSample(FeedbackSampleType.TextAddCaps); else - playSample(FeedbackSampleType.TextAdd); + PlayFeedbackSample(FeedbackSampleType.TextAdd); } protected override void OnUserTextRemoved(string removed) { base.OnUserTextRemoved(removed); - playSample(FeedbackSampleType.TextRemove); + PlayFeedbackSample(FeedbackSampleType.TextRemove); } protected override void NotifyInputError() { base.NotifyInputError(); - playSample(FeedbackSampleType.TextInvalid); + PlayFeedbackSample(FeedbackSampleType.TextInvalid); } protected override void OnTextCommitted(bool textChanged) { base.OnTextCommitted(textChanged); - playSample(FeedbackSampleType.TextConfirm); + PlayFeedbackSample(FeedbackSampleType.TextConfirm); } protected override void OnCaretMoved(bool selecting) @@ -148,7 +148,7 @@ namespace osu.Game.Graphics.UserInterface base.OnCaretMoved(selecting); if (!selecting) - playSample(FeedbackSampleType.CaretMove); + PlayFeedbackSample(FeedbackSampleType.CaretMove); } protected override void OnTextSelectionChanged(TextSelectionType selectionType) @@ -158,15 +158,15 @@ namespace osu.Game.Graphics.UserInterface switch (selectionType) { case TextSelectionType.Character: - playSample(FeedbackSampleType.SelectCharacter); + PlayFeedbackSample(FeedbackSampleType.SelectCharacter); break; case TextSelectionType.Word: - playSample(selectionStarted ? FeedbackSampleType.SelectCharacter : FeedbackSampleType.SelectWord); + PlayFeedbackSample(selectionStarted ? FeedbackSampleType.SelectCharacter : FeedbackSampleType.SelectWord); break; case TextSelectionType.All: - playSample(FeedbackSampleType.SelectAll); + PlayFeedbackSample(FeedbackSampleType.SelectAll); break; } @@ -179,7 +179,7 @@ namespace osu.Game.Graphics.UserInterface if (!selectionStarted) return; - playSample(FeedbackSampleType.Deselect); + PlayFeedbackSample(FeedbackSampleType.Deselect); selectionStarted = false; } @@ -198,13 +198,13 @@ namespace osu.Game.Graphics.UserInterface case 1: // composition probably ended by pressing backspace, or was cancelled. - playSample(FeedbackSampleType.TextRemove); + PlayFeedbackSample(FeedbackSampleType.TextRemove); return; default: // longer text removed, composition ended because it was cancelled. // could be a different sample if desired. - playSample(FeedbackSampleType.TextRemove); + PlayFeedbackSample(FeedbackSampleType.TextRemove); return; } } @@ -212,7 +212,7 @@ namespace osu.Game.Graphics.UserInterface if (addedTextLength > 0) { // some text was added, probably due to typing new text or by changing the candidate. - playSample(FeedbackSampleType.TextAdd); + PlayFeedbackSample(FeedbackSampleType.TextAdd); return; } @@ -220,14 +220,14 @@ namespace osu.Game.Graphics.UserInterface { // text was probably removed by backspacing. // it's also possible that a candidate that only removed text was changed to. - playSample(FeedbackSampleType.TextRemove); + PlayFeedbackSample(FeedbackSampleType.TextRemove); return; } if (caretMoved) { // only the caret/selection was moved. - playSample(FeedbackSampleType.CaretMove); + PlayFeedbackSample(FeedbackSampleType.CaretMove); } } @@ -238,13 +238,13 @@ namespace osu.Game.Graphics.UserInterface if (successful) { // composition was successfully completed, usually by pressing the enter key. - playSample(FeedbackSampleType.TextConfirm); + PlayFeedbackSample(FeedbackSampleType.TextConfirm); } else { // composition was prematurely ended, eg. by clicking inside the textbox. // could be a different sample if desired. - playSample(FeedbackSampleType.TextConfirm); + PlayFeedbackSample(FeedbackSampleType.TextConfirm); } } @@ -283,7 +283,7 @@ namespace osu.Game.Graphics.UserInterface return samples[RNG.Next(0, samples.Length)]?.GetChannel(); } - private void playSample(FeedbackSampleType feedbackSample) => Schedule(() => + protected void PlayFeedbackSample(FeedbackSampleType feedbackSample) => Schedule(() => { if (Time.Current < sampleLastPlaybackTime + 15) return; From 241d33d415df0bae374889080a3ba0318937ec2f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 7 Sep 2022 14:04:51 +0900 Subject: [PATCH 310/420] Apply NRT to `BeatmapCarousel` --- osu.Game/Screens/Select/BeatmapCarousel.cs | 57 ++++++++++------------ 1 file changed, 27 insertions(+), 30 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 0f130714f1..3370dfbb4e 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.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. -#nullable disable - using System; using System.Collections.Generic; using System.Diagnostics; @@ -49,31 +47,31 @@ namespace osu.Game.Screens.Select /// /// Triggered when the loaded change and are completely loaded. /// - public Action BeatmapSetsChanged; + public Action? BeatmapSetsChanged; /// /// The currently selected beatmap. /// - public BeatmapInfo SelectedBeatmapInfo => selectedBeatmap?.BeatmapInfo; + public BeatmapInfo? SelectedBeatmapInfo => selectedBeatmap?.BeatmapInfo; - private CarouselBeatmap selectedBeatmap => selectedBeatmapSet?.Beatmaps.FirstOrDefault(s => s.State.Value == CarouselItemState.Selected); + private CarouselBeatmap? selectedBeatmap => selectedBeatmapSet?.Beatmaps.FirstOrDefault(s => s.State.Value == CarouselItemState.Selected); /// /// The currently selected beatmap set. /// - public BeatmapSetInfo SelectedBeatmapSet => selectedBeatmapSet?.BeatmapSet; + public BeatmapSetInfo? SelectedBeatmapSet => selectedBeatmapSet?.BeatmapSet; /// /// A function to optionally decide on a recommended difficulty from a beatmap set. /// - public Func, BeatmapInfo> GetRecommendedBeatmap; + public Func, BeatmapInfo>? GetRecommendedBeatmap; - private CarouselBeatmapSet selectedBeatmapSet; + private CarouselBeatmapSet? selectedBeatmapSet; /// /// Raised when the is changed. /// - public Action SelectionChanged; + public Action? SelectionChanged; public override bool HandleNonPositionalInput => AllowSelection; public override bool HandlePositionalInput => AllowSelection; @@ -151,15 +149,15 @@ namespace osu.Game.Screens.Select private CarouselRoot root; - private IDisposable subscriptionSets; - private IDisposable subscriptionDeletedSets; - private IDisposable subscriptionBeatmaps; - private IDisposable subscriptionHiddenBeatmaps; + private IDisposable? subscriptionSets; + private IDisposable? subscriptionDeletedSets; + private IDisposable? subscriptionBeatmaps; + private IDisposable? subscriptionHiddenBeatmaps; private readonly DrawablePool setPool = new DrawablePool(100); - private Sample spinSample; - private Sample randomSelectSample; + private Sample? spinSample; + private Sample? randomSelectSample; private int visibleSetsCount; @@ -200,7 +198,7 @@ namespace osu.Game.Screens.Select } [Resolved] - private RealmAccess realm { get; set; } + private RealmAccess realm { get; set; } = null!; protected override void LoadComplete() { @@ -215,7 +213,7 @@ namespace osu.Game.Screens.Select subscriptionHiddenBeatmaps = realm.RegisterForNotifications(r => r.All().Where(b => b.Hidden), beatmapsChanged); } - private void deletedBeatmapSetsChanged(IRealmCollection sender, ChangeSet changes, Exception error) + private void deletedBeatmapSetsChanged(IRealmCollection sender, ChangeSet? changes, Exception? error) { // If loading test beatmaps, avoid overwriting with realm subscription callbacks. if (loadedTestBeatmaps) @@ -228,7 +226,7 @@ namespace osu.Game.Screens.Select removeBeatmapSet(sender[i].ID); } - private void beatmapSetsChanged(IRealmCollection sender, ChangeSet changes, Exception error) + private void beatmapSetsChanged(IRealmCollection sender, ChangeSet? changes, Exception? error) { // If loading test beatmaps, avoid overwriting with realm subscription callbacks. if (loadedTestBeatmaps) @@ -304,7 +302,7 @@ namespace osu.Game.Screens.Select } } - private void beatmapsChanged(IRealmCollection sender, ChangeSet changes, Exception error) + private void beatmapsChanged(IRealmCollection sender, ChangeSet? changes, Exception? error) { // we only care about actual changes in hidden status. if (changes == null) @@ -367,7 +365,7 @@ namespace osu.Game.Screens.Select // check if we can/need to maintain our current selection. if (previouslySelectedID != null) - select((CarouselItem)newSet.Beatmaps.FirstOrDefault(b => b.BeatmapInfo.ID == previouslySelectedID) ?? newSet); + select((CarouselItem?)newSet.Beatmaps.FirstOrDefault(b => b.BeatmapInfo.ID == previouslySelectedID) ?? newSet); } itemsCache.Invalidate(); @@ -384,7 +382,7 @@ namespace osu.Game.Screens.Select /// The beatmap to select. /// Whether to select the beatmap even if it is filtered (i.e., not visible on carousel). /// True if a selection was made, False if it wasn't. - public bool SelectBeatmap(BeatmapInfo beatmapInfo, bool bypassFilters = true) + public bool SelectBeatmap(BeatmapInfo? beatmapInfo, bool bypassFilters = true) { // ensure that any pending events from BeatmapManager have been run before attempting a selection. Scheduler.Update(); @@ -549,7 +547,7 @@ namespace osu.Game.Screens.Select randomSelectSample?.Play(); } - private void select(CarouselItem item) + private void select(CarouselItem? item) { if (!AllowSelection) return; @@ -561,7 +559,7 @@ namespace osu.Game.Screens.Select private FilterCriteria activeCriteria = new FilterCriteria(); - protected ScheduledDelegate PendingFilter; + protected ScheduledDelegate? PendingFilter; public bool AllowSelection = true; @@ -593,7 +591,7 @@ namespace osu.Game.Screens.Select } } - public void Filter(FilterCriteria newCriteria, bool debounce = true) + public void Filter(FilterCriteria? newCriteria, bool debounce = true) { if (newCriteria != null) activeCriteria = newCriteria; @@ -796,7 +794,7 @@ namespace osu.Game.Screens.Select return (firstIndex, lastIndex); } - private CarouselBeatmapSet createCarouselSet(BeatmapSetInfo beatmapSet) + private CarouselBeatmapSet? createCarouselSet(BeatmapSetInfo beatmapSet) { // This can be moved to the realm query if required using: // .Filter("DeletePending == false && Protected == false && ANY Beatmaps.Hidden == false") @@ -962,7 +960,7 @@ namespace osu.Game.Screens.Select /// /// The item to be updated. /// For nested items, the parent of the item to be updated. - private void updateItem(DrawableCarouselItem item, DrawableCarouselItem parent = null) + private void updateItem(DrawableCarouselItem item, DrawableCarouselItem? parent = null) { Vector2 posInScroll = Scroll.ScrollContent.ToLocalSpace(item.Header.ScreenSpaceDrawQuad.Centre); float itemDrawY = posInScroll.Y - visibleUpperBound; @@ -990,8 +988,7 @@ namespace osu.Game.Screens.Select /// private class CarouselBoundsItem : CarouselItem { - public override DrawableCarouselItem CreateDrawableRepresentation() => - throw new NotImplementedException(); + public override DrawableCarouselItem CreateDrawableRepresentation() => throw new NotImplementedException(); } private class CarouselRoot : CarouselGroupEagerSelect @@ -1017,7 +1014,7 @@ namespace osu.Game.Screens.Select base.AddItem(i); } - public CarouselBeatmapSet RemoveChild(Guid beatmapSetID) + public CarouselBeatmapSet? RemoveChild(Guid beatmapSetID) { if (BeatmapSetsByID.TryGetValue(beatmapSetID, out var carouselBeatmapSet)) { @@ -1039,7 +1036,7 @@ namespace osu.Game.Screens.Select protected override void PerformSelection() { if (LastSelected == null || LastSelected.Filtered.Value) - carousel?.SelectNextRandom(); + carousel.SelectNextRandom(); else base.PerformSelection(); } From e18b524f8e8cd89e94d99d39d324cb0a4d556263 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 7 Sep 2022 14:08:07 +0900 Subject: [PATCH 311/420] Fix missing null checks on `selectedBeatmap` fields in `BeatmapCarousel` --- osu.Game/Screens/Select/BeatmapCarousel.cs | 30 +++++++++++++++------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 3370dfbb4e..e0dfbe2a1c 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -264,8 +264,11 @@ namespace osu.Game.Screens.Select foreach (int i in changes.InsertedIndices) UpdateBeatmapSet(sender[i].Detach()); - if (changes.DeletedIndices.Length > 0) + if (changes.DeletedIndices.Length > 0 && SelectedBeatmapInfo != null) { + // If SelectedBeatmapInfo is non-null, the set should also be non-null. + Debug.Assert(SelectedBeatmapSet != null); + // To handle the beatmap update flow, attempt to track selection changes across delete-insert transactions. // When an update occurs, the previous beatmap set is either soft or hard deleted. // Check if the current selection was potentially deleted by re-querying its validity. @@ -440,6 +443,9 @@ namespace osu.Game.Screens.Select private void selectNextSet(int direction, bool skipDifficulties) { + if (selectedBeatmap == null || selectedBeatmapSet == null) + return; + var unfilteredSets = beatmapSets.Where(s => !s.Filtered.Value).ToList(); var nextSet = unfilteredSets[(unfilteredSets.IndexOf(selectedBeatmapSet) + direction + unfilteredSets.Count) % unfilteredSets.Count]; @@ -452,7 +458,7 @@ namespace osu.Game.Screens.Select private void selectNextDifficulty(int direction) { - if (selectedBeatmap == null) + if (selectedBeatmap == null || selectedBeatmapSet == null) return; var unfilteredDifficulties = selectedBeatmapSet.Items.Where(s => !s.Filtered.Value).ToList(); @@ -481,7 +487,7 @@ namespace osu.Game.Screens.Select if (!visibleSets.Any()) return false; - if (selectedBeatmap != null) + if (selectedBeatmap != null && selectedBeatmapSet != null) { randomSelectedBeatmaps.Push(selectedBeatmap); @@ -524,11 +530,13 @@ namespace osu.Game.Screens.Select if (!beatmap.Filtered.Value) { - if (RandomAlgorithm.Value == RandomSelectAlgorithm.RandomPermutation) - previouslyVisitedRandomSets.Remove(selectedBeatmapSet); - if (selectedBeatmapSet != null) + { + if (RandomAlgorithm.Value == RandomSelectAlgorithm.RandomPermutation) + previouslyVisitedRandomSets.Remove(selectedBeatmapSet); + playSpinSample(distanceBetween(beatmap, selectedBeatmapSet)); + } select(beatmap); break; @@ -540,9 +548,13 @@ namespace osu.Game.Screens.Select private void playSpinSample(double distance) { - var chan = spinSample.GetChannel(); - chan.Frequency.Value = 1f + Math.Min(1f, distance / visibleSetsCount); - chan.Play(); + var chan = spinSample?.GetChannel(); + + if (chan != null) + { + chan.Frequency.Value = 1f + Math.Min(1f, distance / visibleSetsCount); + chan.Play(); + } randomSelectSample?.Play(); } From f3bda4e040196f38763001768922f3c2553b6dd8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 7 Sep 2022 14:27:24 +0900 Subject: [PATCH 312/420] Fix weird edge case of nullability in `CarouselRoot` A bit unfortunately, but it's what we get for having ctor level bindings.. --- osu.Game/Screens/Select/BeatmapCarousel.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index e0dfbe2a1c..a8cb06b888 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -1005,7 +1005,8 @@ namespace osu.Game.Screens.Select private class CarouselRoot : CarouselGroupEagerSelect { - private readonly BeatmapCarousel carousel; + // May only be null during construction (State.Value set causes PerformSelection to be triggered). + private readonly BeatmapCarousel? carousel; public readonly Dictionary BeatmapSetsByID = new Dictionary(); @@ -1048,7 +1049,7 @@ namespace osu.Game.Screens.Select protected override void PerformSelection() { if (LastSelected == null || LastSelected.Filtered.Value) - carousel.SelectNextRandom(); + carousel?.SelectNextRandom(); else base.PerformSelection(); } From 866bc553fe65a8720c82c804f36a7f97c6a3f30c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 7 Sep 2022 15:30:48 +0900 Subject: [PATCH 313/420] Tidy up `TestSceneStoryboard` --- .../Visual/Gameplay/TestSceneStoryboard.cs | 54 ++++++++----------- 1 file changed, 22 insertions(+), 32 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboard.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboard.cs index 2ec675874b..eaf22ba9cc 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboard.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboard.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. -#nullable disable - using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -24,8 +22,9 @@ namespace osu.Game.Tests.Visual.Gameplay [TestFixture] public class TestSceneStoryboard : OsuTestScene { - private Container storyboardContainer; - private DrawableStoryboard storyboard; + private Container storyboardContainer = null!; + + private DrawableStoryboard? storyboard; [Test] public void TestStoryboard() @@ -40,7 +39,7 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestStoryboardMissingVideo() { - AddStep("Load storyboard with missing video", loadStoryboardNoVideo); + AddStep("Load storyboard with missing video", () => loadStoryboard("storyboard_no_video.osu")); } [BackgroundDependencyLoader] @@ -77,18 +76,18 @@ namespace osu.Game.Tests.Visual.Gameplay Beatmap.BindValueChanged(beatmapChanged, true); } - private void beatmapChanged(ValueChangedEvent e) => loadStoryboard(e.NewValue); + private void beatmapChanged(ValueChangedEvent e) => loadStoryboard(e.NewValue.Storyboard); private void restart() { var track = Beatmap.Value.Track; track.Reset(); - loadStoryboard(Beatmap.Value); + loadStoryboard(Beatmap.Value.Storyboard); track.Start(); } - private void loadStoryboard(IWorkingBeatmap working) + private void loadStoryboard(Storyboard toLoad) { if (storyboard != null) storyboardContainer.Remove(storyboard, true); @@ -96,34 +95,25 @@ namespace osu.Game.Tests.Visual.Gameplay var decoupledClock = new DecoupleableInterpolatingFramedClock { IsCoupled = true }; storyboardContainer.Clock = decoupledClock; - storyboard = working.Storyboard.CreateDrawable(SelectedMods.Value); + storyboard = toLoad.CreateDrawable(SelectedMods.Value); storyboard.Passing = false; - storyboardContainer.Add(storyboard); - decoupledClock.ChangeSource(working.Track); - } - - private void loadStoryboardNoVideo() - { - if (storyboard != null) - storyboardContainer.Remove(storyboard, true); - - var decoupledClock = new DecoupleableInterpolatingFramedClock { IsCoupled = true }; - storyboardContainer.Clock = decoupledClock; - - Storyboard sb; - - using (var str = TestResources.OpenResource("storyboard_no_video.osu")) - using (var bfr = new LineBufferedReader(str)) - { - var decoder = new LegacyStoryboardDecoder(); - sb = decoder.Decode(bfr); - } - - storyboard = sb.CreateDrawable(SelectedMods.Value); - storyboardContainer.Add(storyboard); decoupledClock.ChangeSource(Beatmap.Value.Track); } + + private void loadStoryboard(string filename) + { + Storyboard loaded; + + using (var str = TestResources.OpenResource(filename)) + using (var bfr = new LineBufferedReader(str)) + { + var decoder = new LegacyStoryboardDecoder(); + loaded = decoder.Decode(bfr); + } + + loadStoryboard(loaded); + } } } From 258b8f015c9dfda22b7d1720c61347c7ba449411 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 7 Sep 2022 15:37:22 +0900 Subject: [PATCH 314/420] Add test coverage of storyboard using zero `VectorScale` --- .../Gameplay/TestSceneDrawableStoryboardSprite.cs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableStoryboardSprite.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableStoryboardSprite.cs index ca7d7b42d8..34723e3329 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableStoryboardSprite.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableStoryboardSprite.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. -#nullable disable - using System.Collections.Generic; using System.Linq; using NUnit.Framework; @@ -73,6 +71,17 @@ namespace osu.Game.Tests.Visual.Gameplay AddAssert("origin flipped", () => sprites.All(s => s.Origin == Anchor.BottomRight)); } + [Test] + public void TestZeroScale() + { + const string lookup_name = "hitcircleoverlay"; + + AddStep("allow skin lookup", () => storyboard.UseSkinSprites = true); + AddStep("create sprites", () => SetContents(_ => createSprite(lookup_name, Anchor.TopLeft, Vector2.Zero))); + AddStep("scale sprite", () => sprites.ForEach(s => s.VectorScale = new Vector2(0, 1))); + AddAssert("zero width", () => sprites.All(s => s.ScreenSpaceDrawQuad.Width == 0)); + } + [Test] public void TestNegativeScale() { From 824e68dab35f114c1960c5a5c30dacbbe3c99896 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 7 Sep 2022 15:16:20 +0900 Subject: [PATCH 315/420] Fix `VectorScale` set to 0 still showing some sprites in storyboard This implementation was matching the [framework side implementation of scale](https://github.com/ppy/osu-framework/blob/16d1c2d3356b0f59271fb49cff5fe8373ae9cdf3/osu.Framework/Graphics/Drawable.cs#L973-L976) but I don't think it's required here. I'm still not sure if the framework implementation is correct, but removing it locally does seem to fix broken storyboard cases. Closes https://github.com/ppy/osu/issues/20155. --- .../Storyboards/Drawables/DrawableStoryboardAnimation.cs | 5 ----- osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs | 5 ----- 2 files changed, 10 deletions(-) diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs index 9822f36620..f3187d77b7 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs @@ -56,11 +56,6 @@ namespace osu.Game.Storyboards.Drawables get => vectorScale; set { - if (Math.Abs(value.X) < Precision.FLOAT_EPSILON) - value.X = Precision.FLOAT_EPSILON; - if (Math.Abs(value.Y) < Precision.FLOAT_EPSILON) - value.Y = Precision.FLOAT_EPSILON; - if (vectorScale == value) return; diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs index 28ed2e65e3..b86b021d51 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs @@ -55,11 +55,6 @@ namespace osu.Game.Storyboards.Drawables get => vectorScale; set { - if (Math.Abs(value.X) < Precision.FLOAT_EPSILON) - value.X = Precision.FLOAT_EPSILON; - if (Math.Abs(value.Y) < Precision.FLOAT_EPSILON) - value.Y = Precision.FLOAT_EPSILON; - if (vectorScale == value) return; From b50116e9e4c29b33130712783e2625ee7a391d4e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 7 Sep 2022 14:40:51 +0900 Subject: [PATCH 316/420] Add missing null check in `BeatmapCarousel` tests --- .../Visual/SongSelect/TestSceneBeatmapCarousel.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs index c9e63fa621..0e72463d1e 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs @@ -173,7 +173,7 @@ namespace osu.Game.Tests.Visual.SongSelect if (isIterating) AddUntilStep("selection changed", () => !carousel.SelectedBeatmapInfo?.Equals(selection) == true); else - AddUntilStep("selection not changed", () => carousel.SelectedBeatmapInfo.Equals(selection)); + AddUntilStep("selection not changed", () => carousel.SelectedBeatmapInfo?.Equals(selection) == true); } } } @@ -382,7 +382,7 @@ namespace osu.Game.Tests.Visual.SongSelect // buffer the selection setSelected(3, 2); - AddStep("get search text", () => searchText = carousel.SelectedBeatmapSet.Metadata.Title); + AddStep("get search text", () => searchText = carousel.SelectedBeatmapSet!.Metadata.Title); setSelected(1, 3); @@ -701,7 +701,7 @@ namespace osu.Game.Tests.Visual.SongSelect setSelected(2, 1); AddAssert("Selection is non-null", () => currentSelection != null); - AddStep("Remove selected", () => carousel.RemoveBeatmapSet(carousel.SelectedBeatmapSet)); + AddStep("Remove selected", () => carousel.RemoveBeatmapSet(carousel.SelectedBeatmapSet!)); waitForSelection(2); AddStep("Remove first", () => carousel.RemoveBeatmapSet(carousel.BeatmapSets.First())); @@ -804,7 +804,7 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("filter to ruleset 0", () => carousel.Filter(new FilterCriteria { Ruleset = rulesets.AvailableRulesets.ElementAt(0) }, false)); AddStep("select filtered map skipping filtered", () => carousel.SelectBeatmap(testMixed.Beatmaps[1], false)); - AddAssert("unfiltered beatmap not selected", () => carousel.SelectedBeatmapInfo.Ruleset.OnlineID == 0); + AddAssert("unfiltered beatmap not selected", () => carousel.SelectedBeatmapInfo?.Ruleset.OnlineID == 0); AddStep("remove mixed set", () => { @@ -854,7 +854,7 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("Restore no filter", () => { carousel.Filter(new FilterCriteria(), false); - eagerSelectedIDs.Add(carousel.SelectedBeatmapSet.ID); + eagerSelectedIDs.Add(carousel.SelectedBeatmapSet!.ID); }); } @@ -899,10 +899,10 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("Restore different ruleset filter", () => { carousel.Filter(new FilterCriteria { Ruleset = rulesets.GetRuleset(1) }, false); - eagerSelectedIDs.Add(carousel.SelectedBeatmapSet.ID); + eagerSelectedIDs.Add(carousel.SelectedBeatmapSet!.ID); }); - AddAssert("selection changed", () => !carousel.SelectedBeatmapInfo.Equals(manySets.First().Beatmaps.First())); + AddAssert("selection changed", () => !carousel.SelectedBeatmapInfo!.Equals(manySets.First().Beatmaps.First())); } AddAssert("Selection was random", () => eagerSelectedIDs.Count > 2); From cb1bb99208d24c7d15f6adb64e289f87ae0116b8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 7 Sep 2022 16:43:48 +0900 Subject: [PATCH 317/420] Tidy up test logic --- ...estSceneHitEventTimingDistributionGraph.cs | 26 ++++++++++++++----- 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneHitEventTimingDistributionGraph.cs b/osu.Game.Tests/Visual/Ranking/TestSceneHitEventTimingDistributionGraph.cs index 4825cb7f80..198be4035b 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneHitEventTimingDistributionGraph.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneHitEventTimingDistributionGraph.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. -#nullable disable - using System; using System.Collections.Generic; using System.Linq; @@ -20,7 +18,7 @@ namespace osu.Game.Tests.Visual.Ranking { public class TestSceneHitEventTimingDistributionGraph : OsuTestScene { - private HitEventTimingDistributionGraph graph; + private HitEventTimingDistributionGraph graph = null!; private static readonly HitObject placeholder_object = new HitCircle(); @@ -63,7 +61,12 @@ namespace osu.Game.Tests.Visual.Ranking createTest(CreateDistributedHitEvents(0, 50).Select(h => { double offset = Math.Abs(h.TimeOffset); - var result = offset > 36 ? HitResult.Miss : offset > 32 ? HitResult.Meh : offset > 24 ? HitResult.Ok : offset > 16 ? HitResult.Good : offset > 8 ? HitResult.Great : HitResult.Perfect; + HitResult result = offset > 36 ? HitResult.Miss + : offset > 32 ? HitResult.Meh + : offset > 24 ? HitResult.Ok + : offset > 16 ? HitResult.Good + : offset > 8 ? HitResult.Great + : HitResult.Perfect; return new HitEvent(h.TimeOffset, result, placeholder_object, placeholder_object, null); }).ToList()); } @@ -74,13 +77,24 @@ namespace osu.Game.Tests.Visual.Ranking var wide = CreateDistributedHitEvents(0, 50).Select(h => { double offset = Math.Abs(h.TimeOffset); - var result = offset > 36 ? HitResult.Miss : offset > 32 ? HitResult.Meh : offset > 24 ? HitResult.Ok : offset > 16 ? HitResult.Good : offset > 8 ? HitResult.Great : HitResult.Perfect; + HitResult result = offset > 36 ? HitResult.Miss + : offset > 32 ? HitResult.Meh + : offset > 24 ? HitResult.Ok + : offset > 16 ? HitResult.Good + : offset > 8 ? HitResult.Great + : HitResult.Perfect; + return new HitEvent(h.TimeOffset, result, placeholder_object, placeholder_object, null); }); var narrow = CreateDistributedHitEvents(0, 50).Select(h => { double offset = Math.Abs(h.TimeOffset); - var result = offset > 25 ? HitResult.Miss : offset > 20 ? HitResult.Meh : offset > 15 ? HitResult.Ok : offset > 10 ? HitResult.Good : offset > 5 ? HitResult.Great : HitResult.Perfect; + HitResult result = offset > 25 ? HitResult.Miss + : offset > 20 ? HitResult.Meh + : offset > 15 ? HitResult.Ok + : offset > 10 ? HitResult.Good + : offset > 5 ? HitResult.Great + : HitResult.Perfect; return new HitEvent(h.TimeOffset, result, placeholder_object, placeholder_object, null); }); createTest(wide.Concat(narrow).ToList()); From 99ef0c95fec70b164c745f589fc8940726faf454 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 7 Sep 2022 16:51:51 +0900 Subject: [PATCH 318/420] Simplify children assignment --- .../Ranking/Statistics/HitEventTimingDistributionGraph.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs index d3c2716f8b..37765fe962 100644 --- a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs +++ b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs @@ -252,7 +252,7 @@ namespace osu.Game.Screens.Ranking.Statistics if (values.Any()) { - boxOriginals = values.Select((v, i) => new Circle + InternalChildren = boxOriginals = values.Select((v, i) => new Circle { RelativeSizeAxes = Axes.Both, Anchor = Anchor.BottomCentre, @@ -260,7 +260,6 @@ namespace osu.Game.Screens.Ranking.Statistics Colour = isCentre && i == 0 ? Color4.White : v.Colour, Height = 0, }).ToArray(); - InternalChildren = boxOriginals.Reverse().ToArray(); } else { From b5b66de3c9c8a3a99b6e9ffd6d4994a3fa6e6b61 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 7 Sep 2022 17:45:15 +0900 Subject: [PATCH 319/420] Fix target mod crashing if beatmap is played with a break after all hitobjects Closes https://github.com/ppy/osu/issues/20161. --- osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs index 5e2a92e5e9..861ad80b7f 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs @@ -362,10 +362,12 @@ namespace osu.Game.Rulesets.Osu.Mods { return breaks.Any(breakPeriod => { - var firstObjAfterBreak = originalHitObjects.First(obj => almostBigger(obj.StartTime, breakPeriod.EndTime)); + OsuHitObject? firstObjAfterBreak = originalHitObjects.FirstOrDefault(obj => almostBigger(obj.StartTime, breakPeriod.EndTime)); return almostBigger(time, breakPeriod.StartTime) - && definitelyBigger(firstObjAfterBreak.StartTime, time); + // There should never really be a break section with no objects after it, but we've seen crashes from users with malformed beatmaps, + // so it's best to guard against this. + && (firstObjAfterBreak == null || definitelyBigger(firstObjAfterBreak.StartTime, time)); }); } From 75d0deef72ab44b075345f6f4aeddde4d1a52492 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 7 Sep 2022 17:38:00 +0900 Subject: [PATCH 320/420] Apply proposed changes to remove inheritance from `MasterGameplayClockContainer` --- .../NonVisual/GameplayClockContainerTest.cs | 6 +- .../Rulesets/UI/FrameStabilityContainer.cs | 7 ++- .../Screens/Play/GameplayClockContainer.cs | 5 +- .../Screens/Play/GameplayClockExtensions.cs | 8 ++- osu.Game/Screens/Play/IGameplayClock.cs | 6 +- .../Play/MasterGameplayClockContainer.cs | 57 +------------------ osu.Game/Screens/Play/Player.cs | 7 ++- 7 files changed, 24 insertions(+), 72 deletions(-) diff --git a/osu.Game.Tests/NonVisual/GameplayClockContainerTest.cs b/osu.Game.Tests/NonVisual/GameplayClockContainerTest.cs index 95bf1ab354..80f0aaeb55 100644 --- a/osu.Game.Tests/NonVisual/GameplayClockContainerTest.cs +++ b/osu.Game.Tests/NonVisual/GameplayClockContainerTest.cs @@ -1,8 +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 NUnit.Framework; +using osu.Framework.Audio; +using osu.Framework.Bindables; using osu.Framework.Timing; using osu.Game.Screens.Play; @@ -23,11 +24,10 @@ namespace osu.Game.Tests.NonVisual private class TestGameplayClockContainer : GameplayClockContainer { - public override IEnumerable GameplayAdjustments => new[] { 2.0 }; - public TestGameplayClockContainer(IFrameBasedClock underlyingClock) : base(underlyingClock) { + GameplayAdjustments.AddAdjustment(AdjustableProperty.Frequency, new BindableDouble(2.0)); } } } diff --git a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs index 4f4a2d908d..f0c7a398eb 100644 --- a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs +++ b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs @@ -2,10 +2,9 @@ // 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 osu.Framework.Allocation; +using osu.Framework.Audio; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -264,7 +263,9 @@ namespace osu.Game.Rulesets.UI public double StartTime => parentGameplayClock?.StartTime ?? 0; - public IEnumerable GameplayAdjustments => parentGameplayClock?.GameplayAdjustments ?? Enumerable.Empty(); + private readonly AudioAdjustments gameplayAdjustments = new AudioAdjustments(); + + public IAdjustableAudioComponent GameplayAdjustments => parentGameplayClock?.GameplayAdjustments ?? gameplayAdjustments; #endregion diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index 5dfaf2d584..e64c628fa0 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -2,9 +2,8 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Collections.Generic; -using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Audio; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -45,7 +44,7 @@ namespace osu.Game.Screens.Play /// public double StartTime { get; protected set; } - public virtual IEnumerable GameplayAdjustments => Enumerable.Empty(); + public IAdjustableAudioComponent GameplayAdjustments { get; } = new AudioAdjustments(); private readonly BindableBool isPaused = new BindableBool(true); diff --git a/osu.Game/Screens/Play/GameplayClockExtensions.cs b/osu.Game/Screens/Play/GameplayClockExtensions.cs index b683c61f63..3cc12f7afe 100644 --- a/osu.Game/Screens/Play/GameplayClockExtensions.cs +++ b/osu.Game/Screens/Play/GameplayClockExtensions.cs @@ -13,10 +13,12 @@ namespace osu.Game.Screens.Play /// public static double GetTrueGameplayRate(this IGameplayClock clock) { + // To handle rewind, we still want to maintain the same direction as the underlying clock. double rate = Math.Sign(clock.Rate); - foreach (double a in clock.GameplayAdjustments) - rate *= a; - return rate; + + return rate + * clock.GameplayAdjustments.AggregateFrequency.Value + * clock.GameplayAdjustments.AggregateTempo.Value; } } } diff --git a/osu.Game/Screens/Play/IGameplayClock.cs b/osu.Game/Screens/Play/IGameplayClock.cs index 7c50b9d407..c58d2dbcac 100644 --- a/osu.Game/Screens/Play/IGameplayClock.cs +++ b/osu.Game/Screens/Play/IGameplayClock.cs @@ -1,7 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System.Collections.Generic; +using osu.Framework.Audio; using osu.Framework.Bindables; using osu.Framework.Timing; @@ -19,9 +19,9 @@ namespace osu.Game.Screens.Play double StartTime { get; } /// - /// All adjustments applied to this clock which don't come from gameplay or mods. + /// All adjustments applied to this clock which come from gameplay or mods. /// - IEnumerable GameplayAdjustments { get; } + IAdjustableAudioComponent GameplayAdjustments { get; } IBindable IsPaused { get; } } diff --git a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs index 7c30f86125..226ce8b0d8 100644 --- a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs +++ b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs @@ -2,13 +2,11 @@ // 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; 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.Beatmaps.ControlPoints; @@ -25,7 +23,7 @@ namespace osu.Game.Screens.Play /// /// This is intended to be used as a single controller for gameplay, or as a reference source for other s. /// - public class MasterGameplayClockContainer : GameplayClockContainer, IBeatSyncProvider, IAdjustableAudioComponent + public class MasterGameplayClockContainer : GameplayClockContainer, IBeatSyncProvider { /// /// Duration before gameplay start time required before skip button displays. @@ -57,11 +55,6 @@ namespace osu.Game.Screens.Play /// private double? actualStopTime; - /// - /// Maintained solely to delegate pieces to (to maintain parent lookups). - /// - private readonly AudioContainer audioContainer; - /// /// Create a new master gameplay clock container. /// @@ -75,8 +68,6 @@ namespace osu.Game.Screens.Play this.skipTargetTime = skipTargetTime; StartTime = findEarliestStartTime(); - - AddInternal(audioContainer = new AudioContainer()); } private double findEarliestStartTime() @@ -202,6 +193,7 @@ namespace osu.Game.Screens.Play if (speedAdjustmentsApplied) return; + track.BindAdjustments(GameplayAdjustments); track.AddAdjustment(AdjustableProperty.Frequency, GameplayClock.ExternalPauseFrequencyAdjust); track.AddAdjustment(AdjustableProperty.Tempo, UserPlaybackRate); @@ -213,6 +205,7 @@ namespace osu.Game.Screens.Play if (!speedAdjustmentsApplied) return; + track.UnbindAdjustments(GameplayAdjustments); track.RemoveAdjustment(AdjustableProperty.Frequency, GameplayClock.ExternalPauseFrequencyAdjust); track.RemoveAdjustment(AdjustableProperty.Tempo, UserPlaybackRate); @@ -229,49 +222,5 @@ namespace osu.Game.Screens.Play IClock IBeatSyncProvider.Clock => this; ChannelAmplitudes IHasAmplitudes.CurrentAmplitudes => beatmap.TrackLoaded ? beatmap.Track.CurrentAmplitudes : ChannelAmplitudes.Empty; - - private readonly List> speedAdjustments = new List>(); - - public override IEnumerable GameplayAdjustments => speedAdjustments.Select(bindable => bindable.Value); - - void IAdjustableAudioComponent.AddAdjustment(AdjustableProperty type, IBindable adjustBindable) - { - speedAdjustments.Add(adjustBindable); - track.AddAdjustment(type, adjustBindable); - } - - void IAdjustableAudioComponent.RemoveAdjustment(AdjustableProperty type, IBindable adjustBindable) - { - speedAdjustments.Remove(adjustBindable); - track.RemoveAdjustment(type, adjustBindable); - } - - void IAdjustableAudioComponent.RemoveAllAdjustments(AdjustableProperty type) => audioContainer.RemoveAllAdjustments(type); - - void IAdjustableAudioComponent.BindAdjustments(IAggregateAudioAdjustment component) => audioContainer.BindAdjustments(component); - - void IAdjustableAudioComponent.UnbindAdjustments(IAggregateAudioAdjustment component) => audioContainer.UnbindAdjustments(component); - - BindableNumber IAdjustableAudioComponent.Volume => audioContainer.Volume; - - BindableNumber IAdjustableAudioComponent.Balance => audioContainer.Balance; - - BindableNumber IAdjustableAudioComponent.Frequency => audioContainer.Frequency; - - BindableNumber IAdjustableAudioComponent.Tempo => audioContainer.Tempo; - - public override void ResetSpeedAdjustments() - { - track.RemoveAllAdjustments(AdjustableProperty.Frequency); - track.RemoveAllAdjustments(AdjustableProperty.Tempo); - } - - IBindable IAggregateAudioAdjustment.AggregateVolume => audioContainer.AggregateVolume; - - IBindable IAggregateAudioAdjustment.AggregateBalance => audioContainer.AggregateBalance; - - IBindable IAggregateAudioAdjustment.AggregateFrequency => audioContainer.AggregateFrequency; - - IBindable IAggregateAudioAdjustment.AggregateTempo => audioContainer.AggregateTempo; } } diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 21a02fbe0b..17cae05862 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -999,11 +999,12 @@ namespace osu.Game.Screens.Play // 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... - if (GameplayClockContainer is IAdjustableAudioComponent adjustableClock) + if (GameplayClockContainer is MasterGameplayClockContainer masterClock) { - GameplayClockContainer.ResetSpeedAdjustments(); + musicController.ResetTrackAdjustments(); + foreach (var mod in GameplayState.Mods.OfType()) - mod.ApplyToTrack(adjustableClock); + mod.ApplyToTrack(masterClock.GameplayAdjustments); } updateGameplayState(); From fa15502384f86d6a323cf999eb6692497beb8a7e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 7 Sep 2022 19:12:16 +0900 Subject: [PATCH 321/420] Move full track adjustment flow inside `MasterGameplayClockContainer` --- .../Screens/Play/MasterGameplayClockContainer.cs | 7 +++++++ osu.Game/Screens/Play/Player.cs | 12 ++---------- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs index 226ce8b0d8..26fb127e83 100644 --- a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs +++ b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs @@ -3,6 +3,7 @@ using System; using System.Linq; +using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Track; using osu.Framework.Bindables; @@ -10,6 +11,7 @@ using osu.Framework.Graphics; using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Overlays; namespace osu.Game.Screens.Play { @@ -55,6 +57,9 @@ namespace osu.Game.Screens.Play ///
private double? actualStopTime; + [Resolved] + private MusicController musicController { get; set; } = null!; + /// /// Create a new master gameplay clock container. /// @@ -193,6 +198,8 @@ namespace osu.Game.Screens.Play if (speedAdjustmentsApplied) return; + musicController.ResetTrackAdjustments(); + track.BindAdjustments(GameplayAdjustments); track.AddAdjustment(AdjustableProperty.Frequency, GameplayClock.ExternalPauseFrequencyAdjust); track.AddAdjustment(AdjustableProperty.Tempo, UserPlaybackRate); diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 17cae05862..e93502da13 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -996,16 +996,8 @@ namespace osu.Game.Screens.Play foreach (var mod in GameplayState.Mods.OfType()) mod.ApplyToHUD(HUDOverlay); - // 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... - if (GameplayClockContainer is MasterGameplayClockContainer masterClock) - { - musicController.ResetTrackAdjustments(); - - foreach (var mod in GameplayState.Mods.OfType()) - mod.ApplyToTrack(masterClock.GameplayAdjustments); - } + foreach (var mod in GameplayState.Mods.OfType()) + mod.ApplyToTrack(GameplayClockContainer.GameplayAdjustments); updateGameplayState(); From 1be3b74ff33367af48d34c7e42fb0bd7b530c94c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 7 Sep 2022 19:12:34 +0900 Subject: [PATCH 322/420] Fix multiplayer spectator not getting gameplay adjustments applied --- .../Spectate/MultiSpectatorPlayer.cs | 11 ++++++++++- .../Spectate/MultiSpectatorScreen.cs | 19 +++++++++++++++++++ .../Spectate/SpectatorPlayerClock.cs | 3 +++ 3 files changed, 32 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorPlayer.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorPlayer.cs index d351d121c6..7e910b7946 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorPlayer.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorPlayer.cs @@ -53,6 +53,15 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate } protected override GameplayClockContainer CreateGameplayClockContainer(WorkingBeatmap beatmap, double gameplayStart) - => new GameplayClockContainer(spectatorPlayerClock); + { + var gameplayClockContainer = new GameplayClockContainer(spectatorPlayerClock); + + // Directionality is important, as BindAdjustments is... not actually a bidirectional bind... + // We want to ensure that any adjustments applied by the Player instance are applied to the SpectatorPlayerClock + // so they can be consumed by the spectator screen (and applied to the master clock / track). + spectatorPlayerClock.GameplayAdjustments.BindAdjustments(gameplayClockContainer.GameplayAdjustments); + + return gameplayClockContainer; + } } } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs index c2ece90472..b7c07372dc 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs @@ -4,6 +4,7 @@ using System; using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Audio; using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -43,6 +44,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate [Resolved] private MultiplayerClient multiplayerClient { get; set; } = null!; + private AudioAdjustments? boundAdjustments; + private readonly PlayerArea[] instances; private MasterGameplayClockContainer masterClockContainer = null!; private SpectatorSyncManager syncManager = null!; @@ -157,6 +160,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate base.LoadComplete(); masterClockContainer.Reset(); + + // Start with adjustments from the first player to keep a sane state. + bindAudioAdjustments(instances.First()); } protected override void Update() @@ -169,11 +175,24 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate .OrderBy(i => Math.Abs(i.SpectatorPlayerClock.CurrentTime - syncManager.CurrentMasterTime)) .FirstOrDefault(); + // Only bind adjustments if there's actually a valid source, else just use the previous ones to ensure no sudden changes to audio. + if (currentAudioSource != null) + bindAudioAdjustments(currentAudioSource); + foreach (var instance in instances) instance.Mute = instance != currentAudioSource; } } + private void bindAudioAdjustments(PlayerArea first) + { + if (boundAdjustments != null) + masterClockContainer.GameplayAdjustments.UnbindAdjustments(boundAdjustments); + + boundAdjustments = first.SpectatorPlayerClock.GameplayAdjustments; + masterClockContainer.GameplayAdjustments.BindAdjustments(boundAdjustments); + } + private bool isCandidateAudioSource(SpectatorPlayerClock? clock) => clock?.IsRunning == true && !clock.IsCatchingUp && !clock.WaitingOnFrames; diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/SpectatorPlayerClock.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/SpectatorPlayerClock.cs index 45615d4e19..5667be1f4b 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/SpectatorPlayerClock.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/SpectatorPlayerClock.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using osu.Framework.Audio; using osu.Framework.Timing; using osu.Game.Screens.Play; @@ -19,6 +20,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate private readonly GameplayClockContainer masterClock; + public readonly AudioAdjustments GameplayAdjustments = new AudioAdjustments(); + public double CurrentTime { get; private set; } /// From e6b449fe0b602a76a7d61efb5bff0a609fb241cd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 7 Sep 2022 19:23:44 +0900 Subject: [PATCH 323/420] Fix case of zero rate calculating a zero true gameplay rate --- osu.Game/Screens/Play/GameplayClockExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/GameplayClockExtensions.cs b/osu.Game/Screens/Play/GameplayClockExtensions.cs index 3cc12f7afe..ec77a94ce9 100644 --- a/osu.Game/Screens/Play/GameplayClockExtensions.cs +++ b/osu.Game/Screens/Play/GameplayClockExtensions.cs @@ -14,7 +14,7 @@ namespace osu.Game.Screens.Play public static double GetTrueGameplayRate(this IGameplayClock clock) { // To handle rewind, we still want to maintain the same direction as the underlying clock. - double rate = Math.Sign(clock.Rate); + double rate = clock.Rate == 0 ? 1 : Math.Sign(clock.Rate); return rate * clock.GameplayAdjustments.AggregateFrequency.Value From cb9bae1f5c87ac68fa621a7652db9a9e3dea11df Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 7 Sep 2022 19:05:53 +0900 Subject: [PATCH 324/420] Enable NRT --- .../Match/MultiplayerMatchSettingsOverlay.cs | 56 +++++++++---------- .../Playlists/PlaylistsSongSelect.cs | 2 - 2 files changed, 28 insertions(+), 30 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs index 3d6127e8e7..c2dafda592 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs @@ -1,12 +1,10 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.ComponentModel; using System.Diagnostics; -using JetBrains.Annotations; +using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions; @@ -15,6 +13,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Screens; +using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; @@ -30,12 +29,12 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match { public class MultiplayerMatchSettingsOverlay : RoomSettingsOverlay { - private MatchSettings settings; + private MatchSettings settings = null!; protected override OsuButton SubmitButton => settings.ApplyButton; [Resolved] - private OngoingOperationTracker ongoingOperationTracker { get; set; } + private OngoingOperationTracker ongoingOperationTracker { get; set; } = null!; protected override bool IsLoading => ongoingOperationTracker.InProgress.Value; @@ -57,20 +56,24 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match { private const float disabled_alpha = 0.2f; - public Action SettingsApplied; + public Action? SettingsApplied; - public OsuTextBox NameField, MaxParticipantsField; - public MatchTypePicker TypePicker; - public OsuEnumDropdown QueueModeDropdown; - public OsuTextBox PasswordTextBox; - public OsuCheckbox AutoSkipCheckbox; - public TriangleButton ApplyButton; + public OsuTextBox NameField = null!; + public OsuTextBox MaxParticipantsField = null!; + public MatchTypePicker TypePicker = null!; + public OsuEnumDropdown QueueModeDropdown = null!; + public OsuTextBox PasswordTextBox = null!; + public OsuCheckbox AutoSkipCheckbox = null!; + public TriangleButton ApplyButton = null!; - public OsuSpriteText ErrorText; + public OsuSpriteText ErrorText = null!; - private OsuEnumDropdown startModeDropdown; - private OsuSpriteText typeLabel; - private LoadingLayer loadingLayer; + private OsuEnumDropdown startModeDropdown = null!; + private OsuSpriteText typeLabel = null!; + private LoadingLayer loadingLayer = null!; + + [Resolved] + private BeatmapManager beatmapManager { get; set; } = null!; public void SelectBeatmap() { @@ -79,26 +82,23 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match } [Resolved] - private MultiplayerMatchSubScreen matchSubScreen { get; set; } + private MultiplayerMatchSubScreen matchSubScreen { get; set; } = null!; [Resolved] - private IRoomManager manager { get; set; } + private IRoomManager manager { get; set; } = null!; [Resolved] - private MultiplayerClient client { get; set; } + private MultiplayerClient client { get; set; } = null!; [Resolved] - private OngoingOperationTracker ongoingOperationTracker { get; set; } + private OngoingOperationTracker ongoingOperationTracker { get; set; } = null!; private readonly IBindable operationInProgress = new BindableBool(); - - [CanBeNull] - private IDisposable applyingSettingsOperation; - private readonly Room room; - private Drawable playlistContainer; - private DrawableRoomPlaylist drawablePlaylist; + private IDisposable? applyingSettingsOperation; + private Drawable playlistContainer = null!; + private DrawableRoomPlaylist drawablePlaylist = null!; public MatchSettings(Room room) { @@ -423,7 +423,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match else room.MaxParticipants.Value = null; - manager?.CreateRoom(room, onSuccess, onError); + manager.CreateRoom(room, onSuccess, onError); } } @@ -466,7 +466,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match public class CreateOrUpdateButton : TriangleButton { [Resolved(typeof(Room), nameof(Room.RoomID))] - private Bindable roomId { get; set; } + private Bindable roomId { get; set; } = null!; protected override void LoadComplete() { diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsSongSelect.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsSongSelect.cs index 73765fc661..e3f7b5dfc4 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsSongSelect.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsSongSelect.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. -#nullable disable - using System.Linq; using osu.Framework.Screens; using osu.Game.Online.API; From 770c1ade2fee41abc436f38dc24685c5c8560a25 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 7 Sep 2022 20:00:24 +0900 Subject: [PATCH 325/420] Add test coverage of track rate adjusting during multi spectator --- .../TestSceneMultiSpectatorScreen.cs | 21 ++++++++++++++++--- .../Multiplayer/TestMultiplayerClient.cs | 7 ++++--- .../Visual/Spectator/TestSpectatorClient.cs | 8 ++++++- 3 files changed, 29 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs index a11a67aebd..70f498e7f2 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs @@ -13,9 +13,11 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Configuration; +using osu.Game.Online.API; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Multiplayer; using osu.Game.Online.Multiplayer.MatchTypes.TeamVersus; +using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.UI; using osu.Game.Screens.OnlinePlay.Multiplayer.Spectate; using osu.Game.Screens.Play; @@ -332,6 +334,18 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("player 2 playing from correct point in time", () => getPlayer(PLAYER_2_ID).ChildrenOfType().Single().FrameStableClock.CurrentTime > 30000); } + [Test] + public void TestGameplayRateAdjust() + { + start(getPlayerIds(4), mods: new[] { new APIMod(new OsuModDoubleTime()) }); + + loadSpectateScreen(); + + sendFrames(getPlayerIds(4), 300); + + AddUntilStep("wait for correct track speed", () => Beatmap.Value.Track.Rate, () => Is.EqualTo(1.5)); + } + [Test] public void TestPlayersLeaveWhileSpectating() { @@ -420,7 +434,7 @@ namespace osu.Game.Tests.Visual.Multiplayer private void start(int userId, int? beatmapId = null) => start(new[] { userId }, beatmapId); - private void start(int[] userIds, int? beatmapId = null) + private void start(int[] userIds, int? beatmapId = null, APIMod[]? mods = null) { AddStep("start play", () => { @@ -429,10 +443,11 @@ namespace osu.Game.Tests.Visual.Multiplayer var user = new MultiplayerRoomUser(id) { User = new APIUser { Id = id }, + Mods = mods ?? Array.Empty(), }; - OnlinePlayDependencies.MultiplayerClient.AddUser(user.User, true); - SpectatorClient.SendStartPlay(id, beatmapId ?? importedBeatmapId); + OnlinePlayDependencies.MultiplayerClient.AddUser(user, true); + SpectatorClient.SendStartPlay(id, beatmapId ?? importedBeatmapId, mods); playingUsers.Add(user); } diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs index 19b887eea5..84737bce3f 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs @@ -81,13 +81,14 @@ namespace osu.Game.Tests.Visual.Multiplayer public void Disconnect() => isConnected.Value = false; public MultiplayerRoomUser AddUser(APIUser user, bool markAsPlaying = false) - { - var roomUser = new MultiplayerRoomUser(user.Id) { User = user }; + => AddUser(new MultiplayerRoomUser(user.Id) { User = user }, markAsPlaying); + public MultiplayerRoomUser AddUser(MultiplayerRoomUser roomUser, bool markAsPlaying = false) + { addUser(roomUser); if (markAsPlaying) - PlayingUserIds.Add(user.Id); + PlayingUserIds.Add(roomUser.UserID); return roomUser; } diff --git a/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs b/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs index 2531f3c485..cb3711ebb5 100644 --- a/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs +++ b/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs @@ -37,6 +37,7 @@ namespace osu.Game.Tests.Visual.Spectator private readonly Dictionary lastReceivedUserFrames = new Dictionary(); private readonly Dictionary userBeatmapDictionary = new Dictionary(); + private readonly Dictionary userModsDictionary = new Dictionary(); private readonly Dictionary userNextFrameDictionary = new Dictionary(); [Resolved] @@ -52,9 +53,11 @@ namespace osu.Game.Tests.Visual.Spectator /// /// The user to start play for. /// The playing beatmap id. - public void SendStartPlay(int userId, int beatmapId) + /// The mods the user has applied. + public void SendStartPlay(int userId, int beatmapId, APIMod[]? mods = null) { userBeatmapDictionary[userId] = beatmapId; + userModsDictionary[userId] = mods ?? Array.Empty(); userNextFrameDictionary[userId] = 0; sendPlayingState(userId); } @@ -73,10 +76,12 @@ namespace osu.Game.Tests.Visual.Spectator { BeatmapID = userBeatmapDictionary[userId], RulesetID = 0, + Mods = userModsDictionary[userId], State = state }); userBeatmapDictionary.Remove(userId); + userModsDictionary.Remove(userId); } /// @@ -158,6 +163,7 @@ namespace osu.Game.Tests.Visual.Spectator { BeatmapID = userBeatmapDictionary[userId], RulesetID = 0, + Mods = userModsDictionary[userId], State = SpectatedUserState.Playing }); } From 208bd0f3914d77847fa49ed8815f67490d80b05c Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 7 Sep 2022 20:01:17 +0900 Subject: [PATCH 326/420] Give OnlinePlaySongSelect a reference PlaylistItem --- .../TestSceneMultiplayerMatchSongSelect.cs | 4 +- .../Match/MultiplayerMatchSettingsOverlay.cs | 5 -- .../Multiplayer/MultiplayerMatchSongSelect.cs | 23 ++---- .../Multiplayer/MultiplayerMatchSubScreen.cs | 14 +--- .../OnlinePlay/OnlinePlaySongSelect.cs | 72 +++++++++++++------ 5 files changed, 58 insertions(+), 60 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs index 2281235f25..0ecfc059e4 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs @@ -152,8 +152,8 @@ namespace osu.Game.Tests.Visual.Multiplayer public new BeatmapCarousel Carousel => base.Carousel; - public TestMultiplayerMatchSongSelect(Room room, WorkingBeatmap beatmap = null, RulesetInfo ruleset = null) - : base(room, null, beatmap, ruleset) + public TestMultiplayerMatchSongSelect(Room room) + : base(room) { } } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs index c2dafda592..75bd6eb04d 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs @@ -4,7 +4,6 @@ using System; using System.ComponentModel; using System.Diagnostics; -using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions; @@ -13,7 +12,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Screens; -using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; @@ -72,9 +70,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match private OsuSpriteText typeLabel = null!; private LoadingLayer loadingLayer = null!; - [Resolved] - private BeatmapManager beatmapManager { get; set; } = null!; - public void SelectBeatmap() { if (matchSubScreen.IsCurrentScreen()) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs index dbd679104e..3fe236bd7a 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs @@ -8,11 +8,9 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Logging; using osu.Framework.Screens; -using osu.Game.Beatmaps; using osu.Game.Graphics.UserInterface; using osu.Game.Online.Multiplayer; using osu.Game.Online.Rooms; -using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Screens.Select; @@ -27,7 +25,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer private OngoingOperationTracker operationTracker { get; set; } = null!; private readonly IBindable operationInProgress = new Bindable(); - private readonly long? itemToEdit; + private readonly PlaylistItem? itemToEdit; private LoadingLayer loadingLayer = null!; private IDisposable? selectionOperation; @@ -37,21 +35,10 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer /// /// The room. /// The item to be edited. May be null, in which case a new item will be added to the playlist. - /// An optional initial beatmap selection to perform. - /// An optional initial ruleset selection to perform. - public MultiplayerMatchSongSelect(Room room, long? itemToEdit = null, WorkingBeatmap? beatmap = null, RulesetInfo? ruleset = null) - : base(room) + public MultiplayerMatchSongSelect(Room room, PlaylistItem? itemToEdit = null) + : base(room, itemToEdit) { this.itemToEdit = itemToEdit; - - if (beatmap != null || ruleset != null) - { - Schedule(() => - { - if (beatmap != null) Beatmap.Value = beatmap; - if (ruleset != null) Ruleset.Value = ruleset; - }); - } } [BackgroundDependencyLoader] @@ -80,7 +67,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer { if (operationInProgress.Value) { - Logger.Log($"{nameof(SelectedItem)} aborted due to {nameof(operationInProgress)}"); + Logger.Log($"{nameof(SelectItem)} aborted due to {nameof(operationInProgress)}"); return false; } @@ -92,7 +79,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer var multiplayerItem = new MultiplayerPlaylistItem { - ID = itemToEdit ?? 0, + ID = itemToEdit?.ID ?? 0, BeatmapID = item.Beatmap.OnlineID, BeatmapChecksum = item.Beatmap.MD5Hash, RulesetID = item.RulesetID, diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index ceadfa1527..db752f2b42 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -49,11 +49,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer [Resolved] private MultiplayerClient client { get; set; } - [Resolved] - private BeatmapManager beatmapManager { get; set; } - - private readonly IBindable isConnected = new Bindable(); - private AddItemButton addItemButton; public MultiplayerMatchSubScreen(Room room) @@ -227,12 +222,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer if (!this.IsCurrentScreen()) return; - int id = itemToEdit?.Beatmap.OnlineID ?? Room.Playlist.Last().Beatmap.OnlineID; - var localBeatmap = beatmapManager.QueryBeatmap(b => b.OnlineID == id); - - var workingBeatmap = localBeatmap == null ? null : beatmapManager.GetWorkingBeatmap(localBeatmap); - - this.Push(new MultiplayerMatchSongSelect(Room, itemToEdit?.ID, workingBeatmap)); + this.Push(new MultiplayerMatchSongSelect(Room, itemToEdit)); } protected override Drawable CreateFooter() => new MultiplayerMatchFooter(); @@ -424,7 +414,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer return; } - this.Push(new MultiplayerMatchSongSelect(Room, client.Room.Settings.PlaylistItemId, beatmap, ruleset)); + this.Push(new MultiplayerMatchSongSelect(Room, Room.Playlist.Single(item => item.ID == client.Room.Settings.PlaylistItemId))); } protected override void Dispose(bool isDisposing) diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs b/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs index f26480909e..e841d9c41e 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs @@ -1,13 +1,11 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using Humanizer; -using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -35,32 +33,39 @@ namespace osu.Game.Screens.OnlinePlay public override bool AllowEditing => false; [Resolved(typeof(Room), nameof(Room.Playlist))] - protected BindableList Playlist { get; private set; } - - [CanBeNull] - [Resolved(CanBeNull = true)] - protected IBindable SelectedItem { get; private set; } + protected BindableList Playlist { get; private set; } = null!; [Resolved] - private RulesetStore rulesets { get; set; } + private RulesetStore rulesets { get; set; } = null!; + + [Resolved] + private BeatmapManager beatmapManager { get; set; } = null!; protected override UserActivity InitialActivity => new UserActivity.InLobby(room); protected readonly Bindable> FreeMods = new Bindable>(Array.Empty()); private readonly Room room; + private readonly PlaylistItem? initialItem; + private readonly FreeModSelectOverlay freeModSelectOverlay; - private WorkingBeatmap initialBeatmap; - private RulesetInfo initialRuleset; - private IReadOnlyList initialMods; + private WorkingBeatmap initialBeatmap = null!; + private RulesetInfo initialRuleset = null!; + private IReadOnlyList initialMods = null!; private bool itemSelected; - private readonly FreeModSelectOverlay freeModSelectOverlay; - private IDisposable freeModSelectOverlayRegistration; + private IDisposable? freeModSelectOverlayRegistration; - protected OnlinePlaySongSelect(Room room) + /// + /// Creates a new . + /// + /// The room. + /// An optional initial to use for the initial beatmap/ruleset/mods. + /// If null, the last in the room will be used. + protected OnlinePlaySongSelect(Room room, PlaylistItem? initialItem = null) { this.room = room; + this.initialItem = initialItem ?? room.Playlist.LastOrDefault(); Padding = new MarginPadding { Horizontal = HORIZONTAL_OVERFLOW_PADDING }; @@ -76,6 +81,7 @@ namespace osu.Game.Screens.OnlinePlay { LeftArea.Padding = new MarginPadding { Top = Header.HEIGHT }; + // Store the initial beatmap/ruleset/mods at the point of entering song select, so they can be reverted to upon exit. initialBeatmap = Beatmap.Value; initialRuleset = Ruleset.Value; initialMods = Mods.Value.ToList(); @@ -87,14 +93,35 @@ namespace osu.Game.Screens.OnlinePlay { base.LoadComplete(); - var rulesetInstance = SelectedItem?.Value?.RulesetID == null ? null : rulesets.GetRuleset(SelectedItem.Value.RulesetID)?.CreateInstance(); - - if (rulesetInstance != null) + if (initialItem != null) { - // At this point, Mods contains both the required and allowed mods. For selection purposes, it should only contain the required mods. - // Similarly, freeMods is currently empty but should only contain the allowed mods. - Mods.Value = SelectedItem.Value.RequiredMods.Select(m => m.ToMod(rulesetInstance)).ToArray(); - FreeMods.Value = SelectedItem.Value.AllowedMods.Select(m => m.ToMod(rulesetInstance)).ToArray(); + // Prefer using a local databased beatmap lookup since OnlineId may be -1 for an invalid beatmap selection. + BeatmapInfo? beatmapInfo = initialItem.Beatmap as BeatmapInfo; + + // And in the case that this isn't a local databased beatmap, query by online ID. + if (beatmapInfo == null) + { + int onlineId = initialItem.Beatmap.OnlineID; + beatmapInfo = beatmapManager.QueryBeatmap(b => b.OnlineID == onlineId); + } + + if (beatmapInfo != null) + Beatmap.Value = beatmapManager.GetWorkingBeatmap(beatmapInfo); + + RulesetInfo? ruleset = rulesets.GetRuleset(initialItem.RulesetID); + + if (ruleset != null) + { + Ruleset.Value = ruleset; + + var rulesetInstance = ruleset.CreateInstance(); + Debug.Assert(rulesetInstance != null); + + // At this point, Mods contains both the required and allowed mods. For selection purposes, it should only contain the required mods. + // Similarly, freeMods is currently empty but should only contain the allowed mods. + Mods.Value = initialItem.RequiredMods.Select(m => m.ToMod(rulesetInstance)).ToArray(); + FreeMods.Value = initialItem.AllowedMods.Select(m => m.ToMod(rulesetInstance)).ToArray(); + } } Mods.BindValueChanged(onModsChanged); @@ -199,7 +226,6 @@ namespace osu.Game.Screens.OnlinePlay protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); - freeModSelectOverlayRegistration?.Dispose(); } } From fcea244537db30790f77c758cce7717d44ef7d11 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 7 Sep 2022 21:21:19 +0900 Subject: [PATCH 327/420] Remove initial selection from OnlinePlaySongSelect This stuff never really worked anyway - every case except with an already created multiplayer room was broken anyway. --- .../OnlinePlay/OnlinePlaySongSelect.cs | 27 +------------------ 1 file changed, 1 insertion(+), 26 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs b/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs index e841d9c41e..ea20270c1e 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs @@ -49,11 +49,6 @@ namespace osu.Game.Screens.OnlinePlay private readonly PlaylistItem? initialItem; private readonly FreeModSelectOverlay freeModSelectOverlay; - private WorkingBeatmap initialBeatmap = null!; - private RulesetInfo initialRuleset = null!; - private IReadOnlyList initialMods = null!; - private bool itemSelected; - private IDisposable? freeModSelectOverlayRegistration; /// @@ -80,12 +75,6 @@ namespace osu.Game.Screens.OnlinePlay private void load() { LeftArea.Padding = new MarginPadding { Top = Header.HEIGHT }; - - // Store the initial beatmap/ruleset/mods at the point of entering song select, so they can be reverted to upon exit. - initialBeatmap = Beatmap.Value; - initialRuleset = Ruleset.Value; - initialMods = Mods.Value.ToList(); - LoadComponent(freeModSelectOverlay); } @@ -152,13 +141,7 @@ namespace osu.Game.Screens.OnlinePlay AllowedMods = FreeMods.Value.Select(m => new APIMod(m)).ToArray() }; - if (SelectItem(item)) - { - itemSelected = true; - return true; - } - - return false; + return SelectItem(item); } /// @@ -181,15 +164,7 @@ namespace osu.Game.Screens.OnlinePlay public override bool OnExiting(ScreenExitEvent e) { - if (!itemSelected) - { - Beatmap.Value = initialBeatmap; - Ruleset.Value = initialRuleset; - Mods.Value = initialMods; - } - freeModSelectOverlay.Hide(); - return base.OnExiting(e); } From 83c0cb1acce2fe9e6c27080651e82b413add1c5e Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 7 Sep 2022 21:22:26 +0900 Subject: [PATCH 328/420] Fix beatmap set to null after exiting song select --- osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs index 03216180fb..00c819e5e4 100644 --- a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs @@ -433,6 +433,9 @@ namespace osu.Game.Screens.OnlinePlay.Match private void updateWorkingBeatmap() { + if (SelectedItem.Value == null || !this.IsCurrentScreen()) + return; + var beatmap = SelectedItem.Value?.Beatmap; // Retrieve the corresponding local beatmap, since we can't directly use the playlist's beatmap info From b560b6f7454983bf2275a76974a6c4c38e828ba9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Acid=20Chicken=20=28=E7=A1=AB=E9=85=B8=E9=B6=8F=29?= Date: Wed, 7 Sep 2022 23:29:10 +0900 Subject: [PATCH 329/420] refactor(osu.Game): arrange the code for the timing distribution graph --- .../HitEventTimingDistributionGraph.cs | 51 ++++++++----------- 1 file changed, 21 insertions(+), 30 deletions(-) diff --git a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs index 37765fe962..ad2f979138 100644 --- a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs +++ b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs @@ -47,9 +47,6 @@ namespace osu.Game.Screens.Ranking.Statistics /// private readonly IReadOnlyList hitEvents; - [Resolved] - private OsuColour colours { get; set; } - /// /// Creates a new . /// @@ -129,13 +126,7 @@ namespace osu.Game.Screens.Ranking.Statistics else { int maxCount = bins.Max(b => b.Values.Sum()); - barDrawables = new Bar[total_timing_distribution_bins]; - - for (int i = 0; i < barDrawables.Length; i++) - { - IReadOnlyList values = bins[i].Select(b => new BarValue(b.Key.OrderingIndex(), b.Value, colours.DrawForHitResult(b.Key))).OrderBy(b => b.Index).ToList(); - barDrawables[i] = new Bar(values, maxCount, i == timing_distribution_centre_bin_index); - } + barDrawables = bins.Select((bin, i) => new Bar(bins[i], maxCount, i == timing_distribution_centre_bin_index)).ToArray(); Container axisFlow; @@ -216,53 +207,53 @@ namespace osu.Game.Screens.Ranking.Statistics } } - private readonly struct BarValue - { - public readonly int Index; - public readonly float Value; - public readonly Color4 Colour; - - public BarValue(int index, float value, Color4 colour) - { - Index = index; - Value = value; - Colour = colour; - } - } - private class Bar : CompositeDrawable { private float totalValue => values.Sum(v => v.Value); private float basalHeight => BoundingBox.Width / BoundingBox.Height; private float availableHeight => 1 - basalHeight; - private readonly IReadOnlyList values; + private readonly IReadOnlyList> values; private readonly float maxValue; + private readonly bool isCentre; - private readonly Circle[] boxOriginals; + private Circle[] boxOriginals; private Circle boxAdjustment; - public Bar(IReadOnlyList values, float maxValue, bool isCentre) + [Resolved] + private OsuColour colours { get; set; } + + public Bar(IDictionary values, float maxValue, bool isCentre) { - this.values = values; + this.values = values.OrderBy(v => v.Key.OrderingIndex()).ToList(); this.maxValue = maxValue; + this.isCentre = isCentre; RelativeSizeAxes = Axes.Both; Masking = true; + } + [BackgroundDependencyLoader] + private void load() + { if (values.Any()) { - InternalChildren = boxOriginals = values.Select((v, i) => new Circle + boxOriginals = values.Select((v, i) => new Circle { RelativeSizeAxes = Axes.Both, Anchor = Anchor.BottomCentre, Origin = Anchor.BottomCentre, - Colour = isCentre && i == 0 ? Color4.White : v.Colour, + Colour = isCentre && i == 0 ? Color4.White : colours.DrawForHitResult(v.Key), Height = 0, }).ToArray(); + // The bars of the stacked bar graph will be processed (stacked) from the bottom, which is the base position, + // to the top, and the bottom bar should be drawn more toward the front by design, + // while the drawing order is from the back to the front, so the order passed to `InternalChildren` is the opposite. + InternalChildren = boxOriginals.Reverse().ToArray(); } else { + // A bin with no value draws a grey dot instead. InternalChildren = boxOriginals = new[] { new Circle From 54f0bb797e46bb581017482876bea3ca4aec83a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Acid=20Chicken=20=28=E7=A1=AB=E9=85=B8=E9=B6=8F=29?= Date: Wed, 7 Sep 2022 23:32:45 +0900 Subject: [PATCH 330/420] refactor(osu.Game): remove nullable optouts in HitResult.cs --- osu.Game/Rulesets/Scoring/HitResult.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Rulesets/Scoring/HitResult.cs b/osu.Game/Rulesets/Scoring/HitResult.cs index 8078363212..3349bcf245 100644 --- a/osu.Game/Rulesets/Scoring/HitResult.cs +++ b/osu.Game/Rulesets/Scoring/HitResult.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. -#nullable disable - using System; using System.Collections.Generic; using System.ComponentModel; From 267465df182b67e783d13b015c646c4d501ad7b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Acid=20Chicken=20=28=E7=A1=AB=E9=85=B8=E9=B6=8F=29?= Date: Wed, 7 Sep 2022 23:34:46 +0900 Subject: [PATCH 331/420] chore(osu.Game): combine `Osu.Colour.{Draw,Text}ForHitResult` into `OsuColour.ForHitResult` --- osu.Game/Graphics/OsuColour.cs | 25 +------------------ .../Leaderboards/LeaderboardScoreTooltip.cs | 2 +- .../Judgements/DefaultJudgementPiece.cs | 2 +- .../Play/HUD/HitErrorMeters/HitErrorMeter.cs | 2 +- .../Expanded/Statistics/HitResultStatistic.cs | 2 +- .../HitEventTimingDistributionGraph.cs | 2 +- 6 files changed, 6 insertions(+), 29 deletions(-) diff --git a/osu.Game/Graphics/OsuColour.cs b/osu.Game/Graphics/OsuColour.cs index 022b1a363f..91161d5c71 100644 --- a/osu.Game/Graphics/OsuColour.cs +++ b/osu.Game/Graphics/OsuColour.cs @@ -102,30 +102,7 @@ namespace osu.Game.Graphics /// /// Retrieves the colour for a . /// - public Color4 TextForHitResult(HitResult judgement) - { - switch (judgement) - { - case HitResult.Perfect: - case HitResult.Great: - return Blue; - - case HitResult.Ok: - case HitResult.Good: - return Green; - - case HitResult.Meh: - return Yellow; - - case HitResult.Miss: - return Red; - - default: - return Color4.White; - } - } - - public Color4 DrawForHitResult(HitResult result) + public Color4 ForHitResult(HitResult result) { switch (result) { diff --git a/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs b/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs index 23d4e64191..2f3ece0e3b 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs @@ -156,7 +156,7 @@ namespace osu.Game.Online.Leaderboards { Font = OsuFont.Torus.With(size: 12, weight: FontWeight.SemiBold), Text = displayName.ToUpper(), - Colour = colours.TextForHitResult(result), + Colour = colours.ForHitResult(result), }, new OsuSpriteText { diff --git a/osu.Game/Rulesets/Judgements/DefaultJudgementPiece.cs b/osu.Game/Rulesets/Judgements/DefaultJudgementPiece.cs index a854bf37f5..c2b27d4ce8 100644 --- a/osu.Game/Rulesets/Judgements/DefaultJudgementPiece.cs +++ b/osu.Game/Rulesets/Judgements/DefaultJudgementPiece.cs @@ -42,7 +42,7 @@ namespace osu.Game.Rulesets.Judgements Anchor = Anchor.Centre, Origin = Anchor.Centre, Text = Result.GetDescription().ToUpperInvariant(), - Colour = colours.TextForHitResult(Result), + Colour = colours.ForHitResult(Result), Font = OsuFont.Numeric.With(size: 20), Scale = new Vector2(0.85f, 1), } diff --git a/osu.Game/Screens/Play/HUD/HitErrorMeters/HitErrorMeter.cs b/osu.Game/Screens/Play/HUD/HitErrorMeters/HitErrorMeter.cs index 35d28b8e98..dda17c25e6 100644 --- a/osu.Game/Screens/Play/HUD/HitErrorMeters/HitErrorMeter.cs +++ b/osu.Game/Screens/Play/HUD/HitErrorMeters/HitErrorMeter.cs @@ -59,7 +59,7 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters protected Color4 GetColourForHitResult(HitResult result) { - return colours.DrawForHitResult(result); + return colours.ForHitResult(result); } /// diff --git a/osu.Game/Screens/Ranking/Expanded/Statistics/HitResultStatistic.cs b/osu.Game/Screens/Ranking/Expanded/Statistics/HitResultStatistic.cs index 429b72c07c..c23a5e668d 100644 --- a/osu.Game/Screens/Ranking/Expanded/Statistics/HitResultStatistic.cs +++ b/osu.Game/Screens/Ranking/Expanded/Statistics/HitResultStatistic.cs @@ -23,7 +23,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics [BackgroundDependencyLoader] private void load(OsuColour colours) { - HeaderText.Colour = colours.TextForHitResult(Result); + HeaderText.Colour = colours.ForHitResult(Result); } } } diff --git a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs index ad2f979138..52682c35a0 100644 --- a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs +++ b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs @@ -243,7 +243,7 @@ namespace osu.Game.Screens.Ranking.Statistics RelativeSizeAxes = Axes.Both, Anchor = Anchor.BottomCentre, Origin = Anchor.BottomCentre, - Colour = isCentre && i == 0 ? Color4.White : colours.DrawForHitResult(v.Key), + Colour = isCentre && i == 0 ? Color4.White : colours.ForHitResult(v.Key), Height = 0, }).ToArray(); // The bars of the stacked bar graph will be processed (stacked) from the bottom, which is the base position, From 68ea5a765f25f8d6a0df05d95076d9ff21ee8598 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 7 Sep 2022 23:56:45 +0900 Subject: [PATCH 332/420] 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 219912425f..cd4dfec3d7 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,7 +52,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 43e3076f5c..6d0827a107 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -36,7 +36,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/osu.iOS.props b/osu.iOS.props index 0f0bf2848c..3316bf5a49 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -61,7 +61,7 @@ - + @@ -84,7 +84,7 @@ - + From 50923b6e5b5722f59e4bbaaa8ef3f218117bd414 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 8 Sep 2022 00:25:55 +0300 Subject: [PATCH 333/420] Move track assignment below --- osu.Game/Screens/Play/MasterGameplayClockContainer.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs index 26fb127e83..20a5c95605 100644 --- a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs +++ b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs @@ -68,10 +68,11 @@ namespace osu.Game.Screens.Play public MasterGameplayClockContainer(WorkingBeatmap beatmap, double skipTargetTime) : base(beatmap.Track, true) { - track = beatmap.Track; this.beatmap = beatmap; this.skipTargetTime = skipTargetTime; + track = beatmap.Track; + StartTime = findEarliestStartTime(); } From 3b116a1a474c786eb7beba91317dc43f31a9b93d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 8 Sep 2022 13:17:27 +0900 Subject: [PATCH 334/420] Fix mods not being set on `BeginPlayingInternal` --- osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs b/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs index cb3711ebb5..e6d8e473bb 100644 --- a/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs +++ b/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Linq; using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -130,6 +131,7 @@ namespace osu.Game.Tests.Visual.Spectator // Track the local user's playing beatmap ID. Debug.Assert(state.BeatmapID != null); userBeatmapDictionary[api.LocalUser.Value.Id] = state.BeatmapID.Value; + userModsDictionary[api.LocalUser.Value.Id] = state.Mods.ToArray(); return ((ISpectatorClient)this).UserBeganPlaying(api.LocalUser.Value.Id, state); } From 6aac0bd4e96e9da903c22a86162b596108847335 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 8 Sep 2022 13:43:37 +0900 Subject: [PATCH 335/420] Update test to account for the fact that SSDQ is not zero anymore --- .../Visual/Gameplay/TestSceneDrawableStoryboardSprite.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableStoryboardSprite.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableStoryboardSprite.cs index 34723e3329..5c9e9043a7 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableStoryboardSprite.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableStoryboardSprite.cs @@ -78,8 +78,9 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("allow skin lookup", () => storyboard.UseSkinSprites = true); AddStep("create sprites", () => SetContents(_ => createSprite(lookup_name, Anchor.TopLeft, Vector2.Zero))); + AddAssert("sprites present", () => sprites.All(s => s.IsPresent)); AddStep("scale sprite", () => sprites.ForEach(s => s.VectorScale = new Vector2(0, 1))); - AddAssert("zero width", () => sprites.All(s => s.ScreenSpaceDrawQuad.Width == 0)); + AddAssert("sprites not present", () => sprites.All(s => s.IsPresent)); } [Test] From 3d9bf25c8afde4a8225035175f1ac4109955d757 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 8 Sep 2022 14:49:43 +0900 Subject: [PATCH 336/420] Apply NRT to `TestScenePlaySongSelect` Very mess. --- .../SongSelect/TestScenePlaySongSelect.cs | 309 ++++++++++-------- 1 file changed, 172 insertions(+), 137 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs index 5db46e3097..cc8746959b 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs @@ -1,10 +1,9 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Threading.Tasks; using NUnit.Framework; @@ -13,6 +12,7 @@ using osu.Framework.Audio; using osu.Framework.Bindables; using osu.Framework.Extensions; using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics.Containers; using osu.Framework.Platform; using osu.Framework.Screens; @@ -46,11 +46,12 @@ namespace osu.Game.Tests.Visual.SongSelect [TestFixture] public class TestScenePlaySongSelect : ScreenTestScene { - private BeatmapManager manager; - private RulesetStore rulesets; - private MusicController music; - private WorkingBeatmap defaultBeatmap; - private TestSongSelect songSelect; + private BeatmapManager manager = null!; + private RulesetStore rulesets = null!; + private MusicController music = null!; + private WorkingBeatmap defaultBeatmap = null!; + private OsuConfigManager config = null!; + private TestSongSelect? songSelect; [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) @@ -69,8 +70,6 @@ namespace osu.Game.Tests.Visual.SongSelect Dependencies.Cache(config = new OsuConfigManager(LocalStorage)); } - private OsuConfigManager config; - public override void SetUpSteps() { base.SetUpSteps(); @@ -85,7 +84,7 @@ namespace osu.Game.Tests.Visual.SongSelect songSelect = null; }); - AddStep("delete all beatmaps", () => manager?.Delete()); + AddStep("delete all beatmaps", () => manager.Delete()); } [Test] @@ -98,7 +97,7 @@ namespace osu.Game.Tests.Visual.SongSelect addRulesetImportStep(0); AddUntilStep("wait for placeholder hidden", () => getPlaceholder()?.State.Value == Visibility.Hidden); - AddStep("delete all beatmaps", () => manager?.Delete()); + AddStep("delete all beatmaps", () => manager.Delete()); AddUntilStep("wait for placeholder visible", () => getPlaceholder()?.State.Value == Visibility.Visible); } @@ -144,7 +143,7 @@ namespace osu.Game.Tests.Visual.SongSelect createSongSelect(); - AddAssert("filter count is 1", () => songSelect.FilterCount == 1); + AddAssert("filter count is 1", () => songSelect?.FilterCount == 1); } [Test] @@ -156,7 +155,7 @@ namespace osu.Game.Tests.Visual.SongSelect waitForInitialSelection(); - WorkingBeatmap selected = null; + WorkingBeatmap? selected = null; AddStep("store selected beatmap", () => selected = Beatmap.Value); @@ -166,7 +165,7 @@ namespace osu.Game.Tests.Visual.SongSelect InputManager.Key(Key.Enter); }); - AddUntilStep("wait for not current", () => !songSelect.IsCurrentScreen()); + AddUntilStep("wait for not current", () => !songSelect!.IsCurrentScreen()); AddAssert("ensure selection changed", () => selected != Beatmap.Value); } @@ -179,7 +178,7 @@ namespace osu.Game.Tests.Visual.SongSelect waitForInitialSelection(); - WorkingBeatmap selected = null; + WorkingBeatmap? selected = null; AddStep("store selected beatmap", () => selected = Beatmap.Value); @@ -189,7 +188,7 @@ namespace osu.Game.Tests.Visual.SongSelect InputManager.Key(Key.Down); }); - AddUntilStep("wait for not current", () => !songSelect.IsCurrentScreen()); + AddUntilStep("wait for not current", () => !songSelect!.IsCurrentScreen()); AddAssert("ensure selection didn't change", () => selected == Beatmap.Value); } @@ -202,23 +201,23 @@ namespace osu.Game.Tests.Visual.SongSelect AddUntilStep("wait for initial selection", () => !Beatmap.IsDefault); - WorkingBeatmap selected = null; + WorkingBeatmap? selected = null; AddStep("store selected beatmap", () => selected = Beatmap.Value); - AddUntilStep("wait for beatmaps to load", () => songSelect.Carousel.ChildrenOfType().Any()); + AddUntilStep("wait for beatmaps to load", () => songSelect!.Carousel.ChildrenOfType().Any()); AddStep("select next and enter", () => { - InputManager.MoveMouseTo(songSelect.Carousel.ChildrenOfType() - .First(b => !((CarouselBeatmap)b.Item).BeatmapInfo.Equals(songSelect.Carousel.SelectedBeatmapInfo))); + InputManager.MoveMouseTo(songSelect!.Carousel.ChildrenOfType() + .First(b => !((CarouselBeatmap)b.Item).BeatmapInfo.Equals(songSelect!.Carousel.SelectedBeatmapInfo))); InputManager.Click(MouseButton.Left); InputManager.Key(Key.Enter); }); - AddUntilStep("wait for not current", () => !songSelect.IsCurrentScreen()); + AddUntilStep("wait for not current", () => !songSelect!.IsCurrentScreen()); AddAssert("ensure selection changed", () => selected != Beatmap.Value); } @@ -231,14 +230,14 @@ namespace osu.Game.Tests.Visual.SongSelect waitForInitialSelection(); - WorkingBeatmap selected = null; + WorkingBeatmap? selected = null; AddStep("store selected beatmap", () => selected = Beatmap.Value); AddStep("select next and enter", () => { - InputManager.MoveMouseTo(songSelect.Carousel.ChildrenOfType() - .First(b => !((CarouselBeatmap)b.Item).BeatmapInfo.Equals(songSelect.Carousel.SelectedBeatmapInfo))); + InputManager.MoveMouseTo(songSelect!.Carousel.ChildrenOfType() + .First(b => !((CarouselBeatmap)b.Item).BeatmapInfo.Equals(songSelect!.Carousel.SelectedBeatmapInfo))); InputManager.PressButton(MouseButton.Left); @@ -247,7 +246,7 @@ namespace osu.Game.Tests.Visual.SongSelect InputManager.ReleaseButton(MouseButton.Left); }); - AddUntilStep("wait for not current", () => !songSelect.IsCurrentScreen()); + AddUntilStep("wait for not current", () => !songSelect!.IsCurrentScreen()); AddAssert("ensure selection didn't change", () => selected == Beatmap.Value); } @@ -260,11 +259,11 @@ namespace osu.Game.Tests.Visual.SongSelect createSongSelect(); AddStep("push child screen", () => Stack.Push(new TestSceneOsuScreenStack.TestScreen("test child"))); - AddUntilStep("wait for not current", () => !songSelect.IsCurrentScreen()); + AddUntilStep("wait for not current", () => !songSelect!.IsCurrentScreen()); - AddStep("return", () => songSelect.MakeCurrent()); - AddUntilStep("wait for current", () => songSelect.IsCurrentScreen()); - AddAssert("filter count is 1", () => songSelect.FilterCount == 1); + AddStep("return", () => songSelect!.MakeCurrent()); + AddUntilStep("wait for current", () => songSelect!.IsCurrentScreen()); + AddAssert("filter count is 1", () => songSelect!.FilterCount == 1); } [Test] @@ -278,13 +277,13 @@ namespace osu.Game.Tests.Visual.SongSelect createSongSelect(); AddStep("push child screen", () => Stack.Push(new TestSceneOsuScreenStack.TestScreen("test child"))); - AddUntilStep("wait for not current", () => !songSelect.IsCurrentScreen()); + AddUntilStep("wait for not current", () => !songSelect!.IsCurrentScreen()); AddStep("change convert setting", () => config.SetValue(OsuSetting.ShowConvertedBeatmaps, true)); - AddStep("return", () => songSelect.MakeCurrent()); - AddUntilStep("wait for current", () => songSelect.IsCurrentScreen()); - AddAssert("filter count is 2", () => songSelect.FilterCount == 2); + AddStep("return", () => songSelect!.MakeCurrent()); + AddUntilStep("wait for current", () => songSelect!.IsCurrentScreen()); + AddAssert("filter count is 2", () => songSelect!.FilterCount == 2); } [Test] @@ -295,7 +294,7 @@ namespace osu.Game.Tests.Visual.SongSelect createSongSelect(); AddStep("push child screen", () => Stack.Push(new TestSceneOsuScreenStack.TestScreen("test child"))); - AddUntilStep("wait for not current", () => !songSelect.IsCurrentScreen()); + AddUntilStep("wait for not current", () => !songSelect!.IsCurrentScreen()); AddStep("update beatmap", () => { @@ -304,9 +303,9 @@ namespace osu.Game.Tests.Visual.SongSelect Beatmap.Value = manager.GetWorkingBeatmap(anotherBeatmap); }); - AddStep("return", () => songSelect.MakeCurrent()); - AddUntilStep("wait for current", () => songSelect.IsCurrentScreen()); - AddAssert("carousel updated", () => songSelect.Carousel.SelectedBeatmapInfo.Equals(Beatmap.Value.BeatmapInfo)); + AddStep("return", () => songSelect!.MakeCurrent()); + AddUntilStep("wait for current", () => songSelect!.IsCurrentScreen()); + AddAssert("carousel updated", () => songSelect!.Carousel.SelectedBeatmapInfo?.Equals(Beatmap.Value.BeatmapInfo) == true); } [Test] @@ -318,15 +317,15 @@ namespace osu.Game.Tests.Visual.SongSelect addRulesetImportStep(0); checkMusicPlaying(true); - AddStep("select first", () => songSelect.Carousel.SelectBeatmap(songSelect.Carousel.BeatmapSets.First().Beatmaps.First())); + AddStep("select first", () => songSelect!.Carousel.SelectBeatmap(songSelect!.Carousel.BeatmapSets.First().Beatmaps.First())); checkMusicPlaying(true); AddStep("manual pause", () => music.TogglePause()); checkMusicPlaying(false); - AddStep("select next difficulty", () => songSelect.Carousel.SelectNext(skipDifficulties: false)); + AddStep("select next difficulty", () => songSelect!.Carousel.SelectNext(skipDifficulties: false)); checkMusicPlaying(false); - AddStep("select next set", () => songSelect.Carousel.SelectNext()); + AddStep("select next set", () => songSelect!.Carousel.SelectNext()); checkMusicPlaying(true); } @@ -366,13 +365,13 @@ namespace osu.Game.Tests.Visual.SongSelect public void TestDummy() { createSongSelect(); - AddUntilStep("dummy selected", () => songSelect.CurrentBeatmap == defaultBeatmap); + AddUntilStep("dummy selected", () => songSelect!.CurrentBeatmap == defaultBeatmap); - AddUntilStep("dummy shown on wedge", () => songSelect.CurrentBeatmapDetailsBeatmap == defaultBeatmap); + AddUntilStep("dummy shown on wedge", () => songSelect!.CurrentBeatmapDetailsBeatmap == defaultBeatmap); addManyTestMaps(); - AddUntilStep("random map selected", () => songSelect.CurrentBeatmap != defaultBeatmap); + AddUntilStep("random map selected", () => songSelect!.CurrentBeatmap != defaultBeatmap); } [Test] @@ -381,7 +380,7 @@ namespace osu.Game.Tests.Visual.SongSelect createSongSelect(); addManyTestMaps(); - AddUntilStep("random map selected", () => songSelect.CurrentBeatmap != defaultBeatmap); + AddUntilStep("random map selected", () => songSelect!.CurrentBeatmap != defaultBeatmap); AddStep(@"Sort by Artist", () => config.SetValue(OsuSetting.SongSelectSortingMode, SortMode.Artist)); AddStep(@"Sort by Title", () => config.SetValue(OsuSetting.SongSelectSortingMode, SortMode.Title)); @@ -398,7 +397,7 @@ namespace osu.Game.Tests.Visual.SongSelect { createSongSelect(); addRulesetImportStep(2); - AddUntilStep("no selection", () => songSelect.Carousel.SelectedBeatmapInfo == null); + AddUntilStep("no selection", () => songSelect!.Carousel.SelectedBeatmapInfo == null); } [Test] @@ -408,13 +407,13 @@ namespace osu.Game.Tests.Visual.SongSelect changeRuleset(2); addRulesetImportStep(2); addRulesetImportStep(1); - AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmapInfo.Ruleset.OnlineID == 2); + AddUntilStep("has selection", () => songSelect!.Carousel.SelectedBeatmapInfo?.Ruleset.OnlineID == 2); changeRuleset(1); - AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmapInfo.Ruleset.OnlineID == 1); + AddUntilStep("has selection", () => songSelect!.Carousel.SelectedBeatmapInfo?.Ruleset.OnlineID == 1); changeRuleset(0); - AddUntilStep("no selection", () => songSelect.Carousel.SelectedBeatmapInfo == null); + AddUntilStep("no selection", () => songSelect!.Carousel.SelectedBeatmapInfo == null); } [Test] @@ -423,7 +422,7 @@ namespace osu.Game.Tests.Visual.SongSelect createSongSelect(); changeRuleset(0); - Live original = null!; + Live? original = null; int originalOnlineSetID = 0; AddStep(@"Sort by artist", () => config.SetValue(OsuSetting.SongSelectSortingMode, SortMode.Artist)); @@ -431,12 +430,17 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("import original", () => { original = manager.Import(new ImportTask(TestResources.GetQuickTestBeatmapForImport())).GetResultSafely(); - originalOnlineSetID = original!.Value.OnlineID; + + Debug.Assert(original != null); + + originalOnlineSetID = original.Value.OnlineID; }); // This will move the beatmap set to a different location in the carousel. AddStep("Update original with bogus info", () => { + Debug.Assert(original != null); + original.PerformWrite(set => { foreach (var beatmap in set.Beatmaps) @@ -457,13 +461,19 @@ namespace osu.Game.Tests.Visual.SongSelect manager.Import(testBeatmapSetInfo); }, 10); - AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmapInfo?.BeatmapSet?.OnlineID == originalOnlineSetID); + AddUntilStep("has selection", () => songSelect!.Carousel.SelectedBeatmapInfo?.BeatmapSet?.OnlineID == originalOnlineSetID); - Task> updateTask = null!; - AddStep("update beatmap", () => updateTask = manager.ImportAsUpdate(new ProgressNotification(), new ImportTask(TestResources.GetQuickTestBeatmapForImport()), original.Value)); + Task?> updateTask = null!; + + AddStep("update beatmap", () => + { + Debug.Assert(original != null); + + updateTask = manager.ImportAsUpdate(new ProgressNotification(), new ImportTask(TestResources.GetQuickTestBeatmapForImport()), original.Value); + }); AddUntilStep("wait for update completion", () => updateTask.IsCompleted); - AddUntilStep("retained selection", () => songSelect.Carousel.SelectedBeatmapInfo?.BeatmapSet?.OnlineID == originalOnlineSetID); + AddUntilStep("retained selection", () => songSelect!.Carousel.SelectedBeatmapInfo?.BeatmapSet?.OnlineID == originalOnlineSetID); } [Test] @@ -473,13 +483,13 @@ namespace osu.Game.Tests.Visual.SongSelect changeRuleset(2); addRulesetImportStep(2); - AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmapInfo.Ruleset.OnlineID == 2); + AddUntilStep("has selection", () => songSelect!.Carousel.SelectedBeatmapInfo?.Ruleset.OnlineID == 2); addRulesetImportStep(0); addRulesetImportStep(0); addRulesetImportStep(0); - BeatmapInfo target = null; + BeatmapInfo? target = null; AddStep("select beatmap/ruleset externally", () => { @@ -490,10 +500,10 @@ namespace osu.Game.Tests.Visual.SongSelect Beatmap.Value = manager.GetWorkingBeatmap(target); }); - AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmapInfo.Equals(target)); + AddUntilStep("has selection", () => songSelect!.Carousel.SelectedBeatmapInfo?.Equals(target) == true); // this is an important check, to make sure updateComponentFromBeatmap() was actually run - AddUntilStep("selection shown on wedge", () => songSelect.CurrentBeatmapDetailsBeatmap.BeatmapInfo.MatchesOnlineID(target)); + AddUntilStep("selection shown on wedge", () => songSelect!.CurrentBeatmapDetailsBeatmap.BeatmapInfo.MatchesOnlineID(target)); } [Test] @@ -503,13 +513,13 @@ namespace osu.Game.Tests.Visual.SongSelect changeRuleset(2); addRulesetImportStep(2); - AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmapInfo.Ruleset.OnlineID == 2); + AddUntilStep("has selection", () => songSelect!.Carousel.SelectedBeatmapInfo?.Ruleset.OnlineID == 2); addRulesetImportStep(0); addRulesetImportStep(0); addRulesetImportStep(0); - BeatmapInfo target = null; + BeatmapInfo? target = null; AddStep("select beatmap/ruleset externally", () => { @@ -520,12 +530,12 @@ namespace osu.Game.Tests.Visual.SongSelect Ruleset.Value = rulesets.AvailableRulesets.First(r => r.OnlineID == 0); }); - AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmapInfo.Equals(target)); + AddUntilStep("has selection", () => songSelect!.Carousel.SelectedBeatmapInfo?.Equals(target) == true); AddUntilStep("has correct ruleset", () => Ruleset.Value.OnlineID == 0); // this is an important check, to make sure updateComponentFromBeatmap() was actually run - AddUntilStep("selection shown on wedge", () => songSelect.CurrentBeatmapDetailsBeatmap.BeatmapInfo.MatchesOnlineID(target)); + AddUntilStep("selection shown on wedge", () => songSelect!.CurrentBeatmapDetailsBeatmap.BeatmapInfo.MatchesOnlineID(target)); } [Test] @@ -543,12 +553,12 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("change ruleset", () => { SelectedMods.ValueChanged += onModChange; - songSelect.Ruleset.ValueChanged += onRulesetChange; + songSelect!.Ruleset.ValueChanged += onRulesetChange; Ruleset.Value = new TaikoRuleset().RulesetInfo; SelectedMods.ValueChanged -= onModChange; - songSelect.Ruleset.ValueChanged -= onRulesetChange; + songSelect!.Ruleset.ValueChanged -= onRulesetChange; }); AddAssert("mods changed before ruleset", () => modChangeIndex < rulesetChangeIndex); @@ -579,18 +589,18 @@ namespace osu.Game.Tests.Visual.SongSelect { createSongSelect(); addManyTestMaps(); - AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmapInfo != null); + AddUntilStep("has selection", () => songSelect!.Carousel.SelectedBeatmapInfo != null); bool startRequested = false; AddStep("set filter and finalize", () => { - songSelect.StartRequested = () => startRequested = true; + songSelect!.StartRequested = () => startRequested = true; - songSelect.Carousel.Filter(new FilterCriteria { SearchText = "somestringthatshouldn'tbematchable" }); - songSelect.FinaliseSelection(); + songSelect!.Carousel.Filter(new FilterCriteria { SearchText = "somestringthatshouldn'tbematchable" }); + songSelect!.FinaliseSelection(); - songSelect.StartRequested = null; + songSelect!.StartRequested = null; }); AddAssert("start not requested", () => !startRequested); @@ -610,15 +620,15 @@ namespace osu.Game.Tests.Visual.SongSelect // used for filter check below AddStep("allow convert display", () => config.SetValue(OsuSetting.ShowConvertedBeatmaps, true)); - AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmapInfo != null); + AddUntilStep("has selection", () => songSelect!.Carousel.SelectedBeatmapInfo != null); - AddStep("set filter text", () => songSelect.FilterControl.ChildrenOfType().First().Text = "nonono"); + AddStep("set filter text", () => songSelect!.FilterControl.ChildrenOfType().First().Text = "nonono"); AddUntilStep("dummy selected", () => Beatmap.Value is DummyWorkingBeatmap); - AddUntilStep("has no selection", () => songSelect.Carousel.SelectedBeatmapInfo == null); + AddUntilStep("has no selection", () => songSelect!.Carousel.SelectedBeatmapInfo == null); - BeatmapInfo target = null; + BeatmapInfo? target = null; int targetRuleset = differentRuleset ? 1 : 0; @@ -632,24 +642,24 @@ namespace osu.Game.Tests.Visual.SongSelect Beatmap.Value = manager.GetWorkingBeatmap(target); }); - AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmapInfo != null); + AddUntilStep("has selection", () => songSelect!.Carousel.SelectedBeatmapInfo != null); AddAssert("selected only shows expected ruleset (plus converts)", () => { - var selectedPanel = songSelect.Carousel.ChildrenOfType().First(s => s.Item.State.Value == CarouselItemState.Selected); + var selectedPanel = songSelect!.Carousel.ChildrenOfType().First(s => s.Item.State.Value == CarouselItemState.Selected); // special case for converts checked here. return selectedPanel.ChildrenOfType().All(i => i.IsFiltered || i.Item.BeatmapInfo.Ruleset.OnlineID == targetRuleset || i.Item.BeatmapInfo.Ruleset.OnlineID == 0); }); - AddUntilStep("carousel has correct", () => songSelect.Carousel.SelectedBeatmapInfo?.MatchesOnlineID(target) == true); + AddUntilStep("carousel has correct", () => songSelect!.Carousel.SelectedBeatmapInfo?.MatchesOnlineID(target) == true); AddUntilStep("game has correct", () => Beatmap.Value.BeatmapInfo.MatchesOnlineID(target)); - AddStep("reset filter text", () => songSelect.FilterControl.ChildrenOfType().First().Text = string.Empty); + AddStep("reset filter text", () => songSelect!.FilterControl.ChildrenOfType().First().Text = string.Empty); AddAssert("game still correct", () => Beatmap.Value?.BeatmapInfo.MatchesOnlineID(target) == true); - AddAssert("carousel still correct", () => songSelect.Carousel.SelectedBeatmapInfo.MatchesOnlineID(target)); + AddAssert("carousel still correct", () => songSelect!.Carousel.SelectedBeatmapInfo.MatchesOnlineID(target)); } [Test] @@ -662,15 +672,15 @@ namespace osu.Game.Tests.Visual.SongSelect changeRuleset(0); - AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmapInfo != null); + AddUntilStep("has selection", () => songSelect!.Carousel.SelectedBeatmapInfo != null); - AddStep("set filter text", () => songSelect.FilterControl.ChildrenOfType().First().Text = "nonono"); + AddStep("set filter text", () => songSelect!.FilterControl.ChildrenOfType().First().Text = "nonono"); AddUntilStep("dummy selected", () => Beatmap.Value is DummyWorkingBeatmap); - AddUntilStep("has no selection", () => songSelect.Carousel.SelectedBeatmapInfo == null); + AddUntilStep("has no selection", () => songSelect!.Carousel.SelectedBeatmapInfo == null); - BeatmapInfo target = null; + BeatmapInfo? target = null; AddStep("select beatmap externally", () => { @@ -682,15 +692,15 @@ namespace osu.Game.Tests.Visual.SongSelect Beatmap.Value = manager.GetWorkingBeatmap(target); }); - AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmapInfo != null); + AddUntilStep("has selection", () => songSelect!.Carousel.SelectedBeatmapInfo != null); - AddUntilStep("carousel has correct", () => songSelect.Carousel.SelectedBeatmapInfo?.MatchesOnlineID(target) == true); + AddUntilStep("carousel has correct", () => songSelect!.Carousel.SelectedBeatmapInfo?.MatchesOnlineID(target) == true); AddUntilStep("game has correct", () => Beatmap.Value.BeatmapInfo.MatchesOnlineID(target)); - AddStep("set filter text", () => songSelect.FilterControl.ChildrenOfType().First().Text = "nononoo"); + AddStep("set filter text", () => songSelect!.FilterControl.ChildrenOfType().First().Text = "nononoo"); AddUntilStep("game lost selection", () => Beatmap.Value is DummyWorkingBeatmap); - AddAssert("carousel lost selection", () => songSelect.Carousel.SelectedBeatmapInfo == null); + AddAssert("carousel lost selection", () => songSelect!.Carousel.SelectedBeatmapInfo == null); } [Test] @@ -711,11 +721,11 @@ namespace osu.Game.Tests.Visual.SongSelect AddUntilStep("wait for player", () => Stack.CurrentScreen is PlayerLoader); - AddAssert("autoplay selected", () => songSelect.Mods.Value.Single() is ModAutoplay); + AddAssert("autoplay selected", () => songSelect!.Mods.Value.Single() is ModAutoplay); - AddUntilStep("wait for return to ss", () => songSelect.IsCurrentScreen()); + AddUntilStep("wait for return to ss", () => songSelect!.IsCurrentScreen()); - AddAssert("no mods selected", () => songSelect.Mods.Value.Count == 0); + AddAssert("no mods selected", () => songSelect!.Mods.Value.Count == 0); } [Test] @@ -738,11 +748,11 @@ namespace osu.Game.Tests.Visual.SongSelect AddUntilStep("wait for player", () => Stack.CurrentScreen is PlayerLoader); - AddAssert("autoplay selected", () => songSelect.Mods.Value.Single() is ModAutoplay); + AddAssert("autoplay selected", () => songSelect!.Mods.Value.Single() is ModAutoplay); - AddUntilStep("wait for return to ss", () => songSelect.IsCurrentScreen()); + AddUntilStep("wait for return to ss", () => songSelect!.IsCurrentScreen()); - AddAssert("autoplay still selected", () => songSelect.Mods.Value.Single() is ModAutoplay); + AddAssert("autoplay still selected", () => songSelect!.Mods.Value.Single() is ModAutoplay); } [Test] @@ -765,11 +775,11 @@ namespace osu.Game.Tests.Visual.SongSelect AddUntilStep("wait for player", () => Stack.CurrentScreen is PlayerLoader); - AddAssert("only autoplay selected", () => songSelect.Mods.Value.Single() is ModAutoplay); + AddAssert("only autoplay selected", () => songSelect!.Mods.Value.Single() is ModAutoplay); - AddUntilStep("wait for return to ss", () => songSelect.IsCurrentScreen()); + AddUntilStep("wait for return to ss", () => songSelect!.IsCurrentScreen()); - AddAssert("relax returned", () => songSelect.Mods.Value.Single() is ModRelax); + AddAssert("relax returned", () => songSelect!.Mods.Value.Single() is ModRelax); } [Test] @@ -778,10 +788,10 @@ namespace osu.Game.Tests.Visual.SongSelect Guid? previousID = null; createSongSelect(); addRulesetImportStep(0); - AddStep("Move to last difficulty", () => songSelect.Carousel.SelectBeatmap(songSelect.Carousel.BeatmapSets.First().Beatmaps.Last())); - AddStep("Store current ID", () => previousID = songSelect.Carousel.SelectedBeatmapInfo.ID); - AddStep("Hide first beatmap", () => manager.Hide(songSelect.Carousel.SelectedBeatmapSet.Beatmaps.First())); - AddAssert("Selected beatmap has not changed", () => songSelect.Carousel.SelectedBeatmapInfo.ID == previousID); + AddStep("Move to last difficulty", () => songSelect!.Carousel.SelectBeatmap(songSelect!.Carousel.BeatmapSets.First().Beatmaps.Last())); + AddStep("Store current ID", () => previousID = songSelect!.Carousel.SelectedBeatmapInfo!.ID); + AddStep("Hide first beatmap", () => manager.Hide(songSelect!.Carousel.SelectedBeatmapSet!.Beatmaps.First())); + AddAssert("Selected beatmap has not changed", () => songSelect!.Carousel.SelectedBeatmapInfo?.ID == previousID); } [Test] @@ -792,17 +802,24 @@ namespace osu.Game.Tests.Visual.SongSelect AddUntilStep("wait for selection", () => !Beatmap.IsDefault); - DrawableCarouselBeatmapSet set = null; + DrawableCarouselBeatmapSet set = null!; AddStep("Find the DrawableCarouselBeatmapSet", () => { - set = songSelect.Carousel.ChildrenOfType().First(); + set = songSelect!.Carousel.ChildrenOfType().First(); }); - FilterableDifficultyIcon difficultyIcon = null; + FilterableDifficultyIcon difficultyIcon = null!; + AddUntilStep("Find an icon", () => { - return (difficultyIcon = set.ChildrenOfType() - .FirstOrDefault(icon => getDifficultyIconIndex(set, icon) != getCurrentBeatmapIndex())) != null; + var foundIcon = set.ChildrenOfType() + .FirstOrDefault(icon => getDifficultyIconIndex(set, icon) != getCurrentBeatmapIndex()); + + if (foundIcon == null) + return false; + + difficultyIcon = foundIcon; + return true; }); AddStep("Click on a difficulty", () => @@ -815,21 +832,24 @@ namespace osu.Game.Tests.Visual.SongSelect AddAssert("Selected beatmap correct", () => getCurrentBeatmapIndex() == getDifficultyIconIndex(set, difficultyIcon)); double? maxBPM = null; - AddStep("Filter some difficulties", () => songSelect.Carousel.Filter(new FilterCriteria + AddStep("Filter some difficulties", () => songSelect!.Carousel.Filter(new FilterCriteria { BPM = new FilterCriteria.OptionalRange { - Min = maxBPM = songSelect.Carousel.SelectedBeatmapSet.MaxBPM, + Min = maxBPM = songSelect!.Carousel.SelectedBeatmapSet!.MaxBPM, IsLowerInclusive = true } })); - BeatmapInfo filteredBeatmap = null; - FilterableDifficultyIcon filteredIcon = null; + BeatmapInfo? filteredBeatmap = null; + FilterableDifficultyIcon? filteredIcon = null; AddStep("Get filtered icon", () => { - var selectedSet = songSelect.Carousel.SelectedBeatmapSet; + var selectedSet = songSelect!.Carousel.SelectedBeatmapSet; + + Debug.Assert(selectedSet != null); + filteredBeatmap = selectedSet.Beatmaps.First(b => b.BPM < maxBPM); int filteredBeatmapIndex = getBeatmapIndex(selectedSet, filteredBeatmap); filteredIcon = set.ChildrenOfType().ElementAt(filteredBeatmapIndex); @@ -842,7 +862,7 @@ namespace osu.Game.Tests.Visual.SongSelect InputManager.Click(MouseButton.Left); }); - AddAssert("Selected beatmap correct", () => songSelect.Carousel.SelectedBeatmapInfo.Equals(filteredBeatmap)); + AddAssert("Selected beatmap correct", () => songSelect!.Carousel.SelectedBeatmapInfo?.Equals(filteredBeatmap) == true); } [Test] @@ -907,14 +927,14 @@ namespace osu.Game.Tests.Visual.SongSelect manager.Import(TestResources.CreateTestBeatmapSetInfo(3, usableRulesets)); }); - DrawableCarouselBeatmapSet set = null; + DrawableCarouselBeatmapSet? set = null; AddUntilStep("Find the DrawableCarouselBeatmapSet", () => { - set = songSelect.Carousel.ChildrenOfType().FirstOrDefault(); + set = songSelect!.Carousel.ChildrenOfType().FirstOrDefault(); return set != null; }); - FilterableDifficultyIcon difficultyIcon = null; + FilterableDifficultyIcon? difficultyIcon = null; AddUntilStep("Find an icon for different ruleset", () => { difficultyIcon = set.ChildrenOfType() @@ -937,7 +957,7 @@ namespace osu.Game.Tests.Visual.SongSelect AddUntilStep("Check ruleset changed to mania", () => Ruleset.Value.OnlineID == 3); - AddAssert("Selected beatmap still same set", () => songSelect.Carousel.SelectedBeatmapInfo.BeatmapSet?.OnlineID == previousSetID); + AddAssert("Selected beatmap still same set", () => songSelect!.Carousel.SelectedBeatmapInfo?.BeatmapSet?.OnlineID == previousSetID); AddAssert("Selected beatmap is mania", () => Beatmap.Value.BeatmapInfo.Ruleset.OnlineID == 3); } @@ -948,7 +968,7 @@ namespace osu.Game.Tests.Visual.SongSelect createSongSelect(); - BeatmapSetInfo imported = null; + BeatmapSetInfo? imported = null; AddStep("import huge difficulty count map", () => { @@ -956,20 +976,27 @@ namespace osu.Game.Tests.Visual.SongSelect imported = manager.Import(TestResources.CreateTestBeatmapSetInfo(50, usableRulesets))?.Value; }); - AddStep("select the first beatmap of import", () => Beatmap.Value = manager.GetWorkingBeatmap(imported.Beatmaps.First())); + AddStep("select the first beatmap of import", () => Beatmap.Value = manager.GetWorkingBeatmap(imported?.Beatmaps.First())); - DrawableCarouselBeatmapSet set = null; + DrawableCarouselBeatmapSet? set = null; AddUntilStep("Find the DrawableCarouselBeatmapSet", () => { - set = songSelect.Carousel.ChildrenOfType().FirstOrDefault(); + set = songSelect!.Carousel.ChildrenOfType().FirstOrDefault(); return set != null; }); - GroupedDifficultyIcon groupIcon = null; + GroupedDifficultyIcon groupIcon = null!; + AddUntilStep("Find group icon for different ruleset", () => { - return (groupIcon = set.ChildrenOfType() - .FirstOrDefault(icon => icon.Items.First().BeatmapInfo.Ruleset.OnlineID == 3)) != null; + var foundIcon = set.ChildrenOfType() + .FirstOrDefault(icon => icon.Items.First().BeatmapInfo.Ruleset.OnlineID == 3); + + if (foundIcon == null) + return false; + + groupIcon = foundIcon; + return true; }); AddAssert("Check ruleset is osu!", () => Ruleset.Value.OnlineID == 0); @@ -1004,7 +1031,7 @@ namespace osu.Game.Tests.Visual.SongSelect // this ruleset change should be overridden by the present. Ruleset.Value = getSwitchBeatmap().Ruleset; - songSelect.PresentScore(new ScoreInfo + songSelect!.PresentScore(new ScoreInfo { User = new APIUser { Username = "woo" }, BeatmapInfo = getPresentBeatmap(), @@ -1012,7 +1039,7 @@ namespace osu.Game.Tests.Visual.SongSelect }); }); - AddUntilStep("wait for results screen presented", () => !songSelect.IsCurrentScreen()); + AddUntilStep("wait for results screen presented", () => !songSelect!.IsCurrentScreen()); AddAssert("check beatmap is correct for score", () => Beatmap.Value.BeatmapInfo.MatchesOnlineID(getPresentBeatmap())); AddAssert("check ruleset is correct for score", () => Ruleset.Value.OnlineID == 0); @@ -1038,10 +1065,10 @@ namespace osu.Game.Tests.Visual.SongSelect // this beatmap change should be overridden by the present. Beatmap.Value = manager.GetWorkingBeatmap(getSwitchBeatmap()); - songSelect.PresentScore(TestResources.CreateTestScoreInfo(getPresentBeatmap())); + songSelect!.PresentScore(TestResources.CreateTestScoreInfo(getPresentBeatmap())); }); - AddUntilStep("wait for results screen presented", () => !songSelect.IsCurrentScreen()); + AddUntilStep("wait for results screen presented", () => !songSelect!.IsCurrentScreen()); AddAssert("check beatmap is correct for score", () => Beatmap.Value.BeatmapInfo.MatchesOnlineID(getPresentBeatmap())); AddAssert("check ruleset is correct for score", () => Ruleset.Value.OnlineID == 0); @@ -1054,23 +1081,29 @@ namespace osu.Game.Tests.Visual.SongSelect createSongSelect(); AddStep("toggle mod overlay on", () => InputManager.Key(Key.F1)); - AddUntilStep("mod overlay shown", () => songSelect.ModSelect.State.Value == Visibility.Visible); + AddUntilStep("mod overlay shown", () => songSelect!.ModSelect.State.Value == Visibility.Visible); AddStep("toggle mod overlay off", () => InputManager.Key(Key.F1)); - AddUntilStep("mod overlay hidden", () => songSelect.ModSelect.State.Value == Visibility.Hidden); + AddUntilStep("mod overlay hidden", () => songSelect!.ModSelect.State.Value == Visibility.Hidden); } private void waitForInitialSelection() { AddUntilStep("wait for initial selection", () => !Beatmap.IsDefault); - AddUntilStep("wait for difficulty panels visible", () => songSelect.Carousel.ChildrenOfType().Any()); + AddUntilStep("wait for difficulty panels visible", () => songSelect!.Carousel.ChildrenOfType().Any()); } private int getBeatmapIndex(BeatmapSetInfo set, BeatmapInfo info) => set.Beatmaps.IndexOf(info); - private NoResultsPlaceholder getPlaceholder() => songSelect.ChildrenOfType().FirstOrDefault(); + private NoResultsPlaceholder? getPlaceholder() => songSelect!.ChildrenOfType().FirstOrDefault(); - private int getCurrentBeatmapIndex() => getBeatmapIndex(songSelect.Carousel.SelectedBeatmapSet, songSelect.Carousel.SelectedBeatmapInfo); + private int getCurrentBeatmapIndex() + { + Debug.Assert(songSelect!.Carousel.SelectedBeatmapSet != null); + Debug.Assert(songSelect!.Carousel.SelectedBeatmapInfo != null); + + return getBeatmapIndex(songSelect!.Carousel.SelectedBeatmapSet, songSelect!.Carousel.SelectedBeatmapInfo); + } private int getDifficultyIconIndex(DrawableCarouselBeatmapSet set, FilterableDifficultyIcon icon) { @@ -1079,14 +1112,14 @@ namespace osu.Game.Tests.Visual.SongSelect private void addRulesetImportStep(int id) { - Live imported = null; + Live? imported = null; AddStep($"import test map for ruleset {id}", () => imported = importForRuleset(id)); // This is specifically for cases where the add is happening post song select load. // For cases where song select is null, the assertions are provided by the load checks. - AddUntilStep("wait for imported to arrive in carousel", () => songSelect == null || songSelect.Carousel.BeatmapSets.Any(s => s.ID == imported?.ID)); + AddUntilStep("wait for imported to arrive in carousel", () => songSelect == null || songSelect!.Carousel.BeatmapSets.Any(s => s.ID == imported?.ID)); } - private Live importForRuleset(int id) => manager.Import(TestResources.CreateTestBeatmapSetInfo(3, rulesets.AvailableRulesets.Where(r => r.OnlineID == id).ToArray())); + private Live? importForRuleset(int id) => manager.Import(TestResources.CreateTestBeatmapSetInfo(3, rulesets.AvailableRulesets.Where(r => r.OnlineID == id).ToArray())); private void checkMusicPlaying(bool playing) => AddUntilStep($"music {(playing ? "" : "not ")}playing", () => music.IsPlaying == playing); @@ -1098,8 +1131,8 @@ namespace osu.Game.Tests.Visual.SongSelect private void createSongSelect() { AddStep("create song select", () => LoadScreen(songSelect = new TestSongSelect())); - AddUntilStep("wait for present", () => songSelect.IsCurrentScreen()); - AddUntilStep("wait for carousel loaded", () => songSelect.Carousel.IsAlive); + AddUntilStep("wait for present", () => songSelect!.IsCurrentScreen()); + AddUntilStep("wait for carousel loaded", () => songSelect!.Carousel.IsAlive); } /// @@ -1123,12 +1156,14 @@ namespace osu.Game.Tests.Visual.SongSelect protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); - rulesets?.Dispose(); + + if (rulesets.IsNotNull()) + rulesets.Dispose(); } private class TestSongSelect : PlaySongSelect { - public Action StartRequested; + public Action? StartRequested; public new Bindable Ruleset => base.Ruleset; From 7b079c134edae38793d3c98cd14ff2f205888525 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 8 Sep 2022 15:48:04 +0900 Subject: [PATCH 337/420] Update test to actually test what was intended --- .../Visual/Gameplay/TestSceneDrawableStoryboardSprite.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableStoryboardSprite.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableStoryboardSprite.cs index 5c9e9043a7..54b0bc2080 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableStoryboardSprite.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableStoryboardSprite.cs @@ -80,7 +80,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("create sprites", () => SetContents(_ => createSprite(lookup_name, Anchor.TopLeft, Vector2.Zero))); AddAssert("sprites present", () => sprites.All(s => s.IsPresent)); AddStep("scale sprite", () => sprites.ForEach(s => s.VectorScale = new Vector2(0, 1))); - AddAssert("sprites not present", () => sprites.All(s => s.IsPresent)); + AddAssert("sprites not present", () => !sprites.All(s => s.IsPresent)); } [Test] From 9ead5e59d3262d1e18ec9ba5dce893f275385d0e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 8 Sep 2022 15:51:15 +0900 Subject: [PATCH 338/420] Fix incorrectly displaying minimum value in placeholder messaging --- osu.Game/Screens/Select/NoResultsPlaceholder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/NoResultsPlaceholder.cs b/osu.Game/Screens/Select/NoResultsPlaceholder.cs index f44aa01588..73b53defe0 100644 --- a/osu.Game/Screens/Select/NoResultsPlaceholder.cs +++ b/osu.Game/Screens/Select/NoResultsPlaceholder.cs @@ -127,7 +127,7 @@ namespace osu.Game.Screens.Select config.SetValue(OsuSetting.DisplayStarsMaximum, 10.1); }); - string lowerStar = filter.UserStarDifficulty.Min == null ? "0,0" : $"{filter.UserStarDifficulty.Min:N1}"; + string lowerStar = $"{filter.UserStarDifficulty.Min ?? 0:N1}"; string upperStar = filter.UserStarDifficulty.Max == null ? "∞" : $"{filter.UserStarDifficulty.Max:N1}"; textFlow.AddText($" the {lowerStar} - {upperStar} star difficulty filter."); From d523a2ac339c88ade0176df1d9dbe26b678cbc7e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 8 Sep 2022 15:53:08 +0900 Subject: [PATCH 339/420] Rename default value field and make `private` --- .../Select/DifficultyRangeFilterControl.cs | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/Select/DifficultyRangeFilterControl.cs b/osu.Game/Screens/Select/DifficultyRangeFilterControl.cs index eb5a797ac0..45e7ff4caa 100644 --- a/osu.Game/Screens/Select/DifficultyRangeFilterControl.cs +++ b/osu.Game/Screens/Select/DifficultyRangeFilterControl.cs @@ -65,7 +65,10 @@ namespace osu.Game.Screens.Select private class MinimumStarsSlider : StarsSlider { - public MinimumStarsSlider() : base("0") { } + public MinimumStarsSlider() + : base("0") + { + } public override LocalisableString TooltipText => Current.Value.ToString(@"0.## stars"); @@ -86,7 +89,10 @@ namespace osu.Game.Screens.Select private class MaximumStarsSlider : StarsSlider { - public MaximumStarsSlider() : base("∞") { } + public MaximumStarsSlider() + : base("∞") + { + } protected override void LoadComplete() { @@ -102,15 +108,15 @@ namespace osu.Game.Screens.Select private class StarsSlider : OsuSliderBar { + private readonly string defaultString; + public override LocalisableString TooltipText => Current.IsDefault ? UserInterfaceStrings.NoLimit : Current.Value.ToString(@"0.## stars"); - protected readonly string DefaultValue; - - public StarsSlider(string defaultValue) + protected StarsSlider(string defaultString) { - DefaultValue = defaultValue; + this.defaultString = defaultString; } protected override bool OnHover(HoverEvent e) @@ -138,7 +144,7 @@ namespace osu.Game.Screens.Select Current.BindValueChanged(current => { - currentDisplay.Text = current.NewValue != Current.Default ? current.NewValue.ToString("N1") : DefaultValue; + currentDisplay.Text = current.NewValue != Current.Default ? current.NewValue.ToString("N1") : defaultString; }, true); } } From 9e42d6167fdd630c360ec6706a6cc5f20f9ce836 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 8 Sep 2022 16:07:21 +0900 Subject: [PATCH 340/420] Fix tournament match scores resetting if `StartMatch` is called on an in-progress match --- osu.Game.Tournament/Models/TournamentMatch.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tournament/Models/TournamentMatch.cs b/osu.Game.Tournament/Models/TournamentMatch.cs index 2f72dc9257..97c2060f2c 100644 --- a/osu.Game.Tournament/Models/TournamentMatch.cs +++ b/osu.Game.Tournament/Models/TournamentMatch.cs @@ -106,13 +106,16 @@ namespace osu.Game.Tournament.Models } /// - /// Initialise this match with zeroed scores. Will be a noop if either team is not present. + /// Initialise this match with zeroed scores. Will be a noop if either team is not present or if either of the scores are non-zero. /// public void StartMatch() { if (Team1.Value == null || Team2.Value == null) return; + if (Team1Score.Value > 0 || Team2Score.Value > 0) + return; + Team1Score.Value = 0; Team2Score.Value = 0; } From b0b4da533a399327da175f01da06668f821b95e9 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 8 Sep 2022 16:59:20 +0900 Subject: [PATCH 341/420] Expose gameplay adjustments via MultiSpectatorPlayer instead --- .../Multiplayer/Spectate/MultiSpectatorPlayer.cs | 14 ++++---------- .../Multiplayer/Spectate/MultiSpectatorScreen.cs | 2 +- .../OnlinePlay/Multiplayer/Spectate/PlayerArea.cs | 8 ++++++++ .../Multiplayer/Spectate/SpectatorPlayerClock.cs | 3 --- 4 files changed, 13 insertions(+), 14 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorPlayer.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorPlayer.cs index 7e910b7946..32d0e66d90 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorPlayer.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorPlayer.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Framework.Audio; using osu.Game.Beatmaps; using osu.Game.Scoring; using osu.Game.Screens.Play; @@ -13,6 +14,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate /// public class MultiSpectatorPlayer : SpectatorPlayer { + public IAdjustableAudioComponent GameplayAdjustments => GameplayClockContainer.GameplayAdjustments; + private readonly SpectatorPlayerClock spectatorPlayerClock; /// @@ -53,15 +56,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate } protected override GameplayClockContainer CreateGameplayClockContainer(WorkingBeatmap beatmap, double gameplayStart) - { - var gameplayClockContainer = new GameplayClockContainer(spectatorPlayerClock); - - // Directionality is important, as BindAdjustments is... not actually a bidirectional bind... - // We want to ensure that any adjustments applied by the Player instance are applied to the SpectatorPlayerClock - // so they can be consumed by the spectator screen (and applied to the master clock / track). - spectatorPlayerClock.GameplayAdjustments.BindAdjustments(gameplayClockContainer.GameplayAdjustments); - - return gameplayClockContainer; - } + => new GameplayClockContainer(spectatorPlayerClock); } } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs index b7c07372dc..617977bdde 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs @@ -189,7 +189,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate if (boundAdjustments != null) masterClockContainer.GameplayAdjustments.UnbindAdjustments(boundAdjustments); - boundAdjustments = first.SpectatorPlayerClock.GameplayAdjustments; + boundAdjustments = first.GameplayAdjustments; masterClockContainer.GameplayAdjustments.BindAdjustments(boundAdjustments); } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs index 36f6631ebf..8ecb5adb76 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs @@ -42,6 +42,11 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate /// public readonly SpectatorPlayerClock SpectatorPlayerClock; + /// + /// The gameplay adjustments applied by the loaded in this area. + /// + public readonly AudioAdjustments GameplayAdjustments = new AudioAdjustments(); + /// /// The currently-loaded score. /// @@ -97,6 +102,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate { var player = new MultiSpectatorPlayer(Score, SpectatorPlayerClock); player.OnGameplayStarted += () => OnGameplayStarted?.Invoke(); + + GameplayAdjustments.BindAdjustments(player.GameplayAdjustments); + return player; })); diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/SpectatorPlayerClock.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/SpectatorPlayerClock.cs index 5667be1f4b..45615d4e19 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/SpectatorPlayerClock.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/SpectatorPlayerClock.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using osu.Framework.Audio; using osu.Framework.Timing; using osu.Game.Screens.Play; @@ -20,8 +19,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate private readonly GameplayClockContainer masterClock; - public readonly AudioAdjustments GameplayAdjustments = new AudioAdjustments(); - public double CurrentTime { get; private set; } /// From ed81297611bbe18d3df0beba6b6e07448df0b282 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 8 Sep 2022 16:48:43 +0900 Subject: [PATCH 342/420] Fix playlist items showing download button briefly during initial local presence checks --- .../Beatmaps/TestSceneBeatmapCardDownloadButton.cs | 2 ++ .../Gameplay/TestScenePlayerLocalScoreImport.cs | 4 +++- .../Visual/Gameplay/TestSceneReplayDownloadButton.cs | 2 +- .../Drawables/Cards/Buttons/DownloadButton.cs | 12 ++++++++---- osu.Game/Online/DownloadState.cs | 1 + .../Rooms/OnlinePlayBeatmapAvailabilityTracker.cs | 1 + .../Screens/OnlinePlay/DrawableRoomPlaylistItem.cs | 4 ++++ osu.Game/Screens/Play/SaveFailedScoreButton.cs | 3 +-- 8 files changed, 21 insertions(+), 8 deletions(-) diff --git a/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCardDownloadButton.cs b/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCardDownloadButton.cs index 3308ffe714..10515fd95f 100644 --- a/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCardDownloadButton.cs +++ b/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCardDownloadButton.cs @@ -9,6 +9,7 @@ using osu.Framework.Graphics; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables.Cards.Buttons; using osu.Game.Configuration; +using osu.Game.Online; using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays; using osu.Game.Resources.Localisation.Web; @@ -58,6 +59,7 @@ namespace osu.Game.Tests.Visual.Beatmaps { Anchor = Anchor.Centre, Origin = Anchor.Centre, + State = { Value = DownloadState.NotDownloaded }, Scale = new Vector2(2) }; }); diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs index f3e436e31f..247b822dc3 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs @@ -81,9 +81,11 @@ namespace osu.Game.Tests.Visual.Gameplay CreateTest(); AddUntilStep("fail screen displayed", () => Player.ChildrenOfType().First().State.Value == Visibility.Visible); + AddUntilStep("wait for button clickable", () => Player.ChildrenOfType().First().ChildrenOfType().First().Enabled.Value); + AddUntilStep("score not in database", () => Realm.Run(r => r.Find(Player.Score.ScoreInfo.ID) == null)); AddStep("click save button", () => Player.ChildrenOfType().First().ChildrenOfType().First().TriggerClick()); - AddUntilStep("score not in database", () => Realm.Run(r => r.Find(Player.Score.ScoreInfo.ID) != null)); + AddUntilStep("score in database", () => Realm.Run(r => r.Find(Player.Score.ScoreInfo.ID) != null)); } [Test] diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs index 9d70d1ef33..cd227630c1 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs @@ -202,7 +202,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddUntilStep("wait for load", () => downloadButton.IsLoaded); - AddAssert("state is not downloaded", () => downloadButton.State.Value == DownloadState.NotDownloaded); + AddAssert("state is unknown", () => downloadButton.State.Value == DownloadState.Unknown); AddAssert("button is not enabled", () => !downloadButton.ChildrenOfType().First().Enabled.Value); } diff --git a/osu.Game/Beatmaps/Drawables/Cards/Buttons/DownloadButton.cs b/osu.Game/Beatmaps/Drawables/Cards/Buttons/DownloadButton.cs index fdb43cb47e..1b15b2498c 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/Buttons/DownloadButton.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/Buttons/DownloadButton.cs @@ -17,8 +17,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards.Buttons { public class DownloadButton : BeatmapCardIconButton { - public IBindable State => state; - private readonly Bindable state = new Bindable(); + public Bindable State { get; } = new Bindable(); private readonly APIBeatmapSet beatmapSet; @@ -48,14 +47,19 @@ namespace osu.Game.Beatmaps.Drawables.Cards.Buttons { base.LoadComplete(); preferNoVideo.BindValueChanged(_ => updateState()); - state.BindValueChanged(_ => updateState(), true); + State.BindValueChanged(_ => updateState(), true); FinishTransforms(true); } private void updateState() { - switch (state.Value) + switch (State.Value) { + case DownloadState.Unknown: + Action = null; + TooltipText = string.Empty; + break; + case DownloadState.Downloading: case DownloadState.Importing: Action = null; diff --git a/osu.Game/Online/DownloadState.cs b/osu.Game/Online/DownloadState.cs index a58c40d16a..f4ecb28b90 100644 --- a/osu.Game/Online/DownloadState.cs +++ b/osu.Game/Online/DownloadState.cs @@ -5,6 +5,7 @@ namespace osu.Game.Online { public enum DownloadState { + Unknown, NotDownloaded, Downloading, Importing, diff --git a/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs b/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs index bb8ec4f6ff..7f8f9703e4 100644 --- a/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs +++ b/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs @@ -114,6 +114,7 @@ namespace osu.Game.Online.Rooms switch (downloadTracker.State.Value) { + case DownloadState.Unknown: case DownloadState.NotDownloaded: availability.Value = BeatmapAvailability.NotDownloaded(); break; diff --git a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs index beecd56b52..bda616d5c3 100644 --- a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs +++ b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs @@ -561,6 +561,10 @@ namespace osu.Game.Screens.OnlinePlay { switch (state.NewValue) { + case DownloadState.Unknown: + // Ignore initial state to ensure the button doesn't briefly appear. + break; + case DownloadState.LocallyAvailable: // Perform a local query of the beatmap by beatmap checksum, and reset the state if not matching. if (beatmapManager.QueryBeatmap(b => b.MD5Hash == beatmap.MD5Hash) == null) diff --git a/osu.Game/Screens/Play/SaveFailedScoreButton.cs b/osu.Game/Screens/Play/SaveFailedScoreButton.cs index 3f6e741dff..7358ff3de4 100644 --- a/osu.Game/Screens/Play/SaveFailedScoreButton.cs +++ b/osu.Game/Screens/Play/SaveFailedScoreButton.cs @@ -63,8 +63,7 @@ namespace osu.Game.Screens.Play if (player != null) { importedScore = realm.Run(r => r.Find(player.Score.ScoreInfo.ID)?.Detach()); - if (importedScore != null) - state.Value = DownloadState.LocallyAvailable; + state.Value = importedScore != null ? DownloadState.LocallyAvailable : DownloadState.NotDownloaded; } state.BindValueChanged(state => From b559d4ecdf8dc6c4df4b6582bc34710d734d3b35 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 8 Sep 2022 17:14:06 +0900 Subject: [PATCH 343/420] Rename GameplayAdjustments -> AdjustmentsFromMods --- .../NonVisual/GameplayClockContainerTest.cs | 2 +- osu.Game/Rulesets/UI/FrameStabilityContainer.cs | 2 +- .../Multiplayer/Spectate/MultiSpectatorPlayer.cs | 11 +++++++++-- .../Multiplayer/Spectate/MultiSpectatorScreen.cs | 6 +++--- .../OnlinePlay/Multiplayer/Spectate/PlayerArea.cs | 6 +++--- osu.Game/Screens/Play/GameplayClockContainer.cs | 2 +- osu.Game/Screens/Play/GameplayClockExtensions.cs | 4 ++-- osu.Game/Screens/Play/IGameplayClock.cs | 4 ++-- osu.Game/Screens/Play/MasterGameplayClockContainer.cs | 4 ++-- osu.Game/Screens/Play/Player.cs | 2 +- 10 files changed, 25 insertions(+), 18 deletions(-) diff --git a/osu.Game.Tests/NonVisual/GameplayClockContainerTest.cs b/osu.Game.Tests/NonVisual/GameplayClockContainerTest.cs index 80f0aaeb55..363c7a459e 100644 --- a/osu.Game.Tests/NonVisual/GameplayClockContainerTest.cs +++ b/osu.Game.Tests/NonVisual/GameplayClockContainerTest.cs @@ -27,7 +27,7 @@ namespace osu.Game.Tests.NonVisual public TestGameplayClockContainer(IFrameBasedClock underlyingClock) : base(underlyingClock) { - GameplayAdjustments.AddAdjustment(AdjustableProperty.Frequency, new BindableDouble(2.0)); + AdjustmentsFromMods.AddAdjustment(AdjustableProperty.Frequency, new BindableDouble(2.0)); } } } diff --git a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs index f0c7a398eb..6f57cfe2f7 100644 --- a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs +++ b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs @@ -265,7 +265,7 @@ namespace osu.Game.Rulesets.UI private readonly AudioAdjustments gameplayAdjustments = new AudioAdjustments(); - public IAdjustableAudioComponent GameplayAdjustments => parentGameplayClock?.GameplayAdjustments ?? gameplayAdjustments; + public IAdjustableAudioComponent AdjustmentsFromMods => parentGameplayClock?.AdjustmentsFromMods ?? gameplayAdjustments; #endregion diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorPlayer.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorPlayer.cs index 32d0e66d90..2d32bc7516 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorPlayer.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorPlayer.cs @@ -14,7 +14,10 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate /// public class MultiSpectatorPlayer : SpectatorPlayer { - public IAdjustableAudioComponent GameplayAdjustments => GameplayClockContainer.GameplayAdjustments; + /// + /// All adjustments applied to the clock of this which come from mods. + /// + public readonly AudioAdjustments ClockAdjustmentsFromMods = new AudioAdjustments(); private readonly SpectatorPlayerClock spectatorPlayerClock; @@ -56,6 +59,10 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate } protected override GameplayClockContainer CreateGameplayClockContainer(WorkingBeatmap beatmap, double gameplayStart) - => new GameplayClockContainer(spectatorPlayerClock); + { + var gameplayClockContainer = new GameplayClockContainer(spectatorPlayerClock); + ClockAdjustmentsFromMods.BindAdjustments(gameplayClockContainer.AdjustmentsFromMods); + return gameplayClockContainer; + } } } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs index 617977bdde..42a946a023 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs @@ -187,10 +187,10 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate private void bindAudioAdjustments(PlayerArea first) { if (boundAdjustments != null) - masterClockContainer.GameplayAdjustments.UnbindAdjustments(boundAdjustments); + masterClockContainer.AdjustmentsFromMods.UnbindAdjustments(boundAdjustments); - boundAdjustments = first.GameplayAdjustments; - masterClockContainer.GameplayAdjustments.BindAdjustments(boundAdjustments); + boundAdjustments = first.ClockAdjustmentsFromMods; + masterClockContainer.AdjustmentsFromMods.BindAdjustments(boundAdjustments); } private bool isCandidateAudioSource(SpectatorPlayerClock? clock) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs index 8ecb5adb76..585d464a2a 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs @@ -43,9 +43,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate public readonly SpectatorPlayerClock SpectatorPlayerClock; /// - /// The gameplay adjustments applied by the loaded in this area. + /// The clock adjustments applied by the loaded in this area. /// - public readonly AudioAdjustments GameplayAdjustments = new AudioAdjustments(); + public readonly AudioAdjustments ClockAdjustmentsFromMods = new AudioAdjustments(); /// /// The currently-loaded score. @@ -103,7 +103,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate var player = new MultiSpectatorPlayer(Score, SpectatorPlayerClock); player.OnGameplayStarted += () => OnGameplayStarted?.Invoke(); - GameplayAdjustments.BindAdjustments(player.GameplayAdjustments); + ClockAdjustmentsFromMods.BindAdjustments(player.ClockAdjustmentsFromMods); return player; })); diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index e64c628fa0..35b79fd628 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -44,7 +44,7 @@ namespace osu.Game.Screens.Play /// public double StartTime { get; protected set; } - public IAdjustableAudioComponent GameplayAdjustments { get; } = new AudioAdjustments(); + public IAdjustableAudioComponent AdjustmentsFromMods { get; } = new AudioAdjustments(); private readonly BindableBool isPaused = new BindableBool(true); diff --git a/osu.Game/Screens/Play/GameplayClockExtensions.cs b/osu.Game/Screens/Play/GameplayClockExtensions.cs index ec77a94ce9..5e88b41080 100644 --- a/osu.Game/Screens/Play/GameplayClockExtensions.cs +++ b/osu.Game/Screens/Play/GameplayClockExtensions.cs @@ -17,8 +17,8 @@ namespace osu.Game.Screens.Play double rate = clock.Rate == 0 ? 1 : Math.Sign(clock.Rate); return rate - * clock.GameplayAdjustments.AggregateFrequency.Value - * clock.GameplayAdjustments.AggregateTempo.Value; + * clock.AdjustmentsFromMods.AggregateFrequency.Value + * clock.AdjustmentsFromMods.AggregateTempo.Value; } } } diff --git a/osu.Game/Screens/Play/IGameplayClock.cs b/osu.Game/Screens/Play/IGameplayClock.cs index c58d2dbcac..83ba5f3474 100644 --- a/osu.Game/Screens/Play/IGameplayClock.cs +++ b/osu.Game/Screens/Play/IGameplayClock.cs @@ -19,9 +19,9 @@ namespace osu.Game.Screens.Play double StartTime { get; } /// - /// All adjustments applied to this clock which come from gameplay or mods. + /// All adjustments applied to this clock which come from mods. /// - IAdjustableAudioComponent GameplayAdjustments { get; } + IAdjustableAudioComponent AdjustmentsFromMods { get; } IBindable IsPaused { get; } } diff --git a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs index 20a5c95605..047f25a111 100644 --- a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs +++ b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs @@ -201,7 +201,7 @@ namespace osu.Game.Screens.Play musicController.ResetTrackAdjustments(); - track.BindAdjustments(GameplayAdjustments); + track.BindAdjustments(AdjustmentsFromMods); track.AddAdjustment(AdjustableProperty.Frequency, GameplayClock.ExternalPauseFrequencyAdjust); track.AddAdjustment(AdjustableProperty.Tempo, UserPlaybackRate); @@ -213,7 +213,7 @@ namespace osu.Game.Screens.Play if (!speedAdjustmentsApplied) return; - track.UnbindAdjustments(GameplayAdjustments); + track.UnbindAdjustments(AdjustmentsFromMods); track.RemoveAdjustment(AdjustableProperty.Frequency, GameplayClock.ExternalPauseFrequencyAdjust); track.RemoveAdjustment(AdjustableProperty.Tempo, UserPlaybackRate); diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index e93502da13..1732c6533e 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -997,7 +997,7 @@ namespace osu.Game.Screens.Play mod.ApplyToHUD(HUDOverlay); foreach (var mod in GameplayState.Mods.OfType()) - mod.ApplyToTrack(GameplayClockContainer.GameplayAdjustments); + mod.ApplyToTrack(GameplayClockContainer.AdjustmentsFromMods); updateGameplayState(); From c61c596c1f392c894e9511a3d2c4576e6ede0838 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 8 Sep 2022 17:37:02 +0900 Subject: [PATCH 344/420] Expose as readonly IAggregateAudioAdjustment --- .../Multiplayer/Spectate/MultiSpectatorPlayer.cs | 5 +++-- .../Multiplayer/Spectate/MultiSpectatorScreen.cs | 2 +- .../OnlinePlay/Multiplayer/Spectate/PlayerArea.cs | 10 +++++++--- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorPlayer.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorPlayer.cs index 2d32bc7516..8e79c89685 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorPlayer.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorPlayer.cs @@ -17,8 +17,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate /// /// All adjustments applied to the clock of this which come from mods. /// - public readonly AudioAdjustments ClockAdjustmentsFromMods = new AudioAdjustments(); + public IAggregateAudioAdjustment ClockAdjustmentsFromMods => clockAdjustmentsFromMods; + private readonly AudioAdjustments clockAdjustmentsFromMods = new AudioAdjustments(); private readonly SpectatorPlayerClock spectatorPlayerClock; /// @@ -61,7 +62,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate protected override GameplayClockContainer CreateGameplayClockContainer(WorkingBeatmap beatmap, double gameplayStart) { var gameplayClockContainer = new GameplayClockContainer(spectatorPlayerClock); - ClockAdjustmentsFromMods.BindAdjustments(gameplayClockContainer.AdjustmentsFromMods); + clockAdjustmentsFromMods.BindAdjustments(gameplayClockContainer.AdjustmentsFromMods); return gameplayClockContainer; } } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs index 42a946a023..1fd04d35f8 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs @@ -44,7 +44,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate [Resolved] private MultiplayerClient multiplayerClient { get; set; } = null!; - private AudioAdjustments? boundAdjustments; + private IAggregateAudioAdjustment? boundAdjustments; private readonly PlayerArea[] instances; private MasterGameplayClockContainer masterClockContainer = null!; diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs index 585d464a2a..8eee3d0fb6 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs @@ -45,7 +45,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate /// /// The clock adjustments applied by the loaded in this area. /// - public readonly AudioAdjustments ClockAdjustmentsFromMods = new AudioAdjustments(); + public IAggregateAudioAdjustment ClockAdjustmentsFromMods => clockAdjustmentsFromMods; /// /// The currently-loaded score. @@ -55,6 +55,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate [Resolved] private IBindable beatmap { get; set; } = null!; + private readonly AudioAdjustments clockAdjustmentsFromMods = new AudioAdjustments(); private readonly BindableDouble volumeAdjustment = new BindableDouble(); private readonly Container gameplayContent; private readonly LoadingLayer loadingLayer; @@ -101,9 +102,12 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate stack.Push(new MultiSpectatorPlayerLoader(Score, () => { var player = new MultiSpectatorPlayer(Score, SpectatorPlayerClock); - player.OnGameplayStarted += () => OnGameplayStarted?.Invoke(); - ClockAdjustmentsFromMods.BindAdjustments(player.ClockAdjustmentsFromMods); + player.OnGameplayStarted += () => + { + clockAdjustmentsFromMods.BindAdjustments(player.ClockAdjustmentsFromMods); + OnGameplayStarted?.Invoke(); + }; return player; })); From 76eae73fa4b66268c2bc87a82d330bbc130a9c7f Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 8 Sep 2022 17:41:23 +0900 Subject: [PATCH 345/420] Revert unintended change --- .../Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs index 8eee3d0fb6..96f134568d 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs @@ -102,12 +102,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate stack.Push(new MultiSpectatorPlayerLoader(Score, () => { var player = new MultiSpectatorPlayer(Score, SpectatorPlayerClock); + player.OnGameplayStarted += () => OnGameplayStarted?.Invoke(); - player.OnGameplayStarted += () => - { - clockAdjustmentsFromMods.BindAdjustments(player.ClockAdjustmentsFromMods); - OnGameplayStarted?.Invoke(); - }; + clockAdjustmentsFromMods.BindAdjustments(player.ClockAdjustmentsFromMods); return player; })); From c6521e4c7274e3cdd7a681a817622bb01e719f20 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 8 Sep 2022 17:50:27 +0900 Subject: [PATCH 346/420] Rename ordering helper method --- osu.Game/Rulesets/Scoring/HitResult.cs | 7 ++----- .../Ranking/Statistics/HitEventTimingDistributionGraph.cs | 2 +- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/osu.Game/Rulesets/Scoring/HitResult.cs b/osu.Game/Rulesets/Scoring/HitResult.cs index 3349bcf245..96e13e5861 100644 --- a/osu.Game/Rulesets/Scoring/HitResult.cs +++ b/osu.Game/Rulesets/Scoring/HitResult.cs @@ -286,14 +286,11 @@ namespace osu.Game.Rulesets.Scoring } /// - /// Ordered index of a . Used for sorting. + /// Ordered index of a . Used for consistent order when displaying hit results to the user. /// /// The to get the index of. /// The index of . - public static int OrderingIndex(this HitResult result) - { - return order.IndexOf(result); - } + public static int GetIndexForOrderedDisplay(this HitResult result) => order.IndexOf(result); } #pragma warning restore CS0618 } diff --git a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs index 52682c35a0..5335d77243 100644 --- a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs +++ b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs @@ -225,7 +225,7 @@ namespace osu.Game.Screens.Ranking.Statistics public Bar(IDictionary values, float maxValue, bool isCentre) { - this.values = values.OrderBy(v => v.Key.OrderingIndex()).ToList(); + this.values = values.OrderBy(v => v.Key.GetIndexForOrderedDisplay()).ToList(); this.maxValue = maxValue; this.isCentre = isCentre; From 0de220c45c502e618cf857b76f963471e54b8c26 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 8 Sep 2022 17:54:29 +0900 Subject: [PATCH 347/420] Change `IsExclusive` default value to `true` --- osu.Game/Online/Multiplayer/ForceGameplayStartCountdown.cs | 1 - osu.Game/Online/Multiplayer/MatchStartCountdown.cs | 1 - osu.Game/Online/Multiplayer/MultiplayerCountdown.cs | 2 +- 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game/Online/Multiplayer/ForceGameplayStartCountdown.cs b/osu.Game/Online/Multiplayer/ForceGameplayStartCountdown.cs index 81ba56f35c..bbfc5a02c6 100644 --- a/osu.Game/Online/Multiplayer/ForceGameplayStartCountdown.cs +++ b/osu.Game/Online/Multiplayer/ForceGameplayStartCountdown.cs @@ -15,6 +15,5 @@ namespace osu.Game.Online.Multiplayer [MessagePackObject] public sealed class ForceGameplayStartCountdown : MultiplayerCountdown { - public override bool IsExclusive => true; } } diff --git a/osu.Game/Online/Multiplayer/MatchStartCountdown.cs b/osu.Game/Online/Multiplayer/MatchStartCountdown.cs index b4c66e6f5b..fe65ebb059 100644 --- a/osu.Game/Online/Multiplayer/MatchStartCountdown.cs +++ b/osu.Game/Online/Multiplayer/MatchStartCountdown.cs @@ -11,6 +11,5 @@ namespace osu.Game.Online.Multiplayer [MessagePackObject] public sealed class MatchStartCountdown : MultiplayerCountdown { - public override bool IsExclusive => true; } } diff --git a/osu.Game/Online/Multiplayer/MultiplayerCountdown.cs b/osu.Game/Online/Multiplayer/MultiplayerCountdown.cs index e8e2365f7b..61637ae970 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerCountdown.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerCountdown.cs @@ -33,6 +33,6 @@ namespace osu.Game.Online.Multiplayer /// /// Whether only a single instance of this type may be active at any one time. /// - public virtual bool IsExclusive => false; + public virtual bool IsExclusive => true; } } From 53219ae4e7c7d71dd38dcf12c25b95faeaf7fca8 Mon Sep 17 00:00:00 2001 From: cdwcgt Date: Thu, 8 Sep 2022 16:59:48 +0800 Subject: [PATCH 348/420] show fixed speed in mania scroll speed settings --- .../Configuration/ManiaRulesetConfigManager.cs | 2 -- osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs | 10 +++++++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Configuration/ManiaRulesetConfigManager.cs b/osu.Game.Rulesets.Mania/Configuration/ManiaRulesetConfigManager.cs index 35ad21442e..8e09a01469 100644 --- a/osu.Game.Rulesets.Mania/Configuration/ManiaRulesetConfigManager.cs +++ b/osu.Game.Rulesets.Mania/Configuration/ManiaRulesetConfigManager.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. -#nullable disable - using System; using osu.Framework.Configuration.Tracking; using osu.Game.Configuration; diff --git a/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs b/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs index 43d4aa77a2..51a45abd70 100644 --- a/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs +++ b/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs @@ -1,8 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - +using System; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Localisation; @@ -34,7 +33,7 @@ namespace osu.Game.Rulesets.Mania LabelText = "Scrolling direction", Current = config.GetBindable(ManiaRulesetSetting.ScrollDirection) }, - new SettingsSlider + new SettingsSlider { LabelText = "Scroll speed", Current = config.GetBindable(ManiaRulesetSetting.ScrollTime), @@ -47,5 +46,10 @@ namespace osu.Game.Rulesets.Mania } }; } + + private class ManiaScorllSlider : OsuSliderBar + { + public override LocalisableString TooltipText => $"{(int)Math.Round(DrawableManiaRuleset.MAX_TIME_RANGE / Current.Value)} ({Current.Value}ms)"; + } } } From f0850c42e536178aceeb17a965c9b4e56aea0560 Mon Sep 17 00:00:00 2001 From: cdwcgt Date: Thu, 8 Sep 2022 18:16:23 +0900 Subject: [PATCH 349/420] fix typo `ManiaScorllSlider` -> `ManiaScrollSlider` --- osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs b/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs index 51a45abd70..622450ac4a 100644 --- a/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs +++ b/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs @@ -33,7 +33,7 @@ namespace osu.Game.Rulesets.Mania LabelText = "Scrolling direction", Current = config.GetBindable(ManiaRulesetSetting.ScrollDirection) }, - new SettingsSlider + new SettingsSlider { LabelText = "Scroll speed", Current = config.GetBindable(ManiaRulesetSetting.ScrollTime), @@ -47,7 +47,7 @@ namespace osu.Game.Rulesets.Mania }; } - private class ManiaScorllSlider : OsuSliderBar + private class ManiaScrollSlider : OsuSliderBar { public override LocalisableString TooltipText => $"{(int)Math.Round(DrawableManiaRuleset.MAX_TIME_RANGE / Current.Value)} ({Current.Value}ms)"; } From 5c2fb3e43453febce253e83c74f5e78e0659bdcc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 8 Sep 2022 18:22:53 +0900 Subject: [PATCH 350/420] Simplify calculation method --- .../ClicksPerSecondCalculator.cs | 55 ++++++++----------- 1 file changed, 23 insertions(+), 32 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/ClicksPerSecond/ClicksPerSecondCalculator.cs b/osu.Game/Screens/Play/HUD/ClicksPerSecond/ClicksPerSecondCalculator.cs index 20ac923b82..b8cf20ee8f 100644 --- a/osu.Game/Screens/Play/HUD/ClicksPerSecond/ClicksPerSecondCalculator.cs +++ b/osu.Game/Screens/Play/HUD/ClicksPerSecond/ClicksPerSecondCalculator.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; -using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Game.Rulesets.UI; @@ -11,56 +10,48 @@ namespace osu.Game.Screens.Play.HUD.ClicksPerSecond { public class ClicksPerSecondCalculator : Component { - private readonly List timestamps; + private readonly List timestamps = new List(); [Resolved] private IGameplayClock gameplayClock { get; set; } = null!; - [Resolved] - private DrawableRuleset drawableRuleset { get; set; } = null!; - - private double rate; - - // The latest timestamp GC seeked. Does not affect normal gameplay - // but prevents duplicate inputs on replays. - private double latestTime = double.NegativeInfinity; + [Resolved(canBeNull: true)] + private DrawableRuleset? drawableRuleset { get; set; } public int Value { get; private set; } + private IGameplayClock clock => drawableRuleset?.FrameStableClock ?? gameplayClock; + public ClicksPerSecondCalculator() { RelativeSizeAxes = Axes.Both; - timestamps = new List(); } + public void AddInputTimestamp() => timestamps.Add(clock.CurrentTime); + protected override void Update() { base.Update(); - // When pausing in replays (using the space bar) GC.TrueGameplayRate returns 0 - // To prevent CPS value being 0, we store and use the last non-zero TrueGameplayRate - if (gameplayClock.TrueGameplayRate > 0) + double latestValidTime = clock.CurrentTime; + double earliestTimeValid = latestValidTime - 1000 * gameplayClock.TrueGameplayRate; + + int count = 0; + + for (int i = timestamps.Count - 1; i >= 0; i--) { - rate = gameplayClock.TrueGameplayRate; + // handle rewinding by removing future timestamps as we go + if (timestamps[i] > latestValidTime) + { + timestamps.RemoveAt(i); + continue; + } + + if (timestamps[i] >= earliestTimeValid) + count++; } - Value = timestamps.Count(timestamp => - { - double window = 1000 * rate; - double relativeTime = drawableRuleset.FrameStableClock.CurrentTime - timestamp; - return relativeTime > 0 && relativeTime <= window; - }); - } - - public void AddTimestamp() - { - // Discard inputs if current gameplay time is not the latest - // to prevent duplicate inputs - if (drawableRuleset.FrameStableClock.CurrentTime >= latestTime) - { - timestamps.Add(drawableRuleset.FrameStableClock.CurrentTime); - latestTime = drawableRuleset.FrameStableClock.CurrentTime; - } + Value = count; } } } From 6729bb3e1a24e38c92caa542c3e67ddc1c59ad47 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 8 Sep 2022 18:23:54 +0900 Subject: [PATCH 351/420] Change `FrameStableClock` to inherit `IGameplayClock` --- osu.Game/Rulesets/UI/FrameStabilityContainer.cs | 2 +- osu.Game/Rulesets/UI/IFrameStableClock.cs | 4 ++-- osu.Game/Rulesets/UI/RulesetInputManager.cs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs index 18d0ff0bed..9446ba946b 100644 --- a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs +++ b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs @@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.UI /// [Cached(typeof(IGameplayClock))] [Cached(typeof(IFrameStableClock))] - public sealed class FrameStabilityContainer : Container, IHasReplayHandler, IFrameStableClock, IGameplayClock + public sealed class FrameStabilityContainer : Container, IHasReplayHandler, IFrameStableClock { public ReplayInputHandler? ReplayInputHandler { get; set; } diff --git a/osu.Game/Rulesets/UI/IFrameStableClock.cs b/osu.Game/Rulesets/UI/IFrameStableClock.cs index 569ef5e06c..4e50d059e9 100644 --- a/osu.Game/Rulesets/UI/IFrameStableClock.cs +++ b/osu.Game/Rulesets/UI/IFrameStableClock.cs @@ -2,11 +2,11 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Bindables; -using osu.Framework.Timing; +using osu.Game.Screens.Play; namespace osu.Game.Rulesets.UI { - public interface IFrameStableClock : IFrameBasedClock + public interface IFrameStableClock : IGameplayClock { IBindable IsCatchingUp { get; } diff --git a/osu.Game/Rulesets/UI/RulesetInputManager.cs b/osu.Game/Rulesets/UI/RulesetInputManager.cs index dcd2c4fb5d..1a97153f2f 100644 --- a/osu.Game/Rulesets/UI/RulesetInputManager.cs +++ b/osu.Game/Rulesets/UI/RulesetInputManager.cs @@ -207,7 +207,7 @@ namespace osu.Game.Rulesets.UI public bool OnPressed(KeyBindingPressEvent e) { - calculator.AddTimestamp(); + calculator.AddInputTimestamp(); return false; } From ee094e3a858e409f21b20602438595d8a770d372 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 8 Sep 2022 19:05:13 +0900 Subject: [PATCH 352/420] Rewrite tests --- .../Gameplay/TestSceneClicksPerSecond.cs | 292 +++--------------- 1 file changed, 50 insertions(+), 242 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneClicksPerSecond.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneClicksPerSecond.cs index a140460251..ed5102e3b3 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneClicksPerSecond.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneClicksPerSecond.cs @@ -3,22 +3,11 @@ using System; using System.Collections.Generic; -using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; -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.Timing; -using osu.Game.Rulesets; -using osu.Game.Rulesets.Judgements; -using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Objects; -using osu.Game.Rulesets.Osu; -using osu.Game.Rulesets.UI; -using osu.Game.Scoring; using osu.Game.Screens.Play; using osu.Game.Screens.Play.HUD.ClicksPerSecond; using osuTK; @@ -27,290 +16,109 @@ namespace osu.Game.Tests.Visual.Gameplay { public class TestSceneClicksPerSecond : OsuTestScene { - private DependencyProvidingContainer dependencyContainer = null!; private ClicksPerSecondCalculator calculator = null!; - private GameplayClockContainer gameplayClockContainer = null!; - private ManualClock manualClock = null!; - private DrawableRuleset? drawableRuleset; - private IFrameStableClock? frameStableClock; + + private TestGameplayClock manualGameplayClock = null!; [SetUpSteps] public void SetUpSteps() { AddStep("create components", () => { - var ruleset = CreateRuleset(); + manualGameplayClock = new TestGameplayClock(); - Debug.Assert(ruleset != null); - - Child = gameplayClockContainer = new GameplayClockContainer(manualClock = new ManualClock()); - gameplayClockContainer.AddRange(new Drawable[] + Child = new DependencyProvidingContainer { - drawableRuleset = new TestDrawableRuleset(frameStableClock = new TestFrameStableClock(manualClock)), - dependencyContainer = new DependencyProvidingContainer + RelativeSizeAxes = Axes.Both, + CachedDependencies = new (Type, object)[] { (typeof(IGameplayClock), manualGameplayClock) }, + Children = new Drawable[] { - RelativeSizeAxes = Axes.Both, - CachedDependencies = new (Type, object)[] + calculator = new ClicksPerSecondCalculator(), + new DependencyProvidingContainer { - (typeof(DrawableRuleset), drawableRuleset), - (typeof(IGameplayClock), gameplayClockContainer) + RelativeSizeAxes = Axes.Both, + CachedDependencies = new (Type, object)[] { (typeof(ClicksPerSecondCalculator), calculator) }, + Child = new ClicksPerSecondCounter + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Scale = new Vector2(5), + } } - } - }); + }, + }; }); } [Test] public void TestBasicConsistency() { - createCalculator(); - startClock(); - - AddStep("Create gradually increasing KPS inputs", () => - { - addInputs(generateGraduallyIncreasingKps()); - }); - - for (int i = 0; i < 10; i++) - { - seek(i * 10000); - advanceForwards(2); - int kps = i + 1; - AddAssert($"{kps} KPS", () => calculator.Value == kps); - } + seek(1000); + AddStep("add inputs in past", () => addInputs(new double[] { 0, 100, 200, 300, 400, 500, 600, 700, 800, 900 })); + checkClicksPerSecondValue(10); } [Test] public void TestRateAdjustConsistency() { - createCalculator(); - startClock(); - - AddStep("Create consistent KPS inputs", () => addInputs(generateConsistentKps(10))); - - advanceForwards(2); - - for (double i = 1; i <= 2; i += 0.25) - { - changeRate(i); - double rate = i; - AddAssert($"KPS approx. = {i}", () => MathHelper.ApproximatelyEquivalent(calculator.Value, 10 * rate, 0.5)); - } - - for (double i = 1; i >= 0.5; i -= 0.25) - { - changeRate(i); - double rate = i; - AddAssert($"KPS approx. = {i}", () => MathHelper.ApproximatelyEquivalent(calculator.Value, 10 * rate, 0.5)); - } + seek(1000); + AddStep("add inputs in past", () => addInputs(new double[] { 0, 100, 200, 300, 400, 500, 600, 700, 800, 900 })); + checkClicksPerSecondValue(10); + AddStep("set rate 0.5x", () => manualGameplayClock.TrueGameplayRate = 0.5); + checkClicksPerSecondValue(5); } [Test] public void TestInputsDiscardedOnRewind() { - createCalculator(); - startClock(); - - AddStep("Create consistent KPS inputs", () => addInputs(generateConsistentKps(10))); seek(1000); - - AddAssert("KPS = 10", () => calculator.Value == 10); - - AddStep("Create delayed inputs", () => addInputs(generateConsistentKps(10, 50))); + AddStep("add inputs in past", () => addInputs(new double[] { 0, 100, 200, 300, 400, 500, 600, 700, 800, 900 })); + checkClicksPerSecondValue(10); + seek(500); + checkClicksPerSecondValue(6); seek(1000); - AddAssert("KPS didn't changed", () => calculator.Value == 10); + checkClicksPerSecondValue(6); } - private void seekAllClocks(double time) - { - gameplayClockContainer.Seek(time); - manualClock.CurrentTime = time; - } + private void checkClicksPerSecondValue(int i) => AddAssert("clicks/s is correct", () => calculator.Value, () => Is.EqualTo(i)); - protected override Ruleset CreateRuleset() => new OsuRuleset(); + private void seekClockImmediately(double time) => manualGameplayClock.CurrentTime = time; - #region Quick steps methods - - private void createCalculator() - { - AddStep("create calculator", () => - { - dependencyContainer.Children = new Drawable[] - { - calculator = new ClicksPerSecondCalculator(), - new DependencyProvidingContainer - { - RelativeSizeAxes = Axes.Both, - CachedDependencies = new (Type, object)[] { (typeof(ClicksPerSecondCalculator), calculator) }, - Child = new ClicksPerSecondCounter // For visual debugging, has no real purpose in the tests - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Scale = new Vector2(5), - } - } - }; - }); - } - - private void seek(double time) => AddStep($"Seek clocks to {time}ms", () => seekAllClocks(time)); - - private void changeRate(double rate) => AddStep($"Change rate to x{rate}", () => manualClock.Rate = rate); - - private void advanceForwards(double time) => - AddStep($"Advance clocks {time} seconds forward.", () => - { - gameplayClockContainer.Seek(gameplayClockContainer.CurrentTime + time * manualClock.Rate); - - for (int i = 0; i < time; i++) - { - frameStableClock?.ProcessFrame(); - } - }); - - private void startClock() => AddStep("Start clocks", () => - { - gameplayClockContainer.Start(); - manualClock.Rate = 1; - }); - - #endregion - - #region Input generation + private void seek(double time) => AddStep($"Seek to {time}ms", () => seekClockImmediately(time)); private void addInputs(IEnumerable inputs) { - if (!inputs.Any()) return; - - double baseTime = gameplayClockContainer.CurrentTime; + double baseTime = manualGameplayClock.CurrentTime; foreach (double timestamp in inputs) { - seekAllClocks(timestamp); - calculator.AddTimestamp(); + seekClockImmediately(timestamp); + calculator.AddInputTimestamp(); } - seekAllClocks(baseTime); + seekClockImmediately(baseTime); } - private IEnumerable generateGraduallyIncreasingKps() + private class TestGameplayClock : IGameplayClock { - IEnumerable final = null!; + public double CurrentTime { get; set; } - for (int i = 1; i <= 10; i++) - { - var currentKps = generateConsistentKps(i, (i - 1) * 10000); + public double Rate => 1; - if (i == 1) - { - final = currentKps; - continue; - } + public bool IsRunning => true; - final = final.Concat(currentKps); - } - - return final; - } - - private IEnumerable generateConsistentKps(double kps, double start = 0, double duration = 10) - { - double end = start + 1000 * duration; - - for (; start < end; start += 1000 / kps) - { - yield return start; - } - } - - #endregion - - #region Test classes - - private class TestFrameStableClock : IFrameStableClock - { - public TestFrameStableClock(IClock source, double startTime = 0) - { - this.source = source; - - if (source is ManualClock manualClock) - { - manualClock.CurrentTime = startTime; - } - } - - public double CurrentTime => source.CurrentTime; - public double Rate => source.Rate; - public bool IsRunning => source.IsRunning; - - private readonly IClock source; + public double TrueGameplayRate { get; set; } = 1; public void ProcessFrame() { - if (source is ManualClock manualClock) - { - manualClock.CurrentTime += 1000 * Rate; - } - - TimeInfo = new FrameTimeInfo - { - Elapsed = 1000 * Rate, - Current = CurrentTime - }; } - public double ElapsedFrameTime => TimeInfo.Elapsed; - public double FramesPerSecond => 1 / ElapsedFrameTime * 1000; - public FrameTimeInfo TimeInfo { get; private set; } - - public IEnumerable NonGameplayAdjustments => Enumerable.Empty(); - public IBindable IsCatchingUp => new Bindable(); - public IBindable WaitingOnFrames => new Bindable(); + public double ElapsedFrameTime => throw new NotImplementedException(); + public double FramesPerSecond => throw new NotImplementedException(); + public FrameTimeInfo TimeInfo => throw new NotImplementedException(); + public double StartTime => throw new NotImplementedException(); + public IEnumerable NonGameplayAdjustments => throw new NotImplementedException(); + public IBindable IsPaused => throw new NotImplementedException(); } - - [SuppressMessage("ReSharper", "UnassignedGetOnlyAutoProperty")] - private class TestDrawableRuleset : DrawableRuleset - { - public override IEnumerable Objects => Enumerable.Empty(); - - public override event Action NewResult - { - add => throw new InvalidOperationException($"{nameof(NewResult)} operations not supported in test context"); - remove => throw new InvalidOperationException($"{nameof(NewResult)} operations not supported in test context"); - } - - public override event Action RevertResult - { - add => throw new InvalidOperationException($"{nameof(RevertResult)} operations not supported in test context"); - remove => throw new InvalidOperationException($"{nameof(RevertResult)} operations not supported in test context"); - } - - public override Playfield Playfield => null!; - public override Container Overlays => null!; - public override Container FrameStableComponents => null!; - public override IFrameStableClock FrameStableClock { get; } - - internal override bool FrameStablePlayback { get; set; } - public override IReadOnlyList Mods => Array.Empty(); - - public override double GameplayStartTime => 0; - public override GameplayCursorContainer Cursor => null!; - - public TestDrawableRuleset(IFrameStableClock frameStableClock) - : base(new OsuRuleset()) - { - FrameStableClock = frameStableClock; - } - - public override void SetReplayScore(Score replayScore) => throw new NotImplementedException(); - - public override void SetRecordTarget(Score score) => throw new NotImplementedException(); - - public override void RequestResume(Action continueResume) => throw new NotImplementedException(); - - public override void CancelResume() => throw new NotImplementedException(); - } - - #endregion } } From a98c6b2c1f43e910045cc75a879452a732268be5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 8 Sep 2022 19:14:23 +0900 Subject: [PATCH 353/420] Add comment metioning why we need to use `DrawableRuleset` lookup --- .../Play/HUD/ClicksPerSecond/ClicksPerSecondCalculator.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Screens/Play/HUD/ClicksPerSecond/ClicksPerSecondCalculator.cs b/osu.Game/Screens/Play/HUD/ClicksPerSecond/ClicksPerSecondCalculator.cs index b8cf20ee8f..82bf9cfa1e 100644 --- a/osu.Game/Screens/Play/HUD/ClicksPerSecond/ClicksPerSecondCalculator.cs +++ b/osu.Game/Screens/Play/HUD/ClicksPerSecond/ClicksPerSecondCalculator.cs @@ -20,6 +20,8 @@ namespace osu.Game.Screens.Play.HUD.ClicksPerSecond public int Value { get; private set; } + // Even though `FrameStabilityContainer` caches as a `GameplayClock`, we need to check it directly via `drawableRuleset` + // as this calculator is not contained within the `FrameStabilityContainer` and won't see the dependency. private IGameplayClock clock => drawableRuleset?.FrameStableClock ?? gameplayClock; public ClicksPerSecondCalculator() From 15a4eb46c4d3ea9cf387116837733fc741bd53bb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 8 Sep 2022 19:20:26 +0900 Subject: [PATCH 354/420] Rename test scene to match class name --- ...ClicksPerSecond.cs => TestSceneClicksPerSecondCalculator.cs} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename osu.Game.Tests/Visual/Gameplay/{TestSceneClicksPerSecond.cs => TestSceneClicksPerSecondCalculator.cs} (98%) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneClicksPerSecond.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneClicksPerSecondCalculator.cs similarity index 98% rename from osu.Game.Tests/Visual/Gameplay/TestSceneClicksPerSecond.cs rename to osu.Game.Tests/Visual/Gameplay/TestSceneClicksPerSecondCalculator.cs index ed5102e3b3..b740c06e03 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneClicksPerSecond.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneClicksPerSecondCalculator.cs @@ -14,7 +14,7 @@ using osuTK; namespace osu.Game.Tests.Visual.Gameplay { - public class TestSceneClicksPerSecond : OsuTestScene + public class TestSceneClicksPerSecondCalculator : OsuTestScene { private ClicksPerSecondCalculator calculator = null!; From 8de896a393d14af7c78b17b8f11eaf6d808383d7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 8 Sep 2022 19:21:15 +0900 Subject: [PATCH 355/420] 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 cd4dfec3d7..2c186a52dd 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,7 +52,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 6d0827a107..fabef87c28 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -36,7 +36,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/osu.iOS.props b/osu.iOS.props index 3316bf5a49..89166f924c 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -61,7 +61,7 @@ - + @@ -84,7 +84,7 @@ - + From c585f08a3bd3895bea3c5a94a3daf5043ed7baf1 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 8 Sep 2022 19:42:09 +0900 Subject: [PATCH 356/420] Fix still inverted condition --- .../Visual/Gameplay/TestSceneDrawableStoryboardSprite.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableStoryboardSprite.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableStoryboardSprite.cs index 54b0bc2080..3fc456c411 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableStoryboardSprite.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableStoryboardSprite.cs @@ -80,7 +80,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("create sprites", () => SetContents(_ => createSprite(lookup_name, Anchor.TopLeft, Vector2.Zero))); AddAssert("sprites present", () => sprites.All(s => s.IsPresent)); AddStep("scale sprite", () => sprites.ForEach(s => s.VectorScale = new Vector2(0, 1))); - AddAssert("sprites not present", () => !sprites.All(s => s.IsPresent)); + AddAssert("sprites not present", () => sprites.All(s => !s.IsPresent)); } [Test] From 4f22616860df51d79b4f7c7e110d0621047da1c1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 8 Sep 2022 19:41:04 +0900 Subject: [PATCH 357/420] Rename class to match osu! version --- osu.Game.Rulesets.Catch.Tests/TestSceneCatchTouchInput.cs | 8 ++++---- .../UI/{TouchInputField.cs => CatchTouchInputMapper.cs} | 2 +- osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) rename osu.Game.Rulesets.Catch/UI/{TouchInputField.cs => CatchTouchInputMapper.cs} (99%) diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatchTouchInput.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchTouchInput.cs index 24afda4cfa..b510a69f14 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatchTouchInput.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchTouchInput.cs @@ -12,19 +12,19 @@ namespace osu.Game.Rulesets.Catch.Tests [TestFixture] public class TestSceneCatchTouchInput : OsuTestScene { - private TouchInputField touchInputField = null!; + private CatchTouchInputMapper catchTouchInputMapper = null!; [SetUpSteps] public void SetUpSteps() { - AddStep("create inputfield", () => + AddStep("create input overlay", () => { Child = new CatchInputManager(new CatchRuleset().RulesetInfo) { RelativeSizeAxes = Axes.Both, Children = new Drawable[] { - touchInputField = new TouchInputField + catchTouchInputMapper = new CatchTouchInputMapper { Anchor = Anchor.Centre, Origin = Anchor.Centre @@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Catch.Tests [Test] public void TestInputField() { - AddStep("show inputfield", () => touchInputField.Show()); + AddStep("show overlay", () => catchTouchInputMapper.Show()); } } } diff --git a/osu.Game.Rulesets.Catch/UI/TouchInputField.cs b/osu.Game.Rulesets.Catch/UI/CatchTouchInputMapper.cs similarity index 99% rename from osu.Game.Rulesets.Catch/UI/TouchInputField.cs rename to osu.Game.Rulesets.Catch/UI/CatchTouchInputMapper.cs index 80453d6aa3..7607bf60cd 100644 --- a/osu.Game.Rulesets.Catch/UI/TouchInputField.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchTouchInputMapper.cs @@ -16,7 +16,7 @@ using System.Collections.Generic; namespace osu.Game.Rulesets.Catch.UI { - public class TouchInputField : VisibilityContainer + public class CatchTouchInputMapper : VisibilityContainer { public enum TouchCatchAction { diff --git a/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs b/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs index b84d0c60d5..ef2936ac94 100644 --- a/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs @@ -36,7 +36,7 @@ namespace osu.Game.Rulesets.Catch.UI [BackgroundDependencyLoader] private void load() { - KeyBindingInputManager.Add(new TouchInputField()); + KeyBindingInputManager.Add(new CatchTouchInputMapper()); } protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new CatchFramedReplayInputHandler(replay); From b9afe6f4cf86416eba1497cdeaf56ce7ed82cae6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 8 Sep 2022 19:44:29 +0900 Subject: [PATCH 358/420] Tidy up code quality --- .../UI/CatchTouchInputMapper.cs | 27 ++++++++----------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/osu.Game.Rulesets.Catch/UI/CatchTouchInputMapper.cs b/osu.Game.Rulesets.Catch/UI/CatchTouchInputMapper.cs index 7607bf60cd..bdbd958f6a 100644 --- a/osu.Game.Rulesets.Catch/UI/CatchTouchInputMapper.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchTouchInputMapper.cs @@ -18,28 +18,17 @@ namespace osu.Game.Rulesets.Catch.UI { public class CatchTouchInputMapper : VisibilityContainer { - public enum TouchCatchAction - { - MoveLeft = 0, - MoveRight = 1, - DashLeft = 2, - DashRight = 3, - None = 4 - } - private Dictionary trackedActions = new Dictionary(); private KeyBindingContainer keyBindingContainer = null!; private Container mainContent = null!; - // Fill values with null because UI is not declared in constructor private ArrowHitbox leftBox = null!; private ArrowHitbox rightBox = null!; private ArrowHitbox leftDashBox = null!; private ArrowHitbox rightDashBox = null!; - // Force input to be prossed even when hidden. public override bool PropagatePositionalInputSubTree => true; public override bool PropagateNonPositionalInputSubTree => true; @@ -151,11 +140,6 @@ namespace osu.Game.Rulesets.Catch.UI return true; } - protected override void OnDragEnd(DragEndEvent e) - { - base.OnDragEnd(e); - } - protected override void OnDrag(DragEvent e) { // I'm not sure if this is posible but let's be safe @@ -167,6 +151,7 @@ namespace osu.Game.Rulesets.Catch.UI base.OnDrag(e); } + protected override void OnTouchMove(TouchMoveEvent e) { // I'm not sure if this is posible but let's be safe @@ -240,6 +225,7 @@ namespace osu.Game.Rulesets.Catch.UI return TouchCatchAction.MoveLeft; if (rightBox.Contains(inputPosition)) return TouchCatchAction.MoveRight; + return TouchCatchAction.None; } @@ -316,5 +302,14 @@ namespace osu.Game.Rulesets.Catch.UI } } } + + public enum TouchCatchAction + { + MoveLeft = 0, + MoveRight = 1, + DashLeft = 2, + DashRight = 3, + None = 4 + } } } From a90ca94a18b3be9db81cbc8c21648dcb4e4fb412 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 8 Sep 2022 19:51:28 +0900 Subject: [PATCH 359/420] Remove outdated tests --- .../TestSceneMultiplayerMatchSongSelect.cs | 32 ------------------- 1 file changed, 32 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs index 0ecfc059e4..b87321c047 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs @@ -19,7 +19,6 @@ using osu.Game.Database; using osu.Game.Online.Rooms; using osu.Game.Overlays.Mods; using osu.Game.Rulesets; -using osu.Game.Rulesets.Catch; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Mods; @@ -68,37 +67,6 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("wait for present", () => songSelect.IsCurrentScreen() && songSelect.BeatmapSetsLoaded); } - [Test] - public void TestBeatmapRevertedOnExitIfNoSelection() - { - BeatmapInfo selectedBeatmap = null; - - AddStep("select beatmap", - () => songSelect.Carousel.SelectBeatmap(selectedBeatmap = beatmaps.Where(beatmap => beatmap.Ruleset.OnlineID == new OsuRuleset().LegacyID).ElementAt(1))); - AddUntilStep("wait for selection", () => Beatmap.Value.BeatmapInfo.Equals(selectedBeatmap)); - - AddStep("exit song select", () => songSelect.Exit()); - AddAssert("beatmap reverted", () => Beatmap.IsDefault); - } - - [Test] - public void TestModsRevertedOnExitIfNoSelection() - { - AddStep("change mods", () => SelectedMods.Value = new[] { new OsuModDoubleTime() }); - - AddStep("exit song select", () => songSelect.Exit()); - AddAssert("mods reverted", () => SelectedMods.Value.Count == 0); - } - - [Test] - public void TestRulesetRevertedOnExitIfNoSelection() - { - AddStep("change ruleset", () => Ruleset.Value = new CatchRuleset().RulesetInfo); - - AddStep("exit song select", () => songSelect.Exit()); - AddAssert("ruleset reverted", () => Ruleset.Value.Equals(new OsuRuleset().RulesetInfo)); - } - [Test] public void TestBeatmapConfirmed() { From 09fa24c563a8cf476bcf11d88228aadee486c15b Mon Sep 17 00:00:00 2001 From: cdwcgt Date: Thu, 8 Sep 2022 19:12:54 +0800 Subject: [PATCH 360/420] new display format --- .../Configuration/ManiaRulesetConfigManager.cs | 2 +- osu.Game.Rulesets.Mania/ManiaSettingsSubsection.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 8e09a01469..64e1f75e2c 100644 --- a/osu.Game.Rulesets.Mania/Configuration/ManiaRulesetConfigManager.cs +++ b/osu.Game.Rulesets.Mania/Configuration/ManiaRulesetConfigManager.cs @@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Mania.Configuration scrollTime => new SettingDescription( rawValue: scrollTime, name: "Scroll Speed", - value: $"{(int)Math.Round(DrawableManiaRuleset.MAX_TIME_RANGE / scrollTime)} ({scrollTime}ms)" + value: $"{scrollTime}ms (speed {(int)Math.Round(DrawableManiaRuleset.MAX_TIME_RANGE / scrollTime)})" ) ) }; diff --git a/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs b/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs index 622450ac4a..e239068d1d 100644 --- a/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs +++ b/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs @@ -49,7 +49,7 @@ namespace osu.Game.Rulesets.Mania private class ManiaScrollSlider : OsuSliderBar { - public override LocalisableString TooltipText => $"{(int)Math.Round(DrawableManiaRuleset.MAX_TIME_RANGE / Current.Value)} ({Current.Value}ms)"; + public override LocalisableString TooltipText => $"{Current.Value}ms (speed {(int)Math.Round(DrawableManiaRuleset.MAX_TIME_RANGE / Current.Value)})"; } } } From 888d8b281747b7a34072cc270152aafbe21163d9 Mon Sep 17 00:00:00 2001 From: Josh <43808099+josh-codes@users.noreply.github.com> Date: Thu, 8 Sep 2022 20:39:53 +0800 Subject: [PATCH 361/420] Removed redudent code & converted use of `OnDrag to `OnMouseMove` --- .../UI/CatchTouchInputMapper.cs | 47 +++++++------------ 1 file changed, 17 insertions(+), 30 deletions(-) diff --git a/osu.Game.Rulesets.Catch/UI/CatchTouchInputMapper.cs b/osu.Game.Rulesets.Catch/UI/CatchTouchInputMapper.cs index bdbd958f6a..7886df69b6 100644 --- a/osu.Game.Rulesets.Catch/UI/CatchTouchInputMapper.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchTouchInputMapper.cs @@ -13,6 +13,7 @@ using osu.Game.Graphics; using osuTK.Graphics; using osuTK; using System.Collections.Generic; +using osuTK.Input; namespace osu.Game.Rulesets.Catch.UI { @@ -119,45 +120,29 @@ namespace osu.Game.Rulesets.Catch.UI protected override bool OnMouseDown(MouseDownEvent e) { - if (getTouchCatchActionFromInput(e.ScreenSpaceMousePosition) == TouchCatchAction.None) - return false; - - handleDown(e.Button, e.ScreenSpaceMousePosition); - return true; + return handleDown(e.Button, e.ScreenSpaceMousePosition); } protected override void OnMouseUp(MouseUpEvent e) { - if (getTouchCatchActionFromInput(e.ScreenSpaceMousePosition) == TouchCatchAction.None) - return; - handleUp(e.Button); base.OnMouseUp(e); } - protected override bool OnDragStart(DragStartEvent e) + protected override bool OnMouseMove(MouseMoveEvent e) { - return true; - } + TouchCatchAction touchCatchAction = getTouchCatchActionFromInput(e.ScreenSpaceMousePosition); - protected override void OnDrag(DragEvent e) - { - // I'm not sure if this is posible but let's be safe - if (!trackedActions.ContainsKey(e.Button)) - trackedActions.Add(e.Button, TouchCatchAction.None); + // Loop through the buttons to avoid keeping a button pressed if both mouse buttons are pressed. + foreach (MouseButton i in e.PressedButtons) + trackedActions[i] = touchCatchAction; - trackedActions[e.Button] = getTouchCatchActionFromInput(e.ScreenSpaceMousePosition); calculateActiveKeys(); - - base.OnDrag(e); + return true; } protected override void OnTouchMove(TouchMoveEvent e) { - // I'm not sure if this is posible but let's be safe - if (!trackedActions.ContainsKey(e.Touch.Source)) - trackedActions.Add(e.Touch.Source, TouchCatchAction.None); - trackedActions[e.Touch.Source] = getTouchCatchActionFromInput(e.ScreenSpaceTouch.Position); calculateActiveKeys(); @@ -194,18 +179,20 @@ namespace osu.Game.Rulesets.Catch.UI keyBindingContainer.TriggerReleased(CatchAction.Dash); } - private void handleDown(object source, Vector2 position) + private bool handleDown(object source, Vector2 position) { - Show(); - TouchCatchAction catchAction = getTouchCatchActionFromInput(position); - // Not too sure how this can happen, but let's avoid throwing. - if (trackedActions.ContainsKey(source)) - return; + if (catchAction == TouchCatchAction.None) + return false; + + Show(); + + trackedActions[source] = catchAction; - trackedActions.Add(source, catchAction); calculateActiveKeys(); + + return true; } private void handleUp(object source) From 45239fc737423f05f63c93b05e72ef20a33ef267 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 8 Sep 2022 23:03:15 +0900 Subject: [PATCH 362/420] Update `TrueGameplayRate` accessing --- .../Play/HUD/ClicksPerSecond/ClicksPerSecondCalculator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/HUD/ClicksPerSecond/ClicksPerSecondCalculator.cs b/osu.Game/Screens/Play/HUD/ClicksPerSecond/ClicksPerSecondCalculator.cs index 82bf9cfa1e..04774b974f 100644 --- a/osu.Game/Screens/Play/HUD/ClicksPerSecond/ClicksPerSecondCalculator.cs +++ b/osu.Game/Screens/Play/HUD/ClicksPerSecond/ClicksPerSecondCalculator.cs @@ -36,7 +36,7 @@ namespace osu.Game.Screens.Play.HUD.ClicksPerSecond base.Update(); double latestValidTime = clock.CurrentTime; - double earliestTimeValid = latestValidTime - 1000 * gameplayClock.TrueGameplayRate; + double earliestTimeValid = latestValidTime - 1000 * gameplayClock.GetTrueGameplayRate(); int count = 0; From 27aa3552dc03846cf7d23eb7b731b574be20e147 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Sep 2022 00:00:08 +0900 Subject: [PATCH 363/420] Update in line with `TrueGameplayRate` changes --- .../Visual/Gameplay/TestSceneClicksPerSecondCalculator.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneClicksPerSecondCalculator.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneClicksPerSecondCalculator.cs index b740c06e03..2dad5e2c32 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneClicksPerSecondCalculator.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneClicksPerSecondCalculator.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using NUnit.Framework; +using osu.Framework.Audio; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Testing; @@ -107,7 +108,9 @@ namespace osu.Game.Tests.Visual.Gameplay public bool IsRunning => true; - public double TrueGameplayRate { get; set; } = 1; + public double TrueGameplayRate { set => adjustableAudioComponent.Tempo.Value = value; } + + private readonly AudioAdjustments adjustableAudioComponent = new AudioAdjustments(); public void ProcessFrame() { @@ -117,6 +120,9 @@ namespace osu.Game.Tests.Visual.Gameplay public double FramesPerSecond => throw new NotImplementedException(); public FrameTimeInfo TimeInfo => throw new NotImplementedException(); public double StartTime => throw new NotImplementedException(); + + public IAdjustableAudioComponent AdjustmentsFromMods => adjustableAudioComponent; + public IEnumerable NonGameplayAdjustments => throw new NotImplementedException(); public IBindable IsPaused => throw new NotImplementedException(); } From d1e27e8a69d061492e7cf5c8223840de19d6de96 Mon Sep 17 00:00:00 2001 From: cdwcgt Date: Thu, 8 Sep 2022 23:14:34 +0800 Subject: [PATCH 364/420] add arrow short cut for skin editor basically from `ComposeBlueprintContainer` because they have the same logic --- .../Skinning/Editor/SkinBlueprintContainer.cs | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/osu.Game/Skinning/Editor/SkinBlueprintContainer.cs b/osu.Game/Skinning/Editor/SkinBlueprintContainer.cs index 46f5c1e67f..5a1ef34151 100644 --- a/osu.Game/Skinning/Editor/SkinBlueprintContainer.cs +++ b/osu.Game/Skinning/Editor/SkinBlueprintContainer.cs @@ -11,9 +11,12 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Screens; using osu.Framework.Testing; +using osu.Framework.Input.Events; using osu.Game.Rulesets.Edit; using osu.Game.Screens; using osu.Game.Screens.Edit.Compose.Components; +using osuTK; +using osuTK.Input; namespace osu.Game.Skinning.Editor { @@ -90,6 +93,47 @@ namespace osu.Game.Skinning.Editor base.AddBlueprintFor(item); } + protected override bool OnKeyDown(KeyDownEvent e) + { + switch (e.Key) + { + case Key.Left: + moveSelection(new Vector2(-1, 0)); + return true; + + case Key.Right: + moveSelection(new Vector2(1, 0)); + return true; + + case Key.Up: + moveSelection(new Vector2(0, -1)); + return true; + + case Key.Down: + moveSelection(new Vector2(0, 1)); + return true; + } + + return false; + } + + /// + /// Move the current selection spatially by the specified delta, in screen coordinates (ie. the same coordinates as the blueprints). + /// + /// + private void moveSelection(Vector2 delta) + { + var firstBlueprint = SelectionHandler.SelectedBlueprints.FirstOrDefault(); + + if (firstBlueprint == null) + return; + + // convert to game space coordinates + delta = firstBlueprint.ToScreenSpace(delta) - firstBlueprint.ToScreenSpace(Vector2.Zero); + + SelectionHandler.HandleMovement(new MoveSelectionEvent(firstBlueprint, delta)); + } + protected override SelectionHandler CreateSelectionHandler() => new SkinSelectionHandler(); protected override SelectionBlueprint CreateBlueprintFor(ISkinnableDrawable component) From 2e775e688643176142460c5cc71aee748d7d1418 Mon Sep 17 00:00:00 2001 From: cdwcgt Date: Thu, 8 Sep 2022 23:47:55 +0800 Subject: [PATCH 365/420] Add test for object move --- osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs index bd274dfef5..19d20dd552 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.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. -#nullable disable - using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; @@ -22,7 +20,7 @@ namespace osu.Game.Tests.Visual.Gameplay { public class TestSceneSkinEditor : PlayerTestScene { - private SkinEditor skinEditor; + private SkinEditor skinEditor = null!; protected override bool Autoplay => true; @@ -54,7 +52,7 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestEditComponent() { - BarHitErrorMeter hitErrorMeter = null; + BarHitErrorMeter hitErrorMeter = null!; AddStep("select bar hit error blueprint", () => { @@ -65,6 +63,10 @@ namespace osu.Game.Tests.Visual.Gameplay skinEditor.SelectedComponents.Add(blueprint.Item); }); + AddStep("move by keyboard", () => InputManager.Key(Key.Right)); + + AddAssert("hitErrorMeter moved", () => hitErrorMeter.X != 0); + AddAssert("value is default", () => hitErrorMeter.JudgementLineThickness.IsDefault); AddStep("hover first slider", () => From 28477f3b9763fe876030b24c03ea884c3bf35930 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 9 Sep 2022 08:55:35 +0900 Subject: [PATCH 366/420] Fix inspection --- osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs index 19d20dd552..578718b7c9 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs @@ -20,7 +20,7 @@ namespace osu.Game.Tests.Visual.Gameplay { public class TestSceneSkinEditor : PlayerTestScene { - private SkinEditor skinEditor = null!; + private SkinEditor? skinEditor; protected override bool Autoplay => true; @@ -40,13 +40,13 @@ namespace osu.Game.Tests.Visual.Gameplay Player.ScaleTo(0.4f); LoadComponentAsync(skinEditor = new SkinEditor(Player), Add); }); - AddUntilStep("wait for loaded", () => skinEditor.IsLoaded); + AddUntilStep("wait for loaded", () => skinEditor!.IsLoaded); } [Test] public void TestToggleEditor() { - AddToggleStep("toggle editor visibility", _ => skinEditor.ToggleVisibility()); + AddToggleStep("toggle editor visibility", _ => skinEditor!.ToggleVisibility()); } [Test] @@ -59,7 +59,7 @@ namespace osu.Game.Tests.Visual.Gameplay var blueprint = skinEditor.ChildrenOfType().First(b => b.Item is BarHitErrorMeter); hitErrorMeter = (BarHitErrorMeter)blueprint.Item; - skinEditor.SelectedComponents.Clear(); + skinEditor!.SelectedComponents.Clear(); skinEditor.SelectedComponents.Add(blueprint.Item); }); From 731d3f3b6358710943b1ed5230bedc3b7c9cfbcf Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 8 Sep 2022 22:06:44 +0900 Subject: [PATCH 367/420] Add MaximumStatistics upgrade for databased scores --- osu.Game/BackgroundBeatmapProcessor.cs | 55 ++++++++++++++++++- osu.Game/OsuGameBase.cs | 2 +- osu.Game/Scoring/ScoreImporter.cs | 73 +++++++++++++++++++++++++- osu.Game/Scoring/ScoreManager.cs | 11 +++- 4 files changed, 136 insertions(+), 5 deletions(-) diff --git a/osu.Game/BackgroundBeatmapProcessor.cs b/osu.Game/BackgroundBeatmapProcessor.cs index 14fdb2e1ef..405386660c 100644 --- a/osu.Game/BackgroundBeatmapProcessor.cs +++ b/osu.Game/BackgroundBeatmapProcessor.cs @@ -7,6 +7,7 @@ using System.Diagnostics; using System.Linq; using System.Threading; using System.Threading.Tasks; +using Newtonsoft.Json; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -14,6 +15,7 @@ using osu.Framework.Logging; using osu.Game.Beatmaps; using osu.Game.Database; using osu.Game.Rulesets; +using osu.Game.Scoring; using osu.Game.Screens.Play; namespace osu.Game @@ -23,6 +25,9 @@ namespace osu.Game [Resolved] private RulesetStore rulesetStore { get; set; } = null!; + [Resolved] + private ScoreManager scoreManager { get; set; } = null!; + [Resolved] private RealmAccess realmAccess { get; set; } = null!; @@ -41,11 +46,12 @@ namespace osu.Game { base.LoadComplete(); - Task.Run(() => + Task.Run(async () => { Logger.Log("Beginning background beatmap processing.."); checkForOutdatedStarRatings(); processBeatmapSetsWithMissingMetrics(); + await processScoresWithMissingStatistics(); }).ContinueWith(t => { if (t.Exception?.InnerException is ObjectDisposedException) @@ -140,5 +146,52 @@ namespace osu.Game }); } } + + private async Task processScoresWithMissingStatistics() + { + HashSet scoreIds = new HashSet(); + + Logger.Log("Querying for scores to reprocess..."); + + realmAccess.Run(r => + { + foreach (var score in r.All()) + { + if (score.Statistics.Sum(kvp => kvp.Value) > 0 && score.MaximumStatistics.Sum(kvp => kvp.Value) == 0) + scoreIds.Add(score.ID); + } + }); + + Logger.Log($"Found {scoreIds.Count} scores which require reprocessing."); + + foreach (var id in scoreIds) + { + while (localUserPlayInfo?.IsPlaying.Value == true) + { + Logger.Log("Background processing sleeping due to active gameplay..."); + Thread.Sleep(TimeToSleepDuringGameplay); + } + + try + { + var score = scoreManager.Query(s => s.ID == id); + + await scoreManager.PopulateMaximumStatistics(score); + + // Can't use async overload because we're not on the update thread. + // ReSharper disable once MethodHasAsyncOverload + realmAccess.Write(r => + { + r.Find(id).MaximumStatisticsJson = JsonConvert.SerializeObject(score.MaximumStatistics); + }); + + Logger.Log($"Populated maximum statistics for score {id}"); + } + catch (Exception e) + { + Logger.Log(@$"Failed to populate maximum statistics for {id}: {e}"); + } + } + } } } diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index c95a281f09..c95dde8759 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -279,7 +279,7 @@ namespace osu.Game dependencies.Cache(difficultyCache = new BeatmapDifficultyCache()); // ordering is important here to ensure foreign keys rules are not broken in ModelStore.Cleanup() - dependencies.Cache(ScoreManager = new ScoreManager(RulesetStore, () => BeatmapManager, Storage, realm, API, LocalConfig)); + dependencies.Cache(ScoreManager = new ScoreManager(RulesetStore, () => BeatmapManager, Storage, realm, API, LocalConfig, difficultyCache)); dependencies.Cache(BeatmapManager = new BeatmapManager(Storage, realm, API, Audio, Resources, Host, defaultBeatmap, difficultyCache, performOnlineLookups: true)); diff --git a/osu.Game/Scoring/ScoreImporter.cs b/osu.Game/Scoring/ScoreImporter.cs index 45f827354e..488f5815f8 100644 --- a/osu.Game/Scoring/ScoreImporter.cs +++ b/osu.Game/Scoring/ScoreImporter.cs @@ -5,7 +5,9 @@ using System; using System.Collections.Generic; using System.Linq; using System.Threading; +using System.Threading.Tasks; using Newtonsoft.Json; +using osu.Framework.Extensions; using osu.Framework.Logging; using osu.Framework.Platform; using osu.Game.Beatmaps; @@ -16,6 +18,8 @@ using osu.Game.Scoring.Legacy; using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests.Responses; +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Scoring; using Realms; namespace osu.Game.Scoring @@ -28,15 +32,17 @@ namespace osu.Game.Scoring private readonly RulesetStore rulesets; private readonly Func beatmaps; + private readonly BeatmapDifficultyCache? difficultyCache; private readonly IAPIProvider api; - public ScoreImporter(RulesetStore rulesets, Func beatmaps, Storage storage, RealmAccess realm, IAPIProvider api) + public ScoreImporter(RulesetStore rulesets, Func beatmaps, Storage storage, RealmAccess realm, IAPIProvider api, BeatmapDifficultyCache? difficultyCache = null) : base(storage, realm) { this.rulesets = rulesets; this.beatmaps = beatmaps; this.api = api; + this.difficultyCache = difficultyCache; } protected override ScoreInfo? CreateModel(ArchiveReader archive) @@ -71,6 +77,8 @@ namespace osu.Game.Scoring if (model.BeatmapInfo == null) throw new ArgumentNullException(nameof(model.BeatmapInfo)); if (model.Ruleset == null) throw new ArgumentNullException(nameof(model.Ruleset)); + PopulateMaximumStatistics(model).WaitSafely(); + if (string.IsNullOrEmpty(model.StatisticsJson)) model.StatisticsJson = JsonConvert.SerializeObject(model.Statistics); @@ -78,6 +86,69 @@ namespace osu.Game.Scoring model.MaximumStatisticsJson = JsonConvert.SerializeObject(model.MaximumStatistics); } + /// + /// Populates the for a given . + /// + /// The score to populate the statistics of. + public async Task PopulateMaximumStatistics(ScoreInfo score) + { + if (score.MaximumStatistics.Select(kvp => kvp.Value).Sum() > 0) + return; + + var beatmap = score.BeatmapInfo.Detach(); + var ruleset = score.Ruleset.Detach(); + + // Populate the maximum statistics. + HitResult maxBasicResult = ruleset.CreateInstance().GetHitResults() + .Select(h => h.result) + .Where(h => h.IsBasic()) + .OrderByDescending(Judgement.ToNumericResult).First(); + + foreach ((HitResult result, int count) in score.Statistics) + { + switch (result) + { + case HitResult.LargeTickHit: + case HitResult.LargeTickMiss: + score.MaximumStatistics[HitResult.LargeTickHit] = score.MaximumStatistics.GetValueOrDefault(HitResult.LargeTickHit) + count; + break; + + case HitResult.SmallTickHit: + case HitResult.SmallTickMiss: + score.MaximumStatistics[HitResult.SmallTickHit] = score.MaximumStatistics.GetValueOrDefault(HitResult.SmallTickHit) + count; + break; + + case HitResult.IgnoreHit: + case HitResult.IgnoreMiss: + case HitResult.SmallBonus: + case HitResult.LargeBonus: + break; + + default: + score.MaximumStatistics[maxBasicResult] = score.MaximumStatistics.GetValueOrDefault(maxBasicResult) + count; + break; + } + } + + if (!score.IsLegacyScore) + return; + +#pragma warning disable CS0618 + if (difficultyCache == null) + throw new InvalidOperationException($"Cannot populate legacy score statistics without a {nameof(BeatmapDifficultyCache)}."); + + // In osu! and osu!mania, some judgements affect combo but aren't stored to scores. + // A special hit result is used to pad out the combo value to match, based on the max combo from the difficulty attributes. + StarDifficulty? difficulty = await difficultyCache.GetDifficultyAsync(beatmap, ruleset, score.Mods); + if (difficulty == null) + throw new InvalidOperationException("Failed to populate maximum statistics due to missing difficulty attributes."); + + int maxComboFromStatistics = score.MaximumStatistics.Where(kvp => kvp.Key.AffectsCombo()).Select(kvp => kvp.Value).DefaultIfEmpty(0).Sum(); + if (difficulty.Value.MaxCombo > maxComboFromStatistics) + score.MaximumStatistics[HitResult.LegacyComboIncrease] = difficulty.Value.MaxCombo - maxComboFromStatistics; +#pragma warning restore CS0618 + } + protected override void PostImport(ScoreInfo model, Realm realm, bool batchImport) { base.PostImport(model, realm, batchImport); diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index 782590114f..87bb834a75 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -28,12 +28,13 @@ namespace osu.Game.Scoring private readonly OsuConfigManager configManager; private readonly ScoreImporter scoreImporter; - public ScoreManager(RulesetStore rulesets, Func beatmaps, Storage storage, RealmAccess realm, IAPIProvider api, OsuConfigManager configManager = null) + public ScoreManager(RulesetStore rulesets, Func beatmaps, Storage storage, RealmAccess realm, IAPIProvider api, + OsuConfigManager configManager = null, BeatmapDifficultyCache difficultyCache = null) : base(storage, realm) { this.configManager = configManager; - scoreImporter = new ScoreImporter(rulesets, beatmaps, storage, realm, api) + scoreImporter = new ScoreImporter(rulesets, beatmaps, storage, realm, api, difficultyCache) { PostNotification = obj => PostNotification?.Invoke(obj) }; @@ -178,6 +179,12 @@ namespace osu.Game.Scoring public Live Import(ScoreInfo item, ArchiveReader archive = null, bool batchImport = false, CancellationToken cancellationToken = default) => scoreImporter.ImportModel(item, archive, batchImport, cancellationToken); + /// + /// Populates the for a given . + /// + /// The score to populate the statistics of. + public Task PopulateMaximumStatistics(ScoreInfo score) => scoreImporter.PopulateMaximumStatistics(score); + #region Implementation of IPresentImports public Action>> PresentImport From 3b932b46ca8870c25e04a4fdcd1db3e792c18212 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 9 Sep 2022 09:59:39 +0900 Subject: [PATCH 368/420] Fix entire TPL thread potentially being consumed during gameplay --- osu.Game/BackgroundBeatmapProcessor.cs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/osu.Game/BackgroundBeatmapProcessor.cs b/osu.Game/BackgroundBeatmapProcessor.cs index 405386660c..7a38abd071 100644 --- a/osu.Game/BackgroundBeatmapProcessor.cs +++ b/osu.Game/BackgroundBeatmapProcessor.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; -using System.Threading; using System.Threading.Tasks; using Newtonsoft.Json; using osu.Framework.Allocation; @@ -50,7 +49,7 @@ namespace osu.Game { Logger.Log("Beginning background beatmap processing.."); checkForOutdatedStarRatings(); - processBeatmapSetsWithMissingMetrics(); + await processBeatmapSetsWithMissingMetrics(); await processScoresWithMissingStatistics(); }).ContinueWith(t => { @@ -100,7 +99,7 @@ namespace osu.Game } } - private void processBeatmapSetsWithMissingMetrics() + private async Task processBeatmapSetsWithMissingMetrics() { HashSet beatmapSetIds = new HashSet(); @@ -124,7 +123,7 @@ namespace osu.Game while (localUserPlayInfo?.IsPlaying.Value == true) { Logger.Log("Background processing sleeping due to active gameplay..."); - Thread.Sleep(TimeToSleepDuringGameplay); + await Task.Delay(TimeToSleepDuringGameplay); } realmAccess.Run(r => @@ -169,7 +168,7 @@ namespace osu.Game while (localUserPlayInfo?.IsPlaying.Value == true) { Logger.Log("Background processing sleeping due to active gameplay..."); - Thread.Sleep(TimeToSleepDuringGameplay); + await Task.Delay(TimeToSleepDuringGameplay); } try From ba2ef424d4cac407692ad78c7c1c75b44110d419 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 9 Sep 2022 11:30:36 +0900 Subject: [PATCH 369/420] Turn score ids into `ulong`s --- osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs | 2 +- osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs b/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs index cfa9f77634..a0f76c4e14 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs @@ -154,7 +154,7 @@ namespace osu.Game.Tests.Visual.Online }); } - private int onlineID = 1; + private ulong onlineID = 1; private APIScoresCollection createScores() { diff --git a/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs b/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs index d7b97cdddf..2c9f250028 100644 --- a/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs +++ b/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs @@ -81,12 +81,12 @@ namespace osu.Game.Online.API.Requests.Responses public int? LegacyTotalScore { get; set; } [JsonProperty("legacy_score_id")] - public uint? LegacyScoreId { get; set; } + public ulong? LegacyScoreId { get; set; } #region osu-web API additions (not stored to database). [JsonProperty("id")] - public long? ID { get; set; } + public ulong? ID { get; set; } [JsonProperty("user")] public APIUser? User { get; set; } @@ -190,6 +190,6 @@ namespace osu.Game.Online.API.Requests.Responses MaximumStatistics = score.MaximumStatistics.Where(kvp => kvp.Value != 0).ToDictionary(kvp => kvp.Key, kvp => kvp.Value), }; - public long OnlineID => ID ?? -1; + public long OnlineID => (long?)ID ?? -1; } } From 08d0c08750ae7341334a99aefa8920c9922f36f3 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 9 Sep 2022 13:57:01 +0900 Subject: [PATCH 370/420] Fix async exception by using difficulty calculator directly --- osu.Game/BackgroundBeatmapProcessor.cs | 2 +- osu.Game/OsuGameBase.cs | 2 +- osu.Game/Scoring/ScoreImporter.cs | 34 ++++++++++++-------------- osu.Game/Scoring/ScoreManager.cs | 6 ++--- 4 files changed, 20 insertions(+), 24 deletions(-) diff --git a/osu.Game/BackgroundBeatmapProcessor.cs b/osu.Game/BackgroundBeatmapProcessor.cs index 7a38abd071..071862146f 100644 --- a/osu.Game/BackgroundBeatmapProcessor.cs +++ b/osu.Game/BackgroundBeatmapProcessor.cs @@ -175,7 +175,7 @@ namespace osu.Game { var score = scoreManager.Query(s => s.ID == id); - await scoreManager.PopulateMaximumStatistics(score); + scoreManager.PopulateMaximumStatistics(score); // Can't use async overload because we're not on the update thread. // ReSharper disable once MethodHasAsyncOverload diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index c95dde8759..c95a281f09 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -279,7 +279,7 @@ namespace osu.Game dependencies.Cache(difficultyCache = new BeatmapDifficultyCache()); // ordering is important here to ensure foreign keys rules are not broken in ModelStore.Cleanup() - dependencies.Cache(ScoreManager = new ScoreManager(RulesetStore, () => BeatmapManager, Storage, realm, API, LocalConfig, difficultyCache)); + dependencies.Cache(ScoreManager = new ScoreManager(RulesetStore, () => BeatmapManager, Storage, realm, API, LocalConfig)); dependencies.Cache(BeatmapManager = new BeatmapManager(Storage, realm, API, Audio, Resources, Host, defaultBeatmap, difficultyCache, performOnlineLookups: true)); diff --git a/osu.Game/Scoring/ScoreImporter.cs b/osu.Game/Scoring/ScoreImporter.cs index 488f5815f8..5c8e21014c 100644 --- a/osu.Game/Scoring/ScoreImporter.cs +++ b/osu.Game/Scoring/ScoreImporter.cs @@ -3,11 +3,10 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Threading; -using System.Threading.Tasks; using Newtonsoft.Json; -using osu.Framework.Extensions; using osu.Framework.Logging; using osu.Framework.Platform; using osu.Game.Beatmaps; @@ -32,17 +31,15 @@ namespace osu.Game.Scoring private readonly RulesetStore rulesets; private readonly Func beatmaps; - private readonly BeatmapDifficultyCache? difficultyCache; private readonly IAPIProvider api; - public ScoreImporter(RulesetStore rulesets, Func beatmaps, Storage storage, RealmAccess realm, IAPIProvider api, BeatmapDifficultyCache? difficultyCache = null) + public ScoreImporter(RulesetStore rulesets, Func beatmaps, Storage storage, RealmAccess realm, IAPIProvider api) : base(storage, realm) { this.rulesets = rulesets; this.beatmaps = beatmaps; this.api = api; - this.difficultyCache = difficultyCache; } protected override ScoreInfo? CreateModel(ArchiveReader archive) @@ -77,7 +74,7 @@ namespace osu.Game.Scoring if (model.BeatmapInfo == null) throw new ArgumentNullException(nameof(model.BeatmapInfo)); if (model.Ruleset == null) throw new ArgumentNullException(nameof(model.Ruleset)); - PopulateMaximumStatistics(model).WaitSafely(); + PopulateMaximumStatistics(model); if (string.IsNullOrEmpty(model.StatisticsJson)) model.StatisticsJson = JsonConvert.SerializeObject(model.Statistics); @@ -90,19 +87,22 @@ namespace osu.Game.Scoring /// Populates the for a given . /// /// The score to populate the statistics of. - public async Task PopulateMaximumStatistics(ScoreInfo score) + public void PopulateMaximumStatistics(ScoreInfo score) { if (score.MaximumStatistics.Select(kvp => kvp.Value).Sum() > 0) return; var beatmap = score.BeatmapInfo.Detach(); var ruleset = score.Ruleset.Detach(); + var rulesetInstance = ruleset.CreateInstance(); + + Debug.Assert(rulesetInstance != null); // Populate the maximum statistics. - HitResult maxBasicResult = ruleset.CreateInstance().GetHitResults() - .Select(h => h.result) - .Where(h => h.IsBasic()) - .OrderByDescending(Judgement.ToNumericResult).First(); + HitResult maxBasicResult = rulesetInstance.GetHitResults() + .Select(h => h.result) + .Where(h => h.IsBasic()) + .OrderByDescending(Judgement.ToNumericResult).First(); foreach ((HitResult result, int count) in score.Statistics) { @@ -134,18 +134,14 @@ namespace osu.Game.Scoring return; #pragma warning disable CS0618 - if (difficultyCache == null) - throw new InvalidOperationException($"Cannot populate legacy score statistics without a {nameof(BeatmapDifficultyCache)}."); - // In osu! and osu!mania, some judgements affect combo but aren't stored to scores. // A special hit result is used to pad out the combo value to match, based on the max combo from the difficulty attributes. - StarDifficulty? difficulty = await difficultyCache.GetDifficultyAsync(beatmap, ruleset, score.Mods); - if (difficulty == null) - throw new InvalidOperationException("Failed to populate maximum statistics due to missing difficulty attributes."); + var calculator = rulesetInstance.CreateDifficultyCalculator(beatmaps().GetWorkingBeatmap(beatmap)); + var attributes = calculator.Calculate(score.Mods); int maxComboFromStatistics = score.MaximumStatistics.Where(kvp => kvp.Key.AffectsCombo()).Select(kvp => kvp.Value).DefaultIfEmpty(0).Sum(); - if (difficulty.Value.MaxCombo > maxComboFromStatistics) - score.MaximumStatistics[HitResult.LegacyComboIncrease] = difficulty.Value.MaxCombo - maxComboFromStatistics; + if (attributes.MaxCombo > maxComboFromStatistics) + score.MaximumStatistics[HitResult.LegacyComboIncrease] = attributes.MaxCombo - maxComboFromStatistics; #pragma warning restore CS0618 } diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index 87bb834a75..6bb31eb4db 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -29,12 +29,12 @@ namespace osu.Game.Scoring private readonly ScoreImporter scoreImporter; public ScoreManager(RulesetStore rulesets, Func beatmaps, Storage storage, RealmAccess realm, IAPIProvider api, - OsuConfigManager configManager = null, BeatmapDifficultyCache difficultyCache = null) + OsuConfigManager configManager = null) : base(storage, realm) { this.configManager = configManager; - scoreImporter = new ScoreImporter(rulesets, beatmaps, storage, realm, api, difficultyCache) + scoreImporter = new ScoreImporter(rulesets, beatmaps, storage, realm, api) { PostNotification = obj => PostNotification?.Invoke(obj) }; @@ -183,7 +183,7 @@ namespace osu.Game.Scoring /// Populates the for a given . /// /// The score to populate the statistics of. - public Task PopulateMaximumStatistics(ScoreInfo score) => scoreImporter.PopulateMaximumStatistics(score); + public void PopulateMaximumStatistics(ScoreInfo score) => scoreImporter.PopulateMaximumStatistics(score); #region Implementation of IPresentImports From 20ffbc4676a60e6c192131ce33678e2fdada6136 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Sep 2022 14:01:52 +0900 Subject: [PATCH 371/420] Fix beat sync stopping after returning to menu from a failed play Closes #20193. Explanation is inline comment. --- osu.Game/Beatmaps/FramedBeatmapClock.cs | 11 +++++++++++ osu.Game/OsuGameBase.cs | 5 ----- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/osu.Game/Beatmaps/FramedBeatmapClock.cs b/osu.Game/Beatmaps/FramedBeatmapClock.cs index a4787a34e8..3c528ba838 100644 --- a/osu.Game/Beatmaps/FramedBeatmapClock.cs +++ b/osu.Game/Beatmaps/FramedBeatmapClock.cs @@ -121,6 +121,17 @@ namespace osu.Game.Beatmaps protected override void Update() { base.Update(); + + if (Source != null && Source is not IAdjustableClock && Source.CurrentTime < finalClockSource.CurrentTime) + { + // InterpolatingFramedClock won't interpolate backwards unless its source has an ElapsedFrameTime. + // See https://github.com/ppy/osu-framework/blob/ba1385330cc501f34937e08257e586c84e35d772/osu.Framework/Timing/InterpolatingFramedClock.cs#L91-L93 + // This is not always the case here when doing large seeks. + // (Of note, this is not an issue if the source is adjustable, as the source is seeked to be in time by DecoupleableInterpolatingFramedClock). + // Rather than trying to get around this by fixing the framework clock stack, let's work around it for now. + Seek(Source.CurrentTime); + } + finalClockSource.ProcessFrame(); } diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index c95a281f09..97142d5472 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -390,11 +390,6 @@ namespace osu.Game var framedClock = new FramedClock(beatmap.Track); beatmapClock.ChangeSource(framedClock); - - // Normally the internal decoupled clock will seek the *track* to the decoupled time, but we blocked this. - // It won't behave nicely unless we also set it to the track's time. - // Probably another thing which should be fixed in the decoupled mess (or just replaced). - beatmapClock.Seek(beatmap.Track.CurrentTime); } protected virtual void InitialiseFonts() From 64cf6b9014aa0db328722e33e17f4b6ff2e64c98 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Sep 2022 14:35:35 +0900 Subject: [PATCH 372/420] Compare with decoupled clock directly to avoid including offsets --- osu.Game/Beatmaps/FramedBeatmapClock.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/FramedBeatmapClock.cs b/osu.Game/Beatmaps/FramedBeatmapClock.cs index 3c528ba838..594663e7f2 100644 --- a/osu.Game/Beatmaps/FramedBeatmapClock.cs +++ b/osu.Game/Beatmaps/FramedBeatmapClock.cs @@ -122,7 +122,7 @@ namespace osu.Game.Beatmaps { base.Update(); - if (Source != null && Source is not IAdjustableClock && Source.CurrentTime < finalClockSource.CurrentTime) + if (Source != null && Source is not IAdjustableClock && Source.CurrentTime < decoupledClock.CurrentTime) { // InterpolatingFramedClock won't interpolate backwards unless its source has an ElapsedFrameTime. // See https://github.com/ppy/osu-framework/blob/ba1385330cc501f34937e08257e586c84e35d772/osu.Framework/Timing/InterpolatingFramedClock.cs#L91-L93 From d6748d6921117a65846da635a45e0c6d04a5eef8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Sep 2022 14:35:47 +0900 Subject: [PATCH 373/420] Avoid double call to `ProcessFrame` --- osu.Game/Beatmaps/FramedBeatmapClock.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Beatmaps/FramedBeatmapClock.cs b/osu.Game/Beatmaps/FramedBeatmapClock.cs index 594663e7f2..c7050cc50f 100644 --- a/osu.Game/Beatmaps/FramedBeatmapClock.cs +++ b/osu.Game/Beatmaps/FramedBeatmapClock.cs @@ -131,8 +131,8 @@ namespace osu.Game.Beatmaps // Rather than trying to get around this by fixing the framework clock stack, let's work around it for now. Seek(Source.CurrentTime); } - - finalClockSource.ProcessFrame(); + else + finalClockSource.ProcessFrame(); } public double TotalAppliedOffset From 856dbbba692a9d1ad31561a78a174c959a05a586 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Sep 2022 14:52:46 +0900 Subject: [PATCH 374/420] Fix attempting to use "home" key binding while exiting game causing errors --- osu.Game/OsuGame.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 108153fd9d..9e2384322a 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -839,7 +839,9 @@ namespace osu.Game OnHome = delegate { CloseAllOverlays(false); - menuScreen?.MakeCurrent(); + + if (menuScreen?.GetChildScreen() != null) + menuScreen.MakeCurrent(); }, }, topMostOverlayContent.Add); From 2709a4d3984fffd805655ddc722bc72918c63875 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Sep 2022 15:04:25 +0900 Subject: [PATCH 375/420] Ensure overlay is always shown when movement is detected on mouse or keyboard --- osu.Game.Rulesets.Catch/UI/CatchTouchInputMapper.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Catch/UI/CatchTouchInputMapper.cs b/osu.Game.Rulesets.Catch/UI/CatchTouchInputMapper.cs index 7886df69b6..1abca2ae98 100644 --- a/osu.Game.Rulesets.Catch/UI/CatchTouchInputMapper.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchTouchInputMapper.cs @@ -131,6 +131,8 @@ namespace osu.Game.Rulesets.Catch.UI protected override bool OnMouseMove(MouseMoveEvent e) { + Show(); + TouchCatchAction touchCatchAction = getTouchCatchActionFromInput(e.ScreenSpaceMousePosition); // Loop through the buttons to avoid keeping a button pressed if both mouse buttons are pressed. @@ -143,6 +145,8 @@ namespace osu.Game.Rulesets.Catch.UI protected override void OnTouchMove(TouchMoveEvent e) { + Show(); + trackedActions[e.Touch.Source] = getTouchCatchActionFromInput(e.ScreenSpaceTouch.Position); calculateActiveKeys(); @@ -186,8 +190,6 @@ namespace osu.Game.Rulesets.Catch.UI if (catchAction == TouchCatchAction.None) return false; - Show(); - trackedActions[source] = catchAction; calculateActiveKeys(); From 715e9018da3dd4d57084cbdc76c84100f712a3fb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Sep 2022 15:11:26 +0900 Subject: [PATCH 376/420] Tidy up code and naming --- .../UI/CatchTouchInputMapper.cs | 66 +++++++++---------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/osu.Game.Rulesets.Catch/UI/CatchTouchInputMapper.cs b/osu.Game.Rulesets.Catch/UI/CatchTouchInputMapper.cs index 1abca2ae98..c17946bdb1 100644 --- a/osu.Game.Rulesets.Catch/UI/CatchTouchInputMapper.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchTouchInputMapper.cs @@ -19,16 +19,16 @@ namespace osu.Game.Rulesets.Catch.UI { public class CatchTouchInputMapper : VisibilityContainer { - private Dictionary trackedActions = new Dictionary(); + private Dictionary trackedActionSources = new Dictionary(); private KeyBindingContainer keyBindingContainer = null!; private Container mainContent = null!; - private ArrowHitbox leftBox = null!; - private ArrowHitbox rightBox = null!; - private ArrowHitbox leftDashBox = null!; - private ArrowHitbox rightDashBox = null!; + private InputArea leftBox = null!; + private InputArea rightBox = null!; + private InputArea leftDashBox = null!; + private InputArea rightDashBox = null!; public override bool PropagatePositionalInputSubTree => true; public override bool PropagateNonPositionalInputSubTree => true; @@ -61,7 +61,7 @@ namespace osu.Game.Rulesets.Catch.UI Origin = Anchor.CentreLeft, Children = new Drawable[] { - leftBox = new ArrowHitbox(TouchCatchAction.MoveLeft, ref trackedActions, colours.Gray3) + leftBox = new InputArea(TouchCatchAction.MoveLeft, ref trackedActionSources, colours.Gray3) { Anchor = Anchor.CentreRight, Origin = Anchor.CentreRight, @@ -69,7 +69,7 @@ namespace osu.Game.Rulesets.Catch.UI RelativePositionAxes = Axes.Both, Width = 0.5f, }, - leftDashBox = new ArrowHitbox(TouchCatchAction.DashLeft, ref trackedActions, colours.Gray2) + leftDashBox = new InputArea(TouchCatchAction.DashLeft, ref trackedActionSources, colours.Gray2) { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, @@ -88,7 +88,7 @@ namespace osu.Game.Rulesets.Catch.UI Origin = Anchor.CentreRight, Children = new Drawable[] { - rightBox = new ArrowHitbox(TouchCatchAction.MoveRight, ref trackedActions, colours.Gray3) + rightBox = new InputArea(TouchCatchAction.MoveRight, ref trackedActionSources, colours.Gray3) { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, @@ -96,7 +96,7 @@ namespace osu.Game.Rulesets.Catch.UI RelativePositionAxes = Axes.Both, Width = 0.5f, }, - rightDashBox = new ArrowHitbox(TouchCatchAction.DashRight, ref trackedActions, colours.Gray2) + rightDashBox = new InputArea(TouchCatchAction.DashRight, ref trackedActionSources, colours.Gray2) { Anchor = Anchor.CentreRight, Origin = Anchor.CentreRight, @@ -137,7 +137,7 @@ namespace osu.Game.Rulesets.Catch.UI // Loop through the buttons to avoid keeping a button pressed if both mouse buttons are pressed. foreach (MouseButton i in e.PressedButtons) - trackedActions[i] = touchCatchAction; + trackedActionSources[i] = touchCatchAction; calculateActiveKeys(); return true; @@ -147,7 +147,7 @@ namespace osu.Game.Rulesets.Catch.UI { Show(); - trackedActions[e.Touch.Source] = getTouchCatchActionFromInput(e.ScreenSpaceTouch.Position); + trackedActionSources[e.Touch.Source] = getTouchCatchActionFromInput(e.ScreenSpaceTouch.Position); calculateActiveKeys(); base.OnTouchMove(e); @@ -165,24 +165,6 @@ namespace osu.Game.Rulesets.Catch.UI base.OnTouchUp(e); } - private void calculateActiveKeys() - { - if (trackedActions.ContainsValue(TouchCatchAction.DashLeft) || trackedActions.ContainsValue(TouchCatchAction.MoveLeft)) - keyBindingContainer.TriggerPressed(CatchAction.MoveLeft); - else - keyBindingContainer.TriggerReleased(CatchAction.MoveLeft); - - if (trackedActions.ContainsValue(TouchCatchAction.DashRight) || trackedActions.ContainsValue(TouchCatchAction.MoveRight)) - keyBindingContainer.TriggerPressed(CatchAction.MoveRight); - else - keyBindingContainer.TriggerReleased(CatchAction.MoveRight); - - if (trackedActions.ContainsValue(TouchCatchAction.DashRight) || trackedActions.ContainsValue(TouchCatchAction.DashLeft)) - keyBindingContainer.TriggerPressed(CatchAction.Dash); - else - keyBindingContainer.TriggerReleased(CatchAction.Dash); - } - private bool handleDown(object source, Vector2 position) { TouchCatchAction catchAction = getTouchCatchActionFromInput(position); @@ -190,7 +172,7 @@ namespace osu.Game.Rulesets.Catch.UI if (catchAction == TouchCatchAction.None) return false; - trackedActions[source] = catchAction; + trackedActionSources[source] = catchAction; calculateActiveKeys(); @@ -199,11 +181,29 @@ namespace osu.Game.Rulesets.Catch.UI private void handleUp(object source) { - trackedActions.Remove(source); + trackedActionSources.Remove(source); calculateActiveKeys(); } + private void calculateActiveKeys() + { + if (trackedActionSources.ContainsValue(TouchCatchAction.DashLeft) || trackedActionSources.ContainsValue(TouchCatchAction.MoveLeft)) + keyBindingContainer.TriggerPressed(CatchAction.MoveLeft); + else + keyBindingContainer.TriggerReleased(CatchAction.MoveLeft); + + if (trackedActionSources.ContainsValue(TouchCatchAction.DashRight) || trackedActionSources.ContainsValue(TouchCatchAction.MoveRight)) + keyBindingContainer.TriggerPressed(CatchAction.MoveRight); + else + keyBindingContainer.TriggerReleased(CatchAction.MoveRight); + + if (trackedActionSources.ContainsValue(TouchCatchAction.DashRight) || trackedActionSources.ContainsValue(TouchCatchAction.DashLeft)) + keyBindingContainer.TriggerPressed(CatchAction.Dash); + else + keyBindingContainer.TriggerReleased(CatchAction.Dash); + } + private TouchCatchAction getTouchCatchActionFromInput(Vector2 inputPosition) { if (leftDashBox.Contains(inputPosition)) @@ -228,7 +228,7 @@ namespace osu.Game.Rulesets.Catch.UI mainContent.FadeOut(300); } - private class ArrowHitbox : CompositeDrawable, IKeyBindingHandler + private class InputArea : CompositeDrawable, IKeyBindingHandler { private readonly TouchCatchAction handledAction; @@ -238,7 +238,7 @@ namespace osu.Game.Rulesets.Catch.UI private bool isHiglighted; - public ArrowHitbox(TouchCatchAction handledAction, ref Dictionary trackedActions, Color4 colour) + public InputArea(TouchCatchAction handledAction, ref Dictionary trackedActions, Color4 colour) { this.handledAction = handledAction; this.trackedActions = trackedActions; From a42c1af09ea2571d20afeb5435e4388af4225c68 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Sep 2022 15:19:30 +0900 Subject: [PATCH 377/420] Tidy up highlighting code and ensure read-only access to dictionary by highlight areas --- .../UI/CatchTouchInputMapper.cs | 90 ++++++++----------- 1 file changed, 35 insertions(+), 55 deletions(-) diff --git a/osu.Game.Rulesets.Catch/UI/CatchTouchInputMapper.cs b/osu.Game.Rulesets.Catch/UI/CatchTouchInputMapper.cs index c17946bdb1..58cda7fef4 100644 --- a/osu.Game.Rulesets.Catch/UI/CatchTouchInputMapper.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchTouchInputMapper.cs @@ -1,8 +1,9 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Collections.Generic; +using System.Linq; using osu.Framework.Allocation; -using System.Diagnostics; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -10,16 +11,18 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Game.Graphics; -using osuTK.Graphics; using osuTK; -using System.Collections.Generic; +using osuTK.Graphics; using osuTK.Input; namespace osu.Game.Rulesets.Catch.UI { public class CatchTouchInputMapper : VisibilityContainer { - private Dictionary trackedActionSources = new Dictionary(); + public override bool PropagatePositionalInputSubTree => true; + public override bool PropagateNonPositionalInputSubTree => true; + + private readonly Dictionary trackedActionSources = new Dictionary(); private KeyBindingContainer keyBindingContainer = null!; @@ -30,17 +33,11 @@ namespace osu.Game.Rulesets.Catch.UI private InputArea leftDashBox = null!; private InputArea rightDashBox = null!; - public override bool PropagatePositionalInputSubTree => true; - public override bool PropagateNonPositionalInputSubTree => true; - [BackgroundDependencyLoader] private void load(CatchInputManager catchInputManager, OsuColour colours) { - Debug.Assert(catchInputManager.KeyBindingContainer != null); - keyBindingContainer = catchInputManager.KeyBindingContainer; - // Container should handle input everywhere. RelativeSizeAxes = Axes.Both; Children = new Drawable[] @@ -48,7 +45,6 @@ namespace osu.Game.Rulesets.Catch.UI mainContent = new Container { RelativeSizeAxes = Axes.Both, - RelativePositionAxes = Axes.Both, Alpha = 0, Children = new Drawable[] { @@ -56,25 +52,18 @@ namespace osu.Game.Rulesets.Catch.UI { RelativeSizeAxes = Axes.Both, Width = 0.15f, - Height = 1, - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, Children = new Drawable[] { - leftBox = new InputArea(TouchCatchAction.MoveLeft, ref trackedActionSources, colours.Gray3) + leftBox = new InputArea(TouchCatchAction.MoveLeft, trackedActionSources, colours.Gray3) { - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, RelativeSizeAxes = Axes.Both, - RelativePositionAxes = Axes.Both, Width = 0.5f, + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, }, - leftDashBox = new InputArea(TouchCatchAction.DashLeft, ref trackedActionSources, colours.Gray2) + leftDashBox = new InputArea(TouchCatchAction.DashLeft, trackedActionSources, colours.Gray2) { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, RelativeSizeAxes = Axes.Both, - RelativePositionAxes = Axes.Both, Width = 0.5f, } } @@ -83,26 +72,21 @@ namespace osu.Game.Rulesets.Catch.UI { RelativeSizeAxes = Axes.Both, Width = 0.15f, - Height = 1, - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, Children = new Drawable[] { - rightBox = new InputArea(TouchCatchAction.MoveRight, ref trackedActionSources, colours.Gray3) + rightBox = new InputArea(TouchCatchAction.MoveRight, trackedActionSources, colours.Gray3) { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, RelativeSizeAxes = Axes.Both, - RelativePositionAxes = Axes.Both, Width = 0.5f, }, - rightDashBox = new InputArea(TouchCatchAction.DashRight, ref trackedActionSources, colours.Gray2) + rightDashBox = new InputArea(TouchCatchAction.DashRight, trackedActionSources, colours.Gray2) { - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, RelativeSizeAxes = Axes.Both, - RelativePositionAxes = Axes.Both, Width = 0.5f, + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, }, } }, @@ -218,15 +202,9 @@ namespace osu.Game.Rulesets.Catch.UI return TouchCatchAction.None; } - protected override void PopIn() - { - mainContent.FadeIn(500, Easing.OutQuint); - } + protected override void PopIn() => mainContent.FadeIn(500, Easing.OutQuint); - protected override void PopOut() - { - mainContent.FadeOut(300); - } + protected override void PopOut() => mainContent.FadeOut(300); private class InputArea : CompositeDrawable, IKeyBindingHandler { @@ -234,11 +212,11 @@ namespace osu.Game.Rulesets.Catch.UI private readonly Box overlay; - private readonly Dictionary trackedActions; + private readonly IEnumerable> trackedActions; - private bool isHiglighted; + private bool isHighlighted; - public InputArea(TouchCatchAction handledAction, ref Dictionary trackedActions, Color4 colour) + public InputArea(TouchCatchAction handledAction, IEnumerable> trackedActions, Color4 colour) { this.handledAction = handledAction; this.trackedActions = trackedActions; @@ -273,22 +251,24 @@ namespace osu.Game.Rulesets.Catch.UI public bool OnPressed(KeyBindingPressEvent _) { - if (trackedActions.ContainsValue(handledAction)) - { - isHiglighted = true; - overlay.FadeTo(0.5f, 80, Easing.OutQuint); - } - + updateHighlight(); return false; } public void OnReleased(KeyBindingReleaseEvent _) { - if (isHiglighted && !trackedActions.ContainsValue(handledAction)) - { - isHiglighted = false; - overlay.FadeOut(1000, Easing.Out); - } + updateHighlight(); + } + + private void updateHighlight() + { + bool isHandling = trackedActions.Any(a => a.Value == handledAction); + + if (isHandling == isHighlighted) + return; + + isHighlighted = isHandling; + overlay.FadeTo(isHighlighted ? 0.5f : 0, isHighlighted ? 80 : 400, Easing.OutQuint); } } From e6ba95ee16e7595f748f2ee39a94289918a47327 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Sep 2022 15:22:12 +0900 Subject: [PATCH 378/420] Don't bother calculating active keys if input source was not handled --- osu.Game.Rulesets.Catch/UI/CatchTouchInputMapper.cs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Catch/UI/CatchTouchInputMapper.cs b/osu.Game.Rulesets.Catch/UI/CatchTouchInputMapper.cs index 58cda7fef4..847814c0ea 100644 --- a/osu.Game.Rulesets.Catch/UI/CatchTouchInputMapper.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchTouchInputMapper.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; @@ -149,15 +150,14 @@ namespace osu.Game.Rulesets.Catch.UI base.OnTouchUp(e); } - private bool handleDown(object source, Vector2 position) + private bool handleDown(object inputSource, Vector2 position) { TouchCatchAction catchAction = getTouchCatchActionFromInput(position); if (catchAction == TouchCatchAction.None) return false; - trackedActionSources[source] = catchAction; - + trackedActionSources[inputSource] = catchAction; calculateActiveKeys(); return true; @@ -165,9 +165,8 @@ namespace osu.Game.Rulesets.Catch.UI private void handleUp(object source) { - trackedActionSources.Remove(source); - - calculateActiveKeys(); + if (trackedActionSources.Remove(source)) + calculateActiveKeys(); } private void calculateActiveKeys() From ba951b76f73b16c6ea7b1305a78697bea90a6fc4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Sep 2022 15:28:40 +0900 Subject: [PATCH 379/420] Unify and simplify input handling code --- .../UI/CatchTouchInputMapper.cs | 63 +++++++++---------- 1 file changed, 30 insertions(+), 33 deletions(-) diff --git a/osu.Game.Rulesets.Catch/UI/CatchTouchInputMapper.cs b/osu.Game.Rulesets.Catch/UI/CatchTouchInputMapper.cs index 847814c0ea..3d634c4e00 100644 --- a/osu.Game.Rulesets.Catch/UI/CatchTouchInputMapper.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchTouchInputMapper.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; -using System.Diagnostics; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; @@ -108,40 +107,30 @@ namespace osu.Game.Rulesets.Catch.UI return handleDown(e.Button, e.ScreenSpaceMousePosition); } - protected override void OnMouseUp(MouseUpEvent e) + protected override bool OnTouchDown(TouchDownEvent e) { - handleUp(e.Button); - base.OnMouseUp(e); + handleDown(e.Touch.Source, e.ScreenSpaceTouch.Position); + return true; } protected override bool OnMouseMove(MouseMoveEvent e) { - Show(); - - TouchCatchAction touchCatchAction = getTouchCatchActionFromInput(e.ScreenSpaceMousePosition); - - // Loop through the buttons to avoid keeping a button pressed if both mouse buttons are pressed. - foreach (MouseButton i in e.PressedButtons) - trackedActionSources[i] = touchCatchAction; - - calculateActiveKeys(); + // multiple mouse buttons may be pressed and handling the same action. + foreach (MouseButton button in e.PressedButtons) + handleMove(button, e.ScreenSpaceMousePosition); return true; } protected override void OnTouchMove(TouchMoveEvent e) { - Show(); - - trackedActionSources[e.Touch.Source] = getTouchCatchActionFromInput(e.ScreenSpaceTouch.Position); - calculateActiveKeys(); - + handleMove(e.Touch.Source, e.ScreenSpaceTouch.Position); base.OnTouchMove(e); } - protected override bool OnTouchDown(TouchDownEvent e) + protected override void OnMouseUp(MouseUpEvent e) { - handleDown(e.Touch.Source, e.ScreenSpaceTouch.Position); - return true; + handleUp(e.Button); + base.OnMouseUp(e); } protected override void OnTouchUp(TouchUpEvent e) @@ -150,15 +139,23 @@ namespace osu.Game.Rulesets.Catch.UI base.OnTouchUp(e); } - private bool handleDown(object inputSource, Vector2 position) + private void handleMove(object inputSource, Vector2 screenSpaceInputPosition) { - TouchCatchAction catchAction = getTouchCatchActionFromInput(position); + Show(); - if (catchAction == TouchCatchAction.None) + trackedActionSources[inputSource] = getTouchCatchActionFromInput(screenSpaceInputPosition); + updatePressedActions(); + } + + private bool handleDown(object inputSource, Vector2 screenSpaceInputPosition) + { + TouchCatchAction action = getTouchCatchActionFromInput(screenSpaceInputPosition); + + if (action == TouchCatchAction.None) return false; - trackedActionSources[inputSource] = catchAction; - calculateActiveKeys(); + trackedActionSources[inputSource] = action; + updatePressedActions(); return true; } @@ -166,10 +163,10 @@ namespace osu.Game.Rulesets.Catch.UI private void handleUp(object source) { if (trackedActionSources.Remove(source)) - calculateActiveKeys(); + updatePressedActions(); } - private void calculateActiveKeys() + private void updatePressedActions() { if (trackedActionSources.ContainsValue(TouchCatchAction.DashLeft) || trackedActionSources.ContainsValue(TouchCatchAction.MoveLeft)) keyBindingContainer.TriggerPressed(CatchAction.MoveLeft); @@ -187,15 +184,15 @@ namespace osu.Game.Rulesets.Catch.UI keyBindingContainer.TriggerReleased(CatchAction.Dash); } - private TouchCatchAction getTouchCatchActionFromInput(Vector2 inputPosition) + private TouchCatchAction getTouchCatchActionFromInput(Vector2 screenSpaceInputPosition) { - if (leftDashBox.Contains(inputPosition)) + if (leftDashBox.Contains(screenSpaceInputPosition)) return TouchCatchAction.DashLeft; - if (rightDashBox.Contains(inputPosition)) + if (rightDashBox.Contains(screenSpaceInputPosition)) return TouchCatchAction.DashRight; - if (leftBox.Contains(inputPosition)) + if (leftBox.Contains(screenSpaceInputPosition)) return TouchCatchAction.MoveLeft; - if (rightBox.Contains(inputPosition)) + if (rightBox.Contains(screenSpaceInputPosition)) return TouchCatchAction.MoveRight; return TouchCatchAction.None; From 64eaf461ac9ce39a9f13ae082b4f56eaacf83a49 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Sep 2022 15:58:43 +0900 Subject: [PATCH 380/420] Simplify input handling even further --- .../UI/CatchTouchInputMapper.cs | 97 +++++++++---------- 1 file changed, 46 insertions(+), 51 deletions(-) diff --git a/osu.Game.Rulesets.Catch/UI/CatchTouchInputMapper.cs b/osu.Game.Rulesets.Catch/UI/CatchTouchInputMapper.cs index 3d634c4e00..13333ecc61 100644 --- a/osu.Game.Rulesets.Catch/UI/CatchTouchInputMapper.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchTouchInputMapper.cs @@ -4,7 +4,6 @@ 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; @@ -36,6 +35,8 @@ namespace osu.Game.Rulesets.Catch.UI [BackgroundDependencyLoader] private void load(CatchInputManager catchInputManager, OsuColour colours) { + const float width = 0.15f; + keyBindingContainer = catchInputManager.KeyBindingContainer; RelativeSizeAxes = Axes.Both; @@ -51,7 +52,7 @@ namespace osu.Game.Rulesets.Catch.UI new Container { RelativeSizeAxes = Axes.Both, - Width = 0.15f, + Width = width, Children = new Drawable[] { leftBox = new InputArea(TouchCatchAction.MoveLeft, trackedActionSources, colours.Gray3) @@ -71,7 +72,7 @@ namespace osu.Game.Rulesets.Catch.UI new Container { RelativeSizeAxes = Axes.Both, - Width = 0.15f, + Width = width, Anchor = Anchor.TopRight, Origin = Anchor.TopRight, Children = new Drawable[] @@ -104,70 +105,69 @@ namespace osu.Game.Rulesets.Catch.UI protected override bool OnMouseDown(MouseDownEvent e) { - return handleDown(e.Button, e.ScreenSpaceMousePosition); + return updateAction(e.Button, getTouchCatchActionFromInput(e.ScreenSpaceMousePosition)); } protected override bool OnTouchDown(TouchDownEvent e) { - handleDown(e.Touch.Source, e.ScreenSpaceTouch.Position); - return true; + return updateAction(e.Touch.Source, getTouchCatchActionFromInput(e.ScreenSpaceTouch.Position)); } protected override bool OnMouseMove(MouseMoveEvent e) { + Show(); + + TouchCatchAction? action = getTouchCatchActionFromInput(e.ScreenSpaceMousePosition); + // multiple mouse buttons may be pressed and handling the same action. foreach (MouseButton button in e.PressedButtons) - handleMove(button, e.ScreenSpaceMousePosition); - return true; + updateAction(button, action); + + return false; } protected override void OnTouchMove(TouchMoveEvent e) { - handleMove(e.Touch.Source, e.ScreenSpaceTouch.Position); + updateAction(e.Touch.Source, getTouchCatchActionFromInput(e.ScreenSpaceTouch.Position)); base.OnTouchMove(e); } protected override void OnMouseUp(MouseUpEvent e) { - handleUp(e.Button); + updateAction(e.Button, null); base.OnMouseUp(e); } protected override void OnTouchUp(TouchUpEvent e) { - handleUp(e.Touch.Source); + updateAction(e.Touch.Source, null); base.OnTouchUp(e); } - private void handleMove(object inputSource, Vector2 screenSpaceInputPosition) + private bool updateAction(object source, TouchCatchAction? newAction) { - Show(); + TouchCatchAction? actionBefore = null; - trackedActionSources[inputSource] = getTouchCatchActionFromInput(screenSpaceInputPosition); - updatePressedActions(); - } + if (trackedActionSources.TryGetValue(source, out TouchCatchAction found)) + actionBefore = found; - private bool handleDown(object inputSource, Vector2 screenSpaceInputPosition) - { - TouchCatchAction action = getTouchCatchActionFromInput(screenSpaceInputPosition); + if (actionBefore != newAction) + { + if (newAction != null) + trackedActionSources[source] = newAction.Value; + else + trackedActionSources.Remove(source); - if (action == TouchCatchAction.None) - return false; - - trackedActionSources[inputSource] = action; - updatePressedActions(); - - return true; - } - - private void handleUp(object source) - { - if (trackedActionSources.Remove(source)) updatePressedActions(); + } + + return newAction != null; } private void updatePressedActions() { + Show(); + if (trackedActionSources.ContainsValue(TouchCatchAction.DashLeft) || trackedActionSources.ContainsValue(TouchCatchAction.MoveLeft)) keyBindingContainer.TriggerPressed(CatchAction.MoveLeft); else @@ -178,13 +178,13 @@ namespace osu.Game.Rulesets.Catch.UI else keyBindingContainer.TriggerReleased(CatchAction.MoveRight); - if (trackedActionSources.ContainsValue(TouchCatchAction.DashRight) || trackedActionSources.ContainsValue(TouchCatchAction.DashLeft)) + if (trackedActionSources.ContainsValue(TouchCatchAction.DashLeft) || trackedActionSources.ContainsValue(TouchCatchAction.DashRight)) keyBindingContainer.TriggerPressed(CatchAction.Dash); else keyBindingContainer.TriggerReleased(CatchAction.Dash); } - private TouchCatchAction getTouchCatchActionFromInput(Vector2 screenSpaceInputPosition) + private TouchCatchAction? getTouchCatchActionFromInput(Vector2 screenSpaceInputPosition) { if (leftDashBox.Contains(screenSpaceInputPosition)) return TouchCatchAction.DashLeft; @@ -195,18 +195,18 @@ namespace osu.Game.Rulesets.Catch.UI if (rightBox.Contains(screenSpaceInputPosition)) return TouchCatchAction.MoveRight; - return TouchCatchAction.None; + return null; } - protected override void PopIn() => mainContent.FadeIn(500, Easing.OutQuint); + protected override void PopIn() => mainContent.FadeIn(300, Easing.OutQuint); - protected override void PopOut() => mainContent.FadeOut(300); + protected override void PopOut() => mainContent.FadeOut(300, Easing.OutQuint); private class InputArea : CompositeDrawable, IKeyBindingHandler { private readonly TouchCatchAction handledAction; - private readonly Box overlay; + private readonly Box highlightOverlay; private readonly IEnumerable> trackedActions; @@ -221,24 +221,20 @@ namespace osu.Game.Rulesets.Catch.UI { new Container { - Width = 1, RelativeSizeAxes = Axes.Both, Children = new Drawable[] { new Box { + RelativeSizeAxes = Axes.Both, Alpha = 0.8f, Colour = colour, - Width = 1, - RelativeSizeAxes = Axes.Both, }, - overlay = new Box + highlightOverlay = new Box { - Alpha = 0, - Colour = colour.Multiply(1.4f), - Blending = BlendingParameters.Additive, - Width = 1, RelativeSizeAxes = Axes.Both, + Alpha = 0, + Blending = BlendingParameters.Additive, } } } @@ -264,17 +260,16 @@ namespace osu.Game.Rulesets.Catch.UI return; isHighlighted = isHandling; - overlay.FadeTo(isHighlighted ? 0.5f : 0, isHighlighted ? 80 : 400, Easing.OutQuint); + highlightOverlay.FadeTo(isHighlighted ? 0.1f : 0, isHighlighted ? 80 : 400, Easing.OutQuint); } } public enum TouchCatchAction { - MoveLeft = 0, - MoveRight = 1, - DashLeft = 2, - DashRight = 3, - None = 4 + MoveLeft, + MoveRight, + DashLeft, + DashRight, } } } From f3fc8af6ee9fde39b0b4c644c2bc702ae0242107 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Sep 2022 16:03:36 +0900 Subject: [PATCH 381/420] Adjust visuals --- .../UI/CatchTouchInputMapper.cs | 25 ++++++++++--------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/osu.Game.Rulesets.Catch/UI/CatchTouchInputMapper.cs b/osu.Game.Rulesets.Catch/UI/CatchTouchInputMapper.cs index 13333ecc61..cdb587ad64 100644 --- a/osu.Game.Rulesets.Catch/UI/CatchTouchInputMapper.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchTouchInputMapper.cs @@ -11,7 +11,6 @@ using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Game.Graphics; using osuTK; -using osuTK.Graphics; using osuTK.Input; namespace osu.Game.Rulesets.Catch.UI @@ -55,18 +54,19 @@ namespace osu.Game.Rulesets.Catch.UI Width = width, Children = new Drawable[] { - leftBox = new InputArea(TouchCatchAction.MoveLeft, trackedActionSources, colours.Gray3) + leftDashBox = new InputArea(TouchCatchAction.DashLeft, trackedActionSources) { RelativeSizeAxes = Axes.Both, Width = 0.5f, + }, + leftBox = new InputArea(TouchCatchAction.MoveLeft, trackedActionSources) + { + RelativeSizeAxes = Axes.Both, + Width = 0.5f, + Colour = colours.GrayC, Anchor = Anchor.TopRight, Origin = Anchor.TopRight, }, - leftDashBox = new InputArea(TouchCatchAction.DashLeft, trackedActionSources, colours.Gray2) - { - RelativeSizeAxes = Axes.Both, - Width = 0.5f, - } } }, new Container @@ -77,12 +77,13 @@ namespace osu.Game.Rulesets.Catch.UI Origin = Anchor.TopRight, Children = new Drawable[] { - rightBox = new InputArea(TouchCatchAction.MoveRight, trackedActionSources, colours.Gray3) + rightBox = new InputArea(TouchCatchAction.MoveRight, trackedActionSources) { RelativeSizeAxes = Axes.Both, Width = 0.5f, + Colour = colours.GrayC, }, - rightDashBox = new InputArea(TouchCatchAction.DashRight, trackedActionSources, colours.Gray2) + rightDashBox = new InputArea(TouchCatchAction.DashRight, trackedActionSources) { RelativeSizeAxes = Axes.Both, Width = 0.5f, @@ -212,7 +213,7 @@ namespace osu.Game.Rulesets.Catch.UI private bool isHighlighted; - public InputArea(TouchCatchAction handledAction, IEnumerable> trackedActions, Color4 colour) + public InputArea(TouchCatchAction handledAction, IEnumerable> trackedActions) { this.handledAction = handledAction; this.trackedActions = trackedActions; @@ -227,8 +228,8 @@ namespace osu.Game.Rulesets.Catch.UI new Box { RelativeSizeAxes = Axes.Both, - Alpha = 0.8f, - Colour = colour, + Alpha = 0.2f, + Colour = OsuColour.Gray(0.8f), }, highlightOverlay = new Box { From 280b1dd48406d2af07be856e8abc1cdae097d18f Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 9 Sep 2022 16:12:18 +0900 Subject: [PATCH 382/420] Revert async Task change --- osu.Game/BackgroundBeatmapProcessor.cs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/osu.Game/BackgroundBeatmapProcessor.cs b/osu.Game/BackgroundBeatmapProcessor.cs index 071862146f..ea5904a8d3 100644 --- a/osu.Game/BackgroundBeatmapProcessor.cs +++ b/osu.Game/BackgroundBeatmapProcessor.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using System.Threading; using System.Threading.Tasks; using Newtonsoft.Json; using osu.Framework.Allocation; @@ -45,12 +46,12 @@ namespace osu.Game { base.LoadComplete(); - Task.Run(async () => + Task.Run(() => { Logger.Log("Beginning background beatmap processing.."); checkForOutdatedStarRatings(); - await processBeatmapSetsWithMissingMetrics(); - await processScoresWithMissingStatistics(); + processBeatmapSetsWithMissingMetrics(); + processScoresWithMissingStatistics(); }).ContinueWith(t => { if (t.Exception?.InnerException is ObjectDisposedException) @@ -99,7 +100,7 @@ namespace osu.Game } } - private async Task processBeatmapSetsWithMissingMetrics() + private void processBeatmapSetsWithMissingMetrics() { HashSet beatmapSetIds = new HashSet(); @@ -123,7 +124,7 @@ namespace osu.Game while (localUserPlayInfo?.IsPlaying.Value == true) { Logger.Log("Background processing sleeping due to active gameplay..."); - await Task.Delay(TimeToSleepDuringGameplay); + Thread.Sleep(TimeToSleepDuringGameplay); } realmAccess.Run(r => @@ -146,7 +147,7 @@ namespace osu.Game } } - private async Task processScoresWithMissingStatistics() + private void processScoresWithMissingStatistics() { HashSet scoreIds = new HashSet(); @@ -168,7 +169,7 @@ namespace osu.Game while (localUserPlayInfo?.IsPlaying.Value == true) { Logger.Log("Background processing sleeping due to active gameplay..."); - await Task.Delay(TimeToSleepDuringGameplay); + Thread.Sleep(TimeToSleepDuringGameplay); } try From bffc9555bfcf5861ea9bc566f0d76876eef0ef5c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Sep 2022 16:12:54 +0900 Subject: [PATCH 383/420] Adjust visuals slightly further (and remove double-gray application) --- .../TestSceneCatchTouchInput.cs | 2 +- osu.Game.Rulesets.Catch/UI/CatchTouchInputMapper.cs | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatchTouchInput.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchTouchInput.cs index b510a69f14..cbf6e8f202 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatchTouchInput.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchTouchInput.cs @@ -35,7 +35,7 @@ namespace osu.Game.Rulesets.Catch.Tests } [Test] - public void TestInputField() + public void TestBasic() { AddStep("show overlay", () => catchTouchInputMapper.Show()); } diff --git a/osu.Game.Rulesets.Catch/UI/CatchTouchInputMapper.cs b/osu.Game.Rulesets.Catch/UI/CatchTouchInputMapper.cs index cdb587ad64..e6736d6c93 100644 --- a/osu.Game.Rulesets.Catch/UI/CatchTouchInputMapper.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchTouchInputMapper.cs @@ -63,7 +63,7 @@ namespace osu.Game.Rulesets.Catch.UI { RelativeSizeAxes = Axes.Both, Width = 0.5f, - Colour = colours.GrayC, + Colour = colours.Gray9, Anchor = Anchor.TopRight, Origin = Anchor.TopRight, }, @@ -81,7 +81,7 @@ namespace osu.Game.Rulesets.Catch.UI { RelativeSizeAxes = Axes.Both, Width = 0.5f, - Colour = colours.GrayC, + Colour = colours.Gray9, }, rightDashBox = new InputArea(TouchCatchAction.DashRight, trackedActionSources) { @@ -223,13 +223,14 @@ namespace osu.Game.Rulesets.Catch.UI new Container { RelativeSizeAxes = Axes.Both, + Masking = true, + CornerRadius = 10, Children = new Drawable[] { new Box { RelativeSizeAxes = Axes.Both, - Alpha = 0.2f, - Colour = OsuColour.Gray(0.8f), + Alpha = 0.15f, }, highlightOverlay = new Box { From ec21ab8171c7b2a346e1f11e4550b5c6896506e6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Sep 2022 16:39:22 +0900 Subject: [PATCH 384/420] Reduce ramp mod multipliers in line with other difficulty change mods for now Closes https://github.com/ppy/osu/issues/20204. Will require reprocessing of everything server-side. --- osu.Game/Rulesets/Mods/ModTimeRamp.cs | 2 ++ osu.Game/Rulesets/Mods/ModWindDown.cs | 1 - osu.Game/Rulesets/Mods/ModWindUp.cs | 1 - 3 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Mods/ModTimeRamp.cs b/osu.Game/Rulesets/Mods/ModTimeRamp.cs index 7031489d0e..72a7f4b9a3 100644 --- a/osu.Game/Rulesets/Mods/ModTimeRamp.cs +++ b/osu.Game/Rulesets/Mods/ModTimeRamp.cs @@ -19,6 +19,8 @@ namespace osu.Game.Rulesets.Mods /// public const double FINAL_RATE_PROGRESS = 0.75f; + public override double ScoreMultiplier => 0.5; + [SettingSource("Initial rate", "The starting speed of the track")] public abstract BindableNumber InitialRate { get; } diff --git a/osu.Game/Rulesets/Mods/ModWindDown.cs b/osu.Game/Rulesets/Mods/ModWindDown.cs index e84bdab69c..22ed7c2efd 100644 --- a/osu.Game/Rulesets/Mods/ModWindDown.cs +++ b/osu.Game/Rulesets/Mods/ModWindDown.cs @@ -16,7 +16,6 @@ namespace osu.Game.Rulesets.Mods public override string Acronym => "WD"; public override LocalisableString Description => "Sloooow doooown..."; public override IconUsage? Icon => FontAwesome.Solid.ChevronCircleDown; - public override double ScoreMultiplier => 1.0; [SettingSource("Initial rate", "The starting speed of the track")] public override BindableNumber InitialRate { get; } = new BindableDouble diff --git a/osu.Game/Rulesets/Mods/ModWindUp.cs b/osu.Game/Rulesets/Mods/ModWindUp.cs index 39cee50f96..13ece6d9a3 100644 --- a/osu.Game/Rulesets/Mods/ModWindUp.cs +++ b/osu.Game/Rulesets/Mods/ModWindUp.cs @@ -16,7 +16,6 @@ namespace osu.Game.Rulesets.Mods public override string Acronym => "WU"; public override LocalisableString Description => "Can you keep up?"; public override IconUsage? Icon => FontAwesome.Solid.ChevronCircleUp; - public override double ScoreMultiplier => 1.0; [SettingSource("Initial rate", "The starting speed of the track")] public override BindableNumber InitialRate { get; } = new BindableDouble From 648c6245bbe72f1625dcecdb35875580d5f76a93 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Sep 2022 17:39:54 +0900 Subject: [PATCH 385/420] Add xmldoc --- osu.Game/Graphics/Sprites/SizePreservingSpriteText.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/Sprites/SizePreservingSpriteText.cs b/osu.Game/Graphics/Sprites/SizePreservingSpriteText.cs index 11b81b26fd..baffe106ed 100644 --- a/osu.Game/Graphics/Sprites/SizePreservingSpriteText.cs +++ b/osu.Game/Graphics/Sprites/SizePreservingSpriteText.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. -#nullable disable - using System; using System.Collections.Generic; using osu.Framework.Graphics; @@ -14,6 +12,9 @@ using osuTK.Graphics; namespace osu.Game.Graphics.Sprites { + /// + /// A wrapped version of which will expand in size based on text content, but never shrink back down. + /// public class SizePreservingSpriteText : CompositeDrawable { private readonly OsuSpriteText text = new OsuSpriteText(); From 68d1b7d7cf61603c1ee05e4958be1aec371cc8d4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Sep 2022 17:48:51 +0900 Subject: [PATCH 386/420] Reduce test count --- .../TestSceneUprightAspectMaintainingContainer.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneUprightAspectMaintainingContainer.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneUprightAspectMaintainingContainer.cs index f81f8e8d85..00ecc166d5 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneUprightAspectMaintainingContainer.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneUprightAspectMaintainingContainer.cs @@ -31,7 +31,7 @@ namespace osu.Game.Tests.Visual.UserInterface private readonly List> childContainers = new List>(rows); // Preferably should be set to (4 * 2^n) - private const int rotation_step_count = 8; + private const int rotation_step_count = 3; private readonly List flipStates = new List(); private readonly List rotationSteps = new List(); @@ -127,7 +127,7 @@ namespace osu.Game.Tests.Visual.UserInterface flipStates.AddRange(new[] { 1, -1 }); rotationSteps.AddRange(Enumerable.Range(0, rotation_step_count).Select(x => 360f * ((float)x / rotation_step_count))); - scaleSteps.AddRange(new[] { 1, 0.5f, 0.3f, 1.5f, 2.0f }); + scaleSteps.AddRange(new[] { 1, 0.3f, 1.5f }); } [Test] From 9ec399a25ac560d409930d0387499ed247e0c549 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Sep 2022 17:49:31 +0900 Subject: [PATCH 387/420] Remove NRT overrides in new tests --- .../Visual/UserInterface/TestSceneSizePreservingSpriteText.cs | 2 -- .../UserInterface/TestSceneUprightAspectMaintainingContainer.cs | 2 -- 2 files changed, 4 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneSizePreservingSpriteText.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneSizePreservingSpriteText.cs index 69f5015af6..c4568d9aeb 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneSizePreservingSpriteText.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneSizePreservingSpriteText.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. -#nullable disable - using System; using System.Collections.Generic; using System.Globalization; diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneUprightAspectMaintainingContainer.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneUprightAspectMaintainingContainer.cs index 00ecc166d5..67c26829df 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneUprightAspectMaintainingContainer.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneUprightAspectMaintainingContainer.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. -#nullable disable - using System; using System.Collections.Generic; using System.Linq; From a2f96ea120b9461edae3194e0bf7a6229fedceab Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Sep 2022 17:55:29 +0900 Subject: [PATCH 388/420] Make `random` implicitly null to avoid potential incorrect behaviour in `randomBool` --- osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs | 24 +++++++++------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs index 17df1bf489..056a325dce 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs @@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Osu.Mods private static readonly float playfield_diagonal = OsuPlayfield.BASE_SIZE.LengthFast; - private Random? rng; + private Random random = null!; public void ApplyToBeatmap(IBeatmap beatmap) { @@ -36,7 +36,7 @@ namespace osu.Game.Rulesets.Osu.Mods Seed.Value ??= RNG.Next(); - rng = new Random((int)Seed.Value); + random = new Random((int)Seed.Value); var positionInfos = OsuHitObjectGenerationUtils.GeneratePositionInfos(osuBeatmap.HitObjects); @@ -50,14 +50,14 @@ namespace osu.Game.Rulesets.Osu.Mods { if (shouldStartNewSection(osuBeatmap, positionInfos, i)) { - sectionOffset = OsuHitObjectGenerationUtils.RandomGaussian(rng, 0, 0.0008f); + sectionOffset = OsuHitObjectGenerationUtils.RandomGaussian(random, 0, 0.0008f); flowDirection = !flowDirection; } if (i == 0) { - positionInfos[i].DistanceFromPrevious = (float)(rng.NextDouble() * OsuPlayfield.BASE_SIZE.Y / 2); - positionInfos[i].RelativeAngle = (float)(rng.NextDouble() * 2 * Math.PI - Math.PI); + positionInfos[i].DistanceFromPrevious = (float)(random.NextDouble() * OsuPlayfield.BASE_SIZE.Y / 2); + positionInfos[i].RelativeAngle = (float)(random.NextDouble() * 2 * Math.PI - Math.PI); } else { @@ -65,11 +65,11 @@ namespace osu.Game.Rulesets.Osu.Mods float flowChangeOffset = 0; // Offsets only the angle of the current hit object. - float oneTimeOffset = OsuHitObjectGenerationUtils.RandomGaussian(rng, 0, 0.002f); + float oneTimeOffset = OsuHitObjectGenerationUtils.RandomGaussian(random, 0, 0.002f); if (shouldApplyFlowChange(positionInfos, i)) { - flowChangeOffset = OsuHitObjectGenerationUtils.RandomGaussian(rng, 0, 0.002f); + flowChangeOffset = OsuHitObjectGenerationUtils.RandomGaussian(random, 0, 0.002f); flowDirection = !flowDirection; } @@ -108,9 +108,9 @@ namespace osu.Game.Rulesets.Osu.Mods bool previousObjectWasOnDownbeat = OsuHitObjectGenerationUtils.IsHitObjectOnBeat(beatmap, positionInfos[i - 1].HitObject, true); bool previousObjectWasOnBeat = OsuHitObjectGenerationUtils.IsHitObjectOnBeat(beatmap, positionInfos[i - 1].HitObject); - return (previousObjectStartedCombo && randomBool(0.6f)) || + return (previousObjectStartedCombo && random.NextDouble() < 0.6f) || previousObjectWasOnDownbeat || - (previousObjectWasOnBeat && randomBool(0.4f)); + (previousObjectWasOnBeat && random.NextDouble() < 0.4f); } /// Whether a flow change should be applied at the current . @@ -120,11 +120,7 @@ namespace osu.Game.Rulesets.Osu.Mods bool previousObjectStartedCombo = positionInfos[Math.Max(0, i - 2)].HitObject.IndexInCurrentCombo > 1 && positionInfos[i - 1].HitObject.NewCombo; - return previousObjectStartedCombo && randomBool(0.6f); + return previousObjectStartedCombo && random.NextDouble() < 0.6f; } - - /// true with a probability of , false otherwise. - private bool randomBool(float probability) => - rng?.NextDouble() < probability; } } From 31cd7cdca0902e10605552cfdb004531bfb4a3df Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Sep 2022 18:00:51 +0900 Subject: [PATCH 389/420] Refactor `IsHitObjectOnBeat` to be understandable --- .../Utils/OsuHitObjectGenerationUtils.cs | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs index a890dbde43..3a8b3f67d0 100644 --- a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs +++ b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs @@ -195,19 +195,17 @@ namespace osu.Game.Rulesets.Osu.Utils /// true if hitObject is on a (down-)beat, false otherwise. public static bool IsHitObjectOnBeat(OsuBeatmap beatmap, OsuHitObject hitObject, bool downbeatsOnly = false) { - var timingPoints = beatmap.ControlPointInfo.TimingPoints; - var currentTimingPoint = timingPoints.LastOrDefault(p => p.Time <= hitObject.StartTime); + var timingPoint = beatmap.ControlPointInfo.TimingPointAt(hitObject.StartTime); - if (currentTimingPoint == null) - return false; + double timeSinceTimingPoint = hitObject.StartTime - timingPoint.Time; - double timeSinceTimingPoint = hitObject.StartTime - currentTimingPoint.Time; + double beatLength = timingPoint.BeatLength; - double length = downbeatsOnly - ? currentTimingPoint.BeatLength * currentTimingPoint.TimeSignature.Numerator - : currentTimingPoint.BeatLength; + if (downbeatsOnly) + beatLength *= timingPoint.TimeSignature.Numerator; - return (timeSinceTimingPoint + 1) % length < 2; + // Ensure within 1ms of expected location. + return Math.Abs(timeSinceTimingPoint + 1) % beatLength < 2; } /// From 7d100c5eeca6aab52fc624b31f99b4d66515d01d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Sep 2022 18:10:10 +0900 Subject: [PATCH 390/420] Fix test in line with new expectations --- .../Visual/Beatmaps/TestSceneBeatmapCardThumbnail.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCardThumbnail.cs b/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCardThumbnail.cs index 0af876bfe8..5afda0ad0c 100644 --- a/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCardThumbnail.cs +++ b/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCardThumbnail.cs @@ -42,7 +42,7 @@ namespace osu.Game.Tests.Visual.Beatmaps }; }); AddStep("enable dim", () => thumbnail.Dimmed.Value = true); - AddUntilStep("button visible", () => playButton.IsPresent); + AddUntilStep("button visible", () => playButton.Alpha == 1); AddStep("click button", () => { @@ -70,7 +70,7 @@ namespace osu.Game.Tests.Visual.Beatmaps AddStep("disable dim", () => thumbnail.Dimmed.Value = false); AddWaitStep("wait some", 3); - AddAssert("button still visible", () => playButton.IsPresent); + AddAssert("button still visible", () => playButton.Alpha == 1); // The track plays in real-time, so we need to check for progress in increments to avoid timeout. AddUntilStep("progress > 0.25", () => thumbnail.ChildrenOfType().Single().Progress.Value > 0.25); @@ -78,7 +78,7 @@ namespace osu.Game.Tests.Visual.Beatmaps AddUntilStep("progress > 0.75", () => thumbnail.ChildrenOfType().Single().Progress.Value > 0.75); AddUntilStep("wait for track to end", () => !playButton.Playing.Value); - AddUntilStep("button hidden", () => !playButton.IsPresent); + AddUntilStep("button hidden", () => playButton.Alpha == 0); } private void iconIs(IconUsage usage) => AddUntilStep("icon is correct", () => playButton.ChildrenOfType().Any(icon => icon.Icon.Equals(usage))); From 602ffebd54d79e76bb19a4b95572b4023f07562f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Sep 2022 23:29:04 +0900 Subject: [PATCH 391/420] Apply NRT and fix code style --- osu.Game/Screens/Select/FilterQueryParser.cs | 25 ++++++++++++-------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/osu.Game/Screens/Select/FilterQueryParser.cs b/osu.Game/Screens/Select/FilterQueryParser.cs index 75574c061d..a7c1aec361 100644 --- a/osu.Game/Screens/Select/FilterQueryParser.cs +++ b/osu.Game/Screens/Select/FilterQueryParser.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. -#nullable disable - using System; using System.Collections.Generic; using System.Globalization; @@ -126,16 +124,21 @@ namespace osu.Game.Screens.Select { if (Enum.TryParse(value, true, out result)) return true; - value = Enum.GetNames(typeof(TEnum)).FirstOrDefault(name => name.StartsWith(value, true, CultureInfo.InvariantCulture)); + string? prefixMatch = Enum.GetNames(typeof(TEnum)).FirstOrDefault(name => name.StartsWith(value, true, CultureInfo.InvariantCulture)); + + if (prefixMatch == null) + return false; + return Enum.TryParse(value, true, out result); } - private static GroupCollection tryMatchRegex(string value, string regex) + + private static GroupCollection? tryMatchRegex(string value, string regex) { - Match matchs = Regex.Match(value, regex); - if (matchs.Success) - { - return matchs.Groups; - } + Match matches = Regex.Match(value, regex); + + if (matches.Success) + return matches.Groups; + return null; } @@ -324,7 +327,8 @@ namespace osu.Game.Screens.Select { List parts = new List(); - GroupCollection match = null; + GroupCollection? match = null; + match ??= tryMatchRegex(val, @"^((?\d+):)?(?\d+):(?\d+)$"); match ??= tryMatchRegex(val, @"^((?\d+(\.\d+)?)h)?((?\d+(\.\d+)?)m)?((?\d+(\.\d+)?)s)?$"); match ??= tryMatchRegex(val, @"^(?\d+(\.\d+)?)$"); @@ -358,6 +362,7 @@ namespace osu.Game.Screens.Select totalLength += length * scale; minScale = Math.Min(minScale, scale); } + return tryUpdateCriteriaRange(ref criteria.Length, op, totalLength, minScale / 2.0); } } From ae53e27e6cad2157dba515c71946012dcbca1c5d Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 9 Sep 2022 19:39:01 +0300 Subject: [PATCH 392/420] Adjust test coverage to read better --- .../SongSelect/TestSceneTopLocalRank.cs | 63 ++++++------------- 1 file changed, 19 insertions(+), 44 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneTopLocalRank.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneTopLocalRank.cs index 1a72dc2a72..cced9b8b89 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneTopLocalRank.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneTopLocalRank.cs @@ -1,14 +1,12 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System.Collections.Generic; using System.Diagnostics; using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Extensions; -using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Platform; using osu.Framework.Testing; @@ -49,12 +47,12 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("Create local rank", () => { - Add(topLocalRank = new TopLocalRank(importedBeatmap) + Child = topLocalRank = new TopLocalRank(importedBeatmap) { Anchor = Anchor.Centre, Origin = Anchor.Centre, Scale = new Vector2(10), - }); + }; }); AddAssert("No rank displayed initially", () => topLocalRank.DisplayedRank == null); @@ -63,7 +61,7 @@ namespace osu.Game.Tests.Visual.SongSelect [Test] public void TestBasicImportDelete() { - ScoreInfo? testScoreInfo = null; + ScoreInfo testScoreInfo = null!; AddStep("Add score for current user", () => { @@ -77,10 +75,7 @@ namespace osu.Game.Tests.Visual.SongSelect AddUntilStep("B rank displayed", () => topLocalRank.DisplayedRank == ScoreRank.B); - AddStep("Delete score", () => - { - scoreManager.Delete(testScoreInfo.AsNonNull()); - }); + AddStep("Delete score", () => scoreManager.Delete(testScoreInfo)); AddUntilStep("No rank displayed", () => topLocalRank.DisplayedRank == null); } @@ -88,11 +83,9 @@ namespace osu.Game.Tests.Visual.SongSelect [Test] public void TestRulesetChange() { - ScoreInfo testScoreInfo; - AddStep("Add score for current user", () => { - testScoreInfo = TestResources.CreateTestScoreInfo(importedBeatmap); + var testScoreInfo = TestResources.CreateTestScoreInfo(importedBeatmap); testScoreInfo.User = API.LocalUser.Value; testScoreInfo.Rank = ScoreRank.B; @@ -112,11 +105,9 @@ namespace osu.Game.Tests.Visual.SongSelect [Test] public void TestHigherScoreSet() { - ScoreInfo? testScoreInfo = null; - AddStep("Add score for current user", () => { - testScoreInfo = TestResources.CreateTestScoreInfo(importedBeatmap); + var testScoreInfo = TestResources.CreateTestScoreInfo(importedBeatmap); testScoreInfo.User = API.LocalUser.Value; testScoreInfo.Rank = ScoreRank.B; @@ -131,28 +122,20 @@ namespace osu.Game.Tests.Visual.SongSelect var testScoreInfo2 = TestResources.CreateTestScoreInfo(importedBeatmap); testScoreInfo2.User = API.LocalUser.Value; - testScoreInfo2.Rank = ScoreRank.S; - testScoreInfo2.TotalScore = testScoreInfo.AsNonNull().TotalScore + 1; - testScoreInfo2.Statistics = new Dictionary - { - [HitResult.Miss] = 0, - [HitResult.Perfect] = 970, - [HitResult.SmallTickHit] = 75, - [HitResult.LargeTickHit] = 150, - [HitResult.LargeBonus] = 10, - [HitResult.SmallBonus] = 50 - }; + testScoreInfo2.Rank = ScoreRank.X; + testScoreInfo2.TotalScore = 1000000; + testScoreInfo2.Statistics = testScoreInfo2.MaximumStatistics; scoreManager.Import(testScoreInfo2); }); - AddUntilStep("S rank displayed", () => topLocalRank.DisplayedRank == ScoreRank.S); + AddUntilStep("SS rank displayed", () => topLocalRank.DisplayedRank == ScoreRank.X); } [Test] public void TestLegacyScore() { - ScoreInfo? testScoreInfo = null; + ScoreInfo testScoreInfo = null!; AddStep("Add legacy score for current user", () => { @@ -160,7 +143,7 @@ namespace osu.Game.Tests.Visual.SongSelect testScoreInfo.User = API.LocalUser.Value; testScoreInfo.Rank = ScoreRank.B; - testScoreInfo.TotalScore = scoreManager.GetTotalScoreAsync(testScoreInfo, ScoringMode.Classic).GetResultSafely(); + testScoreInfo.TotalScore = scoreManager.GetTotalScore(testScoreInfo, ScoringMode.Classic); scoreManager.Import(testScoreInfo); }); @@ -172,26 +155,18 @@ namespace osu.Game.Tests.Visual.SongSelect var testScoreInfo2 = TestResources.CreateTestScoreInfo(importedBeatmap); testScoreInfo2.User = API.LocalUser.Value; - testScoreInfo2.Rank = ScoreRank.S; - testScoreInfo2.Statistics = new Dictionary - { - [HitResult.Miss] = 0, - [HitResult.Perfect] = 970, - [HitResult.SmallTickHit] = 75, - [HitResult.LargeTickHit] = 150, - [HitResult.LargeBonus] = 10, - [HitResult.SmallBonus] = 50 - }; + testScoreInfo2.Rank = ScoreRank.X; + testScoreInfo2.Statistics = testScoreInfo2.MaximumStatistics; + testScoreInfo2.TotalScore = scoreManager.GetTotalScore(testScoreInfo2); - testScoreInfo2.TotalScore = scoreManager.GetTotalScoreAsync(testScoreInfo.AsNonNull()).GetResultSafely(); - - // ensure standardised total score is less than classic, otherwise this test is pointless. - Debug.Assert(testScoreInfo2.TotalScore < testScoreInfo.AsNonNull().TotalScore); + // ensure second score has a total score (standardised) less than first one (classic) + // despite having better statistics, otherwise this test is pointless. + Debug.Assert(testScoreInfo2.TotalScore < testScoreInfo.TotalScore); scoreManager.Import(testScoreInfo2); }); - AddUntilStep("S rank displayed", () => topLocalRank.DisplayedRank == ScoreRank.S); + AddUntilStep("SS rank displayed", () => topLocalRank.DisplayedRank == ScoreRank.X); } } } From 95062c2cc8fc804eb8e4a7d52d28e1f8b190d6c7 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 9 Sep 2022 19:42:58 +0300 Subject: [PATCH 393/420] Remove center origin specification to avoid single-frame layout issues --- osu.Game/Screens/Select/Carousel/TopLocalRank.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Screens/Select/Carousel/TopLocalRank.cs b/osu.Game/Screens/Select/Carousel/TopLocalRank.cs index cb2ff86794..0f000555d5 100644 --- a/osu.Game/Screens/Select/Carousel/TopLocalRank.cs +++ b/osu.Game/Screens/Select/Carousel/TopLocalRank.cs @@ -49,8 +49,6 @@ namespace osu.Game.Screens.Select.Carousel InternalChild = updateable = new UpdateableRank { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, Size = new Vector2(40, 20), Alpha = 0, }; From 5e4e3dfc2cb8ea50f83d4edbaab4f8ab19a77e8a Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 10 Sep 2022 02:55:20 +0300 Subject: [PATCH 394/420] Fix markdown container not rendering certain text correctly --- .../Markdown/OsuMarkdownContainer.cs | 36 ++++++++++++++++--- .../Comments/CommentMarkdownContainer.cs | 2 ++ .../Wiki/Markdown/WikiMarkdownContainer.cs | 2 ++ 3 files changed, 35 insertions(+), 5 deletions(-) diff --git a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownContainer.cs b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownContainer.cs index a2370a76cb..62fefe201d 100644 --- a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownContainer.cs +++ b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownContainer.cs @@ -4,7 +4,9 @@ #nullable disable using Markdig; -using Markdig.Extensions.AutoIdentifiers; +using Markdig.Extensions.AutoLinks; +using Markdig.Extensions.EmphasisExtras; +using Markdig.Extensions.Footnotes; using Markdig.Extensions.Tables; using Markdig.Extensions.Yaml; using Markdig.Syntax; @@ -18,6 +20,18 @@ namespace osu.Game.Graphics.Containers.Markdown { public class OsuMarkdownContainer : MarkdownContainer { + /// + /// Allows this markdown container to parse and link footnotes. + /// + /// + protected virtual bool Footnotes => false; + + /// + /// Allows this markdown container to make URL text clickable. + /// + /// + protected virtual bool Autolinks => false; + public OsuMarkdownContainer() { LineSpacing = 21; @@ -78,10 +92,22 @@ namespace osu.Game.Graphics.Containers.Markdown return new OsuMarkdownUnorderedListItem(level); } + // reference: https://github.com/ppy/osu-web/blob/05488a96b25b5a09f2d97c54c06dd2bae59d1dc8/app/Libraries/Markdown/OsuMarkdown.php#L301 protected override MarkdownPipeline CreateBuilder() - => new MarkdownPipelineBuilder().UseAutoIdentifiers(AutoIdentifierOptions.GitHub) - .UseEmojiAndSmiley() - .UseYamlFrontMatter() - .UseAdvancedExtensions().Build(); + { + var pipeline = new MarkdownPipelineBuilder() + .UseAutoIdentifiers() + .UsePipeTables() + .UseEmphasisExtras(EmphasisExtraOptions.Strikethrough) + .UseYamlFrontMatter(); + + if (Footnotes) + pipeline = pipeline.UseFootnotes(); + + if (Autolinks) + pipeline = pipeline.UseAutoLinks(); + + return pipeline.Build(); + } } } diff --git a/osu.Game/Overlays/Comments/CommentMarkdownContainer.cs b/osu.Game/Overlays/Comments/CommentMarkdownContainer.cs index 8fc011b2bf..b32b1c74c4 100644 --- a/osu.Game/Overlays/Comments/CommentMarkdownContainer.cs +++ b/osu.Game/Overlays/Comments/CommentMarkdownContainer.cs @@ -11,6 +11,8 @@ namespace osu.Game.Overlays.Comments { public class CommentMarkdownContainer : OsuMarkdownContainer { + protected override bool Autolinks => true; + protected override MarkdownHeading CreateHeading(HeadingBlock headingBlock) => new CommentMarkdownHeading(headingBlock); private class CommentMarkdownHeading : OsuMarkdownHeading diff --git a/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownContainer.cs b/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownContainer.cs index 7754c1d450..4f1083a75c 100644 --- a/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownContainer.cs +++ b/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownContainer.cs @@ -15,6 +15,8 @@ namespace osu.Game.Overlays.Wiki.Markdown { public class WikiMarkdownContainer : OsuMarkdownContainer { + protected override bool Footnotes => true; + public string CurrentPath { set => DocumentUrl = value; From ec86700f2903bca40fd9cbc7f5a0d062e0373466 Mon Sep 17 00:00:00 2001 From: andy840119 Date: Sat, 10 Sep 2022 10:45:51 +0800 Subject: [PATCH 395/420] Add the nullable disable annotation back becuse it will cause the api broken if remove the nullable disable annotation in the mania ruleset. --- .../Configuration/ManiaRulesetConfigManager.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Rulesets.Mania/Configuration/ManiaRulesetConfigManager.cs b/osu.Game.Rulesets.Mania/Configuration/ManiaRulesetConfigManager.cs index 64e1f75e2c..b30d72f2e2 100644 --- a/osu.Game.Rulesets.Mania/Configuration/ManiaRulesetConfigManager.cs +++ b/osu.Game.Rulesets.Mania/Configuration/ManiaRulesetConfigManager.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. +#nullable disable + using System; using osu.Framework.Configuration.Tracking; using osu.Game.Configuration; From 2f91b5c846b4055422574620717416c60480f60d Mon Sep 17 00:00:00 2001 From: andy840119 Date: Sat, 10 Sep 2022 10:46:10 +0800 Subject: [PATCH 396/420] Add missing import. --- osu.Game.Rulesets.Osu/OsuRuleset.cs | 1 + osu.Game.Rulesets.Taiko/TaikoRuleset.cs | 1 + 2 files changed, 2 insertions(+) diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index 24a4f16e9f..90733e0206 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.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.Extensions.EnumExtensions; diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index b53dc67584..90d9ffe527 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.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.Extensions.EnumExtensions; From 1398a7e11ebffc6815bf26a33a615ae63e30f54c Mon Sep 17 00:00:00 2001 From: andy840119 Date: Sat, 10 Sep 2022 10:46:40 +0800 Subject: [PATCH 397/420] Property should accept the nullable state. --- osu.Game/Online/API/APIMod.cs | 2 +- osu.Game/Overlays/Settings/Sections/RulesetSection.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Online/API/APIMod.cs b/osu.Game/Online/API/APIMod.cs index dcbaaea012..176f10975d 100644 --- a/osu.Game/Online/API/APIMod.cs +++ b/osu.Game/Online/API/APIMod.cs @@ -48,7 +48,7 @@ namespace osu.Game.Online.API public Mod ToMod(Ruleset ruleset) { - Mod resultMod = ruleset.CreateModFromAcronym(Acronym); + Mod? resultMod = ruleset.CreateModFromAcronym(Acronym); if (resultMod == null) { diff --git a/osu.Game/Overlays/Settings/Sections/RulesetSection.cs b/osu.Game/Overlays/Settings/Sections/RulesetSection.cs index a5f5810214..6f0b3c27a0 100644 --- a/osu.Game/Overlays/Settings/Sections/RulesetSection.cs +++ b/osu.Game/Overlays/Settings/Sections/RulesetSection.cs @@ -28,7 +28,7 @@ namespace osu.Game.Overlays.Settings.Sections { try { - SettingsSubsection section = ruleset.CreateSettings(); + SettingsSubsection? section = ruleset.CreateSettings(); if (section != null) Add(section); From 3d7367a8420ca60b029d4b557b7458dff7e7d487 Mon Sep 17 00:00:00 2001 From: andy840119 Date: Sat, 10 Sep 2022 11:07:23 +0800 Subject: [PATCH 398/420] Move the CreateConvertibleReplayFrame() into the base ruleset class for avoid api breaking change in the customized ruleset. --- 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/ILegacyRuleset.cs | 9 --------- osu.Game/Rulesets/Ruleset.cs | 8 ++++++++ osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs | 2 +- osu.Game/Screens/Play/SpectatorPlayer.cs | 7 ++----- 8 files changed, 15 insertions(+), 19 deletions(-) diff --git a/osu.Game.Rulesets.Catch/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs index d2e05fbeb9..999b9f6e2c 100644 --- a/osu.Game.Rulesets.Catch/CatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs @@ -188,7 +188,7 @@ namespace osu.Game.Rulesets.Catch public int LegacyID => 2; - public IConvertibleReplayFrame CreateConvertibleReplayFrame() => new CatchReplayFrame(); + public override IConvertibleReplayFrame CreateConvertibleReplayFrame() => new CatchReplayFrame(); public override HitObjectComposer CreateHitObjectComposer() => new CatchHitObjectComposer(this); diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index 72be77ddab..c6a5e8bdaa 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -281,7 +281,7 @@ namespace osu.Game.Rulesets.Mania public int LegacyID => 3; - public IConvertibleReplayFrame CreateConvertibleReplayFrame() => new ManiaReplayFrame(); + public override IConvertibleReplayFrame CreateConvertibleReplayFrame() => new ManiaReplayFrame(); public override IRulesetConfigManager CreateConfig(SettingsStore? settings) => new ManiaRulesetConfigManager(settings, RulesetInfo); diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index 90733e0206..6af765361d 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -235,7 +235,7 @@ namespace osu.Game.Rulesets.Osu public int LegacyID => 0; - public IConvertibleReplayFrame CreateConvertibleReplayFrame() => new OsuReplayFrame(); + public override IConvertibleReplayFrame CreateConvertibleReplayFrame() => new OsuReplayFrame(); public override IRulesetConfigManager CreateConfig(SettingsStore? settings) => new OsuRulesetConfigManager(settings, RulesetInfo); diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index 90d9ffe527..6beb83575d 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -178,7 +178,7 @@ namespace osu.Game.Rulesets.Taiko public int LegacyID => 1; - public IConvertibleReplayFrame CreateConvertibleReplayFrame() => new TaikoReplayFrame(); + public override IConvertibleReplayFrame CreateConvertibleReplayFrame() => new TaikoReplayFrame(); protected override IEnumerable GetValidHitResults() { diff --git a/osu.Game/Rulesets/ILegacyRuleset.cs b/osu.Game/Rulesets/ILegacyRuleset.cs index c2617a065c..f4b03baccd 100644 --- a/osu.Game/Rulesets/ILegacyRuleset.cs +++ b/osu.Game/Rulesets/ILegacyRuleset.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.Replays.Types; - namespace osu.Game.Rulesets { public interface ILegacyRuleset @@ -13,12 +11,5 @@ namespace osu.Game.Rulesets /// Identifies the server-side ID of a legacy ruleset. /// int LegacyID { get; } - - /// - /// For rulesets which support legacy (osu-stable) replay conversion, this method will create an empty replay frame - /// for conversion use. - /// - /// An empty frame for the current ruleset, or null if unsupported. - IConvertibleReplayFrame CreateConvertibleReplayFrame(); } } diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index a10575ef73..4bc82a5f7e 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -23,6 +23,7 @@ using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Filter; using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Replays.Types; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; using osu.Game.Scoring; @@ -302,6 +303,13 @@ namespace osu.Game.Rulesets /// A descriptive name of the variant. public virtual LocalisableString GetVariantName(int variant) => string.Empty; + /// + /// For rulesets which support legacy (osu-stable) replay conversion, this method will create an empty replay frame + /// for conversion use. + /// + /// An empty frame for the current ruleset, or null if unsupported. + public virtual IConvertibleReplayFrame? CreateConvertibleReplayFrame() => null; + /// /// Creates the statistics for a to be displayed in the results screen. /// diff --git a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs index 2b22b4abc7..f64e730c06 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs @@ -282,7 +282,7 @@ namespace osu.Game.Scoring.Legacy private ReplayFrame convertFrame(LegacyReplayFrame currentFrame, ReplayFrame lastFrame) { - var convertible = (currentRuleset as ILegacyRuleset)?.CreateConvertibleReplayFrame(); + var convertible = currentRuleset.CreateConvertibleReplayFrame(); if (convertible == null) throw new InvalidOperationException($"Legacy replay cannot be converted for the ruleset: {currentRuleset.Description}"); diff --git a/osu.Game/Screens/Play/SpectatorPlayer.cs b/osu.Game/Screens/Play/SpectatorPlayer.cs index 659d7d5d6c..68cc21fc1c 100644 --- a/osu.Game/Screens/Play/SpectatorPlayer.cs +++ b/osu.Game/Screens/Play/SpectatorPlayer.cs @@ -11,7 +11,6 @@ using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Online.Spectator; -using osu.Game.Rulesets; using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.Replays.Types; using osu.Game.Scoring; @@ -81,14 +80,12 @@ namespace osu.Game.Screens.Play if (!this.IsCurrentScreen()) return; - var legacyRuleset = GameplayState.Ruleset as ILegacyRuleset; - Debug.Assert(legacyRuleset != null); - bool isFirstBundle = score.Replay.Frames.Count == 0; foreach (var frame in bundle.Frames) { - IConvertibleReplayFrame convertibleFrame = legacyRuleset.CreateConvertibleReplayFrame(); + IConvertibleReplayFrame convertibleFrame = GameplayState.Ruleset.CreateConvertibleReplayFrame(); + Debug.Assert(convertibleFrame != null); convertibleFrame.FromLegacy(frame, GameplayState.Beatmap); var convertedFrame = (ReplayFrame)convertibleFrame; From efe3b1aa7bd047847510fe50f75f1f87d0c2f78b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 10 Sep 2022 14:10:49 +0900 Subject: [PATCH 399/420] Allow dismissing notifications without performing action using middle / right click --- .../TestSceneNotificationOverlay.cs | 60 ++++++++++++++++++- .../Overlays/Notifications/Notification.cs | 19 +++++- 2 files changed, 75 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs index 699b8f7d89..c0b29dde9a 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs @@ -12,11 +12,13 @@ using osu.Framework.Utils; using osu.Game.Graphics.Sprites; using osu.Game.Overlays; using osu.Game.Overlays.Notifications; +using osuTK; +using osuTK.Input; namespace osu.Game.Tests.Visual.UserInterface { [TestFixture] - public class TestSceneNotificationOverlay : OsuTestScene + public class TestSceneNotificationOverlay : OsuManualInputManagerTestScene { private NotificationOverlay notificationOverlay = null!; @@ -32,7 +34,7 @@ namespace osu.Game.Tests.Visual.UserInterface TimeToCompleteProgress = 2000; progressingNotifications.Clear(); - Content.Children = new Drawable[] + Children = new Drawable[] { notificationOverlay = new NotificationOverlay { @@ -45,6 +47,60 @@ namespace osu.Game.Tests.Visual.UserInterface notificationOverlay.UnreadCount.ValueChanged += count => { displayedCount.Text = $"displayed count: {count.NewValue}"; }; }); + [Test] + public void TestDismissWithoutActivationRightClick() + { + bool activated = false; + SimpleNotification notification = null!; + + AddStep("post", () => + { + activated = false; + notificationOverlay.Post(notification = new SimpleNotification + { + Text = @"Welcome to osu!. Enjoy your stay!", + Activated = () => activated = true, + }); + }); + + AddStep("click to activate", () => + { + InputManager.MoveMouseTo(notificationOverlay.ChildrenOfType().Single()); + InputManager.Click(MouseButton.Right); + }); + + AddUntilStep("wait for closed", () => notification.WasClosed); + AddAssert("was not activated", () => !activated); + AddStep("reset mouse position", () => InputManager.MoveMouseTo(Vector2.Zero)); + } + + [Test] + public void TestActivate() + { + bool activated = false; + SimpleNotification notification = null!; + + AddStep("post", () => + { + activated = false; + notificationOverlay.Post(notification = new SimpleNotification + { + Text = @"Welcome to osu!. Enjoy your stay!", + Activated = () => activated = true, + }); + }); + + AddStep("click to activate", () => + { + InputManager.MoveMouseTo(notificationOverlay.ChildrenOfType().Single()); + InputManager.Click(MouseButton.Left); + }); + + AddUntilStep("wait for closed", () => notification.WasClosed); + AddAssert("was activated", () => activated); + AddStep("reset mouse position", () => InputManager.MoveMouseTo(Vector2.Zero)); + } + [Test] public void TestPresence() { diff --git a/osu.Game/Overlays/Notifications/Notification.cs b/osu.Game/Overlays/Notifications/Notification.cs index e5f739bb08..09abdb865d 100644 --- a/osu.Game/Overlays/Notifications/Notification.cs +++ b/osu.Game/Overlays/Notifications/Notification.cs @@ -16,6 +16,7 @@ using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osuTK; using osuTK.Graphics; +using osuTK.Input; namespace osu.Game.Overlays.Notifications { @@ -170,11 +171,25 @@ namespace osu.Game.Overlays.Notifications base.OnHoverLost(e); } + protected override bool OnMouseDown(MouseDownEvent e) + { + // right click doesn't trigger OnClick so we need to handle here until that changes. + if (e.Button != MouseButton.Left) + { + Close(); + return true; + } + + return base.OnMouseDown(e); + } + protected override bool OnClick(ClickEvent e) { - if (Activated?.Invoke() ?? true) - Close(); + // Clicking with anything but left button should dismiss but not perform the activation action. + if (e.Button == MouseButton.Left) + Activated?.Invoke(); + Close(); return true; } From a9094c6b03bf302382cf171c72a5f217e2668189 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 10 Sep 2022 15:18:32 +0900 Subject: [PATCH 400/420] Add test coverage of clicking close button to dismiss --- .../TestSceneNotificationOverlay.cs | 29 +++++++++++++++++++ .../Overlays/Notifications/Notification.cs | 2 +- 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs index c0b29dde9a..5f82a0024b 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs @@ -47,6 +47,35 @@ namespace osu.Game.Tests.Visual.UserInterface notificationOverlay.UnreadCount.ValueChanged += count => { displayedCount.Text = $"displayed count: {count.NewValue}"; }; }); + [Test] + public void TestDismissWithoutActivationCloseButton() + { + bool activated = false; + SimpleNotification notification = null!; + + AddStep("post", () => + { + activated = false; + notificationOverlay.Post(notification = new SimpleNotification + { + Text = @"Welcome to osu!. Enjoy your stay!", + Activated = () => activated = true, + }); + }); + + AddStep("click to activate", () => + { + InputManager.MoveMouseTo(notificationOverlay + .ChildrenOfType().Single() + .ChildrenOfType().Single()); + InputManager.Click(MouseButton.Left); + }); + + AddUntilStep("wait for closed", () => notification.WasClosed); + AddAssert("was not activated", () => !activated); + AddStep("reset mouse position", () => InputManager.MoveMouseTo(Vector2.Zero)); + } + [Test] public void TestDismissWithoutActivationRightClick() { diff --git a/osu.Game/Overlays/Notifications/Notification.cs b/osu.Game/Overlays/Notifications/Notification.cs index 09abdb865d..67739c2089 100644 --- a/osu.Game/Overlays/Notifications/Notification.cs +++ b/osu.Game/Overlays/Notifications/Notification.cs @@ -218,7 +218,7 @@ namespace osu.Game.Overlays.Notifications Expire(); } - private class CloseButton : OsuClickableContainer + internal class CloseButton : OsuClickableContainer { private SpriteIcon icon = null!; private Box background = null!; From 1eb2c74e57c3d18dfcac974bf7c52101a18806ec Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Sun, 11 Sep 2022 18:50:51 +0900 Subject: [PATCH 401/420] Fix countdown serialisation --- osu.Game/Online/Multiplayer/MultiplayerCountdown.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Online/Multiplayer/MultiplayerCountdown.cs b/osu.Game/Online/Multiplayer/MultiplayerCountdown.cs index 61637ae970..fd22420b99 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerCountdown.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerCountdown.cs @@ -33,6 +33,7 @@ namespace osu.Game.Online.Multiplayer /// /// Whether only a single instance of this type may be active at any one time. /// + [IgnoreMember] public virtual bool IsExclusive => true; } } From bb3d5bc9f4d8b5c47ec2c833fd59a985c90264e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Acid=20Chicken=20=28=E7=A1=AB=E9=85=B8=E9=B6=8F=29?= Date: Mon, 12 Sep 2022 04:43:15 +0900 Subject: [PATCH 402/420] test(osu.Game): add slider steps to change the size in timing distribution graph tests --- .../TestSceneHitEventTimingDistributionGraph.cs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneHitEventTimingDistributionGraph.cs b/osu.Game.Tests/Visual/Ranking/TestSceneHitEventTimingDistributionGraph.cs index 198be4035b..e014d79402 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneHitEventTimingDistributionGraph.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneHitEventTimingDistributionGraph.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Linq; using NUnit.Framework; +using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; @@ -19,14 +20,24 @@ namespace osu.Game.Tests.Visual.Ranking public class TestSceneHitEventTimingDistributionGraph : OsuTestScene { private HitEventTimingDistributionGraph graph = null!; + private readonly BindableFloat width = new BindableFloat(600); + private readonly BindableFloat height = new BindableFloat(130); private static readonly HitObject placeholder_object = new HitCircle(); + public TestSceneHitEventTimingDistributionGraph() + { + width.BindValueChanged(e => graph.Width = e.NewValue); + height.BindValueChanged(e => graph.Height = e.NewValue); + } + [Test] public void TestManyDistributedEvents() { createTest(CreateDistributedHitEvents()); AddStep("add adjustment", () => graph.UpdateOffset(10)); + AddSliderStep("width", 0.0f, 1000.0f, width.Value, width.Set); + AddSliderStep("height", 0.0f, 1000.0f, height.Value, height.Set); } [Test] @@ -137,7 +148,7 @@ namespace osu.Game.Tests.Visual.Ranking { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Size = new Vector2(600, 130) + Size = new Vector2(width.Value, height.Value) } }; }); From ac324c63e2d0e3f0e9293519b6728aa7d415f90b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Acid=20Chicken=20=28=E7=A1=AB=E9=85=B8=E9=B6=8F=29?= Date: Mon, 12 Sep 2022 04:46:32 +0900 Subject: [PATCH 403/420] fix(osu.Game): handle size changes in timing distribution graph --- .../HitEventTimingDistributionGraph.cs | 83 ++++++++++++++----- 1 file changed, 63 insertions(+), 20 deletions(-) diff --git a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs index 5335d77243..72603fd46b 100644 --- a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs +++ b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs @@ -7,6 +7,7 @@ using System; using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -209,13 +210,12 @@ namespace osu.Game.Screens.Ranking.Statistics private class Bar : CompositeDrawable { - private float totalValue => values.Sum(v => v.Value); - private float basalHeight => BoundingBox.Width / BoundingBox.Height; - private float availableHeight => 1 - basalHeight; - private readonly IReadOnlyList> values; private readonly float maxValue; private readonly bool isCentre; + private readonly float totalValue; + private readonly BindableFloat basalHeight; + private readonly BindableFloat offsetAdjustment; private Circle[] boxOriginals; private Circle boxAdjustment; @@ -228,6 +228,9 @@ namespace osu.Game.Screens.Ranking.Statistics this.values = values.OrderBy(v => v.Key.GetIndexForOrderedDisplay()).ToList(); this.maxValue = maxValue; this.isCentre = isCentre; + totalValue = values.Sum(v => v.Value); + basalHeight = new BindableFloat(); + offsetAdjustment = new BindableFloat(totalValue); RelativeSizeAxes = Axes.Both; Masking = true; @@ -254,38 +257,70 @@ namespace osu.Game.Screens.Ranking.Statistics else { // A bin with no value draws a grey dot instead. - InternalChildren = boxOriginals = new[] + Circle dot = new Circle { - new Circle - { - RelativeSizeAxes = Axes.Both, - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - Colour = isCentre ? Color4.White : Color4.Gray, - Height = 0, - }, + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + Colour = isCentre ? Color4.White : Color4.Gray, + Height = 0, }; + InternalChildren = boxOriginals = new[] { dot }; + basalHeight.BindValueChanged(e => dot.Height = e.NewValue); } + + offsetAdjustment.BindValueChanged(_ => drawAdjustmentBar()); } private const double duration = 300; - private float offsetForValue(float value) + private float calculateBasalHeight() => DrawHeight == 0 ? 0 : DrawWidth / DrawHeight; + + private float offsetForValue(float value) => (1 - basalHeight.Value) * value / maxValue; + + private float heightForValue(float value) => MathF.Max(basalHeight.Value + offsetForValue(value), 0); + + private void draw() { - return availableHeight * value / maxValue; + resizeBars(); + + if (boxAdjustment != null) + drawAdjustmentBar(); } - private float heightForValue(float value) + private void resizeBars() { - return basalHeight + offsetForValue(value); + float offsetValue = 0; + + for (int i = 0; i < values.Count; i++) + { + boxOriginals[i].Y = offsetForValue(offsetValue) * BoundingBox.Height; + boxOriginals[i].Height = heightForValue(values[i].Value); + offsetValue -= values[i].Value; + } + } + + private void drawAdjustmentBar() + { + bool hasAdjustment = offsetAdjustment.Value != totalValue; + + boxAdjustment.ResizeHeightTo(heightForValue(offsetAdjustment.Value), duration, Easing.OutQuint); + boxAdjustment.FadeTo(!hasAdjustment ? 0 : 1, duration, Easing.OutQuint); } protected override void LoadComplete() { base.LoadComplete(); + float height = calculateBasalHeight(); + + basalHeight.Set(height); + + if (!values.Any()) + return; + foreach (var boxOriginal in boxOriginals) - boxOriginal.Height = basalHeight; + boxOriginal.Height = height; float offsetValue = 0; @@ -295,6 +330,15 @@ namespace osu.Game.Screens.Ranking.Statistics boxOriginals[i].ResizeHeightTo(heightForValue(values[i].Value), duration, Easing.OutQuint); offsetValue -= values[i].Value; } + + basalHeight.BindValueChanged(_ => draw()); + } + + protected override void Update() + { + base.Update(); + + basalHeight.Set(calculateBasalHeight()); } public void UpdateOffset(float adjustment) @@ -318,8 +362,7 @@ namespace osu.Game.Screens.Ranking.Statistics }); } - boxAdjustment.ResizeHeightTo(heightForValue(adjustment), duration, Easing.OutQuint); - boxAdjustment.FadeTo(!hasAdjustment ? 0 : 1, duration, Easing.OutQuint); + offsetAdjustment.Set(adjustment); } } } From 24138b65a74c1d57a2267dce3510f01442a3fabb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Sep 2022 14:05:16 +0900 Subject: [PATCH 404/420] Fix storyboard animations not starting their animation playback from the correct point in time --- .../Drawables/DrawableStoryboardAnimation.cs | 16 ++++++++++++++++ osu.Game/Storyboards/StoryboardSprite.cs | 10 +++++++++- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs index f3187d77b7..369a3ee7ba 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs @@ -10,6 +10,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Animations; using osu.Framework.Graphics.Textures; using osu.Framework.Utils; +using osu.Game.Screens.Play; using osu.Game.Skinning; using osuTK; @@ -115,6 +116,21 @@ namespace osu.Game.Storyboards.Drawables Animation.ApplyTransforms(this); } + [Resolved] + private IGameplayClock gameplayClock { get; set; } + + protected override void LoadComplete() + { + base.LoadComplete(); + + // Framework animation class tries its best to synchronise the animation at LoadComplete, + // but in some cases (such as fast forward) this results in an incorrect start offset. + // + // In the case of storyboard animations, we want to synchronise with game time perfectly + // so let's get a correct time based on gameplay clock and earliest transform. + PlaybackPosition = gameplayClock.CurrentTime - Animation.EarliestTransformTime; + } + private void skinSourceChanged() { ClearFrames(); diff --git a/osu.Game/Storyboards/StoryboardSprite.cs b/osu.Game/Storyboards/StoryboardSprite.cs index 1eeaa0f084..589bcb60be 100644 --- a/osu.Game/Storyboards/StoryboardSprite.cs +++ b/osu.Game/Storyboards/StoryboardSprite.cs @@ -26,7 +26,7 @@ namespace osu.Game.Storyboards public readonly CommandTimelineGroup TimelineGroup = new CommandTimelineGroup(); - public double StartTime + public virtual double StartTime { get { @@ -54,6 +54,14 @@ namespace osu.Game.Storyboards return firstAlpha.startTime; } + return EarliestTransformTime; + } + } + + public double EarliestTransformTime + { + get + { // If we got to this point, either no alpha commands were present, or the earliest had a non-zero start value. // The sprite's StartTime will be determined by the earliest command, regardless of type. double earliestStartTime = TimelineGroup.StartTime; From cf25ee8e8482b5c60fb6fd8b8f0d7e23fcf903d0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Sep 2022 14:17:15 +0900 Subject: [PATCH 405/420] Add test coverage of storyboard animation start time --- .../Formats/LegacyStoryboardDecoderTest.cs | 19 +++++++++++++++++++ .../animation-starts-before-alpha.osb | 5 +++++ 2 files changed, 24 insertions(+) create mode 100644 osu.Game.Tests/Resources/animation-starts-before-alpha.osb diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs index 6e41043b0b..d9e80fa111 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs @@ -97,6 +97,25 @@ namespace osu.Game.Tests.Beatmaps.Formats } } + [Test] + public void TestCorrectAnimationStartTime() + { + var decoder = new LegacyStoryboardDecoder(); + + using (var resStream = TestResources.OpenResource("animation-starts-before-alpha.osb")) + using (var stream = new LineBufferedReader(resStream)) + { + var storyboard = decoder.Decode(stream); + + StoryboardLayer background = storyboard.Layers.Single(l => l.Depth == 3); + Assert.AreEqual(1, background.Elements.Count); + + Assert.AreEqual(2000, background.Elements[0].StartTime); + // This property should be used in DrawableStoryboardAnimation as a starting point for animation playback. + Assert.AreEqual(1000, (background.Elements[0] as StoryboardAnimation)?.EarliestTransformTime); + } + } + [Test] public void TestOutOfOrderStartTimes() { diff --git a/osu.Game.Tests/Resources/animation-starts-before-alpha.osb b/osu.Game.Tests/Resources/animation-starts-before-alpha.osb new file mode 100644 index 0000000000..ceef204f3f --- /dev/null +++ b/osu.Game.Tests/Resources/animation-starts-before-alpha.osb @@ -0,0 +1,5 @@ +[Events] +//Storyboard Layer 0 (Background) +Animation,Background,Centre,"img.jpg",320,240,2,150,LoopForever + S,0,1000,1500,0.08 // animation should start playing from this point in time.. + F,0,2000,,0,1 // .. not this point in time From bbf906ee064507157512ba692962707a4c682b1b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Sep 2022 14:20:48 +0900 Subject: [PATCH 406/420] Remove unnecessary `virtual` spec --- osu.Game/Storyboards/StoryboardSprite.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Storyboards/StoryboardSprite.cs b/osu.Game/Storyboards/StoryboardSprite.cs index 589bcb60be..cd7788bb08 100644 --- a/osu.Game/Storyboards/StoryboardSprite.cs +++ b/osu.Game/Storyboards/StoryboardSprite.cs @@ -26,7 +26,7 @@ namespace osu.Game.Storyboards public readonly CommandTimelineGroup TimelineGroup = new CommandTimelineGroup(); - public virtual double StartTime + public double StartTime { get { From eca241e9a71943f45b0226b02ea645c1790751e8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Sep 2022 14:52:11 +0900 Subject: [PATCH 407/420] Move `UpdateProgressNotification` to base `UpdateManager` class --- osu.Desktop/Updater/SquirrelUpdateManager.cs | 112 ++++++------------- osu.Game/Updater/UpdateManager.cs | 72 +++++++++++- 2 files changed, 99 insertions(+), 85 deletions(-) diff --git a/osu.Desktop/Updater/SquirrelUpdateManager.cs b/osu.Desktop/Updater/SquirrelUpdateManager.cs index 6f45237522..84bac9da7c 100644 --- a/osu.Desktop/Updater/SquirrelUpdateManager.cs +++ b/osu.Desktop/Updater/SquirrelUpdateManager.cs @@ -5,26 +5,24 @@ using System; using System.Runtime.Versioning; using System.Threading.Tasks; using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Sprites; using osu.Framework.Logging; using osu.Game; -using osu.Game.Graphics; using osu.Game.Overlays; using osu.Game.Overlays.Notifications; -using osuTK; using Squirrel; using Squirrel.SimpleSplat; +using LogLevel = Squirrel.SimpleSplat.LogLevel; +using UpdateManager = osu.Game.Updater.UpdateManager; namespace osu.Desktop.Updater { [SupportedOSPlatform("windows")] - public class SquirrelUpdateManager : osu.Game.Updater.UpdateManager + public class SquirrelUpdateManager : UpdateManager { - private UpdateManager? updateManager; + private Squirrel.UpdateManager? updateManager; private INotificationOverlay notificationOverlay = null!; - public Task PrepareUpdateAsync() => UpdateManager.RestartAppWhenExited(); + public Task PrepareUpdateAsync() => Squirrel.UpdateManager.RestartAppWhenExited(); private static readonly Logger logger = Logger.GetLogger("updater"); @@ -35,6 +33,9 @@ namespace osu.Desktop.Updater private readonly SquirrelLogger squirrelLogger = new SquirrelLogger(); + [Resolved] + private OsuGameBase game { get; set; } = null!; + [BackgroundDependencyLoader] private void load(INotificationOverlay notifications) { @@ -63,7 +64,14 @@ namespace osu.Desktop.Updater if (updatePending) { // the user may have dismissed the completion notice, so show it again. - notificationOverlay.Post(new UpdateCompleteNotification(this)); + notificationOverlay.Post(new UpdateApplicationCompleteNotification + { + Activated = () => + { + restartToApplyUpdate(); + return true; + }, + }); return true; } @@ -75,19 +83,21 @@ namespace osu.Desktop.Updater if (notification == null) { - notification = new UpdateProgressNotification(this) { State = ProgressNotificationState.Active }; + notification = new UpdateProgressNotification + { + CompletionClickAction = restartToApplyUpdate, + }; + Schedule(() => notificationOverlay.Post(notification)); } - notification.Progress = 0; - notification.Text = @"Downloading update..."; + notification.StartDownload(); try { await updateManager.DownloadReleases(info.ReleasesToApply, p => notification.Progress = p / 100f).ConfigureAwait(false); - notification.Progress = 0; - notification.Text = @"Installing update..."; + notification.StartInstall(); await updateManager.ApplyReleases(info, p => notification.Progress = p / 100f).ConfigureAwait(false); @@ -107,9 +117,7 @@ namespace osu.Desktop.Updater else { // In the case of an error, a separate notification will be displayed. - notification.State = ProgressNotificationState.Cancelled; - notification.Close(); - + notification.FailDownload(); Logger.Error(e, @"update failed!"); } } @@ -131,78 +139,24 @@ namespace osu.Desktop.Updater return true; } + private bool restartToApplyUpdate() + { + PrepareUpdateAsync() + .ContinueWith(_ => Schedule(() => game.AttemptExit())); + return true; + } + protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); updateManager?.Dispose(); } - private class UpdateCompleteNotification : ProgressCompletionNotification - { - [Resolved] - private OsuGame game { get; set; } = null!; - - public UpdateCompleteNotification(SquirrelUpdateManager updateManager) - { - Text = @"Update ready to install. Click to restart!"; - - Activated = () => - { - updateManager.PrepareUpdateAsync() - .ContinueWith(_ => updateManager.Schedule(() => game.AttemptExit())); - return true; - }; - } - } - - private class UpdateProgressNotification : ProgressNotification - { - private readonly SquirrelUpdateManager updateManager; - - public UpdateProgressNotification(SquirrelUpdateManager updateManager) - { - this.updateManager = updateManager; - } - - protected override Notification CreateCompletionNotification() - { - return new UpdateCompleteNotification(updateManager); - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - IconContent.AddRange(new Drawable[] - { - new SpriteIcon - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Icon = FontAwesome.Solid.Upload, - Size = new Vector2(20), - } - }); - } - - public override void Close() - { - // cancelling updates is not currently supported by the underlying updater. - // only allow dismissing for now. - - switch (State) - { - case ProgressNotificationState.Cancelled: - base.Close(); - break; - } - } - } - private class SquirrelLogger : ILogger, IDisposable { - public Squirrel.SimpleSplat.LogLevel Level { get; set; } = Squirrel.SimpleSplat.LogLevel.Info; + public LogLevel Level { get; set; } = LogLevel.Info; - public void Write(string message, Squirrel.SimpleSplat.LogLevel logLevel) + public void Write(string message, LogLevel logLevel) { if (logLevel < Level) return; diff --git a/osu.Game/Updater/UpdateManager.cs b/osu.Game/Updater/UpdateManager.cs index 4790055cd1..801f2daac5 100644 --- a/osu.Game/Updater/UpdateManager.cs +++ b/osu.Game/Updater/UpdateManager.cs @@ -1,16 +1,16 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Threading.Tasks; using osu.Framework.Allocation; +using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Overlays; using osu.Game.Overlays.Notifications; +using osuTK; namespace osu.Game.Updater { @@ -27,13 +27,13 @@ namespace osu.Game.Updater GetType() != typeof(UpdateManager); [Resolved] - private OsuConfigManager config { get; set; } + private OsuConfigManager config { get; set; } = null!; [Resolved] - private OsuGameBase game { get; set; } + private OsuGameBase game { get; set; } = null!; [Resolved] - protected INotificationOverlay Notifications { get; private set; } + protected INotificationOverlay Notifications { get; private set; } = null!; protected override void LoadComplete() { @@ -59,7 +59,7 @@ namespace osu.Game.Updater private readonly object updateTaskLock = new object(); - private Task updateCheckTask; + private Task? updateCheckTask; public async Task CheckForUpdateAsync() { @@ -109,5 +109,65 @@ namespace osu.Game.Updater }; } } + + protected class UpdateApplicationCompleteNotification : ProgressCompletionNotification + { + public UpdateApplicationCompleteNotification() + { + Text = @"Update ready to install. Click to restart!"; + } + } + + public class UpdateProgressNotification : ProgressNotification + { + protected override Notification CreateCompletionNotification() => new UpdateApplicationCompleteNotification(); + + [BackgroundDependencyLoader] + private void load() + { + IconContent.AddRange(new Drawable[] + { + new SpriteIcon + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Icon = FontAwesome.Solid.Upload, + Size = new Vector2(14), + } + }); + } + + public override void Close() + { + // cancelling updates is not currently supported by the underlying updater. + // only allow dismissing for now. + + switch (State) + { + case ProgressNotificationState.Cancelled: + base.Close(); + break; + } + } + + public void StartDownload() + { + State = ProgressNotificationState.Active; + Progress = 0; + Text = @"Downloading update..."; + } + + public void StartInstall() + { + Progress = 0; + Text = @"Installing update..."; + } + + public void FailDownload() + { + State = ProgressNotificationState.Cancelled; + Close(); + } + } } } From 2b79e6b2de20e64cd984c4bb8c5878bc28bdb10e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Sep 2022 14:52:16 +0900 Subject: [PATCH 408/420] Add test coverage of update notification --- .../TestSceneNotificationOverlay.cs | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs index 699b8f7d89..1afe8327b3 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs @@ -12,6 +12,7 @@ using osu.Framework.Utils; using osu.Game.Graphics.Sprites; using osu.Game.Overlays; using osu.Game.Overlays.Notifications; +using osu.Game.Updater; namespace osu.Game.Tests.Visual.UserInterface { @@ -134,6 +135,31 @@ namespace osu.Game.Tests.Visual.UserInterface AddStep("cancel notification", () => notification.State = ProgressNotificationState.Cancelled); } + [Test] + public void TestUpdateNotificationFlow() + { + bool applyUpdate = false; + + AddStep(@"post update", () => + { + applyUpdate = false; + + var updateNotification = new UpdateManager.UpdateProgressNotification + { + CompletionClickAction = () => applyUpdate = true + }; + + notificationOverlay.Post(updateNotification); + progressingNotifications.Add(updateNotification); + }); + + checkProgressingCount(1); + waitForCompletion(); + AddStep("click notification", () => notificationOverlay.ChildrenOfType().Single().TriggerClick()); + + AddUntilStep("wait for update applied", () => applyUpdate); + } + [Test] public void TestBasicFlow() { From 60b0b909a51d1986956cddbf56a6927f1025b649 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Sep 2022 14:58:46 +0900 Subject: [PATCH 409/420] Move update icon to background to avoid colour collission with progress spinner --- .../Overlays/Notifications/ProgressNotification.cs | 1 + osu.Game/Updater/UpdateManager.cs | 10 +++++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Notifications/ProgressNotification.cs b/osu.Game/Overlays/Notifications/ProgressNotification.cs index 64ad69adf3..bdf6f704e5 100644 --- a/osu.Game/Overlays/Notifications/ProgressNotification.cs +++ b/osu.Game/Overlays/Notifications/ProgressNotification.cs @@ -226,6 +226,7 @@ namespace osu.Game.Overlays.Notifications { RelativeSizeAxes = Axes.Both, Colour = colourProvider.Background5, + Depth = float.MaxValue, }, loadingSpinner = new LoadingSpinner { diff --git a/osu.Game/Updater/UpdateManager.cs b/osu.Game/Updater/UpdateManager.cs index 801f2daac5..8dcb1c4562 100644 --- a/osu.Game/Updater/UpdateManager.cs +++ b/osu.Game/Updater/UpdateManager.cs @@ -132,11 +132,19 @@ namespace osu.Game.Updater Anchor = Anchor.Centre, Origin = Anchor.Centre, Icon = FontAwesome.Solid.Upload, - Size = new Vector2(14), + Size = new Vector2(34), + Colour = OsuColour.Gray(0.2f), + Depth = float.MaxValue, } }); } + protected override void LoadComplete() + { + base.LoadComplete(); + StartDownload(); + } + public override void Close() { // cancelling updates is not currently supported by the underlying updater. From 9b31aa6d7ace1a41888315f0bdcfc06ebf86c01a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Sep 2022 15:04:30 +0900 Subject: [PATCH 410/420] Fix activation not firing with refactors --- .../Visual/UserInterface/TestSceneNotificationOverlay.cs | 6 +++++- osu.Game/Updater/UpdateManager.cs | 7 +++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs index 1afe8327b3..f497a5bd6b 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs @@ -155,7 +155,11 @@ namespace osu.Game.Tests.Visual.UserInterface checkProgressingCount(1); waitForCompletion(); - AddStep("click notification", () => notificationOverlay.ChildrenOfType().Single().TriggerClick()); + + UpdateManager.UpdateApplicationCompleteNotification? completionNotification = null; + AddUntilStep("wait for completion notification", + () => (completionNotification = notificationOverlay.ChildrenOfType().SingleOrDefault()) != null); + AddStep("click notification", () => completionNotification?.TriggerClick()); AddUntilStep("wait for update applied", () => applyUpdate); } diff --git a/osu.Game/Updater/UpdateManager.cs b/osu.Game/Updater/UpdateManager.cs index 8dcb1c4562..49009e9124 100644 --- a/osu.Game/Updater/UpdateManager.cs +++ b/osu.Game/Updater/UpdateManager.cs @@ -110,7 +110,7 @@ namespace osu.Game.Updater } } - protected class UpdateApplicationCompleteNotification : ProgressCompletionNotification + public class UpdateApplicationCompleteNotification : ProgressCompletionNotification { public UpdateApplicationCompleteNotification() { @@ -120,7 +120,10 @@ namespace osu.Game.Updater public class UpdateProgressNotification : ProgressNotification { - protected override Notification CreateCompletionNotification() => new UpdateApplicationCompleteNotification(); + protected override Notification CreateCompletionNotification() => new UpdateApplicationCompleteNotification + { + Activated = CompletionClickAction + }; [BackgroundDependencyLoader] private void load() From 0755289ec3cfce14b47250baffabbbcacb76203e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Sep 2022 15:48:50 +0900 Subject: [PATCH 411/420] Apply NRT to `HitEventTimingDistributionGraph` --- .../HitEventTimingDistributionGraph.cs | 26 +++++++++---------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs index 72603fd46b..930a702958 100644 --- a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs +++ b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.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. -#nullable disable - using System; using System.Collections.Generic; using System.Linq; @@ -48,6 +46,12 @@ namespace osu.Game.Screens.Ranking.Statistics /// private readonly IReadOnlyList hitEvents; + private readonly IDictionary[] bins; + private double binSize; + private double hitOffset; + + private Bar[]? barDrawables; + /// /// Creates a new . /// @@ -55,22 +59,15 @@ namespace osu.Game.Screens.Ranking.Statistics public HitEventTimingDistributionGraph(IReadOnlyList hitEvents) { this.hitEvents = hitEvents.Where(e => !(e.HitObject.HitWindows is HitWindows.EmptyHitWindows) && e.Result.IsHit()).ToList(); + bins = Enumerable.Range(0, total_timing_distribution_bins).Select(_ => new Dictionary()).ToArray>(); } - private IDictionary[] bins; - private double binSize; - private double hitOffset; - - private Bar[] barDrawables; - [BackgroundDependencyLoader] private void load() { - if (hitEvents == null || hitEvents.Count == 0) + if (hitEvents.Count == 0) return; - bins = Enumerable.Range(0, total_timing_distribution_bins).Select(_ => new Dictionary()).ToArray>(); - binSize = Math.Ceiling(hitEvents.Max(e => Math.Abs(e.TimeOffset)) / timing_distribution_bins); // Prevent div-by-0 by enforcing a minimum bin size @@ -217,11 +214,12 @@ namespace osu.Game.Screens.Ranking.Statistics private readonly BindableFloat basalHeight; private readonly BindableFloat offsetAdjustment; - private Circle[] boxOriginals; - private Circle boxAdjustment; + private Circle[] boxOriginals = null!; + + private Circle? boxAdjustment; [Resolved] - private OsuColour colours { get; set; } + private OsuColour colours { get; set; } = null!; public Bar(IDictionary values, float maxValue, bool isCentre) { From bcd2445d9b7a91d9e870cde3d3db5b505ca94a21 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Sep 2022 15:50:10 +0900 Subject: [PATCH 412/420] Move helper functions down in file --- .../HitEventTimingDistributionGraph.cs | 115 +++++++++--------- 1 file changed, 57 insertions(+), 58 deletions(-) diff --git a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs index 930a702958..8ef85d24c4 100644 --- a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs +++ b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs @@ -221,6 +221,8 @@ namespace osu.Game.Screens.Ranking.Statistics [Resolved] private OsuColour colours { get; set; } = null!; + private const double duration = 300; + public Bar(IDictionary values, float maxValue, bool isCentre) { this.values = values.OrderBy(v => v.Key.GetIndexForOrderedDisplay()).ToList(); @@ -270,7 +272,61 @@ namespace osu.Game.Screens.Ranking.Statistics offsetAdjustment.BindValueChanged(_ => drawAdjustmentBar()); } - private const double duration = 300; + protected override void LoadComplete() + { + base.LoadComplete(); + + float height = calculateBasalHeight(); + + basalHeight.Value = height; + + if (!values.Any()) + return; + + foreach (var boxOriginal in boxOriginals) + boxOriginal.Height = height; + + float offsetValue = 0; + + for (int i = 0; i < values.Count; i++) + { + boxOriginals[i].MoveToY(offsetForValue(offsetValue) * BoundingBox.Height, duration, Easing.OutQuint); + boxOriginals[i].ResizeHeightTo(heightForValue(values[i].Value), duration, Easing.OutQuint); + offsetValue -= values[i].Value; + } + + basalHeight.BindValueChanged(_ => draw()); + } + + protected override void Update() + { + base.Update(); + basalHeight.Value = calculateBasalHeight(); + } + + public void UpdateOffset(float adjustment) + { + bool hasAdjustment = adjustment != totalValue; + + if (boxAdjustment == null) + { + if (!hasAdjustment) + return; + + AddInternal(boxAdjustment = new Circle + { + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + Colour = Color4.Yellow, + Blending = BlendingParameters.Additive, + Alpha = 0.6f, + Height = 0, + }); + } + + offsetAdjustment.Set(adjustment); + } private float calculateBasalHeight() => DrawHeight == 0 ? 0 : DrawWidth / DrawHeight; @@ -305,63 +361,6 @@ namespace osu.Game.Screens.Ranking.Statistics boxAdjustment.ResizeHeightTo(heightForValue(offsetAdjustment.Value), duration, Easing.OutQuint); boxAdjustment.FadeTo(!hasAdjustment ? 0 : 1, duration, Easing.OutQuint); } - - protected override void LoadComplete() - { - base.LoadComplete(); - - float height = calculateBasalHeight(); - - basalHeight.Set(height); - - if (!values.Any()) - return; - - foreach (var boxOriginal in boxOriginals) - boxOriginal.Height = height; - - float offsetValue = 0; - - for (int i = 0; i < values.Count; i++) - { - boxOriginals[i].MoveToY(offsetForValue(offsetValue) * BoundingBox.Height, duration, Easing.OutQuint); - boxOriginals[i].ResizeHeightTo(heightForValue(values[i].Value), duration, Easing.OutQuint); - offsetValue -= values[i].Value; - } - - basalHeight.BindValueChanged(_ => draw()); - } - - protected override void Update() - { - base.Update(); - - basalHeight.Set(calculateBasalHeight()); - } - - public void UpdateOffset(float adjustment) - { - bool hasAdjustment = adjustment != totalValue; - - if (boxAdjustment == null) - { - if (!hasAdjustment) - return; - - AddInternal(boxAdjustment = new Circle - { - RelativeSizeAxes = Axes.Both, - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - Colour = Color4.Yellow, - Blending = BlendingParameters.Additive, - Alpha = 0.6f, - Height = 0, - }); - } - - offsetAdjustment.Set(adjustment); - } } } } From 96951057df199df099e61af0b4136cd1909ebe1f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Sep 2022 15:56:06 +0900 Subject: [PATCH 413/420] Avoid using bindables --- .../HitEventTimingDistributionGraph.cs | 49 ++++++++++--------- 1 file changed, 27 insertions(+), 22 deletions(-) diff --git a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs index 8ef85d24c4..ebf521ba21 100644 --- a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs +++ b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -211,8 +210,9 @@ namespace osu.Game.Screens.Ranking.Statistics private readonly float maxValue; private readonly bool isCentre; private readonly float totalValue; - private readonly BindableFloat basalHeight; - private readonly BindableFloat offsetAdjustment; + + private float basalHeight; + private float offsetAdjustment; private Circle[] boxOriginals = null!; @@ -229,8 +229,7 @@ namespace osu.Game.Screens.Ranking.Statistics this.maxValue = maxValue; this.isCentre = isCentre; totalValue = values.Sum(v => v.Value); - basalHeight = new BindableFloat(); - offsetAdjustment = new BindableFloat(totalValue); + offsetAdjustment = totalValue; RelativeSizeAxes = Axes.Both; Masking = true; @@ -266,25 +265,20 @@ namespace osu.Game.Screens.Ranking.Statistics Height = 0, }; InternalChildren = boxOriginals = new[] { dot }; - basalHeight.BindValueChanged(e => dot.Height = e.NewValue); } - - offsetAdjustment.BindValueChanged(_ => drawAdjustmentBar()); } protected override void LoadComplete() { base.LoadComplete(); - float height = calculateBasalHeight(); - - basalHeight.Value = height; - if (!values.Any()) return; + updateBasalHeight(); + foreach (var boxOriginal in boxOriginals) - boxOriginal.Height = height; + boxOriginal.Height = basalHeight; float offsetValue = 0; @@ -294,14 +288,12 @@ namespace osu.Game.Screens.Ranking.Statistics boxOriginals[i].ResizeHeightTo(heightForValue(values[i].Value), duration, Easing.OutQuint); offsetValue -= values[i].Value; } - - basalHeight.BindValueChanged(_ => draw()); } protected override void Update() { base.Update(); - basalHeight.Value = calculateBasalHeight(); + updateBasalHeight(); } public void UpdateOffset(float adjustment) @@ -325,14 +317,27 @@ namespace osu.Game.Screens.Ranking.Statistics }); } - offsetAdjustment.Set(adjustment); + offsetAdjustment = adjustment; + drawAdjustmentBar(); } - private float calculateBasalHeight() => DrawHeight == 0 ? 0 : DrawWidth / DrawHeight; + private void updateBasalHeight() + { + float newBasalHeight = DrawHeight == 0 ? 0 : DrawWidth / DrawHeight; - private float offsetForValue(float value) => (1 - basalHeight.Value) * value / maxValue; + if (newBasalHeight == basalHeight) + return; - private float heightForValue(float value) => MathF.Max(basalHeight.Value + offsetForValue(value), 0); + basalHeight = newBasalHeight; + foreach (var dot in boxOriginals) + dot.Height = basalHeight; + + draw(); + } + + private float offsetForValue(float value) => (1 - basalHeight) * value / maxValue; + + private float heightForValue(float value) => MathF.Max(basalHeight + offsetForValue(value), 0); private void draw() { @@ -356,9 +361,9 @@ namespace osu.Game.Screens.Ranking.Statistics private void drawAdjustmentBar() { - bool hasAdjustment = offsetAdjustment.Value != totalValue; + bool hasAdjustment = offsetAdjustment != totalValue; - boxAdjustment.ResizeHeightTo(heightForValue(offsetAdjustment.Value), duration, Easing.OutQuint); + boxAdjustment.ResizeHeightTo(heightForValue(offsetAdjustment), duration, Easing.OutQuint); boxAdjustment.FadeTo(!hasAdjustment ? 0 : 1, duration, Easing.OutQuint); } } From 94f8197e22f647d61ee82c42bee7d9dbd386d65c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Sep 2022 16:15:14 +0900 Subject: [PATCH 414/420] Show login overlay when attempting to update a beatmap while logged out --- .../Select/Carousel/UpdateBeatmapSetButton.cs | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Select/Carousel/UpdateBeatmapSetButton.cs b/osu.Game/Screens/Select/Carousel/UpdateBeatmapSetButton.cs index 73e7d23df0..3c4ed4734b 100644 --- a/osu.Game/Screens/Select/Carousel/UpdateBeatmapSetButton.cs +++ b/osu.Game/Screens/Select/Carousel/UpdateBeatmapSetButton.cs @@ -11,6 +11,8 @@ using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; +using osu.Game.Online.API; +using osu.Game.Overlays; using osuTK; using osuTK.Graphics; @@ -22,6 +24,15 @@ namespace osu.Game.Screens.Select.Carousel private SpriteIcon icon = null!; private Box progressFill = null!; + [Resolved] + private BeatmapModelDownloader beatmapDownloader { get; set; } = null!; + + [Resolved] + private IAPIProvider api { get; set; } = null!; + + [Resolved(canBeNull: true)] + private LoginOverlay? loginOverlay { get; set; } + public UpdateBeatmapSetButton(BeatmapSetInfo beatmapSetInfo) { this.beatmapSetInfo = beatmapSetInfo; @@ -32,9 +43,6 @@ namespace osu.Game.Screens.Select.Carousel Origin = Anchor.CentreLeft; } - [Resolved] - private BeatmapModelDownloader beatmapDownloader { get; set; } = null!; - [BackgroundDependencyLoader] private void load() { @@ -90,6 +98,12 @@ namespace osu.Game.Screens.Select.Carousel Action = () => { + if (!api.IsLoggedIn) + { + loginOverlay?.Show(); + return; + } + beatmapDownloader.DownloadAsUpdate(beatmapSetInfo); attachExistingDownload(); }; From 0d0e2e7bcce9445e9513cacb8281b03b8adf91be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Acid=20Chicken=20=28=E7=A1=AB=E9=85=B8=E9=B6=8F=29?= Date: Mon, 12 Sep 2022 16:20:33 +0900 Subject: [PATCH 415/420] fix(osu.Game): bars in hit distribution graph stretching in the opposite direction when `DrawHeight` is less than `DrawWidth` --- .../Ranking/Statistics/HitEventTimingDistributionGraph.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs index ebf521ba21..5f3b2773ea 100644 --- a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs +++ b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs @@ -323,7 +323,7 @@ namespace osu.Game.Screens.Ranking.Statistics private void updateBasalHeight() { - float newBasalHeight = DrawHeight == 0 ? 0 : DrawWidth / DrawHeight; + float newBasalHeight = DrawHeight > DrawWidth ? DrawWidth / DrawHeight : 1; if (newBasalHeight == basalHeight) return; @@ -353,7 +353,7 @@ namespace osu.Game.Screens.Ranking.Statistics for (int i = 0; i < values.Count; i++) { - boxOriginals[i].Y = offsetForValue(offsetValue) * BoundingBox.Height; + boxOriginals[i].Y = offsetForValue(offsetValue) * DrawHeight; boxOriginals[i].Height = heightForValue(values[i].Value); offsetValue -= values[i].Value; } From 4f298db066bba408f1aaaa8c5cfa76b29738cec2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Acid=20Chicken=20=28=E7=A1=AB=E9=85=B8=E9=B6=8F=29?= Date: Mon, 12 Sep 2022 16:25:29 +0900 Subject: [PATCH 416/420] fix(osu.Game): reset Y axis of the bars in hit distribution graph at the first drawing --- .../Ranking/Statistics/HitEventTimingDistributionGraph.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs index 5f3b2773ea..764237ef96 100644 --- a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs +++ b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs @@ -278,7 +278,10 @@ namespace osu.Game.Screens.Ranking.Statistics updateBasalHeight(); foreach (var boxOriginal in boxOriginals) + { + boxOriginal.Y = 0; boxOriginal.Height = basalHeight; + } float offsetValue = 0; From 91c415f29b693460123762b6e7023c15d32b856c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Sep 2022 16:58:20 +0900 Subject: [PATCH 417/420] Fix nullability oversight in `ManiaRulesetConfigManager` --- .../Configuration/ManiaRulesetConfigManager.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Configuration/ManiaRulesetConfigManager.cs b/osu.Game.Rulesets.Mania/Configuration/ManiaRulesetConfigManager.cs index b30d72f2e2..d367c82ed8 100644 --- a/osu.Game.Rulesets.Mania/Configuration/ManiaRulesetConfigManager.cs +++ b/osu.Game.Rulesets.Mania/Configuration/ManiaRulesetConfigManager.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. -#nullable disable - using System; using osu.Framework.Configuration.Tracking; using osu.Game.Configuration; @@ -13,7 +11,7 @@ namespace osu.Game.Rulesets.Mania.Configuration { public class ManiaRulesetConfigManager : RulesetConfigManager { - public ManiaRulesetConfigManager(SettingsStore settings, RulesetInfo ruleset, int? variant = null) + public ManiaRulesetConfigManager(SettingsStore? settings, RulesetInfo ruleset, int? variant = null) : base(settings, ruleset, variant) { } From 89e128c0f3469e31d80ef2e165b324d4cbe80385 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Sep 2022 17:35:56 +0900 Subject: [PATCH 418/420] Fix bindable event binds to `HitObject` directly --- .../Blueprints/Sliders/Components/PathControlPointPiece.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs index 92d83d900e..ab4b492767 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs @@ -6,6 +6,7 @@ using System; using System.Collections.Generic; using System.Linq; +using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; @@ -53,6 +54,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components private IBindable sliderPosition; private IBindable sliderScale; + [UsedImplicitly] + private readonly IBindable sliderVersion; + public PathControlPointPiece(Slider slider, PathControlPoint controlPoint) { this.slider = slider; @@ -61,7 +65,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components // we don't want to run the path type update on construction as it may inadvertently change the slider. cachePoints(slider); - slider.Path.Version.BindValueChanged(_ => + sliderVersion = slider.Path.Version.GetBoundCopy(); + sliderVersion.BindValueChanged(_ => { cachePoints(slider); updatePathType(); From 58c1ea03215bc894bb770e2864d3b33c869d19db Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Sep 2022 17:51:15 +0900 Subject: [PATCH 419/420] Fix potential crash when opening changelog overlay if entry has no URL --- osu.Game/Overlays/Changelog/ChangelogEntry.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Changelog/ChangelogEntry.cs b/osu.Game/Overlays/Changelog/ChangelogEntry.cs index fdeba3f304..4d034007b1 100644 --- a/osu.Game/Overlays/Changelog/ChangelogEntry.cs +++ b/osu.Game/Overlays/Changelog/ChangelogEntry.cs @@ -4,6 +4,7 @@ #nullable disable using System; +using System.Diagnostics; using System.Net; using System.Text.RegularExpressions; using osu.Framework.Allocation; @@ -93,7 +94,7 @@ namespace osu.Game.Overlays.Changelog t.Colour = entryColour; }); - if (!string.IsNullOrEmpty(entry.Repository)) + if (!string.IsNullOrEmpty(entry.Repository) && !string.IsNullOrEmpty(entry.GithubUrl)) addRepositoryReference(title, entryColour); if (entry.GithubUser != null) @@ -104,17 +105,22 @@ namespace osu.Game.Overlays.Changelog private void addRepositoryReference(LinkFlowContainer title, Color4 entryColour) { + Debug.Assert(!string.IsNullOrEmpty(entry.Repository)); + Debug.Assert(!string.IsNullOrEmpty(entry.GithubUrl)); + title.AddText(" (", t => { t.Font = fontLarge; t.Colour = entryColour; }); + title.AddLink($"{entry.Repository.Replace("ppy/", "")}#{entry.GithubPullRequestId}", entry.GithubUrl, t => { t.Font = fontLarge; t.Colour = entryColour; }); + title.AddText(")", t => { t.Font = fontLarge; From 431e84f99207fb0a429b85ad0a61ab7f7f55a206 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Sep 2022 19:09:24 +0900 Subject: [PATCH 420/420] Cancel in progress CI runs when new commits are pushed Same as https://github.com/ppy/osu-web/pull/9268. Should hopefully help with organisation concurrent limits. --- .github/workflows/ci.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ef729a779f..082e0d247c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,5 +1,8 @@ on: [push, pull_request] name: Continuous Integration +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true jobs: inspect-code: