From 6aa894e55e0f25d184125efa649c610ef6816648 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 24 Aug 2021 18:23:02 +0900 Subject: [PATCH 001/160] Split out separate component --- .../Drawables/DrawableManiaHitObject.cs | 6 -- osu.Game.Rulesets.Mania/UI/Column.cs | 38 ++------- .../Objects/Drawables/DrawableHitObject.cs | 5 ++ .../UI/GameplaySampleTriggerSource.cs | 84 +++++++++++++++++++ 4 files changed, 94 insertions(+), 39 deletions(-) create mode 100644 osu.Game/Rulesets/UI/GameplaySampleTriggerSource.cs diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs index 5aff4e200b..9ac223a0d7 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs @@ -6,7 +6,6 @@ using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Game.Audio; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Rulesets.Mania.UI; @@ -29,11 +28,6 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables [Resolved(canBeNull: true)] private ManiaPlayfield playfield { get; set; } - /// - /// Gets the samples that are played by this object during gameplay. - /// - public ISampleInfo[] GetGameplaySamples() => Samples.Samples; - protected override float SamplePlaybackPosition { get diff --git a/osu.Game.Rulesets.Mania/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs index 9b5893b268..f5e30efd91 100644 --- a/osu.Game.Rulesets.Mania/UI/Column.cs +++ b/osu.Game.Rulesets.Mania/UI/Column.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System.Linq; using osuTK.Graphics; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -19,6 +18,7 @@ using osuTK; using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects.Drawables; +using osu.Game.Rulesets.UI; namespace osu.Game.Rulesets.Mania.UI { @@ -28,12 +28,6 @@ namespace osu.Game.Rulesets.Mania.UI public const float COLUMN_WIDTH = 80; public const float SPECIAL_COLUMN_WIDTH = 70; - /// - /// For hitsounds played by this (i.e. not as a result of hitting a hitobject), - /// a certain number of samples are allowed to be played concurrently so that it feels better when spam-pressing the key. - /// - private const int max_concurrent_hitsounds = OsuGameBase.SAMPLE_CONCURRENCY; - /// /// The index of this column as part of the whole playfield. /// @@ -45,10 +39,10 @@ namespace osu.Game.Rulesets.Mania.UI internal readonly Container TopLevelContainer; private readonly DrawablePool hitExplosionPool; private readonly OrderedHitPolicy hitPolicy; - private readonly Container hitSounds; - public Container UnderlayElements => HitObjectArea.UnderlayElements; + private readonly GameplaySampleTriggerSource sampleTriggerSource; + public Column(int index) { Index = index; @@ -64,6 +58,7 @@ namespace osu.Game.Rulesets.Mania.UI InternalChildren = new[] { hitExplosionPool = new DrawablePool(5), + sampleTriggerSource = new GameplaySampleTriggerSource(HitObjectContainer), // For input purposes, the background is added at the highest depth, but is then proxied back below all other elements background.CreateProxy(), HitObjectArea = new ColumnHitObjectArea(Index, HitObjectContainer) { RelativeSizeAxes = Axes.Both }, @@ -72,12 +67,6 @@ namespace osu.Game.Rulesets.Mania.UI RelativeSizeAxes = Axes.Both }, background, - hitSounds = new Container - { - Name = "Column samples pool", - RelativeSizeAxes = Axes.Both, - Children = Enumerable.Range(0, max_concurrent_hitsounds).Select(_ => new SkinnableSound()).ToArray() - }, TopLevelContainer = new Container { RelativeSizeAxes = Axes.Both } }; @@ -133,29 +122,12 @@ namespace osu.Game.Rulesets.Mania.UI HitObjectArea.Explosions.Add(hitExplosionPool.Get(e => e.Apply(result))); } - private int nextHitSoundIndex; - public bool OnPressed(ManiaAction action) { if (action != Action.Value) return false; - var nextObject = - HitObjectContainer.AliveObjects.FirstOrDefault(h => h.HitObject.StartTime > Time.Current) ?? - // fallback to non-alive objects to find next off-screen object - HitObjectContainer.Objects.FirstOrDefault(h => h.HitObject.StartTime > Time.Current) ?? - HitObjectContainer.Objects.LastOrDefault(); - - if (nextObject is DrawableManiaHitObject maniaObject) - { - var hitSound = hitSounds[nextHitSoundIndex]; - - hitSound.Samples = maniaObject.GetGameplaySamples(); - hitSound.Play(); - - nextHitSoundIndex = (nextHitSoundIndex + 1) % max_concurrent_hitsounds; - } - + sampleTriggerSource.Play(); return true; } diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 29d8a475ef..b3e1b24d8d 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -54,6 +54,11 @@ namespace osu.Game.Rulesets.Objects.Drawables /// public readonly Bindable AccentColour = new Bindable(Color4.Gray); + /// + /// Gets the samples that are played by this object during gameplay. + /// + public ISampleInfo[] GetGameplaySamples() => Samples.Samples; + protected PausableSkinnableSound Samples { get; private set; } public virtual IEnumerable GetSamples() => HitObject.Samples; diff --git a/osu.Game/Rulesets/UI/GameplaySampleTriggerSource.cs b/osu.Game/Rulesets/UI/GameplaySampleTriggerSource.cs new file mode 100644 index 0000000000..fedbcd541c --- /dev/null +++ b/osu.Game/Rulesets/UI/GameplaySampleTriggerSource.cs @@ -0,0 +1,84 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Audio; +using osu.Game.Rulesets.Objects; +using osu.Game.Skinning; + +namespace osu.Game.Rulesets.UI +{ + /// + /// A component which can trigger the most appropriate hit sound for a given point in time, based on the state of a + /// + public class GameplaySampleTriggerSource : CompositeDrawable + { + private readonly HitObjectContainer hitObjectContainer; + + private int nextHitSoundIndex; + + /// + /// The number of concurrent samples allowed to be played concurrently so that it feels better when spam-pressing a key. + /// + private const int max_concurrent_hitsounds = OsuGameBase.SAMPLE_CONCURRENCY; + + private readonly Container hitSounds; + + [Resolved] + private DrawableRuleset drawableRuleset { get; set; } + + public GameplaySampleTriggerSource(HitObjectContainer hitObjectContainer) + { + this.hitObjectContainer = hitObjectContainer; + InternalChildren = new Drawable[] + { + hitSounds = new Container + { + Name = "concurrent sample pool", + RelativeSizeAxes = Axes.Both, + Children = Enumerable.Range(0, max_concurrent_hitsounds).Select(_ => new SkinnableSound()).ToArray() + }, + }; + } + + private ISampleInfo[] playableSampleInfo; + + /// + /// Play the most appropriate hit sound for the current point in time. + /// + public void Play() + { + var nextObject = + hitObjectContainer.AliveObjects.FirstOrDefault(h => h.HitObject.StartTime > Time.Current)?.HitObject ?? + // fallback to non-alive objects to find next off-screen object + // TODO: make lookup more efficient? + drawableRuleset.Objects.FirstOrDefault(h => h.StartTime > Time.Current) ?? + drawableRuleset.Objects.LastOrDefault(); + + if (nextObject != null) + { + var hitSound = getNextSample(); + playableSampleInfo = GetPlayableSampleInfo(nextObject); + hitSound.Samples = playableSampleInfo; + hitSound.Play(); + } + } + + protected virtual ISampleInfo[] GetPlayableSampleInfo(HitObject nextObject) => + // TODO: avoid cast somehow? + nextObject.Samples.Cast().ToArray(); + + private SkinnableSound getNextSample() + { + var hitSound = hitSounds[nextHitSoundIndex]; + + // round robin over available samples to allow for concurrent playback. + nextHitSoundIndex = (nextHitSoundIndex + 1) % max_concurrent_hitsounds; + + return hitSound; + } + } +} From 4a294d4de47c9cef709457ee8cf8357be11aa894 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 24 Aug 2021 19:05:59 +0900 Subject: [PATCH 002/160] Optimise fallback logic to reduce lookups to bare minimum --- .../UI/GameplaySampleTriggerSource.cs | 36 ++++++++++++------- 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/osu.Game/Rulesets/UI/GameplaySampleTriggerSource.cs b/osu.Game/Rulesets/UI/GameplaySampleTriggerSource.cs index fedbcd541c..51f3052509 100644 --- a/osu.Game/Rulesets/UI/GameplaySampleTriggerSource.cs +++ b/osu.Game/Rulesets/UI/GameplaySampleTriggerSource.cs @@ -44,32 +44,44 @@ namespace osu.Game.Rulesets.UI }; } - private ISampleInfo[] playableSampleInfo; + private HitObject fallbackObject; /// /// Play the most appropriate hit sound for the current point in time. /// public void Play() { - var nextObject = - hitObjectContainer.AliveObjects.FirstOrDefault(h => h.HitObject.StartTime > Time.Current)?.HitObject ?? - // fallback to non-alive objects to find next off-screen object - // TODO: make lookup more efficient? - drawableRuleset.Objects.FirstOrDefault(h => h.StartTime > Time.Current) ?? - drawableRuleset.Objects.LastOrDefault(); + var nextObject = hitObjectContainer.AliveObjects.FirstOrDefault(h => h.HitObject.StartTime > Time.Current)?.HitObject; + + if (nextObject == null) + { + if (fallbackObject == null || fallbackObject.StartTime < Time.Current) + { + // in the case a next object isn't available in drawable form, we need to do a somewhat expensive traversal to get a valid sound to play. + // note that we don't want to cache the object if it is an alive object, as once it is hit we don't want to continue playing its sound. + // check whether we can use the previous computed sample. + + // fallback to non-alive objects to find next off-screen object + // TODO: make lookup more efficient? + fallbackObject = hitObjectContainer.Entries + .Where(e => e.Result?.HasResult != true && e.HitObject.StartTime > Time.Current)? + .OrderBy(e => e.HitObject.StartTime) + .FirstOrDefault()?.HitObject ?? hitObjectContainer.Entries.FirstOrDefault()?.HitObject; + } + + nextObject = fallbackObject; + } if (nextObject != null) { var hitSound = getNextSample(); - playableSampleInfo = GetPlayableSampleInfo(nextObject); - hitSound.Samples = playableSampleInfo; + hitSound.Samples = GetPlayableSampleInfo(nextObject).Select(s => nextObject.SampleControlPoint.ApplyTo(s)).Cast().ToArray(); hitSound.Play(); } } - protected virtual ISampleInfo[] GetPlayableSampleInfo(HitObject nextObject) => - // TODO: avoid cast somehow? - nextObject.Samples.Cast().ToArray(); + protected virtual HitSampleInfo[] GetPlayableSampleInfo(HitObject nextObject) => + nextObject.Samples.ToArray(); private SkinnableSound getNextSample() { From 681215e5b58a799edc1c9e4098e9a781a5011105 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 25 Aug 2021 14:57:41 +0900 Subject: [PATCH 003/160] Rewrite object lookup to use previous entry regardless This changes the fallback logic to always prefer the previous resolved lifetime entry rather than fallback to the first entry ever. I think this is more correct in all cases. Also rewrites the inline comments to hopefully be easier to parse. --- .../UI/GameplaySampleTriggerSource.cs | 38 +++++++++++-------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/osu.Game/Rulesets/UI/GameplaySampleTriggerSource.cs b/osu.Game/Rulesets/UI/GameplaySampleTriggerSource.cs index 51f3052509..1713104f01 100644 --- a/osu.Game/Rulesets/UI/GameplaySampleTriggerSource.cs +++ b/osu.Game/Rulesets/UI/GameplaySampleTriggerSource.cs @@ -16,15 +16,15 @@ namespace osu.Game.Rulesets.UI /// public class GameplaySampleTriggerSource : CompositeDrawable { - private readonly HitObjectContainer hitObjectContainer; - - private int nextHitSoundIndex; - /// /// The number of concurrent samples allowed to be played concurrently so that it feels better when spam-pressing a key. /// private const int max_concurrent_hitsounds = OsuGameBase.SAMPLE_CONCURRENCY; + private readonly HitObjectContainer hitObjectContainer; + + private int nextHitSoundIndex; + private readonly Container hitSounds; [Resolved] @@ -44,32 +44,38 @@ namespace osu.Game.Rulesets.UI }; } - private HitObject fallbackObject; + private HitObjectLifetimeEntry fallbackObject; /// /// Play the most appropriate hit sound for the current point in time. /// public void Play() { + // The most optimal lookup case we have is when an object is alive. There are usually very few alive objects so there's no drawbacks in attempting this lookup each time. var nextObject = hitObjectContainer.AliveObjects.FirstOrDefault(h => h.HitObject.StartTime > Time.Current)?.HitObject; + // In the case a next object isn't available in drawable form, we need to do a somewhat expensive traversal to get a valid sound to play. if (nextObject == null) { - if (fallbackObject == null || fallbackObject.StartTime < Time.Current) + // This lookup can be skipped if the last entry is still valid (in the future and not yet hit). + if (fallbackObject == null || fallbackObject.HitObject.StartTime < Time.Current || fallbackObject.Result.IsHit) { - // in the case a next object isn't available in drawable form, we need to do a somewhat expensive traversal to get a valid sound to play. - // note that we don't want to cache the object if it is an alive object, as once it is hit we don't want to continue playing its sound. - // check whether we can use the previous computed sample. + // We need to use lifetime entries to find the next object (we can't just use `hitObjectContainer.Objects` due to pooling - it may even be empty). + // If required, we can make this lookup more efficient by adding support to get next-future-entry in LifetimeEntryManager. + var lookup = hitObjectContainer.Entries + .Where(e => e.Result?.HasResult != true && e.HitObject.StartTime > Time.Current) + .OrderBy(e => e.HitObject.StartTime) + .FirstOrDefault(); - // fallback to non-alive objects to find next off-screen object - // TODO: make lookup more efficient? - fallbackObject = hitObjectContainer.Entries - .Where(e => e.Result?.HasResult != true && e.HitObject.StartTime > Time.Current)? - .OrderBy(e => e.HitObject.StartTime) - .FirstOrDefault()?.HitObject ?? hitObjectContainer.Entries.FirstOrDefault()?.HitObject; + // If the lookup failed, use the previously resolved lookup (we still want to play a sound, and it is still likely the most valid result). + if (lookup != null) + fallbackObject = lookup; + + // If we still can't find anything, just play whatever we can to get a sound out. + fallbackObject ??= hitObjectContainer.Entries.FirstOrDefault(); } - nextObject = fallbackObject; + nextObject = fallbackObject?.HitObject; } if (nextObject != null) From a1936b141bb3acf91e79ca6725d14b5300e114fc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 25 Aug 2021 15:24:01 +0900 Subject: [PATCH 004/160] Refactor base class to allow correct usage in taiko drum --- .../UI/GameplaySampleTriggerSource.cs | 36 +++++++++++++------ 1 file changed, 25 insertions(+), 11 deletions(-) diff --git a/osu.Game/Rulesets/UI/GameplaySampleTriggerSource.cs b/osu.Game/Rulesets/UI/GameplaySampleTriggerSource.cs index 1713104f01..015c85beb9 100644 --- a/osu.Game/Rulesets/UI/GameplaySampleTriggerSource.cs +++ b/osu.Game/Rulesets/UI/GameplaySampleTriggerSource.cs @@ -49,7 +49,29 @@ namespace osu.Game.Rulesets.UI /// /// Play the most appropriate hit sound for the current point in time. /// - public void Play() + public virtual void Play() + { + var nextObject = GetMostValidObject(); + + if (nextObject == null) + return; + + var samples = nextObject.Samples + .Select(s => nextObject.SampleControlPoint.ApplyTo(s)) + .Cast() + .ToArray(); + + PlaySamples(samples); + } + + protected void PlaySamples(ISampleInfo[] samples) + { + var hitSound = getNextSample(); + hitSound.Samples = samples; + hitSound.Play(); + } + + protected HitObject GetMostValidObject() { // The most optimal lookup case we have is when an object is alive. There are usually very few alive objects so there's no drawbacks in attempting this lookup each time. var nextObject = hitObjectContainer.AliveObjects.FirstOrDefault(h => h.HitObject.StartTime > Time.Current)?.HitObject; @@ -58,7 +80,7 @@ namespace osu.Game.Rulesets.UI if (nextObject == null) { // This lookup can be skipped if the last entry is still valid (in the future and not yet hit). - if (fallbackObject == null || fallbackObject.HitObject.StartTime < Time.Current || fallbackObject.Result.IsHit) + if (fallbackObject == null || fallbackObject.HitObject.StartTime < Time.Current || fallbackObject.Result?.IsHit == true) { // We need to use lifetime entries to find the next object (we can't just use `hitObjectContainer.Objects` due to pooling - it may even be empty). // If required, we can make this lookup more efficient by adding support to get next-future-entry in LifetimeEntryManager. @@ -78,17 +100,9 @@ namespace osu.Game.Rulesets.UI nextObject = fallbackObject?.HitObject; } - if (nextObject != null) - { - var hitSound = getNextSample(); - hitSound.Samples = GetPlayableSampleInfo(nextObject).Select(s => nextObject.SampleControlPoint.ApplyTo(s)).Cast().ToArray(); - hitSound.Play(); - } + return nextObject; } - protected virtual HitSampleInfo[] GetPlayableSampleInfo(HitObject nextObject) => - nextObject.Samples.ToArray(); - private SkinnableSound getNextSample() { var hitSound = hitSounds[nextHitSoundIndex]; From 8e0a04c4e5c2f4d8a40867856f90832f34b6e500 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 25 Aug 2021 15:24:13 +0900 Subject: [PATCH 005/160] Update taiko `InputDrum` to use new trigger logic --- .../Skinning/TestSceneDrawableBarLine.cs | 4 +- .../Skinning/TestSceneInputDrum.cs | 10 +- .../Skinning/TestSceneTaikoPlayfield.cs | 2 +- .../Audio/DrumSampleContainer.cs | 104 ------------------ .../Skinning/Legacy/LegacyInputDrum.cs | 10 +- .../UI/DrawableTaikoRuleset.cs | 2 +- osu.Game.Rulesets.Taiko/UI/InputDrum.cs | 43 ++++++-- osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs | 10 +- 8 files changed, 49 insertions(+), 136 deletions(-) delete mode 100644 osu.Game.Rulesets.Taiko/Audio/DrumSampleContainer.cs diff --git a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableBarLine.cs b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableBarLine.cs index f9b8e9a985..269a855219 100644 --- a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableBarLine.cs +++ b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableBarLine.cs @@ -40,7 +40,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning Origin = Anchor.Centre, Children = new Drawable[] { - new TaikoPlayfield(new ControlPointInfo()), + new TaikoPlayfield(), hoc = new ScrollingHitObjectContainer() } }; @@ -66,7 +66,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning Origin = Anchor.Centre, Children = new Drawable[] { - new TaikoPlayfield(new ControlPointInfo()), + new TaikoPlayfield(), hoc = new ScrollingHitObjectContainer() } }; diff --git a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneInputDrum.cs b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneInputDrum.cs index 055a292fe8..24db046748 100644 --- a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneInputDrum.cs +++ b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneInputDrum.cs @@ -5,7 +5,6 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Taiko.UI; using osuTK; @@ -17,6 +16,13 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning [BackgroundDependencyLoader] private void load() { + var playfield = new TaikoPlayfield(); + + var beatmap = CreateWorkingBeatmap(new TaikoRuleset().RulesetInfo).GetPlayableBeatmap(new TaikoRuleset().RulesetInfo); + + foreach (var h in beatmap.HitObjects) + playfield.Add(h); + SetContents(_ => new TaikoInputManager(new TaikoRuleset().RulesetInfo) { RelativeSizeAxes = Axes.Both, @@ -25,7 +31,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning Anchor = Anchor.Centre, Origin = Anchor.Centre, Size = new Vector2(200), - Child = new InputDrum(new ControlPointInfo()) + Child = new InputDrum(playfield.HitObjectContainer) } }); } diff --git a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneTaikoPlayfield.cs b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneTaikoPlayfield.cs index f96297a06d..6f2fcd08f1 100644 --- a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneTaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneTaikoPlayfield.cs @@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning Beatmap.Value.Track.Start(); }); - AddStep("Load playfield", () => SetContents(_ => new TaikoPlayfield(new ControlPointInfo()) + AddStep("Load playfield", () => SetContents(_ => new TaikoPlayfield { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, diff --git a/osu.Game.Rulesets.Taiko/Audio/DrumSampleContainer.cs b/osu.Game.Rulesets.Taiko/Audio/DrumSampleContainer.cs deleted file mode 100644 index e4dc261363..0000000000 --- a/osu.Game.Rulesets.Taiko/Audio/DrumSampleContainer.cs +++ /dev/null @@ -1,104 +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 System.Linq; -using osu.Framework.Allocation; -using osu.Framework.Bindables; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Game.Audio; -using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Skinning; - -namespace osu.Game.Rulesets.Taiko.Audio -{ - /// - /// Stores samples for the input drum. - /// The lifetime of the samples is adjusted so that they are only alive during the appropriate sample control point. - /// - public class DrumSampleContainer : LifetimeManagementContainer - { - private readonly ControlPointInfo controlPoints; - private readonly Dictionary mappings = new Dictionary(); - - private readonly IBindableList samplePoints = new BindableList(); - - public DrumSampleContainer(ControlPointInfo controlPoints) - { - this.controlPoints = controlPoints; - } - - [BackgroundDependencyLoader] - private void load() - { - samplePoints.BindTo(controlPoints.SamplePoints); - samplePoints.BindCollectionChanged((_, __) => recreateMappings(), true); - } - - private void recreateMappings() - { - mappings.Clear(); - ClearInternal(); - - SampleControlPoint[] points = samplePoints.Count == 0 - ? new[] { controlPoints.SamplePointAt(double.MinValue) } - : samplePoints.ToArray(); - - for (int i = 0; i < points.Length; i++) - { - var samplePoint = points[i]; - - var lifetimeStart = i > 0 ? samplePoint.Time : double.MinValue; - var lifetimeEnd = i + 1 < points.Length ? points[i + 1].Time : double.MaxValue; - - AddInternal(mappings[samplePoint.Time] = new DrumSample(samplePoint) - { - LifetimeStart = lifetimeStart, - LifetimeEnd = lifetimeEnd - }); - } - } - - public DrumSample SampleAt(double time) => mappings[controlPoints.SamplePointAt(time).Time]; - - public class DrumSample : CompositeDrawable - { - public override bool RemoveWhenNotAlive => false; - - public PausableSkinnableSound Centre { get; private set; } - public PausableSkinnableSound Rim { get; private set; } - - private readonly SampleControlPoint samplePoint; - - private Bindable sampleBank; - private BindableNumber sampleVolume; - - public DrumSample(SampleControlPoint samplePoint) - { - this.samplePoint = samplePoint; - } - - [BackgroundDependencyLoader] - private void load() - { - sampleBank = samplePoint.SampleBankBindable.GetBoundCopy(); - sampleBank.BindValueChanged(_ => recreate()); - - sampleVolume = samplePoint.SampleVolumeBindable.GetBoundCopy(); - sampleVolume.BindValueChanged(_ => recreate()); - - recreate(); - } - - private void recreate() - { - InternalChildren = new Drawable[] - { - Centre = new PausableSkinnableSound(samplePoint.GetSampleInfo()), - Rim = new PausableSkinnableSound(samplePoint.GetSampleInfo(HitSampleInfo.HIT_CLAP)) - }; - } - } - } -} diff --git a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs index 795885d4b9..5a76694913 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs @@ -7,7 +7,8 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Bindings; -using osu.Game.Rulesets.Taiko.Audio; +using osu.Game.Rulesets.Taiko.Objects; +using osu.Game.Rulesets.Taiko.UI; using osu.Game.Skinning; using osuTK; @@ -111,7 +112,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy public readonly Sprite Centre; [Resolved] - private DrumSampleContainer sampleContainer { get; set; } + private InputDrum.DrumSampleTriggerSource sampleTriggerSource { get; set; } public LegacyHalfDrum(bool flipped) { @@ -143,17 +144,16 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy public bool OnPressed(TaikoAction action) { Drawable target = null; - var drumSample = sampleContainer.SampleAt(Time.Current); if (action == CentreAction) { target = Centre; - drumSample.Centre?.Play(); + sampleTriggerSource.Play(HitType.Centre); } else if (action == RimAction) { target = Rim; - drumSample.Rim?.Play(); + sampleTriggerSource.Play(HitType.Rim); } if (target != null) diff --git a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs index 650ce1f5a3..6ddbf3c16b 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs @@ -64,7 +64,7 @@ namespace osu.Game.Rulesets.Taiko.UI protected override PassThroughInputManager CreateInputManager() => new TaikoInputManager(Ruleset.RulesetInfo); - protected override Playfield CreatePlayfield() => new TaikoPlayfield(Beatmap.ControlPointInfo); + protected override Playfield CreatePlayfield() => new TaikoPlayfield(); public override DrawableHitObject CreateDrawableRepresentation(TaikoHitObject h) => null; diff --git a/osu.Game.Rulesets.Taiko/UI/InputDrum.cs b/osu.Game.Rulesets.Taiko/UI/InputDrum.cs index 1ca1be1bdf..24e2dddb49 100644 --- a/osu.Game.Rulesets.Taiko/UI/InputDrum.cs +++ b/osu.Game.Rulesets.Taiko/UI/InputDrum.cs @@ -2,18 +2,19 @@ // See the LICENCE file in the repository root for full licence text. using System; -using osuTK; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; using osu.Framework.Input.Bindings; -using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Audio; using osu.Game.Graphics; -using osu.Game.Rulesets.Taiko.Audio; +using osu.Game.Rulesets.Taiko.Objects; +using osu.Game.Rulesets.UI; using osu.Game.Screens.Play; using osu.Game.Skinning; +using osuTK; namespace osu.Game.Rulesets.Taiko.UI { @@ -25,11 +26,11 @@ namespace osu.Game.Rulesets.Taiko.UI private const float middle_split = 0.025f; [Cached] - private DrumSampleContainer sampleContainer; + private DrumSampleTriggerSource sampleTriggerSource; - public InputDrum(ControlPointInfo controlPoints) + public InputDrum(HitObjectContainer hitObjectContainer) { - sampleContainer = new DrumSampleContainer(controlPoints); + sampleTriggerSource = new DrumSampleTriggerSource(hitObjectContainer); RelativeSizeAxes = Axes.Both; } @@ -70,7 +71,7 @@ namespace osu.Game.Rulesets.Taiko.UI } } }), - sampleContainer + sampleTriggerSource }; } @@ -95,7 +96,7 @@ namespace osu.Game.Rulesets.Taiko.UI private readonly Sprite centreHit; [Resolved] - private DrumSampleContainer sampleContainer { get; set; } + private DrumSampleTriggerSource sampleTriggerSource { get; set; } public TaikoHalfDrum(bool flipped) { @@ -156,21 +157,19 @@ namespace osu.Game.Rulesets.Taiko.UI Drawable target = null; Drawable back = null; - var drumSample = sampleContainer.SampleAt(Time.Current); - if (action == CentreAction) { target = centreHit; back = centre; - drumSample.Centre?.Play(); + sampleTriggerSource.Play(HitType.Centre); } else if (action == RimAction) { target = rimHit; back = rim; - drumSample.Rim?.Play(); + sampleTriggerSource.Play(HitType.Rim); } if (target != null) @@ -201,5 +200,25 @@ namespace osu.Game.Rulesets.Taiko.UI { } } + + public class DrumSampleTriggerSource : GameplaySampleTriggerSource + { + public DrumSampleTriggerSource(HitObjectContainer hitObjectContainer) + : base(hitObjectContainer) + { + } + + public void Play(HitType hitType) + { + var hitObject = GetMostValidObject(); + + if (hitObject == null) + return; + + PlaySamples(new ISampleInfo[] { hitObject.SampleControlPoint.GetSampleInfo(hitType == HitType.Rim ? HitSampleInfo.HIT_CLAP : HitSampleInfo.HIT_NORMAL) }); + } + + public override void Play() => throw new InvalidOperationException(@"Use override with HitType parameter instead"); + } } } diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs index 0d9e08b8b7..d650cab729 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs @@ -8,7 +8,6 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Pooling; -using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Judgements; @@ -27,8 +26,6 @@ namespace osu.Game.Rulesets.Taiko.UI { public class TaikoPlayfield : ScrollingPlayfield { - private readonly ControlPointInfo controlPoints; - /// /// Default height of a when inside a . /// @@ -56,11 +53,6 @@ namespace osu.Game.Rulesets.Taiko.UI private Container hitTargetOffsetContent; - public TaikoPlayfield(ControlPointInfo controlPoints) - { - this.controlPoints = controlPoints; - } - [BackgroundDependencyLoader] private void load(OsuColour colours) { @@ -131,7 +123,7 @@ namespace osu.Game.Rulesets.Taiko.UI Children = new Drawable[] { new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.PlayfieldBackgroundLeft), _ => new PlayfieldBackgroundLeft()), - new InputDrum(controlPoints) + new InputDrum(HitObjectContainer) { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, From ef2b5e1c51d0fbcc4caece8588590375104a9b90 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 25 Aug 2021 15:29:47 +0900 Subject: [PATCH 006/160] Tidy up variable names and unused resolved properties --- .../UI/GameplaySampleTriggerSource.cs | 26 +++++++------------ 1 file changed, 9 insertions(+), 17 deletions(-) diff --git a/osu.Game/Rulesets/UI/GameplaySampleTriggerSource.cs b/osu.Game/Rulesets/UI/GameplaySampleTriggerSource.cs index 015c85beb9..48905e7232 100644 --- a/osu.Game/Rulesets/UI/GameplaySampleTriggerSource.cs +++ b/osu.Game/Rulesets/UI/GameplaySampleTriggerSource.cs @@ -2,8 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System.Linq; -using osu.Framework.Allocation; -using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Audio; using osu.Game.Rulesets.Objects; @@ -27,20 +25,14 @@ namespace osu.Game.Rulesets.UI private readonly Container hitSounds; - [Resolved] - private DrawableRuleset drawableRuleset { get; set; } - public GameplaySampleTriggerSource(HitObjectContainer hitObjectContainer) { this.hitObjectContainer = hitObjectContainer; - InternalChildren = new Drawable[] + + InternalChild = hitSounds = new Container { - hitSounds = new Container - { - Name = "concurrent sample pool", - RelativeSizeAxes = Axes.Both, - Children = Enumerable.Range(0, max_concurrent_hitsounds).Select(_ => new SkinnableSound()).ToArray() - }, + Name = "concurrent sample pool", + ChildrenEnumerable = Enumerable.Range(0, max_concurrent_hitsounds).Select(_ => new SkinnableSound()) }; } @@ -74,10 +66,10 @@ namespace osu.Game.Rulesets.UI protected HitObject GetMostValidObject() { // The most optimal lookup case we have is when an object is alive. There are usually very few alive objects so there's no drawbacks in attempting this lookup each time. - var nextObject = hitObjectContainer.AliveObjects.FirstOrDefault(h => h.HitObject.StartTime > Time.Current)?.HitObject; + var hitObject = hitObjectContainer.AliveObjects.FirstOrDefault(h => h.HitObject.StartTime > Time.Current)?.HitObject; // In the case a next object isn't available in drawable form, we need to do a somewhat expensive traversal to get a valid sound to play. - if (nextObject == null) + if (hitObject == null) { // This lookup can be skipped if the last entry is still valid (in the future and not yet hit). if (fallbackObject == null || fallbackObject.HitObject.StartTime < Time.Current || fallbackObject.Result?.IsHit == true) @@ -97,15 +89,15 @@ namespace osu.Game.Rulesets.UI fallbackObject ??= hitObjectContainer.Entries.FirstOrDefault(); } - nextObject = fallbackObject?.HitObject; + hitObject = fallbackObject?.HitObject; } - return nextObject; + return hitObject; } private SkinnableSound getNextSample() { - var hitSound = hitSounds[nextHitSoundIndex]; + SkinnableSound hitSound = hitSounds[nextHitSoundIndex]; // round robin over available samples to allow for concurrent playback. nextHitSoundIndex = (nextHitSoundIndex + 1) % max_concurrent_hitsounds; From fc85ae0e349b7f01b4087cdef76250ac160873d8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 25 Aug 2021 16:55:03 +0900 Subject: [PATCH 007/160] Add test coverage --- .../TestSceneGameplaySampleTriggerSource.cs | 135 ++++++++++++++++++ 1 file changed, 135 insertions(+) create mode 100644 osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySampleTriggerSource.cs diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySampleTriggerSource.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySampleTriggerSource.cs new file mode 100644 index 0000000000..c446a7efd1 --- /dev/null +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySampleTriggerSource.cs @@ -0,0 +1,135 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using NUnit.Framework; +using osu.Game.Audio; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.UI; +using osuTK.Input; + +namespace osu.Game.Tests.Visual.Gameplay +{ + public class TestSceneGameplaySampleTriggerSource : PlayerTestScene + { + private TestGameplaySampleTriggerSource sampleTriggerSource; + protected override Ruleset CreatePlayerRuleset() => new OsuRuleset(); + + private Beatmap beatmap; + + protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) + { + beatmap = new Beatmap + { + BeatmapInfo = new BeatmapInfo + { + BaseDifficulty = new BeatmapDifficulty { CircleSize = 6, SliderMultiplier = 3 }, + Ruleset = ruleset + } + }; + + const double start_offset = 8000; + const double spacing = 2000; + + double t = start_offset; + beatmap.HitObjects.AddRange(new[] + { + new HitCircle + { + // intentionally start objects a bit late so we can test the case of no alive objects. + StartTime = t += spacing, + Samples = new[] { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) } + }, + new HitCircle + { + StartTime = t += spacing, + Samples = new[] { new HitSampleInfo(HitSampleInfo.HIT_WHISTLE) } + }, + new HitCircle + { + StartTime = t += spacing, + Samples = new[] { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) }, + SampleControlPoint = new SampleControlPoint { SampleBank = "soft" }, + }, + new HitCircle + { + StartTime = t += spacing, + Samples = new[] { new HitSampleInfo(HitSampleInfo.HIT_WHISTLE) }, + SampleControlPoint = new SampleControlPoint { SampleBank = "soft" }, + }, + }); + + return beatmap; + } + + public override void SetUpSteps() + { + base.SetUpSteps(); + + AddStep("Add trigger source", () => Player.HUDOverlay.Add(sampleTriggerSource = new TestGameplaySampleTriggerSource(Player.DrawableRuleset.Playfield.HitObjectContainer))); + } + + [Test] + public void TestCorrectHitObject() + { + HitObjectLifetimeEntry nextObjectEntry = null; + + AddUntilStep("no alive objects", () => getNextAliveObject() == null); + + AddAssert("check initially correct object", () => sampleTriggerSource.GetMostValidObject() == beatmap.HitObjects[0]); + + AddUntilStep("get next object", () => + { + var nextDrawableObject = getNextAliveObject(); + + if (nextDrawableObject != null) + { + nextObjectEntry = nextDrawableObject.Entry; + InputManager.MoveMouseTo(nextDrawableObject.ScreenSpaceDrawQuad.Centre); + return true; + } + + return false; + }); + + AddUntilStep("hit first hitobject", () => + { + InputManager.Click(MouseButton.Left); + return nextObjectEntry.Result.HasResult; + }); + + AddAssert("check correct object after hit", () => sampleTriggerSource.GetMostValidObject() == beatmap.HitObjects[1]); + + AddUntilStep("check correct object after miss", () => sampleTriggerSource.GetMostValidObject() == beatmap.HitObjects[2]); + AddUntilStep("check correct object after miss", () => sampleTriggerSource.GetMostValidObject() == beatmap.HitObjects[3]); + + AddUntilStep("no alive objects", () => getNextAliveObject() == null); + AddAssert("check correct object after none alive", () => sampleTriggerSource.GetMostValidObject() == beatmap.HitObjects[3]); + } + + private DrawableHitObject getNextAliveObject() => + Player.DrawableRuleset.Playfield.HitObjectContainer.AliveObjects.FirstOrDefault(); + + [Test] + public void TestSampleTriggering() + { + AddRepeatStep("trigger sample", () => sampleTriggerSource.Play(), 10); + } + + public class TestGameplaySampleTriggerSource : GameplaySampleTriggerSource + { + public TestGameplaySampleTriggerSource(HitObjectContainer hitObjectContainer) + : base(hitObjectContainer) + { + } + + public new HitObject GetMostValidObject() => base.GetMostValidObject(); + } + } +} From ccfff50c6f18c5a9b790e02e08cb9b16c3b6bac8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 25 Aug 2021 16:55:34 +0900 Subject: [PATCH 008/160] Apply fixes in line with issues found during testing I was trying to be too smart with caching, but if the `Play` method was not called often enough it would have a recent reference. Unfortunately this requires a separate query to `Entries`, but is also a special case (no future hitobjects). This also removes the time-based checks (result status alone should be all we care about). --- .../UI/GameplaySampleTriggerSource.cs | 20 ++++++++----------- 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/osu.Game/Rulesets/UI/GameplaySampleTriggerSource.cs b/osu.Game/Rulesets/UI/GameplaySampleTriggerSource.cs index 48905e7232..bceb5996b9 100644 --- a/osu.Game/Rulesets/UI/GameplaySampleTriggerSource.cs +++ b/osu.Game/Rulesets/UI/GameplaySampleTriggerSource.cs @@ -66,27 +66,23 @@ namespace osu.Game.Rulesets.UI protected HitObject GetMostValidObject() { // The most optimal lookup case we have is when an object is alive. There are usually very few alive objects so there's no drawbacks in attempting this lookup each time. - var hitObject = hitObjectContainer.AliveObjects.FirstOrDefault(h => h.HitObject.StartTime > Time.Current)?.HitObject; + var hitObject = hitObjectContainer.AliveObjects.FirstOrDefault(h => h.Result?.IsHit != true)?.HitObject; // In the case a next object isn't available in drawable form, we need to do a somewhat expensive traversal to get a valid sound to play. if (hitObject == null) { // This lookup can be skipped if the last entry is still valid (in the future and not yet hit). - if (fallbackObject == null || fallbackObject.HitObject.StartTime < Time.Current || fallbackObject.Result?.IsHit == true) + if (fallbackObject == null || fallbackObject.Result?.HasResult == true) { // We need to use lifetime entries to find the next object (we can't just use `hitObjectContainer.Objects` due to pooling - it may even be empty). // If required, we can make this lookup more efficient by adding support to get next-future-entry in LifetimeEntryManager. - var lookup = hitObjectContainer.Entries - .Where(e => e.Result?.HasResult != true && e.HitObject.StartTime > Time.Current) - .OrderBy(e => e.HitObject.StartTime) - .FirstOrDefault(); + fallbackObject = hitObjectContainer.Entries + .Where(e => e.Result?.HasResult != true) + .OrderBy(e => e.HitObject.StartTime) + .FirstOrDefault(); - // If the lookup failed, use the previously resolved lookup (we still want to play a sound, and it is still likely the most valid result). - if (lookup != null) - fallbackObject = lookup; - - // If we still can't find anything, just play whatever we can to get a sound out. - fallbackObject ??= hitObjectContainer.Entries.FirstOrDefault(); + // In the case there are no unjudged objects, the last hit object should be used instead. + fallbackObject ??= hitObjectContainer.Entries.LastOrDefault(); } hitObject = fallbackObject?.HitObject; From fd78d0440bfca93e6313e546bd91d5f5aec3794d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 25 Aug 2021 17:00:32 +0900 Subject: [PATCH 009/160] Update missed conditional --- osu.Game/Rulesets/UI/GameplaySampleTriggerSource.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/UI/GameplaySampleTriggerSource.cs b/osu.Game/Rulesets/UI/GameplaySampleTriggerSource.cs index bceb5996b9..ac2067a913 100644 --- a/osu.Game/Rulesets/UI/GameplaySampleTriggerSource.cs +++ b/osu.Game/Rulesets/UI/GameplaySampleTriggerSource.cs @@ -66,7 +66,7 @@ namespace osu.Game.Rulesets.UI protected HitObject GetMostValidObject() { // The most optimal lookup case we have is when an object is alive. There are usually very few alive objects so there's no drawbacks in attempting this lookup each time. - var hitObject = hitObjectContainer.AliveObjects.FirstOrDefault(h => h.Result?.IsHit != true)?.HitObject; + var hitObject = hitObjectContainer.AliveObjects.FirstOrDefault(h => h.Result?.HasResult != true)?.HitObject; // In the case a next object isn't available in drawable form, we need to do a somewhat expensive traversal to get a valid sound to play. if (hitObject == null) From 599145b46aaaf543146307f575ec2e0632b7f3ac Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 25 Aug 2021 11:29:48 +0300 Subject: [PATCH 010/160] Stop clocks when removing them from sync manager --- .../OnlinePlay/Multiplayer/Spectate/CatchUpSyncManager.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/CatchUpSyncManager.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/CatchUpSyncManager.cs index cf0dfbb585..b8f47c16ff 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/CatchUpSyncManager.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/CatchUpSyncManager.cs @@ -61,7 +61,11 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate playerClocks.Add(clock); } - public void RemovePlayerClock(ISpectatorPlayerClock clock) => playerClocks.Remove(clock); + public void RemovePlayerClock(ISpectatorPlayerClock clock) + { + playerClocks.Remove(clock); + clock.Stop(); + } protected override void Update() { From 196c74fce87048c33e6c3ed1ee099b870438adb3 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 25 Aug 2021 11:30:13 +0300 Subject: [PATCH 011/160] Gray out and remove player clock when users stop playing --- .../Multiplayer/Spectate/MultiSpectatorScreen.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs index d10917259d..bf7c738882 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs @@ -8,6 +8,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Game.Graphics; using osu.Game.Online.Multiplayer; using osu.Game.Online.Spectator; using osu.Game.Screens.Play; @@ -32,6 +33,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate /// public bool AllPlayersLoaded => instances.All(p => p?.PlayerLoaded == true); + [Resolved] + private OsuColour colours { get; set; } + [Resolved] private SpectatorClient spectatorClient { get; set; } @@ -215,6 +219,11 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate protected override void EndGameplay(int userId) { RemoveUser(userId); + + var instance = instances.Single(i => i.UserId == userId); + + instance.FadeColour(colours.Gray4, 400, Easing.OutQuint); + syncManager.RemovePlayerClock(instance.GameplayClock); leaderboard.RemoveClock(userId); } From 13acdb5f19137d5868ac946885ed29a0e3a9e6f9 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 25 Aug 2021 11:30:37 +0300 Subject: [PATCH 012/160] Add test coverage --- .../TestSceneMultiSpectatorScreen.cs | 48 ++++++++++++++++++- 1 file changed, 46 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs index 18e4a6c575..1c198c11aa 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Testing; @@ -19,6 +20,7 @@ using osu.Game.Screens.Play.HUD; using osu.Game.Screens.Play.PlayerSettings; using osu.Game.Tests.Beatmaps.IO; using osu.Game.Users; +using osuTK.Graphics; namespace osu.Game.Tests.Visual.Multiplayer { @@ -314,6 +316,25 @@ 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 TestPlayersLeaveWhileSpectating() + { + start(Enumerable.Range(PLAYER_1_ID, 8).ToArray()); + sendFrames(Enumerable.Range(PLAYER_1_ID, 8).ToArray(), 300); + + loadSpectateScreen(); + + for (int i = 7; i >= 0; i--) + { + var id = PLAYER_1_ID + i; + + end(new[] { id }); + AddUntilStep("player area grayed", () => getInstance(id).Colour != Color4.White); + AddUntilStep("score quit set", () => getLeaderboardScore(id).HasQuit.Value); + sendFrames(Enumerable.Range(PLAYER_1_ID, i).ToArray(), 300); + } + } + private void loadSpectateScreen(bool waitForPlayerLoad = true) { AddStep("load screen", () => @@ -333,10 +354,31 @@ namespace osu.Game.Tests.Visual.Multiplayer { foreach (int id in userIds) { - OnlinePlayDependencies.Client.AddUser(new User { Id = id }, true); + var user = new MultiplayerRoomUser(id) + { + User = new User { Id = id }, + }; + OnlinePlayDependencies.Client.AddUser(user.User, true); SpectatorClient.StartPlay(id, beatmapId ?? importedBeatmapId); - playingUsers.Add(new MultiplayerRoomUser(id)); + + playingUsers.Add(user); + } + }); + } + + private void end(int[] userIds) + { + AddStep("end play", () => + { + foreach (int id in userIds) + { + var user = playingUsers.Single(u => u.UserID == id); + + OnlinePlayDependencies.Client.RemoveUser(user.User.AsNonNull()); + SpectatorClient.EndPlay(id); + + playingUsers.Remove(user); } }); } @@ -374,5 +416,7 @@ namespace osu.Game.Tests.Visual.Multiplayer private Player getPlayer(int userId) => getInstance(userId).ChildrenOfType().Single(); private PlayerArea getInstance(int userId) => spectatorScreen.ChildrenOfType().Single(p => p.UserId == userId); + + private GameplayLeaderboardScore getLeaderboardScore(int userId) => spectatorScreen.ChildrenOfType().Single(s => s.User?.Id == userId); } } From 7e6e2a7e292b5030739c07b342c46d0f68118323 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 25 Aug 2021 17:39:06 +0900 Subject: [PATCH 013/160] Remove unused assignment --- .../Visual/Gameplay/TestSceneGameplaySampleTriggerSource.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySampleTriggerSource.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySampleTriggerSource.cs index c446a7efd1..3e0a937ffa 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySampleTriggerSource.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySampleTriggerSource.cs @@ -59,7 +59,7 @@ namespace osu.Game.Tests.Visual.Gameplay }, new HitCircle { - StartTime = t += spacing, + StartTime = t + spacing, Samples = new[] { new HitSampleInfo(HitSampleInfo.HIT_WHISTLE) }, SampleControlPoint = new SampleControlPoint { SampleBank = "soft" }, }, From 998abcbf31350ae033c6159904365337196cb0a9 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 25 Aug 2021 18:25:31 +0300 Subject: [PATCH 014/160] Replace occurences of `Enumerable.Range(PLAYER_1_ID, ...)` with a method --- .../Multiplayer/TestSceneMultiSpectatorScreen.cs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs index 1c198c11aa..97c992e8ec 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs @@ -80,7 +80,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestGeneral() { - int[] userIds = Enumerable.Range(0, 4).Select(i => PLAYER_1_ID + i).ToArray(); + int[] userIds = getPlayerIds(4); start(userIds); loadSpectateScreen(); @@ -319,19 +319,19 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestPlayersLeaveWhileSpectating() { - start(Enumerable.Range(PLAYER_1_ID, 8).ToArray()); - sendFrames(Enumerable.Range(PLAYER_1_ID, 8).ToArray(), 300); + start(getPlayerIds(8)); + sendFrames(getPlayerIds(8), 300); loadSpectateScreen(); - for (int i = 7; i >= 0; i--) + for (int count = 7; count >= 0; count--) { - var id = PLAYER_1_ID + i; + var id = PLAYER_1_ID + count; end(new[] { id }); AddUntilStep("player area grayed", () => getInstance(id).Colour != Color4.White); AddUntilStep("score quit set", () => getLeaderboardScore(id).HasQuit.Value); - sendFrames(Enumerable.Range(PLAYER_1_ID, i).ToArray(), 300); + sendFrames(getPlayerIds(count), 300); } } @@ -418,5 +418,7 @@ namespace osu.Game.Tests.Visual.Multiplayer private PlayerArea getInstance(int userId) => spectatorScreen.ChildrenOfType().Single(p => p.UserId == userId); private GameplayLeaderboardScore getLeaderboardScore(int userId) => spectatorScreen.ChildrenOfType().Single(s => s.User?.Id == userId); + + private int[] getPlayerIds(int count) => Enumerable.Range(PLAYER_1_ID, count).ToArray(); } } From 5acaafa7089f28104c53685bf9db9c505d8c3890 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 25 Aug 2021 18:28:23 +0300 Subject: [PATCH 015/160] Make `end` accept one user ID rather than unnecessarily an array --- .../TestSceneMultiSpectatorScreen.cs | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs index 97c992e8ec..c3be5c56ef 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs @@ -328,7 +328,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { var id = PLAYER_1_ID + count; - end(new[] { id }); + end(id); AddUntilStep("player area grayed", () => getInstance(id).Colour != Color4.White); AddUntilStep("score quit set", () => getLeaderboardScore(id).HasQuit.Value); sendFrames(getPlayerIds(count), 300); @@ -367,19 +367,16 @@ namespace osu.Game.Tests.Visual.Multiplayer }); } - private void end(int[] userIds) + private void end(int userId) { - AddStep("end play", () => + AddStep($"end play for {userId}", () => { - foreach (int id in userIds) - { - var user = playingUsers.Single(u => u.UserID == id); + var user = playingUsers.Single(u => u.UserID == userId); - OnlinePlayDependencies.Client.RemoveUser(user.User.AsNonNull()); - SpectatorClient.EndPlay(id); + OnlinePlayDependencies.Client.RemoveUser(user.User.AsNonNull()); + SpectatorClient.EndPlay(userId); - playingUsers.Remove(user); - } + playingUsers.Remove(user); }); } From ec85d7f3567569dbc9da26306b4d11ab843809ba Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 26 Aug 2021 17:15:23 +0900 Subject: [PATCH 016/160] Remove unused helper method --- osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index b3e1b24d8d..29d8a475ef 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -54,11 +54,6 @@ namespace osu.Game.Rulesets.Objects.Drawables /// public readonly Bindable AccentColour = new Bindable(Color4.Gray); - /// - /// Gets the samples that are played by this object during gameplay. - /// - public ISampleInfo[] GetGameplaySamples() => Samples.Samples; - protected PausableSkinnableSound Samples { get; private set; } public virtual IEnumerable GetSamples() => HitObject.Samples; From 15aa0458bc801e3d3ecf6e3a8cc6d93fa0e1ea1d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 26 Aug 2021 17:15:36 +0900 Subject: [PATCH 017/160] Use `PausableSkinnableSound` instead --- osu.Game/Rulesets/UI/GameplaySampleTriggerSource.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/UI/GameplaySampleTriggerSource.cs b/osu.Game/Rulesets/UI/GameplaySampleTriggerSource.cs index ac2067a913..c18698f77e 100644 --- a/osu.Game/Rulesets/UI/GameplaySampleTriggerSource.cs +++ b/osu.Game/Rulesets/UI/GameplaySampleTriggerSource.cs @@ -32,7 +32,7 @@ namespace osu.Game.Rulesets.UI InternalChild = hitSounds = new Container { Name = "concurrent sample pool", - ChildrenEnumerable = Enumerable.Range(0, max_concurrent_hitsounds).Select(_ => new SkinnableSound()) + ChildrenEnumerable = Enumerable.Range(0, max_concurrent_hitsounds).Select(_ => new PausableSkinnableSound()) }; } From f078a9d2bf942745f6f400a5c996357b27a70e21 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 26 Aug 2021 17:17:39 +0900 Subject: [PATCH 018/160] Fix incorrect step type --- .../Visual/Gameplay/TestSceneGameplaySampleTriggerSource.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySampleTriggerSource.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySampleTriggerSource.cs index 3e0a937ffa..fccc1a377c 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySampleTriggerSource.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySampleTriggerSource.cs @@ -80,7 +80,7 @@ namespace osu.Game.Tests.Visual.Gameplay { HitObjectLifetimeEntry nextObjectEntry = null; - AddUntilStep("no alive objects", () => getNextAliveObject() == null); + AddAssert("no alive objects", () => getNextAliveObject() == null); AddAssert("check initially correct object", () => sampleTriggerSource.GetMostValidObject() == beatmap.HitObjects[0]); From 90e81a595d0c2fafae408bc8667043682c8a2587 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 26 Aug 2021 17:19:46 +0900 Subject: [PATCH 019/160] Move `DrumSampleTriggerSource` into its own class to avoid nested references --- .../Skinning/Legacy/LegacyInputDrum.cs | 2 +- .../UI/DrumSampleTriggerSource.cs | 30 +++++++++++++++++++ osu.Game.Rulesets.Taiko/UI/InputDrum.cs | 20 ------------- 3 files changed, 31 insertions(+), 21 deletions(-) create mode 100644 osu.Game.Rulesets.Taiko/UI/DrumSampleTriggerSource.cs diff --git a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs index 5a76694913..9d35093591 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs @@ -112,7 +112,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy public readonly Sprite Centre; [Resolved] - private InputDrum.DrumSampleTriggerSource sampleTriggerSource { get; set; } + private DrumSampleTriggerSource sampleTriggerSource { get; set; } public LegacyHalfDrum(bool flipped) { diff --git a/osu.Game.Rulesets.Taiko/UI/DrumSampleTriggerSource.cs b/osu.Game.Rulesets.Taiko/UI/DrumSampleTriggerSource.cs new file mode 100644 index 0000000000..3279d128d3 --- /dev/null +++ b/osu.Game.Rulesets.Taiko/UI/DrumSampleTriggerSource.cs @@ -0,0 +1,30 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Game.Audio; +using osu.Game.Rulesets.Taiko.Objects; +using osu.Game.Rulesets.UI; + +namespace osu.Game.Rulesets.Taiko.UI +{ + public class DrumSampleTriggerSource : GameplaySampleTriggerSource + { + public DrumSampleTriggerSource(HitObjectContainer hitObjectContainer) + : base(hitObjectContainer) + { + } + + public void Play(HitType hitType) + { + var hitObject = GetMostValidObject(); + + if (hitObject == null) + return; + + PlaySamples(new ISampleInfo[] { hitObject.SampleControlPoint.GetSampleInfo(hitType == HitType.Rim ? HitSampleInfo.HIT_CLAP : HitSampleInfo.HIT_NORMAL) }); + } + + public override void Play() => throw new InvalidOperationException(@"Use override with HitType parameter instead"); + } +} diff --git a/osu.Game.Rulesets.Taiko/UI/InputDrum.cs b/osu.Game.Rulesets.Taiko/UI/InputDrum.cs index 24e2dddb49..3eafd201b7 100644 --- a/osu.Game.Rulesets.Taiko/UI/InputDrum.cs +++ b/osu.Game.Rulesets.Taiko/UI/InputDrum.cs @@ -8,7 +8,6 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; using osu.Framework.Input.Bindings; -using osu.Game.Audio; using osu.Game.Graphics; using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Rulesets.UI; @@ -201,24 +200,5 @@ namespace osu.Game.Rulesets.Taiko.UI } } - public class DrumSampleTriggerSource : GameplaySampleTriggerSource - { - public DrumSampleTriggerSource(HitObjectContainer hitObjectContainer) - : base(hitObjectContainer) - { - } - - public void Play(HitType hitType) - { - var hitObject = GetMostValidObject(); - - if (hitObject == null) - return; - - PlaySamples(new ISampleInfo[] { hitObject.SampleControlPoint.GetSampleInfo(hitType == HitType.Rim ? HitSampleInfo.HIT_CLAP : HitSampleInfo.HIT_NORMAL) }); - } - - public override void Play() => throw new InvalidOperationException(@"Use override with HitType parameter instead"); - } } } From cea632463e781f59ad307e2f373632998656b41e Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 26 Aug 2021 22:30:20 +0300 Subject: [PATCH 020/160] Remove empty newline --- osu.Game.Rulesets.Taiko/UI/InputDrum.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/UI/InputDrum.cs b/osu.Game.Rulesets.Taiko/UI/InputDrum.cs index 3eafd201b7..ddfaf64549 100644 --- a/osu.Game.Rulesets.Taiko/UI/InputDrum.cs +++ b/osu.Game.Rulesets.Taiko/UI/InputDrum.cs @@ -199,6 +199,5 @@ namespace osu.Game.Rulesets.Taiko.UI { } } - } } From b7a031619460698faa284ab71b5b4668775ab00d Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 27 Aug 2021 13:14:56 +0300 Subject: [PATCH 021/160] Shorten test player count to 4 for less steps --- .../Multiplayer/TestSceneMultiSpectatorScreen.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs index c3be5c56ef..6f6769bdb8 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs @@ -319,18 +319,18 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestPlayersLeaveWhileSpectating() { - start(getPlayerIds(8)); - sendFrames(getPlayerIds(8), 300); + start(getPlayerIds(4)); + sendFrames(getPlayerIds(4), 300); loadSpectateScreen(); - for (int count = 7; count >= 0; count--) + for (int count = 3; count >= 0; count--) { var id = PLAYER_1_ID + count; end(id); - AddUntilStep("player area grayed", () => getInstance(id).Colour != Color4.White); - AddUntilStep("score quit set", () => getLeaderboardScore(id).HasQuit.Value); + AddUntilStep($"{id} area grayed", () => getInstance(id).Colour != Color4.White); + AddUntilStep($"{id} score quit set", () => getLeaderboardScore(id).HasQuit.Value); sendFrames(getPlayerIds(count), 300); } } From 1650fbb8be04d4a0c7deeb805651803d956fcfe1 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 27 Aug 2021 13:15:12 +0300 Subject: [PATCH 022/160] Add failing test steps --- .../Multiplayer/TestSceneMultiSpectatorScreen.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs index 6f6769bdb8..bfcb55ce33 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs @@ -333,6 +333,17 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep($"{id} score quit set", () => getLeaderboardScore(id).HasQuit.Value); sendFrames(getPlayerIds(count), 300); } + + Player player = null; + + AddStep($"get {PLAYER_1_ID} player instance", () => player = getInstance(PLAYER_1_ID).ChildrenOfType().Single()); + + start(new[] { PLAYER_1_ID }); + sendFrames(PLAYER_1_ID, 300); + + AddAssert($"{PLAYER_1_ID} player instance still same", () => getInstance(PLAYER_1_ID).ChildrenOfType().Single() == player); + AddAssert($"{PLAYER_1_ID} area still grayed", () => getInstance(PLAYER_1_ID).Colour != Color4.White); + AddAssert($"{PLAYER_1_ID} score quit still set", () => getLeaderboardScore(PLAYER_1_ID).HasQuit.Value); } private void loadSpectateScreen(bool waitForPlayerLoad = true) From 378734a7f8cdd6678fcc42886f99bacfcfefcba6 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 27 Aug 2021 13:16:08 +0300 Subject: [PATCH 023/160] Separate solo spectator player and "exit on restart" logic to own class --- osu.Game/Screens/Play/SoloSpectator.cs | 2 +- osu.Game/Screens/Play/SoloSpectatorPlayer.cs | 52 +++++++++++++++++++ osu.Game/Screens/Play/SpectatorPlayer.cs | 30 +++-------- .../Screens/Play/SpectatorPlayerLoader.cs | 7 +-- 4 files changed, 61 insertions(+), 30 deletions(-) create mode 100644 osu.Game/Screens/Play/SoloSpectatorPlayer.cs diff --git a/osu.Game/Screens/Play/SoloSpectator.cs b/osu.Game/Screens/Play/SoloSpectator.cs index 820d776e63..4520e2e825 100644 --- a/osu.Game/Screens/Play/SoloSpectator.cs +++ b/osu.Game/Screens/Play/SoloSpectator.cs @@ -211,7 +211,7 @@ namespace osu.Game.Screens.Play Beatmap.Value = gameplayState.Beatmap; Ruleset.Value = gameplayState.Ruleset.RulesetInfo; - this.Push(new SpectatorPlayerLoader(gameplayState.Score)); + this.Push(new SpectatorPlayerLoader(gameplayState.Score, () => new SoloSpectatorPlayer(gameplayState.Score))); } } diff --git a/osu.Game/Screens/Play/SoloSpectatorPlayer.cs b/osu.Game/Screens/Play/SoloSpectatorPlayer.cs new file mode 100644 index 0000000000..969a5bf2b4 --- /dev/null +++ b/osu.Game/Screens/Play/SoloSpectatorPlayer.cs @@ -0,0 +1,52 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Screens; +using osu.Game.Online.Spectator; +using osu.Game.Scoring; + +namespace osu.Game.Screens.Play +{ + public class SoloSpectatorPlayer : SpectatorPlayer + { + private readonly Score score; + + public SoloSpectatorPlayer(Score score, PlayerConfiguration configuration = null) + : base(score, configuration) + { + this.score = score; + } + + [BackgroundDependencyLoader] + private void load() + { + SpectatorClient.OnUserBeganPlaying += userBeganPlaying; + } + + public override bool OnExiting(IScreen next) + { + SpectatorClient.OnUserBeganPlaying -= userBeganPlaying; + + return base.OnExiting(next); + } + + private void userBeganPlaying(int userId, SpectatorState state) + { + if (userId != score.ScoreInfo.UserID) return; + + Schedule(() => + { + if (this.IsCurrentScreen()) this.Exit(); + }); + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + if (SpectatorClient != null) + SpectatorClient.OnUserBeganPlaying -= userBeganPlaying; + } + } +} diff --git a/osu.Game/Screens/Play/SpectatorPlayer.cs b/osu.Game/Screens/Play/SpectatorPlayer.cs index 1dae28092a..d7e42a9cd1 100644 --- a/osu.Game/Screens/Play/SpectatorPlayer.cs +++ b/osu.Game/Screens/Play/SpectatorPlayer.cs @@ -14,16 +14,16 @@ using osu.Game.Screens.Ranking; namespace osu.Game.Screens.Play { - public class SpectatorPlayer : Player + public abstract class SpectatorPlayer : Player { [Resolved] - private SpectatorClient spectatorClient { get; set; } + protected SpectatorClient SpectatorClient { get; private set; } private readonly Score score; protected override bool CheckModsAllowFailure() => false; // todo: better support starting mid-way through beatmap - public SpectatorPlayer(Score score, PlayerConfiguration configuration = null) + protected SpectatorPlayer(Score score, PlayerConfiguration configuration = null) : base(configuration) { this.score = score; @@ -32,8 +32,6 @@ namespace osu.Game.Screens.Play [BackgroundDependencyLoader] private void load() { - spectatorClient.OnUserBeganPlaying += userBeganPlaying; - AddInternal(new OsuSpriteText { Text = $"Watching {score.ScoreInfo.User.Username} playing live!", @@ -50,7 +48,7 @@ namespace osu.Game.Screens.Play // Start gameplay along with the very first arrival frame (the latest one). score.Replay.Frames.Clear(); - spectatorClient.OnNewFrames += userSentFrames; + SpectatorClient.OnNewFrames += userSentFrames; } private void userSentFrames(int userId, FrameDataBundle bundle) @@ -93,31 +91,17 @@ namespace osu.Game.Screens.Play public override bool OnExiting(IScreen next) { - spectatorClient.OnUserBeganPlaying -= userBeganPlaying; - spectatorClient.OnNewFrames -= userSentFrames; + SpectatorClient.OnNewFrames -= userSentFrames; return base.OnExiting(next); } - private void userBeganPlaying(int userId, SpectatorState state) - { - if (userId != score.ScoreInfo.UserID) return; - - Schedule(() => - { - if (this.IsCurrentScreen()) this.Exit(); - }); - } - protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); - if (spectatorClient != null) - { - spectatorClient.OnUserBeganPlaying -= userBeganPlaying; - spectatorClient.OnNewFrames -= userSentFrames; - } + if (SpectatorClient != null) + SpectatorClient.OnNewFrames -= userSentFrames; } } } diff --git a/osu.Game/Screens/Play/SpectatorPlayerLoader.cs b/osu.Game/Screens/Play/SpectatorPlayerLoader.cs index bdd23962dc..10cc36c9a9 100644 --- a/osu.Game/Screens/Play/SpectatorPlayerLoader.cs +++ b/osu.Game/Screens/Play/SpectatorPlayerLoader.cs @@ -11,12 +11,7 @@ namespace osu.Game.Screens.Play { public readonly ScoreInfo Score; - public SpectatorPlayerLoader(Score score) - : this(score, () => new SpectatorPlayer(score)) - { - } - - public SpectatorPlayerLoader(Score score, Func createPlayer) + public SpectatorPlayerLoader(Score score, Func createPlayer) : base(createPlayer) { if (score.Replay == null) From e527bfd4bf4d36cf71a6871828a129760e7efabc Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 28 Aug 2021 02:37:46 +0300 Subject: [PATCH 024/160] Move incompatibility icon logic to local player mod select overlays --- .../Overlays/Mods/LocalPlayerModButton.cs | 69 +++++++++++++++++++ .../Mods/LocalPlayerModSelectOverlay.cs | 12 ++++ osu.Game/Overlays/Mods/ModButton.cs | 55 +++------------ osu.Game/Overlays/Mods/ModSection.cs | 10 +-- 4 files changed, 96 insertions(+), 50 deletions(-) create mode 100644 osu.Game/Overlays/Mods/LocalPlayerModButton.cs diff --git a/osu.Game/Overlays/Mods/LocalPlayerModButton.cs b/osu.Game/Overlays/Mods/LocalPlayerModButton.cs new file mode 100644 index 0000000000..10c81da7a6 --- /dev/null +++ b/osu.Game/Overlays/Mods/LocalPlayerModButton.cs @@ -0,0 +1,69 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using 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.Cursor; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Mods; +using osu.Game.Screens.Play.HUD; +using osu.Game.Utils; +using osuTK; + +namespace osu.Game.Overlays.Mods +{ + public class LocalPlayerModButton : ModButton + { + private readonly CompositeDrawable incompatibleIcon; + + [Resolved] + private Bindable> selectedMods { get; set; } + + public LocalPlayerModButton(Mod mod) + : base(mod) + { + ButtonContent.Add(incompatibleIcon = new IncompatibleIcon + { + Anchor = Anchor.BottomRight, + Origin = Anchor.Centre, + Position = new Vector2(-13), + }); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + selectedMods.BindValueChanged(_ => Scheduler.AddOnce(updateCompatibility), true); + } + + protected override void DisplayMod(Mod mod) + { + base.DisplayMod(mod); + + Scheduler.AddOnce(updateCompatibility); + } + + private void updateCompatibility() + { + var m = SelectedMod ?? Mods.First(); + + bool isIncompatible = false; + + if (selectedMods.Value.Count > 0 && !selectedMods.Value.Contains(m)) + isIncompatible = !ModUtils.CheckCompatibleSet(selectedMods.Value.Append(m)); + + if (isIncompatible) + incompatibleIcon.Show(); + else + incompatibleIcon.Hide(); + } + } +} diff --git a/osu.Game/Overlays/Mods/LocalPlayerModSelectOverlay.cs b/osu.Game/Overlays/Mods/LocalPlayerModSelectOverlay.cs index db76581108..b8e0c27007 100644 --- a/osu.Game/Overlays/Mods/LocalPlayerModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/LocalPlayerModSelectOverlay.cs @@ -14,5 +14,17 @@ namespace osu.Game.Overlays.Mods foreach (var section in ModSectionsContainer.Children) section.DeselectTypes(mod.IncompatibleMods, true, mod); } + + protected override ModSection CreateModSection(ModType type) => new LocalPlayerModSection(type); + + private class LocalPlayerModSection : ModSection + { + public LocalPlayerModSection(ModType type) + : base(type) + { + } + + protected override ModButton CreateModButton(Mod mod) => new LocalPlayerModButton(mod); + } } } diff --git a/osu.Game/Overlays/Mods/ModButton.cs b/osu.Game/Overlays/Mods/ModButton.cs index 4675eb6bc8..8f6fc734e8 100644 --- a/osu.Game/Overlays/Mods/ModButton.cs +++ b/osu.Game/Overlays/Mods/ModButton.cs @@ -11,16 +11,12 @@ using osu.Game.Graphics.Sprites; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.UI; using System; -using System.Collections.Generic; using System.Linq; -using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics.Cursor; using osu.Framework.Input.Events; using osu.Framework.Localisation; using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; -using osu.Game.Utils; namespace osu.Game.Overlays.Mods { @@ -33,7 +29,6 @@ namespace osu.Game.Overlays.Mods private ModIcon backgroundIcon; private readonly SpriteText text; private readonly Container iconsContainer; - private readonly CompositeDrawable incompatibleIcon; /// /// Fired when the selection changes. @@ -48,9 +43,6 @@ namespace osu.Game.Overlays.Mods // A selected index of -1 means not selected. private int selectedIndex = -1; - [Resolved] - private Bindable> selectedMods { get; set; } - /// /// Change the selected mod index of this button. /// @@ -109,7 +101,7 @@ namespace osu.Game.Overlays.Mods .RotateTo(rotate_angle * direction) .RotateTo(0f, mod_switch_duration, mod_switch_easing); - Schedule(() => displayMod(newSelection)); + Schedule(() => DisplayMod(newSelection)); } } @@ -138,7 +130,8 @@ namespace osu.Game.Overlays.Mods } private Mod mod; - private readonly Container scaleContainer; + + protected readonly Container ButtonContent; public Mod Mod { @@ -162,7 +155,7 @@ namespace osu.Game.Overlays.Mods if (Mods.Length > 0) { - displayMod(Mods[0]); + DisplayMod(Mods[0]); } } } @@ -173,13 +166,13 @@ namespace osu.Game.Overlays.Mods protected override bool OnMouseDown(MouseDownEvent e) { - scaleContainer.ScaleTo(0.9f, 800, Easing.Out); + ButtonContent.ScaleTo(0.9f, 800, Easing.Out); return base.OnMouseDown(e); } protected override void OnMouseUp(MouseUpEvent e) { - scaleContainer.ScaleTo(1, 500, Easing.OutElastic); + ButtonContent.ScaleTo(1, 500, Easing.OutElastic); // only trigger the event if we are inside the area of the button if (Contains(e.ScreenSpaceMousePosition)) @@ -238,30 +231,13 @@ namespace osu.Game.Overlays.Mods public void Deselect() => changeSelectedIndex(-1); - private void displayMod(Mod mod) + protected virtual void DisplayMod(Mod mod) { if (backgroundIcon != null) backgroundIcon.Mod = foregroundIcon.Mod; foregroundIcon.Mod = mod; text.Text = mod.Name; Colour = mod.HasImplementation ? Color4.White : Color4.Gray; - - Scheduler.AddOnce(updateCompatibility); - } - - private void updateCompatibility() - { - var m = SelectedMod ?? Mods.First(); - - bool isIncompatible = false; - - if (selectedMods.Value.Count > 0 && !selectedMods.Value.Contains(m)) - isIncompatible = !ModUtils.CheckCompatibleSet(selectedMods.Value.Append(m)); - - if (isIncompatible) - incompatibleIcon.Show(); - else - incompatibleIcon.Hide(); } private void createIcons() @@ -307,7 +283,7 @@ namespace osu.Game.Overlays.Mods Anchor = Anchor.TopCentre, Children = new Drawable[] { - scaleContainer = new Container + ButtonContent = new Container { Children = new Drawable[] { @@ -317,12 +293,6 @@ namespace osu.Game.Overlays.Mods Origin = Anchor.Centre, Anchor = Anchor.Centre, }, - incompatibleIcon = new IncompatibleIcon - { - Origin = Anchor.Centre, - Anchor = Anchor.BottomRight, - Position = new Vector2(-13), - } }, RelativeSizeAxes = Axes.Both, Origin = Anchor.Centre, @@ -342,14 +312,7 @@ namespace osu.Game.Overlays.Mods Mod = mod; } - protected override void LoadComplete() - { - base.LoadComplete(); - - selectedMods.BindValueChanged(_ => Scheduler.AddOnce(updateCompatibility), true); - } - - public ITooltip GetCustomTooltip() => new ModButtonTooltip(); + public virtual ITooltip GetCustomTooltip() => new ModButtonTooltip(); public object TooltipContent => SelectedMod ?? Mods.FirstOrDefault(); } diff --git a/osu.Game/Overlays/Mods/ModSection.cs b/osu.Game/Overlays/Mods/ModSection.cs index 6e289dc8aa..faad23a4e1 100644 --- a/osu.Game/Overlays/Mods/ModSection.cs +++ b/osu.Game/Overlays/Mods/ModSection.cs @@ -51,14 +51,14 @@ namespace osu.Game.Overlays.Mods if (m == null) return new ModButtonEmpty(); - return new ModButton(m) + return CreateModButton(m).With(b => { - SelectionChanged = mod => + b.SelectionChanged = mod => { ModButtonStateChanged(mod); Action?.Invoke(mod); - }, - }; + }; + }); }).ToArray(); modsLoadCts?.Cancel(); @@ -247,6 +247,8 @@ namespace osu.Game.Overlays.Mods Text = text }; + protected virtual ModButton CreateModButton(Mod mod) => new ModButton(mod); + /// /// Play out all remaining animations immediately to leave mods in a good (final) state. /// From 589f2863ca16b75a959a873d1f4c6a1517107ede Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 28 Aug 2021 02:38:45 +0300 Subject: [PATCH 025/160] Move incompatibility tooltip logic to local player mod select overlays This one turned out to be a bit more involved, due to tooltips being shared and having the potential of being used somewhere where it shouldn't be, due to the same content type matching. That's the reason I've defined a protected `TargetContentType`, to be able to separate "local player mod tooltips" and regular mod tooltips apart. Definitely unsure about the solution, but that's as far as I can think of right now. --- .../Overlays/Mods/LocalPlayerModButton.cs | 51 ++++++++++++++++ osu.Game/Overlays/Mods/ModButton.cs | 2 +- osu.Game/Overlays/Mods/ModButtonTooltip.cs | 58 +++++-------------- 3 files changed, 68 insertions(+), 43 deletions(-) diff --git a/osu.Game/Overlays/Mods/LocalPlayerModButton.cs b/osu.Game/Overlays/Mods/LocalPlayerModButton.cs index 10c81da7a6..9427c3e63e 100644 --- a/osu.Game/Overlays/Mods/LocalPlayerModButton.cs +++ b/osu.Game/Overlays/Mods/LocalPlayerModButton.cs @@ -65,5 +65,56 @@ namespace osu.Game.Overlays.Mods else incompatibleIcon.Hide(); } + + public override ITooltip GetCustomTooltip() => new LocalPlayerModButtonTooltip(); + + private class LocalPlayerModButtonTooltip : ModButtonTooltip + { + private readonly OsuSpriteText incompatibleText; + + private readonly Bindable> incompatibleMods = new Bindable>(); + + [Resolved] + private Bindable ruleset { get; set; } + + public LocalPlayerModButtonTooltip() + { + AddRange(new Drawable[] + { + incompatibleText = new OsuSpriteText + { + Margin = new MarginPadding { Top = 5 }, + Font = OsuFont.GetFont(weight: FontWeight.Regular), + Text = "Incompatible with:" + }, + new ModDisplay + { + Current = incompatibleMods, + ExpansionMode = ExpansionMode.AlwaysExpanded, + Scale = new Vector2(0.7f) + } + }); + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + incompatibleText.Colour = colours.BlueLight; + } + + protected override Type TargetContentType => typeof(LocalPlayerModButton); + + protected override void UpdateDisplay(Mod mod) + { + base.UpdateDisplay(mod); + + var incompatibleTypes = mod.IncompatibleMods; + + var allMods = ruleset.Value.CreateInstance().GetAllMods(); + + incompatibleMods.Value = allMods.Where(m => m.GetType() != mod.GetType() && incompatibleTypes.Any(t => t.IsInstanceOfType(m))).ToList(); + incompatibleText.Text = incompatibleMods.Value.Any() ? "Incompatible with:" : "Compatible with all mods"; + } + } } } diff --git a/osu.Game/Overlays/Mods/ModButton.cs b/osu.Game/Overlays/Mods/ModButton.cs index 8f6fc734e8..cc8acb7513 100644 --- a/osu.Game/Overlays/Mods/ModButton.cs +++ b/osu.Game/Overlays/Mods/ModButton.cs @@ -314,6 +314,6 @@ namespace osu.Game.Overlays.Mods public virtual ITooltip GetCustomTooltip() => new ModButtonTooltip(); - public object TooltipContent => SelectedMod ?? Mods.FirstOrDefault(); + public object TooltipContent => this; } } diff --git a/osu.Game/Overlays/Mods/ModButtonTooltip.cs b/osu.Game/Overlays/Mods/ModButtonTooltip.cs index 666ed07e28..125357ea44 100644 --- a/osu.Game/Overlays/Mods/ModButtonTooltip.cs +++ b/osu.Game/Overlays/Mods/ModButtonTooltip.cs @@ -1,19 +1,16 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System.Collections.Generic; +using System; using System.Linq; using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; -using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; -using osu.Game.Screens.Play.HUD; using osuTK; namespace osu.Game.Overlays.Mods @@ -22,12 +19,8 @@ namespace osu.Game.Overlays.Mods { private readonly OsuSpriteText descriptionText; private readonly Box background; - private readonly OsuSpriteText incompatibleText; - private readonly Bindable> incompatibleMods = new Bindable>(); - - [Resolved] - private Bindable ruleset { get; set; } + protected override Container Content { get; } public ModButtonTooltip() { @@ -35,13 +28,13 @@ namespace osu.Game.Overlays.Mods Masking = true; CornerRadius = 5; - Children = new Drawable[] + InternalChildren = new Drawable[] { background = new Box { RelativeSizeAxes = Axes.Both }, - new FillFlowContainer + Content = new FillFlowContainer { AutoSizeAxes = Axes.Both, Direction = FillDirection.Vertical, @@ -51,19 +44,7 @@ namespace osu.Game.Overlays.Mods descriptionText = new OsuSpriteText { Font = OsuFont.GetFont(weight: FontWeight.Regular), - Margin = new MarginPadding { Bottom = 5 } }, - incompatibleText = new OsuSpriteText - { - Font = OsuFont.GetFont(weight: FontWeight.Regular), - Text = "Incompatible with:" - }, - new ModDisplay - { - Current = incompatibleMods, - ExpansionMode = ExpansionMode.AlwaysExpanded, - Scale = new Vector2(0.7f) - } } }, }; @@ -74,7 +55,6 @@ namespace osu.Game.Overlays.Mods { background.Colour = colours.Gray3; descriptionText.Colour = colours.BlueLighter; - incompatibleText.Colour = colours.BlueLight; } protected override void PopIn() => this.FadeIn(200, Easing.OutQuint); @@ -82,34 +62,28 @@ namespace osu.Game.Overlays.Mods private Mod lastMod; - public bool SetContent(object content) + protected virtual Type TargetContentType => typeof(ModButton); + + public virtual bool SetContent(object content) { - if (!(content is Mod mod)) + if (!(content is ModButton button) || content.GetType() != TargetContentType) return false; + var mod = button.SelectedMod ?? button.Mods.First(); + if (mod.Equals(lastMod)) return true; lastMod = mod; - descriptionText.Text = mod.Description; - - var incompatibleTypes = mod.IncompatibleMods; - - var allMods = ruleset.Value.CreateInstance().GetAllMods(); - - incompatibleMods.Value = allMods.Where(m => m.GetType() != mod.GetType() && incompatibleTypes.Any(t => t.IsInstanceOfType(m))).ToList(); - - if (!incompatibleMods.Value.Any()) - { - incompatibleText.Text = "Compatible with all mods"; - return true; - } - - incompatibleText.Text = "Incompatible with:"; - + UpdateDisplay(mod); return true; } + protected virtual void UpdateDisplay(Mod mod) + { + descriptionText.Text = mod.Description; + } + public void Move(Vector2 pos) => Position = pos; } } From 7457480b50c9a69c01287feec8dff1b08b6b15db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 28 Aug 2021 18:47:34 +0200 Subject: [PATCH 026/160] Add local popover container to lounge subscreen --- osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs b/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs index cf3d76a3fb..cca1394b6d 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs @@ -11,6 +11,7 @@ using osu.Framework.Bindables; using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; using osu.Framework.Logging; @@ -72,6 +73,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge private readonly Bindable filter = new Bindable(new FilterCriteria()); private readonly IBindable operationInProgress = new Bindable(); private readonly IBindable isIdle = new BindableBool(); + private PopoverContainer popoverContainer; private LoadingLayer loadingLayer; private RoomsContainer roomsContainer; private SearchTextBox searchTextBox; @@ -90,7 +92,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge InternalChildren = new Drawable[] { ListingPollingComponent = CreatePollingComponent().With(c => c.Filter.BindTarget = filter), - new Container + popoverContainer = new PopoverContainer { Name = @"Rooms area", RelativeSizeAxes = Axes.Both, @@ -285,7 +287,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge searchTextBox.HoldFocus = false; // ensure any password prompt is dismissed. - this.HidePopover(); + popoverContainer.HidePopover(); } public void Join(Room room, string password) => Schedule(() => From e94d96f25093a2b32511554548e1a1314db6ce45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 28 Aug 2021 18:49:24 +0200 Subject: [PATCH 027/160] Add local popover container to editor screens --- osu.Game/Screens/Edit/EditorScreen.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/EditorScreen.cs b/osu.Game/Screens/Edit/EditorScreen.cs index d7fe5207d0..5665e6cb55 100644 --- a/osu.Game/Screens/Edit/EditorScreen.cs +++ b/osu.Game/Screens/Edit/EditorScreen.cs @@ -4,6 +4,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; namespace osu.Game.Screens.Edit { @@ -28,7 +29,7 @@ namespace osu.Game.Screens.Edit Origin = Anchor.Centre; RelativeSizeAxes = Axes.Both; - InternalChild = content = new Container { RelativeSizeAxes = Axes.Both }; + InternalChild = content = new PopoverContainer { RelativeSizeAxes = Axes.Both }; } protected override void PopIn() From fcc3e57d5dd86d82f0d9c56e6c4c5fa67fe3ef9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 28 Aug 2021 18:50:13 +0200 Subject: [PATCH 028/160] Move overlay colour provider up to editor screen --- osu.Game/Screens/Edit/EditorRoundedScreen.cs | 5 ----- osu.Game/Screens/Edit/EditorScreen.cs | 6 ++++++ 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Edit/EditorRoundedScreen.cs b/osu.Game/Screens/Edit/EditorRoundedScreen.cs index c6ced02021..b271a145f5 100644 --- a/osu.Game/Screens/Edit/EditorRoundedScreen.cs +++ b/osu.Game/Screens/Edit/EditorRoundedScreen.cs @@ -6,7 +6,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; -using osu.Game.Overlays; namespace osu.Game.Screens.Edit { @@ -17,9 +16,6 @@ namespace osu.Game.Screens.Edit [Resolved] private OsuColour colours { get; set; } - [Cached] - protected readonly OverlayColourProvider ColourProvider; - private Container roundedContent; protected override Container Content => roundedContent; @@ -27,7 +23,6 @@ namespace osu.Game.Screens.Edit public EditorRoundedScreen(EditorScreenMode mode) : base(mode) { - ColourProvider = new OverlayColourProvider(OverlayColourScheme.Blue); } [BackgroundDependencyLoader] diff --git a/osu.Game/Screens/Edit/EditorScreen.cs b/osu.Game/Screens/Edit/EditorScreen.cs index 5665e6cb55..2810f78835 100644 --- a/osu.Game/Screens/Edit/EditorScreen.cs +++ b/osu.Game/Screens/Edit/EditorScreen.cs @@ -5,6 +5,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; +using osu.Game.Overlays; namespace osu.Game.Screens.Edit { @@ -16,6 +17,9 @@ namespace osu.Game.Screens.Edit [Resolved] protected EditorBeatmap EditorBeatmap { get; private set; } + [Cached] + protected readonly OverlayColourProvider ColourProvider; + protected override Container Content => content; private readonly Container content; @@ -29,6 +33,8 @@ namespace osu.Game.Screens.Edit Origin = Anchor.Centre; RelativeSizeAxes = Axes.Both; + ColourProvider = new OverlayColourProvider(OverlayColourScheme.Blue); + InternalChild = content = new PopoverContainer { RelativeSizeAxes = Axes.Both }; } From d9db1ecee9c6183424b86881b099d732933a424c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 28 Aug 2021 18:51:12 +0200 Subject: [PATCH 029/160] Remove game-global popover container --- osu.Game/OsuGameBase.cs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index f2d575550a..69f6bc1b7b 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -13,7 +13,6 @@ using osu.Framework.Development; using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Cursor; using osu.Framework.IO.Stores; using osu.Framework.Platform; using osu.Game.Beatmaps; @@ -342,11 +341,7 @@ namespace osu.Game globalBindings = new GlobalActionContainer(this) }; - MenuCursorContainer.Child = new PopoverContainer - { - RelativeSizeAxes = Axes.Both, - Child = content = new OsuTooltipContainer(MenuCursorContainer.Cursor) { RelativeSizeAxes = Axes.Both } - }; + MenuCursorContainer.Child = content = new OsuTooltipContainer(MenuCursorContainer.Cursor) { RelativeSizeAxes = Axes.Both }; base.Content.Add(CreateScalingContainer().WithChildren(mainContent)); From 2efe82a18db9c46d2502852201fea28dfb114e73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 28 Aug 2021 20:20:42 +0200 Subject: [PATCH 030/160] Remove popover container from manual input manager test scene --- .../TestSceneLabelledColourPalette.cs | 21 ++++++++++++------- .../Visual/OsuManualInputManagerTestScene.cs | 7 +------ 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledColourPalette.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledColourPalette.cs index 6fafb8f87a..e1ea02ba67 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledColourPalette.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledColourPalette.cs @@ -5,6 +5,7 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; using osu.Framework.Testing; using osu.Framework.Utils; using osu.Game.Graphics.Cursor; @@ -55,20 +56,24 @@ namespace osu.Game.Tests.Visual.UserInterface { AddStep("create component", () => { - Child = new OsuContextMenuContainer + Child = new PopoverContainer { RelativeSizeAxes = Axes.Both, - Child = new Container + Child = new OsuContextMenuContainer { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Width = 500, - AutoSizeAxes = Axes.Y, - Child = component = new LabelledColourPalette + RelativeSizeAxes = Axes.Both, + Child = new Container { Anchor = Anchor.Centre, Origin = Anchor.Centre, - ColourNamePrefix = "My colour #" + Width = 500, + AutoSizeAxes = Axes.Y, + Child = component = new LabelledColourPalette + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + ColourNamePrefix = "My colour #" + } } } }; diff --git a/osu.Game/Tests/Visual/OsuManualInputManagerTestScene.cs b/osu.Game/Tests/Visual/OsuManualInputManagerTestScene.cs index c5e2e67eaf..752794d25a 100644 --- a/osu.Game/Tests/Visual/OsuManualInputManagerTestScene.cs +++ b/osu.Game/Tests/Visual/OsuManualInputManagerTestScene.cs @@ -3,7 +3,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; using osu.Framework.Testing.Input; using osu.Game.Graphics.Cursor; @@ -35,11 +34,7 @@ namespace osu.Game.Tests.Visual { MenuCursorContainer cursorContainer; - CompositeDrawable mainContent = new PopoverContainer - { - RelativeSizeAxes = Axes.Both, - Child = cursorContainer = new MenuCursorContainer { RelativeSizeAxes = Axes.Both, } - }; + CompositeDrawable mainContent = cursorContainer = new MenuCursorContainer { RelativeSizeAxes = Axes.Both }; cursorContainer.Child = content = new OsuTooltipContainer(cursorContainer.Cursor) { From 38912bfc1663db6ac5e278bc3a1b50ae1f5089bc Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Sat, 28 Aug 2021 20:13:01 -0700 Subject: [PATCH 031/160] Fix floating overlays not closing when clicking some empty area of the toolbar --- osu.Game/OsuGame.cs | 26 +++++++++++++++--------- osu.Game/Overlays/LoginOverlay.cs | 13 ------------ osu.Game/Overlays/NotificationOverlay.cs | 13 ------------ osu.Game/Overlays/NowPlayingOverlay.cs | 6 ------ osu.Game/Overlays/SettingsPanel.cs | 6 ------ 5 files changed, 16 insertions(+), 48 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 4d952c39c6..a584644fc9 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -104,6 +104,8 @@ namespace osu.Game protected Container ScreenOffsetContainer { get; private set; } + private Container overlayOffsetContainer; + [Resolved] private FrameworkConfigManager frameworkConfig { get; set; } @@ -120,7 +122,7 @@ namespace osu.Game public virtual StableStorage GetStorageForStableInstall() => null; - public float ToolbarOffset => (Toolbar?.Position.Y ?? 0) + (Toolbar?.DrawHeight ?? 0); + private float toolbarOffset => (Toolbar?.Position.Y ?? 0) + (Toolbar?.DrawHeight ?? 0); private IdleTracker idleTracker; @@ -692,9 +694,16 @@ namespace osu.Game }, } }, - overlayContent = new Container { RelativeSizeAxes = Axes.Both }, - rightFloatingOverlayContent = new Container { RelativeSizeAxes = Axes.Both }, - leftFloatingOverlayContent = new Container { RelativeSizeAxes = Axes.Both }, + overlayOffsetContainer = new Container + { + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + overlayContent = new Container { RelativeSizeAxes = Axes.Both }, + rightFloatingOverlayContent = new Container { RelativeSizeAxes = Axes.Both }, + leftFloatingOverlayContent = new Container { RelativeSizeAxes = Axes.Both }, + } + }, topMostOverlayContent = new Container { RelativeSizeAxes = Axes.Both }, idleTracker, new ConfineMouseTracker() @@ -731,7 +740,6 @@ namespace osu.Game loadComponentSingleFile(Notifications.With(d => { - d.GetToolbarHeight = () => ToolbarOffset; d.Anchor = Anchor.TopRight; d.Origin = Anchor.TopRight; }), rightFloatingOverlayContent.Add, true); @@ -757,7 +765,7 @@ namespace osu.Game loadComponentSingleFile(channelManager = new ChannelManager(), AddInternal, true); loadComponentSingleFile(chatOverlay = new ChatOverlay(), overlayContent.Add, true); loadComponentSingleFile(new MessageNotifier(), AddInternal, true); - loadComponentSingleFile(Settings = new SettingsOverlay { GetToolbarHeight = () => ToolbarOffset }, leftFloatingOverlayContent.Add, true); + loadComponentSingleFile(Settings = new SettingsOverlay(), leftFloatingOverlayContent.Add, true); var changelogOverlay = loadComponentSingleFile(new ChangelogOverlay(), overlayContent.Add, true); loadComponentSingleFile(userProfile = new UserProfileOverlay(), overlayContent.Add, true); loadComponentSingleFile(beatmapSetOverlay = new BeatmapSetOverlay(), overlayContent.Add, true); @@ -766,14 +774,12 @@ namespace osu.Game loadComponentSingleFile(new LoginOverlay { - GetToolbarHeight = () => ToolbarOffset, Anchor = Anchor.TopRight, Origin = Anchor.TopRight, }, rightFloatingOverlayContent.Add, true); loadComponentSingleFile(new NowPlayingOverlay { - GetToolbarHeight = () => ToolbarOffset, Anchor = Anchor.TopRight, Origin = Anchor.TopRight, }, rightFloatingOverlayContent.Add, true); @@ -1013,8 +1019,8 @@ namespace osu.Game { base.UpdateAfterChildren(); - ScreenOffsetContainer.Padding = new MarginPadding { Top = ToolbarOffset }; - overlayContent.Padding = new MarginPadding { Top = ToolbarOffset }; + ScreenOffsetContainer.Padding = new MarginPadding { Top = toolbarOffset }; + overlayOffsetContainer.Padding = new MarginPadding { Top = toolbarOffset }; var horizontalOffset = 0f; diff --git a/osu.Game/Overlays/LoginOverlay.cs b/osu.Game/Overlays/LoginOverlay.cs index d0411ba9e7..e7caaa3aca 100644 --- a/osu.Game/Overlays/LoginOverlay.cs +++ b/osu.Game/Overlays/LoginOverlay.cs @@ -10,7 +10,6 @@ using osuTK.Graphics; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Cursor; -using System; namespace osu.Game.Overlays { @@ -20,11 +19,6 @@ namespace osu.Game.Overlays private const float transition_time = 400; - /// - /// Provide a source for the toolbar height. - /// - public Func GetToolbarHeight; - public LoginOverlay() { AutoSizeAxes = Axes.Both; @@ -94,12 +88,5 @@ namespace osu.Game.Overlays settingsSection.Bounding = false; this.FadeOut(transition_time); } - - protected override void UpdateAfterChildren() - { - base.UpdateAfterChildren(); - - Padding = new MarginPadding { Top = GetToolbarHeight?.Invoke() ?? 0 }; - } } } diff --git a/osu.Game/Overlays/NotificationOverlay.cs b/osu.Game/Overlays/NotificationOverlay.cs index e3956089c2..2175e17da9 100644 --- a/osu.Game/Overlays/NotificationOverlay.cs +++ b/osu.Game/Overlays/NotificationOverlay.cs @@ -8,7 +8,6 @@ using osu.Framework.Graphics.Containers; using osu.Game.Overlays.Notifications; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics.Containers; -using System; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Localisation; @@ -30,11 +29,6 @@ namespace osu.Game.Overlays private FlowContainer sections; - /// - /// Provide a source for the toolbar height. - /// - public Func GetToolbarHeight; - [BackgroundDependencyLoader] private void load() { @@ -168,12 +162,5 @@ namespace osu.Game.Overlays updateCounts(); } - - protected override void UpdateAfterChildren() - { - base.UpdateAfterChildren(); - - Padding = new MarginPadding { Top = GetToolbarHeight?.Invoke() ?? 0 }; - } } } diff --git a/osu.Game/Overlays/NowPlayingOverlay.cs b/osu.Game/Overlays/NowPlayingOverlay.cs index f88be91c01..5619d7b38a 100644 --- a/osu.Game/Overlays/NowPlayingOverlay.cs +++ b/osu.Game/Overlays/NowPlayingOverlay.cs @@ -55,11 +55,6 @@ namespace osu.Game.Overlays protected override string PopInSampleName => "UI/now-playing-pop-in"; protected override string PopOutSampleName => "UI/now-playing-pop-out"; - /// - /// Provide a source for the toolbar height. - /// - public Func GetToolbarHeight; - [Resolved] private MusicController musicController { get; set; } @@ -246,7 +241,6 @@ namespace osu.Game.Overlays base.UpdateAfterChildren(); Height = dragContainer.Height; - dragContainer.Padding = new MarginPadding { Top = GetToolbarHeight?.Invoke() ?? 0 }; } protected override void Update() diff --git a/osu.Game/Overlays/SettingsPanel.cs b/osu.Game/Overlays/SettingsPanel.cs index 5589786169..bda4bb5ece 100644 --- a/osu.Game/Overlays/SettingsPanel.cs +++ b/osu.Game/Overlays/SettingsPanel.cs @@ -54,11 +54,6 @@ namespace osu.Game.Overlays protected override string PopInSampleName => "UI/settings-pop-in"; - /// - /// Provide a source for the toolbar height. - /// - public Func GetToolbarHeight; - private readonly bool showSidebar; private LoadingLayer loading; @@ -193,7 +188,6 @@ namespace osu.Game.Overlays base.UpdateAfterChildren(); ContentContainer.Margin = new MarginPadding { Left = Sidebar?.DrawWidth ?? 0 }; - Padding = new MarginPadding { Top = GetToolbarHeight?.Invoke() ?? 0 }; } private const double fade_in_duration = 1000; From 9a5445bdeda54f9d40a48e20a556ebd5dea0d811 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Sat, 28 Aug 2021 22:25:13 -0700 Subject: [PATCH 032/160] Fix overlays closing when clicking any empty area of the toolbar instead --- osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs | 4 ++-- osu.Game/Overlays/Toolbar/Toolbar.cs | 6 +++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs b/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs index b9b098df80..0e635d26c2 100644 --- a/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs +++ b/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs @@ -75,14 +75,14 @@ namespace osu.Game.Graphics.Containers protected override bool OnMouseDown(MouseDownEvent e) { - closeOnMouseUp = !base.ReceivePositionalInputAt(e.ScreenSpaceMousePosition); + closeOnMouseUp = !base.ReceivePositionalInputAt(e.ScreenSpaceMousePosition) && (game?.Toolbar.IsHovered == false); return base.OnMouseDown(e); } protected override void OnMouseUp(MouseUpEvent e) { - if (closeOnMouseUp && !base.ReceivePositionalInputAt(e.ScreenSpaceMousePosition)) + if (closeOnMouseUp && !base.ReceivePositionalInputAt(e.ScreenSpaceMousePosition) && (game?.Toolbar.IsHovered == false)) Hide(); base.OnMouseUp(e); diff --git a/osu.Game/Overlays/Toolbar/Toolbar.cs b/osu.Game/Overlays/Toolbar/Toolbar.cs index 3d88171ba7..918e3b7105 100644 --- a/osu.Game/Overlays/Toolbar/Toolbar.cs +++ b/osu.Game/Overlays/Toolbar/Toolbar.cs @@ -41,6 +41,9 @@ namespace osu.Game.Overlays.Toolbar // Toolbar and its components need keyboard input even when hidden. public override bool PropagateNonPositionalInputSubTree => true; + // IsHovered is used + public override bool HandlePositionalInput => true; + public Toolbar() { RelativeSizeAxes = Axes.X; @@ -140,12 +143,13 @@ namespace osu.Game.Overlays.Toolbar protected override bool OnHover(HoverEvent e) { gradientBackground.FadeIn(transition_time, Easing.OutQuint); - return true; + return base.OnHover(e); } protected override void OnHoverLost(HoverLostEvent e) { gradientBackground.FadeOut(transition_time, Easing.OutQuint); + base.OnHoverLost(e); } } From e374ef163de240c91bff458fd59e10b62e2d91e7 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 29 Aug 2021 15:00:28 +0300 Subject: [PATCH 033/160] Update localisable formattable extensions usages inline with framework change --- osu.Game/Beatmaps/Drawables/StarRatingDisplay.cs | 2 +- osu.Game/Overlays/BeatmapSet/BasicStats.cs | 1 + osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs | 2 +- osu.Game/Overlays/BeatmapSet/Scores/TopScoreUserSection.cs | 1 + osu.Game/Overlays/BeatmapSet/SuccessRate.cs | 2 +- osu.Game/Overlays/Profile/Header/CentreHeaderContainer.cs | 1 + osu.Game/Overlays/Profile/Header/Components/LevelProgressBar.cs | 1 + .../Profile/Header/Components/ProfileHeaderStatisticsButton.cs | 2 +- osu.Game/Overlays/Profile/Header/Components/RankGraph.cs | 1 + osu.Game/Overlays/Profile/Header/DetailHeaderContainer.cs | 1 + osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs | 1 + osu.Game/Overlays/Profile/Sections/CounterPill.cs | 2 +- .../Overlays/Profile/Sections/Historical/ProfileLineChart.cs | 1 + .../Overlays/Profile/Sections/Historical/UserHistoryGraph.cs | 1 + osu.Game/Overlays/Profile/Sections/Kudosu/KudosuInfo.cs | 1 + .../Profile/Sections/Ranks/DrawableProfileWeightedScore.cs | 2 +- osu.Game/Overlays/Rankings/SpotlightSelector.cs | 1 + osu.Game/Overlays/Rankings/Tables/CountriesTable.cs | 2 +- osu.Game/Overlays/Rankings/Tables/PerformanceTable.cs | 2 +- osu.Game/Overlays/Rankings/Tables/RankingsTable.cs | 1 + osu.Game/Overlays/Rankings/Tables/ScoresTable.cs | 2 +- osu.Game/Overlays/Rankings/Tables/UserBasedTable.cs | 1 + osu.Game/Screens/Play/HUD/MatchScoreDisplay.cs | 1 + osu.Game/Screens/Select/Details/UserRatings.cs | 2 +- osu.Game/Utils/FormatUtils.cs | 1 + 25 files changed, 25 insertions(+), 10 deletions(-) diff --git a/osu.Game/Beatmaps/Drawables/StarRatingDisplay.cs b/osu.Game/Beatmaps/Drawables/StarRatingDisplay.cs index c239fda455..dde7680989 100644 --- a/osu.Game/Beatmaps/Drawables/StarRatingDisplay.cs +++ b/osu.Game/Beatmaps/Drawables/StarRatingDisplay.cs @@ -4,12 +4,12 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.UserInterface; -using osu.Framework.Localisation; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Overlays; diff --git a/osu.Game/Overlays/BeatmapSet/BasicStats.cs b/osu.Game/Overlays/BeatmapSet/BasicStats.cs index 2dcb2f1777..5a6cde8229 100644 --- a/osu.Game/Overlays/BeatmapSet/BasicStats.cs +++ b/osu.Game/Overlays/BeatmapSet/BasicStats.cs @@ -4,6 +4,7 @@ using System; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; diff --git a/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs b/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs index b16fb76ec3..60e341d2ac 100644 --- a/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs +++ b/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs @@ -6,12 +6,12 @@ using System.Linq; using osu.Framework; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; -using osu.Framework.Localisation; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; using osu.Game.Graphics; diff --git a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreUserSection.cs b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreUserSection.cs index c934020059..7704fa24df 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreUserSection.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreUserSection.cs @@ -3,6 +3,7 @@ using System; using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Effects; diff --git a/osu.Game/Overlays/BeatmapSet/SuccessRate.cs b/osu.Game/Overlays/BeatmapSet/SuccessRate.cs index b1e9abe3aa..cde4589c98 100644 --- a/osu.Game/Overlays/BeatmapSet/SuccessRate.cs +++ b/osu.Game/Overlays/BeatmapSet/SuccessRate.cs @@ -2,9 +2,9 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Localisation; using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; diff --git a/osu.Game/Overlays/Profile/Header/CentreHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/CentreHeaderContainer.cs index f15fa2705a..180a288729 100644 --- a/osu.Game/Overlays/Profile/Header/CentreHeaderContainer.cs +++ b/osu.Game/Overlays/Profile/Header/CentreHeaderContainer.cs @@ -3,6 +3,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; diff --git a/osu.Game/Overlays/Profile/Header/Components/LevelProgressBar.cs b/osu.Game/Overlays/Profile/Header/Components/LevelProgressBar.cs index 877637be22..2c8c421eba 100644 --- a/osu.Game/Overlays/Profile/Header/Components/LevelProgressBar.cs +++ b/osu.Game/Overlays/Profile/Header/Components/LevelProgressBar.cs @@ -3,6 +3,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; diff --git a/osu.Game/Overlays/Profile/Header/Components/ProfileHeaderStatisticsButton.cs b/osu.Game/Overlays/Profile/Header/Components/ProfileHeaderStatisticsButton.cs index 1235836aac..b098f9f840 100644 --- a/osu.Game/Overlays/Profile/Header/Components/ProfileHeaderStatisticsButton.cs +++ b/osu.Game/Overlays/Profile/Header/Components/ProfileHeaderStatisticsButton.cs @@ -1,10 +1,10 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Framework.Extensions.LocalisationExtensions; 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 osuTK; diff --git a/osu.Game/Overlays/Profile/Header/Components/RankGraph.cs b/osu.Game/Overlays/Profile/Header/Components/RankGraph.cs index 74a25591b4..e8bce404e1 100644 --- a/osu.Game/Overlays/Profile/Header/Components/RankGraph.cs +++ b/osu.Game/Overlays/Profile/Header/Components/RankGraph.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Linq; using Humanizer; using osu.Framework.Bindables; +using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics; using osu.Framework.Localisation; using osu.Game.Graphics; diff --git a/osu.Game/Overlays/Profile/Header/DetailHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/DetailHeaderContainer.cs index 9e52751904..8ca6961950 100644 --- a/osu.Game/Overlays/Profile/Header/DetailHeaderContainer.cs +++ b/osu.Game/Overlays/Profile/Header/DetailHeaderContainer.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; diff --git a/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs index 438f52a2ce..cf930e985c 100644 --- a/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs +++ b/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs @@ -4,6 +4,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; diff --git a/osu.Game/Overlays/Profile/Sections/CounterPill.cs b/osu.Game/Overlays/Profile/Sections/CounterPill.cs index 34211b40b7..bd6cb4d09b 100644 --- a/osu.Game/Overlays/Profile/Sections/CounterPill.cs +++ b/osu.Game/Overlays/Profile/Sections/CounterPill.cs @@ -8,7 +8,7 @@ using osu.Game.Graphics; using osu.Framework.Bindables; using osu.Game.Graphics.Sprites; using osu.Framework.Allocation; -using osu.Framework.Localisation; +using osu.Framework.Extensions.LocalisationExtensions; namespace osu.Game.Overlays.Profile.Sections { diff --git a/osu.Game/Overlays/Profile/Sections/Historical/ProfileLineChart.cs b/osu.Game/Overlays/Profile/Sections/Historical/ProfileLineChart.cs index 449b1da35d..a75235359a 100644 --- a/osu.Game/Overlays/Profile/Sections/Historical/ProfileLineChart.cs +++ b/osu.Game/Overlays/Profile/Sections/Historical/ProfileLineChart.cs @@ -9,6 +9,7 @@ using System.Linq; using osu.Game.Graphics.Sprites; using osu.Framework.Utils; using osu.Framework.Allocation; +using osu.Framework.Extensions.LocalisationExtensions; using osu.Game.Graphics; using osu.Framework.Graphics.Shapes; using osuTK; diff --git a/osu.Game/Overlays/Profile/Sections/Historical/UserHistoryGraph.cs b/osu.Game/Overlays/Profile/Sections/Historical/UserHistoryGraph.cs index ac94f0fc87..d25c53b5ec 100644 --- a/osu.Game/Overlays/Profile/Sections/Historical/UserHistoryGraph.cs +++ b/osu.Game/Overlays/Profile/Sections/Historical/UserHistoryGraph.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Linq; using JetBrains.Annotations; +using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Localisation; using static osu.Game.Users.User; diff --git a/osu.Game/Overlays/Profile/Sections/Kudosu/KudosuInfo.cs b/osu.Game/Overlays/Profile/Sections/Kudosu/KudosuInfo.cs index eb55a0a78d..762716efab 100644 --- a/osu.Game/Overlays/Profile/Sections/Kudosu/KudosuInfo.cs +++ b/osu.Game/Overlays/Profile/Sections/Kudosu/KudosuInfo.cs @@ -12,6 +12,7 @@ using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Users; using osu.Framework.Allocation; +using osu.Framework.Extensions.LocalisationExtensions; using osu.Game.Resources.Localisation.Web; using osu.Framework.Localisation; diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileWeightedScore.cs b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileWeightedScore.cs index 4e4a665a60..f77464ecb9 100644 --- a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileWeightedScore.cs +++ b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileWeightedScore.cs @@ -1,9 +1,9 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Localisation; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Resources.Localisation.Web; diff --git a/osu.Game/Overlays/Rankings/SpotlightSelector.cs b/osu.Game/Overlays/Rankings/SpotlightSelector.cs index 5309778a47..0f071883ca 100644 --- a/osu.Game/Overlays/Rankings/SpotlightSelector.cs +++ b/osu.Game/Overlays/Rankings/SpotlightSelector.cs @@ -16,6 +16,7 @@ using System.Collections.Generic; using osu.Framework.Graphics.UserInterface; using osu.Game.Online.API.Requests; using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Localisation; using osu.Game.Resources.Localisation.Web; diff --git a/osu.Game/Overlays/Rankings/Tables/CountriesTable.cs b/osu.Game/Overlays/Rankings/Tables/CountriesTable.cs index 85a317728f..a908380e95 100644 --- a/osu.Game/Overlays/Rankings/Tables/CountriesTable.cs +++ b/osu.Game/Overlays/Rankings/Tables/CountriesTable.cs @@ -9,8 +9,8 @@ using osu.Game.Graphics; using osu.Game.Graphics.Containers; using System.Collections.Generic; using osu.Framework.Allocation; +using osu.Framework.Extensions.LocalisationExtensions; using osu.Game.Resources.Localisation.Web; -using osu.Framework.Localisation; namespace osu.Game.Overlays.Rankings.Tables { diff --git a/osu.Game/Overlays/Rankings/Tables/PerformanceTable.cs b/osu.Game/Overlays/Rankings/Tables/PerformanceTable.cs index 6facf1e7a2..215cc95198 100644 --- a/osu.Game/Overlays/Rankings/Tables/PerformanceTable.cs +++ b/osu.Game/Overlays/Rankings/Tables/PerformanceTable.cs @@ -2,9 +2,9 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Localisation; using osu.Game.Resources.Localisation.Web; using osu.Game.Users; diff --git a/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs b/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs index bc8eac16a9..6e6230f958 100644 --- a/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs +++ b/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs @@ -10,6 +10,7 @@ using osu.Framework.Extensions; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Framework.Extensions.LocalisationExtensions; using osu.Game.Users; using osu.Game.Users.Drawables; using osuTK; diff --git a/osu.Game/Overlays/Rankings/Tables/ScoresTable.cs b/osu.Game/Overlays/Rankings/Tables/ScoresTable.cs index b6bb66e2c8..934da4501e 100644 --- a/osu.Game/Overlays/Rankings/Tables/ScoresTable.cs +++ b/osu.Game/Overlays/Rankings/Tables/ScoresTable.cs @@ -2,9 +2,9 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Localisation; using osu.Game.Resources.Localisation.Web; using osu.Game.Users; diff --git a/osu.Game/Overlays/Rankings/Tables/UserBasedTable.cs b/osu.Game/Overlays/Rankings/Tables/UserBasedTable.cs index b96ab556df..cc2ef55a2b 100644 --- a/osu.Game/Overlays/Rankings/Tables/UserBasedTable.cs +++ b/osu.Game/Overlays/Rankings/Tables/UserBasedTable.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; +using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics; diff --git a/osu.Game/Screens/Play/HUD/MatchScoreDisplay.cs b/osu.Game/Screens/Play/HUD/MatchScoreDisplay.cs index 68e3f0df7d..d04e60a2ab 100644 --- a/osu.Game/Screens/Play/HUD/MatchScoreDisplay.cs +++ b/osu.Game/Screens/Play/HUD/MatchScoreDisplay.cs @@ -4,6 +4,7 @@ using System; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; diff --git a/osu.Game/Screens/Select/Details/UserRatings.cs b/osu.Game/Screens/Select/Details/UserRatings.cs index a7f28b932a..eabc476db9 100644 --- a/osu.Game/Screens/Select/Details/UserRatings.cs +++ b/osu.Game/Screens/Select/Details/UserRatings.cs @@ -8,8 +8,8 @@ using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using System.Linq; +using osu.Framework.Extensions.LocalisationExtensions; using osu.Game.Beatmaps; -using osu.Framework.Localisation; using osu.Game.Resources.Localisation.Web; namespace osu.Game.Screens.Select.Details diff --git a/osu.Game/Utils/FormatUtils.cs b/osu.Game/Utils/FormatUtils.cs index e763558647..d14dbb49f3 100644 --- a/osu.Game/Utils/FormatUtils.cs +++ b/osu.Game/Utils/FormatUtils.cs @@ -3,6 +3,7 @@ using System; using Humanizer; +using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Localisation; namespace osu.Game.Utils From 8f3416d8534405210acb21b939ef22c011e674c9 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 29 Aug 2021 16:03:37 +0300 Subject: [PATCH 034/160] Assert PP not null when `showPerformancePoints` is true --- osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs index a154016824..8fe1d35b62 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs @@ -4,6 +4,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Extensions; @@ -192,6 +193,8 @@ namespace osu.Game.Overlays.BeatmapSet.Scores if (showPerformancePoints) { + Debug.Assert(score.PP != null); + content.Add(new OsuSpriteText { Text = score.PP.ToLocalisableString(@"N0"), From 6aaef7b0be3985b30f142f1e8562a744bd4373cc Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 29 Aug 2021 17:19:13 +0300 Subject: [PATCH 035/160] Handle null PP during score set in `TopScoreStatisticsSection` Supersedes #14562 Closes #14541 --- .../Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs index 23069eccdf..883e83ce6e 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs @@ -116,7 +116,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores maxComboColumn.Text = value.MaxCombo.ToLocalisableString(@"0\x"); ppColumn.Alpha = value.Beatmap?.Status.GrantsPerformancePoints() == true ? 1 : 0; - ppColumn.Text = value.PP.ToLocalisableString(@"N0"); + ppColumn.Text = value.PP?.ToLocalisableString(@"N0"); statisticsColumns.ChildrenEnumerable = value.GetStatisticsForDisplay().Select(createStatisticsColumn); modsColumn.Mods = value.Mods; From 6dc11543ad527683bb8691e609ec4e486191f138 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 29 Aug 2021 17:20:33 +0300 Subject: [PATCH 036/160] Handle (null?) PP in `PerformanceTable` --- osu.Game/Overlays/Rankings/Tables/PerformanceTable.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Rankings/Tables/PerformanceTable.cs b/osu.Game/Overlays/Rankings/Tables/PerformanceTable.cs index 215cc95198..17c17b1f1a 100644 --- a/osu.Game/Overlays/Rankings/Tables/PerformanceTable.cs +++ b/osu.Game/Overlays/Rankings/Tables/PerformanceTable.cs @@ -24,7 +24,7 @@ namespace osu.Game.Overlays.Rankings.Tables protected override Drawable[] CreateUniqueContent(UserStatistics item) => new Drawable[] { - new RowText { Text = item.PP.ToLocalisableString(@"N0"), } + new RowText { Text = item.PP?.ToLocalisableString(@"N0"), } }; } } From 90c313e2ad4d67d827f5617feacba4b1a693fb12 Mon Sep 17 00:00:00 2001 From: Davran Dilshat Date: Sun, 29 Aug 2021 19:19:55 +0100 Subject: [PATCH 037/160] add methods to get a user from their username --- osu.Game/Online/API/Requests/GetUserRequest.cs | 16 +++++++++++++--- osu.Game/OsuGame.cs | 9 +++++++-- osu.Game/Overlays/UserProfileOverlay.cs | 4 +++- 3 files changed, 23 insertions(+), 6 deletions(-) diff --git a/osu.Game/Online/API/Requests/GetUserRequest.cs b/osu.Game/Online/API/Requests/GetUserRequest.cs index 42aad6f9eb..48041cd40c 100644 --- a/osu.Game/Online/API/Requests/GetUserRequest.cs +++ b/osu.Game/Online/API/Requests/GetUserRequest.cs @@ -8,15 +8,25 @@ namespace osu.Game.Online.API.Requests { public class GetUserRequest : APIRequest { - private readonly long? userId; + private readonly string userIdentifier; public readonly RulesetInfo Ruleset; + public GetUserRequest() + { + } + public GetUserRequest(long? userId = null, RulesetInfo ruleset = null) { - this.userId = userId; + this.userIdentifier = userId.ToString(); Ruleset = ruleset; } - protected override string Target => userId.HasValue ? $@"users/{userId}/{Ruleset?.ShortName}" : $@"me/{Ruleset?.ShortName}"; + public GetUserRequest(string username = null, RulesetInfo ruleset = null) + { + this.userIdentifier = username; + Ruleset = ruleset; + } + + protected override string Target => (userIdentifier != null) ? $@"users/{userIdentifier}/{Ruleset?.ShortName}" : $@"me/{Ruleset?.ShortName}"; } } diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 4d952c39c6..26fa1d5a4c 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -329,8 +329,7 @@ namespace osu.Game break; case LinkAction.OpenUserProfile: - if (int.TryParse(link.Argument, out int userId)) - ShowUser(userId); + ShowUser(link.Argument); break; case LinkAction.OpenWiki: @@ -378,6 +377,12 @@ namespace osu.Game /// The user to display. public void ShowUser(int userId) => waitForReady(() => userProfile, _ => userProfile.ShowUser(userId)); + /// + /// Show a user's profile as an overlay. + /// + /// The user to display. + public void ShowUser(string username) => waitForReady(() => userProfile, _ => userProfile.ShowUser(username)); + /// /// Show a beatmap's set as an overlay, displaying the given beatmap. /// diff --git a/osu.Game/Overlays/UserProfileOverlay.cs b/osu.Game/Overlays/UserProfileOverlay.cs index 299a14b250..6e74acc96a 100644 --- a/osu.Game/Overlays/UserProfileOverlay.cs +++ b/osu.Game/Overlays/UserProfileOverlay.cs @@ -40,6 +40,8 @@ namespace osu.Game.Overlays public void ShowUser(int userId) => ShowUser(new User { Id = userId }); + public void ShowUser(string username) => ShowUser(new User { Username = username }); + public void ShowUser(User user, bool fetchOnline = true) { if (user == User.SYSTEM_USER) @@ -116,7 +118,7 @@ namespace osu.Game.Overlays if (fetchOnline) { - userReq = new GetUserRequest(user.Id); + userReq = user.Username != null ? new GetUserRequest(user.Username) : new GetUserRequest(user.Id); userReq.Success += userLoadComplete; API.Queue(userReq); } From 7bb2269eba507366da5f6cc85c7cee48421ff8a2 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Sun, 29 Aug 2021 22:27:56 -0700 Subject: [PATCH 038/160] Add overlay closing behavior test --- .../Navigation/TestSceneScreenNavigation.cs | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs index 3c65f46c79..a112534837 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs @@ -323,6 +323,69 @@ namespace osu.Game.Tests.Visual.Navigation AddWaitStep("wait two frames", 2); } + [Test] + public void TestOverlayClosing() + { + // use now playing overlay for "overlay -> background" drag case + // since most overlays use a scroll container that absorbs on mouse down + NowPlayingOverlay nowPlayingOverlay = null; + + AddStep("enter menu", () => InputManager.Key(Key.Enter)); + + AddStep("get and press now playing hotkey", () => + { + nowPlayingOverlay = Game.ChildrenOfType().Single(); + InputManager.Key(Key.F6); + }); + + // drag tests + + // background -> toolbar + AddStep("move cursor to background", () => InputManager.MoveMouseTo(Game.ScreenSpaceDrawQuad.BottomRight)); + AddStep("press left mouse button", () => InputManager.PressButton(MouseButton.Left)); + AddStep("move cursor to toolbar", () => InputManager.MoveMouseTo(Game.Toolbar.ScreenSpaceDrawQuad.Centre)); + AddStep("release left mouse button", () => InputManager.ReleaseButton(MouseButton.Left)); + AddAssert("now playing is still visible", () => nowPlayingOverlay.State.Value == Visibility.Visible); + + // toolbar -> background + AddStep("press left mouse button", () => InputManager.PressButton(MouseButton.Left)); + AddStep("move cursor to background", () => InputManager.MoveMouseTo(Game.ScreenSpaceDrawQuad.BottomRight)); + AddStep("release left mouse button", () => InputManager.ReleaseButton(MouseButton.Left)); + AddAssert("now playing is still visible", () => nowPlayingOverlay.State.Value == Visibility.Visible); + + // background -> overlay + AddStep("press left mouse button", () => InputManager.PressButton(MouseButton.Left)); + AddStep("move cursor to now playing overlay", () => InputManager.MoveMouseTo(nowPlayingOverlay.ScreenSpaceDrawQuad.Centre)); + AddStep("release left mouse button", () => InputManager.ReleaseButton(MouseButton.Left)); + AddAssert("now playing is still visible", () => nowPlayingOverlay.State.Value == Visibility.Visible); + + // overlay -> background + AddStep("press left mouse button", () => InputManager.PressButton(MouseButton.Left)); + AddStep("move cursor to background", () => InputManager.MoveMouseTo(Game.ScreenSpaceDrawQuad.BottomRight)); + AddStep("release left mouse button", () => InputManager.ReleaseButton(MouseButton.Left)); + AddAssert("now playing is still visible", () => nowPlayingOverlay.State.Value == Visibility.Visible); + + // background -> background + AddStep("press left mouse button", () => InputManager.PressButton(MouseButton.Left)); + AddStep("move cursor to left", () => InputManager.MoveMouseTo(Game.ScreenSpaceDrawQuad.BottomLeft)); + AddStep("release left mouse button", () => InputManager.ReleaseButton(MouseButton.Left)); + AddAssert("now playing is hidden", () => nowPlayingOverlay.State.Value == Visibility.Hidden); + + AddStep("press now playing hotkey", () => InputManager.Key(Key.F6)); + + // click tests + + // toolbar + AddStep("move cursor to toolbar", () => InputManager.MoveMouseTo(Game.Toolbar.ScreenSpaceDrawQuad.Centre)); + AddStep("click left mouse button", () => InputManager.Click(MouseButton.Left)); + AddAssert("now playing is still visible", () => nowPlayingOverlay.State.Value == Visibility.Visible); + + // background + AddStep("move cursor to background", () => InputManager.MoveMouseTo(Game.ScreenSpaceDrawQuad.BottomRight)); + AddStep("click left mouse button", () => InputManager.Click(MouseButton.Left)); + AddAssert("now playing is hidden", () => nowPlayingOverlay.State.Value == Visibility.Hidden); + } + private void pushEscape() => AddStep("Press escape", () => InputManager.Key(Key.Escape)); From ee49305cad0f0edc6c7a7c0b5c65d8d63b95a3b8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 30 Aug 2021 14:40:25 +0900 Subject: [PATCH 039/160] Move taiko legacy speed multiplier to `osu.Game` project Allows it to be used in local case in `LegacyBeatmapEncoder`. --- .../Beatmaps/TaikoBeatmapConverter.cs | 10 ++-------- osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs | 4 ++-- osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs | 8 +++++++- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs index 90c99316b1..77f058fad9 100644 --- a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs +++ b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs @@ -18,12 +18,6 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps { internal class TaikoBeatmapConverter : BeatmapConverter { - /// - /// osu! is generally slower than taiko, so a factor is added to increase - /// speed. This must be used everywhere slider length or beat length is used. - /// - public const float LEGACY_VELOCITY_MULTIPLIER = 1.4f; - /// /// Because swells are easier in taiko than spinners are in osu!, /// legacy taiko multiplies a factor when converting the number of required hits. @@ -55,7 +49,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps // Rewrite the beatmap info to add the slider velocity multiplier original.BeatmapInfo = original.BeatmapInfo.Clone(); original.BeatmapInfo.BaseDifficulty = original.BeatmapInfo.BaseDifficulty.Clone(); - original.BeatmapInfo.BaseDifficulty.SliderMultiplier *= LEGACY_VELOCITY_MULTIPLIER; + original.BeatmapInfo.BaseDifficulty.SliderMultiplier *= LegacyBeatmapEncoder.LEGACY_TAIKO_VELOCITY_MULTIPLIER; Beatmap converted = base.ConvertBeatmap(original, cancellationToken); @@ -155,7 +149,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps // The true distance, accounting for any repeats. This ends up being the drum roll distance later int spans = (obj as IHasRepeats)?.SpanCount() ?? 1; - double distance = distanceData.Distance * spans * LEGACY_VELOCITY_MULTIPLIER; + double distance = distanceData.Distance * spans * LegacyBeatmapEncoder.LEGACY_TAIKO_VELOCITY_MULTIPLIER; TimingControlPoint timingPoint = beatmap.ControlPointInfo.TimingPointAt(obj.StartTime); DifficultyControlPoint difficultyPoint = beatmap.ControlPointInfo.DifficultyPointAt(obj.StartTime); diff --git a/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs b/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs index c0377c67a5..b0634295d0 100644 --- a/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs +++ b/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs @@ -6,10 +6,10 @@ using System; using System.Threading; 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.Scoring; -using osu.Game.Rulesets.Taiko.Beatmaps; using osu.Game.Rulesets.Taiko.Judgements; using osuTK; @@ -120,7 +120,7 @@ namespace osu.Game.Rulesets.Taiko.Objects double IHasDistance.Distance => Duration * Velocity; SliderPath IHasPath.Path - => new SliderPath(PathType.Linear, new[] { Vector2.Zero, new Vector2(1) }, ((IHasDistance)this).Distance / TaikoBeatmapConverter.LEGACY_VELOCITY_MULTIPLIER); + => new SliderPath(PathType.Linear, new[] { Vector2.Zero, new Vector2(1) }, ((IHasDistance)this).Distance / LegacyBeatmapEncoder.LEGACY_TAIKO_VELOCITY_MULTIPLIER); #endregion } diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs index 246dc991d5..1595ba5b8e 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs @@ -24,6 +24,12 @@ namespace osu.Game.Beatmaps.Formats { public const int LATEST_VERSION = 128; + /// + /// osu! is generally slower than taiko, so a factor is added to increase + /// speed. This must be used everywhere slider length or beat length is used. + /// + public const float LEGACY_TAIKO_VELOCITY_MULTIPLIER = 1.4f; + private readonly IBeatmap beatmap; [CanBeNull] @@ -142,7 +148,7 @@ namespace osu.Game.Beatmaps.Formats // Taiko adjusts the slider multiplier (see: TaikoBeatmapConverter.LEGACY_VELOCITY_MULTIPLIER) writer.WriteLine(beatmap.BeatmapInfo.RulesetID == 1 - ? FormattableString.Invariant($"SliderMultiplier: {beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier / 1.4f}") + ? FormattableString.Invariant($"SliderMultiplier: {beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier / LEGACY_TAIKO_VELOCITY_MULTIPLIER}") : FormattableString.Invariant($"SliderMultiplier: {beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier}")); writer.WriteLine(FormattableString.Invariant($"SliderTickRate: {beatmap.BeatmapInfo.BaseDifficulty.SliderTickRate}")); From 4adfe9a6dc3a4b05d67c4c626f2e7b2009d9b785 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 30 Aug 2021 15:30:04 +0900 Subject: [PATCH 040/160] Add test coverage of double-convert stability --- .../Beatmaps/Formats/LegacyBeatmapEncoderTest.cs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs index 855a75117d..96986f06d5 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs @@ -49,6 +49,22 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.IsTrue(areComboColoursEqual(decodedAfterEncode.beatmapSkin.Configuration, decoded.beatmapSkin.Configuration)); } + [TestCaseSource(nameof(allBeatmaps))] + public void TestEncodeDecodeStabilityDoubleConvert(string name) + { + var decoded = decodeFromLegacy(beatmaps_resource_store.GetStream(name), name); + var decodedAfterEncode = decodeFromLegacy(encodeToLegacy(decoded), name); + + // run an extra convert. this is expected to be stable. + decodedAfterEncode.beatmap = convert(decodedAfterEncode.beatmap); + + sort(decoded.beatmap); + sort(decodedAfterEncode.beatmap); + + Assert.That(decodedAfterEncode.beatmap.Serialize(), Is.EqualTo(decoded.beatmap.Serialize())); + Assert.IsTrue(areComboColoursEqual(decodedAfterEncode.beatmapSkin.Configuration, decoded.beatmapSkin.Configuration)); + } + [Test] public void TestEncodeMultiSegmentSliderWithFloatingPointError() { From 6a6dac609caaad6e60d94a49224efb532e1111eb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 30 Aug 2021 15:30:17 +0900 Subject: [PATCH 041/160] Fix instability of taiko double conversion Until now, the taiko speed multiplier was potentially applied more than once if conversion was run multiple times. --- .../Beatmaps/TaikoBeatmapConverter.cs | 19 +++++++++++++++---- osu.Game/Beatmaps/BeatmapDifficulty.cs | 18 +++++++++++++++++- 2 files changed, 32 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs index 77f058fad9..9b73e644c5 100644 --- a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs +++ b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs @@ -46,10 +46,12 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps protected override Beatmap ConvertBeatmap(IBeatmap original, CancellationToken cancellationToken) { - // Rewrite the beatmap info to add the slider velocity multiplier - original.BeatmapInfo = original.BeatmapInfo.Clone(); - original.BeatmapInfo.BaseDifficulty = original.BeatmapInfo.BaseDifficulty.Clone(); - original.BeatmapInfo.BaseDifficulty.SliderMultiplier *= LegacyBeatmapEncoder.LEGACY_TAIKO_VELOCITY_MULTIPLIER; + if (!(original.BeatmapInfo.BaseDifficulty is TaikoMutliplierAppliedDifficulty)) + { + // Rewrite the beatmap info to add the slider velocity multiplier + original.BeatmapInfo = original.BeatmapInfo.Clone(); + original.BeatmapInfo.BaseDifficulty = new TaikoMutliplierAppliedDifficulty(original.BeatmapInfo.BaseDifficulty); + } Beatmap converted = base.ConvertBeatmap(original, cancellationToken); @@ -188,5 +190,14 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps } protected override Beatmap CreateBeatmap() => new TaikoBeatmap(); + + private class TaikoMutliplierAppliedDifficulty : BeatmapDifficulty + { + public TaikoMutliplierAppliedDifficulty(BeatmapDifficulty difficulty) + { + difficulty.CopyTo(this); + SliderMultiplier *= LegacyBeatmapEncoder.LEGACY_TAIKO_VELOCITY_MULTIPLIER; + } + } } } diff --git a/osu.Game/Beatmaps/BeatmapDifficulty.cs b/osu.Game/Beatmaps/BeatmapDifficulty.cs index c56fec67aa..1844b193f2 100644 --- a/osu.Game/Beatmaps/BeatmapDifficulty.cs +++ b/osu.Game/Beatmaps/BeatmapDifficulty.cs @@ -32,7 +32,23 @@ namespace osu.Game.Beatmaps /// /// Returns a shallow-clone of this . /// - public BeatmapDifficulty Clone() => (BeatmapDifficulty)MemberwiseClone(); + public BeatmapDifficulty Clone() + { + var diff = new BeatmapDifficulty(); + CopyTo(diff); + return diff; + } + + public void CopyTo(BeatmapDifficulty difficulty) + { + difficulty.ApproachRate = ApproachRate; + difficulty.DrainRate = DrainRate; + difficulty.CircleSize = CircleSize; + difficulty.OverallDifficulty = OverallDifficulty; + + difficulty.SliderMultiplier = SliderMultiplier; + difficulty.SliderTickRate = SliderTickRate; + } /// /// Maps a difficulty value [0, 10] to a two-piece linear range of values. From 58a052ea1f7bf15a980870d48b7176b9e5c026d8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 30 Aug 2021 16:00:07 +0900 Subject: [PATCH 042/160] 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 f18400bf2f..8a9bf1b9cd 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 a89d57cd1f..b4d4aa3070 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 bb4700a081..29e9b9fe20 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -93,7 +93,7 @@ - + From fa2bf421886fe70c14706ae2d41831afcf7e49fb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 30 Aug 2021 16:04:54 +0900 Subject: [PATCH 043/160] Update tooltip implementations --- osu.Game/Beatmaps/Drawables/DifficultyIcon.cs | 6 ++---- .../Graphics/Cursor/OsuTooltipContainer.cs | 9 ++------- osu.Game/Graphics/DateTooltip.cs | 8 ++------ osu.Game/Overlays/Mods/ModButtonTooltip.cs | 19 ++++--------------- .../Profile/Header/Components/RankGraph.cs | 5 ++--- .../Sections/Historical/UserHistoryGraph.cs | 5 ++--- osu.Game/Overlays/Profile/UserGraph.cs | 2 +- 7 files changed, 15 insertions(+), 39 deletions(-) diff --git a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs index 3210ef0112..199f719893 100644 --- a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs +++ b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs @@ -259,10 +259,10 @@ namespace osu.Game.Beatmaps.Drawables private readonly IBindable starDifficulty = new Bindable(); - public bool SetContent(object content) + public void SetContent(object content) { if (!(content is DifficultyIconTooltipContent iconContent)) - return false; + return; difficultyName.Text = iconContent.Beatmap.Version; @@ -273,8 +273,6 @@ namespace osu.Game.Beatmaps.Drawables starRating.Text = $"{difficulty.NewValue.Stars:0.##}"; difficultyFlow.Colour = colours.ForStarDifficulty(difficulty.NewValue.Stars); }, true); - - return true; } public void Move(Vector2 pos) => Position = pos; diff --git a/osu.Game/Graphics/Cursor/OsuTooltipContainer.cs b/osu.Game/Graphics/Cursor/OsuTooltipContainer.cs index 81dca99ddd..35d7b4e795 100644 --- a/osu.Game/Graphics/Cursor/OsuTooltipContainer.cs +++ b/osu.Game/Graphics/Cursor/OsuTooltipContainer.cs @@ -31,12 +31,9 @@ namespace osu.Game.Graphics.Cursor private readonly OsuSpriteText text; private bool instantMovement = true; - public override bool SetContent(object content) + public override void SetContent(LocalisableString contentString) { - if (!(content is LocalisableString contentString)) - return false; - - if (contentString == text.Text) return true; + if (contentString == text.Text) return; text.Text = contentString; @@ -47,8 +44,6 @@ namespace osu.Game.Graphics.Cursor } else AutoSizeDuration = 0; - - return true; } public OsuTooltip() diff --git a/osu.Game/Graphics/DateTooltip.cs b/osu.Game/Graphics/DateTooltip.cs index 67fcab43f7..3094f9cc2b 100644 --- a/osu.Game/Graphics/DateTooltip.cs +++ b/osu.Game/Graphics/DateTooltip.cs @@ -12,7 +12,7 @@ using osuTK; namespace osu.Game.Graphics { - public class DateTooltip : VisibilityContainer, ITooltip + public class DateTooltip : VisibilityContainer, ITooltip { private readonly OsuSpriteText dateText, timeText; private readonly Box background; @@ -63,14 +63,10 @@ namespace osu.Game.Graphics protected override void PopIn() => this.FadeIn(200, Easing.OutQuint); protected override void PopOut() => this.FadeOut(200, Easing.OutQuint); - public bool SetContent(object content) + public void SetContent(DateTimeOffset date) { - if (!(content is DateTimeOffset date)) - return false; - dateText.Text = $"{date:d MMMM yyyy} "; timeText.Text = $"{date:HH:mm:ss \"UTC\"z}"; - return true; } public void Move(Vector2 pos) => Position = pos; diff --git a/osu.Game/Overlays/Mods/ModButtonTooltip.cs b/osu.Game/Overlays/Mods/ModButtonTooltip.cs index 666ed07e28..89fcd61d76 100644 --- a/osu.Game/Overlays/Mods/ModButtonTooltip.cs +++ b/osu.Game/Overlays/Mods/ModButtonTooltip.cs @@ -18,7 +18,7 @@ using osuTK; namespace osu.Game.Overlays.Mods { - public class ModButtonTooltip : VisibilityContainer, ITooltip + public class ModButtonTooltip : VisibilityContainer, ITooltip { private readonly OsuSpriteText descriptionText; private readonly Box background; @@ -82,12 +82,9 @@ namespace osu.Game.Overlays.Mods private Mod lastMod; - public bool SetContent(object content) + public void SetContent(Mod mod) { - if (!(content is Mod mod)) - return false; - - if (mod.Equals(lastMod)) return true; + if (mod.Equals(lastMod)) return; lastMod = mod; @@ -99,15 +96,7 @@ namespace osu.Game.Overlays.Mods incompatibleMods.Value = allMods.Where(m => m.GetType() != mod.GetType() && incompatibleTypes.Any(t => t.IsInstanceOfType(m))).ToList(); - if (!incompatibleMods.Value.Any()) - { - incompatibleText.Text = "Compatible with all mods"; - return true; - } - - incompatibleText.Text = "Incompatible with:"; - - return true; + incompatibleText.Text = !incompatibleMods.Value.Any() ? "Compatible with all mods" : "Incompatible with:"; } public void Move(Vector2 pos) => Position = pos; diff --git a/osu.Game/Overlays/Profile/Header/Components/RankGraph.cs b/osu.Game/Overlays/Profile/Header/Components/RankGraph.cs index e8bce404e1..7ba8ae7c80 100644 --- a/osu.Game/Overlays/Profile/Header/Components/RankGraph.cs +++ b/osu.Game/Overlays/Profile/Header/Components/RankGraph.cs @@ -81,14 +81,13 @@ namespace osu.Game.Overlays.Profile.Header.Components { } - public override bool SetContent(object content) + public override void SetContent(object content) { if (!(content is TooltipDisplayContent info)) - return false; + return; Counter.Text = info.Rank; BottomText.Text = info.Time; - return true; } } diff --git a/osu.Game/Overlays/Profile/Sections/Historical/UserHistoryGraph.cs b/osu.Game/Overlays/Profile/Sections/Historical/UserHistoryGraph.cs index d25c53b5ec..85287d2325 100644 --- a/osu.Game/Overlays/Profile/Sections/Historical/UserHistoryGraph.cs +++ b/osu.Game/Overlays/Profile/Sections/Historical/UserHistoryGraph.cs @@ -50,14 +50,13 @@ namespace osu.Game.Overlays.Profile.Sections.Historical this.tooltipCounterName = tooltipCounterName; } - public override bool SetContent(object content) + public override void SetContent(object content) { if (!(content is TooltipDisplayContent info) || info.Name != tooltipCounterName) - return false; + return; Counter.Text = info.Count; BottomText.Text = info.Date; - return true; } } diff --git a/osu.Game/Overlays/Profile/UserGraph.cs b/osu.Game/Overlays/Profile/UserGraph.cs index b7a08b6c5e..b88cc32ff7 100644 --- a/osu.Game/Overlays/Profile/UserGraph.cs +++ b/osu.Game/Overlays/Profile/UserGraph.cs @@ -268,7 +268,7 @@ namespace osu.Game.Overlays.Profile background.Colour = colours.Gray1; } - public abstract bool SetContent(object content); + public abstract void SetContent(object content); private bool instantMove = true; From 678386f5c4672bda1c762ccbe26673259808e928 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 30 Aug 2021 16:05:56 +0900 Subject: [PATCH 044/160] Fix missed null coalesce --- osu.Game/Overlays/BeatmapSet/Scores/TopScoreUserSection.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreUserSection.cs b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreUserSection.cs index 7704fa24df..5c3906cb39 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreUserSection.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreUserSection.cs @@ -128,7 +128,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores public int? ScorePosition { - set => rankText.Text = value == null ? (LocalisableString)"-" : value.ToLocalisableString(@"\##"); + set => rankText.Text = value?.ToLocalisableString(@"\##") ?? (LocalisableString)"-"; } /// From da7a871afa1edc784fe111e4f500baaa9d1f740e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 30 Aug 2021 16:27:24 +0900 Subject: [PATCH 045/160] Update inline comment to point to new variable location Co-authored-by: PercyDan <50285552+PercyDan54@users.noreply.github.com> --- osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs index 1595ba5b8e..fb6806a13d 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs @@ -146,7 +146,7 @@ namespace osu.Game.Beatmaps.Formats writer.WriteLine(FormattableString.Invariant($"OverallDifficulty: {beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty}")); writer.WriteLine(FormattableString.Invariant($"ApproachRate: {beatmap.BeatmapInfo.BaseDifficulty.ApproachRate}")); - // Taiko adjusts the slider multiplier (see: TaikoBeatmapConverter.LEGACY_VELOCITY_MULTIPLIER) + // Taiko adjusts the slider multiplier (see: LEGACY_TAIKO_VELOCITY_MULTIPLIER) writer.WriteLine(beatmap.BeatmapInfo.RulesetID == 1 ? FormattableString.Invariant($"SliderMultiplier: {beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier / LEGACY_TAIKO_VELOCITY_MULTIPLIER}") : FormattableString.Invariant($"SliderMultiplier: {beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier}")); From 7257aae7f26bd4c322d04e5aa739207773c5bfbd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 25 Aug 2021 18:00:57 +0900 Subject: [PATCH 046/160] Move samples to `LegacyControlPointInfo` --- .../Formats/LegacyBeatmapDecoderTest.cs | 5 +- .../NonVisual/ControlPointInfoTest.cs | 4 +- .../ControlPoints/ControlPointInfo.cs | 119 +++++++++++------- .../Beatmaps/Formats/LegacyBeatmapDecoder.cs | 22 ++++ .../Beatmaps/Formats/LegacyBeatmapEncoder.cs | 4 +- osu.Game/Rulesets/Objects/HitObject.cs | 7 +- osu.Game/Screens/Edit/Timing/SampleSection.cs | 12 +- 7 files changed, 110 insertions(+), 63 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index 12633ee8c9..24410c886f 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -10,6 +10,7 @@ using osu.Game.Tests.Resources; using System.Linq; using osu.Game.Audio; using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Objects.Types; using osu.Game.Beatmaps.Formats; using osu.Game.Beatmaps.Timing; @@ -166,7 +167,7 @@ namespace osu.Game.Tests.Beatmaps.Formats using (var stream = new LineBufferedReader(resStream)) { var beatmap = decoder.Decode(stream); - var controlPoints = beatmap.ControlPointInfo; + var controlPoints = (LegacyControlPointInfo)beatmap.ControlPointInfo; Assert.AreEqual(4, controlPoints.TimingPoints.Count); Assert.AreEqual(5, controlPoints.DifficultyPoints.Count); @@ -240,7 +241,7 @@ namespace osu.Game.Tests.Beatmaps.Formats using (var resStream = TestResources.OpenResource("overlapping-control-points.osu")) using (var stream = new LineBufferedReader(resStream)) { - var controlPoints = decoder.Decode(stream).ControlPointInfo; + var controlPoints = (LegacyControlPointInfo)decoder.Decode(stream).ControlPointInfo; Assert.That(controlPoints.TimingPoints.Count, Is.EqualTo(4)); Assert.That(controlPoints.DifficultyPoints.Count, Is.EqualTo(3)); diff --git a/osu.Game.Tests/NonVisual/ControlPointInfoTest.cs b/osu.Game.Tests/NonVisual/ControlPointInfoTest.cs index 240ae4a90c..e1d6df814e 100644 --- a/osu.Game.Tests/NonVisual/ControlPointInfoTest.cs +++ b/osu.Game.Tests/NonVisual/ControlPointInfoTest.cs @@ -64,7 +64,7 @@ namespace osu.Game.Tests.NonVisual [Test] public void TestAddRedundantSample() { - var cpi = new ControlPointInfo(); + var cpi = new LegacyControlPointInfo(); cpi.Add(0, new SampleControlPoint()); // is *not* redundant, special exception for first sample point cpi.Add(1000, new SampleControlPoint()); // is redundant @@ -142,7 +142,7 @@ namespace osu.Game.Tests.NonVisual [Test] public void TestRemoveGroupAlsoRemovedControlPoints() { - var cpi = new ControlPointInfo(); + var cpi = new LegacyControlPointInfo(); var group = cpi.GroupAt(1000, true); diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs index 2d0fc17a7b..7cc4fa6dc4 100644 --- a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs +++ b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs @@ -14,6 +14,64 @@ using osu.Game.Utils; namespace osu.Game.Beatmaps.ControlPoints { + public class LegacyControlPointInfo : ControlPointInfo + { + /// + /// All sound points. + /// + [JsonProperty] + public IBindableList SamplePoints => samplePoints; + + private readonly BindableList samplePoints = new BindableList(); + + /// + /// Finds the sound control point that is active at . + /// + /// The time to find the sound control point at. + /// The sound control point. + [NotNull] + public SampleControlPoint SamplePointAt(double time) => BinarySearchWithFallback(SamplePoints, time, SamplePoints.Count > 0 ? SamplePoints[0] : SampleControlPoint.DEFAULT); + + public override void Clear() + { + base.Clear(); + samplePoints.Clear(); + } + + protected override bool CheckAlreadyExisting(double time, ControlPoint newPoint) + { + if (newPoint is SampleControlPoint _) + { + var existing = BinarySearch(SamplePoints, time); + return newPoint?.IsRedundant(existing) == true; + } + + return base.CheckAlreadyExisting(time, newPoint); + } + + protected override void GroupItemAdded(ControlPoint controlPoint) + { + if (controlPoint is SampleControlPoint typed) + { + samplePoints.Add(typed); + return; + } + + base.GroupItemAdded(controlPoint); + } + + protected override void GroupItemRemoved(ControlPoint controlPoint) + { + if (controlPoint is SampleControlPoint typed) + { + samplePoints.Remove(typed); + return; + } + + base.GroupItemRemoved(controlPoint); + } + } + [Serializable] public class ControlPointInfo : IDeepCloneable { @@ -41,14 +99,6 @@ namespace osu.Game.Beatmaps.ControlPoints private readonly SortedList difficultyPoints = new SortedList(Comparer.Default); - /// - /// All sound points. - /// - [JsonProperty] - public IBindableList SamplePoints => samplePoints; - - private readonly BindableList samplePoints = new BindableList(); - /// /// All effect points. /// @@ -69,7 +119,7 @@ namespace osu.Game.Beatmaps.ControlPoints /// The time to find the difficulty control point at. /// The difficulty control point. [NotNull] - public DifficultyControlPoint DifficultyPointAt(double time) => binarySearchWithFallback(DifficultyPoints, time, DifficultyControlPoint.DEFAULT); + public DifficultyControlPoint DifficultyPointAt(double time) => BinarySearchWithFallback(DifficultyPoints, time, DifficultyControlPoint.DEFAULT); /// /// Finds the effect control point that is active at . @@ -77,15 +127,7 @@ namespace osu.Game.Beatmaps.ControlPoints /// The time to find the effect control point at. /// The effect control point. [NotNull] - public EffectControlPoint EffectPointAt(double time) => binarySearchWithFallback(EffectPoints, time, EffectControlPoint.DEFAULT); - - /// - /// Finds the sound control point that is active at . - /// - /// The time to find the sound control point at. - /// The sound control point. - [NotNull] - public SampleControlPoint SamplePointAt(double time) => binarySearchWithFallback(SamplePoints, time, SamplePoints.Count > 0 ? SamplePoints[0] : SampleControlPoint.DEFAULT); + public EffectControlPoint EffectPointAt(double time) => BinarySearchWithFallback(EffectPoints, time, EffectControlPoint.DEFAULT); /// /// Finds the timing control point that is active at . @@ -93,7 +135,7 @@ namespace osu.Game.Beatmaps.ControlPoints /// The time to find the timing control point at. /// The timing control point. [NotNull] - public TimingControlPoint TimingPointAt(double time) => binarySearchWithFallback(TimingPoints, time, TimingPoints.Count > 0 ? TimingPoints[0] : TimingControlPoint.DEFAULT); + public TimingControlPoint TimingPointAt(double time) => BinarySearchWithFallback(TimingPoints, time, TimingPoints.Count > 0 ? TimingPoints[0] : TimingControlPoint.DEFAULT); /// /// Finds the maximum BPM represented by any timing control point. @@ -112,12 +154,11 @@ namespace osu.Game.Beatmaps.ControlPoints /// /// Remove all s and return to a pristine state. /// - public void Clear() + public virtual void Clear() { groups.Clear(); timingPoints.Clear(); difficultyPoints.Clear(); - samplePoints.Clear(); effectPoints.Clear(); } @@ -129,7 +170,7 @@ namespace osu.Game.Beatmaps.ControlPoints /// Whether the control point was added. public bool Add(double time, ControlPoint controlPoint) { - if (checkAlreadyExisting(time, controlPoint)) + if (CheckAlreadyExisting(time, controlPoint)) return false; GroupAt(time, true).Add(controlPoint); @@ -147,8 +188,8 @@ namespace osu.Game.Beatmaps.ControlPoints if (addIfNotExisting) { - newGroup.ItemAdded += groupItemAdded; - newGroup.ItemRemoved += groupItemRemoved; + newGroup.ItemAdded += GroupItemAdded; + newGroup.ItemRemoved += GroupItemRemoved; groups.Insert(~i, newGroup); return newGroup; @@ -162,8 +203,8 @@ namespace osu.Game.Beatmaps.ControlPoints foreach (var item in group.ControlPoints.ToArray()) group.Remove(item); - group.ItemAdded -= groupItemAdded; - group.ItemRemoved -= groupItemRemoved; + group.ItemAdded -= GroupItemAdded; + group.ItemRemoved -= GroupItemRemoved; groups.Remove(group); } @@ -228,10 +269,10 @@ namespace osu.Game.Beatmaps.ControlPoints /// The time to find the control point at. /// The control point to use when is before any control points. /// The active control point at , or a fallback if none found. - private T binarySearchWithFallback(IReadOnlyList list, double time, T fallback) + protected T BinarySearchWithFallback(IReadOnlyList list, double time, T fallback) where T : ControlPoint { - return binarySearch(list, time) ?? fallback; + return BinarySearch(list, time) ?? fallback; } /// @@ -240,7 +281,7 @@ namespace osu.Game.Beatmaps.ControlPoints /// The list to search. /// The time to find the control point at. /// The active control point at . - private T binarySearch(IReadOnlyList list, double time) + protected virtual T BinarySearch(IReadOnlyList list, double time) where T : ControlPoint { if (list == null) @@ -280,7 +321,7 @@ namespace osu.Game.Beatmaps.ControlPoints /// The time to find the timing control point at. /// A point to be added. /// Whether the new point should be added. - private bool checkAlreadyExisting(double time, ControlPoint newPoint) + protected virtual bool CheckAlreadyExisting(double time, ControlPoint newPoint) { ControlPoint existing = null; @@ -288,17 +329,13 @@ namespace osu.Game.Beatmaps.ControlPoints { case TimingControlPoint _: // Timing points are a special case and need to be added regardless of fallback availability. - existing = binarySearch(TimingPoints, time); + existing = BinarySearch(TimingPoints, time); break; case EffectControlPoint _: existing = EffectPointAt(time); break; - case SampleControlPoint _: - existing = binarySearch(SamplePoints, time); - break; - case DifficultyControlPoint _: existing = DifficultyPointAt(time); break; @@ -307,7 +344,7 @@ namespace osu.Game.Beatmaps.ControlPoints return newPoint?.IsRedundant(existing) == true; } - private void groupItemAdded(ControlPoint controlPoint) + protected virtual void GroupItemAdded(ControlPoint controlPoint) { switch (controlPoint) { @@ -319,17 +356,13 @@ namespace osu.Game.Beatmaps.ControlPoints effectPoints.Add(typed); break; - case SampleControlPoint typed: - samplePoints.Add(typed); - break; - case DifficultyControlPoint typed: difficultyPoints.Add(typed); break; } } - private void groupItemRemoved(ControlPoint controlPoint) + protected virtual void GroupItemRemoved(ControlPoint controlPoint) { switch (controlPoint) { @@ -341,10 +374,6 @@ namespace osu.Game.Beatmaps.ControlPoints effectPoints.Remove(typed); break; - case SampleControlPoint typed: - samplePoints.Remove(typed); - break; - case DifficultyControlPoint typed: difficultyPoints.Remove(typed); break; diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index 0ddc9e4c48..9bd76a9f2c 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -44,6 +44,13 @@ namespace osu.Game.Beatmaps.Formats offset = FormatVersion < 5 ? 24 : 0; } + protected override Beatmap CreateTemplateObject() + { + var templateBeatmap = base.CreateTemplateObject(); + templateBeatmap.ControlPointInfo = new LegacyControlPointInfo(); + return templateBeatmap; + } + protected override void ParseStreamInto(LineBufferedReader stream, Beatmap beatmap) { this.beatmap = beatmap; @@ -394,8 +401,16 @@ namespace osu.Game.Beatmaps.Formats private readonly HashSet pendingControlPointTypes = new HashSet(); private double pendingControlPointsTime; + private readonly LegacyControlPointInfo controlPointInfo = new LegacyControlPointInfo(); + private void addControlPoint(double time, ControlPoint point, bool timingChange) { + if (point is SampleControlPoint) + { + controlPointInfo.Add(time, point); + return; + } + if (time != pendingControlPointsTime) flushPendingPoints(); @@ -430,8 +445,15 @@ namespace osu.Game.Beatmaps.Formats parser ??= new Rulesets.Objects.Legacy.Osu.ConvertHitObjectParser(getOffsetTime(), FormatVersion); var obj = parser.Parse(line); + if (obj != null) + { + // assign legacy control points directly to hitobject + //obj.SampleControlPoint = controlPointInfo.SamplePointAt(obj.StartTime); + obj.ApplyDefaults(controlPointInfo, beatmap.BeatmapInfo.BaseDifficulty); + beatmap.HitObjects.Add(obj); + } } private int getOffsetTime(int time) => time + (ApplyOffsets ? offset : 0); diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs index 246dc991d5..a6c41a3cf6 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs @@ -80,7 +80,7 @@ namespace osu.Game.Beatmaps.Formats writer.WriteLine(FormattableString.Invariant($"AudioLeadIn: {beatmap.BeatmapInfo.AudioLeadIn}")); writer.WriteLine(FormattableString.Invariant($"PreviewTime: {beatmap.Metadata.PreviewTime}")); writer.WriteLine(FormattableString.Invariant($"Countdown: {(int)beatmap.BeatmapInfo.Countdown}")); - writer.WriteLine(FormattableString.Invariant($"SampleSet: {toLegacySampleBank(beatmap.ControlPointInfo.SamplePointAt(double.MinValue).SampleBank)}")); + writer.WriteLine(FormattableString.Invariant($"SampleSet: {toLegacySampleBank((beatmap.HitObjects.FirstOrDefault()?.SampleControlPoint ?? SampleControlPoint.DEFAULT).SampleBank)}")); writer.WriteLine(FormattableString.Invariant($"StackLeniency: {beatmap.BeatmapInfo.StackLeniency}")); writer.WriteLine(FormattableString.Invariant($"Mode: {beatmap.BeatmapInfo.RulesetID}")); writer.WriteLine(FormattableString.Invariant($"LetterboxInBreaks: {(beatmap.BeatmapInfo.LetterboxInBreaks ? '1' : '0')}")); @@ -187,7 +187,7 @@ namespace osu.Game.Beatmaps.Formats void outputControlPointEffectsAt(double time, bool isTimingPoint) { - var samplePoint = beatmap.ControlPointInfo.SamplePointAt(time); + var samplePoint = ((LegacyControlPointInfo)beatmap.ControlPointInfo).SamplePointAt(time); var effectPoint = beatmap.ControlPointInfo.EffectPointAt(time); // Apply the control point to a hit sample to uncover legacy properties (e.g. suffix) diff --git a/osu.Game/Rulesets/Objects/HitObject.cs b/osu.Game/Rulesets/Objects/HitObject.cs index 422655502d..fd9f02604c 100644 --- a/osu.Game/Rulesets/Objects/HitObject.cs +++ b/osu.Game/Rulesets/Objects/HitObject.cs @@ -106,8 +106,11 @@ namespace osu.Game.Rulesets.Objects { ApplyDefaultsToSelf(controlPointInfo, difficulty); - // This is done here since ApplyDefaultsToSelf may be used to determine the end time - SampleControlPoint = controlPointInfo.SamplePointAt(this.GetEndTime() + control_point_leniency); + if (controlPointInfo is LegacyControlPointInfo legacyInfo) + { + // This is done here since ApplyDefaultsToSelf may be used to determine the end time + SampleControlPoint = legacyInfo.SamplePointAt(this.GetEndTime() + control_point_leniency); + } nestedHitObjects.Clear(); diff --git a/osu.Game/Screens/Edit/Timing/SampleSection.cs b/osu.Game/Screens/Edit/Timing/SampleSection.cs index cc73af6349..be8de18502 100644 --- a/osu.Game/Screens/Edit/Timing/SampleSection.cs +++ b/osu.Game/Screens/Edit/Timing/SampleSection.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -42,15 +43,6 @@ namespace osu.Game.Screens.Edit.Timing } } - protected override SampleControlPoint CreatePoint() - { - var reference = Beatmap.ControlPointInfo.SamplePointAt(SelectedGroup.Value.Time); - - return new SampleControlPoint - { - SampleBank = reference.SampleBank, - SampleVolume = reference.SampleVolume, - }; - } + protected override SampleControlPoint CreatePoint() => new SampleControlPoint(); // TODO: remove } } From ccacf56dd843352743d589adfe98c6c0c48a54db Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 30 Aug 2021 14:12:30 +0900 Subject: [PATCH 047/160] Move to legacy namespace --- .../Formats/LegacyBeatmapDecoderTest.cs | 12 ++-- .../NonVisual/ControlPointInfoTest.cs | 1 + .../ControlPoints/ControlPointInfo.cs | 58 ---------------- .../Beatmaps/Legacy/LegacyControlPointInfo.cs | 68 +++++++++++++++++++ osu.Game/Rulesets/Objects/HitObject.cs | 1 + osu.Game/Screens/Edit/Timing/SampleSection.cs | 1 - 6 files changed, 76 insertions(+), 65 deletions(-) create mode 100644 osu.Game/Beatmaps/Legacy/LegacyControlPointInfo.cs diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index 24410c886f..8560a36fb4 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -3,16 +3,12 @@ using System; using System.IO; -using NUnit.Framework; -using osuTK; -using osuTK.Graphics; -using osu.Game.Tests.Resources; using System.Linq; +using NUnit.Framework; using osu.Game.Audio; using osu.Game.Beatmaps; -using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Rulesets.Objects.Types; using osu.Game.Beatmaps.Formats; +using osu.Game.Beatmaps.Legacy; using osu.Game.Beatmaps.Timing; using osu.Game.IO; using osu.Game.Rulesets.Catch; @@ -20,9 +16,13 @@ using osu.Game.Rulesets.Catch.Beatmaps; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Legacy; +using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Beatmaps; using osu.Game.Skinning; +using osu.Game.Tests.Resources; +using osuTK; +using osuTK.Graphics; namespace osu.Game.Tests.Beatmaps.Formats { diff --git a/osu.Game.Tests/NonVisual/ControlPointInfoTest.cs b/osu.Game.Tests/NonVisual/ControlPointInfoTest.cs index e1d6df814e..fabb016d5f 100644 --- a/osu.Game.Tests/NonVisual/ControlPointInfoTest.cs +++ b/osu.Game.Tests/NonVisual/ControlPointInfoTest.cs @@ -4,6 +4,7 @@ using System.Linq; using NUnit.Framework; using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Beatmaps.Legacy; namespace osu.Game.Tests.NonVisual { diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs index 7cc4fa6dc4..d2a3b2fc8b 100644 --- a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs +++ b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs @@ -14,64 +14,6 @@ using osu.Game.Utils; namespace osu.Game.Beatmaps.ControlPoints { - public class LegacyControlPointInfo : ControlPointInfo - { - /// - /// All sound points. - /// - [JsonProperty] - public IBindableList SamplePoints => samplePoints; - - private readonly BindableList samplePoints = new BindableList(); - - /// - /// Finds the sound control point that is active at . - /// - /// The time to find the sound control point at. - /// The sound control point. - [NotNull] - public SampleControlPoint SamplePointAt(double time) => BinarySearchWithFallback(SamplePoints, time, SamplePoints.Count > 0 ? SamplePoints[0] : SampleControlPoint.DEFAULT); - - public override void Clear() - { - base.Clear(); - samplePoints.Clear(); - } - - protected override bool CheckAlreadyExisting(double time, ControlPoint newPoint) - { - if (newPoint is SampleControlPoint _) - { - var existing = BinarySearch(SamplePoints, time); - return newPoint?.IsRedundant(existing) == true; - } - - return base.CheckAlreadyExisting(time, newPoint); - } - - protected override void GroupItemAdded(ControlPoint controlPoint) - { - if (controlPoint is SampleControlPoint typed) - { - samplePoints.Add(typed); - return; - } - - base.GroupItemAdded(controlPoint); - } - - protected override void GroupItemRemoved(ControlPoint controlPoint) - { - if (controlPoint is SampleControlPoint typed) - { - samplePoints.Remove(typed); - return; - } - - base.GroupItemRemoved(controlPoint); - } - } - [Serializable] public class ControlPointInfo : IDeepCloneable { diff --git a/osu.Game/Beatmaps/Legacy/LegacyControlPointInfo.cs b/osu.Game/Beatmaps/Legacy/LegacyControlPointInfo.cs new file mode 100644 index 0000000000..db9ff27f73 --- /dev/null +++ b/osu.Game/Beatmaps/Legacy/LegacyControlPointInfo.cs @@ -0,0 +1,68 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using JetBrains.Annotations; +using Newtonsoft.Json; +using osu.Framework.Bindables; +using osu.Game.Beatmaps.ControlPoints; + +namespace osu.Game.Beatmaps.Legacy +{ + public class LegacyControlPointInfo : ControlPointInfo + { + /// + /// All sound points. + /// + [JsonProperty] + public IBindableList SamplePoints => samplePoints; + + private readonly BindableList samplePoints = new BindableList(); + + /// + /// Finds the sound control point that is active at . + /// + /// The time to find the sound control point at. + /// The sound control point. + [NotNull] + public SampleControlPoint SamplePointAt(double time) => BinarySearchWithFallback(SamplePoints, time, SamplePoints.Count > 0 ? SamplePoints[0] : SampleControlPoint.DEFAULT); + + public override void Clear() + { + base.Clear(); + samplePoints.Clear(); + } + + protected override bool CheckAlreadyExisting(double time, ControlPoint newPoint) + { + if (newPoint is SampleControlPoint _) + { + var existing = BinarySearch(SamplePoints, time); + return newPoint?.IsRedundant(existing) == true; + } + + return base.CheckAlreadyExisting(time, newPoint); + } + + protected override void GroupItemAdded(ControlPoint controlPoint) + { + if (controlPoint is SampleControlPoint typed) + { + samplePoints.Add(typed); + return; + } + + base.GroupItemAdded(controlPoint); + } + + protected override void GroupItemRemoved(ControlPoint controlPoint) + { + if (controlPoint is SampleControlPoint typed) + { + samplePoints.Remove(typed); + return; + } + + base.GroupItemRemoved(controlPoint); + } + } +} diff --git a/osu.Game/Rulesets/Objects/HitObject.cs b/osu.Game/Rulesets/Objects/HitObject.cs index fd9f02604c..3e95659243 100644 --- a/osu.Game/Rulesets/Objects/HitObject.cs +++ b/osu.Game/Rulesets/Objects/HitObject.cs @@ -11,6 +11,7 @@ using osu.Framework.Bindables; using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Beatmaps.Legacy; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Scoring; diff --git a/osu.Game/Screens/Edit/Timing/SampleSection.cs b/osu.Game/Screens/Edit/Timing/SampleSection.cs index be8de18502..52709a2bbe 100644 --- a/osu.Game/Screens/Edit/Timing/SampleSection.cs +++ b/osu.Game/Screens/Edit/Timing/SampleSection.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; From 6fd24a5d92b933d45a78378ed509e6e444f24561 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 30 Aug 2021 15:17:20 +0900 Subject: [PATCH 048/160] Remove redundant null coalesce --- osu.Game/Beatmaps/Legacy/LegacyControlPointInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/Legacy/LegacyControlPointInfo.cs b/osu.Game/Beatmaps/Legacy/LegacyControlPointInfo.cs index db9ff27f73..fdfc22700a 100644 --- a/osu.Game/Beatmaps/Legacy/LegacyControlPointInfo.cs +++ b/osu.Game/Beatmaps/Legacy/LegacyControlPointInfo.cs @@ -37,7 +37,7 @@ namespace osu.Game.Beatmaps.Legacy if (newPoint is SampleControlPoint _) { var existing = BinarySearch(SamplePoints, time); - return newPoint?.IsRedundant(existing) == true; + return newPoint.IsRedundant(existing); } return base.CheckAlreadyExisting(time, newPoint); From 6ee4a6526caf053c236984e0eb383576aabbefd7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 30 Aug 2021 16:58:03 +0900 Subject: [PATCH 049/160] Don't block sample points from still being added to `ControlPointInfo` --- osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index 9bd76a9f2c..ec08c4a3f3 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -408,7 +408,6 @@ namespace osu.Game.Beatmaps.Formats if (point is SampleControlPoint) { controlPointInfo.Add(time, point); - return; } if (time != pendingControlPointsTime) From d35c4da90658b0fb7361e120dd3a001b2da7be04 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 30 Aug 2021 16:58:21 +0900 Subject: [PATCH 050/160] Add new control point to legacy regeneration logic --- .../Beatmaps/Formats/LegacyBeatmapEncoder.cs | 30 +++++++++++++++++-- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs index a6c41a3cf6..574af848d0 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs @@ -166,6 +166,30 @@ namespace osu.Game.Beatmaps.Formats writer.WriteLine("[TimingPoints]"); + if (!(beatmap.ControlPointInfo is LegacyControlPointInfo)) // todo: always run this? probably no harm. + { + var legacyControlPoints = new LegacyControlPointInfo(); + + foreach (var point in beatmap.ControlPointInfo.AllControlPoints) + legacyControlPoints.Add(point.Time, point.DeepClone()); + + beatmap.ControlPointInfo = legacyControlPoints; + + SampleControlPoint lastRelevantSamplePoint = null; + + // iterate over hitobjects and pull out all required sample changes + foreach (var h in beatmap.HitObjects) + { + var hSamplePoint = h.SampleControlPoint; + + if (!hSamplePoint.IsRedundant(lastRelevantSamplePoint)) + { + legacyControlPoints.Add(hSamplePoint.Time, hSamplePoint); + lastRelevantSamplePoint = hSamplePoint; + } + } + } + foreach (var group in beatmap.ControlPointInfo.Groups) { var groupTimingPoint = group.ControlPoints.OfType().FirstOrDefault(); @@ -175,17 +199,17 @@ namespace osu.Game.Beatmaps.Formats { writer.Write(FormattableString.Invariant($"{groupTimingPoint.Time},")); writer.Write(FormattableString.Invariant($"{groupTimingPoint.BeatLength},")); - outputControlPointEffectsAt(groupTimingPoint.Time, true); + outputControlPointAt(groupTimingPoint.Time, true); } // Output any remaining effects as secondary non-timing control point. var difficultyPoint = beatmap.ControlPointInfo.DifficultyPointAt(group.Time); writer.Write(FormattableString.Invariant($"{group.Time},")); writer.Write(FormattableString.Invariant($"{-100 / difficultyPoint.SpeedMultiplier},")); - outputControlPointEffectsAt(group.Time, false); + outputControlPointAt(group.Time, false); } - void outputControlPointEffectsAt(double time, bool isTimingPoint) + void outputControlPointAt(double time, bool isTimingPoint) { var samplePoint = ((LegacyControlPointInfo)beatmap.ControlPointInfo).SamplePointAt(time); var effectPoint = beatmap.ControlPointInfo.EffectPointAt(time); From 2115d6f93e8d2bf844ca1f69c85f7658167663a9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 30 Aug 2021 16:57:49 +0900 Subject: [PATCH 051/160] Add test coverage of legacy sample point recreation --- .../Formats/LegacyBeatmapEncoderTest.cs | 29 ++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs index 855a75117d..812b20d447 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs @@ -14,6 +14,7 @@ using osu.Framework.IO.Stores; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.Formats; +using osu.Game.Beatmaps.Legacy; using osu.Game.IO; using osu.Game.IO.Serialization; using osu.Game.Rulesets.Catch; @@ -76,6 +77,32 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.That(decodedSlider.Path.ControlPoints.Count, Is.EqualTo(5)); } + [TestCaseSource(nameof(allBeatmaps))] + public void TestEncodeDecodeStabilityWithNonLegacyControlPoints(string name) + { + var decoded = decodeFromLegacy(beatmaps_resource_store.GetStream(name), name); + + sort(decoded.beatmap); + + var originalSerialized = decoded.beatmap.Serialize(); + var encoded = encodeToLegacy(decoded); + + Assert.AreEqual(typeof(LegacyControlPointInfo), decoded.beatmap.ControlPointInfo.GetType()); + + // emulate non-legacy control points by cloning the non-legacy portion. + // the assertion is that the encoder can recreate this losslessly from hitobject data. + decoded.beatmap.ControlPointInfo = decoded.beatmap.ControlPointInfo.DeepClone(); + + Assert.AreNotEqual(typeof(LegacyControlPointInfo), decoded.beatmap.ControlPointInfo.GetType()); + + var decodedAfterEncode = decodeFromLegacy(encoded, name); + + sort(decodedAfterEncode.beatmap); + + Assert.That(decodedAfterEncode.beatmap.Serialize(), Is.EqualTo(originalSerialized)); + Assert.IsTrue(areComboColoursEqual(decodedAfterEncode.beatmapSkin.Configuration, decoded.beatmapSkin.Configuration)); + } + private bool areComboColoursEqual(IHasComboColours a, IHasComboColours b) { // equal to null, no need to SequenceEqual @@ -116,7 +143,7 @@ namespace osu.Game.Tests.Beatmaps.Formats } } - private Stream encodeToLegacy((IBeatmap beatmap, ISkin beatmapSkin) fullBeatmap) + private MemoryStream encodeToLegacy((IBeatmap beatmap, ISkin beatmapSkin) fullBeatmap) { var (beatmap, beatmapSkin) = fullBeatmap; var stream = new MemoryStream(); From 4da2dca33918ebfc5b57e9f0c30ddd6f06927fd5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 30 Aug 2021 17:21:05 +0900 Subject: [PATCH 052/160] Apply the default `SampleControlPoint` if not externally provided This is mostly to handle tests for now, as generally this should be provided by an external source in all other cases. --- osu.Game/Rulesets/Objects/HitObject.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Rulesets/Objects/HitObject.cs b/osu.Game/Rulesets/Objects/HitObject.cs index 3e95659243..fb1b6cd267 100644 --- a/osu.Game/Rulesets/Objects/HitObject.cs +++ b/osu.Game/Rulesets/Objects/HitObject.cs @@ -112,6 +112,10 @@ namespace osu.Game.Rulesets.Objects // This is done here since ApplyDefaultsToSelf may be used to determine the end time SampleControlPoint = legacyInfo.SamplePointAt(this.GetEndTime() + control_point_leniency); } + else + { + SampleControlPoint ??= SampleControlPoint.DEFAULT; + } nestedHitObjects.Clear(); From 9fae2c350d0eef1ae7e365e9bc636a76a672135f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 30 Aug 2021 17:25:36 +0900 Subject: [PATCH 053/160] Fix test regressions --- osu.Game.Tests/Editing/Checks/CheckMutedObjectsTest.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Editing/Checks/CheckMutedObjectsTest.cs b/osu.Game.Tests/Editing/Checks/CheckMutedObjectsTest.cs index 41a8f72305..4ab6e5cef6 100644 --- a/osu.Game.Tests/Editing/Checks/CheckMutedObjectsTest.cs +++ b/osu.Game.Tests/Editing/Checks/CheckMutedObjectsTest.cs @@ -7,6 +7,7 @@ using NUnit.Framework; using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Beatmaps.Legacy; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Checks; using osu.Game.Rulesets.Objects; @@ -30,7 +31,7 @@ namespace osu.Game.Tests.Editing.Checks { check = new CheckMutedObjects(); - cpi = new ControlPointInfo(); + cpi = new LegacyControlPointInfo(); cpi.Add(0, new SampleControlPoint { SampleVolume = volume_regular }); cpi.Add(1000, new SampleControlPoint { SampleVolume = volume_low }); cpi.Add(2000, new SampleControlPoint { SampleVolume = volume_muted }); From 1aeae2b8c8537927bc9c93553abddf3c1c935b24 Mon Sep 17 00:00:00 2001 From: Davran Dilshat Date: Mon, 30 Aug 2021 10:11:41 +0100 Subject: [PATCH 054/160] reverse ternary operator --- osu.Game/Overlays/UserProfileOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/UserProfileOverlay.cs b/osu.Game/Overlays/UserProfileOverlay.cs index 6e74acc96a..b0327987f2 100644 --- a/osu.Game/Overlays/UserProfileOverlay.cs +++ b/osu.Game/Overlays/UserProfileOverlay.cs @@ -118,7 +118,7 @@ namespace osu.Game.Overlays if (fetchOnline) { - userReq = user.Username != null ? new GetUserRequest(user.Username) : new GetUserRequest(user.Id); + userReq = user.Id > 1 ? new GetUserRequest(user.Id) : new GetUserRequest(user.Username); userReq.Success += userLoadComplete; API.Queue(userReq); } From 015df282fe61d700aa1ecb8bc41c8200021d3d67 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 30 Aug 2021 18:32:55 +0900 Subject: [PATCH 055/160] Simplify copy operations --- .../Visual/Editing/TestSceneEditorSeeking.cs | 2 +- osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs | 11 +---------- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorSeeking.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorSeeking.cs index 96ce418851..ff741a8ed5 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorSeeking.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorSeeking.cs @@ -21,7 +21,7 @@ namespace osu.Game.Tests.Visual.Editing beatmap.BeatmapInfo.BeatDivisor = 1; - beatmap.ControlPointInfo = new ControlPointInfo(); + beatmap.ControlPointInfo.Clear(); beatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = 1000 }); beatmap.ControlPointInfo.Add(2000, new TimingControlPoint { BeatLength = 500 }); diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index ec08c4a3f3..accefb2583 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -401,15 +401,8 @@ namespace osu.Game.Beatmaps.Formats private readonly HashSet pendingControlPointTypes = new HashSet(); private double pendingControlPointsTime; - private readonly LegacyControlPointInfo controlPointInfo = new LegacyControlPointInfo(); - private void addControlPoint(double time, ControlPoint point, bool timingChange) { - if (point is SampleControlPoint) - { - controlPointInfo.Add(time, point); - } - if (time != pendingControlPointsTime) flushPendingPoints(); @@ -447,9 +440,7 @@ namespace osu.Game.Beatmaps.Formats if (obj != null) { - // assign legacy control points directly to hitobject - //obj.SampleControlPoint = controlPointInfo.SamplePointAt(obj.StartTime); - obj.ApplyDefaults(controlPointInfo, beatmap.BeatmapInfo.BaseDifficulty); + obj.ApplyDefaults(beatmap.ControlPointInfo, beatmap.BeatmapInfo.BaseDifficulty); beatmap.HitObjects.Add(obj); } From 1aaea7011ae2bc308913a59523503f549b67deee Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 30 Aug 2021 18:33:05 +0900 Subject: [PATCH 056/160] Fix early return causing event loss in case of multiple control points in group --- osu.Game/Beatmaps/Legacy/LegacyControlPointInfo.cs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/osu.Game/Beatmaps/Legacy/LegacyControlPointInfo.cs b/osu.Game/Beatmaps/Legacy/LegacyControlPointInfo.cs index fdfc22700a..5152549bed 100644 --- a/osu.Game/Beatmaps/Legacy/LegacyControlPointInfo.cs +++ b/osu.Game/Beatmaps/Legacy/LegacyControlPointInfo.cs @@ -46,10 +46,7 @@ namespace osu.Game.Beatmaps.Legacy protected override void GroupItemAdded(ControlPoint controlPoint) { if (controlPoint is SampleControlPoint typed) - { samplePoints.Add(typed); - return; - } base.GroupItemAdded(controlPoint); } @@ -57,10 +54,7 @@ namespace osu.Game.Beatmaps.Legacy protected override void GroupItemRemoved(ControlPoint controlPoint) { if (controlPoint is SampleControlPoint typed) - { samplePoints.Remove(typed); - return; - } base.GroupItemRemoved(controlPoint); } From 04bf667d0db6de78dfb8e3e17a0f3d371d42023b Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Mon, 30 Aug 2021 17:49:18 +0800 Subject: [PATCH 057/160] Parse partially typed enum names in filter query --- osu.Game/Screens/Select/FilterQueryParser.cs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Select/FilterQueryParser.cs b/osu.Game/Screens/Select/FilterQueryParser.cs index 72d10019b2..591a632a7c 100644 --- a/osu.Game/Screens/Select/FilterQueryParser.cs +++ b/osu.Game/Screens/Select/FilterQueryParser.cs @@ -3,8 +3,8 @@ using System; using System.Globalization; +using System.Linq; using System.Text.RegularExpressions; -using osu.Game.Beatmaps; using osu.Game.Screens.Select.Filter; namespace osu.Game.Screens.Select @@ -64,8 +64,7 @@ namespace osu.Game.Screens.Select return TryUpdateCriteriaRange(ref criteria.BeatDivisor, op, value, tryParseInt); case "status": - return TryUpdateCriteriaRange(ref criteria.OnlineStatus, op, value, - (string s, out BeatmapSetOnlineStatus val) => Enum.TryParse(value, true, out val)); + return TryUpdateCriteriaRange(ref criteria.OnlineStatus, op, value, tryParseEnum); case "creator": return TryUpdateCriteriaText(ref criteria.Creator, op, value); @@ -120,6 +119,14 @@ namespace osu.Game.Screens.Select private static bool tryParseInt(string value, out int result) => int.TryParse(value, NumberStyles.None, CultureInfo.InvariantCulture, out result); + private static bool tryParseEnum(string value, out TEnum result) where TEnum : struct + { + if (Enum.TryParse(value, true, out result)) return true; + + string status = Enum.GetNames(typeof(TEnum)).FirstOrDefault(name => name.StartsWith(value, true, CultureInfo.InvariantCulture)); + return Enum.TryParse(status, true, out result); + } + /// /// Attempts to parse a keyword filter with the specified and textual . /// If the value indicates a valid textual filter, the function returns true and the resulting data is stored into From 8137eee527e93bc5963533b0ea3404cde110c4b1 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Mon, 30 Aug 2021 18:05:47 +0800 Subject: [PATCH 058/160] Reuse `value` to save enum name Co-authored-by: Salman Ahmed --- osu.Game/Screens/Select/FilterQueryParser.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Select/FilterQueryParser.cs b/osu.Game/Screens/Select/FilterQueryParser.cs index 591a632a7c..a882148392 100644 --- a/osu.Game/Screens/Select/FilterQueryParser.cs +++ b/osu.Game/Screens/Select/FilterQueryParser.cs @@ -123,8 +123,8 @@ namespace osu.Game.Screens.Select { if (Enum.TryParse(value, true, out result)) return true; - string status = Enum.GetNames(typeof(TEnum)).FirstOrDefault(name => name.StartsWith(value, true, CultureInfo.InvariantCulture)); - return Enum.TryParse(status, true, out result); + value = Enum.GetNames(typeof(TEnum)).FirstOrDefault(name => name.StartsWith(value, true, CultureInfo.InvariantCulture)); + return Enum.TryParse(value, true, out result); } /// From c789163d01e367f867e8f4a754516c6e895ade24 Mon Sep 17 00:00:00 2001 From: rednir Date: Mon, 30 Aug 2021 13:22:12 +0100 Subject: [PATCH 059/160] use user ID overload when its supposed to be used Co-authored-by: Salman Ahmed --- osu.Game/OsuGame.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 26fa1d5a4c..1e6e1e0ead 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -329,7 +329,11 @@ namespace osu.Game break; case LinkAction.OpenUserProfile: - ShowUser(link.Argument); + if (int.TryParse(link.Argument, out var userId)) + ShowUser(userId); + else + ShowUser(link.Argument); + break; case LinkAction.OpenWiki: From 8104b15874c5b358c22f7d3f8d6fb9ac42b09b94 Mon Sep 17 00:00:00 2001 From: rednir Date: Mon, 30 Aug 2021 13:23:33 +0100 Subject: [PATCH 060/160] remove braces Co-authored-by: Salman Ahmed --- osu.Game/Online/API/Requests/GetUserRequest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/API/Requests/GetUserRequest.cs b/osu.Game/Online/API/Requests/GetUserRequest.cs index 48041cd40c..0c8a4a3578 100644 --- a/osu.Game/Online/API/Requests/GetUserRequest.cs +++ b/osu.Game/Online/API/Requests/GetUserRequest.cs @@ -27,6 +27,6 @@ namespace osu.Game.Online.API.Requests Ruleset = ruleset; } - protected override string Target => (userIdentifier != null) ? $@"users/{userIdentifier}/{Ruleset?.ShortName}" : $@"me/{Ruleset?.ShortName}"; + protected override string Target => userIdentifier != null ? $@"users/{userIdentifier}/{Ruleset?.ShortName}" : $@"me/{Ruleset?.ShortName}"; } } From a2cff75fc0183f0c0e899f2fc0cba05c1898779e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 30 Aug 2021 21:55:08 +0900 Subject: [PATCH 061/160] Fix editor not cloning control points as expected --- .../Beatmaps/Formats/LegacyBeatmapEncoderTest.cs | 7 ++++++- osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs index 812b20d447..e66221514c 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs @@ -91,7 +91,12 @@ namespace osu.Game.Tests.Beatmaps.Formats // emulate non-legacy control points by cloning the non-legacy portion. // the assertion is that the encoder can recreate this losslessly from hitobject data. - decoded.beatmap.ControlPointInfo = decoded.beatmap.ControlPointInfo.DeepClone(); + var controlPointInfo = new ControlPointInfo(); + + foreach (var point in decoded.beatmap.ControlPointInfo.AllControlPoints) + controlPointInfo.Add(point.Time, point.DeepClone()); + + decoded.beatmap.ControlPointInfo = controlPointInfo; Assert.AreNotEqual(typeof(LegacyControlPointInfo), decoded.beatmap.ControlPointInfo.GetType()); diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs index d2a3b2fc8b..3ff40fe194 100644 --- a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs +++ b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs @@ -324,7 +324,7 @@ namespace osu.Game.Beatmaps.ControlPoints public ControlPointInfo DeepClone() { - var controlPointInfo = new ControlPointInfo(); + var controlPointInfo = (ControlPointInfo)Activator.CreateInstance(GetType()); foreach (var point in AllControlPoints) controlPointInfo.Add(point.Time, point.DeepClone()); From acf38c723a5fd681ba62c83cf1629f4d26783629 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 22 Aug 2021 14:11:57 +0200 Subject: [PATCH 062/160] Move labelled dropdown from tournament to main game --- .../Screens/Setup/SetupScreen.cs | 22 ------------- .../UserInterfaceV2/LabelledDropdown.cs | 31 +++++++++++++++++++ 2 files changed, 31 insertions(+), 22 deletions(-) create mode 100644 osu.Game/Graphics/UserInterfaceV2/LabelledDropdown.cs diff --git a/osu.Game.Tournament/Screens/Setup/SetupScreen.cs b/osu.Game.Tournament/Screens/Setup/SetupScreen.cs index 5d8f0405ca..f6d28c15e0 100644 --- a/osu.Game.Tournament/Screens/Setup/SetupScreen.cs +++ b/osu.Game.Tournament/Screens/Setup/SetupScreen.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.Drawing; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Configuration; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Online.API; using osu.Game.Overlays; @@ -131,25 +129,5 @@ namespace osu.Game.Tournament.Screens.Setup resolution.Value = $"{ScreenSpaceDrawQuad.Width:N0}x{ScreenSpaceDrawQuad.Height:N0}"; } - - public class LabelledDropdown : LabelledComponent, T> - { - public LabelledDropdown() - : base(true) - { - } - - public IEnumerable Items - { - get => Component.Items; - set => Component.Items = value; - } - - protected override OsuDropdown CreateComponent() => new OsuDropdown - { - RelativeSizeAxes = Axes.X, - Width = 0.5f, - }; - } } } diff --git a/osu.Game/Graphics/UserInterfaceV2/LabelledDropdown.cs b/osu.Game/Graphics/UserInterfaceV2/LabelledDropdown.cs new file mode 100644 index 0000000000..44f09f13eb --- /dev/null +++ b/osu.Game/Graphics/UserInterfaceV2/LabelledDropdown.cs @@ -0,0 +1,31 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using osu.Framework.Graphics; +using osu.Game.Graphics.UserInterface; + +namespace osu.Game.Graphics.UserInterfaceV2 +{ + public class LabelledDropdown : LabelledComponent, TItem> + { + public LabelledDropdown() + : base(true) + { + } + + public IEnumerable Items + { + get => Component.Items; + set => Component.Items = value; + } + + protected sealed override OsuDropdown CreateComponent() => CreateDropdown().With(d => + { + d.RelativeSizeAxes = Axes.X; + d.Width = 0.5f; + }); + + protected virtual OsuDropdown CreateDropdown() => new OsuDropdown(); + } +} From a6d09b0bb0d4f05533baf2b658b22076e4791c11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 22 Aug 2021 14:12:10 +0200 Subject: [PATCH 063/160] Add labelled enum dropdown variant --- .../UserInterfaceV2/LabelledEnumDropdown.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 osu.Game/Graphics/UserInterfaceV2/LabelledEnumDropdown.cs diff --git a/osu.Game/Graphics/UserInterfaceV2/LabelledEnumDropdown.cs b/osu.Game/Graphics/UserInterfaceV2/LabelledEnumDropdown.cs new file mode 100644 index 0000000000..b818c394ae --- /dev/null +++ b/osu.Game/Graphics/UserInterfaceV2/LabelledEnumDropdown.cs @@ -0,0 +1,14 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Game.Graphics.UserInterface; + +namespace osu.Game.Graphics.UserInterfaceV2 +{ + public class LabelledEnumDropdown : LabelledDropdown + where TEnum : struct, Enum + { + protected override OsuDropdown CreateDropdown() => new OsuEnumDropdown(); + } +} From 89429021c999913a057c73d8d3eb263c02ff6bdd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 22 Aug 2021 14:12:17 +0200 Subject: [PATCH 064/160] Add test scene for labelled dropdowns --- .../TestSceneLabelledDropdown.cs | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 osu.Game.Tests/Visual/UserInterface/TestSceneLabelledDropdown.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledDropdown.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledDropdown.cs new file mode 100644 index 0000000000..4b74e37ec4 --- /dev/null +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledDropdown.cs @@ -0,0 +1,34 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; +using osu.Game.Beatmaps; +using osu.Game.Graphics.UserInterfaceV2; + +namespace osu.Game.Tests.Visual.UserInterface +{ + public class TestSceneLabelledDropdown : OsuTestScene + { + [Test] + public void TestLabelledDropdown() + => AddStep(@"create dropdown", () => Child = new LabelledDropdown + { + Label = @"Countdown speed", + Items = new[] + { + @"Half", + @"Normal", + @"Double" + }, + Description = @"This is a description" + }); + + [Test] + public void TestLabelledEnumDropdown() + => AddStep(@"create dropdown", () => Child = new LabelledEnumDropdown + { + Label = @"Beatmap status", + Description = @"This is a description" + }); + } +} From 48e56adcfe500c6f72d653aa170ea1dbcd2f817a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 22 Aug 2021 15:53:30 +0200 Subject: [PATCH 065/160] Add labelled number box control --- .../Graphics/UserInterfaceV2/LabelledNumberBox.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 osu.Game/Graphics/UserInterfaceV2/LabelledNumberBox.cs diff --git a/osu.Game/Graphics/UserInterfaceV2/LabelledNumberBox.cs b/osu.Game/Graphics/UserInterfaceV2/LabelledNumberBox.cs new file mode 100644 index 0000000000..ca247ab679 --- /dev/null +++ b/osu.Game/Graphics/UserInterfaceV2/LabelledNumberBox.cs @@ -0,0 +1,12 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Graphics.UserInterface; + +namespace osu.Game.Graphics.UserInterfaceV2 +{ + public class LabelledNumberBox : LabelledTextBox + { + protected override OsuTextBox CreateTextBox() => new OsuNumberBox(); + } +} From eec9f6d19168adbc563b443ab0429c8aa7bdb2ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 22 Aug 2021 15:53:48 +0200 Subject: [PATCH 066/160] Add countdown settings to design section --- osu.Game/Screens/Edit/Setup/DesignSection.cs | 64 ++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/osu.Game/Screens/Edit/Setup/DesignSection.cs b/osu.Game/Screens/Edit/Setup/DesignSection.cs index 68aaf3dd76..633196ff2e 100644 --- a/osu.Game/Screens/Edit/Setup/DesignSection.cs +++ b/osu.Game/Screens/Edit/Setup/DesignSection.cs @@ -1,14 +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 System; +using System.Globalization; +using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Localisation; +using osu.Game.Beatmaps; using osu.Game.Graphics.UserInterfaceV2; +using osuTK; namespace osu.Game.Screens.Edit.Setup { internal class DesignSection : SetupSection { + private const float fade_duration = 250; + + private LabelledSwitchButton enableCountdown; + private FillFlowContainer countdownSettings; + private LabelledEnumDropdown countdownSpeed; + private LabelledNumberBox countdownOffset; + private LabelledSwitchButton widescreenSupport; private LabelledSwitchButton epilepsyWarning; private LabelledSwitchButton letterboxDuringBreaks; @@ -20,6 +34,35 @@ namespace osu.Game.Screens.Edit.Setup { Children = new[] { + enableCountdown = new LabelledSwitchButton + { + Label = "Enable countdown", + Current = { Value = Beatmap.BeatmapInfo.Countdown != CountdownType.None }, + Description = "If enabled, an \"Are you ready? 3, 2, 1, GO!\" countdown will be inserted at the beginning of the beatmap, assuming there is enough time to do so." + }, + countdownSettings = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Spacing = new Vector2(10), + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + countdownSpeed = new LabelledEnumDropdown + { + Label = "Countdown speed", + Current = { Value = Beatmap.BeatmapInfo.Countdown != CountdownType.None ? Beatmap.BeatmapInfo.Countdown : CountdownType.Normal }, + Items = Enum.GetValues(typeof(CountdownType)).Cast().Where(type => type != CountdownType.None) + }, + countdownOffset = new LabelledNumberBox + { + Label = "Countdown offset", + Current = { Value = Beatmap.BeatmapInfo.CountdownOffset.ToString() }, + Description = "If the countdown sounds off-time, use this to make it appear one or more beats early.", + } + } + }, + Empty(), widescreenSupport = new LabelledSwitchButton { Label = "Widescreen support", @@ -45,13 +88,34 @@ namespace osu.Game.Screens.Edit.Setup { base.LoadComplete(); + enableCountdown.Current.BindValueChanged(_ => updateCountdownSettingsVisibility(), true); + countdownSettings.FinishTransforms(true); + + enableCountdown.Current.BindValueChanged(_ => updateBeatmap()); + countdownSpeed.Current.BindValueChanged(_ => updateBeatmap()); + countdownOffset.OnCommit += (_, __) => updateBeatmap(); + widescreenSupport.Current.BindValueChanged(_ => updateBeatmap()); epilepsyWarning.Current.BindValueChanged(_ => updateBeatmap()); letterboxDuringBreaks.Current.BindValueChanged(_ => updateBeatmap()); } + private void updateCountdownSettingsVisibility() + { + bool countdownEnabled = enableCountdown.Current.Value; + + foreach (var child in countdownSettings) + { + child.ScaleTo(new Vector2(1, countdownEnabled ? 1 : 0), fade_duration, Easing.OutQuint) + .FadeTo(countdownEnabled ? 1 : 0, fade_duration, Easing.OutQuint); + } + } + private void updateBeatmap() { + Beatmap.BeatmapInfo.Countdown = enableCountdown.Current.Value ? countdownSpeed.Current.Value : CountdownType.None; + Beatmap.BeatmapInfo.CountdownOffset = int.TryParse(countdownOffset.Current.Value, NumberStyles.None, CultureInfo.InvariantCulture, out int offset) ? offset : 0; + Beatmap.BeatmapInfo.WidescreenStoryboard = widescreenSupport.Current.Value; Beatmap.BeatmapInfo.EpilepsyWarning = epilepsyWarning.Current.Value; Beatmap.BeatmapInfo.LetterboxInBreaks = letterboxDuringBreaks.Current.Value; From b43ee2d61c20a20c9ff464b2a2a15c9ff0e23579 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 29 Aug 2021 17:20:25 +0200 Subject: [PATCH 067/160] Add descriptions to enum members --- osu.Game/Beatmaps/CountdownType.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/osu.Game/Beatmaps/CountdownType.cs b/osu.Game/Beatmaps/CountdownType.cs index 1831b4576b..73f85bf701 100644 --- a/osu.Game/Beatmaps/CountdownType.cs +++ b/osu.Game/Beatmaps/CountdownType.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.ComponentModel; + namespace osu.Game.Beatmaps { /// @@ -9,8 +11,14 @@ namespace osu.Game.Beatmaps public enum CountdownType { None = 0, + + [Description("Normal")] Normal = 1, + + [Description("Half speed")] HalfSpeed = 2, + + [Description("Double speed")] DoubleSpeed = 3 } } From ddf9d2aa6ca18d1212e8bd97c8743aacac026d88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 29 Aug 2021 18:01:40 +0200 Subject: [PATCH 068/160] Add test coverage --- .../Visual/Editing/TestSceneDesignSection.cs | 97 +++++++++++++++++++ osu.Game/Screens/Edit/Setup/DesignSection.cs | 36 ++++--- 2 files changed, 119 insertions(+), 14 deletions(-) create mode 100644 osu.Game.Tests/Visual/Editing/TestSceneDesignSection.cs diff --git a/osu.Game.Tests/Visual/Editing/TestSceneDesignSection.cs b/osu.Game.Tests/Visual/Editing/TestSceneDesignSection.cs new file mode 100644 index 0000000000..d84ffa1052 --- /dev/null +++ b/osu.Game.Tests/Visual/Editing/TestSceneDesignSection.cs @@ -0,0 +1,97 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Globalization; +using System.Linq; +using NUnit.Framework; +using osu.Framework.Graphics; +using osu.Framework.Graphics.UserInterface; +using osu.Framework.Testing; +using osu.Game.Beatmaps; +using osu.Game.Graphics.UserInterfaceV2; +using osu.Game.Screens.Edit; +using osu.Game.Screens.Edit.Setup; +using osuTK.Input; + +namespace osu.Game.Tests.Visual.Editing +{ + public class TestSceneDesignSection : OsuManualInputManagerTestScene + { + private TestDesignSection designSection; + private EditorBeatmap editorBeatmap { get; set; } + + [SetUpSteps] + public void SetUp() + { + AddStep("create blank beatmap", () => editorBeatmap = new EditorBeatmap(new Beatmap())); + AddStep("create section", () => Child = new DependencyProvidingContainer + { + RelativeSizeAxes = Axes.Both, + CachedDependencies = new (Type, object)[] + { + (typeof(EditorBeatmap), editorBeatmap) + }, + Child = designSection = new TestDesignSection() + }); + } + + [Test] + public void TestCountdownOff() + { + AddStep("turn countdown off", () => designSection.EnableCountdown.Current.Value = false); + + AddAssert("beatmap has correct type", () => editorBeatmap.BeatmapInfo.Countdown == CountdownType.None); + AddUntilStep("other controls hidden", () => !designSection.CountdownSpeed.IsPresent && !designSection.CountdownOffset.IsPresent); + } + + [Test] + public void TestCountdownOn() + { + AddStep("turn countdown on", () => designSection.EnableCountdown.Current.Value = true); + + AddAssert("beatmap has correct type", () => editorBeatmap.BeatmapInfo.Countdown == CountdownType.Normal); + AddUntilStep("other controls shown", () => designSection.CountdownSpeed.IsPresent && designSection.CountdownOffset.IsPresent); + + AddStep("change countdown speed", () => designSection.CountdownSpeed.Current.Value = CountdownType.DoubleSpeed); + + AddAssert("beatmap has correct type", () => editorBeatmap.BeatmapInfo.Countdown == CountdownType.DoubleSpeed); + AddUntilStep("other controls still shown", () => designSection.CountdownSpeed.IsPresent && designSection.CountdownOffset.IsPresent); + } + + [Test] + public void TestCountdownOffset() + { + AddStep("turn countdown on", () => designSection.EnableCountdown.Current.Value = true); + + AddAssert("beatmap has correct type", () => editorBeatmap.BeatmapInfo.Countdown == CountdownType.Normal); + + checkOffsetAfter("1", 1); + checkOffsetAfter(string.Empty, 0); + checkOffsetAfter("123", 123); + checkOffsetAfter("0", 0); + } + + private void checkOffsetAfter(string userInput, int expectedFinalValue) + { + AddStep("click text box", () => + { + var textBox = designSection.CountdownOffset.ChildrenOfType().Single(); + InputManager.MoveMouseTo(textBox); + InputManager.Click(MouseButton.Left); + }); + AddStep("set offset text", () => designSection.CountdownOffset.Current.Value = userInput); + AddStep("commit text", () => InputManager.Key(Key.Enter)); + + AddAssert($"displayed value is {expectedFinalValue}", () => designSection.CountdownOffset.Current.Value == expectedFinalValue.ToString(CultureInfo.InvariantCulture)); + AddAssert($"beatmap value is {expectedFinalValue}", () => editorBeatmap.BeatmapInfo.CountdownOffset == expectedFinalValue); + } + + private class TestDesignSection : DesignSection + { + public new LabelledSwitchButton EnableCountdown => base.EnableCountdown; + public new LabelledEnumDropdown CountdownSpeed => base.CountdownSpeed; + public new LabelledNumberBox CountdownOffset => base.CountdownOffset; + } + } +} diff --git a/osu.Game/Screens/Edit/Setup/DesignSection.cs b/osu.Game/Screens/Edit/Setup/DesignSection.cs index 633196ff2e..d030c2a894 100644 --- a/osu.Game/Screens/Edit/Setup/DesignSection.cs +++ b/osu.Game/Screens/Edit/Setup/DesignSection.cs @@ -18,15 +18,16 @@ namespace osu.Game.Screens.Edit.Setup { private const float fade_duration = 250; - private LabelledSwitchButton enableCountdown; - private FillFlowContainer countdownSettings; - private LabelledEnumDropdown countdownSpeed; - private LabelledNumberBox countdownOffset; + protected LabelledSwitchButton EnableCountdown; + protected LabelledEnumDropdown CountdownSpeed; + protected LabelledNumberBox CountdownOffset; private LabelledSwitchButton widescreenSupport; private LabelledSwitchButton epilepsyWarning; private LabelledSwitchButton letterboxDuringBreaks; + private FillFlowContainer countdownSettings; + public override LocalisableString Title => "Design"; [BackgroundDependencyLoader] @@ -34,7 +35,7 @@ namespace osu.Game.Screens.Edit.Setup { Children = new[] { - enableCountdown = new LabelledSwitchButton + EnableCountdown = new LabelledSwitchButton { Label = "Enable countdown", Current = { Value = Beatmap.BeatmapInfo.Countdown != CountdownType.None }, @@ -48,13 +49,13 @@ namespace osu.Game.Screens.Edit.Setup Direction = FillDirection.Vertical, Children = new Drawable[] { - countdownSpeed = new LabelledEnumDropdown + CountdownSpeed = new LabelledEnumDropdown { Label = "Countdown speed", Current = { Value = Beatmap.BeatmapInfo.Countdown != CountdownType.None ? Beatmap.BeatmapInfo.Countdown : CountdownType.Normal }, Items = Enum.GetValues(typeof(CountdownType)).Cast().Where(type => type != CountdownType.None) }, - countdownOffset = new LabelledNumberBox + CountdownOffset = new LabelledNumberBox { Label = "Countdown offset", Current = { Value = Beatmap.BeatmapInfo.CountdownOffset.ToString() }, @@ -88,12 +89,12 @@ namespace osu.Game.Screens.Edit.Setup { base.LoadComplete(); - enableCountdown.Current.BindValueChanged(_ => updateCountdownSettingsVisibility(), true); + EnableCountdown.Current.BindValueChanged(_ => updateCountdownSettingsVisibility(), true); countdownSettings.FinishTransforms(true); - enableCountdown.Current.BindValueChanged(_ => updateBeatmap()); - countdownSpeed.Current.BindValueChanged(_ => updateBeatmap()); - countdownOffset.OnCommit += (_, __) => updateBeatmap(); + EnableCountdown.Current.BindValueChanged(_ => updateBeatmap()); + CountdownSpeed.Current.BindValueChanged(_ => updateBeatmap()); + CountdownOffset.OnCommit += (_, __) => onOffsetCommitted(); widescreenSupport.Current.BindValueChanged(_ => updateBeatmap()); epilepsyWarning.Current.BindValueChanged(_ => updateBeatmap()); @@ -102,7 +103,7 @@ namespace osu.Game.Screens.Edit.Setup private void updateCountdownSettingsVisibility() { - bool countdownEnabled = enableCountdown.Current.Value; + bool countdownEnabled = EnableCountdown.Current.Value; foreach (var child in countdownSettings) { @@ -111,10 +112,17 @@ namespace osu.Game.Screens.Edit.Setup } } + private void onOffsetCommitted() + { + updateBeatmap(); + // update displayed text to ensure parsed value matches display (i.e. if empty string was provided). + CountdownOffset.Current.Value = Beatmap.BeatmapInfo.CountdownOffset.ToString(CultureInfo.InvariantCulture); + } + private void updateBeatmap() { - Beatmap.BeatmapInfo.Countdown = enableCountdown.Current.Value ? countdownSpeed.Current.Value : CountdownType.None; - Beatmap.BeatmapInfo.CountdownOffset = int.TryParse(countdownOffset.Current.Value, NumberStyles.None, CultureInfo.InvariantCulture, out int offset) ? offset : 0; + Beatmap.BeatmapInfo.Countdown = EnableCountdown.Current.Value ? CountdownSpeed.Current.Value : CountdownType.None; + Beatmap.BeatmapInfo.CountdownOffset = int.TryParse(CountdownOffset.Current.Value, NumberStyles.None, CultureInfo.InvariantCulture, out int offset) ? offset : 0; Beatmap.BeatmapInfo.WidescreenStoryboard = widescreenSupport.Current.Value; Beatmap.BeatmapInfo.EpilepsyWarning = epilepsyWarning.Current.Value; From 570d36fde79c9a0d8d0aa950a1e990fad40b96fa Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Mon, 30 Aug 2021 20:53:43 -0700 Subject: [PATCH 069/160] Make toolbar handle mouse events instead --- .../Visual/Navigation/TestSceneScreenNavigation.cs | 4 +++- osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs | 4 ++-- osu.Game/Overlays/Toolbar/Toolbar.cs | 6 ++---- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs index a112534837..b536233ff0 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs @@ -345,7 +345,9 @@ namespace osu.Game.Tests.Visual.Navigation AddStep("press left mouse button", () => InputManager.PressButton(MouseButton.Left)); AddStep("move cursor to toolbar", () => InputManager.MoveMouseTo(Game.Toolbar.ScreenSpaceDrawQuad.Centre)); AddStep("release left mouse button", () => InputManager.ReleaseButton(MouseButton.Left)); - AddAssert("now playing is still visible", () => nowPlayingOverlay.State.Value == Visibility.Visible); + AddAssert("now playing is hidden", () => nowPlayingOverlay.State.Value == Visibility.Hidden); + + AddStep("press now playing hotkey", () => InputManager.Key(Key.F6)); // toolbar -> background AddStep("press left mouse button", () => InputManager.PressButton(MouseButton.Left)); diff --git a/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs b/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs index 0e635d26c2..b9b098df80 100644 --- a/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs +++ b/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs @@ -75,14 +75,14 @@ namespace osu.Game.Graphics.Containers protected override bool OnMouseDown(MouseDownEvent e) { - closeOnMouseUp = !base.ReceivePositionalInputAt(e.ScreenSpaceMousePosition) && (game?.Toolbar.IsHovered == false); + closeOnMouseUp = !base.ReceivePositionalInputAt(e.ScreenSpaceMousePosition); return base.OnMouseDown(e); } protected override void OnMouseUp(MouseUpEvent e) { - if (closeOnMouseUp && !base.ReceivePositionalInputAt(e.ScreenSpaceMousePosition) && (game?.Toolbar.IsHovered == false)) + if (closeOnMouseUp && !base.ReceivePositionalInputAt(e.ScreenSpaceMousePosition)) Hide(); base.OnMouseUp(e); diff --git a/osu.Game/Overlays/Toolbar/Toolbar.cs b/osu.Game/Overlays/Toolbar/Toolbar.cs index 918e3b7105..2664301a0c 100644 --- a/osu.Game/Overlays/Toolbar/Toolbar.cs +++ b/osu.Game/Overlays/Toolbar/Toolbar.cs @@ -41,8 +41,7 @@ namespace osu.Game.Overlays.Toolbar // Toolbar and its components need keyboard input even when hidden. public override bool PropagateNonPositionalInputSubTree => true; - // IsHovered is used - public override bool HandlePositionalInput => true; + protected override bool Handle(UIEvent e) => e is MouseEvent; public Toolbar() { @@ -143,13 +142,12 @@ namespace osu.Game.Overlays.Toolbar protected override bool OnHover(HoverEvent e) { gradientBackground.FadeIn(transition_time, Easing.OutQuint); - return base.OnHover(e); + return true; } protected override void OnHoverLost(HoverLostEvent e) { gradientBackground.FadeOut(transition_time, Easing.OutQuint); - base.OnHoverLost(e); } } From 529a9a6ff87eb5a121911502765fb83e515e7462 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 31 Aug 2021 14:08:21 +0900 Subject: [PATCH 070/160] Adjust minimum triangle movement speed to avoid "static" triangles in logo Closes #14584. --- .../Visual/UserInterface/TestSceneOsuLogo.cs | 25 +++++++++++++++++++ osu.Game/Graphics/Backgrounds/Triangles.cs | 2 +- 2 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 osu.Game.Tests/Visual/UserInterface/TestSceneOsuLogo.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneOsuLogo.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneOsuLogo.cs new file mode 100644 index 0000000000..8b91339479 --- /dev/null +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneOsuLogo.cs @@ -0,0 +1,25 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; +using osu.Framework.Graphics; +using osu.Game.Screens.Menu; + +namespace osu.Game.Tests.Visual.UserInterface +{ + public class TestSceneOsuLogo : OsuTestScene + { + [Test] + public void TestBasic() + { + AddStep("Add logo", () => + { + Child = new OsuLogo + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }; + }); + } + } +} diff --git a/osu.Game/Graphics/Backgrounds/Triangles.cs b/osu.Game/Graphics/Backgrounds/Triangles.cs index 269360c492..35c48a50d0 100644 --- a/osu.Game/Graphics/Backgrounds/Triangles.cs +++ b/osu.Game/Graphics/Backgrounds/Triangles.cs @@ -153,7 +153,7 @@ namespace osu.Game.Graphics.Backgrounds TriangleParticle newParticle = parts[i]; // Scale moved distance by the size of the triangle. Smaller triangles should move more slowly. - newParticle.Position.Y += parts[i].Scale * movedDistance; + newParticle.Position.Y += Math.Max(0.5f, parts[i].Scale) * movedDistance; newParticle.Colour.A = adjustedAlpha; parts[i] = newParticle; From c25ab6835caf9aaf1708807b42afeeb6b140b270 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 31 Aug 2021 14:38:35 +0900 Subject: [PATCH 071/160] Remove IJsonSerializable interface Was pretty pointless and made it hard to use the custom serialisation terms arbitrarily in tests. --- osu.Game/Beatmaps/BeatmapInfo.cs | 3 +-- osu.Game/Beatmaps/IBeatmap.cs | 3 +-- ...JsonSerializable.cs => JsonSerializableExtensions.cs} | 9 +-------- osu.Game/Rulesets/Mods/Mod.cs | 3 +-- osu.Game/Rulesets/Timing/MultiplierControlPoint.cs | 3 +-- osu.Game/Screens/Edit/ClipboardContent.cs | 3 +-- osu.Game/Screens/Play/HUD/SkinnableInfo.cs | 3 +-- 7 files changed, 7 insertions(+), 20 deletions(-) rename osu.Game/IO/Serialization/{IJsonSerializable.cs => JsonSerializableExtensions.cs} (76%) diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index 353636c8af..3eb766a667 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -10,7 +10,6 @@ using Newtonsoft.Json; using osu.Framework.Localisation; using osu.Framework.Testing; using osu.Game.Database; -using osu.Game.IO.Serialization; using osu.Game.Rulesets; using osu.Game.Scoring; @@ -18,7 +17,7 @@ namespace osu.Game.Beatmaps { [ExcludeFromDynamicCompile] [Serializable] - public class BeatmapInfo : IEquatable, IJsonSerializable, IHasPrimaryKey + public class BeatmapInfo : IEquatable, IHasPrimaryKey { public int ID { get; set; } diff --git a/osu.Game/Beatmaps/IBeatmap.cs b/osu.Game/Beatmaps/IBeatmap.cs index 769b33009a..f61dd269e1 100644 --- a/osu.Game/Beatmaps/IBeatmap.cs +++ b/osu.Game/Beatmaps/IBeatmap.cs @@ -4,12 +4,11 @@ using System.Collections.Generic; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.Timing; -using osu.Game.IO.Serialization; using osu.Game.Rulesets.Objects; namespace osu.Game.Beatmaps { - public interface IBeatmap : IJsonSerializable + public interface IBeatmap { /// /// This beatmap's info. diff --git a/osu.Game/IO/Serialization/IJsonSerializable.cs b/osu.Game/IO/Serialization/JsonSerializableExtensions.cs similarity index 76% rename from osu.Game/IO/Serialization/IJsonSerializable.cs rename to osu.Game/IO/Serialization/JsonSerializableExtensions.cs index c8d5ce39a6..5b47d0bad1 100644 --- a/osu.Game/IO/Serialization/IJsonSerializable.cs +++ b/osu.Game/IO/Serialization/JsonSerializableExtensions.cs @@ -7,21 +7,14 @@ using osu.Framework.IO.Serialization; namespace osu.Game.IO.Serialization { - public interface IJsonSerializable - { - } - public static class JsonSerializableExtensions { - public static string Serialize(this IJsonSerializable obj) => JsonConvert.SerializeObject(obj, CreateGlobalSettings()); + public static string Serialize(this object obj) => JsonConvert.SerializeObject(obj, CreateGlobalSettings()); public static T Deserialize(this string objString) => JsonConvert.DeserializeObject(objString, CreateGlobalSettings()); public static void DeserializeInto(this string objString, T target) => JsonConvert.PopulateObject(objString, target, CreateGlobalSettings()); - /// - /// Creates the default that should be used for all s. - /// public static JsonSerializerSettings CreateGlobalSettings() => new JsonSerializerSettings { ReferenceLoopHandling = ReferenceLoopHandling.Ignore, diff --git a/osu.Game/Rulesets/Mods/Mod.cs b/osu.Game/Rulesets/Mods/Mod.cs index 9f3b5eaf5b..1199d8a956 100644 --- a/osu.Game/Rulesets/Mods/Mod.cs +++ b/osu.Game/Rulesets/Mods/Mod.cs @@ -11,7 +11,6 @@ using osu.Framework.Extensions.TypeExtensions; using osu.Framework.Graphics.Sprites; using osu.Framework.Testing; using osu.Game.Configuration; -using osu.Game.IO.Serialization; using osu.Game.Rulesets.UI; using osu.Game.Utils; @@ -21,7 +20,7 @@ namespace osu.Game.Rulesets.Mods /// The base class for gameplay modifiers. /// [ExcludeFromDynamicCompile] - public abstract class Mod : IMod, IEquatable, IJsonSerializable, IDeepCloneable + public abstract class Mod : IMod, IEquatable, IDeepCloneable { /// /// The name of this mod. diff --git a/osu.Game/Rulesets/Timing/MultiplierControlPoint.cs b/osu.Game/Rulesets/Timing/MultiplierControlPoint.cs index 4b3c3f90f0..dcd2cc8b55 100644 --- a/osu.Game/Rulesets/Timing/MultiplierControlPoint.cs +++ b/osu.Game/Rulesets/Timing/MultiplierControlPoint.cs @@ -3,14 +3,13 @@ using System; using osu.Game.Beatmaps.ControlPoints; -using osu.Game.IO.Serialization; namespace osu.Game.Rulesets.Timing { /// /// A control point which adds an aggregated multiplier based on the provided 's BeatLength and 's SpeedMultiplier. /// - public class MultiplierControlPoint : IJsonSerializable, IComparable + public class MultiplierControlPoint : IComparable { /// /// The time in milliseconds at which this starts. diff --git a/osu.Game/Screens/Edit/ClipboardContent.cs b/osu.Game/Screens/Edit/ClipboardContent.cs index b2edbedccc..0348a7c15d 100644 --- a/osu.Game/Screens/Edit/ClipboardContent.cs +++ b/osu.Game/Screens/Edit/ClipboardContent.cs @@ -4,13 +4,12 @@ using System.Collections.Generic; using System.Linq; using Newtonsoft.Json; -using osu.Game.IO.Serialization; using osu.Game.IO.Serialization.Converters; using osu.Game.Rulesets.Objects; namespace osu.Game.Screens.Edit { - public class ClipboardContent : IJsonSerializable + public class ClipboardContent { [JsonConverter(typeof(TypedListConverter))] public IList HitObjects; diff --git a/osu.Game/Screens/Play/HUD/SkinnableInfo.cs b/osu.Game/Screens/Play/HUD/SkinnableInfo.cs index b64e5ca98f..a2b84c79af 100644 --- a/osu.Game/Screens/Play/HUD/SkinnableInfo.cs +++ b/osu.Game/Screens/Play/HUD/SkinnableInfo.cs @@ -8,7 +8,6 @@ using Newtonsoft.Json; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Extensions; -using osu.Game.IO.Serialization; using osu.Game.Skinning; using osuTK; @@ -18,7 +17,7 @@ namespace osu.Game.Screens.Play.HUD /// Serialised information governing custom changes to an . /// [Serializable] - public class SkinnableInfo : IJsonSerializable + public class SkinnableInfo { public Type Type { get; set; } From eb21ed08f89a06b76de8b45a45886d80365bf9c2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 31 Aug 2021 14:51:14 +0900 Subject: [PATCH 072/160] Update test to only compare `HitObject`s --- .../Formats/LegacyBeatmapEncoderTest.cs | 72 +++++++++++-------- 1 file changed, 41 insertions(+), 31 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs index 85b8a3d190..896aa53f82 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs @@ -66,6 +66,47 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.IsTrue(areComboColoursEqual(decodedAfterEncode.beatmapSkin.Configuration, decoded.beatmapSkin.Configuration)); } + [TestCaseSource(nameof(allBeatmaps))] + public void TestEncodeDecodeStabilityWithNonLegacyControlPoints(string name) + { + var decoded = decodeFromLegacy(beatmaps_resource_store.GetStream(name), name); + + // we are testing that the transfer of relevant data to hitobjects (from legacy control points) sticks through encode/decode. + // before the encode step, the legacy information is removed here. + decoded.beatmap.ControlPointInfo = removeLegacyControlPointTypes(decoded.beatmap.ControlPointInfo); + + var decodedAfterEncode = decodeFromLegacy(encodeToLegacy(decoded), name); + + // in this process, we may lose some detail in the control points section. + // let's focus on only the hitobjects. + var originalHitObjects = decoded.beatmap.HitObjects.Serialize(); + var newHitObjects = decodedAfterEncode.beatmap.HitObjects.Serialize(); + + Assert.That(newHitObjects, Is.EqualTo(originalHitObjects)); + + ControlPointInfo removeLegacyControlPointTypes(ControlPointInfo controlPointInfo) + { + // emulate non-legacy control points by cloning the non-legacy portion. + // the assertion is that the encoder can recreate this losslessly from hitobject data. + Assert.IsInstanceOf(controlPointInfo); + + var newControlPoints = new ControlPointInfo(); + + foreach (var point in controlPointInfo.AllControlPoints) + { + // completely ignore "legacy" types, which have been moved to HitObjects. + // even though these would mostly be ignored by the Add call, they will still be available in groups, + // which isn't what we want to be testing here. + if (point is SampleControlPoint) + continue; + + newControlPoints.Add(point.Time, point.DeepClone()); + } + + return newControlPoints; + } + } + [Test] public void TestEncodeMultiSegmentSliderWithFloatingPointError() { @@ -93,37 +134,6 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.That(decodedSlider.Path.ControlPoints.Count, Is.EqualTo(5)); } - [TestCaseSource(nameof(allBeatmaps))] - public void TestEncodeDecodeStabilityWithNonLegacyControlPoints(string name) - { - var decoded = decodeFromLegacy(beatmaps_resource_store.GetStream(name), name); - - sort(decoded.beatmap); - - var originalSerialized = decoded.beatmap.Serialize(); - var encoded = encodeToLegacy(decoded); - - Assert.AreEqual(typeof(LegacyControlPointInfo), decoded.beatmap.ControlPointInfo.GetType()); - - // emulate non-legacy control points by cloning the non-legacy portion. - // the assertion is that the encoder can recreate this losslessly from hitobject data. - var controlPointInfo = new ControlPointInfo(); - - foreach (var point in decoded.beatmap.ControlPointInfo.AllControlPoints) - controlPointInfo.Add(point.Time, point.DeepClone()); - - decoded.beatmap.ControlPointInfo = controlPointInfo; - - Assert.AreNotEqual(typeof(LegacyControlPointInfo), decoded.beatmap.ControlPointInfo.GetType()); - - var decodedAfterEncode = decodeFromLegacy(encoded, name); - - sort(decodedAfterEncode.beatmap); - - Assert.That(decodedAfterEncode.beatmap.Serialize(), Is.EqualTo(originalSerialized)); - Assert.IsTrue(areComboColoursEqual(decodedAfterEncode.beatmapSkin.Configuration, decoded.beatmapSkin.Configuration)); - } - private bool areComboColoursEqual(IHasComboColours a, IHasComboColours b) { // equal to null, no need to SequenceEqual From 9fa8bee094cb24fe5656193d0d60a5b5172c6844 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 31 Aug 2021 14:51:19 +0900 Subject: [PATCH 073/160] Remove outdated TODO --- osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs index 461611da3b..75d9a56f3e 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs @@ -172,7 +172,7 @@ namespace osu.Game.Beatmaps.Formats writer.WriteLine("[TimingPoints]"); - if (!(beatmap.ControlPointInfo is LegacyControlPointInfo)) // todo: always run this? probably no harm. + if (!(beatmap.ControlPointInfo is LegacyControlPointInfo)) { var legacyControlPoints = new LegacyControlPointInfo(); From 448c58c35d0aec96e9a3c91323f69564ddcb8c89 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 31 Aug 2021 15:08:07 +0900 Subject: [PATCH 074/160] Remove unnecessary variable discard MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bartłomiej Dach --- osu.Game/Beatmaps/Legacy/LegacyControlPointInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/Legacy/LegacyControlPointInfo.cs b/osu.Game/Beatmaps/Legacy/LegacyControlPointInfo.cs index 5152549bed..ff0ca5ebe1 100644 --- a/osu.Game/Beatmaps/Legacy/LegacyControlPointInfo.cs +++ b/osu.Game/Beatmaps/Legacy/LegacyControlPointInfo.cs @@ -34,7 +34,7 @@ namespace osu.Game.Beatmaps.Legacy protected override bool CheckAlreadyExisting(double time, ControlPoint newPoint) { - if (newPoint is SampleControlPoint _) + if (newPoint is SampleControlPoint) { var existing = BinarySearch(SamplePoints, time); return newPoint.IsRedundant(existing); From d988aa168001290604dcb0f1951886b5012c55ee Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 31 Aug 2021 15:05:10 +0900 Subject: [PATCH 075/160] Actually serialise `SampleControlPoint`s along with `HitObject`s --- osu.Game/Beatmaps/ControlPoints/ControlPoint.cs | 2 ++ osu.Game/Rulesets/Objects/HitObject.cs | 1 - 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs index 643c5d9adb..8203f2e968 100644 --- a/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using Newtonsoft.Json; using osu.Game.Graphics; using osu.Game.Utils; using osuTK.Graphics; @@ -13,6 +14,7 @@ namespace osu.Game.Beatmaps.ControlPoints /// /// The time at which the control point takes effect. /// + [JsonIgnore] public double Time => controlPointGroup?.Time ?? 0; private ControlPointGroup controlPointGroup; diff --git a/osu.Game/Rulesets/Objects/HitObject.cs b/osu.Game/Rulesets/Objects/HitObject.cs index fb1b6cd267..c4b9e6e1ad 100644 --- a/osu.Game/Rulesets/Objects/HitObject.cs +++ b/osu.Game/Rulesets/Objects/HitObject.cs @@ -66,7 +66,6 @@ namespace osu.Game.Rulesets.Objects } } - [JsonIgnore] public SampleControlPoint SampleControlPoint; /// From a2546243737d3b4d9f4f10f8af215a4e42645fb1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 31 Aug 2021 17:18:04 +0900 Subject: [PATCH 076/160] Avoid performing beatmap metadata lookups when entering the editor If none of the lookup parameters are available, skip the lookup completely. --- osu.Game/Beatmaps/BeatmapManager_BeatmapOnlineLookupQueue.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game/Beatmaps/BeatmapManager_BeatmapOnlineLookupQueue.cs b/osu.Game/Beatmaps/BeatmapManager_BeatmapOnlineLookupQueue.cs index 7824205257..6ae7f7481e 100644 --- a/osu.Game/Beatmaps/BeatmapManager_BeatmapOnlineLookupQueue.cs +++ b/osu.Game/Beatmaps/BeatmapManager_BeatmapOnlineLookupQueue.cs @@ -153,6 +153,11 @@ namespace osu.Game.Beatmaps if (!storage.Exists(cache_database_name)) return false; + if (string.IsNullOrEmpty(beatmap.MD5Hash) + && string.IsNullOrEmpty(beatmap.Path) + && beatmap.OnlineBeatmapID == null) + return false; + try { using (var db = new SqliteConnection(storage.GetDatabaseConnectionString("online"))) From 3f93aa15079c6f593d2ad436e09e8536bf074a84 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 31 Aug 2021 20:12:40 +0900 Subject: [PATCH 077/160] Fix traceable sliders incorrectly being opaque Closes https://github.com/ppy/osu/issues/14449. Regressed in https://github.com/ppy/osu/pull/14205. --- .../Skinning/Legacy/LegacySliderBody.cs | 2 -- .../Legacy/OsuLegacySkinTransformer.cs | 21 +++++++++++++++++-- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySliderBody.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySliderBody.cs index 1c8dfeac52..7d69e5ecdc 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySliderBody.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySliderBody.cs @@ -22,8 +22,6 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy // Roughly matches osu!stable's slider border portions. => base.CalculatedBorderPortion * 0.77f; - public new Color4 AccentColour => new Color4(base.AccentColour.R, base.AccentColour.G, base.AccentColour.B, 0.7f); - protected override Color4 ColourAt(float position) { float realBorderPortion = shadow_portion + CalculatedBorderPortion; diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs index 41b0a88f11..16c770706d 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs @@ -3,9 +3,11 @@ using System; using osu.Framework.Bindables; +using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Game.Skinning; using osuTK; +using osuTK.Graphics; namespace osu.Game.Rulesets.Osu.Skinning.Legacy { @@ -118,8 +120,23 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy { switch (lookup) { - case OsuSkinColour colour: - return base.GetConfig(new SkinCustomColourLookup(colour)); + case OsuSkinColour colourLookup: + var colour = base.GetConfig(new SkinCustomColourLookup(colourLookup)); + + if (colour == null) + return null; + + switch (colourLookup) + { + case OsuSkinColour.SliderTrackOverride: + var bindableColour = ((Bindable)colour); + + // legacy skins use a constant value for slider track alpha, regardless of the source colour. + bindableColour.Value = bindableColour.Value.Opacity(0.7f); + break; + } + + return colour; case OsuSkinConfiguration osuLookup: switch (osuLookup) From 79f71e5181eb5410090769117175a03f094a265e Mon Sep 17 00:00:00 2001 From: Davran Dilshat Date: Tue, 31 Aug 2021 13:56:44 +0100 Subject: [PATCH 078/160] get user id when importing scores --- osu.Game/Scoring/ScoreManager.cs | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index 83bcac01ac..4cfcc00bb8 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -23,6 +23,7 @@ using osu.Game.Rulesets; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring.Legacy; +using osu.Game.Users; namespace osu.Game.Scoring { @@ -43,6 +44,8 @@ namespace osu.Game.Scoring [CanBeNull] private readonly OsuConfigManager configManager; + private IAPIProvider api { get; set; } + public ScoreManager(RulesetStore rulesets, Func beatmaps, Storage storage, IAPIProvider api, IDatabaseContextFactory contextFactory, IIpcHost importHost = null, Func difficulties = null, OsuConfigManager configManager = null) : base(storage, contextFactory, api, new ScoreStore(contextFactory, storage), importHost) @@ -51,6 +54,7 @@ namespace osu.Game.Scoring this.beatmaps = beatmaps; this.difficulties = difficulties; this.configManager = configManager; + this.api = api; } protected override ScoreInfo CreateModel(ArchiveReader archive) @@ -72,8 +76,31 @@ namespace osu.Game.Scoring } } + private Dictionary previouslyLookedUpUsernames = new Dictionary(); + protected override Task Populate(ScoreInfo model, ArchiveReader archive, CancellationToken cancellationToken = default) - => Task.CompletedTask; + { + // These scores only provide the user's username but we need the user's ID too. + if (model.UserID <= 1 && model.UserString != null) + { + if (previouslyLookedUpUsernames.TryGetValue(model.UserString, out User user)) + { + model.UserID = user.Id; + return Task.CompletedTask; + } + + var request = new GetUserRequest(model.UserString); + request.Success += user => + { + model.UserID = user.Id; + previouslyLookedUpUsernames.TryAdd(model.UserString, user); + }; + + api.Queue(request); + } + + return Task.CompletedTask; + } protected override void ExportModelTo(ScoreInfo model, Stream outputStream) { From 0a87b461d70ad61e423675d45f04e253bae73259 Mon Sep 17 00:00:00 2001 From: Davran Dilshat Date: Tue, 31 Aug 2021 14:11:37 +0100 Subject: [PATCH 079/160] fix code quality issues --- osu.Game/Scoring/ScoreManager.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index 4cfcc00bb8..d5d33283b3 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -76,7 +76,7 @@ namespace osu.Game.Scoring } } - private Dictionary previouslyLookedUpUsernames = new Dictionary(); + private readonly Dictionary previouslyLookedUpUsernames = new Dictionary(); protected override Task Populate(ScoreInfo model, ArchiveReader archive, CancellationToken cancellationToken = default) { @@ -90,10 +90,10 @@ namespace osu.Game.Scoring } var request = new GetUserRequest(model.UserString); - request.Success += user => + request.Success += u => { - model.UserID = user.Id; - previouslyLookedUpUsernames.TryAdd(model.UserString, user); + model.UserID = u.Id; + previouslyLookedUpUsernames.TryAdd(model.UserString, u); }; api.Queue(request); From 9288ca1191dfbbcf0099ff749890580cc050e27f Mon Sep 17 00:00:00 2001 From: Davran Dilshat Date: Tue, 31 Aug 2021 14:34:45 +0100 Subject: [PATCH 080/160] handle api is null --- osu.Game/Scoring/ScoreManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index d5d33283b3..3c99dd6637 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -96,7 +96,7 @@ namespace osu.Game.Scoring previouslyLookedUpUsernames.TryAdd(model.UserString, u); }; - api.Queue(request); + api?.Queue(request); } return Task.CompletedTask; From a190801291d79d42115f8ed03805109228cda21e Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 31 Aug 2021 19:36:27 +0300 Subject: [PATCH 081/160] Revert no longer required tooltip content changes --- osu.Game/Overlays/Mods/LocalPlayerModButton.cs | 3 +-- osu.Game/Overlays/Mods/ModButton.cs | 6 +++--- osu.Game/Overlays/Mods/ModButtonTooltip.cs | 10 ++++------ 3 files changed, 8 insertions(+), 11 deletions(-) diff --git a/osu.Game/Overlays/Mods/LocalPlayerModButton.cs b/osu.Game/Overlays/Mods/LocalPlayerModButton.cs index 1d5ddfcf06..d26bbf344d 100644 --- a/osu.Game/Overlays/Mods/LocalPlayerModButton.cs +++ b/osu.Game/Overlays/Mods/LocalPlayerModButton.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; @@ -66,7 +65,7 @@ namespace osu.Game.Overlays.Mods incompatibleIcon.Hide(); } - public override ITooltip GetCustomTooltip() => new LocalPlayerModButtonTooltip(); + public override ITooltip GetCustomTooltip() => new LocalPlayerModButtonTooltip(); private class LocalPlayerModButtonTooltip : ModButtonTooltip { diff --git a/osu.Game/Overlays/Mods/ModButton.cs b/osu.Game/Overlays/Mods/ModButton.cs index cc8acb7513..979e2c8da3 100644 --- a/osu.Game/Overlays/Mods/ModButton.cs +++ b/osu.Game/Overlays/Mods/ModButton.cs @@ -23,7 +23,7 @@ namespace osu.Game.Overlays.Mods /// /// Represents a clickable button which can cycle through one of more mods. /// - public class ModButton : ModButtonEmpty, IHasCustomTooltip + public class ModButton : ModButtonEmpty, IHasCustomTooltip { private ModIcon foregroundIcon; private ModIcon backgroundIcon; @@ -312,8 +312,8 @@ namespace osu.Game.Overlays.Mods Mod = mod; } - public virtual ITooltip GetCustomTooltip() => new ModButtonTooltip(); + public virtual ITooltip GetCustomTooltip() => new ModButtonTooltip(); - public object TooltipContent => this; + public Mod TooltipContent => SelectedMod ?? Mods.FirstOrDefault(); } } diff --git a/osu.Game/Overlays/Mods/ModButtonTooltip.cs b/osu.Game/Overlays/Mods/ModButtonTooltip.cs index 0ad479670b..2f50e38a5a 100644 --- a/osu.Game/Overlays/Mods/ModButtonTooltip.cs +++ b/osu.Game/Overlays/Mods/ModButtonTooltip.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -14,7 +13,7 @@ using osuTK; namespace osu.Game.Overlays.Mods { - public class ModButtonTooltip : VisibilityContainer, ITooltip + public class ModButtonTooltip : VisibilityContainer, ITooltip { private readonly OsuSpriteText descriptionText; private readonly Box background; @@ -61,11 +60,10 @@ namespace osu.Game.Overlays.Mods private Mod lastMod; - public virtual void SetContent(ModButton button) + public void SetContent(Mod mod) { - var mod = button.SelectedMod ?? button.Mods.First(); - - if (mod.Equals(lastMod)) return; + if (mod.Equals(lastMod)) + return; lastMod = mod; From 208f66cc76ee102be8ae06397e11c7af4f894f89 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 28 Aug 2021 19:11:44 +0300 Subject: [PATCH 082/160] Simplify user graph tooltips logic The same tooltip can be used for the rank graph, the play history graph, and the replay history graph. The only difference between those three is the displayed label, which has now been included as part of the `TooltipContent`, rather than unnecessarily recreating tooltips just for different sprite texts. --- .../Profile/Header/Components/RankGraph.cs | 33 ++------------- .../Sections/Historical/UserHistoryGraph.cs | 42 +++---------------- osu.Game/Overlays/Profile/UserGraph.cs | 36 ++++++++++------ 3 files changed, 32 insertions(+), 79 deletions(-) diff --git a/osu.Game/Overlays/Profile/Header/Components/RankGraph.cs b/osu.Game/Overlays/Profile/Header/Components/RankGraph.cs index 7ba8ae7c80..3312cb2c4f 100644 --- a/osu.Game/Overlays/Profile/Header/Components/RankGraph.cs +++ b/osu.Game/Overlays/Profile/Header/Components/RankGraph.cs @@ -8,7 +8,6 @@ using Humanizer; using osu.Framework.Bindables; using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics; -using osu.Framework.Localisation; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Resources.Localisation.Web; @@ -61,40 +60,16 @@ namespace osu.Game.Overlays.Profile.Header.Components placeholder.FadeIn(FADE_DURATION, Easing.Out); } - protected override object GetTooltipContent(int index, int rank) + protected override UserGraphTooltipContent GetTooltipContent(int index, int rank) { var days = ranked_days - index + 1; - return new TooltipDisplayContent + return new UserGraphTooltipContent { - Rank = rank.ToLocalisableString("\\##,##0"), + Name = UsersStrings.ShowRankGlobalSimple, + Count = rank.ToLocalisableString("\\##,##0"), Time = days == 0 ? "now" : $"{"day".ToQuantity(days)} ago" }; } - - protected override UserGraphTooltip GetTooltip() => new RankGraphTooltip(); - - private class RankGraphTooltip : UserGraphTooltip - { - public RankGraphTooltip() - : base(UsersStrings.ShowRankGlobalSimple) - { - } - - public override void SetContent(object content) - { - if (!(content is TooltipDisplayContent info)) - return; - - Counter.Text = info.Rank; - BottomText.Text = info.Time; - } - } - - private class TooltipDisplayContent - { - public LocalisableString Rank; - public string Time; - } } } diff --git a/osu.Game/Overlays/Profile/Sections/Historical/UserHistoryGraph.cs b/osu.Game/Overlays/Profile/Sections/Historical/UserHistoryGraph.cs index 85287d2325..d86e976e70 100644 --- a/osu.Game/Overlays/Profile/Sections/Historical/UserHistoryGraph.cs +++ b/osu.Game/Overlays/Profile/Sections/Historical/UserHistoryGraph.cs @@ -28,43 +28,11 @@ namespace osu.Game.Overlays.Profile.Sections.Historical protected override float GetDataPointHeight(long playCount) => playCount; - protected override UserGraphTooltip GetTooltip() => new HistoryGraphTooltip(tooltipCounterName); - - protected override object GetTooltipContent(DateTime date, long playCount) + protected override UserGraphTooltipContent GetTooltipContent(DateTime date, long playCount) => new UserGraphTooltipContent { - return new TooltipDisplayContent - { - Name = tooltipCounterName, - Count = playCount.ToLocalisableString("N0"), - Date = date.ToLocalisableString("MMMM yyyy") - }; - } - - protected class HistoryGraphTooltip : UserGraphTooltip - { - private readonly LocalisableString tooltipCounterName; - - public HistoryGraphTooltip(LocalisableString tooltipCounterName) - : base(tooltipCounterName) - { - this.tooltipCounterName = tooltipCounterName; - } - - public override void SetContent(object content) - { - if (!(content is TooltipDisplayContent info) || info.Name != tooltipCounterName) - return; - - Counter.Text = info.Count; - BottomText.Text = info.Date; - } - } - - private class TooltipDisplayContent - { - public LocalisableString Name; - public LocalisableString Count; - public LocalisableString Date; - } + Name = tooltipCounterName, + Count = playCount.ToLocalisableString("N0"), + Time = date.ToLocalisableString("MMMM yyyy") + }; } } diff --git a/osu.Game/Overlays/Profile/UserGraph.cs b/osu.Game/Overlays/Profile/UserGraph.cs index b88cc32ff7..502bbbe1a6 100644 --- a/osu.Game/Overlays/Profile/UserGraph.cs +++ b/osu.Game/Overlays/Profile/UserGraph.cs @@ -24,7 +24,7 @@ namespace osu.Game.Overlays.Profile /// /// Type of data to be used for X-axis of the graph. /// Type of data to be used for Y-axis of the graph. - public abstract class UserGraph : Container, IHasCustomTooltip + public abstract class UserGraph : Container, IHasCustomTooltip { protected const float FADE_DURATION = 150; @@ -118,23 +118,21 @@ namespace osu.Game.Overlays.Profile protected virtual void ShowGraph() => graph.FadeIn(FADE_DURATION, Easing.Out); protected virtual void HideGraph() => graph.FadeOut(FADE_DURATION, Easing.Out); - public ITooltip GetCustomTooltip() => GetTooltip(); + public ITooltip GetCustomTooltip() => new UserGraphTooltip(); - protected abstract UserGraphTooltip GetTooltip(); - - public object TooltipContent + public UserGraphTooltipContent TooltipContent { get { if (data == null || hoveredIndex == -1) - return null; + return default; var (key, value) = data[hoveredIndex]; return GetTooltipContent(key, value); } } - protected abstract object GetTooltipContent(TKey key, TValue value); + protected abstract UserGraphTooltipContent GetTooltipContent(TKey key, TValue value); protected class UserLineGraph : LineGraph { @@ -207,12 +205,12 @@ namespace osu.Game.Overlays.Profile } } - protected abstract class UserGraphTooltip : VisibilityContainer, ITooltip + private class UserGraphTooltip : VisibilityContainer, ITooltip { - protected readonly OsuSpriteText Counter, BottomText; + protected readonly OsuSpriteText Label, Counter, BottomText; private readonly Box background; - protected UserGraphTooltip(LocalisableString tooltipCounterName) + public UserGraphTooltip() { AutoSizeAxes = Axes.Both; Masking = true; @@ -238,10 +236,9 @@ namespace osu.Game.Overlays.Profile Spacing = new Vector2(3, 0), Children = new Drawable[] { - new OsuSpriteText + Label = new OsuSpriteText { Font = OsuFont.GetFont(size: 12, weight: FontWeight.Bold), - Text = tooltipCounterName }, Counter = new OsuSpriteText { @@ -268,7 +265,12 @@ namespace osu.Game.Overlays.Profile background.Colour = colours.Gray1; } - public abstract void SetContent(object content); + public void SetContent(UserGraphTooltipContent content) + { + Label.Text = content.Name; + Counter.Text = content.Count; + BottomText.Text = content.Time; + } private bool instantMove = true; @@ -292,4 +294,12 @@ namespace osu.Game.Overlays.Profile protected override void PopOut() => this.FadeOut(200, Easing.OutQuint); } } + + public class UserGraphTooltipContent + { + // todo: change to init-only on C# 9 + public LocalisableString Name { get; set; } + public LocalisableString Count { get; set; } + public LocalisableString Time { get; set; } + } } From da7ff4b1601019a44c96f3ee0fcc0004d541d288 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 28 Aug 2021 19:09:37 +0300 Subject: [PATCH 083/160] Update remaining tooltip implementations to use generics --- osu.Game/Beatmaps/Drawables/DifficultyIcon.cs | 115 +---------------- .../Drawables/DifficultyIconTooltip.cs | 121 ++++++++++++++++++ osu.Game/Graphics/DrawableDate.cs | 6 +- .../Home/News/FeaturedNewsItemPanel.cs | 14 +- .../Dashboard/Home/News/NewsGroupItem.cs | 14 +- osu.Game/Overlays/News/NewsCard.cs | 12 +- 6 files changed, 144 insertions(+), 138 deletions(-) create mode 100644 osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs diff --git a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs index 199f719893..cda4377780 100644 --- a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs +++ b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs @@ -16,7 +16,6 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; using osu.Game.Graphics.Containers; -using osu.Game.Graphics.Sprites; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osuTK; @@ -24,7 +23,7 @@ using osuTK.Graphics; namespace osu.Game.Beatmaps.Drawables { - public class DifficultyIcon : CompositeDrawable, IHasCustomTooltip + public class DifficultyIcon : CompositeDrawable, IHasCustomTooltip { private readonly Container iconContainer; @@ -127,9 +126,9 @@ namespace osu.Game.Beatmaps.Drawables difficultyBindable.BindValueChanged(difficulty => background.Colour = colours.ForStarDifficulty(difficulty.NewValue.Stars)); } - public ITooltip GetCustomTooltip() => new DifficultyIconTooltip(); + public ITooltip GetCustomTooltip() => new DifficultyIconTooltip(); - public object TooltipContent => shouldShowTooltip ? new DifficultyIconTooltipContent(beatmap, difficultyBindable) : null; + public DifficultyIconTooltipContent TooltipContent => shouldShowTooltip ? new DifficultyIconTooltipContent(beatmap, difficultyBindable) : null; private class DifficultyRetriever : Component { @@ -173,113 +172,5 @@ namespace osu.Game.Beatmaps.Drawables difficultyCancellation?.Cancel(); } } - - private class DifficultyIconTooltipContent - { - public readonly BeatmapInfo Beatmap; - public readonly IBindable Difficulty; - - public DifficultyIconTooltipContent(BeatmapInfo beatmap, IBindable difficulty) - { - Beatmap = beatmap; - Difficulty = difficulty; - } - } - - private class DifficultyIconTooltip : VisibilityContainer, ITooltip - { - private readonly OsuSpriteText difficultyName, starRating; - private readonly Box background; - private readonly FillFlowContainer difficultyFlow; - - public DifficultyIconTooltip() - { - AutoSizeAxes = Axes.Both; - Masking = true; - CornerRadius = 5; - - Children = new Drawable[] - { - background = new Box - { - RelativeSizeAxes = Axes.Both - }, - new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - AutoSizeDuration = 200, - AutoSizeEasing = Easing.OutQuint, - Direction = FillDirection.Vertical, - Padding = new MarginPadding(10), - Children = new Drawable[] - { - difficultyName = new OsuSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Font = OsuFont.GetFont(size: 16, weight: FontWeight.Bold), - }, - difficultyFlow = new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Direction = FillDirection.Horizontal, - Children = new Drawable[] - { - starRating = new OsuSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Font = OsuFont.GetFont(size: 16, weight: FontWeight.Regular), - }, - new SpriteIcon - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Margin = new MarginPadding { Left = 4 }, - Icon = FontAwesome.Solid.Star, - Size = new Vector2(12), - }, - } - } - } - } - }; - } - - [Resolved] - private OsuColour colours { get; set; } - - [BackgroundDependencyLoader] - private void load() - { - background.Colour = colours.Gray3; - } - - private readonly IBindable starDifficulty = new Bindable(); - - public void SetContent(object content) - { - if (!(content is DifficultyIconTooltipContent iconContent)) - return; - - difficultyName.Text = iconContent.Beatmap.Version; - - starDifficulty.UnbindAll(); - starDifficulty.BindTo(iconContent.Difficulty); - starDifficulty.BindValueChanged(difficulty => - { - starRating.Text = $"{difficulty.NewValue.Stars:0.##}"; - difficultyFlow.Colour = colours.ForStarDifficulty(difficulty.NewValue.Stars); - }, true); - } - - public void Move(Vector2 pos) => Position = pos; - - protected override void PopIn() => this.FadeIn(200, Easing.OutQuint); - - protected override void PopOut() => this.FadeOut(200, Easing.OutQuint); - } } } diff --git a/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs b/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs new file mode 100644 index 0000000000..5b05e39090 --- /dev/null +++ b/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs @@ -0,0 +1,121 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osuTK; + +namespace osu.Game.Beatmaps.Drawables +{ + public class DifficultyIconTooltip : VisibilityContainer, ITooltip + { + private readonly OsuSpriteText difficultyName, starRating; + private readonly Box background; + private readonly FillFlowContainer difficultyFlow; + + public DifficultyIconTooltip() + { + AutoSizeAxes = Axes.Both; + Masking = true; + CornerRadius = 5; + + Children = new Drawable[] + { + background = new Box + { + RelativeSizeAxes = Axes.Both + }, + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + AutoSizeDuration = 200, + AutoSizeEasing = Easing.OutQuint, + Direction = FillDirection.Vertical, + Padding = new MarginPadding(10), + Children = new Drawable[] + { + difficultyName = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Font = OsuFont.GetFont(size: 16, weight: FontWeight.Bold), + }, + difficultyFlow = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Direction = FillDirection.Horizontal, + Children = new Drawable[] + { + starRating = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Font = OsuFont.GetFont(size: 16, weight: FontWeight.Regular), + }, + new SpriteIcon + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Margin = new MarginPadding { Left = 4 }, + Icon = FontAwesome.Solid.Star, + Size = new Vector2(12), + }, + } + } + } + } + }; + } + + [Resolved] + private OsuColour colours { get; set; } + + [BackgroundDependencyLoader] + private void load() + { + background.Colour = colours.Gray3; + } + + private readonly IBindable starDifficulty = new Bindable(); + + public void SetContent(DifficultyIconTooltipContent content) + { + difficultyName.Text = content.Beatmap.Version; + + starDifficulty.UnbindAll(); + starDifficulty.BindTo(content.Difficulty); + starDifficulty.BindValueChanged(difficulty => + { + starRating.Text = $"{difficulty.NewValue.Stars:0.##}"; + difficultyFlow.Colour = colours.ForStarDifficulty(difficulty.NewValue.Stars); + }, true); + } + + public void Move(Vector2 pos) => Position = pos; + + protected override void PopIn() => this.FadeIn(200, Easing.OutQuint); + + protected override void PopOut() => this.FadeOut(200, Easing.OutQuint); + } + + public class DifficultyIconTooltipContent + { + public readonly BeatmapInfo Beatmap; + public readonly IBindable Difficulty; + + public DifficultyIconTooltipContent(BeatmapInfo beatmap, IBindable difficulty) + { + Beatmap = beatmap; + Difficulty = difficulty; + } + } +} diff --git a/osu.Game/Graphics/DrawableDate.cs b/osu.Game/Graphics/DrawableDate.cs index 259d9c8d6e..567a39b4f4 100644 --- a/osu.Game/Graphics/DrawableDate.cs +++ b/osu.Game/Graphics/DrawableDate.cs @@ -10,7 +10,7 @@ using osu.Game.Utils; namespace osu.Game.Graphics { - public class DrawableDate : OsuSpriteText, IHasCustomTooltip + public class DrawableDate : OsuSpriteText, IHasCustomTooltip { private DateTimeOffset date; @@ -75,8 +75,8 @@ namespace osu.Game.Graphics private void updateTime() => Text = Format(); - public ITooltip GetCustomTooltip() => new DateTooltip(); + public ITooltip GetCustomTooltip() => new DateTooltip(); - public object TooltipContent => Date; + public DateTimeOffset TooltipContent => Date; } } diff --git a/osu.Game/Overlays/Dashboard/Home/News/FeaturedNewsItemPanel.cs b/osu.Game/Overlays/Dashboard/Home/News/FeaturedNewsItemPanel.cs index ee88469e2f..72a85bcb6c 100644 --- a/osu.Game/Overlays/Dashboard/Home/News/FeaturedNewsItemPanel.cs +++ b/osu.Game/Overlays/Dashboard/Home/News/FeaturedNewsItemPanel.cs @@ -140,17 +140,15 @@ namespace osu.Game.Overlays.Dashboard.Home.News } } - private class Date : CompositeDrawable, IHasCustomTooltip + private class Date : CompositeDrawable, IHasCustomTooltip { - public ITooltip GetCustomTooltip() => new DateTooltip(); + public ITooltip GetCustomTooltip() => new DateTooltip(); - public object TooltipContent => date; - - private readonly DateTimeOffset date; + public DateTimeOffset TooltipContent { get; } public Date(DateTimeOffset date) { - this.date = date; + TooltipContent = date; } [BackgroundDependencyLoader] @@ -174,7 +172,7 @@ namespace osu.Game.Overlays.Dashboard.Home.News Origin = Anchor.TopRight, Font = OsuFont.GetFont(weight: FontWeight.Bold), // using Bold since there is no 800 weight alternative Colour = colourProvider.Light1, - Text = $"{date:dd}" + Text = $"{TooltipContent:dd}" }, new TextFlowContainer(f => { @@ -185,7 +183,7 @@ namespace osu.Game.Overlays.Dashboard.Home.News Anchor = Anchor.TopRight, Origin = Anchor.TopRight, AutoSizeAxes = Axes.Both, - Text = $"{date:MMM yyyy}" + Text = $"{TooltipContent:MMM yyyy}" } } }; diff --git a/osu.Game/Overlays/Dashboard/Home/News/NewsGroupItem.cs b/osu.Game/Overlays/Dashboard/Home/News/NewsGroupItem.cs index dc4f3f8c92..a681536156 100644 --- a/osu.Game/Overlays/Dashboard/Home/News/NewsGroupItem.cs +++ b/osu.Game/Overlays/Dashboard/Home/News/NewsGroupItem.cs @@ -67,17 +67,15 @@ namespace osu.Game.Overlays.Dashboard.Home.News }; } - private class Date : CompositeDrawable, IHasCustomTooltip + private class Date : CompositeDrawable, IHasCustomTooltip { - public ITooltip GetCustomTooltip() => new DateTooltip(); + public ITooltip GetCustomTooltip() => new DateTooltip(); - public object TooltipContent => date; - - private readonly DateTimeOffset date; + public DateTimeOffset TooltipContent { get; } public Date(DateTimeOffset date) { - this.date = date; + TooltipContent = date; } [BackgroundDependencyLoader] @@ -100,12 +98,12 @@ namespace osu.Game.Overlays.Dashboard.Home.News Margin = new MarginPadding { Vertical = 5 } }; - textFlow.AddText($"{date:dd}", t => + textFlow.AddText($"{TooltipContent:dd}", t => { t.Font = OsuFont.GetFont(size: 14, weight: FontWeight.Bold); }); - textFlow.AddText($"{date: MMM}", t => + textFlow.AddText($"{TooltipContent: MMM}", t => { t.Font = OsuFont.GetFont(size: 14, weight: FontWeight.Regular); }); diff --git a/osu.Game/Overlays/News/NewsCard.cs b/osu.Game/Overlays/News/NewsCard.cs index cc2fa7e1e1..aee0a50de9 100644 --- a/osu.Game/Overlays/News/NewsCard.cs +++ b/osu.Game/Overlays/News/NewsCard.cs @@ -123,17 +123,15 @@ namespace osu.Game.Overlays.News main.AddText(post.Author, t => t.Font = OsuFont.GetFont(size: 12, weight: FontWeight.SemiBold)); } - private class DateContainer : CircularContainer, IHasCustomTooltip + private class DateContainer : CircularContainer, IHasCustomTooltip { - public ITooltip GetCustomTooltip() => new DateTooltip(); + public ITooltip GetCustomTooltip() => new DateTooltip(); - public object TooltipContent => date; - - private readonly DateTimeOffset date; + public DateTimeOffset TooltipContent { get; } public DateContainer(DateTimeOffset date) { - this.date = date; + TooltipContent = date; } [BackgroundDependencyLoader] @@ -150,7 +148,7 @@ namespace osu.Game.Overlays.News }, new OsuSpriteText { - Text = date.ToString("d MMM yyyy").ToUpper(), + Text = TooltipContent.ToString("d MMM yyyy").ToUpper(), Font = OsuFont.GetFont(size: 10, weight: FontWeight.SemiBold), Margin = new MarginPadding { From 69c23a2371ea9f962981a671a710a763178ceee4 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 31 Aug 2021 20:06:34 +0300 Subject: [PATCH 084/160] Explicitly implement tooltips on date drawables to avoid "convert to auto-property" inspections --- .../Dashboard/Home/News/FeaturedNewsItemPanel.cs | 14 ++++++++------ .../Overlays/Dashboard/Home/News/NewsGroupItem.cs | 14 ++++++++------ osu.Game/Overlays/News/NewsCard.cs | 12 +++++++----- 3 files changed, 23 insertions(+), 17 deletions(-) diff --git a/osu.Game/Overlays/Dashboard/Home/News/FeaturedNewsItemPanel.cs b/osu.Game/Overlays/Dashboard/Home/News/FeaturedNewsItemPanel.cs index 72a85bcb6c..0d166eb858 100644 --- a/osu.Game/Overlays/Dashboard/Home/News/FeaturedNewsItemPanel.cs +++ b/osu.Game/Overlays/Dashboard/Home/News/FeaturedNewsItemPanel.cs @@ -142,13 +142,11 @@ namespace osu.Game.Overlays.Dashboard.Home.News private class Date : CompositeDrawable, IHasCustomTooltip { - public ITooltip GetCustomTooltip() => new DateTooltip(); - - public DateTimeOffset TooltipContent { get; } + private readonly DateTimeOffset date; public Date(DateTimeOffset date) { - TooltipContent = date; + this.date = date; } [BackgroundDependencyLoader] @@ -172,7 +170,7 @@ namespace osu.Game.Overlays.Dashboard.Home.News Origin = Anchor.TopRight, Font = OsuFont.GetFont(weight: FontWeight.Bold), // using Bold since there is no 800 weight alternative Colour = colourProvider.Light1, - Text = $"{TooltipContent:dd}" + Text = $"{date:dd}" }, new TextFlowContainer(f => { @@ -183,11 +181,15 @@ namespace osu.Game.Overlays.Dashboard.Home.News Anchor = Anchor.TopRight, Origin = Anchor.TopRight, AutoSizeAxes = Axes.Both, - Text = $"{TooltipContent:MMM yyyy}" + Text = $"{date:MMM yyyy}" } } }; } + + ITooltip IHasCustomTooltip.GetCustomTooltip() => new DateTooltip(); + + DateTimeOffset IHasCustomTooltip.TooltipContent => date; } } } diff --git a/osu.Game/Overlays/Dashboard/Home/News/NewsGroupItem.cs b/osu.Game/Overlays/Dashboard/Home/News/NewsGroupItem.cs index a681536156..77cfbc90b0 100644 --- a/osu.Game/Overlays/Dashboard/Home/News/NewsGroupItem.cs +++ b/osu.Game/Overlays/Dashboard/Home/News/NewsGroupItem.cs @@ -69,13 +69,11 @@ namespace osu.Game.Overlays.Dashboard.Home.News private class Date : CompositeDrawable, IHasCustomTooltip { - public ITooltip GetCustomTooltip() => new DateTooltip(); - - public DateTimeOffset TooltipContent { get; } + private readonly DateTimeOffset date; public Date(DateTimeOffset date) { - TooltipContent = date; + this.date = date; } [BackgroundDependencyLoader] @@ -98,16 +96,20 @@ namespace osu.Game.Overlays.Dashboard.Home.News Margin = new MarginPadding { Vertical = 5 } }; - textFlow.AddText($"{TooltipContent:dd}", t => + textFlow.AddText($"{date:dd}", t => { t.Font = OsuFont.GetFont(size: 14, weight: FontWeight.Bold); }); - textFlow.AddText($"{TooltipContent: MMM}", t => + textFlow.AddText($"{date: MMM}", t => { t.Font = OsuFont.GetFont(size: 14, weight: FontWeight.Regular); }); } + + ITooltip IHasCustomTooltip.GetCustomTooltip() => new DateTooltip(); + + DateTimeOffset IHasCustomTooltip.TooltipContent => date; } } } diff --git a/osu.Game/Overlays/News/NewsCard.cs b/osu.Game/Overlays/News/NewsCard.cs index aee0a50de9..68d0704825 100644 --- a/osu.Game/Overlays/News/NewsCard.cs +++ b/osu.Game/Overlays/News/NewsCard.cs @@ -125,13 +125,11 @@ namespace osu.Game.Overlays.News private class DateContainer : CircularContainer, IHasCustomTooltip { - public ITooltip GetCustomTooltip() => new DateTooltip(); - - public DateTimeOffset TooltipContent { get; } + private readonly DateTimeOffset date; public DateContainer(DateTimeOffset date) { - TooltipContent = date; + this.date = date; } [BackgroundDependencyLoader] @@ -148,7 +146,7 @@ namespace osu.Game.Overlays.News }, new OsuSpriteText { - Text = TooltipContent.ToString("d MMM yyyy").ToUpper(), + Text = date.ToString("d MMM yyyy").ToUpper(), Font = OsuFont.GetFont(size: 10, weight: FontWeight.SemiBold), Margin = new MarginPadding { @@ -160,6 +158,10 @@ namespace osu.Game.Overlays.News } protected override bool OnClick(ClickEvent e) => true; // Protects the NewsCard from clicks while hovering DateContainer + + ITooltip IHasCustomTooltip.GetCustomTooltip() => new DateTooltip(); + + DateTimeOffset IHasCustomTooltip.TooltipContent => date; } } } From 3969350c9a38bd8ccf1408656c08dd280dbf0ec6 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 31 Aug 2021 20:45:32 +0300 Subject: [PATCH 085/160] Convert to `readonly struct` and replace with constructor temporarily --- .../Profile/Header/Components/RankGraph.cs | 10 ++++------ .../Sections/Historical/UserHistoryGraph.cs | 11 ++++++----- osu.Game/Overlays/Profile/UserGraph.cs | 17 ++++++++++++----- 3 files changed, 22 insertions(+), 16 deletions(-) diff --git a/osu.Game/Overlays/Profile/Header/Components/RankGraph.cs b/osu.Game/Overlays/Profile/Header/Components/RankGraph.cs index 3312cb2c4f..ca5f26e375 100644 --- a/osu.Game/Overlays/Profile/Header/Components/RankGraph.cs +++ b/osu.Game/Overlays/Profile/Header/Components/RankGraph.cs @@ -64,12 +64,10 @@ namespace osu.Game.Overlays.Profile.Header.Components { var days = ranked_days - index + 1; - return new UserGraphTooltipContent - { - Name = UsersStrings.ShowRankGlobalSimple, - Count = rank.ToLocalisableString("\\##,##0"), - Time = days == 0 ? "now" : $"{"day".ToQuantity(days)} ago" - }; + return new UserGraphTooltipContent( + UsersStrings.ShowRankGlobalSimple, + rank.ToLocalisableString("\\##,##0"), + days == 0 ? "now" : $"{"day".ToQuantity(days)} ago"); } } } diff --git a/osu.Game/Overlays/Profile/Sections/Historical/UserHistoryGraph.cs b/osu.Game/Overlays/Profile/Sections/Historical/UserHistoryGraph.cs index d86e976e70..738edb9310 100644 --- a/osu.Game/Overlays/Profile/Sections/Historical/UserHistoryGraph.cs +++ b/osu.Game/Overlays/Profile/Sections/Historical/UserHistoryGraph.cs @@ -28,11 +28,12 @@ namespace osu.Game.Overlays.Profile.Sections.Historical protected override float GetDataPointHeight(long playCount) => playCount; - protected override UserGraphTooltipContent GetTooltipContent(DateTime date, long playCount) => new UserGraphTooltipContent + protected override UserGraphTooltipContent GetTooltipContent(DateTime date, long playCount) { - Name = tooltipCounterName, - Count = playCount.ToLocalisableString("N0"), - Time = date.ToLocalisableString("MMMM yyyy") - }; + return new UserGraphTooltipContent( + tooltipCounterName, + playCount.ToLocalisableString("N0"), + date.ToLocalisableString("MMMM yyyy")); + } } } diff --git a/osu.Game/Overlays/Profile/UserGraph.cs b/osu.Game/Overlays/Profile/UserGraph.cs index 502bbbe1a6..f305e3afc3 100644 --- a/osu.Game/Overlays/Profile/UserGraph.cs +++ b/osu.Game/Overlays/Profile/UserGraph.cs @@ -295,11 +295,18 @@ namespace osu.Game.Overlays.Profile } } - public class UserGraphTooltipContent + public readonly struct UserGraphTooltipContent { - // todo: change to init-only on C# 9 - public LocalisableString Name { get; set; } - public LocalisableString Count { get; set; } - public LocalisableString Time { get; set; } + // todo: could use init-only properties on C# 9 which read better than a constructor. + public LocalisableString Name { get; } + public LocalisableString Count { get; } + public LocalisableString Time { get; } + + public UserGraphTooltipContent(LocalisableString name, LocalisableString count, LocalisableString time) + { + Name = name; + Count = count; + Time = time; + } } } From 4a590a041ccf6d755f81f8e052ac092d325ded05 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 31 Aug 2021 20:57:36 +0300 Subject: [PATCH 086/160] Constrain difficulty icon tooltip to `internal` accessibility --- osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs b/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs index 5b05e39090..0329e935bc 100644 --- a/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs +++ b/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs @@ -14,7 +14,7 @@ using osuTK; namespace osu.Game.Beatmaps.Drawables { - public class DifficultyIconTooltip : VisibilityContainer, ITooltip + internal class DifficultyIconTooltip : VisibilityContainer, ITooltip { private readonly OsuSpriteText difficultyName, starRating; private readonly Box background; @@ -107,7 +107,7 @@ namespace osu.Game.Beatmaps.Drawables protected override void PopOut() => this.FadeOut(200, Easing.OutQuint); } - public class DifficultyIconTooltipContent + internal class DifficultyIconTooltipContent { public readonly BeatmapInfo Beatmap; public readonly IBindable Difficulty; From cd356b8eae1500a2f2127058b7686537d1fc4796 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 31 Aug 2021 20:57:47 +0300 Subject: [PATCH 087/160] Revert "Constrain difficulty icon tooltip to `internal` accessibility" This reverts commit 4a590a041ccf6d755f81f8e052ac092d325ded05. --- osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs b/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs index 0329e935bc..5b05e39090 100644 --- a/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs +++ b/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs @@ -14,7 +14,7 @@ using osuTK; namespace osu.Game.Beatmaps.Drawables { - internal class DifficultyIconTooltip : VisibilityContainer, ITooltip + public class DifficultyIconTooltip : VisibilityContainer, ITooltip { private readonly OsuSpriteText difficultyName, starRating; private readonly Box background; @@ -107,7 +107,7 @@ namespace osu.Game.Beatmaps.Drawables protected override void PopOut() => this.FadeOut(200, Easing.OutQuint); } - internal class DifficultyIconTooltipContent + public class DifficultyIconTooltipContent { public readonly BeatmapInfo Beatmap; public readonly IBindable Difficulty; From b0d7104650a125093d8ab6d354c45bfa630f74ee Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 31 Aug 2021 21:13:20 +0300 Subject: [PATCH 088/160] Convert to `class` to allow not displaying tooltips With `struct` content, it is never possible to not show a tooltip. --- osu.Game/Overlays/Profile/UserGraph.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Profile/UserGraph.cs b/osu.Game/Overlays/Profile/UserGraph.cs index f305e3afc3..182221eea7 100644 --- a/osu.Game/Overlays/Profile/UserGraph.cs +++ b/osu.Game/Overlays/Profile/UserGraph.cs @@ -125,7 +125,7 @@ namespace osu.Game.Overlays.Profile get { if (data == null || hoveredIndex == -1) - return default; + return null; var (key, value) = data[hoveredIndex]; return GetTooltipContent(key, value); @@ -295,7 +295,7 @@ namespace osu.Game.Overlays.Profile } } - public readonly struct UserGraphTooltipContent + public class UserGraphTooltipContent { // todo: could use init-only properties on C# 9 which read better than a constructor. public LocalisableString Name { get; } From 505824d8eaccfbd1f9e2550004ca65af7c49c8a4 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 31 Aug 2021 21:16:02 +0300 Subject: [PATCH 089/160] Constrain difficulty icon tooltip to `internal` accessibility" This reverts the reverted commit cd356b8eae1500a2f2127058b7686537d1fc4796. Sorry for the revert-unrevert, rushly pushed without realizing it doesn't even build. --- osu.Game/Beatmaps/Drawables/DifficultyIcon.cs | 4 ++-- osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs index cda4377780..0751a777d8 100644 --- a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs +++ b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs @@ -126,9 +126,9 @@ namespace osu.Game.Beatmaps.Drawables difficultyBindable.BindValueChanged(difficulty => background.Colour = colours.ForStarDifficulty(difficulty.NewValue.Stars)); } - public ITooltip GetCustomTooltip() => new DifficultyIconTooltip(); + ITooltip IHasCustomTooltip.GetCustomTooltip() => new DifficultyIconTooltip(); - public DifficultyIconTooltipContent TooltipContent => shouldShowTooltip ? new DifficultyIconTooltipContent(beatmap, difficultyBindable) : null; + DifficultyIconTooltipContent IHasCustomTooltip.TooltipContent => shouldShowTooltip ? new DifficultyIconTooltipContent(beatmap, difficultyBindable) : null; private class DifficultyRetriever : Component { diff --git a/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs b/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs index 5b05e39090..0329e935bc 100644 --- a/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs +++ b/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs @@ -14,7 +14,7 @@ using osuTK; namespace osu.Game.Beatmaps.Drawables { - public class DifficultyIconTooltip : VisibilityContainer, ITooltip + internal class DifficultyIconTooltip : VisibilityContainer, ITooltip { private readonly OsuSpriteText difficultyName, starRating; private readonly Box background; @@ -107,7 +107,7 @@ namespace osu.Game.Beatmaps.Drawables protected override void PopOut() => this.FadeOut(200, Easing.OutQuint); } - public class DifficultyIconTooltipContent + internal class DifficultyIconTooltipContent { public readonly BeatmapInfo Beatmap; public readonly IBindable Difficulty; From da3fa9304aef4533246bb4b35f358cd536e3bf08 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Tue, 31 Aug 2021 12:39:18 -0700 Subject: [PATCH 090/160] Make toolbar inherit overlay container --- osu.Game/Overlays/Toolbar/Toolbar.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Toolbar/Toolbar.cs b/osu.Game/Overlays/Toolbar/Toolbar.cs index 2664301a0c..8184954753 100644 --- a/osu.Game/Overlays/Toolbar/Toolbar.cs +++ b/osu.Game/Overlays/Toolbar/Toolbar.cs @@ -18,7 +18,7 @@ using osu.Game.Input.Bindings; namespace osu.Game.Overlays.Toolbar { - public class Toolbar : VisibilityContainer, IKeyBindingHandler + public class Toolbar : OverlayContainer, IKeyBindingHandler { public const float HEIGHT = 40; public const float TOOLTIP_HEIGHT = 30; @@ -41,8 +41,6 @@ namespace osu.Game.Overlays.Toolbar // Toolbar and its components need keyboard input even when hidden. public override bool PropagateNonPositionalInputSubTree => true; - protected override bool Handle(UIEvent e) => e is MouseEvent; - public Toolbar() { RelativeSizeAxes = Axes.X; From 7e4ad7d7cfaf6e0dc435f157320807b26f12e184 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Tue, 31 Aug 2021 13:40:13 -0700 Subject: [PATCH 091/160] Fix toolbar blocking scroll input --- osu.Game/Overlays/Toolbar/Toolbar.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Overlays/Toolbar/Toolbar.cs b/osu.Game/Overlays/Toolbar/Toolbar.cs index 8184954753..7481cfdbf5 100644 --- a/osu.Game/Overlays/Toolbar/Toolbar.cs +++ b/osu.Game/Overlays/Toolbar/Toolbar.cs @@ -41,6 +41,8 @@ namespace osu.Game.Overlays.Toolbar // Toolbar and its components need keyboard input even when hidden. public override bool PropagateNonPositionalInputSubTree => true; + protected override bool BlockScrollInput => false; + public Toolbar() { RelativeSizeAxes = Axes.X; From 04773b51bb6efa4964dc6df92158ea23aba679ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 31 Aug 2021 22:37:40 +0200 Subject: [PATCH 092/160] Remove countdown toggle transition for now Tricky to get right and the design isn't final as is anyway, so leaving *something* functioning as a best-effort for now. --- osu.Game/Screens/Edit/Setup/DesignSection.cs | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/osu.Game/Screens/Edit/Setup/DesignSection.cs b/osu.Game/Screens/Edit/Setup/DesignSection.cs index d030c2a894..ede6a52276 100644 --- a/osu.Game/Screens/Edit/Setup/DesignSection.cs +++ b/osu.Game/Screens/Edit/Setup/DesignSection.cs @@ -16,8 +16,6 @@ namespace osu.Game.Screens.Edit.Setup { internal class DesignSection : SetupSection { - private const float fade_duration = 250; - protected LabelledSwitchButton EnableCountdown; protected LabelledEnumDropdown CountdownSpeed; protected LabelledNumberBox CountdownOffset; @@ -90,7 +88,6 @@ namespace osu.Game.Screens.Edit.Setup base.LoadComplete(); EnableCountdown.Current.BindValueChanged(_ => updateCountdownSettingsVisibility(), true); - countdownSettings.FinishTransforms(true); EnableCountdown.Current.BindValueChanged(_ => updateBeatmap()); CountdownSpeed.Current.BindValueChanged(_ => updateBeatmap()); @@ -101,16 +98,7 @@ namespace osu.Game.Screens.Edit.Setup letterboxDuringBreaks.Current.BindValueChanged(_ => updateBeatmap()); } - private void updateCountdownSettingsVisibility() - { - bool countdownEnabled = EnableCountdown.Current.Value; - - foreach (var child in countdownSettings) - { - child.ScaleTo(new Vector2(1, countdownEnabled ? 1 : 0), fade_duration, Easing.OutQuint) - .FadeTo(countdownEnabled ? 1 : 0, fade_duration, Easing.OutQuint); - } - } + private void updateCountdownSettingsVisibility() => countdownSettings.FadeTo(EnableCountdown.Current.Value ? 1 : 0); private void onOffsetCommitted() { From 5dc938cc9f09fc523fd838b7bea7ce7da7e3a908 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 31 Aug 2021 22:40:58 +0200 Subject: [PATCH 093/160] Update tests to match expectations --- osu.Game.Tests/Visual/Editing/TestSceneDesignSection.cs | 9 ++++++--- osu.Game/Screens/Edit/Setup/DesignSection.cs | 8 ++++---- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneDesignSection.cs b/osu.Game.Tests/Visual/Editing/TestSceneDesignSection.cs index d84ffa1052..00f2979691 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneDesignSection.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneDesignSection.cs @@ -6,6 +6,7 @@ using System.Globalization; using System.Linq; using NUnit.Framework; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.UserInterface; using osu.Framework.Testing; using osu.Game.Beatmaps; @@ -42,7 +43,7 @@ namespace osu.Game.Tests.Visual.Editing AddStep("turn countdown off", () => designSection.EnableCountdown.Current.Value = false); AddAssert("beatmap has correct type", () => editorBeatmap.BeatmapInfo.Countdown == CountdownType.None); - AddUntilStep("other controls hidden", () => !designSection.CountdownSpeed.IsPresent && !designSection.CountdownOffset.IsPresent); + AddUntilStep("other controls hidden", () => !designSection.CountdownSettings.IsPresent); } [Test] @@ -51,12 +52,12 @@ namespace osu.Game.Tests.Visual.Editing AddStep("turn countdown on", () => designSection.EnableCountdown.Current.Value = true); AddAssert("beatmap has correct type", () => editorBeatmap.BeatmapInfo.Countdown == CountdownType.Normal); - AddUntilStep("other controls shown", () => designSection.CountdownSpeed.IsPresent && designSection.CountdownOffset.IsPresent); + AddUntilStep("other controls shown", () => designSection.CountdownSettings.IsPresent); AddStep("change countdown speed", () => designSection.CountdownSpeed.Current.Value = CountdownType.DoubleSpeed); AddAssert("beatmap has correct type", () => editorBeatmap.BeatmapInfo.Countdown == CountdownType.DoubleSpeed); - AddUntilStep("other controls still shown", () => designSection.CountdownSpeed.IsPresent && designSection.CountdownOffset.IsPresent); + AddUntilStep("other controls still shown", () => designSection.CountdownSettings.IsPresent); } [Test] @@ -90,6 +91,8 @@ namespace osu.Game.Tests.Visual.Editing private class TestDesignSection : DesignSection { public new LabelledSwitchButton EnableCountdown => base.EnableCountdown; + + public new FillFlowContainer CountdownSettings => base.CountdownSettings; public new LabelledEnumDropdown CountdownSpeed => base.CountdownSpeed; public new LabelledNumberBox CountdownOffset => base.CountdownOffset; } diff --git a/osu.Game/Screens/Edit/Setup/DesignSection.cs b/osu.Game/Screens/Edit/Setup/DesignSection.cs index ede6a52276..90f95a668e 100644 --- a/osu.Game/Screens/Edit/Setup/DesignSection.cs +++ b/osu.Game/Screens/Edit/Setup/DesignSection.cs @@ -17,6 +17,8 @@ namespace osu.Game.Screens.Edit.Setup internal class DesignSection : SetupSection { protected LabelledSwitchButton EnableCountdown; + + protected FillFlowContainer CountdownSettings; protected LabelledEnumDropdown CountdownSpeed; protected LabelledNumberBox CountdownOffset; @@ -24,8 +26,6 @@ namespace osu.Game.Screens.Edit.Setup private LabelledSwitchButton epilepsyWarning; private LabelledSwitchButton letterboxDuringBreaks; - private FillFlowContainer countdownSettings; - public override LocalisableString Title => "Design"; [BackgroundDependencyLoader] @@ -39,7 +39,7 @@ namespace osu.Game.Screens.Edit.Setup Current = { Value = Beatmap.BeatmapInfo.Countdown != CountdownType.None }, Description = "If enabled, an \"Are you ready? 3, 2, 1, GO!\" countdown will be inserted at the beginning of the beatmap, assuming there is enough time to do so." }, - countdownSettings = new FillFlowContainer + CountdownSettings = new FillFlowContainer { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, @@ -98,7 +98,7 @@ namespace osu.Game.Screens.Edit.Setup letterboxDuringBreaks.Current.BindValueChanged(_ => updateBeatmap()); } - private void updateCountdownSettingsVisibility() => countdownSettings.FadeTo(EnableCountdown.Current.Value ? 1 : 0); + private void updateCountdownSettingsVisibility() => CountdownSettings.FadeTo(EnableCountdown.Current.Value ? 1 : 0); private void onOffsetCommitted() { From a773a22726c13aa0687c7630e91a312718c0fc1c Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Tue, 31 Aug 2021 14:29:16 -0700 Subject: [PATCH 094/160] Fix toolbar hiding when clicking home button --- osu.Game/OsuGame.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index a584644fc9..187669cbb4 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -160,7 +160,7 @@ namespace osu.Game private readonly string[] args; - private readonly List overlays = new List(); + private readonly List focusedOverlays = new List(); private readonly List visibleBlockingOverlays = new List(); @@ -195,7 +195,7 @@ namespace osu.Game /// Whether the toolbar should also be hidden. public void CloseAllOverlays(bool hideToolbar = true) { - foreach (var overlay in overlays) + foreach (var overlay in focusedOverlays) overlay.Hide(); if (hideToolbar) Toolbar.Hide(); @@ -910,8 +910,8 @@ namespace osu.Game if (cache) dependencies.CacheAs(component); - if (component is OverlayContainer overlay) - overlays.Add(overlay); + if (component is OsuFocusedOverlayContainer overlay) + focusedOverlays.Add(overlay); // schedule is here to ensure that all component loads are done after LoadComplete is run (and thus all dependencies are cached). // with some better organisation of LoadComplete to do construction and dependency caching in one step, followed by calls to loadComponentSingleFile, From bd0f385cdb5546f060dd73ee7f71e504475ac2a3 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 1 Sep 2021 14:53:11 +0900 Subject: [PATCH 095/160] Make classic scoring a constant multiple of standardised scoring --- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 16f2607bad..2a7691269d 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -222,12 +222,12 @@ namespace osu.Game.Rulesets.Scoring case ScoringMode.Standardised: double accuracyScore = accuracyPortion * accuracyRatio; double comboScore = comboPortion * comboRatio; - return (max_score * (accuracyScore + comboScore) + getBonusScore(statistics)) * scoreMultiplier; case ScoringMode.Classic: - // should emulate osu-stable's scoring as closely as we can (https://osu.ppy.sh/help/wiki/Score/ScoreV1) - return getBonusScore(statistics) + (accuracyRatio * Math.Max(1, maxCombo) * 300) * (1 + Math.Max(0, (comboRatio * maxCombo) - 1) * scoreMultiplier / 25); + // This gives a similar feeling to osu!stable scoring (ScoreV1) while keeping classic scoring as only a constant multiple of standardised scoring. + // The invariant is important to ensure that scores don't get re-ordered on leaderboards between the two scoring modes. + return GetScore(ScoringMode.Standardised, maxCombo, accuracyRatio, comboRatio, statistics) / max_score * Math.Pow(maxCombo, 2) * 25; } } From 7a447f5128e87f965093f04c1840f6e531fdea9f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 1 Sep 2021 15:10:24 +0900 Subject: [PATCH 096/160] Mark `SankingSliderBody` as `abstract` --- osu.Game.Rulesets.Osu/Skinning/Default/SnakingSliderBody.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/SnakingSliderBody.cs b/osu.Game.Rulesets.Osu/Skinning/Default/SnakingSliderBody.cs index ed4e04184b..7b7a89d5e2 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/SnakingSliderBody.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/SnakingSliderBody.cs @@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default /// /// A which changes its curve depending on the snaking progress. /// - public class SnakingSliderBody : SliderBody, ISliderProgress + public abstract class SnakingSliderBody : SliderBody, ISliderProgress { public readonly List CurrentCurve = new List(); From 4f9c3fde07a662ef43925aadb4cd562c2afed1ac Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 1 Sep 2021 15:10:56 +0900 Subject: [PATCH 097/160] Move alpha adjustment back to `LegacySliderBody` to correctly handle default legacy skin --- .../Skinning/Default/PlaySliderBody.cs | 6 +++--- .../Skinning/Legacy/LegacySliderBody.cs | 7 +++++++ .../Legacy/OsuLegacySkinTransformer.cs | 21 ++----------------- 3 files changed, 12 insertions(+), 22 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/PlaySliderBody.cs b/osu.Game.Rulesets.Osu/Skinning/Default/PlaySliderBody.cs index 4dd7b2d69c..8602ebc88b 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/PlaySliderBody.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/PlaySliderBody.cs @@ -35,7 +35,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default pathVersion.BindValueChanged(_ => Refresh()); accentColour = drawableObject.AccentColour.GetBoundCopy(); - accentColour.BindValueChanged(accent => updateAccentColour(skin, accent.NewValue), true); + accentColour.BindValueChanged(accent => AccentColour = GetBodyAccentColour(skin, accent.NewValue), true); config?.BindWith(OsuRulesetSetting.SnakingInSliders, SnakingIn); config?.BindWith(OsuRulesetSetting.SnakingOutSliders, configSnakingOut); @@ -62,7 +62,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default } } - private void updateAccentColour(ISkinSource skin, Color4 defaultAccentColour) - => AccentColour = skin.GetConfig(OsuSkinColour.SliderTrackOverride)?.Value ?? defaultAccentColour; + protected virtual Color4 GetBodyAccentColour(ISkinSource skin, Color4 hitObjectAccentColour) => + skin.GetConfig(OsuSkinColour.SliderTrackOverride)?.Value ?? hitObjectAccentColour; } } diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySliderBody.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySliderBody.cs index 7d69e5ecdc..29a0745193 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySliderBody.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySliderBody.cs @@ -5,6 +5,7 @@ using System; using osu.Framework.Extensions.Color4Extensions; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Skinning.Default; +using osu.Game.Skinning; using osu.Game.Utils; using osuTK.Graphics; @@ -14,6 +15,12 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy { protected override DrawableSliderPath CreateSliderPath() => new LegacyDrawableSliderPath(); + protected override Color4 GetBodyAccentColour(ISkinSource skin, Color4 hitObjectAccentColour) + { + // legacy skins use a constant value for slider track alpha, regardless of the source colour. + return base.GetBodyAccentColour(skin, hitObjectAccentColour).Opacity(0.7f); + } + private class LegacyDrawableSliderPath : DrawableSliderPath { private const float shadow_portion = 1 - (OsuLegacySkinTransformer.LEGACY_CIRCLE_RADIUS / OsuHitObject.OBJECT_RADIUS); diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs index 16c770706d..41b0a88f11 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs @@ -3,11 +3,9 @@ using System; using osu.Framework.Bindables; -using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Game.Skinning; using osuTK; -using osuTK.Graphics; namespace osu.Game.Rulesets.Osu.Skinning.Legacy { @@ -120,23 +118,8 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy { switch (lookup) { - case OsuSkinColour colourLookup: - var colour = base.GetConfig(new SkinCustomColourLookup(colourLookup)); - - if (colour == null) - return null; - - switch (colourLookup) - { - case OsuSkinColour.SliderTrackOverride: - var bindableColour = ((Bindable)colour); - - // legacy skins use a constant value for slider track alpha, regardless of the source colour. - bindableColour.Value = bindableColour.Value.Opacity(0.7f); - break; - } - - return colour; + case OsuSkinColour colour: + return base.GetConfig(new SkinCustomColourLookup(colour)); case OsuSkinConfiguration osuLookup: switch (osuLookup) From 0319177c5c10b8eff6f71f202005a676731b3c3b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 1 Sep 2021 16:46:19 +0900 Subject: [PATCH 098/160] Fix pixels poking out of the top edge of editor setup screen --- osu.Game/Screens/Edit/Setup/SetupScreen.cs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Setup/SetupScreen.cs b/osu.Game/Screens/Edit/Setup/SetupScreen.cs index 72bf3ad67e..746cf38867 100644 --- a/osu.Game/Screens/Edit/Setup/SetupScreen.cs +++ b/osu.Game/Screens/Edit/Setup/SetupScreen.cs @@ -25,7 +25,7 @@ namespace osu.Game.Screens.Edit.Setup { AddRange(new Drawable[] { - sections = new SectionsContainer + sections = new SetupScreenSectionsContainer { FixedHeader = header, RelativeSizeAxes = Axes.Both, @@ -40,5 +40,19 @@ namespace osu.Game.Screens.Edit.Setup }, }); } + + private class SetupScreenSectionsContainer : SectionsContainer + { + protected override UserTrackingScrollContainer CreateScrollContainer() + { + var scrollContainer = base.CreateScrollContainer(); + + // Workaround for masking issues (see https://github.com/ppy/osu-framework/issues/1675#issuecomment-910023157) + // Note that this actually causes the full scroll range to be reduced by 2px at the bottom, but it's not really noticeable. + scrollContainer.Margin = new MarginPadding { Top = 2 }; + + return scrollContainer; + } + } } } From 2251bf3bcbdb82f706ba81624faf6a943b60324a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 1 Sep 2021 17:08:20 +0900 Subject: [PATCH 099/160] Use lambda spec for method --- .../Profile/Sections/Historical/UserHistoryGraph.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/Profile/Sections/Historical/UserHistoryGraph.cs b/osu.Game/Overlays/Profile/Sections/Historical/UserHistoryGraph.cs index 738edb9310..61f77cd6ff 100644 --- a/osu.Game/Overlays/Profile/Sections/Historical/UserHistoryGraph.cs +++ b/osu.Game/Overlays/Profile/Sections/Historical/UserHistoryGraph.cs @@ -28,12 +28,10 @@ namespace osu.Game.Overlays.Profile.Sections.Historical protected override float GetDataPointHeight(long playCount) => playCount; - protected override UserGraphTooltipContent GetTooltipContent(DateTime date, long playCount) - { - return new UserGraphTooltipContent( + protected override UserGraphTooltipContent GetTooltipContent(DateTime date, long playCount) => + new UserGraphTooltipContent( tooltipCounterName, playCount.ToLocalisableString("N0"), date.ToLocalisableString("MMMM yyyy")); - } } } From fb5f3fb9af60aa071992513eb6228a81a6a1bf1c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 1 Sep 2021 17:19:38 +0900 Subject: [PATCH 100/160] Rename button to be more descriptive of its purpose --- ...ayerModButton.cs => IncompatibilityDisplayingModButton.cs} | 4 ++-- osu.Game/Overlays/Mods/LocalPlayerModSelectOverlay.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) rename osu.Game/Overlays/Mods/{LocalPlayerModButton.cs => IncompatibilityDisplayingModButton.cs} (96%) diff --git a/osu.Game/Overlays/Mods/LocalPlayerModButton.cs b/osu.Game/Overlays/Mods/IncompatibilityDisplayingModButton.cs similarity index 96% rename from osu.Game/Overlays/Mods/LocalPlayerModButton.cs rename to osu.Game/Overlays/Mods/IncompatibilityDisplayingModButton.cs index d26bbf344d..e232081dde 100644 --- a/osu.Game/Overlays/Mods/LocalPlayerModButton.cs +++ b/osu.Game/Overlays/Mods/IncompatibilityDisplayingModButton.cs @@ -18,14 +18,14 @@ using osuTK; namespace osu.Game.Overlays.Mods { - public class LocalPlayerModButton : ModButton + public class IncompatibilityDisplayingModButton : ModButton { private readonly CompositeDrawable incompatibleIcon; [Resolved] private Bindable> selectedMods { get; set; } - public LocalPlayerModButton(Mod mod) + public IncompatibilityDisplayingModButton(Mod mod) : base(mod) { ButtonContent.Add(incompatibleIcon = new IncompatibleIcon diff --git a/osu.Game/Overlays/Mods/LocalPlayerModSelectOverlay.cs b/osu.Game/Overlays/Mods/LocalPlayerModSelectOverlay.cs index b8e0c27007..c872f2c79d 100644 --- a/osu.Game/Overlays/Mods/LocalPlayerModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/LocalPlayerModSelectOverlay.cs @@ -24,7 +24,7 @@ namespace osu.Game.Overlays.Mods { } - protected override ModButton CreateModButton(Mod mod) => new LocalPlayerModButton(mod); + protected override ModButton CreateModButton(Mod mod) => new IncompatibilityDisplayingModButton(mod); } } } From 9e21f5a59c8d476af6f1d66334c9395ecd18dbe4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 1 Sep 2021 17:22:52 +0900 Subject: [PATCH 101/160] Rename `LocalPlayer` to `User` in mod select prefixes --- osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs | 2 +- .../Visual/UserInterface/TestSceneModSelectOverlay.cs | 2 +- .../Visual/UserInterface/TestSceneModSettings.cs | 2 +- .../Overlays/Mods/IncompatibilityDisplayingModButton.cs | 6 +++--- ...lPlayerModSelectOverlay.cs => UserModSelectOverlay.cs} | 8 ++++---- osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs | 4 ---- osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs | 2 +- osu.Game/Screens/Select/SongSelect.cs | 2 +- 8 files changed, 12 insertions(+), 16 deletions(-) rename osu.Game/Overlays/Mods/{LocalPlayerModSelectOverlay.cs => UserModSelectOverlay.cs} (77%) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs index 22338bad84..0b70703870 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs @@ -529,7 +529,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("invoke on back button", () => multiplayerScreen.OnBackButton()); - AddAssert("mod overlay is hidden", () => this.ChildrenOfType().Single().State.Value == Visibility.Hidden); + AddAssert("mod overlay is hidden", () => this.ChildrenOfType().Single().State.Value == Visibility.Hidden); AddAssert("dialog overlay is hidden", () => DialogOverlay.State.Value == Visibility.Hidden); diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs index 32c1d262d5..9e253e089d 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs @@ -422,7 +422,7 @@ namespace osu.Game.Tests.Visual.UserInterface }; } - private class TestModSelectOverlay : LocalPlayerModSelectOverlay + private class TestModSelectOverlay : UserModSelectOverlay { public new Bindable> SelectedMods => base.SelectedMods; diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs index 84e2ebb6d8..da0fa5d76d 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs @@ -151,7 +151,7 @@ namespace osu.Game.Tests.Visual.UserInterface AddUntilStep("wait for ready", () => modSelect.State.Value == Visibility.Visible && modSelect.ButtonsLoaded); } - private class TestModSelectOverlay : LocalPlayerModSelectOverlay + private class TestModSelectOverlay : UserModSelectOverlay { public new VisibilityContainer ModSettingsContainer => base.ModSettingsContainer; public new TriangleButton CustomiseButton => base.CustomiseButton; diff --git a/osu.Game/Overlays/Mods/IncompatibilityDisplayingModButton.cs b/osu.Game/Overlays/Mods/IncompatibilityDisplayingModButton.cs index e232081dde..c8e44ee159 100644 --- a/osu.Game/Overlays/Mods/IncompatibilityDisplayingModButton.cs +++ b/osu.Game/Overlays/Mods/IncompatibilityDisplayingModButton.cs @@ -65,9 +65,9 @@ namespace osu.Game.Overlays.Mods incompatibleIcon.Hide(); } - public override ITooltip GetCustomTooltip() => new LocalPlayerModButtonTooltip(); + public override ITooltip GetCustomTooltip() => new IncompatibilityDisplayingTooltip(); - private class LocalPlayerModButtonTooltip : ModButtonTooltip + private class IncompatibilityDisplayingTooltip : ModButtonTooltip { private readonly OsuSpriteText incompatibleText; @@ -76,7 +76,7 @@ namespace osu.Game.Overlays.Mods [Resolved] private Bindable ruleset { get; set; } - public LocalPlayerModButtonTooltip() + public IncompatibilityDisplayingTooltip() { AddRange(new Drawable[] { diff --git a/osu.Game/Overlays/Mods/LocalPlayerModSelectOverlay.cs b/osu.Game/Overlays/Mods/UserModSelectOverlay.cs similarity index 77% rename from osu.Game/Overlays/Mods/LocalPlayerModSelectOverlay.cs rename to osu.Game/Overlays/Mods/UserModSelectOverlay.cs index c872f2c79d..161f89c2eb 100644 --- a/osu.Game/Overlays/Mods/LocalPlayerModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/UserModSelectOverlay.cs @@ -5,7 +5,7 @@ using osu.Game.Rulesets.Mods; namespace osu.Game.Overlays.Mods { - public class LocalPlayerModSelectOverlay : ModSelectOverlay + public class UserModSelectOverlay : ModSelectOverlay { protected override void OnModSelected(Mod mod) { @@ -15,11 +15,11 @@ namespace osu.Game.Overlays.Mods section.DeselectTypes(mod.IncompatibleMods, true, mod); } - protected override ModSection CreateModSection(ModType type) => new LocalPlayerModSection(type); + protected override ModSection CreateModSection(ModType type) => new UserModSection(type); - private class LocalPlayerModSection : ModSection + private class UserModSection : ModSection { - public LocalPlayerModSection(ModType type) + public UserModSection(ModType type) : base(type) { } diff --git a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs index c05022a903..9095b78eb4 100644 --- a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs @@ -429,10 +429,6 @@ namespace osu.Game.Screens.OnlinePlay.Match /// The room to change the settings of. protected abstract RoomSettingsOverlay CreateRoomSettingsOverlay(Room room); - private class UserModSelectOverlay : LocalPlayerModSelectOverlay - { - } - public class UserModSelectButton : PurpleTriangleButton { } diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs b/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs index 1a063fd6c6..4bc0b55433 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs @@ -152,7 +152,7 @@ namespace osu.Game.Screens.OnlinePlay return base.OnExiting(next); } - protected override ModSelectOverlay CreateModSelectOverlay() => new LocalPlayerModSelectOverlay + protected override ModSelectOverlay CreateModSelectOverlay() => new UserModSelectOverlay { IsValidMod = IsValidMod }; diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index bb3df0d4e0..b3d715e580 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -315,7 +315,7 @@ namespace osu.Game.Screens.Select (new FooterButtonOptions(), BeatmapOptions) }; - protected virtual ModSelectOverlay CreateModSelectOverlay() => new LocalPlayerModSelectOverlay(); + protected virtual ModSelectOverlay CreateModSelectOverlay() => new UserModSelectOverlay(); protected virtual void ApplyFilterToCarousel(FilterCriteria criteria) { From 492209fe13b80b94e7aa6602489ac0c00fa067a5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 1 Sep 2021 17:01:49 +0000 Subject: [PATCH 102/160] Bump Sentry from 3.8.3 to 3.9.0 Bumps [Sentry](https://github.com/getsentry/sentry-dotnet) from 3.8.3 to 3.9.0. - [Release notes](https://github.com/getsentry/sentry-dotnet/releases) - [Changelog](https://github.com/getsentry/sentry-dotnet/blob/main/CHANGELOG.md) - [Commits](https://github.com/getsentry/sentry-dotnet/compare/3.8.3...3.9.0) --- updated-dependencies: - dependency-name: Sentry dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- osu.Game/osu.Game.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index b4d4aa3070..75ba85e3ef 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -38,7 +38,7 @@ - + From 6f0d1b394d6d4699404c27b152e711b923d342fa Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 1 Sep 2021 17:01:53 +0000 Subject: [PATCH 103/160] Bump Microsoft.AspNetCore.SignalR.Protocols.MessagePack Bumps [Microsoft.AspNetCore.SignalR.Protocols.MessagePack](https://github.com/dotnet/aspnetcore) from 5.0.8 to 5.0.9. - [Release notes](https://github.com/dotnet/aspnetcore/releases) - [Commits](https://github.com/dotnet/aspnetcore/compare/v5.0.8...v5.0.9) --- updated-dependencies: - dependency-name: Microsoft.AspNetCore.SignalR.Protocols.MessagePack dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- osu.Game/osu.Game.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index b4d4aa3070..bd42923413 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -24,7 +24,7 @@ - + From c5597b7d9ced6af9d81f5b2ad9b08809a6514b17 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 1 Sep 2021 17:02:03 +0000 Subject: [PATCH 104/160] Bump BenchmarkDotNet from 0.13.0 to 0.13.1 Bumps [BenchmarkDotNet](https://github.com/dotnet/BenchmarkDotNet) from 0.13.0 to 0.13.1. - [Release notes](https://github.com/dotnet/BenchmarkDotNet/releases) - [Commits](https://github.com/dotnet/BenchmarkDotNet/compare/v0.13.0...v0.13.1) --- updated-dependencies: - dependency-name: BenchmarkDotNet dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- osu.Game.Benchmarks/osu.Game.Benchmarks.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj b/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj index da8a0540f4..03f39f226c 100644 --- a/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj +++ b/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj @@ -7,7 +7,7 @@ - + From 860f04af0031d6fcd455489dfcd864fbdc3a62a6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 1 Sep 2021 17:02:11 +0000 Subject: [PATCH 105/160] Bump ppy.osu.Framework.NativeLibs from 2021.115.0 to 2021.805.0 Bumps [ppy.osu.Framework.NativeLibs](https://github.com/ppy/osu-framework) from 2021.115.0 to 2021.805.0. - [Release notes](https://github.com/ppy/osu-framework/releases) - [Commits](https://github.com/ppy/osu-framework/compare/2021.115.0...2021.805.0) --- updated-dependencies: - dependency-name: ppy.osu.Framework.NativeLibs dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- osu.iOS.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.iOS.props b/osu.iOS.props index 29e9b9fe20..1714bae53c 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -98,7 +98,7 @@ - + From 5a1eccd8e3a5fb8f5241abda8753e1a432d58649 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 1 Sep 2021 18:17:37 +0000 Subject: [PATCH 106/160] Bump Microsoft.NET.Test.Sdk from 16.10.0 to 16.11.0 Bumps [Microsoft.NET.Test.Sdk](https://github.com/microsoft/vstest) from 16.10.0 to 16.11.0. - [Release notes](https://github.com/microsoft/vstest/releases) - [Commits](https://github.com/microsoft/vstest/compare/v16.10.0...v16.11.0) --- updated-dependencies: - dependency-name: Microsoft.NET.Test.Sdk dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .../osu.Game.Rulesets.EmptyFreeform.Tests.csproj | 2 +- .../osu.Game.Rulesets.Pippidon.Tests.csproj | 2 +- .../osu.Game.Rulesets.EmptyScrolling.Tests.csproj | 2 +- .../osu.Game.Rulesets.Pippidon.Tests.csproj | 2 +- .../osu.Game.Rulesets.Catch.Tests.csproj | 2 +- .../osu.Game.Rulesets.Mania.Tests.csproj | 2 +- osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj | 2 +- .../osu.Game.Rulesets.Taiko.Tests.csproj | 2 +- osu.Game.Tests/osu.Game.Tests.csproj | 2 +- osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj | 2 +- 10 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/osu.Game.Rulesets.EmptyFreeform.Tests.csproj b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/osu.Game.Rulesets.EmptyFreeform.Tests.csproj index 3dd6be7307..e28053d0ca 100644 --- a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/osu.Game.Rulesets.EmptyFreeform.Tests.csproj +++ b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/osu.Game.Rulesets.EmptyFreeform.Tests.csproj @@ -10,7 +10,7 @@ - + diff --git a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj index 0c4bfe0ed7..027bd0b7e2 100644 --- a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj +++ b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj @@ -10,7 +10,7 @@ - + diff --git a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/osu.Game.Rulesets.EmptyScrolling.Tests.csproj b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/osu.Game.Rulesets.EmptyScrolling.Tests.csproj index bb0a487274..e2c715d385 100644 --- a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/osu.Game.Rulesets.EmptyScrolling.Tests.csproj +++ b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/osu.Game.Rulesets.EmptyScrolling.Tests.csproj @@ -10,7 +10,7 @@ - + diff --git a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj index 0c4bfe0ed7..027bd0b7e2 100644 --- a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj +++ b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj @@ -10,7 +10,7 @@ - + diff --git a/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj b/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj index 484da8e22e..6457ec92da 100644 --- a/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj +++ b/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj @@ -2,7 +2,7 @@ - + diff --git a/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj b/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj index 6df555617b..674a22df98 100644 --- a/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj +++ b/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj @@ -2,7 +2,7 @@ - + diff --git a/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj b/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj index 68be34d153..f5f1159542 100644 --- a/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj +++ b/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj @@ -2,7 +2,7 @@ - + diff --git a/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj b/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj index 532fdc5cb0..b9b295767e 100644 --- a/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj +++ b/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj @@ -2,7 +2,7 @@ - + diff --git a/osu.Game.Tests/osu.Game.Tests.csproj b/osu.Game.Tests/osu.Game.Tests.csproj index 161e248d96..696f930467 100644 --- a/osu.Game.Tests/osu.Game.Tests.csproj +++ b/osu.Game.Tests/osu.Game.Tests.csproj @@ -3,7 +3,7 @@ - + diff --git a/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj b/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj index ba096abd36..2673c9ec9f 100644 --- a/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj +++ b/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj @@ -5,7 +5,7 @@ - + From 8ba00f673764d916882665fd9461a9b43bde4a7a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 2 Sep 2021 02:33:56 +0000 Subject: [PATCH 107/160] Bump HtmlAgilityPack from 1.11.34 to 1.11.36 Bumps [HtmlAgilityPack](https://github.com/zzzprojects/html-agility-pack) from 1.11.34 to 1.11.36. - [Release notes](https://github.com/zzzprojects/html-agility-pack/releases) - [Commits](https://github.com/zzzprojects/html-agility-pack/compare/v1.11.34...v1.11.36) --- updated-dependencies: - dependency-name: HtmlAgilityPack dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- osu.Game/osu.Game.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 3ed8e31c85..36fa178189 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -20,7 +20,7 @@ - + From be379051f2684d033fd0e30416ecb1cdad225dc0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 2 Sep 2021 02:34:19 +0000 Subject: [PATCH 108/160] Bump Microsoft.AspNetCore.SignalR.Client from 5.0.8 to 5.0.9 Bumps [Microsoft.AspNetCore.SignalR.Client](https://github.com/dotnet/aspnetcore) from 5.0.8 to 5.0.9. - [Release notes](https://github.com/dotnet/aspnetcore/releases) - [Commits](https://github.com/dotnet/aspnetcore/compare/v5.0.8...v5.0.9) --- updated-dependencies: - dependency-name: Microsoft.AspNetCore.SignalR.Client dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- osu.Game/osu.Game.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 3ed8e31c85..710aad7336 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -23,7 +23,7 @@ - + From e176babb258578c76e0cd3573fe3c337de9c779a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 2 Sep 2021 02:34:24 +0000 Subject: [PATCH 109/160] Bump Microsoft.AspNetCore.SignalR.Protocols.NewtonsoftJson Bumps [Microsoft.AspNetCore.SignalR.Protocols.NewtonsoftJson](https://github.com/dotnet/aspnetcore) from 5.0.8 to 5.0.9. - [Release notes](https://github.com/dotnet/aspnetcore/releases) - [Commits](https://github.com/dotnet/aspnetcore/compare/v5.0.8...v5.0.9) --- updated-dependencies: - dependency-name: Microsoft.AspNetCore.SignalR.Protocols.NewtonsoftJson dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- osu.Game/osu.Game.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 3ed8e31c85..1f2b78f5ba 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -25,7 +25,7 @@ - + From 31433c4b894c80063d78865e9e81658653d6b013 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 2 Sep 2021 16:26:17 +0900 Subject: [PATCH 110/160] Apply @spaceman_atlas' quadratic factor --- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 2a7691269d..e09225f967 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -227,7 +227,8 @@ namespace osu.Game.Rulesets.Scoring case ScoringMode.Classic: // This gives a similar feeling to osu!stable scoring (ScoreV1) while keeping classic scoring as only a constant multiple of standardised scoring. // The invariant is important to ensure that scores don't get re-ordered on leaderboards between the two scoring modes. - return GetScore(ScoringMode.Standardised, maxCombo, accuracyRatio, comboRatio, statistics) / max_score * Math.Pow(maxCombo, 2) * 25; + var scaledStandardised = GetScore(ScoringMode.Standardised, maxCombo, accuracyRatio, comboRatio, statistics) / max_score; + return Math.Pow(scaledStandardised * maxCombo, 2) * 18; } } From b907c2f4f6bb37c22428520c238b7d6861ec5fde Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 2 Sep 2021 16:31:43 +0900 Subject: [PATCH 111/160] Fix osu! judgements getting scaled twice over different durations --- .../UI/DrawableManiaJudgement.cs | 5 +++-- .../Objects/Drawables/DrawableOsuJudgement.cs | 10 ++++++--- .../UI/DrawableTaikoJudgement.cs | 22 +++++++++++++++++++ .../Judgements/DefaultJudgementPiece.cs | 13 ++++++----- 4 files changed, 39 insertions(+), 11 deletions(-) diff --git a/osu.Game.Rulesets.Mania/UI/DrawableManiaJudgement.cs b/osu.Game.Rulesets.Mania/UI/DrawableManiaJudgement.cs index 34d972e60f..8581f016b1 100644 --- a/osu.Game.Rulesets.Mania/UI/DrawableManiaJudgement.cs +++ b/osu.Game.Rulesets.Mania/UI/DrawableManiaJudgement.cs @@ -37,12 +37,11 @@ namespace osu.Game.Rulesets.Mania.UI public override void PlayAnimation() { - base.PlayAnimation(); - switch (Result) { case HitResult.None: case HitResult.Miss: + base.PlayAnimation(); break; default: @@ -52,6 +51,8 @@ namespace osu.Game.Rulesets.Mania.UI this.Delay(50) .ScaleTo(0.75f, 250) .FadeOut(200); + + // osu!mania uses a custom fade length, so the base call is intentionally omitted. break; } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs index b23087a1f3..e4df41a4fe 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs @@ -74,10 +74,14 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables public override void PlayAnimation() { - base.PlayAnimation(); - if (Result != HitResult.Miss) - JudgementText.ScaleTo(new Vector2(0.8f, 1)).Then().ScaleTo(new Vector2(1.2f, 1), 1800, Easing.OutQuint); + { + JudgementText + .ScaleTo(new Vector2(0.8f, 1)) + .ScaleTo(new Vector2(1.2f, 1), 1800, Easing.OutQuint); + } + + base.PlayAnimation(); } } } diff --git a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoJudgement.cs b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoJudgement.cs index 1ad1e4495c..876fa207bf 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoJudgement.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoJudgement.cs @@ -3,6 +3,7 @@ using osu.Framework.Graphics; using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Taiko.UI { @@ -16,5 +17,26 @@ namespace osu.Game.Rulesets.Taiko.UI this.MoveToY(-100, 500); base.ApplyHitAnimations(); } + + protected override Drawable CreateDefaultJudgement(HitResult result) => new TaikoJudgementPiece(result); + + private class TaikoJudgementPiece : DefaultJudgementPiece + { + public TaikoJudgementPiece(HitResult result) + : base(result) + { + } + + public override void PlayAnimation() + { + if (Result != HitResult.Miss) + { + JudgementText.ScaleTo(0.9f); + JudgementText.ScaleTo(1, 500, Easing.OutElastic); + } + + base.PlayAnimation(); + } + } } } diff --git a/osu.Game/Rulesets/Judgements/DefaultJudgementPiece.cs b/osu.Game/Rulesets/Judgements/DefaultJudgementPiece.cs index 21ac017685..29b771a81d 100644 --- a/osu.Game/Rulesets/Judgements/DefaultJudgementPiece.cs +++ b/osu.Game/Rulesets/Judgements/DefaultJudgementPiece.cs @@ -47,6 +47,13 @@ namespace osu.Game.Rulesets.Judgements }; } + /// + /// Plays the default animation for this judgement piece. + /// + /// + /// The base implementation only handles fade (for all result types) and misses. + /// Individual rulesets are recommended to implement their appropriate hit animations. + /// public virtual void PlayAnimation() { switch (Result) @@ -60,12 +67,6 @@ namespace osu.Game.Rulesets.Judgements this.RotateTo(0); this.RotateTo(40, 800, Easing.InQuint); - - break; - - default: - this.ScaleTo(0.9f); - this.ScaleTo(1, 500, Easing.OutElastic); break; } From e2f7aaeb71bbe841e91e61f0433ed40d3886c265 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 2 Sep 2021 17:00:13 +0900 Subject: [PATCH 112/160] Fix 0 score with bonus-only maps --- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index e09225f967..c1234f8fb3 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -227,8 +227,8 @@ namespace osu.Game.Rulesets.Scoring case ScoringMode.Classic: // This gives a similar feeling to osu!stable scoring (ScoreV1) while keeping classic scoring as only a constant multiple of standardised scoring. // The invariant is important to ensure that scores don't get re-ordered on leaderboards between the two scoring modes. - var scaledStandardised = GetScore(ScoringMode.Standardised, maxCombo, accuracyRatio, comboRatio, statistics) / max_score; - return Math.Pow(scaledStandardised * maxCombo, 2) * 18; + double scaledStandardised = GetScore(ScoringMode.Standardised, maxCombo, accuracyRatio, comboRatio, statistics) / max_score; + return Math.Pow(scaledStandardised * (maxCombo + 1), 2) * 18; } } From e3ec7e3ddc7972b0bad19d1750ea0faffd4edc99 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 2 Sep 2021 17:01:09 +0900 Subject: [PATCH 113/160] Adjust test values --- .../Rulesets/Scoring/ScoreProcessorTest.cs | 35 +++++++++---------- 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs index 184a94912a..f0d9ece06f 100644 --- a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs +++ b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs @@ -36,9 +36,9 @@ namespace osu.Game.Tests.Rulesets.Scoring [TestCase(ScoringMode.Standardised, HitResult.Meh, 750_000)] [TestCase(ScoringMode.Standardised, HitResult.Ok, 800_000)] [TestCase(ScoringMode.Standardised, HitResult.Great, 1_000_000)] - [TestCase(ScoringMode.Classic, HitResult.Meh, 50)] - [TestCase(ScoringMode.Classic, HitResult.Ok, 100)] - [TestCase(ScoringMode.Classic, HitResult.Great, 300)] + [TestCase(ScoringMode.Classic, HitResult.Meh, 41)] + [TestCase(ScoringMode.Classic, HitResult.Ok, 46)] + [TestCase(ScoringMode.Classic, HitResult.Great, 72)] public void TestSingleOsuHit(ScoringMode scoringMode, HitResult hitResult, int expectedScore) { scoreProcessor.Mode.Value = scoringMode; @@ -85,19 +85,18 @@ namespace osu.Game.Tests.Rulesets.Scoring [TestCase(ScoringMode.Standardised, HitResult.LargeTickHit, HitResult.LargeTickHit, 575_000)] // (3 * 30) / (4 * 30) * 300_000 + (0 / 4) * 700_000 [TestCase(ScoringMode.Standardised, HitResult.SmallBonus, HitResult.SmallBonus, 1_000_030)] // 1 * 300_000 + 700_000 (max combo 0) + 3 * 10 (bonus points) [TestCase(ScoringMode.Standardised, HitResult.LargeBonus, HitResult.LargeBonus, 1_000_150)] // 1 * 300_000 + 700_000 (max combo 0) + 3 * 50 (bonus points) - [TestCase(ScoringMode.Classic, HitResult.Miss, HitResult.Great, 0)] // (0 * 4 * 300) * (1 + 0 / 25) - [TestCase(ScoringMode.Classic, HitResult.Meh, HitResult.Great, 156)] // (((3 * 50) / (4 * 300)) * 4 * 300) * (1 + 1 / 25) - [TestCase(ScoringMode.Classic, HitResult.Ok, HitResult.Great, 312)] // (((3 * 100) / (4 * 300)) * 4 * 300) * (1 + 1 / 25) - [TestCase(ScoringMode.Classic, HitResult.Good, HitResult.Perfect, 594)] // (((3 * 200) / (4 * 350)) * 4 * 300) * (1 + 1 / 25) - [TestCase(ScoringMode.Classic, HitResult.Great, HitResult.Great, 936)] // (((3 * 300) / (4 * 300)) * 4 * 300) * (1 + 1 / 25) - [TestCase(ScoringMode.Classic, HitResult.Perfect, HitResult.Perfect, 936)] // (((3 * 350) / (4 * 350)) * 4 * 300) * (1 + 1 / 25) - [TestCase(ScoringMode.Classic, HitResult.SmallTickMiss, HitResult.SmallTickHit, 0)] // (0 * 1 * 300) * (1 + 0 / 25) - [TestCase(ScoringMode.Classic, HitResult.SmallTickHit, HitResult.SmallTickHit, 225)] // (((3 * 10) / (4 * 10)) * 1 * 300) * (1 + 0 / 25) - [TestCase(ScoringMode.Classic, HitResult.LargeTickMiss, HitResult.LargeTickHit, 0)] // (0 * 4 * 300) * (1 + 0 / 25) - [TestCase(ScoringMode.Classic, HitResult.LargeTickHit, HitResult.LargeTickHit, 936)] // (((3 * 50) / (4 * 50)) * 4 * 300) * (1 + 1 / 25) - // TODO: The following two cases don't match expectations currently (a single hit is registered in acc portion when it shouldn't be). See https://github.com/ppy/osu/issues/12604. - [TestCase(ScoringMode.Classic, HitResult.SmallBonus, HitResult.SmallBonus, 330)] // (1 * 1 * 300) * (1 + 0 / 25) + 3 * 10 (bonus points) - [TestCase(ScoringMode.Classic, HitResult.LargeBonus, HitResult.LargeBonus, 450)] // (1 * 1 * 300) * (1 + 0 / 25) + 3 * 50 (bonus points) + [TestCase(ScoringMode.Classic, HitResult.Miss, HitResult.Great, 0)] + [TestCase(ScoringMode.Classic, HitResult.Meh, HitResult.Great, 68)] + [TestCase(ScoringMode.Classic, HitResult.Ok, HitResult.Great, 81)] + [TestCase(ScoringMode.Classic, HitResult.Good, HitResult.Perfect, 109)] + [TestCase(ScoringMode.Classic, HitResult.Great, HitResult.Great, 149)] + [TestCase(ScoringMode.Classic, HitResult.Perfect, HitResult.Perfect, 149)] + [TestCase(ScoringMode.Classic, HitResult.SmallTickMiss, HitResult.SmallTickHit, 9)] + [TestCase(ScoringMode.Classic, HitResult.SmallTickHit, HitResult.SmallTickHit, 15)] + [TestCase(ScoringMode.Classic, HitResult.LargeTickMiss, HitResult.LargeTickHit, 0)] + [TestCase(ScoringMode.Classic, HitResult.LargeTickHit, HitResult.LargeTickHit, 149)] + [TestCase(ScoringMode.Classic, HitResult.SmallBonus, HitResult.SmallBonus, 18)] + [TestCase(ScoringMode.Classic, HitResult.LargeBonus, HitResult.LargeBonus, 18)] public void TestFourVariousResultsOneMiss(ScoringMode scoringMode, HitResult hitResult, HitResult maxResult, int expectedScore) { var minResult = new TestJudgement(hitResult).MinResult; @@ -129,8 +128,8 @@ namespace osu.Game.Tests.Rulesets.Scoring /// [TestCase(ScoringMode.Standardised, HitResult.SmallTickHit, 978_571)] // (3 * 10 + 100) / (4 * 10 + 100) * 300_000 + (1 / 1) * 700_000 [TestCase(ScoringMode.Standardised, HitResult.SmallTickMiss, 914_286)] // (3 * 0 + 100) / (4 * 10 + 100) * 300_000 + (1 / 1) * 700_000 - [TestCase(ScoringMode.Classic, HitResult.SmallTickHit, 279)] // (((3 * 10 + 100) / (4 * 10 + 100)) * 1 * 300) * (1 + 0 / 25) - [TestCase(ScoringMode.Classic, HitResult.SmallTickMiss, 214)] // (((3 * 0 + 100) / (4 * 10 + 100)) * 1 * 300) * (1 + 0 / 25) + [TestCase(ScoringMode.Classic, HitResult.SmallTickHit, 69)] // (((3 * 10 + 100) / (4 * 10 + 100)) * 1 * 300) * (1 + 0 / 25) + [TestCase(ScoringMode.Classic, HitResult.SmallTickMiss, 60)] // (((3 * 0 + 100) / (4 * 10 + 100)) * 1 * 300) * (1 + 0 / 25) public void TestSmallTicksAccuracy(ScoringMode scoringMode, HitResult hitResult, int expectedScore) { IEnumerable hitObjects = Enumerable From ce1912781e8b7144fbf4310031e4263636b0178e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 22 Aug 2021 16:40:17 +0200 Subject: [PATCH 114/160] Add extension point for ruleset-specific beatmap setup sections --- osu.Game/Rulesets/Ruleset.cs | 7 +++++ .../Screens/Edit/Setup/RulesetSetupSection.cs | 20 ++++++++++++++ osu.Game/Screens/Edit/Setup/SetupScreen.cs | 27 ++++++++++++------- osu.Game/Screens/Edit/Setup/SetupSection.cs | 7 ++--- 4 files changed, 48 insertions(+), 13 deletions(-) create mode 100644 osu.Game/Screens/Edit/Setup/RulesetSetupSection.cs diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index 80be61ead1..0a537f2442 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -28,6 +28,7 @@ using osu.Framework.Extensions.EnumExtensions; using osu.Framework.Testing; using osu.Game.Extensions; using osu.Game.Rulesets.Filter; +using osu.Game.Screens.Edit.Setup; using osu.Game.Screens.Ranking.Statistics; namespace osu.Game.Rulesets @@ -315,5 +316,11 @@ namespace osu.Game.Rulesets /// [CanBeNull] public virtual IRulesetFilterCriteria CreateRulesetFilterCriteria() => null; + + /// + /// Can be overridden to add a ruleset-specific section to the editor beatmap setup screen. + /// + [CanBeNull] + public virtual RulesetSetupSection CreateEditorSetupSectionForRuleset() => null; } } diff --git a/osu.Game/Screens/Edit/Setup/RulesetSetupSection.cs b/osu.Game/Screens/Edit/Setup/RulesetSetupSection.cs new file mode 100644 index 0000000000..9344d5b491 --- /dev/null +++ b/osu.Game/Screens/Edit/Setup/RulesetSetupSection.cs @@ -0,0 +1,20 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Localisation; +using osu.Game.Rulesets; + +namespace osu.Game.Screens.Edit.Setup +{ + public abstract class RulesetSetupSection : SetupSection + { + public sealed override LocalisableString Title => $"{rulesetInfo.Name}-specific"; + + private readonly RulesetInfo rulesetInfo; + + protected RulesetSetupSection(RulesetInfo rulesetInfo) + { + this.rulesetInfo = rulesetInfo; + } + } +} diff --git a/osu.Game/Screens/Edit/Setup/SetupScreen.cs b/osu.Game/Screens/Edit/Setup/SetupScreen.cs index 746cf38867..53ddb13d85 100644 --- a/osu.Game/Screens/Edit/Setup/SetupScreen.cs +++ b/osu.Game/Screens/Edit/Setup/SetupScreen.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Game.Graphics.Containers; @@ -21,22 +22,28 @@ namespace osu.Game.Screens.Edit.Setup } [BackgroundDependencyLoader] - private void load() + private void load(EditorBeatmap beatmap) { + var sectionsEnumerable = new List + { + new ResourcesSection(), + new MetadataSection(), + new DifficultySection(), + new ColoursSection(), + new DesignSection(), + }; + + var rulesetSpecificSection = beatmap.BeatmapInfo.Ruleset?.CreateInstance()?.CreateEditorSetupSectionForRuleset(); + if (rulesetSpecificSection != null) + sectionsEnumerable.Add(rulesetSpecificSection); + AddRange(new Drawable[] { sections = new SetupScreenSectionsContainer { - FixedHeader = header, RelativeSizeAxes = Axes.Both, - Children = new SetupSection[] - { - new ResourcesSection(), - new MetadataSection(), - new DifficultySection(), - new ColoursSection(), - new DesignSection(), - } + ChildrenEnumerable = sectionsEnumerable, + FixedHeader = header }, }); } diff --git a/osu.Game/Screens/Edit/Setup/SetupSection.cs b/osu.Game/Screens/Edit/Setup/SetupSection.cs index 1f988d62e2..1dde6fb926 100644 --- a/osu.Game/Screens/Edit/Setup/SetupSection.cs +++ b/osu.Game/Screens/Edit/Setup/SetupSection.cs @@ -12,9 +12,9 @@ using osuTK; namespace osu.Game.Screens.Edit.Setup { - internal abstract class SetupSection : Container + public abstract class SetupSection : Container { - private readonly FillFlowContainer flow; + private FillFlowContainer flow; /// /// Used to align some of the child s together to achieve a grid-like look. @@ -31,7 +31,8 @@ namespace osu.Game.Screens.Edit.Setup public abstract LocalisableString Title { get; } - protected SetupSection() + [BackgroundDependencyLoader] + private void load() { RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; From a2d2ed2ef68565a72b3560e81ad3957500d2afa3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 22 Aug 2021 16:49:27 +0200 Subject: [PATCH 115/160] Add stack leniency setting for osu! --- .../Edit/Setup/OsuSetupSection.cs | 52 +++++++++++++++++++ osu.Game.Rulesets.Osu/OsuRuleset.cs | 4 ++ 2 files changed, 56 insertions(+) create mode 100644 osu.Game.Rulesets.Osu/Edit/Setup/OsuSetupSection.cs diff --git a/osu.Game.Rulesets.Osu/Edit/Setup/OsuSetupSection.cs b/osu.Game.Rulesets.Osu/Edit/Setup/OsuSetupSection.cs new file mode 100644 index 0000000000..8cb778a2e1 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Edit/Setup/OsuSetupSection.cs @@ -0,0 +1,52 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Game.Graphics.UserInterfaceV2; +using osu.Game.Screens.Edit.Setup; + +namespace osu.Game.Rulesets.Osu.Edit.Setup +{ + public class OsuSetupSection : RulesetSetupSection + { + private LabelledSliderBar stackLeniency; + + public OsuSetupSection() + : base(new OsuRuleset().RulesetInfo) + { + } + + [BackgroundDependencyLoader] + private void load() + { + Children = new[] + { + stackLeniency = new LabelledSliderBar + { + Label = "Stack Leniency", + Description = "In play mode, osu! automatically stacks notes which occur at the same location. Increasing this value means it is more likely to snap notes of further time-distance.", + Current = new BindableFloat(Beatmap.BeatmapInfo.StackLeniency) + { + Default = 0.7f, + MinValue = 0, + MaxValue = 1, + Precision = 0.1f + } + } + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + stackLeniency.Current.BindValueChanged(_ => updateBeatmap()); + } + + private void updateBeatmap() + { + Beatmap.BeatmapInfo.StackLeniency = stackLeniency.Current.Value; + } + } +} diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index b13cdff1ec..97af7d28af 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -30,9 +30,11 @@ using osu.Game.Skinning; using System; using System.Linq; using osu.Framework.Extensions.EnumExtensions; +using osu.Game.Rulesets.Osu.Edit.Setup; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Skinning.Legacy; using osu.Game.Rulesets.Osu.Statistics; +using osu.Game.Screens.Edit.Setup; using osu.Game.Screens.Ranking.Statistics; namespace osu.Game.Rulesets.Osu @@ -305,5 +307,7 @@ namespace osu.Game.Rulesets.Osu } }; } + + public override RulesetSetupSection CreateEditorSetupSectionForRuleset() => new OsuSetupSection(); } } From 565f147a5c631a97dbddbcac76485104cff8ec03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 22 Aug 2021 17:40:11 +0200 Subject: [PATCH 116/160] Add special style setting for osu!mania --- .../Edit/Setup/ManiaSetupSection.cs | 45 +++++++++++++++++++ osu.Game.Rulesets.Mania/ManiaRuleset.cs | 4 ++ 2 files changed, 49 insertions(+) create mode 100644 osu.Game.Rulesets.Mania/Edit/Setup/ManiaSetupSection.cs diff --git a/osu.Game.Rulesets.Mania/Edit/Setup/ManiaSetupSection.cs b/osu.Game.Rulesets.Mania/Edit/Setup/ManiaSetupSection.cs new file mode 100644 index 0000000000..d0b32a7268 --- /dev/null +++ b/osu.Game.Rulesets.Mania/Edit/Setup/ManiaSetupSection.cs @@ -0,0 +1,45 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Game.Graphics.UserInterfaceV2; +using osu.Game.Screens.Edit.Setup; + +namespace osu.Game.Rulesets.Mania.Edit.Setup +{ + public class ManiaSetupSection : RulesetSetupSection + { + private LabelledSwitchButton specialStyle; + + public ManiaSetupSection() + : base(new ManiaRuleset().RulesetInfo) + { + } + + [BackgroundDependencyLoader] + private void load() + { + Children = new Drawable[] + { + specialStyle = new LabelledSwitchButton + { + Label = "Use special (N+1) style", + Current = { Value = Beatmap.BeatmapInfo.SpecialStyle } + } + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + specialStyle.Current.BindValueChanged(_ => updateBeatmap()); + } + + private void updateBeatmap() + { + Beatmap.BeatmapInfo.SpecialStyle = specialStyle.Current.Value; + } + } +} diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index f4b6e10af4..5ba1e2e6c3 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -27,11 +27,13 @@ using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Configuration; using osu.Game.Rulesets.Mania.Difficulty; using osu.Game.Rulesets.Mania.Edit; +using osu.Game.Rulesets.Mania.Edit.Setup; using osu.Game.Rulesets.Mania.Scoring; using osu.Game.Rulesets.Mania.Skinning.Legacy; using osu.Game.Rulesets.Scoring; using osu.Game.Skinning; using osu.Game.Scoring; +using osu.Game.Screens.Edit.Setup; using osu.Game.Screens.Ranking.Statistics; namespace osu.Game.Rulesets.Mania @@ -390,6 +392,8 @@ namespace osu.Game.Rulesets.Mania { return new ManiaFilterCriteria(); } + + public override RulesetSetupSection CreateEditorSetupSectionForRuleset() => new ManiaSetupSection(); } public enum PlayfieldType From 000b85a860cb4e6f2f8cf12884fdf85adf1799aa Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 3 Sep 2021 17:56:56 +0900 Subject: [PATCH 117/160] Add GH Actions workflow for diffcalc checks --- .github/workflows/test-diffcalc.yml | 150 ++++++++++++++++++++++++++++ 1 file changed, 150 insertions(+) create mode 100644 .github/workflows/test-diffcalc.yml diff --git a/.github/workflows/test-diffcalc.yml b/.github/workflows/test-diffcalc.yml new file mode 100644 index 0000000000..b703c97735 --- /dev/null +++ b/.github/workflows/test-diffcalc.yml @@ -0,0 +1,150 @@ +name: Diffcalc Consistency Checks +on: + issue_comment: + types: [ created ] + +env: + DB_USER: root + DB_HOST: 127.0.0.1 + CONCURRENCY: 4 + ALLOW_DOWNLOAD: 1 + SAVE_DOWNLOADED: 1 + +jobs: + diffcalc: + name: Diffcalc + runs-on: ubuntu-latest + + if: | + contains(github.event.comment.html_url, '/pull/') && + contains(github.event.comment.body, '!pp check') && + ${{ github.event.comment.author_association == 'MEMBER' }} + + + strategy: + fail-fast: false + matrix: + ruleset: + - { name: osu, id: 0 } + - { name: taiko, id: 1 } + - { name: catch, id: 2 } + - { name: mania, id: 3 } + + services: + mysql: + image: mysql:8.0 + env: + MYSQL_ALLOW_EMPTY_PASSWORD: yes + ports: + - 3306:3306 + options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3 + + steps: + - name: Verify MySQL connection from host + run: | + sudo apt-get install -y mysql-client + mysql --host ${{ env.DB_HOST }} -u${{ env.DB_USER }} -e "SHOW DATABASES" + + - name: Create directory structure + run: | + mkdir -p $GITHUB_WORKSPACE/master/ + mkdir -p $GITHUB_WORKSPACE/pr/ + + # Checkout osu + - name: Checkout osu (master) + uses: actions/checkout@v2 + with: + repository: ppy/osu + path: 'master/osu' + - name: Checkout osu (pr) + uses: actions/checkout@v2 + with: + path: 'pr/osu' + + # Checkout osu-difficulty-calculator + - name: Checkout osu-difficulty-calculator (master) + uses: actions/checkout@v2 + with: + repository: ppy/osu-difficulty-calculator + path: 'master/osu-difficulty-calculator' + - name: Checkout osu-difficulty-calculator (pr) + uses: actions/checkout@v2 + with: + repository: ppy/osu-difficulty-calculator + path: 'pr/osu-difficulty-calculator' + + - name: Install .NET 5.0.x + uses: actions/setup-dotnet@v1 + with: + dotnet-version: "5.0.x" + + # Sanity checks to make sure diffcalc is not run when incompatible. + - name: Build diffcalc (master) + run: | + cd $GITHUB_WORKSPACE/master/osu-difficulty-calculator + ./UseLocalOsu.sh + dotnet build + - name: Build diffcalc (pr) + run: | + cd $GITHUB_WORKSPACE/pr/osu-difficulty-calculator + ./UseLocalOsu.sh + dotnet build + + # Initial data imports + - name: Download + import data + run: | + PERFORMANCE_DATA_NAME=$(date +'%Y_%m_01_performance_${{ matrix.ruleset.name }}_random') + BEATMAPS_DATA_NAME=$(date +'%Y_%m_01_osu_files') + + # Set env variable for further steps. + echo "BEATMAPS_PATH=$GITHUB_WORKSPACE/$BEATMAPS_DATA_NAME" >> $GITHUB_ENV + + cd $GITHUB_WORKSPACE + + wget https://data.ppy.sh/$PERFORMANCE_DATA_NAME.tar.bz2 + wget https://data.ppy.sh/$BEATMAPS_DATA_NAME.tar.bz2 + tar -xf $PERFORMANCE_DATA_NAME.tar.bz2 + tar -xf $BEATMAPS_DATA_NAME.tar.bz2 + + cd $GITHUB_WORKSPACE/$PERFORMANCE_DATA_NAME + + mysql --host ${{ env.DB_HOST }} -u${{ env.DB_USER }} -e "CREATE DATABASE osu_master" + mysql --host ${{ env.DB_HOST }} -u${{ env.DB_USER }} -e "CREATE DATABASE osu_pr" + + cat *.sql | mysql --host ${{ env.DB_HOST }} -u${{ env.DB_USER }} --database=osu_master + cat *.sql | mysql --host ${{ env.DB_HOST }} -u${{ env.DB_USER }} --database=osu_pr + + # Run diffcalc + - name: Run diffcalc (master) + env: + DB_NAME: osu_master + run: | + cd $GITHUB_WORKSPACE/master/osu-difficulty-calculator/osu.Server.DifficultyCalculator + dotnet run -c:Release -- all -m ${{ matrix.ruleset.id }} -ac -c ${{ env.CONCURRENCY }} + - name: Run diffcalc (pr) + env: + DB_NAME: osu_pr + run: | + cd $GITHUB_WORKSPACE/pr/osu-difficulty-calculator/osu.Server.DifficultyCalculator + dotnet run -c:Release -- all -m ${{ matrix.ruleset.id }} -ac -c ${{ env.CONCURRENCY }} + + # Print diffs + - name: Print diffs + run: | + mysql --host ${{ env.DB_HOST }} -u${{ env.DB_USER }} -e " + SELECT + m.beatmap_id, + m.mods, + m.diff_unified as `sr_master`, + p.diff_unified as `sr_pr`, + (p.diff_unified - m.diff_unified) as `diff` + FROM osu_master.osu_beatmap_difficulty m + JOIN osu_pr.osu_beatmap_difficulty p + ON m.beatmap_id = p.beatmap_id + AND m.mode = p.mode + AND m.mods = p.mods + WHERE abs(m.diff_unified - p.diff_unified) > 0.1 + ORDER BY abs(m.diff_unified - p.diff_unified) + DESC;" + + # Todo: Run ppcalc \ No newline at end of file From 176c414c112e1321a65fde03b78ecf72a479285a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 3 Sep 2021 18:10:16 +0900 Subject: [PATCH 118/160] More accurate data retrieval --- .github/workflows/test-diffcalc.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test-diffcalc.yml b/.github/workflows/test-diffcalc.yml index b703c97735..3d80f08ebf 100644 --- a/.github/workflows/test-diffcalc.yml +++ b/.github/workflows/test-diffcalc.yml @@ -93,8 +93,8 @@ jobs: # Initial data imports - name: Download + import data run: | - PERFORMANCE_DATA_NAME=$(date +'%Y_%m_01_performance_${{ matrix.ruleset.name }}_random') - BEATMAPS_DATA_NAME=$(date +'%Y_%m_01_osu_files') + PERFORMANCE_DATA_NAME=$(curl https://data.ppy.sh/ | grep performance_${{ matrix.ruleset.name }}_random | tail -1 | awk -F "\"" '{print $2}' | sed 's/\.tar\.bz2//g') + BEATMAPS_DATA_NAME=$(curl https://data.ppy.sh/ | grep osu_files | tail -1 | awk -F "\"" '{print $2}' | sed 's/\.tar\.bz2//g') # Set env variable for further steps. echo "BEATMAPS_PATH=$GITHUB_WORKSPACE/$BEATMAPS_DATA_NAME" >> $GITHUB_ENV From b8ada31d7de0b4fa47abd50a84d83a67e409eec5 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 3 Sep 2021 18:47:15 +0900 Subject: [PATCH 119/160] Match against individual rulesets --- .github/workflows/test-diffcalc.yml | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test-diffcalc.yml b/.github/workflows/test-diffcalc.yml index 3d80f08ebf..48e1e238c1 100644 --- a/.github/workflows/test-diffcalc.yml +++ b/.github/workflows/test-diffcalc.yml @@ -1,3 +1,8 @@ +# Listens to new PR comments containing "!pp check", and runs diffcalc across the PR and master to produce a table of differences. +# Usage: +# !pp check 0 : Runs only the osu! ruleset +# !pp check 0 1 : Runs the osu! and taiko rulesets. + name: Diffcalc Consistency Checks on: issue_comment: @@ -18,8 +23,8 @@ jobs: if: | contains(github.event.comment.html_url, '/pull/') && contains(github.event.comment.body, '!pp check') && - ${{ github.event.comment.author_association == 'MEMBER' }} - + ${{ github.event.comment.author_association == 'MEMBER' }} && + contains(github.event.comment.body, ${{ matrix.ruleset.id }}) strategy: fail-fast: false From 789c108e8d426fba356d4f424427f558c2b19ec8 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 3 Sep 2021 18:56:56 +0900 Subject: [PATCH 120/160] Fix if condition --- .github/workflows/test-diffcalc.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test-diffcalc.yml b/.github/workflows/test-diffcalc.yml index 48e1e238c1..64ffa84c5e 100644 --- a/.github/workflows/test-diffcalc.yml +++ b/.github/workflows/test-diffcalc.yml @@ -23,8 +23,7 @@ jobs: if: | contains(github.event.comment.html_url, '/pull/') && contains(github.event.comment.body, '!pp check') && - ${{ github.event.comment.author_association == 'MEMBER' }} && - contains(github.event.comment.body, ${{ matrix.ruleset.id }}) + ${{ github.event.comment.author_association == 'MEMBER' }} strategy: fail-fast: false @@ -34,6 +33,10 @@ jobs: - { name: taiko, id: 1 } - { name: catch, id: 2 } - { name: mania, id: 3 } + isValidRuleset: + - contains(github.event.comment.body, ${{ matrix.ruleset.id }}) + exclude: + - isValidRuleset: false services: mysql: From 33dcb4915b28e36e354151eb8e7ee929695bddd7 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 3 Sep 2021 19:12:41 +0900 Subject: [PATCH 121/160] Revert "Fix if condition" This reverts commit 789c108e8d426fba356d4f424427f558c2b19ec8. --- .github/workflows/test-diffcalc.yml | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/.github/workflows/test-diffcalc.yml b/.github/workflows/test-diffcalc.yml index 64ffa84c5e..48e1e238c1 100644 --- a/.github/workflows/test-diffcalc.yml +++ b/.github/workflows/test-diffcalc.yml @@ -23,7 +23,8 @@ jobs: if: | contains(github.event.comment.html_url, '/pull/') && contains(github.event.comment.body, '!pp check') && - ${{ github.event.comment.author_association == 'MEMBER' }} + ${{ github.event.comment.author_association == 'MEMBER' }} && + contains(github.event.comment.body, ${{ matrix.ruleset.id }}) strategy: fail-fast: false @@ -33,10 +34,6 @@ jobs: - { name: taiko, id: 1 } - { name: catch, id: 2 } - { name: mania, id: 3 } - isValidRuleset: - - contains(github.event.comment.body, ${{ matrix.ruleset.id }}) - exclude: - - isValidRuleset: false services: mysql: From 54389561aaefd5b1f69f1aa5b360260ba1328676 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 3 Sep 2021 19:12:55 +0900 Subject: [PATCH 122/160] Revert "Match against individual rulesets" This reverts commit b8ada31d7de0b4fa47abd50a84d83a67e409eec5. --- .github/workflows/test-diffcalc.yml | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/.github/workflows/test-diffcalc.yml b/.github/workflows/test-diffcalc.yml index 48e1e238c1..3d80f08ebf 100644 --- a/.github/workflows/test-diffcalc.yml +++ b/.github/workflows/test-diffcalc.yml @@ -1,8 +1,3 @@ -# Listens to new PR comments containing "!pp check", and runs diffcalc across the PR and master to produce a table of differences. -# Usage: -# !pp check 0 : Runs only the osu! ruleset -# !pp check 0 1 : Runs the osu! and taiko rulesets. - name: Diffcalc Consistency Checks on: issue_comment: @@ -23,8 +18,8 @@ jobs: if: | contains(github.event.comment.html_url, '/pull/') && contains(github.event.comment.body, '!pp check') && - ${{ github.event.comment.author_association == 'MEMBER' }} && - contains(github.event.comment.body, ${{ matrix.ruleset.id }}) + ${{ github.event.comment.author_association == 'MEMBER' }} + strategy: fail-fast: false From 893a4d43657ab4d335d951cee8e22708d00e09b4 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 3 Sep 2021 19:23:55 +0900 Subject: [PATCH 123/160] Fix incorrect quoting --- .github/workflows/test-diffcalc.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test-diffcalc.yml b/.github/workflows/test-diffcalc.yml index 3d80f08ebf..1e5c033a67 100644 --- a/.github/workflows/test-diffcalc.yml +++ b/.github/workflows/test-diffcalc.yml @@ -135,9 +135,9 @@ jobs: SELECT m.beatmap_id, m.mods, - m.diff_unified as `sr_master`, - p.diff_unified as `sr_pr`, - (p.diff_unified - m.diff_unified) as `diff` + m.diff_unified as 'sr_master', + p.diff_unified as 'sr_pr', + (p.diff_unified - m.diff_unified) as 'diff' FROM osu_master.osu_beatmap_difficulty m JOIN osu_pr.osu_beatmap_difficulty p ON m.beatmap_id = p.beatmap_id From c6d71e1ae8bb7d3862abd063db7e285f311e4d2d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 3 Sep 2021 20:10:17 +0900 Subject: [PATCH 124/160] Add back ruleset check --- .github/workflows/test-diffcalc.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test-diffcalc.yml b/.github/workflows/test-diffcalc.yml index 1e5c033a67..ab7107f594 100644 --- a/.github/workflows/test-diffcalc.yml +++ b/.github/workflows/test-diffcalc.yml @@ -20,7 +20,6 @@ jobs: contains(github.event.comment.body, '!pp check') && ${{ github.event.comment.author_association == 'MEMBER' }} - strategy: fail-fast: false matrix: @@ -40,6 +39,12 @@ jobs: options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3 steps: + - name: Verify ruleset + if: contains(github.event.comment.body, matrix.ruleset.id) == false + run: | + echo "${{ github.event.comment.body }} doesn't contain ${{ matrix.ruleset.id }}" + exit 1 + - name: Verify MySQL connection from host run: | sudo apt-get install -y mysql-client From 1f7f8bb18992d1a8a558eb432acdc1a7e55e30ec Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 3 Sep 2021 20:13:33 +0900 Subject: [PATCH 125/160] Add description to workflow --- .github/workflows/test-diffcalc.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/test-diffcalc.yml b/.github/workflows/test-diffcalc.yml index ab7107f594..c6b2f254c8 100644 --- a/.github/workflows/test-diffcalc.yml +++ b/.github/workflows/test-diffcalc.yml @@ -1,3 +1,9 @@ +# Listens for new PR comments containing !pp check [id], and runs a diffcalc comparison against master. +# Usage: +# !pp check 0 | Runs only the osu! ruleset. +# !pp check 0 2 | Runs only the osu! and catch rulesets. +# + name: Diffcalc Consistency Checks on: issue_comment: From 88158b79f8e5ad2154fa1efd0f89e10532421600 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 3 Sep 2021 20:37:38 +0900 Subject: [PATCH 126/160] Change to using top scores --- .github/workflows/test-diffcalc.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test-diffcalc.yml b/.github/workflows/test-diffcalc.yml index c6b2f254c8..3bec11928f 100644 --- a/.github/workflows/test-diffcalc.yml +++ b/.github/workflows/test-diffcalc.yml @@ -104,7 +104,7 @@ jobs: # Initial data imports - name: Download + import data run: | - PERFORMANCE_DATA_NAME=$(curl https://data.ppy.sh/ | grep performance_${{ matrix.ruleset.name }}_random | tail -1 | awk -F "\"" '{print $2}' | sed 's/\.tar\.bz2//g') + PERFORMANCE_DATA_NAME=$(curl https://data.ppy.sh/ | grep performance_${{ matrix.ruleset.name }}_top | tail -1 | awk -F "\"" '{print $2}' | sed 's/\.tar\.bz2//g') BEATMAPS_DATA_NAME=$(curl https://data.ppy.sh/ | grep osu_files | tail -1 | awk -F "\"" '{print $2}' | sed 's/\.tar\.bz2//g') # Set env variable for further steps. From 16beb2c90c1884d8829f52cb92142c06498d3b00 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 4 Sep 2021 15:35:46 +0900 Subject: [PATCH 127/160] Expose more pieces of `TabletSettings` --- .../Settings/Sections/Input/TabletAreaSelection.cs | 8 +++++--- .../Overlays/Settings/Sections/Input/TabletSettings.cs | 4 +++- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs b/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs index 412889d210..e18cf7e1c2 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs @@ -17,6 +17,8 @@ namespace osu.Game.Overlays.Settings.Sections.Input { public class TabletAreaSelection : CompositeDrawable { + public bool IsWithinBounds { get; private set; } + private readonly ITabletHandler handler; private Container tabletContainer; @@ -171,10 +173,10 @@ namespace osu.Game.Overlays.Settings.Sections.Input var usableSsdq = usableAreaContainer.ScreenSpaceDrawQuad; - bool isWithinBounds = tabletContainer.ScreenSpaceDrawQuad.Contains(usableSsdq.TopLeft + new Vector2(1)) && - tabletContainer.ScreenSpaceDrawQuad.Contains(usableSsdq.BottomRight - new Vector2(1)); + IsWithinBounds = tabletContainer.ScreenSpaceDrawQuad.Contains(usableSsdq.TopLeft + new Vector2(1)) && + tabletContainer.ScreenSpaceDrawQuad.Contains(usableSsdq.BottomRight - new Vector2(1)); - usableFill.FadeColour(isWithinBounds ? colour.Blue : colour.RedLight, 100); + usableFill.FadeColour(IsWithinBounds ? colour.Blue : colour.RedLight, 100); } protected override void Update() diff --git a/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs b/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs index b8b86d9069..8c60e81fb5 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs @@ -20,6 +20,8 @@ namespace osu.Game.Overlays.Settings.Sections.Input { public class TabletSettings : SettingsSubsection { + public TabletAreaSelection AreaSelection { get; private set; } + private readonly ITabletHandler tabletHandler; private readonly Bindable enabled = new BindableBool(true); @@ -121,7 +123,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input Direction = FillDirection.Vertical, Children = new Drawable[] { - new TabletAreaSelection(tabletHandler) + AreaSelection = new TabletAreaSelection(tabletHandler) { RelativeSizeAxes = Axes.X, Height = 300, From 8d44f059ec952080516bb6f811676404bde64ebb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 4 Sep 2021 15:35:54 +0900 Subject: [PATCH 128/160] Add test coverage of failing validity checks --- .../Settings/TestSceneTabletSettings.cs | 55 ++++++++++++++----- 1 file changed, 42 insertions(+), 13 deletions(-) diff --git a/osu.Game.Tests/Visual/Settings/TestSceneTabletSettings.cs b/osu.Game.Tests/Visual/Settings/TestSceneTabletSettings.cs index da474a64ba..0202393973 100644 --- a/osu.Game.Tests/Visual/Settings/TestSceneTabletSettings.cs +++ b/osu.Game.Tests/Visual/Settings/TestSceneTabletSettings.cs @@ -2,11 +2,10 @@ // See the LICENCE file in the repository root for full licence text. using NUnit.Framework; -using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Input.Handlers.Tablet; -using osu.Framework.Platform; +using osu.Framework.Testing; using osu.Framework.Utils; using osu.Game.Overlays; using osu.Game.Overlays.Settings.Sections.Input; @@ -17,22 +16,34 @@ namespace osu.Game.Tests.Visual.Settings [TestFixture] public class TestSceneTabletSettings : OsuTestScene { - [BackgroundDependencyLoader] - private void load(GameHost host) - { - var tabletHandler = new TestTabletHandler(); + private TestTabletHandler tabletHandler; + private TabletSettings settings; - AddRange(new Drawable[] + [SetUpSteps] + public void SetUpSteps() + { + AddStep("create settings", () => { - new TabletSettings(tabletHandler) + tabletHandler = new TestTabletHandler(); + + Children = new Drawable[] { - RelativeSizeAxes = Axes.None, - Width = SettingsPanel.PANEL_WIDTH, - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - } + settings = new TabletSettings(tabletHandler) + { + RelativeSizeAxes = Axes.None, + Width = SettingsPanel.PANEL_WIDTH, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + } + }; }); + AddStep("set square size", () => tabletHandler.SetTabletSize(new Vector2(100, 100))); + } + + [Test] + public void TestVariousTabletSizes() + { AddStep("Test with wide tablet", () => tabletHandler.SetTabletSize(new Vector2(160, 100))); AddStep("Test with square tablet", () => tabletHandler.SetTabletSize(new Vector2(300, 300))); AddStep("Test with tall tablet", () => tabletHandler.SetTabletSize(new Vector2(100, 300))); @@ -40,6 +51,24 @@ namespace osu.Game.Tests.Visual.Settings AddStep("Test no tablet present", () => tabletHandler.SetTabletSize(Vector2.Zero)); } + [Test] + public void TestValidAfterRotation() + { + AddAssert("area valid", () => settings.AreaSelection.IsWithinBounds); + + AddStep("rotate 90", () => tabletHandler.Rotation.Value = 90); + AddAssert("area valid", () => settings.AreaSelection.IsWithinBounds); + + AddStep("rotate 180", () => tabletHandler.Rotation.Value = 180); + AddAssert("area valid", () => settings.AreaSelection.IsWithinBounds); + + AddStep("rotate 360", () => tabletHandler.Rotation.Value = 360); + AddAssert("area valid", () => settings.AreaSelection.IsWithinBounds); + + AddStep("rotate 0", () => tabletHandler.Rotation.Value = 0); + AddAssert("area valid", () => settings.AreaSelection.IsWithinBounds); + } + public class TestTabletHandler : ITabletHandler { public Bindable AreaOffset { get; } = new Bindable(); From 66daa553de2eac46f35d511a34ff959d1b3ab9c4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 4 Sep 2021 19:34:55 +0900 Subject: [PATCH 129/160] Fix bounds check running too early causing tablet area to show incorrect validity --- .../Overlays/Settings/Sections/Input/TabletAreaSelection.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs b/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs index e18cf7e1c2..4f27a2da70 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs @@ -131,9 +131,9 @@ namespace osu.Game.Overlays.Settings.Sections.Input rotation.BindTo(handler.Rotation); rotation.BindValueChanged(val => { - tabletContainer.RotateTo(-val.NewValue, 800, Easing.OutQuint); - usableAreaContainer.RotateTo(val.NewValue, 100, Easing.OutQuint) - .OnComplete(_ => checkBounds()); // required as we are using SSDQ. + usableAreaContainer.RotateTo(val.NewValue, 100, Easing.OutQuint); + tabletContainer.RotateTo(-val.NewValue, 800, Easing.OutQuint) + .OnComplete(_ => checkBounds()); // required as we are using SSDQ. }, true); tablet.BindTo(handler.Tablet); From 7b26e480e6492348bb0c91b65a20c9f3d9cbfcf5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 4 Sep 2021 22:55:14 +0900 Subject: [PATCH 130/160] Add extended tests --- .../Settings/TestSceneTabletSettings.cs | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Settings/TestSceneTabletSettings.cs b/osu.Game.Tests/Visual/Settings/TestSceneTabletSettings.cs index 0202393973..a854bec1a9 100644 --- a/osu.Game.Tests/Visual/Settings/TestSceneTabletSettings.cs +++ b/osu.Game.Tests/Visual/Settings/TestSceneTabletSettings.cs @@ -1,9 +1,11 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Linq; using NUnit.Framework; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Input.Handlers.Tablet; using osu.Framework.Testing; using osu.Framework.Utils; @@ -57,15 +59,27 @@ namespace osu.Game.Tests.Visual.Settings AddAssert("area valid", () => settings.AreaSelection.IsWithinBounds); AddStep("rotate 90", () => tabletHandler.Rotation.Value = 90); - AddAssert("area valid", () => settings.AreaSelection.IsWithinBounds); + ensureValid(); AddStep("rotate 180", () => tabletHandler.Rotation.Value = 180); - AddAssert("area valid", () => settings.AreaSelection.IsWithinBounds); + + ensureValid(); + + AddStep("rotate 270", () => tabletHandler.Rotation.Value = 270); + + ensureValid(); AddStep("rotate 360", () => tabletHandler.Rotation.Value = 360); - AddAssert("area valid", () => settings.AreaSelection.IsWithinBounds); + + ensureValid(); AddStep("rotate 0", () => tabletHandler.Rotation.Value = 0); + ensureValid(); + } + + private void ensureValid() + { + AddUntilStep("wait for transforms", () => settings.AreaSelection.ChildrenOfType().All(c => !c.Transforms.Any())); AddAssert("area valid", () => settings.AreaSelection.IsWithinBounds); } From 94b34a5474cf625b274f3b6f84477417b62ee179 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 4 Sep 2021 23:19:05 +0900 Subject: [PATCH 131/160] Add test coverage of invalid cases too --- .../Settings/TestSceneTabletSettings.cs | 24 ++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Settings/TestSceneTabletSettings.cs b/osu.Game.Tests/Visual/Settings/TestSceneTabletSettings.cs index a854bec1a9..d5bcbc5fd9 100644 --- a/osu.Game.Tests/Visual/Settings/TestSceneTabletSettings.cs +++ b/osu.Game.Tests/Visual/Settings/TestSceneTabletSettings.cs @@ -54,7 +54,7 @@ namespace osu.Game.Tests.Visual.Settings } [Test] - public void TestValidAfterRotation() + public void TestRotationValidity() { AddAssert("area valid", () => settings.AreaSelection.IsWithinBounds); @@ -75,6 +75,22 @@ namespace osu.Game.Tests.Visual.Settings AddStep("rotate 0", () => tabletHandler.Rotation.Value = 0); ensureValid(); + + AddStep("rotate 0", () => tabletHandler.Rotation.Value = 45); + ensureInvalid(); + + AddStep("rotate 0", () => tabletHandler.Rotation.Value = 0); + ensureValid(); + } + + [Test] + public void TestOffsetValidity() + { + ensureValid(); + AddStep("move right", () => tabletHandler.AreaOffset.Value = Vector2.Zero); + ensureInvalid(); + AddStep("move back", () => tabletHandler.AreaOffset.Value = tabletHandler.AreaSize.Value / 2); + ensureValid(); } private void ensureValid() @@ -83,6 +99,12 @@ namespace osu.Game.Tests.Visual.Settings AddAssert("area valid", () => settings.AreaSelection.IsWithinBounds); } + private void ensureInvalid() + { + AddUntilStep("wait for transforms", () => settings.AreaSelection.ChildrenOfType().All(c => !c.Transforms.Any())); + AddAssert("area invalid", () => !settings.AreaSelection.IsWithinBounds); + } + public class TestTabletHandler : ITabletHandler { public Bindable AreaOffset { get; } = new Bindable(); From 4fb3a1d64162866cb973efeedbe456af2eba8603 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 4 Sep 2021 23:08:35 +0900 Subject: [PATCH 132/160] Update check to inflate in the correct direct Also handles previously unhandled edge cases by comparing all four corners, instead of only two. --- .../Sections/Input/TabletAreaSelection.cs | 27 ++++++++++++++----- 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs b/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs index 4f27a2da70..d12052b24d 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs @@ -4,10 +4,13 @@ using System; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions.MatrixExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Handlers.Tablet; +using osu.Framework.Utils; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osuTK; @@ -131,9 +134,9 @@ namespace osu.Game.Overlays.Settings.Sections.Input rotation.BindTo(handler.Rotation); rotation.BindValueChanged(val => { - usableAreaContainer.RotateTo(val.NewValue, 100, Easing.OutQuint); - tabletContainer.RotateTo(-val.NewValue, 800, Easing.OutQuint) - .OnComplete(_ => checkBounds()); // required as we are using SSDQ. + usableAreaContainer.RotateTo(val.NewValue, 100, Easing.OutQuint) + .OnComplete(_ => checkBounds()); // required as we are using SSDQ. + tabletContainer.RotateTo(-val.NewValue, 800, Easing.OutQuint); }, true); tablet.BindTo(handler.Tablet); @@ -171,10 +174,22 @@ namespace osu.Game.Overlays.Settings.Sections.Input if (tablet.Value == null) return; - var usableSsdq = usableAreaContainer.ScreenSpaceDrawQuad; + // All of this manual logic is just to get around floating point issues when doing a contains check on the screen quads. + // This is best effort, as it's only used for display purposes. If we need for anything more, manual math on the raw values should be preferred. + var containerQuad = tabletContainer.ScreenSpaceDrawQuad.AABBFloat.Inflate(1); + var usableAreaQuad = Quad.FromRectangle(usableAreaContainer.ScreenSpaceDrawQuad.AABBFloat); - IsWithinBounds = tabletContainer.ScreenSpaceDrawQuad.Contains(usableSsdq.TopLeft + new Vector2(1)) && - tabletContainer.ScreenSpaceDrawQuad.Contains(usableSsdq.BottomRight - new Vector2(1)); + var matrix = Matrix3.Identity; + MatrixExtensions.TranslateFromLeft(ref matrix, usableAreaQuad.Centre); + MatrixExtensions.RotateFromLeft(ref matrix, MathUtils.DegreesToRadians(rotation.Value)); + MatrixExtensions.TranslateFromLeft(ref matrix, -usableAreaQuad.Centre); + usableAreaQuad *= matrix; + + IsWithinBounds = + containerQuad.Contains(usableAreaQuad.TopLeft) && + containerQuad.Contains(usableAreaQuad.TopRight) && + containerQuad.Contains(usableAreaQuad.BottomLeft) && + containerQuad.Contains(usableAreaQuad.BottomRight); usableFill.FadeColour(IsWithinBounds ? colour.Blue : colour.RedLight, 100); } From 1a90fb1ef341d3d72a75bc29c6e2410967b67800 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 4 Sep 2021 19:52:42 +0200 Subject: [PATCH 133/160] Fix cached property being assigned twice --- osu.Game/Screens/Edit/Setup/SetupScreen.cs | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/osu.Game/Screens/Edit/Setup/SetupScreen.cs b/osu.Game/Screens/Edit/Setup/SetupScreen.cs index 53ddb13d85..38fcfc5d2f 100644 --- a/osu.Game/Screens/Edit/Setup/SetupScreen.cs +++ b/osu.Game/Screens/Edit/Setup/SetupScreen.cs @@ -11,7 +11,7 @@ namespace osu.Game.Screens.Edit.Setup public class SetupScreen : EditorRoundedScreen { [Cached] - private SectionsContainer sections = new SectionsContainer(); + private SectionsContainer sections { get; } = new SetupScreenSectionsContainer(); [Cached] private SetupScreenHeader header = new SetupScreenHeader(); @@ -37,15 +37,12 @@ namespace osu.Game.Screens.Edit.Setup if (rulesetSpecificSection != null) sectionsEnumerable.Add(rulesetSpecificSection); - AddRange(new Drawable[] + Add(sections.With(s => { - sections = new SetupScreenSectionsContainer - { - RelativeSizeAxes = Axes.Both, - ChildrenEnumerable = sectionsEnumerable, - FixedHeader = header - }, - }); + s.RelativeSizeAxes = Axes.Both; + s.ChildrenEnumerable = sectionsEnumerable; + s.FixedHeader = header; + })); } private class SetupScreenSectionsContainer : SectionsContainer From 4c006333e0298f0998a1a0044df978c521e52dec Mon Sep 17 00:00:00 2001 From: Davran Dilshat Date: Sat, 4 Sep 2021 19:42:14 +0100 Subject: [PATCH 134/160] add /chat command --- osu.Game/Online/Chat/ChannelManager.cs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/Chat/ChannelManager.cs b/osu.Game/Online/Chat/ChannelManager.cs index 1937019ef6..fb8c90a80c 100644 --- a/osu.Game/Online/Chat/ChannelManager.cs +++ b/osu.Game/Online/Chat/ChannelManager.cs @@ -256,8 +256,21 @@ namespace osu.Game.Online.Chat JoinChannel(channel); break; + case "chat": + if (string.IsNullOrWhiteSpace(content)) + { + target.AddNewMessages(new ErrorMessage("Usage: /chat [user]")); + break; + } + + var request = new GetUserRequest(content); + request.Success += u => OpenPrivateChannel(u); + request.Failure += _ => target.AddNewMessages(new ErrorMessage("User not found.")); + api.Queue(request); + break; + case "help": - target.AddNewMessages(new InfoMessage("Supported commands: /help, /me [action], /join [channel], /np")); + target.AddNewMessages(new InfoMessage("Supported commands: /help, /me [action], /join [channel], /chat [user], /np")); break; default: From ea3be927d7f5c8071bacded41a021340b6a6c5e2 Mon Sep 17 00:00:00 2001 From: Davran Dilshat Date: Sat, 4 Sep 2021 20:02:10 +0100 Subject: [PATCH 135/160] convert to method group --- osu.Game/Online/Chat/ChannelManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/Chat/ChannelManager.cs b/osu.Game/Online/Chat/ChannelManager.cs index fb8c90a80c..f58f1ff40c 100644 --- a/osu.Game/Online/Chat/ChannelManager.cs +++ b/osu.Game/Online/Chat/ChannelManager.cs @@ -264,7 +264,7 @@ namespace osu.Game.Online.Chat } var request = new GetUserRequest(content); - request.Success += u => OpenPrivateChannel(u); + request.Success += OpenPrivateChannel; request.Failure += _ => target.AddNewMessages(new ErrorMessage("User not found.")); api.Queue(request); break; From f76f12d361a97238797b7115003bbfc9f79f5272 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 5 Sep 2021 11:14:28 +0900 Subject: [PATCH 136/160] Fix incorrect test step name Co-authored-by: PercyDan <50285552+PercyDan54@users.noreply.github.com> --- osu.Game.Tests/Visual/Settings/TestSceneTabletSettings.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Settings/TestSceneTabletSettings.cs b/osu.Game.Tests/Visual/Settings/TestSceneTabletSettings.cs index d5bcbc5fd9..49d6b7033e 100644 --- a/osu.Game.Tests/Visual/Settings/TestSceneTabletSettings.cs +++ b/osu.Game.Tests/Visual/Settings/TestSceneTabletSettings.cs @@ -76,7 +76,7 @@ namespace osu.Game.Tests.Visual.Settings AddStep("rotate 0", () => tabletHandler.Rotation.Value = 0); ensureValid(); - AddStep("rotate 0", () => tabletHandler.Rotation.Value = 45); + AddStep("rotate 45", () => tabletHandler.Rotation.Value = 45); ensureInvalid(); AddStep("rotate 0", () => tabletHandler.Rotation.Value = 0); From 1d23ac0f2db355914a45affc9491970eeec39248 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 5 Sep 2021 12:54:21 +0900 Subject: [PATCH 137/160] Initial clean up pass on notification logic --- osu.Game/Graphics/UserInterface/HoverSounds.cs | 3 +-- osu.Game/Overlays/NotificationOverlay.cs | 9 +++++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/HoverSounds.cs b/osu.Game/Graphics/UserInterface/HoverSounds.cs index c0ef5cb3fc..7db1efc75f 100644 --- a/osu.Game/Graphics/UserInterface/HoverSounds.cs +++ b/osu.Game/Graphics/UserInterface/HoverSounds.cs @@ -6,7 +6,6 @@ using osu.Framework.Audio; using osu.Framework.Audio.Sample; using osu.Framework.Extensions; using osu.Framework.Graphics; -using osu.Game.Configuration; using osu.Framework.Utils; namespace osu.Game.Graphics.UserInterface @@ -28,7 +27,7 @@ namespace osu.Game.Graphics.UserInterface } [BackgroundDependencyLoader] - private void load(AudioManager audio, SessionStatics statics) + private void load(AudioManager audio) { sampleHover = audio.Samples.Get($@"UI/{SampleSet.GetDescription()}-hover") ?? audio.Samples.Get($@"UI/{HoverSampleSet.Default.GetDescription()}-hover"); diff --git a/osu.Game/Overlays/NotificationOverlay.cs b/osu.Game/Overlays/NotificationOverlay.cs index 2175e17da9..f5b0bf2a7d 100644 --- a/osu.Game/Overlays/NotificationOverlay.cs +++ b/osu.Game/Overlays/NotificationOverlay.cs @@ -98,14 +98,16 @@ namespace osu.Game.Overlays private int runningDepth; - private void notificationClosed() => updateCounts(); - private readonly Scheduler postScheduler = new Scheduler(); public override bool IsPresent => base.IsPresent || postScheduler.HasPendingTasks; private bool processingPosts = true; + /// + /// Post a new notification for display. + /// + /// The notification to display. public void Post(Notification notification) => postScheduler.Add(() => { ++runningDepth; @@ -129,6 +131,7 @@ namespace osu.Game.Overlays protected override void Update() { base.Update(); + if (processingPosts) postScheduler.Update(); } @@ -151,6 +154,8 @@ namespace osu.Game.Overlays this.FadeTo(0, TRANSITION_LENGTH, Easing.OutQuint); } + private void notificationClosed() => updateCounts(); + private void updateCounts() { UnreadCount.Value = sections.Select(c => c.UnreadCount).Sum(); From 473e15e8f3ba1a35419bc0bbb0f72034715a2e80 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 5 Sep 2021 13:22:37 +0900 Subject: [PATCH 138/160] Add debounce to notification sample playback logic --- osu.Game/Overlays/NotificationOverlay.cs | 25 +++++++++++++++- .../Overlays/Notifications/Notification.cs | 29 ++++--------------- .../Notifications/NotificationSection.cs | 7 +---- .../Notifications/ProgressNotification.cs | 4 +-- .../Notifications/SimpleErrorNotification.cs | 2 +- 5 files changed, 34 insertions(+), 33 deletions(-) diff --git a/osu.Game/Overlays/NotificationOverlay.cs b/osu.Game/Overlays/NotificationOverlay.cs index f5b0bf2a7d..fcb8692010 100644 --- a/osu.Game/Overlays/NotificationOverlay.cs +++ b/osu.Game/Overlays/NotificationOverlay.cs @@ -9,6 +9,7 @@ using osu.Game.Overlays.Notifications; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics.Containers; using osu.Framework.Allocation; +using osu.Framework.Audio; using osu.Framework.Bindables; using osu.Framework.Localisation; using osu.Framework.Threading; @@ -29,6 +30,9 @@ namespace osu.Game.Overlays private FlowContainer sections; + [Resolved] + private AudioManager audio { get; set; } + [BackgroundDependencyLoader] private void load() { @@ -104,6 +108,8 @@ namespace osu.Game.Overlays private bool processingPosts = true; + private double? lastSamplePlayback; + /// /// Post a new notification for display. /// @@ -126,6 +132,7 @@ namespace osu.Game.Overlays Show(); updateCounts(); + playDebouncedSample(notification.PopInSampleName); }); protected override void Update() @@ -154,7 +161,23 @@ namespace osu.Game.Overlays this.FadeTo(0, TRANSITION_LENGTH, Easing.OutQuint); } - private void notificationClosed() => updateCounts(); + private void notificationClosed() + { + updateCounts(); + + // this debounce is currently shared between popin/popout sounds, which means one could potentially not play when the user is expecting it. + // popout is constant across all notification types, and should therefore be handled using playback concurrency instead, but seems broken at the moment. + playDebouncedSample("UI/overlay-pop-out"); + } + + private void playDebouncedSample(string sampleName) + { + if (lastSamplePlayback == null || Time.Current - lastSamplePlayback > 50) + { + audio.Samples.Get(sampleName)?.Play(); + lastSamplePlayback = Time.Current; + } + } private void updateCounts() { diff --git a/osu.Game/Overlays/Notifications/Notification.cs b/osu.Game/Overlays/Notifications/Notification.cs index d1a97c74b2..44203e8ee7 100644 --- a/osu.Game/Overlays/Notifications/Notification.cs +++ b/osu.Game/Overlays/Notifications/Notification.cs @@ -3,20 +3,18 @@ using System; using osu.Framework.Allocation; -using osu.Framework.Audio; -using osu.Framework.Audio.Sample; 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.Game.Graphics; -using osuTK; -using osuTK.Graphics; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; +using osu.Game.Graphics; using osu.Game.Graphics.Containers; +using osuTK; +using osuTK.Graphics; namespace osu.Game.Overlays.Notifications { @@ -42,10 +40,7 @@ namespace osu.Game.Overlays.Notifications /// public virtual bool DisplayOnTop => true; - private Sample samplePopIn; - private Sample samplePopOut; - protected virtual string PopInSampleName => "UI/notification-pop-in"; - protected virtual string PopOutSampleName => "UI/overlay-pop-out"; // TODO: replace with a unique sample? + public virtual string PopInSampleName => "UI/notification-pop-in"; protected NotificationLight Light; private readonly CloseButton closeButton; @@ -114,7 +109,7 @@ namespace osu.Game.Overlays.Notifications closeButton = new CloseButton { Alpha = 0, - Action = () => Close(), + Action = Close, Anchor = Anchor.CentreRight, Origin = Anchor.CentreRight, Margin = new MarginPadding @@ -127,13 +122,6 @@ namespace osu.Game.Overlays.Notifications }); } - [BackgroundDependencyLoader] - private void load(AudioManager audio) - { - samplePopIn = audio.Samples.Get(PopInSampleName); - samplePopOut = audio.Samples.Get(PopOutSampleName); - } - protected override bool OnHover(HoverEvent e) { closeButton.FadeIn(75); @@ -158,8 +146,6 @@ namespace osu.Game.Overlays.Notifications { base.LoadComplete(); - samplePopIn?.Play(); - this.FadeInFromZero(200); NotificationContent.MoveToX(DrawSize.X); NotificationContent.MoveToX(0, 500, Easing.OutQuint); @@ -167,15 +153,12 @@ namespace osu.Game.Overlays.Notifications public bool WasClosed; - public virtual void Close(bool playSound = true) + public virtual void Close() { if (WasClosed) return; WasClosed = true; - if (playSound) - samplePopOut?.Play(); - Closed?.Invoke(); this.FadeOut(100); Expire(); diff --git a/osu.Game/Overlays/Notifications/NotificationSection.cs b/osu.Game/Overlays/Notifications/NotificationSection.cs index 2316199049..a23ff07a64 100644 --- a/osu.Game/Overlays/Notifications/NotificationSection.cs +++ b/osu.Game/Overlays/Notifications/NotificationSection.cs @@ -110,12 +110,7 @@ namespace osu.Game.Overlays.Notifications private void clearAll() { - bool first = true; - notifications.Children.ForEach(c => - { - c.Close(first); - first = false; - }); + notifications.Children.ForEach(c => c.Close()); } protected override void Update() diff --git a/osu.Game/Overlays/Notifications/ProgressNotification.cs b/osu.Game/Overlays/Notifications/ProgressNotification.cs index 703c14af2b..3105ecd742 100644 --- a/osu.Game/Overlays/Notifications/ProgressNotification.cs +++ b/osu.Game/Overlays/Notifications/ProgressNotification.cs @@ -150,12 +150,12 @@ namespace osu.Game.Overlays.Notifications colourCancelled = colours.Red; } - public override void Close(bool playSound = true) + public override void Close() { switch (State) { case ProgressNotificationState.Cancelled: - base.Close(playSound); + base.Close(); break; case ProgressNotificationState.Active: diff --git a/osu.Game/Overlays/Notifications/SimpleErrorNotification.cs b/osu.Game/Overlays/Notifications/SimpleErrorNotification.cs index 13c9c5a02d..faab4ed472 100644 --- a/osu.Game/Overlays/Notifications/SimpleErrorNotification.cs +++ b/osu.Game/Overlays/Notifications/SimpleErrorNotification.cs @@ -7,7 +7,7 @@ namespace osu.Game.Overlays.Notifications { public class SimpleErrorNotification : SimpleNotification { - protected override string PopInSampleName => "UI/error-notification-pop-in"; + public override string PopInSampleName => "UI/error-notification-pop-in"; public SimpleErrorNotification() { From ab1c64591f2a9b224cec05919606836edccd5c21 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 5 Sep 2021 13:25:10 +0900 Subject: [PATCH 139/160] Move sample playback debounce time to central `const` --- .../Graphics/UserInterface/HoverSampleDebounceComponent.cs | 7 +------ osu.Game/OsuGameBase.cs | 5 +++++ osu.Game/Overlays/NotificationOverlay.cs | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/HoverSampleDebounceComponent.cs b/osu.Game/Graphics/UserInterface/HoverSampleDebounceComponent.cs index 55f43cfe46..1fd03a34e7 100644 --- a/osu.Game/Graphics/UserInterface/HoverSampleDebounceComponent.cs +++ b/osu.Game/Graphics/UserInterface/HoverSampleDebounceComponent.cs @@ -15,11 +15,6 @@ namespace osu.Game.Graphics.UserInterface /// public abstract class HoverSampleDebounceComponent : CompositeDrawable { - /// - /// Length of debounce for hover sound playback, in milliseconds. - /// - public double HoverDebounceTime { get; } = 20; - private Bindable lastPlaybackTime; [BackgroundDependencyLoader] @@ -34,7 +29,7 @@ namespace osu.Game.Graphics.UserInterface if (e.HasAnyButtonPressed) return false; - bool enoughTimePassedSinceLastPlayback = !lastPlaybackTime.Value.HasValue || Time.Current - lastPlaybackTime.Value >= HoverDebounceTime; + bool enoughTimePassedSinceLastPlayback = !lastPlaybackTime.Value.HasValue || Time.Current - lastPlaybackTime.Value >= OsuGameBase.SAMPLE_DEBOUNCE_TIME; if (enoughTimePassedSinceLastPlayback) { diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 69f6bc1b7b..762216e93c 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -56,6 +56,11 @@ namespace osu.Game public const int SAMPLE_CONCURRENCY = 6; + /// + /// Length of debounce (in milliseconds) for commonly occuring sample playbacks that could stack. + /// + public const int SAMPLE_DEBOUNCE_TIME = 20; + /// /// The maximum volume at which audio tracks should playback. This can be set lower than 1 to create some head-room for sound effects. /// diff --git a/osu.Game/Overlays/NotificationOverlay.cs b/osu.Game/Overlays/NotificationOverlay.cs index fcb8692010..8809dec642 100644 --- a/osu.Game/Overlays/NotificationOverlay.cs +++ b/osu.Game/Overlays/NotificationOverlay.cs @@ -172,7 +172,7 @@ namespace osu.Game.Overlays private void playDebouncedSample(string sampleName) { - if (lastSamplePlayback == null || Time.Current - lastSamplePlayback > 50) + if (lastSamplePlayback == null || Time.Current - lastSamplePlayback > OsuGameBase.SAMPLE_DEBOUNCE_TIME) { audio.Samples.Get(sampleName)?.Play(); lastSamplePlayback = Time.Current; From 25420af078151b68146f8786911562869b39e7fd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 5 Sep 2021 13:34:23 +0900 Subject: [PATCH 140/160] Rename method to drop redundant ruleset suffix --- osu.Game.Rulesets.Mania/ManiaRuleset.cs | 2 +- osu.Game.Rulesets.Osu/OsuRuleset.cs | 2 +- osu.Game/Rulesets/Ruleset.cs | 2 +- osu.Game/Screens/Edit/Setup/SetupScreen.cs | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index 5ba1e2e6c3..1f79dae280 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -393,7 +393,7 @@ namespace osu.Game.Rulesets.Mania return new ManiaFilterCriteria(); } - public override RulesetSetupSection CreateEditorSetupSectionForRuleset() => new ManiaSetupSection(); + public override RulesetSetupSection CreateEditorSetupSection() => new ManiaSetupSection(); } public enum PlayfieldType diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index 97af7d28af..f4a93a571d 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -308,6 +308,6 @@ namespace osu.Game.Rulesets.Osu }; } - public override RulesetSetupSection CreateEditorSetupSectionForRuleset() => new OsuSetupSection(); + public override RulesetSetupSection CreateEditorSetupSection() => new OsuSetupSection(); } } diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index 0a537f2442..de62cf8d33 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -321,6 +321,6 @@ namespace osu.Game.Rulesets /// Can be overridden to add a ruleset-specific section to the editor beatmap setup screen. /// [CanBeNull] - public virtual RulesetSetupSection CreateEditorSetupSectionForRuleset() => null; + public virtual RulesetSetupSection CreateEditorSetupSection() => null; } } diff --git a/osu.Game/Screens/Edit/Setup/SetupScreen.cs b/osu.Game/Screens/Edit/Setup/SetupScreen.cs index 38fcfc5d2f..04767f1786 100644 --- a/osu.Game/Screens/Edit/Setup/SetupScreen.cs +++ b/osu.Game/Screens/Edit/Setup/SetupScreen.cs @@ -33,7 +33,7 @@ namespace osu.Game.Screens.Edit.Setup new DesignSection(), }; - var rulesetSpecificSection = beatmap.BeatmapInfo.Ruleset?.CreateInstance()?.CreateEditorSetupSectionForRuleset(); + var rulesetSpecificSection = beatmap.BeatmapInfo.Ruleset?.CreateInstance()?.CreateEditorSetupSection(); if (rulesetSpecificSection != null) sectionsEnumerable.Add(rulesetSpecificSection); From e0ee2a553375e3ab7460ac3716b280ef577d04eb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 5 Sep 2021 13:34:57 +0900 Subject: [PATCH 141/160] Change section title to read better --- osu.Game/Screens/Edit/Setup/RulesetSetupSection.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Setup/RulesetSetupSection.cs b/osu.Game/Screens/Edit/Setup/RulesetSetupSection.cs index 9344d5b491..935842ff99 100644 --- a/osu.Game/Screens/Edit/Setup/RulesetSetupSection.cs +++ b/osu.Game/Screens/Edit/Setup/RulesetSetupSection.cs @@ -8,7 +8,7 @@ namespace osu.Game.Screens.Edit.Setup { public abstract class RulesetSetupSection : SetupSection { - public sealed override LocalisableString Title => $"{rulesetInfo.Name}-specific"; + public sealed override LocalisableString Title => $"Ruleset ({rulesetInfo.Name})"; private readonly RulesetInfo rulesetInfo; From 1a26658ba4d3ab18ce3661bb4f1ec4b8405d825d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 5 Sep 2021 13:40:49 +0900 Subject: [PATCH 142/160] Add description for mania special style --- osu.Game.Rulesets.Mania/Edit/Setup/ManiaSetupSection.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Rulesets.Mania/Edit/Setup/ManiaSetupSection.cs b/osu.Game.Rulesets.Mania/Edit/Setup/ManiaSetupSection.cs index d0b32a7268..a206aafb8a 100644 --- a/osu.Game.Rulesets.Mania/Edit/Setup/ManiaSetupSection.cs +++ b/osu.Game.Rulesets.Mania/Edit/Setup/ManiaSetupSection.cs @@ -25,6 +25,7 @@ namespace osu.Game.Rulesets.Mania.Edit.Setup specialStyle = new LabelledSwitchButton { Label = "Use special (N+1) style", + Description = "Changes one column to act as a classic \"scratch\" or \"special\" column, which can be moved around by the user's skin (to the left/right/centre). Generally used in 5k (4+1) or 8key (7+1) configurations.", Current = { Value = Beatmap.BeatmapInfo.SpecialStyle } } }; From 6e4efdd1b11dde4ad43bcee7c1745a7374bf482e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 5 Sep 2021 13:40:58 +0900 Subject: [PATCH 143/160] Add test coverage for per-ruleset setup screens --- .../Visual/Editing/TestSceneSetupScreen.cs | 35 +++++++++++++++---- 1 file changed, 28 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneSetupScreen.cs b/osu.Game.Tests/Visual/Editing/TestSceneSetupScreen.cs index 9253023c9a..c3c803ff23 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneSetupScreen.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneSetupScreen.cs @@ -4,8 +4,13 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics.Containers; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Catch; using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Mania; +using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Beatmaps; +using osu.Game.Rulesets.Taiko; using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Setup; @@ -23,15 +28,31 @@ namespace osu.Game.Tests.Visual.Editing editorBeatmap = new EditorBeatmap(new OsuBeatmap()); } - [BackgroundDependencyLoader] - private void load() - { - Beatmap.Value = CreateWorkingBeatmap(editorBeatmap.PlayableBeatmap); + [Test] + public void TestOsu() => runForRuleset(new OsuRuleset().RulesetInfo); - Child = new SetupScreen + [Test] + public void TestTaiko() => runForRuleset(new TaikoRuleset().RulesetInfo); + + [Test] + public void TestCatch() => runForRuleset(new CatchRuleset().RulesetInfo); + + [Test] + public void TestMania() => runForRuleset(new ManiaRuleset().RulesetInfo); + + private void runForRuleset(RulesetInfo rulesetInfo) + { + AddStep("create screen", () => { - State = { Value = Visibility.Visible }, - }; + editorBeatmap.BeatmapInfo.Ruleset = rulesetInfo; + + Beatmap.Value = CreateWorkingBeatmap(editorBeatmap.PlayableBeatmap); + + Child = new SetupScreen + { + State = { Value = Visibility.Visible }, + }; + }); } } } From 9aa1564e0de3482c29e9b07927090df4bf0ebd75 Mon Sep 17 00:00:00 2001 From: Davran Dilshat Date: Sun, 5 Sep 2021 10:19:04 +0100 Subject: [PATCH 144/160] revert ChannelManager changes --- osu.Game/Online/Chat/ChannelManager.cs | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/osu.Game/Online/Chat/ChannelManager.cs b/osu.Game/Online/Chat/ChannelManager.cs index f58f1ff40c..bf411d59f6 100644 --- a/osu.Game/Online/Chat/ChannelManager.cs +++ b/osu.Game/Online/Chat/ChannelManager.cs @@ -256,21 +256,8 @@ namespace osu.Game.Online.Chat JoinChannel(channel); break; - case "chat": - if (string.IsNullOrWhiteSpace(content)) - { - target.AddNewMessages(new ErrorMessage("Usage: /chat [user]")); - break; - } - - var request = new GetUserRequest(content); - request.Success += OpenPrivateChannel; - request.Failure += _ => target.AddNewMessages(new ErrorMessage("User not found.")); - api.Queue(request); - break; - case "help": - target.AddNewMessages(new InfoMessage("Supported commands: /help, /me [action], /join [channel], /chat [user], /np")); + target.AddNewMessages(new InfoMessage("Supported commands: /help, /me [action], /join [channel], /np")); break; default: @@ -614,4 +601,4 @@ namespace osu.Game.Online.Chat : channel.Id == Id; } } -} +} \ No newline at end of file From e409f2dc6d28257b822ffa06230377c80402dae0 Mon Sep 17 00:00:00 2001 From: Davran Dilshat Date: Sun, 5 Sep 2021 10:42:38 +0100 Subject: [PATCH 145/160] add xmldoc --- osu.Game/Online/API/Requests/GetUserRequest.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/osu.Game/Online/API/Requests/GetUserRequest.cs b/osu.Game/Online/API/Requests/GetUserRequest.cs index 0c8a4a3578..9470c77b79 100644 --- a/osu.Game/Online/API/Requests/GetUserRequest.cs +++ b/osu.Game/Online/API/Requests/GetUserRequest.cs @@ -11,16 +11,30 @@ namespace osu.Game.Online.API.Requests private readonly string userIdentifier; public readonly RulesetInfo Ruleset; + + /// + /// Gets the currently logged-in user. + /// public GetUserRequest() { } + /// + /// Gets a user from their ID. + /// + /// The user to get. + /// The ruleset to get the user's info for. public GetUserRequest(long? userId = null, RulesetInfo ruleset = null) { this.userIdentifier = userId.ToString(); Ruleset = ruleset; } + /// + /// Gets a user from their username. + /// + /// The user to get. + /// The ruleset to get the user's info for. public GetUserRequest(string username = null, RulesetInfo ruleset = null) { this.userIdentifier = username; From e5f886a3158e25181b5c047b9e596f88a72332fb Mon Sep 17 00:00:00 2001 From: Davran Dilshat Date: Sun, 5 Sep 2021 10:45:38 +0100 Subject: [PATCH 146/160] revert unnecessary change --- 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 1e6e1e0ead..a83357f4f5 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -329,7 +329,7 @@ namespace osu.Game break; case LinkAction.OpenUserProfile: - if (int.TryParse(link.Argument, out var userId)) + if (int.TryParse(link.Argument, out int userId)) ShowUser(userId); else ShowUser(link.Argument); From f7369e0d682cc27e0e210e1135b1959abc4e1058 Mon Sep 17 00:00:00 2001 From: Davran Dilshat Date: Sun, 5 Sep 2021 14:47:46 +0100 Subject: [PATCH 147/160] create UserIdLookupCache to get user ID when importing scores --- osu.Game/OsuGameBase.cs | 2 +- osu.Game/Scoring/ScoreManager.cs | 35 ++++---- .../Scoring/ScoreManager_UserIdLookupCache.cs | 85 +++++++++++++++++++ 3 files changed, 102 insertions(+), 20 deletions(-) create mode 100644 osu.Game/Scoring/ScoreManager_UserIdLookupCache.cs diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index f2d575550a..484cc23161 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -263,7 +263,7 @@ namespace osu.Game dependencies.Cache(fileStore = new FileStore(contextFactory, Storage)); // ordering is important here to ensure foreign keys rules are not broken in ModelStore.Cleanup() - dependencies.Cache(ScoreManager = new ScoreManager(RulesetStore, () => BeatmapManager, Storage, API, contextFactory, Host, () => difficultyCache, LocalConfig)); + dependencies.Cache(ScoreManager = new ScoreManager(RulesetStore, () => BeatmapManager, Storage, API, contextFactory, Host, () => difficultyCache, LocalConfig, true)); dependencies.Cache(BeatmapManager = new BeatmapManager(Storage, contextFactory, RulesetStore, API, Audio, Resources, Host, defaultBeatmap, true)); // this should likely be moved to ArchiveModelManager when another case appears where it is necessary diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index 3c99dd6637..fcf214268a 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -10,6 +10,7 @@ using System.Threading; using System.Threading.Tasks; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore; +using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Logging; using osu.Framework.Platform; @@ -27,7 +28,7 @@ using osu.Game.Users; namespace osu.Game.Scoring { - public class ScoreManager : DownloadableArchiveModelManager + public partial class ScoreManager : DownloadableArchiveModelManager { public override IEnumerable HandledExtensions => new[] { ".osr" }; @@ -44,10 +45,13 @@ namespace osu.Game.Scoring [CanBeNull] private readonly OsuConfigManager configManager; + [CanBeNull] + private readonly UserIdLookupCache userIdLookupCache; + private IAPIProvider api { get; set; } public ScoreManager(RulesetStore rulesets, Func beatmaps, Storage storage, IAPIProvider api, IDatabaseContextFactory contextFactory, IIpcHost importHost = null, - Func difficulties = null, OsuConfigManager configManager = null) + Func difficulties = null, OsuConfigManager configManager = null, bool performOnlineLookups = false) : base(storage, contextFactory, api, new ScoreStore(contextFactory, storage), importHost) { this.rulesets = rulesets; @@ -55,6 +59,9 @@ namespace osu.Game.Scoring this.difficulties = difficulties; this.configManager = configManager; this.api = api; + + if (performOnlineLookups) + userIdLookupCache = new UserIdLookupCache(api); } protected override ScoreInfo CreateModel(ArchiveReader archive) @@ -76,30 +83,20 @@ namespace osu.Game.Scoring } } - private readonly Dictionary previouslyLookedUpUsernames = new Dictionary(); - - protected override Task Populate(ScoreInfo model, ArchiveReader archive, CancellationToken cancellationToken = default) + protected override async Task Populate(ScoreInfo model, ArchiveReader archive, CancellationToken cancellationToken = default) { // These scores only provide the user's username but we need the user's ID too. - if (model.UserID <= 1 && model.UserString != null) + if (model.UserID <= 1 && model.UserString != null && userIdLookupCache != null) { - if (previouslyLookedUpUsernames.TryGetValue(model.UserString, out User user)) + try { - model.UserID = user.Id; - return Task.CompletedTask; + model.UserID = await userIdLookupCache.GetUserIdAsync(model.UserString, cancellationToken).ConfigureAwait(false); } - - var request = new GetUserRequest(model.UserString); - request.Success += u => + catch (Exception e) { - model.UserID = u.Id; - previouslyLookedUpUsernames.TryAdd(model.UserString, u); - }; - - api?.Queue(request); + LogForModel(model, $"Online retrieval failed for {model.User} ({e.Message})", e); + } } - - return Task.CompletedTask; } protected override void ExportModelTo(ScoreInfo model, Stream outputStream) diff --git a/osu.Game/Scoring/ScoreManager_UserIdLookupCache.cs b/osu.Game/Scoring/ScoreManager_UserIdLookupCache.cs new file mode 100644 index 0000000000..dc7e244f14 --- /dev/null +++ b/osu.Game/Scoring/ScoreManager_UserIdLookupCache.cs @@ -0,0 +1,85 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests; +using osu.Game.Database; + +namespace osu.Game.Scoring +{ + public partial class ScoreManager + { + private class UserIdLookupCache : MemoryCachingComponent + { + private readonly IAPIProvider api; + + public UserIdLookupCache(IAPIProvider api) + { + this.api = api; + } + + /// + /// Perform an API lookup on the specified username, returning the associated ID. + /// + /// The username to lookup. + /// An optional cancellation token. + /// The user ID, or 1 if the user does not exist or the request could not be satisfied. + public Task GetUserIdAsync(string username, CancellationToken token = default) => GetAsync(username, token); + + protected override async Task ComputeValueAsync(string lookup, CancellationToken token = default) + => await queryUserId(lookup).ConfigureAwait(false); + + private readonly Queue<(string username, TaskCompletionSource)> pendingUserTasks = new Queue<(string, TaskCompletionSource)>(); + private Task pendingRequestTask; + private readonly object taskAssignmentLock = new object(); + + private Task queryUserId(string username) + { + lock (taskAssignmentLock) + { + var tcs = new TaskCompletionSource(); + + // Add to the queue. + pendingUserTasks.Enqueue((username, tcs)); + + // Create a request task if there's not already one. + if (pendingRequestTask == null) + createNewTask(); + + return tcs.Task; + } + } + + private void performLookup() + { + (string username, TaskCompletionSource task) next; + + lock (taskAssignmentLock) + { + next = pendingUserTasks.Dequeue(); + } + + var request = new GetUserRequest(next.username); + + // rather than queueing, we maintain our own single-threaded request stream. + // todo: we probably want retry logic here. + api.Perform(request); + + // Create a new request task if there's still more users to query. + lock (taskAssignmentLock) + { + pendingRequestTask = null; + if (pendingUserTasks.Count > 0) + createNewTask(); + } + + next.task.SetResult(request.Result?.Id ?? 1); + } + + private void createNewTask() => pendingRequestTask = Task.Run(performLookup); + } + } +} From 7f9b80e3e5da3c9e362fe028f90d5bfdb5a9e2dc Mon Sep 17 00:00:00 2001 From: Davran Dilshat Date: Sun, 5 Sep 2021 15:11:41 +0100 Subject: [PATCH 148/160] add tests for ShowUser() username overload --- osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs index 03d079261d..70271b0b08 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs @@ -104,6 +104,9 @@ namespace osu.Game.Tests.Visual.Online CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c4.jpg" }, api.IsLoggedIn)); + AddStep("Show ppy from username", () => profile.ShowUser(@"peppy")); + AddStep("Show flyte from username", () => profile.ShowUser(@"flyte")); + AddStep("Hide", profile.Hide); AddStep("Show without reload", profile.Show); } From e78dc1bb4ce6c8d06792bb0c3e2b7042eb33d44f Mon Sep 17 00:00:00 2001 From: Davran Dilshat Date: Sun, 5 Sep 2021 15:27:28 +0100 Subject: [PATCH 149/160] more code quality :/ --- osu.Game/Online/API/Requests/GetUserRequest.cs | 1 - osu.Game/Scoring/ScoreManager.cs | 4 +--- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/osu.Game/Online/API/Requests/GetUserRequest.cs b/osu.Game/Online/API/Requests/GetUserRequest.cs index 9470c77b79..281926c096 100644 --- a/osu.Game/Online/API/Requests/GetUserRequest.cs +++ b/osu.Game/Online/API/Requests/GetUserRequest.cs @@ -11,7 +11,6 @@ namespace osu.Game.Online.API.Requests private readonly string userIdentifier; public readonly RulesetInfo Ruleset; - /// /// Gets the currently logged-in user. /// diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index fcf214268a..ccc5579ee5 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -10,7 +10,6 @@ using System.Threading; using System.Threading.Tasks; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore; -using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Logging; using osu.Framework.Platform; @@ -24,7 +23,6 @@ using osu.Game.Rulesets; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring.Legacy; -using osu.Game.Users; namespace osu.Game.Scoring { @@ -48,7 +46,7 @@ namespace osu.Game.Scoring [CanBeNull] private readonly UserIdLookupCache userIdLookupCache; - private IAPIProvider api { get; set; } + private readonly IAPIProvider api; public ScoreManager(RulesetStore rulesets, Func beatmaps, Storage storage, IAPIProvider api, IDatabaseContextFactory contextFactory, IIpcHost importHost = null, Func difficulties = null, OsuConfigManager configManager = null, bool performOnlineLookups = false) From b1a995e0bb9d64a663c098518f24790ca0e46a47 Mon Sep 17 00:00:00 2001 From: Davran Dilshat Date: Sun, 5 Sep 2021 15:49:48 +0100 Subject: [PATCH 150/160] revert changes --- osu.Game/Online/Chat/ChannelManager.cs | 2 +- osu.Game/OsuGameBase.cs | 2 +- osu.Game/Scoring/ScoreManager.cs | 30 +------ .../Scoring/ScoreManager_UserIdLookupCache.cs | 85 ------------------- 4 files changed, 6 insertions(+), 113 deletions(-) delete mode 100644 osu.Game/Scoring/ScoreManager_UserIdLookupCache.cs diff --git a/osu.Game/Online/Chat/ChannelManager.cs b/osu.Game/Online/Chat/ChannelManager.cs index bf411d59f6..1937019ef6 100644 --- a/osu.Game/Online/Chat/ChannelManager.cs +++ b/osu.Game/Online/Chat/ChannelManager.cs @@ -601,4 +601,4 @@ namespace osu.Game.Online.Chat : channel.Id == Id; } } -} \ No newline at end of file +} diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 484cc23161..f2d575550a 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -263,7 +263,7 @@ namespace osu.Game dependencies.Cache(fileStore = new FileStore(contextFactory, Storage)); // ordering is important here to ensure foreign keys rules are not broken in ModelStore.Cleanup() - dependencies.Cache(ScoreManager = new ScoreManager(RulesetStore, () => BeatmapManager, Storage, API, contextFactory, Host, () => difficultyCache, LocalConfig, true)); + dependencies.Cache(ScoreManager = new ScoreManager(RulesetStore, () => BeatmapManager, Storage, API, contextFactory, Host, () => difficultyCache, LocalConfig)); dependencies.Cache(BeatmapManager = new BeatmapManager(Storage, contextFactory, RulesetStore, API, Audio, Resources, Host, defaultBeatmap, true)); // this should likely be moved to ArchiveModelManager when another case appears where it is necessary diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index ccc5579ee5..83bcac01ac 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -26,7 +26,7 @@ using osu.Game.Scoring.Legacy; namespace osu.Game.Scoring { - public partial class ScoreManager : DownloadableArchiveModelManager + public class ScoreManager : DownloadableArchiveModelManager { public override IEnumerable HandledExtensions => new[] { ".osr" }; @@ -43,23 +43,14 @@ namespace osu.Game.Scoring [CanBeNull] private readonly OsuConfigManager configManager; - [CanBeNull] - private readonly UserIdLookupCache userIdLookupCache; - - private readonly IAPIProvider api; - public ScoreManager(RulesetStore rulesets, Func beatmaps, Storage storage, IAPIProvider api, IDatabaseContextFactory contextFactory, IIpcHost importHost = null, - Func difficulties = null, OsuConfigManager configManager = null, bool performOnlineLookups = false) + Func difficulties = null, OsuConfigManager configManager = null) : base(storage, contextFactory, api, new ScoreStore(contextFactory, storage), importHost) { this.rulesets = rulesets; this.beatmaps = beatmaps; this.difficulties = difficulties; this.configManager = configManager; - this.api = api; - - if (performOnlineLookups) - userIdLookupCache = new UserIdLookupCache(api); } protected override ScoreInfo CreateModel(ArchiveReader archive) @@ -81,21 +72,8 @@ namespace osu.Game.Scoring } } - protected override async Task Populate(ScoreInfo model, ArchiveReader archive, CancellationToken cancellationToken = default) - { - // These scores only provide the user's username but we need the user's ID too. - if (model.UserID <= 1 && model.UserString != null && userIdLookupCache != null) - { - try - { - model.UserID = await userIdLookupCache.GetUserIdAsync(model.UserString, cancellationToken).ConfigureAwait(false); - } - catch (Exception e) - { - LogForModel(model, $"Online retrieval failed for {model.User} ({e.Message})", e); - } - } - } + protected override Task Populate(ScoreInfo model, ArchiveReader archive, CancellationToken cancellationToken = default) + => Task.CompletedTask; protected override void ExportModelTo(ScoreInfo model, Stream outputStream) { diff --git a/osu.Game/Scoring/ScoreManager_UserIdLookupCache.cs b/osu.Game/Scoring/ScoreManager_UserIdLookupCache.cs deleted file mode 100644 index dc7e244f14..0000000000 --- a/osu.Game/Scoring/ScoreManager_UserIdLookupCache.cs +++ /dev/null @@ -1,85 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; -using osu.Game.Online.API; -using osu.Game.Online.API.Requests; -using osu.Game.Database; - -namespace osu.Game.Scoring -{ - public partial class ScoreManager - { - private class UserIdLookupCache : MemoryCachingComponent - { - private readonly IAPIProvider api; - - public UserIdLookupCache(IAPIProvider api) - { - this.api = api; - } - - /// - /// Perform an API lookup on the specified username, returning the associated ID. - /// - /// The username to lookup. - /// An optional cancellation token. - /// The user ID, or 1 if the user does not exist or the request could not be satisfied. - public Task GetUserIdAsync(string username, CancellationToken token = default) => GetAsync(username, token); - - protected override async Task ComputeValueAsync(string lookup, CancellationToken token = default) - => await queryUserId(lookup).ConfigureAwait(false); - - private readonly Queue<(string username, TaskCompletionSource)> pendingUserTasks = new Queue<(string, TaskCompletionSource)>(); - private Task pendingRequestTask; - private readonly object taskAssignmentLock = new object(); - - private Task queryUserId(string username) - { - lock (taskAssignmentLock) - { - var tcs = new TaskCompletionSource(); - - // Add to the queue. - pendingUserTasks.Enqueue((username, tcs)); - - // Create a request task if there's not already one. - if (pendingRequestTask == null) - createNewTask(); - - return tcs.Task; - } - } - - private void performLookup() - { - (string username, TaskCompletionSource task) next; - - lock (taskAssignmentLock) - { - next = pendingUserTasks.Dequeue(); - } - - var request = new GetUserRequest(next.username); - - // rather than queueing, we maintain our own single-threaded request stream. - // todo: we probably want retry logic here. - api.Perform(request); - - // Create a new request task if there's still more users to query. - lock (taskAssignmentLock) - { - pendingRequestTask = null; - if (pendingUserTasks.Count > 0) - createNewTask(); - } - - next.task.SetResult(request.Result?.Id ?? 1); - } - - private void createNewTask() => pendingRequestTask = Task.Run(performLookup); - } - } -} From 458cde832da5a75b742fb808637e559de62fad3a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 6 Sep 2021 14:09:40 +0900 Subject: [PATCH 151/160] Avoid using SSDQ for validity computation --- .../Settings/TestSceneTabletSettings.cs | 14 +----- .../Sections/Input/TabletAreaSelection.cs | 44 ++++++++++++------- 2 files changed, 30 insertions(+), 28 deletions(-) diff --git a/osu.Game.Tests/Visual/Settings/TestSceneTabletSettings.cs b/osu.Game.Tests/Visual/Settings/TestSceneTabletSettings.cs index 49d6b7033e..2486abdd7a 100644 --- a/osu.Game.Tests/Visual/Settings/TestSceneTabletSettings.cs +++ b/osu.Game.Tests/Visual/Settings/TestSceneTabletSettings.cs @@ -1,11 +1,9 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System.Linq; using NUnit.Framework; using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; using osu.Framework.Input.Handlers.Tablet; using osu.Framework.Testing; using osu.Framework.Utils; @@ -93,17 +91,9 @@ namespace osu.Game.Tests.Visual.Settings ensureValid(); } - private void ensureValid() - { - AddUntilStep("wait for transforms", () => settings.AreaSelection.ChildrenOfType().All(c => !c.Transforms.Any())); - AddAssert("area valid", () => settings.AreaSelection.IsWithinBounds); - } + private void ensureValid() => AddAssert("area valid", () => settings.AreaSelection.IsWithinBounds); - private void ensureInvalid() - { - AddUntilStep("wait for transforms", () => settings.AreaSelection.ChildrenOfType().All(c => !c.Transforms.Any())); - AddAssert("area invalid", () => !settings.AreaSelection.IsWithinBounds); - } + private void ensureInvalid() => AddAssert("area invalid", () => !settings.AreaSelection.IsWithinBounds); public class TestTabletHandler : ITabletHandler { diff --git a/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs b/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs index d12052b24d..58abfab29c 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs @@ -114,29 +114,30 @@ namespace osu.Game.Overlays.Settings.Sections.Input areaOffset.BindTo(handler.AreaOffset); areaOffset.BindValueChanged(val => { - usableAreaContainer.MoveTo(val.NewValue, 100, Easing.OutQuint) - .OnComplete(_ => checkBounds()); // required as we are using SSDQ. + usableAreaContainer.MoveTo(val.NewValue, 100, Easing.OutQuint); + checkBounds(); }, true); areaSize.BindTo(handler.AreaSize); areaSize.BindValueChanged(val => { - usableAreaContainer.ResizeTo(val.NewValue, 100, Easing.OutQuint) - .OnComplete(_ => checkBounds()); // required as we are using SSDQ. + usableAreaContainer.ResizeTo(val.NewValue, 100, Easing.OutQuint); int x = (int)val.NewValue.X; int y = (int)val.NewValue.Y; int commonDivider = greatestCommonDivider(x, y); usableAreaText.Text = $"{(float)x / commonDivider}:{(float)y / commonDivider}"; + checkBounds(); }, true); rotation.BindTo(handler.Rotation); rotation.BindValueChanged(val => { - usableAreaContainer.RotateTo(val.NewValue, 100, Easing.OutQuint) - .OnComplete(_ => checkBounds()); // required as we are using SSDQ. + usableAreaContainer.RotateTo(val.NewValue, 100, Easing.OutQuint); tabletContainer.RotateTo(-val.NewValue, 800, Easing.OutQuint); + + checkBounds(); }, true); tablet.BindTo(handler.Tablet); @@ -174,22 +175,33 @@ namespace osu.Game.Overlays.Settings.Sections.Input if (tablet.Value == null) return; - // All of this manual logic is just to get around floating point issues when doing a contains check on the screen quads. - // This is best effort, as it's only used for display purposes. If we need for anything more, manual math on the raw values should be preferred. - var containerQuad = tabletContainer.ScreenSpaceDrawQuad.AABBFloat.Inflate(1); - var usableAreaQuad = Quad.FromRectangle(usableAreaContainer.ScreenSpaceDrawQuad.AABBFloat); + // allow for some degree of floating point error, as we don't care about being perfect here. + const float lenience = 0.5f; + + var tabletArea = new Quad(-lenience, -lenience, tablet.Value.Size.X + lenience * 2, tablet.Value.Size.Y + lenience * 2); + + var halfUsableArea = areaSize.Value / 2; + var offset = areaOffset.Value; + + var usableAreaQuad = new Quad( + new Vector2(-halfUsableArea.X, -halfUsableArea.Y), + new Vector2(halfUsableArea.X, -halfUsableArea.Y), + new Vector2(-halfUsableArea.X, halfUsableArea.Y), + new Vector2(halfUsableArea.X, halfUsableArea.Y) + ); var matrix = Matrix3.Identity; - MatrixExtensions.TranslateFromLeft(ref matrix, usableAreaQuad.Centre); + + MatrixExtensions.TranslateFromLeft(ref matrix, offset); MatrixExtensions.RotateFromLeft(ref matrix, MathUtils.DegreesToRadians(rotation.Value)); - MatrixExtensions.TranslateFromLeft(ref matrix, -usableAreaQuad.Centre); + usableAreaQuad *= matrix; IsWithinBounds = - containerQuad.Contains(usableAreaQuad.TopLeft) && - containerQuad.Contains(usableAreaQuad.TopRight) && - containerQuad.Contains(usableAreaQuad.BottomLeft) && - containerQuad.Contains(usableAreaQuad.BottomRight); + tabletArea.Contains(usableAreaQuad.TopLeft) && + tabletArea.Contains(usableAreaQuad.TopRight) && + tabletArea.Contains(usableAreaQuad.BottomLeft) && + tabletArea.Contains(usableAreaQuad.BottomRight); usableFill.FadeColour(IsWithinBounds ? colour.Blue : colour.RedLight, 100); } From 6f482c3602b3fd2c6801878e0fcb368ba5dd1633 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 6 Sep 2021 14:14:42 +0900 Subject: [PATCH 152/160] Add test coverage of sharper aspect ratio --- .../Settings/TestSceneTabletSettings.cs | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/osu.Game.Tests/Visual/Settings/TestSceneTabletSettings.cs b/osu.Game.Tests/Visual/Settings/TestSceneTabletSettings.cs index 2486abdd7a..997eac709d 100644 --- a/osu.Game.Tests/Visual/Settings/TestSceneTabletSettings.cs +++ b/osu.Game.Tests/Visual/Settings/TestSceneTabletSettings.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Linq; using NUnit.Framework; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -8,6 +9,7 @@ using osu.Framework.Input.Handlers.Tablet; using osu.Framework.Testing; using osu.Framework.Utils; using osu.Game.Overlays; +using osu.Game.Overlays.Settings; using osu.Game.Overlays.Settings.Sections.Input; using osuTK; @@ -51,6 +53,27 @@ namespace osu.Game.Tests.Visual.Settings AddStep("Test no tablet present", () => tabletHandler.SetTabletSize(Vector2.Zero)); } + [Test] + public void TestWideAspectRatioValidity() + { + AddStep("Test with wide tablet", () => tabletHandler.SetTabletSize(new Vector2(160, 100))); + + AddStep("Reset to full area", () => settings.ChildrenOfType().First().TriggerClick()); + ensureValid(); + + AddStep("rotate 10", () => tabletHandler.Rotation.Value = 10); + ensureInvalid(); + + AddStep("scale down", () => tabletHandler.AreaSize.Value *= 0.9f); + ensureInvalid(); + + AddStep("scale down", () => tabletHandler.AreaSize.Value *= 0.9f); + ensureInvalid(); + + AddStep("scale down", () => tabletHandler.AreaSize.Value *= 0.9f); + ensureValid(); + } + [Test] public void TestRotationValidity() { From 1c4a3c584a76fb9ecbf7b56d6d62850fcc89b88d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 6 Sep 2021 15:04:27 +0900 Subject: [PATCH 153/160] Use correct lookup type to ensure username based lookups always prefer username --- osu.Game/Online/API/Requests/GetUserRequest.cs | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/osu.Game/Online/API/Requests/GetUserRequest.cs b/osu.Game/Online/API/Requests/GetUserRequest.cs index 281926c096..e49c4ab298 100644 --- a/osu.Game/Online/API/Requests/GetUserRequest.cs +++ b/osu.Game/Online/API/Requests/GetUserRequest.cs @@ -8,8 +8,9 @@ namespace osu.Game.Online.API.Requests { public class GetUserRequest : APIRequest { - private readonly string userIdentifier; + private readonly string lookup; public readonly RulesetInfo Ruleset; + private readonly LookupType lookupType; /// /// Gets the currently logged-in user. @@ -25,7 +26,8 @@ namespace osu.Game.Online.API.Requests /// The ruleset to get the user's info for. public GetUserRequest(long? userId = null, RulesetInfo ruleset = null) { - this.userIdentifier = userId.ToString(); + lookup = userId.ToString(); + lookupType = LookupType.Id; Ruleset = ruleset; } @@ -36,10 +38,17 @@ namespace osu.Game.Online.API.Requests /// The ruleset to get the user's info for. public GetUserRequest(string username = null, RulesetInfo ruleset = null) { - this.userIdentifier = username; + lookup = username; + lookupType = LookupType.Username; Ruleset = ruleset; } - protected override string Target => userIdentifier != null ? $@"users/{userIdentifier}/{Ruleset?.ShortName}" : $@"me/{Ruleset?.ShortName}"; + protected override string Target => lookup != null ? $@"users/{lookup}/{Ruleset?.ShortName}?k={lookupType.ToString().ToLower()}" : $@"me/{Ruleset?.ShortName}"; + + private enum LookupType + { + Id, + Username + } } } From 62d65f81fb785ae4656b312f6617f469401349d6 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 6 Sep 2021 16:53:09 +0900 Subject: [PATCH 154/160] Limit at 10000 entries --- .github/workflows/test-diffcalc.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test-diffcalc.yml b/.github/workflows/test-diffcalc.yml index 3bec11928f..d963e49d9f 100644 --- a/.github/workflows/test-diffcalc.yml +++ b/.github/workflows/test-diffcalc.yml @@ -156,6 +156,7 @@ jobs: AND m.mods = p.mods WHERE abs(m.diff_unified - p.diff_unified) > 0.1 ORDER BY abs(m.diff_unified - p.diff_unified) - DESC;" + DESC + LIMIT 10000;" # Todo: Run ppcalc \ No newline at end of file From 2a5b857f10f51f0d5e2cd3666be3e08db9ed8338 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 7 Sep 2021 00:45:53 +0900 Subject: [PATCH 155/160] Avoid loading unnecessary fonts in headless testing --- osu.Game/OsuGameBase.cs | 55 +++++++++++++++------------ osu.Game/Tests/Visual/OsuTestScene.cs | 5 +++ 2 files changed, 35 insertions(+), 25 deletions(-) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 762216e93c..a63e59f3d3 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -205,31 +205,7 @@ namespace osu.Game dependencies.CacheAs(this); dependencies.CacheAs(LocalConfig); - AddFont(Resources, @"Fonts/osuFont"); - - AddFont(Resources, @"Fonts/Torus/Torus-Regular"); - AddFont(Resources, @"Fonts/Torus/Torus-Light"); - AddFont(Resources, @"Fonts/Torus/Torus-SemiBold"); - AddFont(Resources, @"Fonts/Torus/Torus-Bold"); - - AddFont(Resources, @"Fonts/Inter/Inter-Regular"); - AddFont(Resources, @"Fonts/Inter/Inter-RegularItalic"); - AddFont(Resources, @"Fonts/Inter/Inter-Light"); - AddFont(Resources, @"Fonts/Inter/Inter-LightItalic"); - AddFont(Resources, @"Fonts/Inter/Inter-SemiBold"); - AddFont(Resources, @"Fonts/Inter/Inter-SemiBoldItalic"); - AddFont(Resources, @"Fonts/Inter/Inter-Bold"); - AddFont(Resources, @"Fonts/Inter/Inter-BoldItalic"); - - AddFont(Resources, @"Fonts/Noto/Noto-Basic"); - AddFont(Resources, @"Fonts/Noto/Noto-Hangul"); - AddFont(Resources, @"Fonts/Noto/Noto-CJK-Basic"); - AddFont(Resources, @"Fonts/Noto/Noto-CJK-Compatibility"); - AddFont(Resources, @"Fonts/Noto/Noto-Thai"); - - AddFont(Resources, @"Fonts/Venera/Venera-Light"); - AddFont(Resources, @"Fonts/Venera/Venera-Bold"); - AddFont(Resources, @"Fonts/Venera/Venera-Black"); + InitialiseFonts(); Audio.Samples.PlaybackConcurrency = SAMPLE_CONCURRENCY; @@ -368,6 +344,35 @@ namespace osu.Game Ruleset.BindValueChanged(onRulesetChanged); } + protected virtual void InitialiseFonts() + { + AddFont(Resources, @"Fonts/osuFont"); + + AddFont(Resources, @"Fonts/Torus/Torus-Regular"); + AddFont(Resources, @"Fonts/Torus/Torus-Light"); + AddFont(Resources, @"Fonts/Torus/Torus-SemiBold"); + AddFont(Resources, @"Fonts/Torus/Torus-Bold"); + + AddFont(Resources, @"Fonts/Inter/Inter-Regular"); + AddFont(Resources, @"Fonts/Inter/Inter-RegularItalic"); + AddFont(Resources, @"Fonts/Inter/Inter-Light"); + AddFont(Resources, @"Fonts/Inter/Inter-LightItalic"); + AddFont(Resources, @"Fonts/Inter/Inter-SemiBold"); + AddFont(Resources, @"Fonts/Inter/Inter-SemiBoldItalic"); + AddFont(Resources, @"Fonts/Inter/Inter-Bold"); + AddFont(Resources, @"Fonts/Inter/Inter-BoldItalic"); + + AddFont(Resources, @"Fonts/Noto/Noto-Basic"); + AddFont(Resources, @"Fonts/Noto/Noto-Hangul"); + AddFont(Resources, @"Fonts/Noto/Noto-CJK-Basic"); + AddFont(Resources, @"Fonts/Noto/Noto-CJK-Compatibility"); + AddFont(Resources, @"Fonts/Noto/Noto-Thai"); + + AddFont(Resources, @"Fonts/Venera/Venera-Light"); + AddFont(Resources, @"Fonts/Venera/Venera-Bold"); + AddFont(Resources, @"Fonts/Venera/Venera-Black"); + } + private IDisposable blocking; private void updateThreadStateChanged(ValueChangedEvent state) diff --git a/osu.Game/Tests/Visual/OsuTestScene.cs b/osu.Game/Tests/Visual/OsuTestScene.cs index ef9181c8a6..03434961ea 100644 --- a/osu.Game/Tests/Visual/OsuTestScene.cs +++ b/osu.Game/Tests/Visual/OsuTestScene.cs @@ -367,6 +367,11 @@ namespace osu.Game.Tests.Visual Add(runner = new TestSceneTestRunner.TestRunner()); } + protected override void InitialiseFonts() + { + // skip fonts load as it's not required for testing purposes. + } + public void RunTestBlocking(TestScene test) => runner.RunTestBlocking(test); } } From 93da531d135890c7b9addf0570721fdf7e6573da Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 7 Sep 2021 14:33:58 +0900 Subject: [PATCH 156/160] Improve code around background screen handling to read better --- osu.Game/Screens/BackgroundScreenStack.cs | 12 +++++++++--- osu.Game/Screens/OsuScreen.cs | 9 +++------ 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/osu.Game/Screens/BackgroundScreenStack.cs b/osu.Game/Screens/BackgroundScreenStack.cs index 294f23d2ac..9f562a618e 100644 --- a/osu.Game/Screens/BackgroundScreenStack.cs +++ b/osu.Game/Screens/BackgroundScreenStack.cs @@ -17,15 +17,21 @@ namespace osu.Game.Screens Origin = Anchor.Centre; } - public void Push(BackgroundScreen screen) + /// + /// Attempt to push a new background screen to this stack. + /// + /// The screen to attempt to push. + /// Whether the push succeeded. For example, if the existing screen was already of the correct type this will return false. + public bool Push(BackgroundScreen screen) { if (screen == null) - return; + return false; if (EqualityComparer.Default.Equals((BackgroundScreen)CurrentScreen, screen)) - return; + return false; base.Push(screen); + return true; } } } diff --git a/osu.Game/Screens/OsuScreen.cs b/osu.Game/Screens/OsuScreen.cs index e3fe14a585..9aec2a5c19 100644 --- a/osu.Game/Screens/OsuScreen.cs +++ b/osu.Game/Screens/OsuScreen.cs @@ -186,17 +186,14 @@ namespace osu.Game.Screens { applyArrivingDefaults(false); - backgroundStack?.Push(ownedBackground = CreateBackground()); - - background = backgroundStack?.CurrentScreen as BackgroundScreen; - - if (background != ownedBackground) + if (backgroundStack?.Push(ownedBackground = CreateBackground()) != true) { - // background may have not been replaced, at which point we don't want to track the background lifetime. + // If the constructed instance was not actually pushed to the background stack, we don't want to track it unnecessarily. ownedBackground?.Dispose(); ownedBackground = null; } + background = backgroundStack?.CurrentScreen as BackgroundScreen; base.OnEntering(last); } From 5b13b566b5151dce86f1f93f54604bbde2f44514 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 7 Sep 2021 15:19:23 +0900 Subject: [PATCH 157/160] Reduce startup overhead during default key binding handling --- .../Database/TestRealmKeyBindingStore.cs | 5 +- osu.Game/Input/RealmKeyBindingStore.cs | 69 ++++++++++--------- osu.Game/OsuGameBase.cs | 5 +- 3 files changed, 39 insertions(+), 40 deletions(-) diff --git a/osu.Game.Tests/Database/TestRealmKeyBindingStore.cs b/osu.Game.Tests/Database/TestRealmKeyBindingStore.cs index 642ecf00b8..8be74f1a7c 100644 --- a/osu.Game.Tests/Database/TestRealmKeyBindingStore.cs +++ b/osu.Game.Tests/Database/TestRealmKeyBindingStore.cs @@ -11,6 +11,7 @@ using osu.Framework.Platform; using osu.Game.Database; using osu.Game.Input; using osu.Game.Input.Bindings; +using osu.Game.Rulesets; using Realms; namespace osu.Game.Tests.Database @@ -42,7 +43,7 @@ namespace osu.Game.Tests.Database KeyBindingContainer testContainer = new TestKeyBindingContainer(); - keyBindingStore.Register(testContainer); + keyBindingStore.Register(testContainer, Enumerable.Empty()); Assert.That(queryCount(), Is.EqualTo(3)); @@ -66,7 +67,7 @@ namespace osu.Game.Tests.Database { KeyBindingContainer testContainer = new TestKeyBindingContainer(); - keyBindingStore.Register(testContainer); + keyBindingStore.Register(testContainer, Enumerable.Empty()); using (var primaryUsage = realmContextFactory.GetForRead()) { diff --git a/osu.Game/Input/RealmKeyBindingStore.cs b/osu.Game/Input/RealmKeyBindingStore.cs index 9089169877..03cb4031ca 100644 --- a/osu.Game/Input/RealmKeyBindingStore.cs +++ b/osu.Game/Input/RealmKeyBindingStore.cs @@ -46,52 +46,53 @@ namespace osu.Game.Input } /// - /// Register a new type of , adding default bindings from . + /// Register all defaults for this store. /// /// The container to populate defaults from. - public void Register(KeyBindingContainer container) => insertDefaults(container.DefaultKeyBindings); - - /// - /// Register a ruleset, adding default bindings for each of its variants. - /// - /// The ruleset to populate defaults from. - public void Register(RulesetInfo ruleset) - { - var instance = ruleset.CreateInstance(); - - foreach (var variant in instance.AvailableVariants) - insertDefaults(instance.GetDefaultKeyBindings(variant), ruleset.ID, variant); - } - - private void insertDefaults(IEnumerable defaults, int? rulesetId = null, int? variant = null) + /// The rulesets to populate defaults from. + public void Register(KeyBindingContainer container, IEnumerable rulesets) { using (var usage = realmFactory.GetForWrite()) { - // compare counts in database vs defaults - foreach (var defaultsForAction in defaults.GroupBy(k => k.Action)) + // intentionally flattened to a list rather than querying against the IQueryable, as nullable fields being queried against aren't indexed. + // this is much faster as a result. + var existingBindings = usage.Realm.All().ToList(); + + insertDefaults(usage, existingBindings, container.DefaultKeyBindings); + + foreach (var ruleset in rulesets) { - int existingCount = usage.Realm.All().Count(k => k.RulesetID == rulesetId && k.Variant == variant && k.ActionInt == (int)defaultsForAction.Key); - - if (defaultsForAction.Count() <= existingCount) - continue; - - foreach (var k in defaultsForAction.Skip(existingCount)) - { - // insert any defaults which are missing. - usage.Realm.Add(new RealmKeyBinding - { - KeyCombinationString = k.KeyCombination.ToString(), - ActionInt = (int)k.Action, - RulesetID = rulesetId, - Variant = variant - }); - } + var instance = ruleset.CreateInstance(); + foreach (var variant in instance.AvailableVariants) + insertDefaults(usage, existingBindings, instance.GetDefaultKeyBindings(variant), ruleset.ID, variant); } usage.Commit(); } } + private void insertDefaults(RealmContextFactory.RealmUsage usage, List existingBindings, IEnumerable defaults, int? rulesetId = null, int? variant = null) + { + // compare counts in database vs defaults for each action type. + foreach (var defaultsForAction in defaults.GroupBy(k => k.Action)) + { + // avoid performing redundant queries when the database is empty and needs to be re-filled. + int existingCount = existingBindings.Count(k => k.RulesetID == rulesetId && k.Variant == variant && k.ActionInt == (int)defaultsForAction.Key); + + if (defaultsForAction.Count() <= existingCount) + continue; + + // insert any defaults which are missing. + usage.Realm.Add(defaultsForAction.Skip(existingCount).Select(k => new RealmKeyBinding + { + KeyCombinationString = k.KeyCombination.ToString(), + ActionInt = (int)k.Action, + RulesetID = rulesetId, + Variant = variant + })); + } + } + /// /// Keys which should not be allowed for gameplay input purposes. /// diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 762216e93c..8563c4e171 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -351,10 +351,7 @@ namespace osu.Game base.Content.Add(CreateScalingContainer().WithChildren(mainContent)); KeyBindingStore = new RealmKeyBindingStore(realmFactory); - KeyBindingStore.Register(globalBindings); - - foreach (var r in RulesetStore.AvailableRulesets) - KeyBindingStore.Register(r); + KeyBindingStore.Register(globalBindings, RulesetStore.AvailableRulesets); dependencies.Cache(globalBindings); From 44b1af5ae4419495dcba68f6472b609e43656726 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 7 Sep 2021 15:28:52 +0900 Subject: [PATCH 158/160] 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 8a9bf1b9cd..05367c00f6 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 ebe3de6ea4..ae423bac8c 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 1714bae53c..be737392e1 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -71,7 +71,7 @@ - + From 61f819b66d16f0d65a14f767cd66d70d13379f00 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 7 Sep 2021 15:51:57 +0900 Subject: [PATCH 159/160] Add COE and better PR condition --- .github/workflows/test-diffcalc.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test-diffcalc.yml b/.github/workflows/test-diffcalc.yml index d963e49d9f..efa36712be 100644 --- a/.github/workflows/test-diffcalc.yml +++ b/.github/workflows/test-diffcalc.yml @@ -22,12 +22,13 @@ jobs: runs-on: ubuntu-latest if: | - contains(github.event.comment.html_url, '/pull/') && + ${{ github.event.issue.pull_request }} && contains(github.event.comment.body, '!pp check') && ${{ github.event.comment.author_association == 'MEMBER' }} strategy: fail-fast: false + continue-on-error: true matrix: ruleset: - { name: osu, id: 0 } From 842696c388a6b2d1401b4a41fbe1d39af53491d1 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 7 Sep 2021 15:54:58 +0900 Subject: [PATCH 160/160] Fix incorrect definition --- .github/workflows/test-diffcalc.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test-diffcalc.yml b/.github/workflows/test-diffcalc.yml index efa36712be..7728d91152 100644 --- a/.github/workflows/test-diffcalc.yml +++ b/.github/workflows/test-diffcalc.yml @@ -20,6 +20,7 @@ jobs: diffcalc: name: Diffcalc runs-on: ubuntu-latest + continue-on-error: true if: | ${{ github.event.issue.pull_request }} && @@ -28,7 +29,6 @@ jobs: strategy: fail-fast: false - continue-on-error: true matrix: ruleset: - { name: osu, id: 0 }