diff --git a/CodeAnalysis/BannedSymbols.txt b/CodeAnalysis/BannedSymbols.txt index c567adc0ae..e96ad48325 100644 --- a/CodeAnalysis/BannedSymbols.txt +++ b/CodeAnalysis/BannedSymbols.txt @@ -13,3 +13,5 @@ M:System.Enum.HasFlag(System.Enum);Use osu.Framework.Extensions.EnumExtensions.H M:Realms.IRealmCollection`1.SubscribeForNotifications`1(Realms.NotificationCallbackDelegate{``0});Use osu.Game.Database.RealmObjectExtensions.QueryAsyncWithNotifications(IRealmCollection,NotificationCallbackDelegate) instead. M:Realms.CollectionExtensions.SubscribeForNotifications`1(System.Linq.IQueryable{``0},Realms.NotificationCallbackDelegate{``0});Use osu.Game.Database.RealmObjectExtensions.QueryAsyncWithNotifications(IQueryable,NotificationCallbackDelegate) instead. M:Realms.CollectionExtensions.SubscribeForNotifications`1(System.Collections.Generic.IList{``0},Realms.NotificationCallbackDelegate{``0});Use osu.Game.Database.RealmObjectExtensions.QueryAsyncWithNotifications(IList,NotificationCallbackDelegate) instead. +M:System.Threading.Tasks.Task.Wait();Don't use Task.Wait. Use Task.WaitSafely() to ensure we avoid deadlocks. +P:System.Threading.Tasks.Task`1.Result;Don't use Task.Result. Use Task.GetResultSafely() to ensure we avoid deadlocks. diff --git a/osu.Android.props b/osu.Android.props index 31a3bc8c45..92adbebf5a 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -51,8 +51,8 @@ - - + + diff --git a/osu.Desktop/Security/ElevatedPrivilegesChecker.cs b/osu.Desktop/Security/ElevatedPrivilegesChecker.cs index 01458b4c37..62ea3e0399 100644 --- a/osu.Desktop/Security/ElevatedPrivilegesChecker.cs +++ b/osu.Desktop/Security/ElevatedPrivilegesChecker.cs @@ -76,7 +76,7 @@ namespace osu.Desktop.Security private void load(OsuColour colours, NotificationOverlay notificationOverlay) { Icon = FontAwesome.Solid.ShieldAlt; - IconBackgound.Colour = colours.YellowDark; + IconBackground.Colour = colours.YellowDark; } } } diff --git a/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs b/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs index be1885cfa6..baca8166d1 100644 --- a/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs +++ b/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs @@ -14,7 +14,6 @@ using osu.Game.Tests.Beatmaps; namespace osu.Game.Rulesets.Catch.Tests { [TestFixture] - [Timeout(10000)] public class CatchBeatmapConversionTest : BeatmapConversionTest { protected override string ResourceAssembly => "osu.Game.Rulesets.Catch"; diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjects.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjects.cs index 459b8e1f6f..23f6222eb6 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjects.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjects.cs @@ -72,7 +72,7 @@ namespace osu.Game.Rulesets.Catch.Tests } [Test] - public void TestJuicestream() + public void TestJuiceStream() { AddStep("hit juicestream", () => spawnJuiceStream(true)); AddUntilStep("wait for completion", () => playfieldIsEmpty); diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneFruitObjects.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneFruitObjects.cs index 943adbef52..12b98dc93c 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneFruitObjects.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneFruitObjects.cs @@ -37,20 +37,20 @@ namespace osu.Game.Rulesets.Catch.Tests AddStep("show hyperdash droplet", () => SetContents(_ => createDrawableDroplet(true))); } - private Drawable createDrawableFruit(int indexInBeatmap, bool hyperdash = false) => + private Drawable createDrawableFruit(int indexInBeatmap, bool hyperDash = false) => new TestDrawableCatchHitObjectSpecimen(new DrawableFruit(new Fruit { IndexInBeatmap = indexInBeatmap, - HyperDashBindable = { Value = hyperdash } + HyperDashBindable = { Value = hyperDash } })); private Drawable createDrawableBanana() => new TestDrawableCatchHitObjectSpecimen(new DrawableBanana(new Banana())); - private Drawable createDrawableDroplet(bool hyperdash = false) => + private Drawable createDrawableDroplet(bool hyperDash = false) => new TestDrawableCatchHitObjectSpecimen(new DrawableDroplet(new Droplet { - HyperDashBindable = { Value = hyperdash } + HyperDashBindable = { Value = hyperDash } })); private Drawable createDrawableTinyDroplet() => new TestDrawableCatchHitObjectSpecimen(new DrawableTinyDroplet(new TinyDroplet())); diff --git a/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs b/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs index 8cb0804ab7..dd5835b4ed 100644 --- a/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs +++ b/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs @@ -52,16 +52,25 @@ namespace osu.Game.Rulesets.Catch.Edit return true; } - public override bool HandleFlip(Direction direction) + public override bool HandleFlip(Direction direction, bool flipOverOrigin) { + if (SelectedItems.Count == 0) + return false; + + // This could be implemented in the future if there's a requirement for it. + if (direction == Direction.Vertical) + return false; + var selectionRange = CatchHitObjectUtils.GetPositionRange(SelectedItems); bool changed = false; + EditorBeatmap.PerformOnSelection(h => { if (h is CatchHitObject catchObject) - changed |= handleFlip(selectionRange, catchObject); + changed |= handleFlip(selectionRange, catchObject, flipOverOrigin); }); + return changed; } @@ -116,7 +125,7 @@ namespace osu.Game.Rulesets.Catch.Edit return Math.Clamp(deltaX, lowerBound, upperBound); } - private bool handleFlip(PositionRange selectionRange, CatchHitObject hitObject) + private bool handleFlip(PositionRange selectionRange, CatchHitObject hitObject, bool flipOverOrigin) { switch (hitObject) { @@ -124,7 +133,7 @@ namespace osu.Game.Rulesets.Catch.Edit return false; case JuiceStream juiceStream: - juiceStream.OriginalX = selectionRange.GetFlippedPosition(juiceStream.OriginalX); + juiceStream.OriginalX = getFlippedPosition(juiceStream.OriginalX); foreach (var point in juiceStream.Path.ControlPoints) point.Position *= new Vector2(-1, 1); @@ -133,9 +142,11 @@ namespace osu.Game.Rulesets.Catch.Edit return true; default: - hitObject.OriginalX = selectionRange.GetFlippedPosition(hitObject.OriginalX); + hitObject.OriginalX = getFlippedPosition(hitObject.OriginalX); return true; } + + float getFlippedPosition(float original) => flipOverOrigin ? CatchPlayfield.WIDTH - original : selectionRange.GetFlippedPosition(original); } } } diff --git a/osu.Game.Rulesets.Catch/Skinning/Default/DefaultHitExplosion.cs b/osu.Game.Rulesets.Catch/Skinning/Default/DefaultHitExplosion.cs index e1fad564a3..f6b3c3d665 100644 --- a/osu.Game.Rulesets.Catch/Skinning/Default/DefaultHitExplosion.cs +++ b/osu.Game.Rulesets.Catch/Skinning/Default/DefaultHitExplosion.cs @@ -90,9 +90,9 @@ namespace osu.Game.Rulesets.Catch.Skinning.Default .ResizeTo(largeFaint.Size * new Vector2(5, 1), duration, Easing.OutQuint) .FadeOut(duration * 2); - const float angle_variangle = 15; // should be less than 45 - directionalGlow1.Rotation = StatelessRNG.NextSingle(-angle_variangle, angle_variangle, randomSeed, 4); - directionalGlow2.Rotation = StatelessRNG.NextSingle(-angle_variangle, angle_variangle, randomSeed, 5); + const float angle_variance = 15; // should be less than 45 + directionalGlow1.Rotation = StatelessRNG.NextSingle(-angle_variance, angle_variance, randomSeed, 4); + directionalGlow2.Rotation = StatelessRNG.NextSingle(-angle_variance, angle_variance, randomSeed, 5); this.FadeInFromZero(50).Then().FadeOut(duration, Easing.Out); } diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs b/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs index 948f088b4e..837474ad9e 100644 --- a/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs +++ b/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs @@ -14,7 +14,6 @@ using osu.Game.Tests.Beatmaps; namespace osu.Game.Rulesets.Mania.Tests { [TestFixture] - [Timeout(10000)] public class ManiaBeatmapConversionTest : BeatmapConversionTest { protected override string ResourceAssembly => "osu.Game.Rulesets.Mania"; diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneHitExplosion.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneHitExplosion.cs index 004793e1e5..9e6e0a7776 100644 --- a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneHitExplosion.cs +++ b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneHitExplosion.cs @@ -24,13 +24,13 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning public TestSceneHitExplosion() { - int runcount = 0; + int runCount = 0; AddRepeatStep("explode", () => { - runcount++; + runCount++; - if (runcount % 15 > 12) + if (runCount % 15 > 12) return; int poolIndex = 0; @@ -39,7 +39,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning { c.Add(hitExplosionPools[poolIndex].Get(e => { - e.Apply(new JudgementResult(new HitObject(), runcount % 6 == 0 ? new HoldNoteTickJudgement() : new ManiaJudgement())); + e.Apply(new JudgementResult(new HitObject(), runCount % 6 == 0 ? new HoldNoteTickJudgement() : new ManiaJudgement())); e.Anchor = Anchor.Centre; e.Origin = Anchor.Centre; diff --git a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmap.cs b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmap.cs index 93a9ce3dbd..0058f6f884 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmap.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmap.cs @@ -40,7 +40,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps public override IEnumerable GetStatistics() { int notes = HitObjects.Count(s => s is Note); - int holdnotes = HitObjects.Count(s => s is HoldNote); + int holdNotes = HitObjects.Count(s => s is HoldNote); return new[] { @@ -54,7 +54,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps { Name = @"Hold Note Count", CreateIcon = () => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Sliders), - Content = holdnotes.ToString(), + Content = holdNotes.ToString(), }, }; } diff --git a/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs index 0588ae57d7..431bd77402 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs @@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy /// Mapping of to their corresponding /// value. /// - private static readonly IReadOnlyDictionary hitresult_mapping + private static readonly IReadOnlyDictionary hit_result_mapping = new Dictionary { { HitResult.Perfect, LegacyManiaSkinConfigurationLookups.Hit300g }, @@ -39,7 +39,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy /// Mapping of to their corresponding /// default filenames. /// - private static readonly IReadOnlyDictionary default_hitresult_skin_filenames + private static readonly IReadOnlyDictionary default_hit_result_skin_filenames = new Dictionary { { HitResult.Perfect, "mania-hit300g" }, @@ -126,11 +126,11 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy private Drawable getResult(HitResult result) { - if (!hitresult_mapping.ContainsKey(result)) + if (!hit_result_mapping.ContainsKey(result)) return null; - string filename = this.GetManiaSkinConfig(hitresult_mapping[result])?.Value - ?? default_hitresult_skin_filenames[result]; + string filename = this.GetManiaSkinConfig(hit_result_mapping[result])?.Value + ?? default_hit_result_skin_filenames[result]; var animation = this.GetAnimation(filename, true, true); return animation == null ? null : new LegacyManiaJudgementPiece(result, animation); diff --git a/osu.Game.Rulesets.Mania/UI/DefaultHitExplosion.cs b/osu.Game.Rulesets.Mania/UI/DefaultHitExplosion.cs index 69b81d6d5c..562d7b04c4 100644 --- a/osu.Game.Rulesets.Mania/UI/DefaultHitExplosion.cs +++ b/osu.Game.Rulesets.Mania/UI/DefaultHitExplosion.cs @@ -42,7 +42,7 @@ namespace osu.Game.Rulesets.Mania.UI [BackgroundDependencyLoader] private void load(IScrollingInfo scrollingInfo) { - const float angle_variangle = 15; // should be less than 45 + const float angle_variance = 15; // should be less than 45 const float roundness = 80; const float initial_height = 10; @@ -90,7 +90,7 @@ namespace osu.Game.Rulesets.Mania.UI Masking = true, Size = new Vector2(0.01f, initial_height), Blending = BlendingParameters.Additive, - Rotation = RNG.NextSingle(-angle_variangle, angle_variangle), + Rotation = RNG.NextSingle(-angle_variance, angle_variance), EdgeEffect = new EdgeEffectParameters { Type = EdgeEffectType.Glow, @@ -107,7 +107,7 @@ namespace osu.Game.Rulesets.Mania.UI Masking = true, Size = new Vector2(0.01f, initial_height), Blending = BlendingParameters.Additive, - Rotation = RNG.NextSingle(-angle_variangle, angle_variangle), + Rotation = RNG.NextSingle(-angle_variance, angle_variance), EdgeEffect = new EdgeEffectParameters { Type = EdgeEffectType.Glow, diff --git a/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs b/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs index 8830c440c0..4cd6624ac6 100644 --- a/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs +++ b/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs @@ -65,7 +65,7 @@ namespace osu.Game.Rulesets.Mania.UI public override bool Remove(DrawableHitObject h) => getStageByColumn(((ManiaHitObject)h.HitObject).Column).Remove(h); - public void Add(BarLine barline) => stages.ForEach(s => s.Add(barline)); + public void Add(BarLine barLine) => stages.ForEach(s => s.Add(barLine)); /// /// Retrieves a column from a screen-space position. diff --git a/osu.Game.Rulesets.Mania/UI/Stage.cs b/osu.Game.Rulesets.Mania/UI/Stage.cs index 8c703e7a8a..94910bb410 100644 --- a/osu.Game.Rulesets.Mania/UI/Stage.cs +++ b/osu.Game.Rulesets.Mania/UI/Stage.cs @@ -147,7 +147,7 @@ namespace osu.Game.Rulesets.Mania.UI public override bool Remove(DrawableHitObject h) => Columns.ElementAt(((ManiaHitObject)h.HitObject).Column - firstColumnIndex).Remove(h); - public void Add(BarLine barline) => base.Add(new DrawableBarLine(barline)); + public void Add(BarLine barLine) => base.Add(new DrawableBarLine(barLine)); internal void OnNewResult(DrawableHitObject judgedObject, JudgementResult result) { diff --git a/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs b/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs index 5f44e1b6b6..4c11efcc7c 100644 --- a/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs +++ b/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs @@ -12,7 +12,6 @@ using osu.Game.Tests.Beatmaps; namespace osu.Game.Rulesets.Osu.Tests { [TestFixture] - [Timeout(10000)] public class OsuBeatmapConversionTest : BeatmapConversionTest { protected override string ResourceAssembly => "osu.Game.Rulesets.Osu"; diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hitcircleoverlay@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hitcircleoverlay-0@2x.png similarity index 100% rename from osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hitcircleoverlay@2x.png rename to osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hitcircleoverlay-0@2x.png diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hitcircleoverlay-1@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hitcircleoverlay-1@2x.png new file mode 100755 index 0000000000..3b5e886933 Binary files /dev/null and b/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hitcircleoverlay-1@2x.png differ diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs index 128ff772fd..126a9b0183 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs @@ -70,8 +70,15 @@ namespace osu.Game.Rulesets.Osu.Difficulty SliderFactor = values[ATTRIB_ID_SLIDER_FACTOR]; } - // Used implicitly by Newtonsoft.Json to not serialize flashlight property in some cases. + #region Newtonsoft.Json implicit ShouldSerialize() methods + + // The properties in this region are used implicitly by Newtonsoft.Json to not serialise certain fields in some cases. + // They rely on being named exactly the same as the corresponding fields (casing included) and as such should NOT be renamed + // unless the fields are also renamed. + [UsedImplicitly] public bool ShouldSerializeFlashlightRating() => Mods.Any(m => m is ModFlashlight); + + #endregion } } diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index d7d294df47..fdf646ef85 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -48,7 +48,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty if (mods.Any(m => m is OsuModNoFail)) multiplier *= Math.Max(0.90, 1.0 - 0.02 * effectiveMissCount); - if (mods.Any(m => m is OsuModSpunOut)) + if (mods.Any(m => m is OsuModSpunOut) && totalHits > 0) multiplier *= 1.0 - Math.Pow((double)Attributes.SpinnerCount / totalHits, 0.85); if (mods.Any(h => h is OsuModRelax)) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs index 3709cd3a7d..09759aa118 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs @@ -250,7 +250,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components switch (pathType) { case PathType.Catmull: - return colours.Seafoam; + return colours.SeaFoam; case PathType.Bezier: return colours.Pink; diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs index 4a57d36eb4..071ecf6329 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs @@ -10,6 +10,7 @@ using osu.Game.Extensions; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.UI; using osu.Game.Screens.Edit.Compose.Components; using osuTK; @@ -84,18 +85,28 @@ namespace osu.Game.Rulesets.Osu.Edit return true; } - public override bool HandleFlip(Direction direction) + public override bool HandleFlip(Direction direction, bool flipOverOrigin) { var hitObjects = selectedMovableObjects; - var selectedObjectsQuad = getSurroundingQuad(hitObjects); + var flipQuad = flipOverOrigin ? new Quad(0, 0, OsuPlayfield.BASE_SIZE.X, OsuPlayfield.BASE_SIZE.Y) : getSurroundingQuad(hitObjects); + + bool didFlip = false; foreach (var h in hitObjects) { - h.Position = GetFlippedPosition(direction, selectedObjectsQuad, h.Position); + var flippedPosition = GetFlippedPosition(direction, flipQuad, h.Position); + + if (!Precision.AlmostEquals(flippedPosition, h.Position)) + { + h.Position = flippedPosition; + didFlip = true; + } if (h is Slider slider) { + didFlip = true; + foreach (var point in slider.Path.ControlPoints) { point.Position = new Vector2( @@ -106,7 +117,7 @@ namespace osu.Game.Rulesets.Osu.Edit } } - return true; + return didFlip; } public override bool HandleScale(Vector2 scale, Anchor reference) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs b/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs index 31474e8fbb..098c639949 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs @@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Osu.Mods { public override string Name => "Spun Out"; public override string Acronym => "SO"; - public override IconUsage? Icon => OsuIcon.ModSpunout; + public override IconUsage? Icon => OsuIcon.ModSpunOut; public override ModType Type => ModType.Automation; public override string Description => @"Spinners will be automatically completed."; public override double ScoreMultiplier => 0.9; diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/KiaiFlashingDrawable.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/KiaiFlashingDrawable.cs new file mode 100644 index 0000000000..4ee28d05b5 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/KiaiFlashingDrawable.cs @@ -0,0 +1,52 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Framework.Audio.Track; +using osu.Framework.Graphics; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Graphics.Containers; + +#nullable enable + +namespace osu.Game.Rulesets.Osu.Skinning.Legacy +{ + internal class KiaiFlashingDrawable : BeatSyncedContainer + { + private readonly Drawable flashingDrawable; + + private const float flash_opacity = 0.3f; + + public KiaiFlashingDrawable(Func creationFunc) + { + AutoSizeAxes = Axes.Both; + + Children = new[] + { + (creationFunc.Invoke() ?? Empty()).With(d => + { + d.Anchor = Anchor.Centre; + d.Origin = Anchor.Centre; + }), + flashingDrawable = (creationFunc.Invoke() ?? Empty()).With(d => + { + d.Anchor = Anchor.Centre; + d.Origin = Anchor.Centre; + d.Alpha = 0; + d.Blending = BlendingParameters.Additive; + }) + }; + } + + protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, ChannelAmplitudes amplitudes) + { + if (!effectPoint.KiaiMode) + return; + + flashingDrawable + .FadeTo(flash_opacity) + .Then() + .FadeOut(timingPoint.BeatLength * 0.75f); + } + } +} diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/KiaiFlashingSprite.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/KiaiFlashingSprite.cs deleted file mode 100644 index 4a1d69ad41..0000000000 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/KiaiFlashingSprite.cs +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using osu.Framework.Audio.Track; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Sprites; -using osu.Framework.Graphics.Textures; -using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Graphics.Containers; - -namespace osu.Game.Rulesets.Osu.Skinning.Legacy -{ - internal class KiaiFlashingSprite : BeatSyncedContainer - { - private readonly Sprite mainSprite; - private readonly Sprite flashingSprite; - - public Texture Texture - { - set - { - mainSprite.Texture = value; - flashingSprite.Texture = value; - } - } - - private const float flash_opacity = 0.3f; - - public KiaiFlashingSprite() - { - AutoSizeAxes = Axes.Both; - - Children = new Drawable[] - { - mainSprite = new Sprite - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - }, - flashingSprite = new Sprite - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Alpha = 0, - Blending = BlendingParameters.Additive, - } - }; - } - - protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, ChannelAmplitudes amplitudes) - { - if (!effectPoint.KiaiMode) - return; - - flashingSprite - .FadeTo(flash_opacity) - .Then() - .FadeOut(timingPoint.BeatLength * 0.75f); - } - } -} diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyMainCirclePiece.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyMainCirclePiece.cs index d2f84dcf84..c6007885be 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyMainCirclePiece.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyMainCirclePiece.cs @@ -5,6 +5,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; @@ -68,13 +69,11 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy // at this point, any further texture fetches should be correctly using the priority source if the base texture was retrieved using it. // the flow above handles the case where a sliderendcircle.png is retrieved from the skin, but sliderendcircleoverlay.png doesn't exist. // expected behaviour in this scenario is not showing the overlay, rather than using hitcircleoverlay.png (potentially from the default/fall-through skin). - Texture overlayTexture = getTextureWithFallback("overlay"); InternalChildren = new[] { - hitCircleSprite = new KiaiFlashingSprite + hitCircleSprite = new KiaiFlashingDrawable(() => new Sprite { Texture = baseTexture }) { - Texture = baseTexture, Anchor = Anchor.Centre, Origin = Anchor.Centre, }, @@ -82,9 +81,8 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Child = hitCircleOverlay = new KiaiFlashingSprite + Child = hitCircleOverlay = new KiaiFlashingDrawable(() => getAnimationWithFallback(@"overlay", 1000 / 2d)) { - Texture = overlayTexture, Anchor = Anchor.Centre, Origin = Anchor.Centre, }, @@ -126,6 +124,21 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy return tex ?? skin.GetTexture($"hitcircle{name}"); } + + Drawable getAnimationWithFallback(string name, double frameLength) + { + Drawable animation = null; + + if (!string.IsNullOrEmpty(priorityLookup)) + { + animation = skin.GetAnimation($"{priorityLookup}{name}", true, true, frameLength: frameLength); + + if (!allowFallback) + return animation; + } + + return animation ?? skin.GetAnimation($"hitcircle{name}", true, true, frameLength: frameLength); + } } protected override void LoadComplete() diff --git a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableBarLine.cs b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableBarLine.cs index 269a855219..16c4148d15 100644 --- a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableBarLine.cs +++ b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableBarLine.cs @@ -83,7 +83,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning private BarLine createBarLineAtCurrentTime(bool major = false) { - var barline = new BarLine + var barLine = new BarLine { Major = major, StartTime = Time.Current + 2000, @@ -92,9 +92,9 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning var cpi = new ControlPointInfo(); cpi.Add(0, new TimingControlPoint { BeatLength = 500 }); - barline.ApplyDefaults(cpi, new BeatmapDifficulty()); + barLine.ApplyDefaults(cpi, new BeatmapDifficulty()); - return barline; + return barLine; } } } diff --git a/osu.Game.Rulesets.Taiko.Tests/TaikoBeatmapConversionTest.cs b/osu.Game.Rulesets.Taiko.Tests/TaikoBeatmapConversionTest.cs index b6db333dc9..b3f6a733d3 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TaikoBeatmapConversionTest.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TaikoBeatmapConversionTest.cs @@ -12,7 +12,6 @@ using osu.Game.Tests.Beatmaps; namespace osu.Game.Rulesets.Taiko.Tests { [TestFixture] - [Timeout(10000)] public class TaikoBeatmapConversionTest : BeatmapConversionTest { protected override string ResourceAssembly => "osu.Game.Rulesets.Taiko"; diff --git a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmap.cs b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmap.cs index 16a0726c8c..41fe63a553 100644 --- a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmap.cs +++ b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmap.cs @@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps public override IEnumerable GetStatistics() { int hits = HitObjects.Count(s => s is Hit); - int drumrolls = HitObjects.Count(s => s is DrumRoll); + int drumRolls = HitObjects.Count(s => s is DrumRoll); int swells = HitObjects.Count(s => s is Swell); return new[] @@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps { Name = @"Drumroll Count", CreateIcon = () => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Sliders), - Content = drumrolls.ToString(), + Content = drumRolls.ToString(), }, new BeatmapStatistic { diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs index 81d89359e0..9ac7838821 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs @@ -95,7 +95,6 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.That(decodedAfterEncode, Is.Not.Null); Assert.That(decodedAfterEncode.ScoreInfo.User.Username, Is.EqualTo(scoreInfo.User.Username)); - Assert.That(decodedAfterEncode.ScoreInfo.BeatmapInfoID, Is.EqualTo(scoreInfo.BeatmapInfoID)); Assert.That(decodedAfterEncode.ScoreInfo.Ruleset, Is.EqualTo(scoreInfo.Ruleset)); Assert.That(decodedAfterEncode.ScoreInfo.TotalScore, Is.EqualTo(scoreInfo.TotalScore)); Assert.That(decodedAfterEncode.ScoreInfo.MaxCombo, Is.EqualTo(scoreInfo.MaxCombo)); diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs index 9f3709f7a3..c02141bf9f 100644 --- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs @@ -93,7 +93,7 @@ namespace osu.Game.Tests.Beatmaps.IO using (var stream = File.OpenRead(tempPath)) { importedSet = await manager.Import(new ImportTask(stream, Path.GetFileName(tempPath))); - ensureLoaded(osu); + await ensureLoaded(osu); } Assert.IsTrue(File.Exists(tempPath), "Stream source file somehow went missing"); @@ -171,7 +171,7 @@ namespace osu.Game.Tests.Beatmaps.IO var importedSecondTime = await osu.Dependencies.Get().Import(new ImportTask(temp)); - ensureLoaded(osu); + await ensureLoaded(osu); // but contents doesn't, so existing should still be used. Assert.IsTrue(imported.ID == importedSecondTime.Value.ID); @@ -225,7 +225,7 @@ namespace osu.Game.Tests.Beatmaps.IO var importedSecondTime = await osu.Dependencies.Get().Import(new ImportTask(temp)); - ensureLoaded(osu); + await ensureLoaded(osu); // check the newly "imported" beatmap is not the original. Assert.IsTrue(imported.ID != importedSecondTime.Value.ID); @@ -277,7 +277,7 @@ namespace osu.Game.Tests.Beatmaps.IO var importedSecondTime = await osu.Dependencies.Get().Import(new ImportTask(temp)); - ensureLoaded(osu); + await ensureLoaded(osu); // check the newly "imported" beatmap is not the original. Assert.IsTrue(imported.ID != importedSecondTime.Value.ID); @@ -328,7 +328,7 @@ namespace osu.Game.Tests.Beatmaps.IO var importedSecondTime = await osu.Dependencies.Get().Import(new ImportTask(temp)); - ensureLoaded(osu); + await ensureLoaded(osu); // check the newly "imported" beatmap is not the original. Assert.IsTrue(imported.ID != importedSecondTime.Value.ID); @@ -637,7 +637,7 @@ namespace osu.Game.Tests.Beatmaps.IO if (!importer.ImportAsync(temp).Wait(10000)) Assert.Fail(@"IPC took too long to send"); - ensureLoaded(osu); + ensureLoaded(osu).WaitSafely(); waitForOrAssert(() => !File.Exists(temp), "Temporary still exists after IPC import", 5000); } @@ -659,7 +659,7 @@ namespace osu.Game.Tests.Beatmaps.IO string temp = TestResources.GetTestBeatmapForImport(); using (File.OpenRead(temp)) await osu.Dependencies.Get().Import(temp); - ensureLoaded(osu); + await ensureLoaded(osu); File.Delete(temp); Assert.IsFalse(File.Exists(temp), "We likely held a read lock on the file when we shouldn't"); } @@ -698,7 +698,7 @@ namespace osu.Game.Tests.Beatmaps.IO await osu.Dependencies.Get().Import(temp); - ensureLoaded(osu); + await ensureLoaded(osu); } finally { @@ -741,7 +741,7 @@ namespace osu.Game.Tests.Beatmaps.IO var imported = await osu.Dependencies.Get().Import(new ImportTask(temp)); - ensureLoaded(osu); + await ensureLoaded(osu); Assert.IsFalse(imported.Value.Files.Any(f => f.Filename.Contains("subfolder")), "Files contain common subfolder"); } @@ -794,7 +794,7 @@ namespace osu.Game.Tests.Beatmaps.IO var imported = await osu.Dependencies.Get().Import(new ImportTask(temp)); - ensureLoaded(osu); + await ensureLoaded(osu); Assert.IsFalse(imported.Value.Files.Any(f => f.Filename.Contains("__MACOSX")), "Files contain resource fork folder, which should be ignored"); Assert.IsFalse(imported.Value.Files.Any(f => f.Filename.Contains("actual_data")), "Files contain common subfolder"); @@ -812,7 +812,7 @@ namespace osu.Game.Tests.Beatmaps.IO } [Test] - public async Task TestUpdateBeatmapInfo() + public void TestUpdateBeatmapInfo() { using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) { @@ -822,7 +822,8 @@ namespace osu.Game.Tests.Beatmaps.IO var manager = osu.Dependencies.Get(); string temp = TestResources.GetTestBeatmapForImport(); - await osu.Dependencies.Get().Import(temp); + + osu.Dependencies.Get().Import(temp).WaitSafely(); // Update via the beatmap, not the beatmap info, to ensure correct linking BeatmapSetInfo setToUpdate = manager.GetAllUsableBeatmapSets()[0]; @@ -842,7 +843,7 @@ namespace osu.Game.Tests.Beatmaps.IO } [Test] - public async Task TestUpdateBeatmapFile() + public void TestUpdateBeatmapFile() { using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) { @@ -852,7 +853,8 @@ namespace osu.Game.Tests.Beatmaps.IO var manager = osu.Dependencies.Get(); string temp = TestResources.GetTestBeatmapForImport(); - await osu.Dependencies.Get().Import(temp); + + osu.Dependencies.Get().Import(temp).WaitSafely(); BeatmapSetInfo setToUpdate = manager.GetAllUsableBeatmapSets()[0]; @@ -976,35 +978,35 @@ namespace osu.Game.Tests.Beatmaps.IO } } - public static async Task LoadQuickOszIntoOsu(OsuGameBase osu) + public static Task LoadQuickOszIntoOsu(OsuGameBase osu) => Task.Factory.StartNew(() => { string temp = TestResources.GetQuickTestBeatmapForImport(); var manager = osu.Dependencies.Get(); - var importedSet = await manager.Import(new ImportTask(temp)).ConfigureAwait(false); + var importedSet = manager.Import(new ImportTask(temp)).GetResultSafely(); - ensureLoaded(osu); + ensureLoaded(osu).WaitSafely(); waitForOrAssert(() => !File.Exists(temp), "Temporary file still exists after standard import", 5000); return manager.GetAllUsableBeatmapSets().Find(beatmapSet => beatmapSet.ID == importedSet.Value.ID); - } + }, TaskCreationOptions.LongRunning); - public static async Task LoadOszIntoOsu(OsuGameBase osu, string path = null, bool virtualTrack = false) + public static Task LoadOszIntoOsu(OsuGameBase osu, string path = null, bool virtualTrack = false) => Task.Factory.StartNew(() => { string temp = path ?? TestResources.GetTestBeatmapForImport(virtualTrack); var manager = osu.Dependencies.Get(); - var importedSet = await manager.Import(new ImportTask(temp)).ConfigureAwait(false); + var importedSet = manager.Import(new ImportTask(temp)).GetResultSafely(); - ensureLoaded(osu); + ensureLoaded(osu).WaitSafely(); waitForOrAssert(() => !File.Exists(temp), "Temporary file still exists after standard import", 5000); return manager.GetAllUsableBeatmapSets().Find(beatmapSet => beatmapSet.ID == importedSet.Value.ID); - } + }, TaskCreationOptions.LongRunning); private void deleteBeatmapSet(BeatmapSetInfo imported, OsuGameBase osu) { @@ -1053,7 +1055,7 @@ namespace osu.Game.Tests.Beatmaps.IO Assert.AreEqual(expected, osu.Dependencies.Get().Get().FileInfo.Count(f => f.ReferenceCount == 1)); } - private static void ensureLoaded(OsuGameBase osu, int timeout = 60000) + private static Task ensureLoaded(OsuGameBase osu, int timeout = 60000) => Task.Factory.StartNew(() => { IEnumerable resultSets = null; var store = osu.Dependencies.Get(); @@ -1089,14 +1091,14 @@ namespace osu.Game.Tests.Beatmaps.IO Assert.IsTrue(beatmap?.HitObjects.Any() == true); beatmap = store.GetWorkingBeatmap(set.Beatmaps.First(b => b.RulesetID == 3))?.Beatmap; Assert.IsTrue(beatmap?.HitObjects.Any() == true); - } + }, TaskCreationOptions.LongRunning); private static void waitForOrAssert(Func result, string failureMessage, int timeout = 60000) { - Task task = Task.Run(() => + Task task = Task.Factory.StartNew(() => { while (!result()) Thread.Sleep(200); - }); + }, TaskCreationOptions.LongRunning); Assert.IsTrue(task.Wait(timeout), failureMessage); } diff --git a/osu.Game.Tests/Beatmaps/TestSceneBeatmapDifficultyCache.cs b/osu.Game.Tests/Beatmaps/TestSceneBeatmapDifficultyCache.cs index 3a82cbc785..26ab8808b9 100644 --- a/osu.Game.Tests/Beatmaps/TestSceneBeatmapDifficultyCache.cs +++ b/osu.Game.Tests/Beatmaps/TestSceneBeatmapDifficultyCache.cs @@ -8,6 +8,7 @@ using System.Threading.Tasks; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions; using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Rulesets; @@ -32,7 +33,7 @@ namespace osu.Game.Tests.Beatmaps [BackgroundDependencyLoader] private void load(OsuGameBase osu) { - importedSet = ImportBeatmapTest.LoadQuickOszIntoOsu(osu).Result; + importedSet = ImportBeatmapTest.LoadQuickOszIntoOsu(osu).GetResultSafely(); } [SetUpSteps] diff --git a/osu.Game.Tests/Chat/MessageFormatterTests.cs b/osu.Game.Tests/Chat/MessageFormatterTests.cs index af87fc17ad..8def8005f1 100644 --- a/osu.Game.Tests/Chat/MessageFormatterTests.cs +++ b/osu.Game.Tests/Chat/MessageFormatterTests.cs @@ -9,6 +9,21 @@ namespace osu.Game.Tests.Chat [TestFixture] public class MessageFormatterTests { + private string originalWebsiteRootUrl; + + [OneTimeSetUp] + public void OneTimeSetUp() + { + originalWebsiteRootUrl = MessageFormatter.WebsiteRootUrl; + MessageFormatter.WebsiteRootUrl = "dev.ppy.sh"; + } + + [OneTimeTearDown] + public void OneTimeTearDown() + { + MessageFormatter.WebsiteRootUrl = originalWebsiteRootUrl; + } + [Test] public void TestBareLink() { @@ -32,8 +47,6 @@ namespace osu.Game.Tests.Chat [TestCase(LinkAction.External, "https://dev.ppy.sh/beatmapsets/discussions/123", "https://dev.ppy.sh/beatmapsets/discussions/123")] public void TestBeatmapLinks(LinkAction expectedAction, string expectedArg, string link) { - MessageFormatter.WebsiteRootUrl = "dev.ppy.sh"; - Message result = MessageFormatter.FormatMessage(new Message { Content = link }); Assert.AreEqual(result.Content, result.DisplayContent); @@ -47,7 +60,10 @@ namespace osu.Game.Tests.Chat [Test] public void TestMultipleComplexLinks() { - Message result = MessageFormatter.FormatMessage(new Message { Content = "This is a http://test.io/link#fragment. (see https://twitter.com). Also, This string should not be altered. http://example.com/" }); + Message result = MessageFormatter.FormatMessage(new Message + { + Content = "This is a http://test.io/link#fragment. (see https://twitter.com). Also, This string should not be altered. http://example.com/" + }); Assert.AreEqual(result.Content, result.DisplayContent); Assert.AreEqual(3, result.Links.Count); @@ -104,7 +120,7 @@ namespace osu.Game.Tests.Chat Assert.AreEqual("This is a Wiki Link.", result.DisplayContent); Assert.AreEqual(1, result.Links.Count); - Assert.AreEqual("https://osu.ppy.sh/wiki/Wiki Link", result.Links[0].Url); + Assert.AreEqual("https://dev.ppy.sh/wiki/Wiki Link", result.Links[0].Url); Assert.AreEqual(10, result.Links[0].Index); Assert.AreEqual(9, result.Links[0].Length); } @@ -117,15 +133,15 @@ namespace osu.Game.Tests.Chat Assert.AreEqual("This is a Wiki Link Wiki:LinkWiki.Link.", result.DisplayContent); Assert.AreEqual(3, result.Links.Count); - Assert.AreEqual("https://osu.ppy.sh/wiki/Wiki Link", result.Links[0].Url); + Assert.AreEqual("https://dev.ppy.sh/wiki/Wiki Link", result.Links[0].Url); Assert.AreEqual(10, result.Links[0].Index); Assert.AreEqual(9, result.Links[0].Length); - Assert.AreEqual("https://osu.ppy.sh/wiki/Wiki:Link", result.Links[1].Url); + Assert.AreEqual("https://dev.ppy.sh/wiki/Wiki:Link", result.Links[1].Url); Assert.AreEqual(20, result.Links[1].Index); Assert.AreEqual(9, result.Links[1].Length); - Assert.AreEqual("https://osu.ppy.sh/wiki/Wiki.Link", result.Links[2].Url); + Assert.AreEqual("https://dev.ppy.sh/wiki/Wiki.Link", result.Links[2].Url); Assert.AreEqual(29, result.Links[2].Index); Assert.AreEqual(9, result.Links[2].Length); } @@ -445,12 +461,15 @@ namespace osu.Game.Tests.Chat [Test] public void TestLinkComplex() { - Message result = MessageFormatter.FormatMessage(new Message { Content = "This is a [http://www.simple-test.com simple test] with some [traps] and [[wiki links]]. Don't forget to visit https://osu.ppy.sh (now!)[http://google.com]\uD83D\uDE12" }); + Message result = MessageFormatter.FormatMessage(new Message + { + Content = "This is a [http://www.simple-test.com simple test] with some [traps] and [[wiki links]]. Don't forget to visit https://osu.ppy.sh (now!)[http://google.com]\uD83D\uDE12" + }); Assert.AreEqual("This is a simple test with some [traps] and wiki links. Don't forget to visit https://osu.ppy.sh now!\0\0\0", result.DisplayContent); Assert.AreEqual(5, result.Links.Count); - Link f = result.Links.Find(l => l.Url == "https://osu.ppy.sh/wiki/wiki links"); + Link f = result.Links.Find(l => l.Url == "https://dev.ppy.sh/wiki/wiki links"); Assert.That(f, Is.Not.Null); Assert.AreEqual(44, f.Index); Assert.AreEqual(10, f.Length); @@ -514,8 +533,6 @@ namespace osu.Game.Tests.Chat [TestCase("https://dev.ppy.sh/home/changelog/lazer/2021.1012", "lazer/2021.1012")] public void TestChangelogLinks(string link, string expectedArg) { - MessageFormatter.WebsiteRootUrl = "dev.ppy.sh"; - LinkDetails result = MessageFormatter.GetLinkDetails(link); Assert.AreEqual(LinkAction.OpenChangelog, result.Action); diff --git a/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs b/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs index d4ec5e897b..53e4ef07e7 100644 --- a/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs +++ b/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs @@ -6,6 +6,7 @@ using System.IO; using System.Text; using System.Threading.Tasks; using NUnit.Framework; +using osu.Framework.Extensions; using osu.Framework.Platform; using osu.Framework.Testing; using osu.Game.Tests.Resources; @@ -179,7 +180,7 @@ namespace osu.Game.Tests.Collections.IO { // intentionally spin this up on a separate task to avoid disposal deadlocks. // see https://github.com/EventStore/EventStore/issues/1179 - await Task.Run(() => osu.CollectionManager.Import(stream).Wait()); + await Task.Factory.StartNew(() => osu.CollectionManager.Import(stream).WaitSafely(), TaskCreationOptions.LongRunning); } } } diff --git a/osu.Game.Tests/Database/RealmLiveTests.cs b/osu.Game.Tests/Database/RealmLiveTests.cs index 06cb5a3607..9432a56741 100644 --- a/osu.Game.Tests/Database/RealmLiveTests.cs +++ b/osu.Game.Tests/Database/RealmLiveTests.cs @@ -6,6 +6,7 @@ using System.Diagnostics; using System.Linq; using System.Threading.Tasks; using NUnit.Framework; +using osu.Framework.Extensions; using osu.Framework.Testing; using osu.Game.Database; using osu.Game.Models; @@ -104,7 +105,7 @@ namespace osu.Game.Tests.Database liveBeatmap = beatmap.ToLive(realmFactory); } - }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).Wait(); + }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).WaitSafely(); Debug.Assert(liveBeatmap != null); @@ -115,7 +116,7 @@ namespace osu.Game.Tests.Database Assert.IsTrue(beatmap.IsValid); Assert.IsFalse(beatmap.Hidden); }); - }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).Wait(); + }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).WaitSafely(); }); } @@ -133,7 +134,7 @@ namespace osu.Game.Tests.Database liveBeatmap = beatmap.ToLive(realmFactory); } - }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).Wait(); + }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).WaitSafely(); Debug.Assert(liveBeatmap != null); @@ -141,7 +142,7 @@ namespace osu.Game.Tests.Database { liveBeatmap.PerformWrite(beatmap => { beatmap.Hidden = true; }); liveBeatmap.PerformRead(beatmap => { Assert.IsTrue(beatmap.Hidden); }); - }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).Wait(); + }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).WaitSafely(); }); } @@ -175,7 +176,7 @@ namespace osu.Game.Tests.Database liveBeatmap = beatmap.ToLive(realmFactory); } - }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).Wait(); + }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).WaitSafely(); Debug.Assert(liveBeatmap != null); @@ -195,7 +196,7 @@ namespace osu.Game.Tests.Database var __ = liveBeatmap.Value; }); } - }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).Wait(); + }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).WaitSafely(); }); } @@ -213,7 +214,7 @@ namespace osu.Game.Tests.Database liveBeatmap = beatmap.ToLive(realmFactory); } - }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).Wait(); + }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).WaitSafely(); Debug.Assert(liveBeatmap != null); @@ -223,7 +224,7 @@ namespace osu.Game.Tests.Database { var unused = liveBeatmap.Value; }); - }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).Wait(); + }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).WaitSafely(); }); } @@ -252,7 +253,7 @@ namespace osu.Game.Tests.Database liveBeatmap = beatmap.ToLive(realmFactory); } - }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).Wait(); + }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).WaitSafely(); Debug.Assert(liveBeatmap != null); diff --git a/osu.Game.Tests/Editing/Checks/CheckMutedObjectsTest.cs b/osu.Game.Tests/Editing/Checks/CheckMutedObjectsTest.cs index 4ab6e5cef6..91d9a8753c 100644 --- a/osu.Game.Tests/Editing/Checks/CheckMutedObjectsTest.cs +++ b/osu.Game.Tests/Editing/Checks/CheckMutedObjectsTest.cs @@ -40,80 +40,80 @@ namespace osu.Game.Tests.Editing.Checks [Test] public void TestNormalControlPointVolume() { - var hitcircle = new HitCircle + var hitCircle = new HitCircle { StartTime = 0, Samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) } }; - hitcircle.ApplyDefaults(cpi, new BeatmapDifficulty()); + hitCircle.ApplyDefaults(cpi, new BeatmapDifficulty()); - assertOk(new List { hitcircle }); + assertOk(new List { hitCircle }); } [Test] public void TestLowControlPointVolume() { - var hitcircle = new HitCircle + var hitCircle = new HitCircle { StartTime = 1000, Samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) } }; - hitcircle.ApplyDefaults(cpi, new BeatmapDifficulty()); + hitCircle.ApplyDefaults(cpi, new BeatmapDifficulty()); - assertLowVolume(new List { hitcircle }); + assertLowVolume(new List { hitCircle }); } [Test] public void TestMutedControlPointVolume() { - var hitcircle = new HitCircle + var hitCircle = new HitCircle { StartTime = 2000, Samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) } }; - hitcircle.ApplyDefaults(cpi, new BeatmapDifficulty()); + hitCircle.ApplyDefaults(cpi, new BeatmapDifficulty()); - assertMuted(new List { hitcircle }); + assertMuted(new List { hitCircle }); } [Test] public void TestNormalSampleVolume() { // The sample volume should take precedence over the control point volume. - var hitcircle = new HitCircle + var hitCircle = new HitCircle { StartTime = 2000, Samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL, volume: volume_regular) } }; - hitcircle.ApplyDefaults(cpi, new BeatmapDifficulty()); + hitCircle.ApplyDefaults(cpi, new BeatmapDifficulty()); - assertOk(new List { hitcircle }); + assertOk(new List { hitCircle }); } [Test] public void TestLowSampleVolume() { - var hitcircle = new HitCircle + var hitCircle = new HitCircle { StartTime = 2000, Samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL, volume: volume_low) } }; - hitcircle.ApplyDefaults(cpi, new BeatmapDifficulty()); + hitCircle.ApplyDefaults(cpi, new BeatmapDifficulty()); - assertLowVolume(new List { hitcircle }); + assertLowVolume(new List { hitCircle }); } [Test] public void TestMutedSampleVolume() { - var hitcircle = new HitCircle + var hitCircle = new HitCircle { StartTime = 0, Samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL, volume: volume_muted) } }; - hitcircle.ApplyDefaults(cpi, new BeatmapDifficulty()); + hitCircle.ApplyDefaults(cpi, new BeatmapDifficulty()); - assertMuted(new List { hitcircle }); + assertMuted(new List { hitCircle }); } [Test] diff --git a/osu.Game.Tests/ImportTest.cs b/osu.Game.Tests/ImportTest.cs index dbeb453d4d..a658a0eaeb 100644 --- a/osu.Game.Tests/ImportTest.cs +++ b/osu.Game.Tests/ImportTest.cs @@ -6,6 +6,7 @@ using System.Threading; using System.Threading.Tasks; using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Extensions; using osu.Framework.Platform; using osu.Game.Collections; using osu.Game.Tests.Resources; @@ -58,7 +59,7 @@ namespace osu.Game.Tests { // Beatmap must be imported before the collection manager is loaded. if (withBeatmap) - BeatmapManager.Import(TestResources.GetTestBeatmapForImport()).Wait(); + BeatmapManager.Import(TestResources.GetTestBeatmapForImport()).WaitSafely(); AddInternal(CollectionManager = new CollectionManager(Storage)); } diff --git a/osu.Game.Tests/Mods/ModSettingsEqualityComparison.cs b/osu.Game.Tests/Mods/ModSettingsEqualityComparison.cs index ce6b3a68a5..cd6879cf01 100644 --- a/osu.Game.Tests/Mods/ModSettingsEqualityComparison.cs +++ b/osu.Game.Tests/Mods/ModSettingsEqualityComparison.cs @@ -34,20 +34,20 @@ namespace osu.Game.Tests.Mods var mod3 = new OsuModDoubleTime { SpeedChange = { Value = 1.26 } }; var doubleConvertedMod1 = new APIMod(mod1).ToMod(ruleset); - var doulbeConvertedMod2 = new APIMod(mod2).ToMod(ruleset); - var doulbeConvertedMod3 = new APIMod(mod3).ToMod(ruleset); + var doubleConvertedMod2 = new APIMod(mod2).ToMod(ruleset); + var doubleConvertedMod3 = new APIMod(mod3).ToMod(ruleset); Assert.That(mod1, Is.Not.EqualTo(mod2)); - Assert.That(doubleConvertedMod1, Is.Not.EqualTo(doulbeConvertedMod2)); + Assert.That(doubleConvertedMod1, Is.Not.EqualTo(doubleConvertedMod2)); Assert.That(mod2, Is.EqualTo(mod2)); - Assert.That(doulbeConvertedMod2, Is.EqualTo(doulbeConvertedMod2)); + Assert.That(doubleConvertedMod2, Is.EqualTo(doubleConvertedMod2)); Assert.That(mod2, Is.EqualTo(mod3)); - Assert.That(doulbeConvertedMod2, Is.EqualTo(doulbeConvertedMod3)); + Assert.That(doubleConvertedMod2, Is.EqualTo(doubleConvertedMod3)); Assert.That(mod3, Is.EqualTo(mod2)); - Assert.That(doulbeConvertedMod3, Is.EqualTo(doulbeConvertedMod2)); + Assert.That(doubleConvertedMod3, Is.EqualTo(doubleConvertedMod2)); } } } diff --git a/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs b/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs index 55378043e6..8ba3d1a6c7 100644 --- a/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs +++ b/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs @@ -16,7 +16,8 @@ namespace osu.Game.Tests.NonVisual.Filtering { private BeatmapInfo getExampleBeatmap() => new BeatmapInfo { - Ruleset = new RulesetInfo { OnlineID = 5 }, + Ruleset = new RulesetInfo { OnlineID = 0 }, + RulesetID = 0, StarRating = 4.0d, BaseDifficulty = new BeatmapDifficulty { diff --git a/osu.Game.Tests/NonVisual/TaskChainTest.cs b/osu.Game.Tests/NonVisual/TaskChainTest.cs index d83eaafe20..3678279035 100644 --- a/osu.Game.Tests/NonVisual/TaskChainTest.cs +++ b/osu.Game.Tests/NonVisual/TaskChainTest.cs @@ -4,6 +4,7 @@ using System.Threading; using System.Threading.Tasks; using NUnit.Framework; +using osu.Framework.Extensions; using osu.Game.Utils; namespace osu.Game.Tests.NonVisual @@ -42,9 +43,9 @@ namespace osu.Game.Tests.NonVisual await Task.WhenAll(task1.task, task2.task, task3.task); - Assert.That(task1.task.Result, Is.EqualTo(1)); - Assert.That(task2.task.Result, Is.EqualTo(2)); - Assert.That(task3.task.Result, Is.EqualTo(3)); + Assert.That(task1.task.GetResultSafely(), Is.EqualTo(1)); + Assert.That(task2.task.GetResultSafely(), Is.EqualTo(2)); + Assert.That(task3.task.GetResultSafely(), Is.EqualTo(3)); } [Test] @@ -68,9 +69,9 @@ namespace osu.Game.Tests.NonVisual // Wait on both tasks. await Task.WhenAll(task1.task, task3.task); - Assert.That(task1.task.Result, Is.EqualTo(1)); + Assert.That(task1.task.GetResultSafely(), Is.EqualTo(1)); Assert.That(task2.task.IsCompleted, Is.False); - Assert.That(task3.task.Result, Is.EqualTo(2)); + Assert.That(task3.task.GetResultSafely(), Is.EqualTo(2)); } [Test] diff --git a/osu.Game.Tests/Online/TestMultiplayerMessagePackSerialization.cs b/osu.Game.Tests/Online/TestMultiplayerMessagePackSerialization.cs index 1027b722d1..81475f2fbe 100644 --- a/osu.Game.Tests/Online/TestMultiplayerMessagePackSerialization.cs +++ b/osu.Game.Tests/Online/TestMultiplayerMessagePackSerialization.cs @@ -39,7 +39,7 @@ namespace osu.Game.Tests.Online } [Test] - public void TestSerialiseUnionFailsWithSingalR() + public void TestSerialiseUnionFailsWithSignalR() { var state = new TeamVersusUserState(); diff --git a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs index 239c787349..a7b431fb6e 100644 --- a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs +++ b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs @@ -100,7 +100,7 @@ namespace osu.Game.Tests.Online public void TestTrackerRespectsSoftDeleting() { AddStep("allow importing", () => beatmaps.AllowImport.SetResult(true)); - AddStep("import beatmap", () => beatmaps.Import(testBeatmapFile).Wait()); + AddStep("import beatmap", () => beatmaps.Import(testBeatmapFile).WaitSafely()); addAvailabilityCheckStep("state locally available", BeatmapAvailability.LocallyAvailable); AddStep("delete beatmap", () => beatmaps.Delete(beatmaps.QueryBeatmapSet(b => b.OnlineID == testBeatmapSet.OnlineID))); @@ -114,12 +114,12 @@ namespace osu.Game.Tests.Online public void TestTrackerRespectsChecksum() { AddStep("allow importing", () => beatmaps.AllowImport.SetResult(true)); - AddStep("import beatmap", () => beatmaps.Import(testBeatmapFile).Wait()); + AddStep("import beatmap", () => beatmaps.Import(testBeatmapFile).WaitSafely()); addAvailabilityCheckStep("initially locally available", BeatmapAvailability.LocallyAvailable); AddStep("import altered beatmap", () => { - beatmaps.Import(TestResources.GetTestBeatmapForImport(true)).Wait(); + beatmaps.Import(TestResources.GetTestBeatmapForImport(true)).WaitSafely(); }); addAvailabilityCheckStep("state not downloaded", BeatmapAvailability.NotDownloaded); @@ -129,7 +129,7 @@ namespace osu.Game.Tests.Online }); addAvailabilityCheckStep("state not downloaded as well", BeatmapAvailability.NotDownloaded); - AddStep("reimport original beatmap", () => beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).Wait()); + AddStep("reimport original beatmap", () => beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely()); addAvailabilityCheckStep("locally available after re-import", BeatmapAvailability.LocallyAvailable); } diff --git a/osu.Game.Tests/Skins/IO/ImportSkinTest.cs b/osu.Game.Tests/Skins/IO/ImportSkinTest.cs index 40d2455106..3f063264e0 100644 --- a/osu.Game.Tests/Skins/IO/ImportSkinTest.cs +++ b/osu.Game.Tests/Skins/IO/ImportSkinTest.cs @@ -8,6 +8,7 @@ using System.Runtime.CompilerServices; using System.Threading.Tasks; using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Extensions; using osu.Framework.Platform; using osu.Game.Database; using osu.Game.IO; @@ -187,7 +188,7 @@ namespace osu.Game.Tests.Skins.IO var imported = skinManager.Import(new ImportTask(exportStream, "exported.osk")); - imported.Result.PerformRead(s => + imported.GetResultSafely().PerformRead(s => { Assert.IsFalse(s.Protected); Assert.AreNotEqual(originalSkinId, s.ID); @@ -222,7 +223,7 @@ namespace osu.Game.Tests.Skins.IO var imported = skinManager.Import(new ImportTask(exportStream, "exported.osk")); - imported.Result.PerformRead(s => + imported.GetResultSafely().PerformRead(s => { Assert.IsFalse(s.Protected); Assert.AreNotEqual(originalSkinId, s.ID); diff --git a/osu.Game.Tests/Skins/TestSceneBeatmapSkinResources.cs b/osu.Game.Tests/Skins/TestSceneBeatmapSkinResources.cs index 1d8b754837..c20ab84a68 100644 --- a/osu.Game.Tests/Skins/TestSceneBeatmapSkinResources.cs +++ b/osu.Game.Tests/Skins/TestSceneBeatmapSkinResources.cs @@ -4,6 +4,7 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio.Track; +using osu.Framework.Extensions; using osu.Framework.Testing; using osu.Game.Audio; using osu.Game.Beatmaps; @@ -24,7 +25,7 @@ namespace osu.Game.Tests.Skins [BackgroundDependencyLoader] private void load() { - var imported = beatmaps.Import(new ZipArchiveReader(TestResources.OpenResource("Archives/ogg-beatmap.osz"))).Result; + var imported = beatmaps.Import(new ZipArchiveReader(TestResources.OpenResource("Archives/ogg-beatmap.osz"))).GetResultSafely(); beatmap = beatmaps.GetWorkingBeatmap(imported.Value.Beatmaps[0]); beatmap.LoadTrack(); } diff --git a/osu.Game.Tests/Skins/TestSceneSkinResources.cs b/osu.Game.Tests/Skins/TestSceneSkinResources.cs index 09535b76e3..0271198049 100644 --- a/osu.Game.Tests/Skins/TestSceneSkinResources.cs +++ b/osu.Game.Tests/Skins/TestSceneSkinResources.cs @@ -3,6 +3,7 @@ using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Extensions; using osu.Framework.Testing; using osu.Game.Audio; using osu.Game.IO.Archives; @@ -23,7 +24,7 @@ namespace osu.Game.Tests.Skins [BackgroundDependencyLoader] private void load() { - var imported = skins.Import(new ZipArchiveReader(TestResources.OpenResource("Archives/ogg-skin.osk"))).Result; + var imported = skins.Import(new ZipArchiveReader(TestResources.OpenResource("Archives/ogg-skin.osk"))).GetResultSafely(); skin = imported.PerformRead(skinInfo => skins.GetSkin(skinInfo)); } diff --git a/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs b/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs index 33b1d9a67d..b1f642b909 100644 --- a/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs +++ b/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs @@ -7,12 +7,14 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Bindables; +using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; using osu.Framework.Input.States; using osu.Framework.Platform; using osu.Framework.Screens; +using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Graphics; @@ -49,7 +51,7 @@ namespace osu.Game.Tests.Visual.Background Dependencies.Cache(manager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, Resources, host, Beatmap.Default)); Dependencies.Cache(new OsuConfigManager(LocalStorage)); - manager.Import(TestResources.GetQuickTestBeatmapForImport()).Wait(); + manager.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely(); Beatmap.SetDefault(); } @@ -322,7 +324,7 @@ namespace osu.Game.Tests.Visual.Background public bool IsBackgroundUndimmed() => background.CurrentColour == Color4.White; - public bool IsUserBlurApplied() => background.CurrentBlur == new Vector2((float)BlurLevel.Value * BackgroundScreenBeatmap.USER_BLUR_FACTOR); + public bool IsUserBlurApplied() => Precision.AlmostEquals(background.CurrentBlur, new Vector2((float)BlurLevel.Value * BackgroundScreenBeatmap.USER_BLUR_FACTOR), 0.1f); public bool IsUserBlurDisabled() => background.CurrentBlur == new Vector2(0); @@ -330,9 +332,9 @@ namespace osu.Game.Tests.Visual.Background public bool IsBackgroundVisible() => background.CurrentAlpha == 1; - public bool IsBackgroundBlur() => background.CurrentBlur == new Vector2(BACKGROUND_BLUR); + public bool IsBackgroundBlur() => Precision.AlmostEquals(background.CurrentBlur, new Vector2(BACKGROUND_BLUR), 0.1f); - public bool CheckBackgroundBlur(Vector2 expected) => background.CurrentBlur == expected; + public bool CheckBackgroundBlur(Vector2 expected) => Precision.AlmostEquals(background.CurrentBlur, expected, 0.1f); /// /// Make sure every time a screen gets pushed, the background doesn't get replaced diff --git a/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs b/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs index 28218ea220..d2b0f7324b 100644 --- a/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs +++ b/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs @@ -4,6 +4,7 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.UserInterface; @@ -38,7 +39,7 @@ namespace osu.Game.Tests.Visual.Collections Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); Dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, Audio, Resources, host, Beatmap.Default)); - beatmapManager.Import(TestResources.GetQuickTestBeatmapForImport()).Wait(); + beatmapManager.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely(); base.Content.AddRange(new Drawable[] { diff --git a/osu.Game.Tests/Visual/Editing/TestSceneDifficultySwitching.cs b/osu.Game.Tests/Visual/Editing/TestSceneDifficultySwitching.cs index c23db5e440..516305079b 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneDifficultySwitching.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneDifficultySwitching.cs @@ -5,6 +5,7 @@ using System; using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Extensions; using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Overlays.Dialog; @@ -32,7 +33,7 @@ namespace osu.Game.Tests.Visual.Editing public override void SetUpSteps() { - AddStep("import test beatmap", () => importedBeatmapSet = ImportBeatmapTest.LoadOszIntoOsu(game, virtualTrack: true).Result); + AddStep("import test beatmap", () => importedBeatmapSet = ImportBeatmapTest.LoadOszIntoOsu(game, virtualTrack: true).GetResultSafely()); base.SetUpSteps(); } diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs index 50794f15ed..6d48ef3ba7 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs @@ -5,6 +5,7 @@ using System; using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Extensions; using osu.Framework.Screens; using osu.Framework.Testing; using osu.Game.Beatmaps; @@ -38,7 +39,7 @@ namespace osu.Game.Tests.Visual.Editing public override void SetUpSteps() { - AddStep("import test beatmap", () => importedBeatmapSet = ImportBeatmapTest.LoadOszIntoOsu(game).Result); + AddStep("import test beatmap", () => importedBeatmapSet = ImportBeatmapTest.LoadOszIntoOsu(game).GetResultSafely()); base.SetUpSteps(); } diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs index 324a132120..cf5aadde6d 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs @@ -3,6 +3,7 @@ using System; using System.Linq; +using System.Threading; using System.Threading.Tasks; using NUnit.Framework; using osu.Framework.Screens; @@ -36,7 +37,9 @@ namespace osu.Game.Tests.Visual.Gameplay protected override bool HasCustomSteps => true; - protected override TestPlayer CreatePlayer(Ruleset ruleset) => new NonImportingPlayer(false); + protected override TestPlayer CreatePlayer(Ruleset ruleset) => new FakeImportingPlayer(false); + + protected new FakeImportingPlayer Player => (FakeImportingPlayer)base.Player; protected override Ruleset CreatePlayerRuleset() => createCustomRuleset?.Invoke() ?? new OsuRuleset(); @@ -54,7 +57,7 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestNoSubmissionOnResultsWithNoToken() { - prepareTokenResponse(false); + prepareTestAPI(false); createPlayerTest(); @@ -74,7 +77,7 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestSubmissionOnResults() { - prepareTokenResponse(true); + prepareTestAPI(true); createPlayerTest(); @@ -93,7 +96,7 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestSubmissionForDifferentRuleset() { - prepareTokenResponse(true); + prepareTestAPI(true); createPlayerTest(createRuleset: () => new TaikoRuleset()); @@ -113,7 +116,7 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestSubmissionForConvertedBeatmap() { - prepareTokenResponse(true); + prepareTestAPI(true); createPlayerTest(createRuleset: () => new ManiaRuleset(), createBeatmap: _ => createTestBeatmap(new OsuRuleset().RulesetInfo)); @@ -133,7 +136,7 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestNoSubmissionOnExitWithNoToken() { - prepareTokenResponse(false); + prepareTestAPI(false); createPlayerTest(); @@ -150,7 +153,7 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestNoSubmissionOnEmptyFail() { - prepareTokenResponse(true); + prepareTestAPI(true); createPlayerTest(true); @@ -165,7 +168,7 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestSubmissionOnFail() { - prepareTokenResponse(true); + prepareTestAPI(true); createPlayerTest(true); @@ -182,7 +185,7 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestNoSubmissionOnEmptyExit() { - prepareTokenResponse(true); + prepareTestAPI(true); createPlayerTest(); @@ -195,7 +198,7 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestSubmissionOnExit() { - prepareTokenResponse(true); + prepareTestAPI(true); createPlayerTest(); @@ -207,10 +210,29 @@ namespace osu.Game.Tests.Visual.Gameplay AddAssert("ensure failing submission", () => Player.SubmittedScore?.ScoreInfo.Passed == false); } + [Test] + public void TestSubmissionOnExitDuringImport() + { + prepareTestAPI(true); + + createPlayerTest(); + AddStep("block imports", () => Player.AllowImportCompletion.Wait()); + + AddUntilStep("wait for token request", () => Player.TokenCreationRequested); + + addFakeHit(); + + AddUntilStep("wait for import to start", () => Player.ScoreImportStarted); + + AddStep("exit", () => Player.Exit()); + AddStep("allow import to proceed", () => Player.AllowImportCompletion.Release(1)); + AddUntilStep("ensure submission", () => Player.SubmittedScore != null && Player.ImportedScore != null); + } + [Test] public void TestNoSubmissionOnLocalBeatmap() { - prepareTokenResponse(true); + prepareTestAPI(true); createPlayerTest(false, r => { @@ -231,7 +253,7 @@ namespace osu.Game.Tests.Visual.Gameplay [TestCase(10)] public void TestNoSubmissionOnCustomRuleset(int? rulesetId) { - prepareTokenResponse(true); + prepareTestAPI(true); createPlayerTest(false, createRuleset: () => new OsuRuleset { RulesetInfo = { OnlineID = rulesetId ?? -1 } }); @@ -253,7 +275,7 @@ namespace osu.Game.Tests.Visual.Gameplay })); } - private void prepareTokenResponse(bool validToken) + private void prepareTestAPI(bool validToken) { AddStep("Prepare test API", () => { @@ -267,6 +289,31 @@ namespace osu.Game.Tests.Visual.Gameplay else tokenRequest.TriggerFailure(new APIException("something went wrong!", null)); return true; + + case SubmitSoloScoreRequest submissionRequest: + if (validToken) + { + var requestScore = submissionRequest.Score; + + submissionRequest.TriggerSuccess(new MultiplayerScore + { + ID = 1234, + User = dummyAPI.LocalUser.Value, + Rank = requestScore.Rank, + TotalScore = requestScore.TotalScore, + Accuracy = requestScore.Accuracy, + MaxCombo = requestScore.MaxCombo, + Mods = requestScore.Mods, + Statistics = requestScore.Statistics, + Passed = requestScore.Passed, + EndedAt = DateTimeOffset.Now, + Position = 1 + }); + + return true; + } + + break; } return false; @@ -288,15 +335,26 @@ namespace osu.Game.Tests.Visual.Gameplay }); } - private class NonImportingPlayer : TestPlayer + protected class FakeImportingPlayer : TestPlayer { - public NonImportingPlayer(bool allowPause = true, bool showResults = true, bool pauseOnFocusLost = false) + public bool ScoreImportStarted { get; set; } + public SemaphoreSlim AllowImportCompletion { get; } + public Score ImportedScore { get; private set; } + + public FakeImportingPlayer(bool allowPause = true, bool showResults = true, bool pauseOnFocusLost = false) : base(allowPause, showResults, pauseOnFocusLost) { + AllowImportCompletion = new SemaphoreSlim(1); } - protected override Task ImportScore(Score score) + protected override async Task ImportScore(Score score) { + ScoreImportStarted = true; + + await AllowImportCompletion.WaitAsync().ConfigureAwait(false); + + ImportedScore = score; + // It was discovered that Score members could sometimes be half-populated. // In particular, the RulesetID property could be set to 0 even on non-osu! maps. // We want to test that the state of that property is consistent in this test. @@ -311,8 +369,7 @@ namespace osu.Game.Tests.Visual.Gameplay // In the above instance, if a ScoreInfo with Ruleset = {mania} and RulesetID = 0 is attached to an EF context, // RulesetID WILL BE SILENTLY SET TO THE CORRECT VALUE of 3. // - // For the above reasons, importing is disabled in this test. - return Task.CompletedTask; + // For the above reasons, actual importing is disabled in this test. } } } diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs index 42c4f89e9d..3168c4b94e 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs @@ -9,6 +9,7 @@ using osu.Game.Online.API.Requests.Responses; using osu.Game.Scoring; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions; using osu.Framework.Testing; using osu.Game.Database; using osu.Game.Graphics.UserInterface; @@ -135,7 +136,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddUntilStep("state is not downloaded", () => downloadButton.State.Value == DownloadState.NotDownloaded); - AddStep("import score", () => imported = scoreManager.Import(getScoreInfo(true)).Result); + AddStep("import score", () => imported = scoreManager.Import(getScoreInfo(true)).GetResultSafely()); AddUntilStep("state is available", () => downloadButton.State.Value == DownloadState.LocallyAvailable); diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs index 9fadbe02bd..242eca0bbc 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs @@ -4,6 +4,7 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Screens; using osu.Framework.Testing; @@ -60,7 +61,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("import beatmap", () => { - importedBeatmap = ImportBeatmapTest.LoadOszIntoOsu(game, virtualTrack: true).Result; + importedBeatmap = ImportBeatmapTest.LoadOszIntoOsu(game, virtualTrack: true).GetResultSafely(); importedBeatmapId = importedBeatmap.Beatmaps.First(b => b.RulesetID == 0).OnlineID ?? -1; }); } diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs index 48a97d54f7..69798dcb82 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs @@ -53,8 +53,8 @@ namespace osu.Game.Tests.Visual.Gameplay CreateTest(null); AddUntilStep("completion set by processor", () => Player.ScoreProcessor.HasCompleted.Value); AddStep("skip outro", () => InputManager.Key(osuTK.Input.Key.Space)); + AddAssert("player is no longer current screen", () => !Player.IsCurrentScreen()); AddUntilStep("wait for score shown", () => Player.IsScoreShown); - AddUntilStep("time less than storyboard duration", () => Player.GameplayClockContainer.GameplayClock.CurrentTime < currentStoryboardDuration); } [Test] diff --git a/osu.Game.Tests/Visual/Menus/TestSceneMusicActionHandling.cs b/osu.Game.Tests/Visual/Menus/TestSceneMusicActionHandling.cs index 55e453c3d3..ee9363fa12 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneMusicActionHandling.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneMusicActionHandling.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Linq; using NUnit.Framework; +using osu.Framework.Extensions; using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Database; @@ -33,11 +34,11 @@ namespace osu.Game.Tests.Visual.Menus Queue<(IWorkingBeatmap working, TrackChangeDirection changeDirection)> trackChangeQueue = null; // ensure we have at least two beatmaps available to identify the direction the music controller navigated to. - AddRepeatStep("import beatmap", () => Game.BeatmapManager.Import(TestResources.CreateTestBeatmapSetInfo()).Wait(), 5); + AddRepeatStep("import beatmap", () => Game.BeatmapManager.Import(TestResources.CreateTestBeatmapSetInfo()).WaitSafely(), 5); AddStep("import beatmap with track", () => { - var setWithTrack = Game.BeatmapManager.Import(new ImportTask(TestResources.GetTestBeatmapForImport())).Result; + var setWithTrack = Game.BeatmapManager.Import(new ImportTask(TestResources.GetTestBeatmapForImport())).GetResultSafely(); Beatmap.Value = Game.BeatmapManager.GetWorkingBeatmap(setWithTrack.Value.Beatmaps.First()); }); diff --git a/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs b/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs index 88c54eb2bb..c4d7bd7e6a 100644 --- a/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs +++ b/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs @@ -5,6 +5,7 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; +using osu.Framework.Extensions; using osu.Framework.Platform; using osu.Framework.Screens; using osu.Framework.Testing; @@ -56,7 +57,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("import beatmap", () => { - beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).Wait(); + beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely(); importedSet = beatmaps.GetAllUsableBeatmapSetsEnumerable(IncludedDetails.All).First(); InitialBeatmap = importedSet.Beatmaps.First(b => b.RulesetID == 0); OtherBeatmap = importedSet.Beatmaps.Last(b => b.RulesetID == 0); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneAllPlayersQueueMode.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneAllPlayersQueueMode.cs index a5744f9986..ad60ac824d 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneAllPlayersQueueMode.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneAllPlayersQueueMode.cs @@ -1,13 +1,25 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +#nullable enable + using System; +using System.Collections.Generic; using System.Linq; using NUnit.Framework; +using osu.Framework.Extensions.ObjectExtensions; +using osu.Framework.Screens; using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Online.Multiplayer; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Catch; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Osu.Mods; using osu.Game.Screens.OnlinePlay.Multiplayer; +using osu.Game.Screens.OnlinePlay.Multiplayer.Match; +using osu.Game.Screens.Play; using osuTK.Input; namespace osu.Game.Tests.Visual.Multiplayer @@ -83,16 +95,60 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("selected beatmap is initial beatmap", () => Beatmap.Value.BeatmapInfo.OnlineID == InitialBeatmap.OnlineID); } - private void addItem(Func beatmap) + [Test] + public void TestCorrectRulesetSelectedAfterNewItemAdded() { + addItem(() => OtherBeatmap, new CatchRuleset().RulesetInfo); + AddUntilStep("selected beatmap is initial beatmap", () => Beatmap.Value.BeatmapInfo.OnlineID == InitialBeatmap.OnlineID); + + AddUntilStep("wait for idle", () => Client.LocalUser?.State == MultiplayerUserState.Idle); + ClickButtonWhenEnabled(); + + AddUntilStep("wait for ready", () => Client.LocalUser?.State == MultiplayerUserState.Ready); + ClickButtonWhenEnabled(); + + AddUntilStep("wait for player", () => CurrentScreen is Player player && player.IsLoaded); + AddAssert("ruleset is correct", () => ((Player)CurrentScreen).Ruleset.Value.Equals(new OsuRuleset().RulesetInfo)); + AddStep("exit player", () => CurrentScreen.Exit()); + } + + [Test] + public void TestCorrectModsSelectedAfterNewItemAdded() + { + addItem(() => OtherBeatmap, mods: new Mod[] { new OsuModDoubleTime() }); + AddUntilStep("selected beatmap is initial beatmap", () => Beatmap.Value.BeatmapInfo.OnlineID == InitialBeatmap.OnlineID); + + AddUntilStep("wait for idle", () => Client.LocalUser?.State == MultiplayerUserState.Idle); + ClickButtonWhenEnabled(); + + AddUntilStep("wait for ready", () => Client.LocalUser?.State == MultiplayerUserState.Ready); + ClickButtonWhenEnabled(); + + AddUntilStep("wait for player", () => CurrentScreen is Player player && player.IsLoaded); + AddAssert("mods are correct", () => !((Player)CurrentScreen).Mods.Value.Any()); + AddStep("exit player", () => CurrentScreen.Exit()); + } + + private void addItem(Func beatmap, RulesetInfo? ruleset = null, IReadOnlyList? mods = null) + { + Screens.Select.SongSelect? songSelect = null; + AddStep("click add button", () => { InputManager.MoveMouseTo(this.ChildrenOfType().Single()); InputManager.Click(MouseButton.Left); }); - AddUntilStep("wait for song select", () => CurrentSubScreen is Screens.Select.SongSelect select && select.BeatmapSetsLoaded); - AddStep("select other beatmap", () => ((Screens.Select.SongSelect)CurrentSubScreen).FinaliseSelection(beatmap())); + AddUntilStep("wait for song select", () => (songSelect = CurrentSubScreen as Screens.Select.SongSelect) != null); + AddUntilStep("wait for loaded", () => songSelect.AsNonNull().BeatmapSetsLoaded); + + if (ruleset != null) + AddStep($"set {ruleset.Name} ruleset", () => songSelect.AsNonNull().Ruleset.Value = ruleset); + + if (mods != null) + AddStep($"set mods to {string.Join(",", mods.Select(m => m.Acronym))}", () => songSelect.AsNonNull().Mods.Value = mods); + + AddStep("select other beatmap", () => songSelect.AsNonNull().FinaliseSelection(beatmap())); AddUntilStep("wait for return to match", () => CurrentSubScreen is MultiplayerMatchSubScreen); } } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs index f9784384fd..147bbf2626 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs @@ -8,6 +8,7 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; +using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Platform; @@ -153,7 +154,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { var beatmap = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo; - AddStep("import beatmap", () => manager.Import(beatmap.BeatmapSet).Wait()); + AddStep("import beatmap", () => manager.Import(beatmap.BeatmapSet).WaitSafely()); createPlaylistWithBeatmaps(beatmap); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs index a61e505970..543e6a91d0 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs @@ -50,6 +50,7 @@ namespace osu.Game.Tests.Visual.Multiplayer }); AddUntilStep("wait for load", () => leaderboard.IsLoaded); + AddUntilStep("wait for user population", () => leaderboard.ChildrenOfType().Count() == 2); AddStep("add clock sources", () => { diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs index b10856b704..61058bc87a 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; using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -46,7 +47,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [BackgroundDependencyLoader] private void load() { - importedSet = ImportBeatmapTest.LoadOszIntoOsu(game, virtualTrack: true).Result; + importedSet = ImportBeatmapTest.LoadOszIntoOsu(game, virtualTrack: true).GetResultSafely(); importedBeatmap = importedSet.Beatmaps.First(b => b.RulesetID == 0); importedBeatmapId = importedBeatmap.OnlineID ?? -1; } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs index 4eebda94e9..3d8c5298dc 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs @@ -7,6 +7,8 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; +using osu.Framework.Extensions; +using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input; @@ -22,6 +24,8 @@ using osu.Game.Online.Multiplayer; using osu.Game.Online.Rooms; using osu.Game.Overlays.Mods; using osu.Game.Rulesets; +using osu.Game.Rulesets.Catch; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Screens.OnlinePlay.Components; @@ -67,7 +71,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("import beatmap", () => { - beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).Wait(); + beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely(); importedSet = beatmaps.GetAllUsableBeatmapSetsEnumerable(IncludedDetails.All).First(); }); @@ -438,6 +442,84 @@ namespace osu.Game.Tests.Visual.Multiplayer AddAssert("Beatmap matches current item", () => Beatmap.Value.BeatmapInfo.OnlineID == client.Room?.Playlist.First().BeatmapID); } + [Test] + public void TestPlayStartsWithCorrectRulesetWhileAtSongSelect() + { + createRoom(() => new Room + { + Name = { Value = "Test Room" }, + Playlist = + { + new PlaylistItem + { + Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo }, + Ruleset = { Value = new OsuRuleset().RulesetInfo }, + } + } + }); + + pressReadyButton(); + + AddStep("Enter song select", () => + { + var currentSubScreen = ((Screens.OnlinePlay.Multiplayer.Multiplayer)multiplayerComponents.CurrentScreen).CurrentSubScreen; + ((MultiplayerMatchSubScreen)currentSubScreen).OpenSongSelection(client.Room?.Settings.PlaylistItemId); + }); + + AddUntilStep("wait for song select", () => this.ChildrenOfType().FirstOrDefault()?.BeatmapSetsLoaded == true); + + AddAssert("Ruleset matches current item", () => Ruleset.Value.OnlineID == client.Room?.Playlist.First().RulesetID); + + AddStep("Switch ruleset", () => ((MultiplayerMatchSongSelect)multiplayerComponents.MultiplayerScreen.CurrentSubScreen).Ruleset.Value = new CatchRuleset().RulesetInfo); + + AddUntilStep("Ruleset doesn't match current item", () => Ruleset.Value.OnlineID != client.Room?.Playlist.First().RulesetID); + + AddStep("start match externally", () => client.StartMatch()); + + AddUntilStep("play started", () => multiplayerComponents.CurrentScreen is Player); + + AddAssert("Ruleset matches current item", () => Ruleset.Value.OnlineID == client.Room?.Playlist.First().RulesetID); + } + + [Test] + public void TestPlayStartsWithCorrectModsWhileAtSongSelect() + { + createRoom(() => new Room + { + Name = { Value = "Test Room" }, + Playlist = + { + new PlaylistItem + { + Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo }, + Ruleset = { Value = new OsuRuleset().RulesetInfo }, + } + } + }); + + pressReadyButton(); + + AddStep("Enter song select", () => + { + var currentSubScreen = ((Screens.OnlinePlay.Multiplayer.Multiplayer)multiplayerComponents.CurrentScreen).CurrentSubScreen; + ((MultiplayerMatchSubScreen)currentSubScreen).OpenSongSelection(client.Room?.Settings.PlaylistItemId); + }); + + AddUntilStep("wait for song select", () => this.ChildrenOfType().FirstOrDefault()?.BeatmapSetsLoaded == true); + + AddAssert("Mods match current item", () => SelectedMods.Value.Select(m => m.Acronym).SequenceEqual(client.Room.AsNonNull().Playlist.First().RequiredMods.Select(m => m.Acronym))); + + AddStep("Switch required mods", () => ((MultiplayerMatchSongSelect)multiplayerComponents.MultiplayerScreen.CurrentSubScreen).Mods.Value = new Mod[] { new OsuModDoubleTime() }); + + AddAssert("Mods don't match current item", () => !SelectedMods.Value.Select(m => m.Acronym).SequenceEqual(client.Room.AsNonNull().Playlist.First().RequiredMods.Select(m => m.Acronym))); + + AddStep("start match externally", () => client.StartMatch()); + + AddUntilStep("play started", () => multiplayerComponents.CurrentScreen is Player); + + AddAssert("Mods match current item", () => SelectedMods.Value.Select(m => m.Acronym).SequenceEqual(client.Room.AsNonNull().Playlist.First().RequiredMods.Select(m => m.Acronym))); + } + [Test] public void TestLocalPlayDoesNotStartWhileSpectatingWithNoBeatmap() { @@ -505,7 +587,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("restore beatmap", () => { - beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).Wait(); + beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely(); importedSet = beatmaps.GetAllUsableBeatmapSetsEnumerable(IncludedDetails.All).First(); }); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs index 25200560e4..07a8ef66e1 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Extensions; using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Utils; @@ -43,7 +44,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { base.SetUpSteps(); - AddStep("set local user", () => ((DummyAPIAccess)API).LocalUser.Value = LookupCache.GetUserAsync(1).Result); + AddStep("set local user", () => ((DummyAPIAccess)API).LocalUser.Value = LookupCache.GetUserAsync(1).GetResultSafely()); AddStep("create leaderboard", () => { @@ -90,7 +91,7 @@ namespace osu.Game.Tests.Visual.Multiplayer public void TestUserQuit() { foreach (int user in users) - AddStep($"mark user {user} quit", () => Client.RemoveUser(LookupCache.GetUserAsync(user).Result.AsNonNull())); + AddStep($"mark user {user} quit", () => Client.RemoveUser(LookupCache.GetUserAsync(user).GetResultSafely().AsNonNull())); } [Test] diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboardTeams.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboardTeams.cs index 16a342df8c..1237a21e94 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboardTeams.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboardTeams.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Linq; using NUnit.Framework; +using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Utils; using osu.Game.Online.API; @@ -47,7 +48,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { base.SetUpSteps(); - AddStep("set local user", () => ((DummyAPIAccess)API).LocalUser.Value = LookupCache.GetUserAsync(1).Result); + AddStep("set local user", () => ((DummyAPIAccess)API).LocalUser.Value = LookupCache.GetUserAsync(1).GetResultSafely()); AddStep("create leaderboard", () => { diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs index d671673d3c..bd4b38b9c0 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs @@ -83,7 +83,7 @@ namespace osu.Game.Tests.Visual.Multiplayer beatmapSetInfo.Beatmaps.Add(beatmap); } - manager.Import(beatmapSetInfo).Wait(); + manager.Import(beatmapSetInfo).WaitSafely(); } public override void SetUpSteps() diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs index 55430fbb41..52e46ef5af 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs @@ -5,6 +5,7 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; +using osu.Framework.Extensions; using osu.Framework.Platform; using osu.Framework.Screens; using osu.Framework.Testing; @@ -39,7 +40,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, Resources, host, Beatmap.Default)); - beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).Wait(); + beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely(); importedSet = beatmaps.GetAllUsableBeatmapSetsEnumerable(IncludedDetails.All).First(); } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs index 7760661232..464c0ea5b6 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs @@ -6,6 +6,7 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; +using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Platform; using osu.Framework.Testing; @@ -53,7 +54,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { AddStep("import beatmap", () => { - beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).Wait(); + beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely(); importedSet = beatmaps.GetAllUsableBeatmapSetsEnumerable(IncludedDetails.All).First(); importedBeatmap = importedSet.Beatmaps.First(b => b.RulesetID == 0); }); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs index 1a646d5e7e..29daff546d 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs @@ -6,6 +6,7 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; +using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Platform; @@ -58,7 +59,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("import beatmap", () => { - beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).Wait(); + beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely(); importedSet = beatmaps.GetAllUsableBeatmapSetsEnumerable(IncludedDetails.All).First(); importedBeatmap = importedSet.Beatmaps.First(b => b.RulesetID == 0); }); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs index 93be28ad90..8f51b1e381 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs @@ -8,6 +8,7 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Bindables; +using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Platform; using osu.Framework.Testing; @@ -41,7 +42,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, Resources, host, Beatmap.Default)); - beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).Wait(); + beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely(); } [SetUp] diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs index eecf0dff9f..d4ff9f8c41 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs @@ -8,6 +8,7 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Bindables; +using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Platform; @@ -42,7 +43,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, Resources, host, Beatmap.Default)); - beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).Wait(); + beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely(); } [SetUp] diff --git a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs index 5aac228f4b..08fcac125d 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs @@ -6,6 +6,7 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; +using osu.Framework.Extensions; using osu.Framework.Platform; using osu.Framework.Screens; using osu.Framework.Utils; @@ -38,7 +39,7 @@ namespace osu.Game.Tests.Visual.Multiplayer var beatmapSet = TestResources.CreateTestBeatmapSetInfo(); - manager.Import(beatmapSet).Wait(); + manager.Import(beatmapSet).WaitSafely(); } public override void SetUpSteps() diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs index 81c59b90f5..d20fbd3539 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs @@ -6,6 +6,7 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; +using osu.Framework.Extensions; using osu.Framework.Platform; using osu.Framework.Testing; using osu.Game.Beatmaps; @@ -51,7 +52,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("import beatmap", () => { - beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).Wait(); + beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely(); importedSet = beatmaps.GetAllUsableBeatmapSetsEnumerable(IncludedDetails.All).First(); }); diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneMouseWheelVolumeAdjust.cs b/osu.Game.Tests/Visual/Navigation/TestSceneMouseWheelVolumeAdjust.cs index b3b80147ca..701ab480f6 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneMouseWheelVolumeAdjust.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneMouseWheelVolumeAdjust.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using NUnit.Framework; +using osu.Framework.Extensions; using osu.Game.Configuration; using osu.Game.Screens.Play; using osu.Game.Tests.Beatmaps.IO; @@ -82,7 +83,7 @@ namespace osu.Game.Tests.Visual.Navigation PushAndConfirm(() => songSelect = new TestSceneScreenNavigation.TestPlaySongSelect()); AddUntilStep("wait for song select", () => songSelect.BeatmapSetsLoaded); - AddStep("import beatmap", () => ImportBeatmapTest.LoadOszIntoOsu(Game, virtualTrack: true).Wait()); + AddStep("import beatmap", () => ImportBeatmapTest.LoadOszIntoOsu(Game, virtualTrack: true).WaitSafely()); AddUntilStep("wait for selected", () => !Game.Beatmap.IsDefault); AddStep("press enter", () => InputManager.Key(Key.Enter)); diff --git a/osu.Game.Tests/Visual/Navigation/TestScenePerformFromScreen.cs b/osu.Game.Tests/Visual/Navigation/TestScenePerformFromScreen.cs index 4e1b3bb9bf..24f5808961 100644 --- a/osu.Game.Tests/Visual/Navigation/TestScenePerformFromScreen.cs +++ b/osu.Game.Tests/Visual/Navigation/TestScenePerformFromScreen.cs @@ -4,6 +4,7 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Extensions; using osu.Framework.Graphics.Containers; using osu.Framework.Screens; using osu.Framework.Testing; @@ -172,7 +173,7 @@ namespace osu.Game.Tests.Visual.Navigation private void importAndWaitForSongSelect() { - AddStep("import beatmap", () => ImportBeatmapTest.LoadQuickOszIntoOsu(Game).Wait()); + AddStep("import beatmap", () => ImportBeatmapTest.LoadQuickOszIntoOsu(Game).WaitSafely()); PushAndConfirm(() => new TestPlaySongSelect()); AddUntilStep("beatmap updated", () => Game.Beatmap.Value.BeatmapSetInfo.OnlineID == 241526); } diff --git a/osu.Game.Tests/Visual/Navigation/TestScenePresentBeatmap.cs b/osu.Game.Tests/Visual/Navigation/TestScenePresentBeatmap.cs index 0d0931f871..6420e7b849 100644 --- a/osu.Game.Tests/Visual/Navigation/TestScenePresentBeatmap.cs +++ b/osu.Game.Tests/Visual/Navigation/TestScenePresentBeatmap.cs @@ -4,6 +4,7 @@ using System; using System.Linq; using NUnit.Framework; +using osu.Framework.Extensions; using osu.Framework.Screens; using osu.Game.Beatmaps; using osu.Game.Extensions; @@ -20,30 +21,30 @@ namespace osu.Game.Tests.Visual.Navigation public void TestFromMainMenu() { var firstImport = importBeatmap(1); - var secondimport = importBeatmap(3); + var secondImport = importBeatmap(3); presentAndConfirm(firstImport); returnToMenu(); - presentAndConfirm(secondimport); + presentAndConfirm(secondImport); returnToMenu(); presentSecondDifficultyAndConfirm(firstImport, 1); returnToMenu(); - presentSecondDifficultyAndConfirm(secondimport, 3); + presentSecondDifficultyAndConfirm(secondImport, 3); } [Test] public void TestFromMainMenuDifferentRuleset() { var firstImport = importBeatmap(1); - var secondimport = importBeatmap(3, new ManiaRuleset().RulesetInfo); + var secondImport = importBeatmap(3, new ManiaRuleset().RulesetInfo); presentAndConfirm(firstImport); returnToMenu(); - presentAndConfirm(secondimport); + presentAndConfirm(secondImport); returnToMenu(); presentSecondDifficultyAndConfirm(firstImport, 1); returnToMenu(); - presentSecondDifficultyAndConfirm(secondimport, 3); + presentSecondDifficultyAndConfirm(secondImport, 3); } [Test] @@ -52,17 +53,17 @@ namespace osu.Game.Tests.Visual.Navigation var firstImport = importBeatmap(1); presentAndConfirm(firstImport); - var secondimport = importBeatmap(3); - presentAndConfirm(secondimport); + var secondImport = importBeatmap(3); + presentAndConfirm(secondImport); // Test presenting same beatmap more than once - presentAndConfirm(secondimport); + presentAndConfirm(secondImport); presentSecondDifficultyAndConfirm(firstImport, 1); - presentSecondDifficultyAndConfirm(secondimport, 3); + presentSecondDifficultyAndConfirm(secondImport, 3); // Test presenting same beatmap more than once - presentSecondDifficultyAndConfirm(secondimport, 3); + presentSecondDifficultyAndConfirm(secondImport, 3); } [Test] @@ -71,11 +72,11 @@ namespace osu.Game.Tests.Visual.Navigation var firstImport = importBeatmap(1); presentAndConfirm(firstImport); - var secondimport = importBeatmap(3, new ManiaRuleset().RulesetInfo); - presentAndConfirm(secondimport); + var secondImport = importBeatmap(3, new ManiaRuleset().RulesetInfo); + presentAndConfirm(secondImport); presentSecondDifficultyAndConfirm(firstImport, 1); - presentSecondDifficultyAndConfirm(secondimport, 3); + presentSecondDifficultyAndConfirm(secondImport, 3); } private void returnToMenu() @@ -126,7 +127,7 @@ namespace osu.Game.Tests.Visual.Navigation Ruleset = ruleset ?? new OsuRuleset().RulesetInfo }, } - }).Result.Value; + }).GetResultSafely().Value; }); AddAssert($"import {i} succeeded", () => imported != null); diff --git a/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs b/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs index 1653247570..5dc1808c12 100644 --- a/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs +++ b/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs @@ -4,6 +4,7 @@ using System; using System.Linq; using NUnit.Framework; +using osu.Framework.Extensions; using osu.Framework.Screens; using osu.Framework.Testing; using osu.Game.Beatmaps; @@ -57,7 +58,7 @@ namespace osu.Game.Tests.Visual.Navigation Ruleset = new OsuRuleset().RulesetInfo }, } - }).Result.Value; + }).GetResultSafely().Value; }); } @@ -65,11 +66,11 @@ namespace osu.Game.Tests.Visual.Navigation public void TestFromMainMenu([Values] ScorePresentType type) { var firstImport = importScore(1); - var secondimport = importScore(3); + var secondImport = importScore(3); presentAndConfirm(firstImport, type); returnToMenu(); - presentAndConfirm(secondimport, type); + presentAndConfirm(secondImport, type); returnToMenu(); returnToMenu(); } @@ -78,11 +79,11 @@ namespace osu.Game.Tests.Visual.Navigation public void TestFromMainMenuDifferentRuleset([Values] ScorePresentType type) { var firstImport = importScore(1); - var secondimport = importScore(3, new ManiaRuleset().RulesetInfo); + var secondImport = importScore(3, new ManiaRuleset().RulesetInfo); presentAndConfirm(firstImport, type); returnToMenu(); - presentAndConfirm(secondimport, type); + presentAndConfirm(secondImport, type); returnToMenu(); returnToMenu(); } @@ -93,8 +94,8 @@ namespace osu.Game.Tests.Visual.Navigation var firstImport = importScore(1); presentAndConfirm(firstImport, type); - var secondimport = importScore(3); - presentAndConfirm(secondimport, type); + var secondImport = importScore(3); + presentAndConfirm(secondImport, type); } [Test] @@ -103,8 +104,8 @@ namespace osu.Game.Tests.Visual.Navigation var firstImport = importScore(1); presentAndConfirm(firstImport, type); - var secondimport = importScore(3, new ManiaRuleset().RulesetInfo); - presentAndConfirm(secondimport, type); + var secondImport = importScore(3, new ManiaRuleset().RulesetInfo); + presentAndConfirm(secondImport, type); } private void returnToMenu() @@ -131,7 +132,7 @@ namespace osu.Game.Tests.Visual.Navigation OnlineID = i, BeatmapInfo = beatmap.Beatmaps.First(), Ruleset = ruleset ?? new OsuRuleset().RulesetInfo - }).Result.Value; + }).GetResultSafely().Value; }); AddAssert($"import {i} succeeded", () => imported != null); diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs index 75d8e62ca7..d094d8b688 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs @@ -4,6 +4,7 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Extensions; using osu.Framework.Graphics.Containers; using osu.Framework.Screens; using osu.Framework.Testing; @@ -70,7 +71,7 @@ namespace osu.Game.Tests.Visual.Navigation PushAndConfirm(() => songSelect = new TestPlaySongSelect()); AddUntilStep("wait for song select", () => songSelect.BeatmapSetsLoaded); - AddStep("import beatmap", () => ImportBeatmapTest.LoadQuickOszIntoOsu(Game).Wait()); + AddStep("import beatmap", () => ImportBeatmapTest.LoadQuickOszIntoOsu(Game).WaitSafely()); AddUntilStep("wait for selected", () => !Game.Beatmap.IsDefault); @@ -104,7 +105,7 @@ namespace osu.Game.Tests.Visual.Navigation PushAndConfirm(() => songSelect = new TestPlaySongSelect()); AddUntilStep("wait for song select", () => songSelect.BeatmapSetsLoaded); - AddStep("import beatmap", () => ImportBeatmapTest.LoadQuickOszIntoOsu(Game).Wait()); + AddStep("import beatmap", () => ImportBeatmapTest.LoadQuickOszIntoOsu(Game).WaitSafely()); AddUntilStep("wait for selected", () => !Game.Beatmap.IsDefault); @@ -138,7 +139,7 @@ namespace osu.Game.Tests.Visual.Navigation PushAndConfirm(() => songSelect = new TestPlaySongSelect()); AddUntilStep("wait for song select", () => songSelect.BeatmapSetsLoaded); - AddStep("import beatmap", () => ImportBeatmapTest.LoadOszIntoOsu(Game, virtualTrack: true).Wait()); + AddStep("import beatmap", () => ImportBeatmapTest.LoadOszIntoOsu(Game, virtualTrack: true).WaitSafely()); AddUntilStep("wait for selected", () => !Game.Beatmap.IsDefault); diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapAvailability.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapAvailability.cs index 176e0592ef..f94ae24a14 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneBeatmapAvailability.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapAvailability.cs @@ -30,7 +30,7 @@ namespace osu.Game.Tests.Visual.Online }, }); - visiblityAssert(true); + visibilityAssert(true); } [Test] @@ -44,7 +44,7 @@ namespace osu.Game.Tests.Visual.Online }, }); - visiblityAssert(true); + visibilityAssert(true); } [Test] @@ -59,7 +59,7 @@ namespace osu.Game.Tests.Visual.Online }, }); - visiblityAssert(true); + visibilityAssert(true); } [Test] @@ -73,10 +73,10 @@ namespace osu.Game.Tests.Visual.Online }, }); - visiblityAssert(false); + visibilityAssert(false); } - private void visiblityAssert(bool shown) + private void visibilityAssert(bool shown) { AddAssert($"is container {(shown ? "visible" : "hidden")}", () => container.Alpha == (shown ? 1 : 0)); } diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs index ee8794ae87..d0e3340f2a 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs @@ -107,19 +107,31 @@ namespace osu.Game.Tests.Visual.Online AddUntilStep("is hidden", () => overlay.State.Value == Visibility.Hidden); } + [Test] + public void TestCorrectOldContentExpiration() + { + AddAssert("is visible", () => overlay.State.Value == Visibility.Visible); + + AddStep("show many results", () => fetchFor(Enumerable.Repeat(CreateAPIBeatmapSet(Ruleset.Value), 100).ToArray())); + assertAllCardsOfType(100); + + AddStep("show more results", () => fetchFor(Enumerable.Repeat(CreateAPIBeatmapSet(Ruleset.Value), 30).ToArray())); + assertAllCardsOfType(30); + } + [Test] public void TestCardSizeSwitching() { AddAssert("is visible", () => overlay.State.Value == Visibility.Visible); AddStep("show many results", () => fetchFor(Enumerable.Repeat(CreateAPIBeatmapSet(Ruleset.Value), 100).ToArray())); - assertAllCardsOfType(); + assertAllCardsOfType(100); setCardSize(BeatmapCardSize.Extra); - assertAllCardsOfType(); + assertAllCardsOfType(100); setCardSize(BeatmapCardSize.Normal); - assertAllCardsOfType(); + assertAllCardsOfType(100); AddStep("fetch for 0 beatmaps", () => fetchFor()); AddUntilStep("placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); @@ -323,13 +335,12 @@ namespace osu.Game.Tests.Visual.Online private void setCardSize(BeatmapCardSize cardSize) => AddStep($"set card size to {cardSize}", () => overlay.ChildrenOfType().Single().Current.Value = cardSize); - private void assertAllCardsOfType() + private void assertAllCardsOfType(int expectedCount) where T : BeatmapCard => AddUntilStep($"all loaded beatmap cards are {typeof(T)}", () => { int loadedCorrectCount = this.ChildrenOfType().Count(card => card.IsLoaded && card.GetType() == typeof(T)); - int totalCount = this.ChildrenOfType().Count(); - return loadedCorrectCount > 0 && loadedCorrectCount == totalCount; + return loadedCorrectCount > 0 && loadedCorrectCount == expectedCount; }); } } diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatLink.cs b/osu.Game.Tests/Visual/Online/TestSceneChatLink.cs index a03c00eb58..12b5f64559 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChatLink.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChatLink.cs @@ -50,63 +50,24 @@ namespace osu.Game.Tests.Visual.Online Dependencies.Cache(new ChatOverlay()); Dependencies.Cache(dialogOverlay); - - testLinksGeneral(); - testEcho(); } - private void clear() => AddStep("clear messages", textContainer.Clear); - - private void addMessageWithChecks(string text, int linkAmount = 0, bool isAction = false, bool isImportant = false, params LinkAction[] expectedActions) + [SetUp] + public void Setup() => Schedule(() => { - int index = textContainer.Count + 1; - var newLine = new ChatLine(new DummyMessage(text, isAction, isImportant, index)); - textContainer.Add(newLine); + textContainer.Clear(); + }); - AddAssert($"msg #{index} has {linkAmount} link(s)", () => newLine.Message.Links.Count == linkAmount); - AddAssert($"msg #{index} has the right action", hasExpectedActions); - //AddAssert($"msg #{index} is " + (isAction ? "italic" : "not italic"), () => newLine.ContentFlow.Any() && isAction == isItalic()); - AddAssert($"msg #{index} shows {linkAmount} link(s)", isShowingLinks); - - bool hasExpectedActions() - { - var expectedActionsList = expectedActions.ToList(); - - if (expectedActionsList.Count != newLine.Message.Links.Count) - return false; - - for (int i = 0; i < newLine.Message.Links.Count; i++) - { - var action = newLine.Message.Links[i].Action; - if (action != expectedActions[i]) return false; - } - - return true; - } - - //bool isItalic() => newLine.ContentFlow.Where(d => d is OsuSpriteText).Cast().All(sprite => sprite.Font.Italics); - - bool isShowingLinks() - { - bool hasBackground = !string.IsNullOrEmpty(newLine.Message.Sender.Colour); - - Color4 textColour = isAction && hasBackground ? Color4Extensions.FromHex(newLine.Message.Sender.Colour) : Color4.White; - - var linkCompilers = newLine.ContentFlow.Where(d => d is DrawableLinkCompiler).ToList(); - var linkSprites = linkCompilers.SelectMany(comp => ((DrawableLinkCompiler)comp).Parts); - - return linkSprites.All(d => d.Colour == linkColour) - && newLine.ContentFlow.Except(linkSprites.Concat(linkCompilers)).All(d => d.Colour == textColour); - } - } - - private void testLinksGeneral() + [Test] + public void TestLinksGeneral() { + int messageIndex = 0; + addMessageWithChecks("test!"); addMessageWithChecks("dev.ppy.sh!"); addMessageWithChecks("https://dev.ppy.sh!", 1, expectedActions: LinkAction.External); addMessageWithChecks("00:12:345 (1,2) - Test?", 1, expectedActions: LinkAction.OpenEditorTimestamp); - addMessageWithChecks("Wiki link for tasty [[Performance Points]]", 1, expectedActions: LinkAction.External); + addMessageWithChecks("Wiki link for tasty [[Performance Points]]", 1, expectedActions: LinkAction.OpenWiki); addMessageWithChecks("(osu forums)[https://dev.ppy.sh/forum] (old link format)", 1, expectedActions: LinkAction.External); addMessageWithChecks("[https://dev.ppy.sh/home New site] (new link format)", 1, expectedActions: LinkAction.External); addMessageWithChecks("[osu forums](https://dev.ppy.sh/forum) (new link format 2)", 1, expectedActions: LinkAction.External); @@ -117,7 +78,8 @@ namespace osu.Game.Tests.Visual.Online expectedActions: new[] { LinkAction.External, LinkAction.OpenBeatmap, LinkAction.External }); addMessageWithChecks("[https://dev.ppy.sh/home New link format with escaped [and \\[ paired] braces]", 1, expectedActions: LinkAction.External); addMessageWithChecks("[Markdown link format with escaped [and \\[ paired] braces](https://dev.ppy.sh/home)", 1, expectedActions: LinkAction.External); - addMessageWithChecks("(Old link format with escaped (and \\( paired) parentheses)[https://dev.ppy.sh/home] and [[also a rogue wiki link]]", 2, expectedActions: new[] { LinkAction.External, LinkAction.External }); + addMessageWithChecks("(Old link format with escaped (and \\( paired) parentheses)[https://dev.ppy.sh/home] and [[also a rogue wiki link]]", 2, + expectedActions: new[] { LinkAction.External, LinkAction.OpenWiki }); // note that there's 0 links here (they get removed if a channel is not found) addMessageWithChecks("#lobby or #osu would be blue (and work) in the ChatDisplay test (when a proper ChatOverlay is present)."); addMessageWithChecks("I am important!", 0, false, true); @@ -129,11 +91,60 @@ namespace osu.Game.Tests.Visual.Online addMessageWithChecks("Join my osu://chan/#english.", 1, expectedActions: LinkAction.OpenChannel); addMessageWithChecks("Join my #english or #japanese channels.", 2, expectedActions: new[] { LinkAction.OpenChannel, LinkAction.OpenChannel }); addMessageWithChecks("Join my #english or #nonexistent #hashtag channels.", 1, expectedActions: LinkAction.OpenChannel); + + void addMessageWithChecks(string text, int linkAmount = 0, bool isAction = false, bool isImportant = false, params LinkAction[] expectedActions) + { + ChatLine newLine = null; + int index = messageIndex++; + + AddStep("add message", () => + { + newLine = new ChatLine(new DummyMessage(text, isAction, isImportant, index)); + textContainer.Add(newLine); + }); + + AddAssert($"msg #{index} has {linkAmount} link(s)", () => newLine.Message.Links.Count == linkAmount); + AddAssert($"msg #{index} has the right action", hasExpectedActions); + //AddAssert($"msg #{index} is " + (isAction ? "italic" : "not italic"), () => newLine.ContentFlow.Any() && isAction == isItalic()); + AddAssert($"msg #{index} shows {linkAmount} link(s)", isShowingLinks); + + bool hasExpectedActions() + { + var expectedActionsList = expectedActions.ToList(); + + if (expectedActionsList.Count != newLine.Message.Links.Count) + return false; + + for (int i = 0; i < newLine.Message.Links.Count; i++) + { + var action = newLine.Message.Links[i].Action; + if (action != expectedActions[i]) return false; + } + + return true; + } + + //bool isItalic() => newLine.ContentFlow.Where(d => d is OsuSpriteText).Cast().All(sprite => sprite.Font.Italics); + + bool isShowingLinks() + { + bool hasBackground = !string.IsNullOrEmpty(newLine.Message.Sender.Colour); + + Color4 textColour = isAction && hasBackground ? Color4Extensions.FromHex(newLine.Message.Sender.Colour) : Color4.White; + + var linkCompilers = newLine.ContentFlow.Where(d => d is DrawableLinkCompiler).ToList(); + var linkSprites = linkCompilers.SelectMany(comp => ((DrawableLinkCompiler)comp).Parts); + + return linkSprites.All(d => d.Colour == linkColour) + && newLine.ContentFlow.Except(linkSprites.Concat(linkCompilers)).All(d => d.Colour == textColour); + } + } } - private void testEcho() + [Test] + public void TestEcho() { - int echoCounter = 0; + int messageIndex = 0; addEchoWithWait("sent!", "received!"); addEchoWithWait("https://dev.ppy.sh/home", null, 500); @@ -142,15 +153,16 @@ namespace osu.Game.Tests.Visual.Online void addEchoWithWait(string text, string completeText = null, double delay = 250) { - var newLine = new ChatLine(new DummyEchoMessage(text)); + int index = messageIndex++; - AddStep($"send msg #{++echoCounter} after {delay}ms", () => + AddStep($"send msg #{index} after {delay}ms", () => { + ChatLine newLine = new ChatLine(new DummyEchoMessage(text)); textContainer.Add(newLine); Scheduler.AddDelayed(() => newLine.Message = new DummyMessage(completeText ?? text), delay); }); - AddUntilStep($"wait for msg #{echoCounter}", () => textContainer.All(line => line.Message is DummyMessage)); + AddUntilStep($"wait for msg #{index}", () => textContainer.All(line => line.Message is DummyMessage)); } } diff --git a/osu.Game.Tests/Visual/Online/TestSceneKudosuHistory.cs b/osu.Game.Tests/Visual/Online/TestSceneKudosuHistory.cs index 2231139856..8bf2ef34f2 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneKudosuHistory.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneKudosuHistory.cs @@ -44,7 +44,7 @@ namespace osu.Game.Tests.Visual.Online [BackgroundDependencyLoader] private void load(OsuColour colours) { - background.Colour = colours.GreySeafoam; + background.Colour = colours.GreySeaFoam; } private readonly IEnumerable items = new[] diff --git a/osu.Game.Tests/Visual/Online/TestScenePlayHistorySubsection.cs b/osu.Game.Tests/Visual/Online/TestScenePlayHistorySubsection.cs index fecc1af03c..a96fde6c20 100644 --- a/osu.Game.Tests/Visual/Online/TestScenePlayHistorySubsection.cs +++ b/osu.Game.Tests/Visual/Online/TestScenePlayHistorySubsection.cs @@ -87,7 +87,7 @@ namespace osu.Game.Tests.Visual.Online { AddStep("Load user", () => user.Value = user_with_filled_values); AddAssert("Section is visible", () => section.Alpha == 1); - AddAssert("Array length is the same", () => user_with_filled_values.MonthlyPlaycounts.Length == getChartValuesLength()); + AddAssert("Array length is the same", () => user_with_filled_values.MonthlyPlayCounts.Length == getChartValuesLength()); } [Test] @@ -108,13 +108,13 @@ namespace osu.Game.Tests.Visual.Online private static readonly APIUser user_with_empty_values = new APIUser { Id = 2, - MonthlyPlaycounts = Array.Empty() + MonthlyPlayCounts = Array.Empty() }; private static readonly APIUser user_with_one_value = new APIUser { Id = 3, - MonthlyPlaycounts = new[] + MonthlyPlayCounts = new[] { new APIUserHistoryCount { Date = new DateTime(2010, 5, 1), Count = 100 } } @@ -123,7 +123,7 @@ namespace osu.Game.Tests.Visual.Online private static readonly APIUser user_with_two_values = new APIUser { Id = 4, - MonthlyPlaycounts = new[] + MonthlyPlayCounts = new[] { new APIUserHistoryCount { Date = new DateTime(2010, 5, 1), Count = 1 }, new APIUserHistoryCount { Date = new DateTime(2010, 6, 1), Count = 2 } @@ -133,7 +133,7 @@ namespace osu.Game.Tests.Visual.Online private static readonly APIUser user_with_constant_values = new APIUser { Id = 5, - MonthlyPlaycounts = new[] + MonthlyPlayCounts = new[] { new APIUserHistoryCount { Date = new DateTime(2010, 5, 1), Count = 5 }, new APIUserHistoryCount { Date = new DateTime(2010, 6, 1), Count = 5 }, @@ -144,7 +144,7 @@ namespace osu.Game.Tests.Visual.Online private static readonly APIUser user_with_zero_values = new APIUser { Id = 6, - MonthlyPlaycounts = new[] + MonthlyPlayCounts = new[] { new APIUserHistoryCount { Date = new DateTime(2010, 5, 1), Count = 0 }, new APIUserHistoryCount { Date = new DateTime(2010, 6, 1), Count = 0 }, @@ -155,7 +155,7 @@ namespace osu.Game.Tests.Visual.Online private static readonly APIUser user_with_filled_values = new APIUser { Id = 7, - MonthlyPlaycounts = new[] + MonthlyPlayCounts = new[] { new APIUserHistoryCount { Date = new DateTime(2010, 5, 1), Count = 1000 }, new APIUserHistoryCount { Date = new DateTime(2010, 6, 1), Count = 20 }, @@ -170,7 +170,7 @@ namespace osu.Game.Tests.Visual.Online private static readonly APIUser user_with_missing_values = new APIUser { Id = 8, - MonthlyPlaycounts = new[] + MonthlyPlayCounts = new[] { new APIUserHistoryCount { Date = new DateTime(2020, 1, 1), Count = 100 }, new APIUserHistoryCount { Date = new DateTime(2020, 7, 1), Count = 200 } diff --git a/osu.Game.Tests/Visual/Online/TestSceneRankingsTables.cs b/osu.Game.Tests/Visual/Online/TestSceneRankingsTables.cs index ee109189c7..35e219f839 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneRankingsTables.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneRankingsTables.cs @@ -1,38 +1,27 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Collections.Generic; using osu.Framework.Graphics.Containers; using osu.Game.Overlays.Rankings.Tables; using osu.Framework.Graphics; -using osu.Game.Online.API.Requests; -using osu.Game.Rulesets; using System.Threading; -using osu.Game.Online.API; -using osu.Game.Rulesets.Osu; -using osu.Game.Rulesets.Mania; -using osu.Game.Rulesets.Taiko; -using osu.Game.Rulesets.Catch; using osu.Framework.Allocation; using osu.Game.Graphics.UserInterface; +using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays; -using osu.Game.Overlays.Rankings; +using osu.Game.Users; namespace osu.Game.Tests.Visual.Online { public class TestSceneRankingsTables : OsuTestScene { - protected override bool UseOnlineAPI => true; - - [Resolved] - private IAPIProvider api { get; set; } - [Cached] private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Green); private readonly BasicScrollContainer scrollFlow; private readonly LoadingLayer loading; private CancellationTokenSource cancellationToken; - private APIRequest request; public TestSceneRankingsTables() { @@ -53,73 +42,120 @@ namespace osu.Game.Tests.Visual.Online { base.LoadComplete(); - AddStep("Osu performance", () => createPerformanceTable(new OsuRuleset().RulesetInfo, null)); - AddStep("Mania scores", () => createScoreTable(new ManiaRuleset().RulesetInfo)); - AddStep("Taiko country scores", () => createCountryTable(new TaikoRuleset().RulesetInfo)); - AddStep("Catch US performance page 10", () => createPerformanceTable(new CatchRuleset().RulesetInfo, "US", 10)); - AddStep("Osu spotlight table (chart 271)", () => createSpotlightTable(new OsuRuleset().RulesetInfo, 271)); + AddStep("User performance", createPerformanceTable); + AddStep("User scores", createScoreTable); + AddStep("Country scores", createCountryTable); } - private void createCountryTable(RulesetInfo ruleset, int page = 1) + private void createCountryTable() { onLoadStarted(); - request = new GetCountryRankingsRequest(ruleset, page); - ((GetCountryRankingsRequest)request).Success += rankings => Schedule(() => + var countries = new List { - var table = new CountriesTable(page, rankings.Countries); - loadTable(table); - }); + new CountryStatistics + { + Country = new Country { FlagName = "US", FullName = "United States" }, + FlagName = "US", + ActiveUsers = 2_972_623, + PlayCount = 3_086_515_743, + RankedScore = 449_407_643_332_546, + Performance = 371_974_024 + }, + new CountryStatistics + { + Country = new Country { FlagName = "RU", FullName = "Russian Federation" }, + FlagName = "RU", + ActiveUsers = 1_609_989, + PlayCount = 1_637_052_841, + RankedScore = 221_660_827_473_004, + Performance = 163_426_476 + } + }; - api.Queue(request); + var table = new CountriesTable(1, countries); + loadTable(table); } - private void createPerformanceTable(RulesetInfo ruleset, string country, int page = 1) + private static List createUserStatistics() => new List + { + new UserStatistics + { + User = new APIUser + { + Username = "first active user", + Country = new Country { FlagName = "JP" }, + Active = true, + }, + Accuracy = 0.9972, + PlayCount = 233_215, + TotalScore = 983_231_234_656, + RankedScore = 593_231_345_897, + PP = 23_934, + GradesCount = new UserStatistics.Grades + { + SS = 35_132, + S = 23_345, + A = 12_234 + } + }, + new UserStatistics + { + User = new APIUser + { + Username = "inactive user", + Country = new Country { FlagName = "AU" }, + Active = false, + }, + Accuracy = 0.9831, + PlayCount = 195_342, + TotalScore = 683_231_234_656, + RankedScore = 393_231_345_897, + PP = 20_934, + GradesCount = new UserStatistics.Grades + { + SS = 32_132, + S = 20_345, + A = 9_234 + } + }, + new UserStatistics + { + User = new APIUser + { + Username = "second active user", + Country = new Country { FlagName = "PL" }, + Active = true, + }, + Accuracy = 0.9584, + PlayCount = 100_903, + TotalScore = 97_242_983_434, + RankedScore = 3_156_345_897, + PP = 9_568, + GradesCount = new UserStatistics.Grades + { + SS = 13_152, + S = 24_375, + A = 9_960 + } + }, + }; + + private void createPerformanceTable() { onLoadStarted(); - - request = new GetUserRankingsRequest(ruleset, country: country, page: page); - ((GetUserRankingsRequest)request).Success += rankings => Schedule(() => - { - var table = new PerformanceTable(page, rankings.Users); - loadTable(table); - }); - - api.Queue(request); + loadTable(new PerformanceTable(1, createUserStatistics())); } - private void createScoreTable(RulesetInfo ruleset, int page = 1) + private void createScoreTable() { onLoadStarted(); - - request = new GetUserRankingsRequest(ruleset, UserRankingsType.Score, page); - ((GetUserRankingsRequest)request).Success += rankings => Schedule(() => - { - var table = new ScoresTable(page, rankings.Users); - loadTable(table); - }); - - api.Queue(request); - } - - private void createSpotlightTable(RulesetInfo ruleset, int spotlight) - { - onLoadStarted(); - - request = new GetSpotlightRankingsRequest(ruleset, spotlight, RankingsSortCriteria.All); - ((GetSpotlightRankingsRequest)request).Success += rankings => Schedule(() => - { - var table = new ScoresTable(1, rankings.Users); - loadTable(table); - }); - - api.Queue(request); + loadTable(new ScoresTable(1, createUserStatistics())); } private void onLoadStarted() { loading.Show(); - request?.Cancel(); cancellationToken?.Cancel(); cancellationToken = new CancellationTokenSource(); } diff --git a/osu.Game.Tests/Visual/Online/TestSceneScoreboardTime.cs b/osu.Game.Tests/Visual/Online/TestSceneScoreboardTime.cs new file mode 100644 index 0000000000..7e33b5240c --- /dev/null +++ b/osu.Game.Tests/Visual/Online/TestSceneScoreboardTime.cs @@ -0,0 +1,57 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Timing; +using osu.Game.Overlays.BeatmapSet.Scores; + +namespace osu.Game.Tests.Visual.Online +{ + public class TestSceneScoreboardTime : OsuTestScene + { + private StopwatchClock stopwatch; + + [Test] + public void TestVariousUnits() + { + AddStep("create various scoreboard times", () => Child = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Clock = new FramedClock(stopwatch = new StopwatchClock()), // prevent time from naturally elapsing. + Direction = FillDirection.Vertical, + ChildrenEnumerable = testCases.Select(dateTime => new ScoreboardTime(dateTime, 24).With(time => time.Anchor = time.Origin = Anchor.TopCentre)) + }); + + AddStep("start stopwatch", () => stopwatch.Start()); + } + + private static IEnumerable testCases => new[] + { + DateTimeOffset.Now, + DateTimeOffset.Now.AddSeconds(-1), + DateTimeOffset.Now.AddSeconds(-25), + DateTimeOffset.Now.AddSeconds(-59), + DateTimeOffset.Now.AddMinutes(-1), + DateTimeOffset.Now.AddMinutes(-25), + DateTimeOffset.Now.AddMinutes(-59), + DateTimeOffset.Now.AddHours(-1), + DateTimeOffset.Now.AddHours(-13), + DateTimeOffset.Now.AddHours(-23), + DateTimeOffset.Now.AddDays(-1), + DateTimeOffset.Now.AddDays(-6), + DateTimeOffset.Now.AddDays(-16), + DateTimeOffset.Now.AddMonths(-1), + DateTimeOffset.Now.AddMonths(-11), + DateTimeOffset.Now.AddYears(-1), + DateTimeOffset.Now.AddYears(-5) + }; + } +} diff --git a/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs b/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs index b7bce012ce..779d72190d 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs @@ -309,8 +309,8 @@ namespace osu.Game.Tests.Visual.Online private class TestStandAloneChatDisplay : StandAloneChatDisplay { - public TestStandAloneChatDisplay(bool textbox = false) - : base(textbox) + public TestStandAloneChatDisplay(bool textBox = false) + : base(textBox) { } diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsMatchSettingsOverlay.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsMatchSettingsOverlay.cs index 278379c692..ca3387392a 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsMatchSettingsOverlay.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsMatchSettingsOverlay.cs @@ -87,7 +87,7 @@ namespace osu.Game.Tests.Visual.Playlists { const string not_found_prefix = "beatmaps not found:"; - string errorMesage = null; + string errorMessage = null; AddStep("setup", () => { @@ -96,9 +96,9 @@ namespace osu.Game.Tests.Visual.Playlists SelectedRoom.Value.Name.Value = "Test Room"; SelectedRoom.Value.Playlist.Add(new PlaylistItem { Beatmap = { Value = beatmap } }); - errorMesage = $"{not_found_prefix} {beatmap.OnlineID}"; + errorMessage = $"{not_found_prefix} {beatmap.OnlineID}"; - RoomManager.CreateRequested = _ => errorMesage; + RoomManager.CreateRequested = _ => errorMessage; }); AddAssert("error not displayed", () => !settings.ErrorText.IsPresent); @@ -107,7 +107,7 @@ namespace osu.Game.Tests.Visual.Playlists AddStep("create room", () => settings.ApplyButton.Action.Invoke()); AddAssert("error displayed", () => settings.ErrorText.IsPresent); - AddAssert("error has custom text", () => settings.ErrorText.Text != errorMesage); + AddAssert("error has custom text", () => settings.ErrorText.Text != errorMessage); AddAssert("playlist item marked invalid", () => !SelectedRoom.Value.Playlist[0].Valid.Value); } diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs index a426f075e1..e59884f4f4 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs @@ -7,6 +7,7 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Bindables; +using osu.Framework.Extensions; using osu.Framework.Platform; using osu.Framework.Screens; using osu.Framework.Testing; @@ -142,7 +143,7 @@ namespace osu.Game.Tests.Visual.Playlists modifiedBeatmap.HitObjects.Clear(); modifiedBeatmap.HitObjects.Add(new HitCircle { StartTime = 5000 }); - manager.Import(modifiedBeatmap.BeatmapInfo.BeatmapSet).Wait(); + manager.Import(modifiedBeatmap.BeatmapInfo.BeatmapSet).WaitSafely(); }); // Create the room using the real beatmap values. @@ -184,7 +185,7 @@ namespace osu.Game.Tests.Visual.Playlists }, }; - manager.Import(originalBeatmap.BeatmapInfo.BeatmapSet).Wait(); + manager.Import(originalBeatmap.BeatmapInfo.BeatmapSet).WaitSafely(); }); AddUntilStep("match has correct beatmap", () => realHash == match.Beatmap.Value.BeatmapInfo.MD5Hash); @@ -201,7 +202,7 @@ namespace osu.Game.Tests.Visual.Playlists }); } - private void importBeatmap() => AddStep("import beatmap", () => importedBeatmap = manager.Import(CreateBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo.BeatmapSet).Result); + private void importBeatmap() => AddStep("import beatmap", () => importedBeatmap = manager.Import(CreateBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo.BeatmapSet).GetResultSafely()); private class TestPlaylistsRoomSubScreen : PlaylistsRoomSubScreen { diff --git a/osu.Game.Tests/Visual/Settings/TestSceneRestoreDefaultValueButton.cs b/osu.Game.Tests/Visual/Settings/TestSceneRestoreDefaultValueButton.cs index 3eb7a77600..0c46fa439a 100644 --- a/osu.Game.Tests/Visual/Settings/TestSceneRestoreDefaultValueButton.cs +++ b/osu.Game.Tests/Visual/Settings/TestSceneRestoreDefaultValueButton.cs @@ -39,7 +39,7 @@ namespace osu.Game.Tests.Visual.Settings new Box { RelativeSizeAxes = Axes.Both, - Colour = colours.GreySeafoam + Colour = colours.GreySeaFoam }, restoreDefaultValueButton = new RestoreDefaultValueButton { diff --git a/osu.Game.Tests/Visual/Settings/TestSceneSettingsSource.cs b/osu.Game.Tests/Visual/Settings/TestSceneSettingsSource.cs index fc81d9792e..ffaa038930 100644 --- a/osu.Game.Tests/Visual/Settings/TestSceneSettingsSource.cs +++ b/osu.Game.Tests/Visual/Settings/TestSceneSettingsSource.cs @@ -61,7 +61,7 @@ namespace osu.Game.Tests.Visual.Settings }; [SettingSource("Sample number textbox", "Textbox number entry", SettingControlType = typeof(SettingsNumberBox))] - public Bindable IntTextboxBindable { get; } = new Bindable + public Bindable IntTextBoxBindable { get; } = new Bindable { Default = null, Value = null diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs index aa36bde030..605e03564d 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs @@ -6,6 +6,7 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; +using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Platform; using osu.Framework.Testing; @@ -74,7 +75,7 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep(@"Set beatmap", () => { - beatmapManager.Import(TestResources.GetQuickTestBeatmapForImport()).Wait(); + beatmapManager.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely(); beatmapInfo = beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps.First(); leaderboard.BeatmapInfo = beatmapInfo; @@ -175,7 +176,7 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep(@"Load new scores via manager", () => { foreach (var score in generateSampleScores(beatmapInfo())) - scoreManager.Import(score).Wait(); + scoreManager.Import(score).WaitSafely(); }); } diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs index ef11ad4153..08b5802713 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Linq; using NUnit.Framework; +using osu.Framework.Extensions; using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Extensions; @@ -181,7 +182,7 @@ namespace osu.Game.Tests.Visual.SongSelect beatmap.DifficultyName = $"SR{i + 1}"; } - return Game.BeatmapManager.Import(beatmapSet).Result.Value; + return Game.BeatmapManager.Import(beatmapSet).GetResultSafely().Value; } private bool ensureAllBeatmapSetsImported(IEnumerable beatmapSets) => beatmapSets.All(set => set != null); diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs index 0ae4e0c5dc..1ee59eccc7 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; @@ -38,7 +39,7 @@ namespace osu.Game.Tests.Visual.SongSelect Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); Dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, Audio, Resources, host, Beatmap.Default)); - beatmapManager.Import(TestResources.GetQuickTestBeatmapForImport()).Wait(); + beatmapManager.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely(); base.Content.AddRange(new Drawable[] { diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs index 912d3f838c..37f110e727 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs @@ -8,6 +8,7 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Bindables; +using osu.Framework.Extensions; using osu.Framework.Platform; using osu.Framework.Screens; using osu.Framework.Testing; @@ -256,7 +257,7 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("import multi-ruleset map", () => { var usableRulesets = rulesets.AvailableRulesets.Where(r => r.OnlineID != 2).ToArray(); - manager.Import(TestResources.CreateTestBeatmapSetInfo(rulesets: usableRulesets)).Wait(); + manager.Import(TestResources.CreateTestBeatmapSetInfo(rulesets: usableRulesets)).WaitSafely(); }); } else @@ -670,7 +671,7 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("import multi-ruleset map", () => { var usableRulesets = rulesets.AvailableRulesets.Where(r => r.OnlineID != 2).ToArray(); - manager.Import(TestResources.CreateTestBeatmapSetInfo(3, usableRulesets)).Wait(); + manager.Import(TestResources.CreateTestBeatmapSetInfo(3, usableRulesets)).WaitSafely(); }); int previousSetID = 0; @@ -710,7 +711,7 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("import multi-ruleset map", () => { var usableRulesets = rulesets.AvailableRulesets.Where(r => r.OnlineID != 2).ToArray(); - manager.Import(TestResources.CreateTestBeatmapSetInfo(3, usableRulesets)).Wait(); + manager.Import(TestResources.CreateTestBeatmapSetInfo(3, usableRulesets)).WaitSafely(); }); DrawableCarouselBeatmapSet set = null; @@ -759,7 +760,7 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("import huge difficulty count map", () => { var usableRulesets = rulesets.AvailableRulesets.Where(r => r.OnlineID != 2).ToArray(); - imported = manager.Import(TestResources.CreateTestBeatmapSetInfo(50, usableRulesets)).Result.Value; + imported = manager.Import(TestResources.CreateTestBeatmapSetInfo(50, usableRulesets)).GetResultSafely().Value; }); AddStep("select the first beatmap of import", () => Beatmap.Value = manager.GetWorkingBeatmap(imported.Beatmaps.First())); @@ -868,7 +869,7 @@ namespace osu.Game.Tests.Visual.SongSelect private void addRulesetImportStep(int id) => AddStep($"import test map for ruleset {id}", () => importForRuleset(id)); - private void importForRuleset(int id) => manager.Import(TestResources.CreateTestBeatmapSetInfo(3, rulesets.AvailableRulesets.Where(r => r.OnlineID == id).ToArray())).Wait(); + private void importForRuleset(int id) => manager.Import(TestResources.CreateTestBeatmapSetInfo(3, rulesets.AvailableRulesets.Where(r => r.OnlineID == id).ToArray())).WaitSafely(); private void checkMusicPlaying(bool playing) => AddUntilStep($"music {(playing ? "" : "not ")}playing", () => music.IsPlaying == playing); @@ -898,7 +899,7 @@ namespace osu.Game.Tests.Visual.SongSelect var usableRulesets = rulesets.AvailableRulesets.Where(r => r.OnlineID != 2).ToArray(); for (int i = 0; i < 10; i++) - manager.Import(TestResources.CreateTestBeatmapSetInfo(difficultyCountPerSet, usableRulesets)).Wait(); + manager.Import(TestResources.CreateTestBeatmapSetInfo(difficultyCountPerSet, usableRulesets)).WaitSafely(); }); } diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs index 2363bbbfcf..a436fc0bfa 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs @@ -7,6 +7,7 @@ using NUnit.Framework; using osu.Framework.Graphics; using osu.Framework.Allocation; using osu.Framework.Audio; +using osu.Framework.Extensions; using osu.Framework.Graphics.Cursor; using osu.Framework.Platform; using osu.Framework.Testing; @@ -85,7 +86,7 @@ namespace osu.Game.Tests.Visual.UserInterface dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, ContextFactory, rulesetStore, null, dependencies.Get(), Resources, dependencies.Get(), Beatmap.Default)); dependencies.Cache(scoreManager = new ScoreManager(rulesetStore, () => beatmapManager, LocalStorage, ContextFactory, Scheduler)); - beatmapInfo = beatmapManager.Import(new ImportTask(TestResources.GetQuickTestBeatmapForImport())).Result.Value.Beatmaps[0]; + beatmapInfo = beatmapManager.Import(new ImportTask(TestResources.GetQuickTestBeatmapForImport())).GetResultSafely().Value.Beatmaps[0]; for (int i = 0; i < 50; i++) { @@ -101,7 +102,7 @@ namespace osu.Game.Tests.Visual.UserInterface User = new APIUser { Username = "TestUser" }, }; - importedScores.Add(scoreManager.Import(score).Result.Value); + importedScores.Add(scoreManager.Import(score).GetResultSafely().Value); } return dependencies; diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneOnScreenDisplay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneOnScreenDisplay.cs index 493e2f54e5..544581082e 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneOnScreenDisplay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneOnScreenDisplay.cs @@ -29,10 +29,10 @@ namespace osu.Game.Tests.Visual.UserInterface AddStep("Display toast with lengthy text", () => osd.Display(new LengthyToast())); AddAssert("Toast width is greater than 240", () => osd.Child.Width > 240); - AddRepeatStep("Change toggle (no bind)", () => config.ToggleSetting(TestConfigSetting.ToggleSettingNoKeybind), 2); - AddRepeatStep("Change toggle (with bind)", () => config.ToggleSetting(TestConfigSetting.ToggleSettingWithKeybind), 2); - AddRepeatStep("Change enum (no bind)", () => config.IncrementEnumSetting(TestConfigSetting.EnumSettingNoKeybind), 3); - AddRepeatStep("Change enum (with bind)", () => config.IncrementEnumSetting(TestConfigSetting.EnumSettingWithKeybind), 3); + AddRepeatStep("Change toggle (no bind)", () => config.ToggleSetting(TestConfigSetting.ToggleSettingNoKeyBind), 2); + AddRepeatStep("Change toggle (with bind)", () => config.ToggleSetting(TestConfigSetting.ToggleSettingWithKeyBind), 2); + AddRepeatStep("Change enum (no bind)", () => config.IncrementEnumSetting(TestConfigSetting.EnumSettingNoKeyBind), 3); + AddRepeatStep("Change enum (with bind)", () => config.IncrementEnumSetting(TestConfigSetting.EnumSettingWithKeyBind), 3); } private class TestConfigManager : ConfigManager @@ -44,10 +44,10 @@ namespace osu.Game.Tests.Visual.UserInterface protected override void InitialiseDefaults() { - SetDefault(TestConfigSetting.ToggleSettingNoKeybind, false); - SetDefault(TestConfigSetting.EnumSettingNoKeybind, EnumSetting.Setting1); - SetDefault(TestConfigSetting.ToggleSettingWithKeybind, false); - SetDefault(TestConfigSetting.EnumSettingWithKeybind, EnumSetting.Setting1); + SetDefault(TestConfigSetting.ToggleSettingNoKeyBind, false); + SetDefault(TestConfigSetting.EnumSettingNoKeyBind, EnumSetting.Setting1); + SetDefault(TestConfigSetting.ToggleSettingWithKeyBind, false); + SetDefault(TestConfigSetting.EnumSettingWithKeyBind, EnumSetting.Setting1); base.InitialiseDefaults(); } @@ -64,10 +64,10 @@ namespace osu.Game.Tests.Visual.UserInterface public override TrackedSettings CreateTrackedSettings() => new TrackedSettings { - new TrackedSetting(TestConfigSetting.ToggleSettingNoKeybind, b => new SettingDescription(b, "toggle setting with no keybind", b ? "enabled" : "disabled")), - new TrackedSetting(TestConfigSetting.EnumSettingNoKeybind, v => new SettingDescription(v, "enum setting with no keybind", v.ToString())), - new TrackedSetting(TestConfigSetting.ToggleSettingWithKeybind, b => new SettingDescription(b, "toggle setting with keybind", b ? "enabled" : "disabled", "fake keybind")), - new TrackedSetting(TestConfigSetting.EnumSettingWithKeybind, v => new SettingDescription(v, "enum setting with keybind", v.ToString(), "fake keybind")), + new TrackedSetting(TestConfigSetting.ToggleSettingNoKeyBind, b => new SettingDescription(b, "toggle setting with no keybind", b ? "enabled" : "disabled")), + new TrackedSetting(TestConfigSetting.EnumSettingNoKeyBind, v => new SettingDescription(v, "enum setting with no keybind", v.ToString())), + new TrackedSetting(TestConfigSetting.ToggleSettingWithKeyBind, b => new SettingDescription(b, "toggle setting with keybind", b ? "enabled" : "disabled", "fake keybind")), + new TrackedSetting(TestConfigSetting.EnumSettingWithKeyBind, v => new SettingDescription(v, "enum setting with keybind", v.ToString(), "fake keybind")), }; protected override void PerformLoad() @@ -79,10 +79,10 @@ namespace osu.Game.Tests.Visual.UserInterface private enum TestConfigSetting { - ToggleSettingNoKeybind, - EnumSettingNoKeybind, - ToggleSettingWithKeybind, - EnumSettingWithKeybind + ToggleSettingNoKeyBind, + EnumSettingNoKeyBind, + ToggleSettingWithKeyBind, + EnumSettingWithKeyBind } private enum EnumSetting diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneOsuTextBox.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneOsuTextBox.cs index fc1866cdf3..353f84c546 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneOsuTextBox.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneOsuTextBox.cs @@ -62,6 +62,6 @@ namespace osu.Game.Tests.Visual.UserInterface } private void clearTextboxes(IEnumerable textBoxes) => AddStep("clear textbox", () => textBoxes.ForEach(textBox => textBox.Text = null)); - private void expectedValue(IEnumerable textBoxes, string value) => AddAssert("expected textbox value", () => textBoxes.All(textbox => textbox.Text == value)); + private void expectedValue(IEnumerable textBoxes, string value) => AddAssert("expected textbox value", () => textBoxes.All(textBox => textBox.Text == value)); } } diff --git a/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs b/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs new file mode 100644 index 0000000000..c99ac52cb1 --- /dev/null +++ b/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs @@ -0,0 +1,79 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Testing; +using osu.Game.Graphics.UserInterface.PageSelector; +using osu.Game.Overlays; + +namespace osu.Game.Tests.Visual.UserInterface +{ + public class TestScenePageSelector : OsuTestScene + { + [Cached] + private OverlayColourProvider provider { get; } = new OverlayColourProvider(OverlayColourScheme.Green); + + private readonly PageSelector pageSelector; + + public TestScenePageSelector() + { + AddRange(new Drawable[] + { + pageSelector = new PageSelector + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + }); + } + + [Test] + public void TestOmittedPages() + { + setAvailablePages(100); + + selectPageIndex(0); + checkVisiblePageNumbers(new[] { 1, 2, 3, 100 }); + + selectPageIndex(6); + checkVisiblePageNumbers(new[] { 1, 5, 6, 7, 8, 9, 100 }); + + selectPageIndex(49); + checkVisiblePageNumbers(new[] { 1, 48, 49, 50, 51, 52, 100 }); + + selectPageIndex(99); + checkVisiblePageNumbers(new[] { 1, 98, 99, 100 }); + } + + [Test] + public void TestResetCurrentPage() + { + setAvailablePages(10); + selectPageIndex(6); + setAvailablePages(11); + AddAssert("Page 1 is current", () => pageSelector.CurrentPage.Value == 0); + } + + [Test] + public void TestOutOfBoundsSelection() + { + setAvailablePages(10); + selectPageIndex(11); + AddAssert("Page 10 is current", () => pageSelector.CurrentPage.Value == pageSelector.AvailablePages.Value - 1); + + selectPageIndex(-1); + AddAssert("Page 1 is current", () => pageSelector.CurrentPage.Value == 0); + } + + private void checkVisiblePageNumbers(int[] expected) => AddAssert($"Sequence is {string.Join(',', expected.Select(i => i.ToString()))}", () => pageSelector.ChildrenOfType().Select(p => p.PageNumber).SequenceEqual(expected)); + + private void selectPageIndex(int pageIndex) => + AddStep($"Select page {pageIndex}", () => pageSelector.CurrentPage.Value = pageIndex); + + private void setAvailablePages(int availablePages) => + AddStep($"Set available pages to {availablePages}", () => pageSelector.AvailablePages.Value = availablePages); + } +} diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneUpdateableBeatmapBackgroundSprite.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneUpdateableBeatmapBackgroundSprite.cs index 3fa9b8b877..6fe1ccc037 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneUpdateableBeatmapBackgroundSprite.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneUpdateableBeatmapBackgroundSprite.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps; @@ -31,7 +32,7 @@ namespace osu.Game.Tests.Visual.UserInterface { this.api = api; - testBeatmap = ImportBeatmapTest.LoadOszIntoOsu(osu).Result; + testBeatmap = ImportBeatmapTest.LoadOszIntoOsu(osu).GetResultSafely(); } [Test] diff --git a/osu.Game.Tests/Visual/UserInterface/ThemeComparisonTestScene.cs b/osu.Game.Tests/Visual/UserInterface/ThemeComparisonTestScene.cs index db1c90f287..7986f14d1d 100644 --- a/osu.Game.Tests/Visual/UserInterface/ThemeComparisonTestScene.cs +++ b/osu.Game.Tests/Visual/UserInterface/ThemeComparisonTestScene.cs @@ -27,7 +27,7 @@ namespace osu.Game.Tests.Visual.UserInterface new Box { RelativeSizeAxes = Axes.Both, - Colour = colours.GreySeafoam + Colour = colours.GreySeaFoam }, CreateContent() }); diff --git a/osu.Game.Tournament.Tests/NonVisual/IPCLocationTest.cs b/osu.Game.Tournament.Tests/NonVisual/IPCLocationTest.cs index 952eb72bf4..03252e3be6 100644 --- a/osu.Game.Tournament.Tests/NonVisual/IPCLocationTest.cs +++ b/osu.Game.Tournament.Tests/NonVisual/IPCLocationTest.cs @@ -36,7 +36,7 @@ namespace osu.Game.Tournament.Tests.NonVisual TournamentStorage storage = (TournamentStorage)osu.Dependencies.Get(); FileBasedIPC ipc = null; - WaitForOrAssert(() => (ipc = osu.Dependencies.Get() as FileBasedIPC) != null, @"ipc could not be populated in a reasonable amount of time"); + WaitForOrAssert(() => (ipc = osu.Dependencies.Get() as FileBasedIPC)?.IsLoaded == true, @"ipc could not be populated in a reasonable amount of time"); Assert.True(ipc.SetIPCLocation(testStableInstallDirectory)); Assert.True(storage.AllTournaments.Exists("stable.json")); diff --git a/osu.Game.Tournament/Screens/Drawings/Components/ScrollingTeamContainer.cs b/osu.Game.Tournament/Screens/Drawings/Components/ScrollingTeamContainer.cs index c7060bd538..6fec74f95b 100644 --- a/osu.Game.Tournament/Screens/Drawings/Components/ScrollingTeamContainer.cs +++ b/osu.Game.Tournament/Screens/Drawings/Components/ScrollingTeamContainer.cs @@ -85,14 +85,14 @@ namespace osu.Game.Tournament.Screens.Drawings.Components private ScrollState scrollState; - private void setScrollState(ScrollState newstate) + private void setScrollState(ScrollState newState) { - if (scrollState == newstate) + if (scrollState == newState) return; delayedStateChangeDelegate?.Cancel(); - switch (scrollState = newstate) + switch (scrollState = newState) { case ScrollState.Scrolling: resetSelected(); diff --git a/osu.Game.Tournament/Screens/Ladder/Components/DrawableTournamentMatch.cs b/osu.Game.Tournament/Screens/Ladder/Components/DrawableTournamentMatch.cs index 3e950310cf..5729e779c4 100644 --- a/osu.Game.Tournament/Screens/Ladder/Components/DrawableTournamentMatch.cs +++ b/osu.Game.Tournament/Screens/Ladder/Components/DrawableTournamentMatch.cs @@ -208,10 +208,10 @@ namespace osu.Game.Tournament.Screens.Ladder.Components { if (Match.Round.Value == null) return; - int instaWinAmount = Match.Round.Value.BestOf.Value / 2; + int instantWinAmount = Match.Round.Value.BestOf.Value / 2; Match.Completed.Value = Match.Round.Value.BestOf.Value > 0 - && (Match.Team1Score.Value + Match.Team2Score.Value >= Match.Round.Value.BestOf.Value || Match.Team1Score.Value > instaWinAmount || Match.Team2Score.Value > instaWinAmount); + && (Match.Team1Score.Value + Match.Team2Score.Value >= Match.Round.Value.BestOf.Value || Match.Team1Score.Value > instantWinAmount || Match.Team2Score.Value > instantWinAmount); } protected override void LoadComplete() diff --git a/osu.Game.Tournament/Screens/Setup/StablePathSelectScreen.cs b/osu.Game.Tournament/Screens/Setup/StablePathSelectScreen.cs index 5a1ceecd01..fb9ca46c2d 100644 --- a/osu.Game.Tournament/Screens/Setup/StablePathSelectScreen.cs +++ b/osu.Game.Tournament/Screens/Setup/StablePathSelectScreen.cs @@ -50,7 +50,7 @@ namespace osu.Game.Tournament.Screens.Setup { new Box { - Colour = colours.GreySeafoamDark, + Colour = colours.GreySeaFoamDark, RelativeSizeAxes = Axes.Both, }, new GridContainer diff --git a/osu.Game/Beatmaps/Beatmap.cs b/osu.Game/Beatmaps/Beatmap.cs index f69a10f000..435183fe92 100644 --- a/osu.Game/Beatmaps/Beatmap.cs +++ b/osu.Game/Beatmaps/Beatmap.cs @@ -93,8 +93,12 @@ namespace osu.Game.Beatmaps if (t.Time > lastTime) return (beatLength: t.BeatLength, 0); + // osu-stable forced the first control point to start at 0. + // This is reproduced here to maintain compatibility around osu!mania scroll speed and song select display. + double currentTime = i == 0 ? 0 : t.Time; double nextTime = i == ControlPointInfo.TimingPoints.Count - 1 ? lastTime : ControlPointInfo.TimingPoints[i + 1].Time; - return (beatLength: t.BeatLength, duration: nextTime - t.Time); + + return (beatLength: t.BeatLength, duration: nextTime - currentTime); }) // Aggregate durations into a set of (beatLength, duration) tuples for each beat length .GroupBy(t => Math.Round(t.beatLength * 1000) / 1000) diff --git a/osu.Game/Beatmaps/BeatmapDifficultyCache.cs b/osu.Game/Beatmaps/BeatmapDifficultyCache.cs index 119906cadc..f760c25170 100644 --- a/osu.Game/Beatmaps/BeatmapDifficultyCache.cs +++ b/osu.Game/Beatmaps/BeatmapDifficultyCache.cs @@ -10,6 +10,7 @@ using System.Threading.Tasks; using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions; using osu.Framework.Lists; using osu.Framework.Logging; using osu.Framework.Threading; @@ -261,13 +262,18 @@ namespace osu.Game.Beatmaps // GetDifficultyAsync will fall back to existing data from IBeatmapInfo if not locally available // (contrary to GetAsync) GetDifficultyAsync(bindable.BeatmapInfo, rulesetInfo, mods, cancellationToken) - .ContinueWith(t => + .ContinueWith(task => { // We're on a threadpool thread, but we should exit back to the update thread so consumers can safely handle value-changed events. Schedule(() => { - if (!cancellationToken.IsCancellationRequested && t.Result != null) - bindable.Value = t.Result; + if (cancellationToken.IsCancellationRequested) + return; + + var starDifficulty = task.GetResultSafely(); + + if (starDifficulty != null) + bindable.Value = starDifficulty.Value; }); }, cancellationToken); } diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 8502b91096..ed7fe0bc91 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -11,6 +11,7 @@ using System.Threading.Tasks; using JetBrains.Annotations; using osu.Framework.Audio; using osu.Framework.Audio.Track; +using osu.Framework.Extensions; using osu.Framework.IO.Stores; using osu.Framework.Platform; using osu.Framework.Testing; @@ -91,7 +92,7 @@ namespace osu.Game.Beatmaps } }; - var imported = beatmapModelManager.Import(set).Result.Value; + var imported = beatmapModelManager.Import(set).GetResultSafely().Value; return GetWorkingBeatmap(imported.Beatmaps.First()); } diff --git a/osu.Game/Beatmaps/Drawables/BeatmapSetOnlineStatusPill.cs b/osu.Game/Beatmaps/Drawables/BeatmapSetOnlineStatusPill.cs index 6345085069..19026638ba 100644 --- a/osu.Game/Beatmaps/Drawables/BeatmapSetOnlineStatusPill.cs +++ b/osu.Game/Beatmaps/Drawables/BeatmapSetOnlineStatusPill.cs @@ -94,9 +94,9 @@ namespace osu.Game.Beatmaps.Drawables if (colourProvider != null) statusText.Colour = status == BeatmapOnlineStatus.Graveyard ? colourProvider.Background1 : colourProvider.Background3; else - statusText.Colour = status == BeatmapOnlineStatus.Graveyard ? colours.GreySeafoamLight : Color4.Black; + statusText.Colour = status == BeatmapOnlineStatus.Graveyard ? colours.GreySeaFoamLight : Color4.Black; - background.Colour = OsuColour.ForBeatmapSetOnlineStatus(Status) ?? colourProvider?.Light1 ?? colours.GreySeafoamLighter; + background.Colour = OsuColour.ForBeatmapSetOnlineStatus(Status) ?? colourProvider?.Light1 ?? colours.GreySeaFoamLighter; } } } diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardContent.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardContent.cs index 497283bc64..1aaa72f5f0 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardContent.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardContent.cs @@ -3,16 +3,13 @@ #nullable enable -using System; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; -using osu.Framework.Input.Events; using osu.Framework.Threading; -using osu.Framework.Utils; using osu.Game.Graphics.Containers; using osu.Game.Overlays; using osuTK; @@ -156,54 +153,5 @@ namespace osu.Game.Beatmaps.Drawables.Cards Hollow = true, }, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); } - - private class ExpandedContentScrollContainer : OsuScrollContainer - { - public ExpandedContentScrollContainer() - { - ScrollbarVisible = false; - } - - protected override void Update() - { - base.Update(); - - Height = Math.Min(Content.DrawHeight, 400); - } - - private bool allowScroll => !Precision.AlmostEquals(DrawSize, Content.DrawSize); - - protected override bool OnDragStart(DragStartEvent e) - { - if (!allowScroll) - return false; - - return base.OnDragStart(e); - } - - protected override void OnDrag(DragEvent e) - { - if (!allowScroll) - return; - - base.OnDrag(e); - } - - protected override void OnDragEnd(DragEndEvent e) - { - if (!allowScroll) - return; - - base.OnDragEnd(e); - } - - protected override bool OnScroll(ScrollEvent e) - { - if (!allowScroll) - return false; - - return base.OnScroll(e); - } - } } } diff --git a/osu.Game/Beatmaps/Drawables/Cards/ExpandedContentScrollContainer.cs b/osu.Game/Beatmaps/Drawables/Cards/ExpandedContentScrollContainer.cs new file mode 100644 index 0000000000..edf4c5328c --- /dev/null +++ b/osu.Game/Beatmaps/Drawables/Cards/ExpandedContentScrollContainer.cs @@ -0,0 +1,61 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Framework.Input.Events; +using osu.Framework.Utils; +using osu.Game.Graphics.Containers; + +namespace osu.Game.Beatmaps.Drawables.Cards +{ + public class ExpandedContentScrollContainer : OsuScrollContainer + { + public const float HEIGHT = 200; + + public ExpandedContentScrollContainer() + { + ScrollbarVisible = false; + } + + protected override void Update() + { + base.Update(); + + Height = Math.Min(Content.DrawHeight, HEIGHT); + } + + private bool allowScroll => !Precision.AlmostEquals(DrawSize, Content.DrawSize); + + protected override bool OnDragStart(DragStartEvent e) + { + if (!allowScroll) + return false; + + return base.OnDragStart(e); + } + + protected override void OnDrag(DragEvent e) + { + if (!allowScroll) + return; + + base.OnDrag(e); + } + + protected override void OnDragEnd(DragEndEvent e) + { + if (!allowScroll) + return; + + base.OnDragEnd(e); + } + + protected override bool OnScroll(ScrollEvent e) + { + if (!allowScroll) + return false; + + return base.OnScroll(e); + } + } +} diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs index 451b4ccac8..8289b32d31 100644 --- a/osu.Game/Beatmaps/WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/WorkingBeatmap.cs @@ -11,6 +11,7 @@ using System.Threading.Tasks; using JetBrains.Annotations; using osu.Framework.Audio; using osu.Framework.Audio.Track; +using osu.Framework.Extensions; using osu.Framework.Graphics.Textures; using osu.Framework.Logging; using osu.Framework.Testing; @@ -185,7 +186,7 @@ namespace osu.Game.Beatmaps { try { - return loadBeatmapAsync().Result; + return loadBeatmapAsync().GetResultSafely(); } catch (AggregateException ae) { diff --git a/osu.Game/Collections/DrawableCollectionListItem.cs b/osu.Game/Collections/DrawableCollectionListItem.cs index e2ec25337e..909595bd1c 100644 --- a/osu.Game/Collections/DrawableCollectionListItem.cs +++ b/osu.Game/Collections/DrawableCollectionListItem.cs @@ -146,8 +146,8 @@ namespace osu.Game.Collections [BackgroundDependencyLoader] private void load(OsuColour colours) { - BackgroundUnfocused = colours.GreySeafoamDarker.Darken(0.5f); - BackgroundFocused = colours.GreySeafoam; + BackgroundUnfocused = colours.GreySeaFoamDarker.Darken(0.5f); + BackgroundFocused = colours.GreySeaFoam; } } diff --git a/osu.Game/Collections/ManageCollectionsDialog.cs b/osu.Game/Collections/ManageCollectionsDialog.cs index 95fbfa0f86..cb350bca33 100644 --- a/osu.Game/Collections/ManageCollectionsDialog.cs +++ b/osu.Game/Collections/ManageCollectionsDialog.cs @@ -46,7 +46,7 @@ namespace osu.Game.Collections { new Box { - Colour = colours.GreySeafoamDark, + Colour = colours.GreySeaFoamDark, RelativeSizeAxes = Axes.Both, }, new Container @@ -82,7 +82,7 @@ namespace osu.Game.Collections Anchor = Anchor.CentreRight, Origin = Anchor.CentreRight, Icon = FontAwesome.Solid.Times, - Colour = colours.GreySeafoamDarker, + Colour = colours.GreySeaFoamDarker, Scale = new Vector2(0.8f), X = -10, Action = () => State.Value = Visibility.Hidden @@ -100,7 +100,7 @@ namespace osu.Game.Collections new Box { RelativeSizeAxes = Axes.Both, - Colour = colours.GreySeafoamDarker + Colour = colours.GreySeaFoamDarker }, new DrawableCollectionList { diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index c6a2abecd7..07d2026c65 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -98,6 +98,8 @@ namespace osu.Game.Configuration SetDefault(OsuSetting.MenuParallax, true); // Gameplay + SetDefault(OsuSetting.PositionalHitsounds, true); // replaced by level setting below, can be removed 20220703. + SetDefault(OsuSetting.PositionalHitsoundsLevel, 0.2f, 0, 1); SetDefault(OsuSetting.DimLevel, 0.8, 0, 1, 0.01); SetDefault(OsuSetting.BlurLevel, 0, 0, 1, 0.01); SetDefault(OsuSetting.LightenDuringBreaks, true); @@ -109,7 +111,6 @@ namespace osu.Game.Configuration SetDefault(OsuSetting.ShowHealthDisplayWhenCantFail, true); SetDefault(OsuSetting.FadePlayfieldWhenHealthLow, true); SetDefault(OsuSetting.KeyOverlay, false); - SetDefault(OsuSetting.PositionalHitSounds, true); SetDefault(OsuSetting.AlwaysPlayFirstComboBreak, true); SetDefault(OsuSetting.FloatingComments, false); @@ -175,9 +176,11 @@ namespace osu.Game.Configuration int combined = (year * 10000) + monthDay; - if (combined < 20210413) + if (combined < 20220103) { - SetValue(OsuSetting.EditorWaveformOpacity, 0.25f); + var positionalHitsoundsEnabled = GetBindable(OsuSetting.PositionalHitsounds); + if (!positionalHitsoundsEnabled.Value) + SetValue(OsuSetting.PositionalHitsoundsLevel, 0); } } @@ -256,7 +259,8 @@ namespace osu.Game.Configuration LightenDuringBreaks, ShowStoryboard, KeyOverlay, - PositionalHitSounds, + PositionalHitsounds, + PositionalHitsoundsLevel, AlwaysPlayFirstComboBreak, FloatingComments, HUDVisibilityMode, diff --git a/osu.Game/Database/OnlineLookupCache.cs b/osu.Game/Database/OnlineLookupCache.cs index 5eb9fa24fa..2f98aef58a 100644 --- a/osu.Game/Database/OnlineLookupCache.cs +++ b/osu.Game/Database/OnlineLookupCache.cs @@ -8,6 +8,7 @@ using System.Threading; using System.Threading.Tasks; using JetBrains.Annotations; using osu.Framework.Allocation; +using osu.Framework.Extensions; using osu.Game.Online.API; namespace osu.Game.Database @@ -58,7 +59,7 @@ namespace osu.Game.Database if (!task.IsCompletedSuccessfully) return null; - return task.Result; + return task.GetResultSafely(); }, token)); } diff --git a/osu.Game/Database/RealmObjectExtensions.cs b/osu.Game/Database/RealmObjectExtensions.cs index e5177823ba..e09f046421 100644 --- a/osu.Game/Database/RealmObjectExtensions.cs +++ b/osu.Game/Database/RealmObjectExtensions.cs @@ -26,6 +26,9 @@ namespace osu.Game.Database /// /// Create a detached copy of the each item in the collection. /// + /// + /// Items which are already detached (ie. not managed by realm) will not be modified. + /// /// A list of managed s to detach. /// The type of object. /// A list containing non-managed copies of provided items. @@ -42,6 +45,9 @@ namespace osu.Game.Database /// /// Create a detached copy of the item. /// + /// + /// If the item if already detached (ie. not managed by realm) it will not be detached again and the original instance will be returned. This allows this method to be potentially called at multiple levels while only incurring the clone overhead once. + /// /// The managed to detach. /// The type of object. /// A non-managed copy of provided item. Will return the provided item if already detached. diff --git a/osu.Game/Extensions/WebRequestExtensions.cs b/osu.Game/Extensions/WebRequestExtensions.cs index b940c7498b..50837a648d 100644 --- a/osu.Game/Extensions/WebRequestExtensions.cs +++ b/osu.Game/Extensions/WebRequestExtensions.cs @@ -1,6 +1,8 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Globalization; +using Newtonsoft.Json.Linq; using osu.Framework.IO.Network; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Game.Online.API.Requests; @@ -16,7 +18,7 @@ namespace osu.Game.Extensions { cursor?.Properties.ForEach(x => { - webRequest.AddParameter("cursor[" + x.Key + "]", x.Value.ToString()); + webRequest.AddParameter("cursor[" + x.Key + "]", (x.Value as JValue)?.ToString(CultureInfo.InvariantCulture) ?? x.Value.ToString()); }); } } diff --git a/osu.Game/Graphics/Backgrounds/Background.cs b/osu.Game/Graphics/Backgrounds/Background.cs index 353054a1f1..b09ec1d9b9 100644 --- a/osu.Game/Graphics/Backgrounds/Background.cs +++ b/osu.Game/Graphics/Backgrounds/Background.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Diagnostics; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -17,8 +18,6 @@ namespace osu.Game.Graphics.Backgrounds /// public class Background : CompositeDrawable, IEquatable { - private const float blur_scale = 0.5f; - public readonly Sprite Sprite; private readonly string textureName; @@ -46,7 +45,7 @@ namespace osu.Game.Graphics.Backgrounds Sprite.Texture = textures.Get(textureName); } - public Vector2 BlurSigma => bufferedContainer?.BlurSigma / blur_scale ?? Vector2.Zero; + public Vector2 BlurSigma => Vector2.Divide(bufferedContainer?.BlurSigma ?? Vector2.Zero, blurScale); /// /// Smoothly adjusts over time. @@ -67,9 +66,48 @@ namespace osu.Game.Graphics.Backgrounds } if (bufferedContainer != null) - bufferedContainer.FrameBufferScale = newBlurSigma == Vector2.Zero ? Vector2.One : new Vector2(blur_scale); + transformBlurSigma(newBlurSigma, duration, easing); + } - bufferedContainer?.BlurTo(newBlurSigma * blur_scale, duration, easing); + private void transformBlurSigma(Vector2 newBlurSigma, double duration, Easing easing) + => this.TransformTo(nameof(blurSigma), newBlurSigma, duration, easing); + + private Vector2 blurSigmaBacking = Vector2.Zero; + private Vector2 blurScale = Vector2.One; + + private Vector2 blurSigma + { + get => blurSigmaBacking; + set + { + Debug.Assert(bufferedContainer != null); + + blurSigmaBacking = value; + blurScale = new Vector2(calculateBlurDownscale(value.X), calculateBlurDownscale(value.Y)); + + bufferedContainer.FrameBufferScale = blurScale; + bufferedContainer.BlurSigma = value * blurScale; // If the image is scaled down, the blur radius also needs to be reduced to cover the same pixel block. + } + } + + /// + /// Determines a factor to downscale the background based on a given blur sigma, in order to reduce the computational complexity of blurs. + /// + /// The blur sigma. + /// The scale-down factor. + private float calculateBlurDownscale(float sigma) + { + // If we're blurring within one pixel, scaling down will always result in an undesirable loss of quality. + // The algorithm below would also cause this value to go above 1, which is likewise undesirable. + if (sigma <= 1) + return 1; + + // A good value is one where the loss in quality as a result of downscaling the image is not easily perceivable. + // The constants here have been experimentally chosen to yield nice transitions by approximating a log curve through the points {{ 1, 1 }, { 4, 0.75 }, { 16, 0.5 }, { 32, 0.25 }}. + float scale = -0.18f * MathF.Log(0.004f * sigma); + + // To reduce shimmering, the scaling transitions are limited to happen only in increments of 0.2. + return MathF.Round(scale / 0.2f, MidpointRounding.AwayFromZero) * 0.2f; } public virtual bool Equals(Background other) diff --git a/osu.Game/Graphics/DateTooltip.cs b/osu.Game/Graphics/DateTooltip.cs index d5768b259a..2dca8719e9 100644 --- a/osu.Game/Graphics/DateTooltip.cs +++ b/osu.Game/Graphics/DateTooltip.cs @@ -56,7 +56,7 @@ namespace osu.Game.Graphics [BackgroundDependencyLoader] private void load(OsuColour colours) { - background.Colour = colours.GreySeafoamDarker; + background.Colour = colours.GreySeaFoamDarker; timeText.Colour = colours.BlueLighter; } diff --git a/osu.Game/Graphics/OsuColour.cs b/osu.Game/Graphics/OsuColour.cs index 69dfd3a7a1..f63bd53549 100644 --- a/osu.Game/Graphics/OsuColour.cs +++ b/osu.Game/Graphics/OsuColour.cs @@ -212,12 +212,12 @@ namespace osu.Game.Graphics public readonly Color4 GreySkyDark = Color4Extensions.FromHex(@"303d47"); public readonly Color4 GreySkyDarker = Color4Extensions.FromHex(@"21272c"); - public readonly Color4 Seafoam = Color4Extensions.FromHex(@"05ffa2"); - public readonly Color4 GreySeafoamLighter = Color4Extensions.FromHex(@"9ebab1"); - public readonly Color4 GreySeafoamLight = Color4Extensions.FromHex(@"4d7365"); - public readonly Color4 GreySeafoam = Color4Extensions.FromHex(@"33413c"); - public readonly Color4 GreySeafoamDark = Color4Extensions.FromHex(@"2c3532"); - public readonly Color4 GreySeafoamDarker = Color4Extensions.FromHex(@"1e2422"); + public readonly Color4 SeaFoam = Color4Extensions.FromHex(@"05ffa2"); + public readonly Color4 GreySeaFoamLighter = Color4Extensions.FromHex(@"9ebab1"); + public readonly Color4 GreySeaFoamLight = Color4Extensions.FromHex(@"4d7365"); + public readonly Color4 GreySeaFoam = Color4Extensions.FromHex(@"33413c"); + public readonly Color4 GreySeaFoamDark = Color4Extensions.FromHex(@"2c3532"); + public readonly Color4 GreySeaFoamDarker = Color4Extensions.FromHex(@"1e2422"); public readonly Color4 Cyan = Color4Extensions.FromHex(@"05f4fd"); public readonly Color4 GreyCyanLighter = Color4Extensions.FromHex(@"77b1b3"); diff --git a/osu.Game/Graphics/OsuIcon.cs b/osu.Game/Graphics/OsuIcon.cs index 982e9dacab..e8267edab0 100644 --- a/osu.Game/Graphics/OsuIcon.cs +++ b/osu.Game/Graphics/OsuIcon.cs @@ -39,10 +39,10 @@ namespace osu.Game.Graphics public static IconUsage ListSearch => Get(0xe032); //osu! playstyles - public static IconUsage PlaystyleTablet => Get(0xe02a); - public static IconUsage PlaystyleMouse => Get(0xe029); - public static IconUsage PlaystyleKeyboard => Get(0xe02b); - public static IconUsage PlaystyleTouch => Get(0xe02c); + public static IconUsage PlayStyleTablet => Get(0xe02a); + public static IconUsage PlayStyleMouse => Get(0xe029); + public static IconUsage PlayStyleKeyboard => Get(0xe02b); + public static IconUsage PlayStyleTouch => Get(0xe02c); // osu! difficulties public static IconUsage EasyOsu => Get(0xe015); @@ -77,17 +77,17 @@ namespace osu.Game.Graphics public static IconUsage ModAutopilot => Get(0xe03a); public static IconUsage ModAuto => Get(0xe03b); public static IconUsage ModCinema => Get(0xe03c); - public static IconUsage ModDoubletime => Get(0xe03d); + public static IconUsage ModDoubleTime => Get(0xe03d); public static IconUsage ModEasy => Get(0xe03e); public static IconUsage ModFlashlight => Get(0xe03f); public static IconUsage ModHalftime => Get(0xe040); - public static IconUsage ModHardrock => Get(0xe041); + public static IconUsage ModHardRock => Get(0xe041); public static IconUsage ModHidden => Get(0xe042); public static IconUsage ModNightcore => Get(0xe043); - public static IconUsage ModNofail => Get(0xe044); + public static IconUsage ModNoFail => Get(0xe044); public static IconUsage ModRelax => Get(0xe045); - public static IconUsage ModSpunout => Get(0xe046); - public static IconUsage ModSuddendeath => Get(0xe047); + public static IconUsage ModSpunOut => Get(0xe046); + public static IconUsage ModSuddenDeath => Get(0xe047); public static IconUsage ModTarget => Get(0xe048); public static IconUsage ModBg => Get(0xe04a); } diff --git a/osu.Game/Graphics/UserInterface/PageSelector/PageEllipsis.cs b/osu.Game/Graphics/UserInterface/PageSelector/PageEllipsis.cs new file mode 100644 index 0000000000..d73d9f5824 --- /dev/null +++ b/osu.Game/Graphics/UserInterface/PageSelector/PageEllipsis.cs @@ -0,0 +1,33 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using osu.Game.Overlays; + +namespace osu.Game.Graphics.UserInterface.PageSelector +{ + internal class PageEllipsis : CompositeDrawable + { + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider) + { + RelativeSizeAxes = Axes.Y; + AutoSizeAxes = Axes.X; + + InternalChildren = new Drawable[] + { + new OsuSpriteText + { + Font = OsuFont.GetFont(size: 12, weight: FontWeight.SemiBold), + Text = "...", + Colour = colourProvider.Light3, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + } + }; + } + } +} diff --git a/osu.Game/Graphics/UserInterface/PageSelector/PageSelector.cs b/osu.Game/Graphics/UserInterface/PageSelector/PageSelector.cs new file mode 100644 index 0000000000..005729580c --- /dev/null +++ b/osu.Game/Graphics/UserInterface/PageSelector/PageSelector.cs @@ -0,0 +1,102 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics; +using osu.Framework.Bindables; + +namespace osu.Game.Graphics.UserInterface.PageSelector +{ + public class PageSelector : CompositeDrawable + { + public readonly BindableInt CurrentPage = new BindableInt { MinValue = 0, }; + + public readonly BindableInt AvailablePages = new BindableInt(1) { MinValue = 1, }; + + private readonly FillFlowContainer itemsFlow; + + private readonly PageSelectorPrevNextButton previousPageButton; + private readonly PageSelectorPrevNextButton nextPageButton; + + public PageSelector() + { + AutoSizeAxes = Axes.Both; + + InternalChild = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Children = new Drawable[] + { + previousPageButton = new PageSelectorPrevNextButton(false, "prev") + { + Action = () => CurrentPage.Value -= 1, + }, + itemsFlow = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + }, + nextPageButton = new PageSelectorPrevNextButton(true, "next") + { + Action = () => CurrentPage.Value += 1 + } + } + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + CurrentPage.BindValueChanged(_ => Scheduler.AddOnce(redraw)); + AvailablePages.BindValueChanged(_ => + { + CurrentPage.Value = 0; + + // AddOnce as the reset of CurrentPage may also trigger a redraw. + Scheduler.AddOnce(redraw); + }, true); + } + + private void redraw() + { + if (CurrentPage.Value >= AvailablePages.Value) + { + CurrentPage.Value = AvailablePages.Value - 1; + return; + } + + previousPageButton.Enabled.Value = CurrentPage.Value != 0; + nextPageButton.Enabled.Value = CurrentPage.Value < AvailablePages.Value - 1; + + itemsFlow.Clear(); + + int totalPages = AvailablePages.Value; + bool lastWasEllipsis = false; + + for (int i = 0; i < totalPages; i++) + { + int pageIndex = i; + + bool shouldShowPage = pageIndex == 0 || pageIndex == totalPages - 1 || Math.Abs(pageIndex - CurrentPage.Value) <= 2; + + if (shouldShowPage) + { + lastWasEllipsis = false; + itemsFlow.Add(new PageSelectorPageButton(pageIndex + 1) + { + Action = () => CurrentPage.Value = pageIndex, + Selected = CurrentPage.Value == pageIndex, + }); + } + else if (!lastWasEllipsis) + { + lastWasEllipsis = true; + itemsFlow.Add(new PageEllipsis()); + } + } + } + } +} diff --git a/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorButton.cs b/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorButton.cs new file mode 100644 index 0000000000..a2c6e8532b --- /dev/null +++ b/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorButton.cs @@ -0,0 +1,71 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics; +using osu.Framework.Allocation; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics.Containers; +using osu.Framework.Input.Events; +using JetBrains.Annotations; +using osu.Game.Overlays; + +namespace osu.Game.Graphics.UserInterface.PageSelector +{ + public abstract class PageSelectorButton : OsuClickableContainer + { + protected const int DURATION = 200; + + [Resolved] + protected OverlayColourProvider ColourProvider { get; private set; } + + protected Box Background; + + protected PageSelectorButton() + { + AutoSizeAxes = Axes.X; + Height = 20; + } + + [BackgroundDependencyLoader] + private void load() + { + Add(new CircularContainer + { + RelativeSizeAxes = Axes.Y, + AutoSizeAxes = Axes.X, + Masking = true, + Children = new[] + { + Background = new Box + { + RelativeSizeAxes = Axes.Both + }, + CreateContent().With(content => + { + content.Anchor = Anchor.Centre; + content.Origin = Anchor.Centre; + content.Margin = new MarginPadding { Horizontal = 10 }; + }) + } + }); + } + + [NotNull] + protected abstract Drawable CreateContent(); + + protected override bool OnHover(HoverEvent e) + { + UpdateHoverState(); + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + base.OnHoverLost(e); + UpdateHoverState(); + } + + protected abstract void UpdateHoverState(); + } +} diff --git a/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorPageButton.cs b/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorPageButton.cs new file mode 100644 index 0000000000..247a003492 --- /dev/null +++ b/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorPageButton.cs @@ -0,0 +1,70 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics; +using osu.Framework.Bindables; +using osu.Framework.Allocation; +using osu.Game.Graphics.Sprites; + +namespace osu.Game.Graphics.UserInterface.PageSelector +{ + public class PageSelectorPageButton : PageSelectorButton + { + private readonly BindableBool selected = new BindableBool(); + + public bool Selected + { + set => selected.Value = value; + } + + public int PageNumber { get; } + + private OsuSpriteText text; + + public PageSelectorPageButton(int pageNumber) + { + PageNumber = pageNumber; + + Action = () => + { + if (!selected.Value) + selected.Value = true; + }; + } + + protected override Drawable CreateContent() => text = new OsuSpriteText + { + Font = OsuFont.GetFont(size: 12, weight: FontWeight.SemiBold), + Text = PageNumber.ToString(), + }; + + [BackgroundDependencyLoader] + private void load() + { + Background.Colour = ColourProvider.Highlight1; + Background.Alpha = 0; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + selected.BindValueChanged(onSelectedChanged, true); + } + + private void onSelectedChanged(ValueChangedEvent selected) + { + Background.FadeTo(selected.NewValue ? 1 : 0, DURATION, Easing.OutQuint); + + text.FadeColour(selected.NewValue ? ColourProvider.Dark4 : ColourProvider.Light3, DURATION, Easing.OutQuint); + text.Font = text.Font.With(weight: IsHovered ? FontWeight.SemiBold : FontWeight.Regular); + } + + protected override void UpdateHoverState() + { + if (selected.Value) + return; + + text.FadeColour(IsHovered ? ColourProvider.Light2 : ColourProvider.Light1, DURATION, Easing.OutQuint); + } + } +} diff --git a/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorPrevNextButton.cs b/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorPrevNextButton.cs new file mode 100644 index 0000000000..7503ab8135 --- /dev/null +++ b/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorPrevNextButton.cs @@ -0,0 +1,78 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Game.Graphics.Sprites; +using osuTK; + +namespace osu.Game.Graphics.UserInterface.PageSelector +{ + public class PageSelectorPrevNextButton : PageSelectorButton + { + private readonly bool rightAligned; + private readonly string text; + + private SpriteIcon icon; + private OsuSpriteText name; + + public PageSelectorPrevNextButton(bool rightAligned, string text) + { + this.rightAligned = rightAligned; + this.text = text; + } + + protected override Drawable CreateContent() => new Container + { + RelativeSizeAxes = Axes.Y, + AutoSizeAxes = Axes.X, + Children = new Drawable[] + { + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Spacing = new Vector2(3, 0), + Children = new Drawable[] + { + name = new OsuSpriteText + { + Font = OsuFont.GetFont(size: 12), + Anchor = rightAligned ? Anchor.CentreLeft : Anchor.CentreRight, + Origin = rightAligned ? Anchor.CentreLeft : Anchor.CentreRight, + Text = text.ToUpper(), + }, + icon = new SpriteIcon + { + Icon = rightAligned ? FontAwesome.Solid.ChevronRight : FontAwesome.Solid.ChevronLeft, + Size = new Vector2(8), + Anchor = rightAligned ? Anchor.CentreLeft : Anchor.CentreRight, + Origin = rightAligned ? Anchor.CentreLeft : Anchor.CentreRight, + }, + } + }, + } + }; + + [BackgroundDependencyLoader] + private void load() + { + Background.Colour = ColourProvider.Dark4; + name.Colour = icon.Colour = ColourProvider.Light1; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + Enabled.BindValueChanged(enabled => Background.FadeTo(enabled.NewValue ? 1 : 0.5f, DURATION), true); + } + + protected override void UpdateHoverState() => + Background.FadeColour(IsHovered ? ColourProvider.Dark3 : ColourProvider.Dark4, DURATION, Easing.OutQuint); + } +} diff --git a/osu.Game/Graphics/UserInterfaceV2/OsuDirectorySelectorDirectory.cs b/osu.Game/Graphics/UserInterfaceV2/OsuDirectorySelectorDirectory.cs index 618c7dabfa..ce2e7794a9 100644 --- a/osu.Game/Graphics/UserInterfaceV2/OsuDirectorySelectorDirectory.cs +++ b/osu.Game/Graphics/UserInterfaceV2/OsuDirectorySelectorDirectory.cs @@ -54,7 +54,7 @@ namespace osu.Game.Graphics.UserInterfaceV2 InternalChild = new Box { - Colour = colours.GreySeafoamDarker, + Colour = colours.GreySeaFoamDarker, RelativeSizeAxes = Axes.Both, }; } diff --git a/osu.Game/Graphics/UserInterfaceV2/OsuHSVColourPicker.cs b/osu.Game/Graphics/UserInterfaceV2/OsuHSVColourPicker.cs index 30e38e8938..0189b30aad 100644 --- a/osu.Game/Graphics/UserInterfaceV2/OsuHSVColourPicker.cs +++ b/osu.Game/Graphics/UserInterfaceV2/OsuHSVColourPicker.cs @@ -26,7 +26,7 @@ namespace osu.Game.Graphics.UserInterfaceV2 [BackgroundDependencyLoader(true)] private void load([CanBeNull] OverlayColourProvider colourProvider, OsuColour osuColour) { - Background.Colour = colourProvider?.Dark5 ?? osuColour.GreySeafoamDark; + Background.Colour = colourProvider?.Dark5 ?? osuColour.GreySeaFoamDark; Content.Padding = new MarginPadding(spacing); Content.Spacing = new Vector2(0, spacing); diff --git a/osu.Game/Graphics/UserInterfaceV2/OsuHexColourPicker.cs b/osu.Game/Graphics/UserInterfaceV2/OsuHexColourPicker.cs index 331a1b67c9..5368a800bc 100644 --- a/osu.Game/Graphics/UserInterfaceV2/OsuHexColourPicker.cs +++ b/osu.Game/Graphics/UserInterfaceV2/OsuHexColourPicker.cs @@ -23,7 +23,7 @@ namespace osu.Game.Graphics.UserInterfaceV2 [BackgroundDependencyLoader(true)] private void load([CanBeNull] OverlayColourProvider overlayColourProvider, OsuColour osuColour) { - Background.Colour = overlayColourProvider?.Dark6 ?? osuColour.GreySeafoamDarker; + Background.Colour = overlayColourProvider?.Dark6 ?? osuColour.GreySeaFoamDarker; } protected override TextBox CreateHexCodeTextBox() => new OsuTextBox(); diff --git a/osu.Game/Graphics/UserInterfaceV2/OsuPopover.cs b/osu.Game/Graphics/UserInterfaceV2/OsuPopover.cs index d1857dd174..085541b3ef 100644 --- a/osu.Game/Graphics/UserInterfaceV2/OsuPopover.cs +++ b/osu.Game/Graphics/UserInterfaceV2/OsuPopover.cs @@ -39,7 +39,7 @@ namespace osu.Game.Graphics.UserInterfaceV2 [BackgroundDependencyLoader(true)] private void load([CanBeNull] OverlayColourProvider colourProvider, OsuColour colours) { - Background.Colour = Arrow.Colour = colourProvider?.Background4 ?? colours.GreySeafoamDarker; + Background.Colour = Arrow.Colour = colourProvider?.Background4 ?? colours.GreySeaFoamDarker; } protected override Drawable CreateArrow() => Empty(); diff --git a/osu.Game/IO/Archives/ArchiveReader.cs b/osu.Game/IO/Archives/ArchiveReader.cs index f787534e2d..1d8da16c72 100644 --- a/osu.Game/IO/Archives/ArchiveReader.cs +++ b/osu.Game/IO/Archives/ArchiveReader.cs @@ -32,7 +32,18 @@ namespace osu.Game.IO.Archives public abstract IEnumerable Filenames { get; } - public virtual byte[] Get(string name) => GetAsync(name).Result; + public virtual byte[] Get(string name) + { + using (Stream input = GetStream(name)) + { + if (input == null) + return null; + + byte[] buffer = new byte[input.Length]; + input.Read(buffer); + return buffer; + } + } public async Task GetAsync(string name, CancellationToken cancellationToken = default) { diff --git a/osu.Game/IO/Legacy/SerializationReader.cs b/osu.Game/IO/Legacy/SerializationReader.cs index 00f90f78e3..f7b3f33e87 100644 --- a/osu.Game/IO/Legacy/SerializationReader.cs +++ b/osu.Game/IO/Legacy/SerializationReader.cs @@ -235,9 +235,9 @@ namespace osu.Game.IO.Legacy if (typeName.Contains("System.Collections.Generic") && typeName.Contains("[[")) { - string[] splitTyps = typeName.Split('['); + string[] splitTypes = typeName.Split('['); - foreach (string typ in splitTyps) + foreach (string typ in splitTypes) { if (typ.Contains("Version")) { diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs index c71cb6a00a..47cb7be2cf 100644 --- a/osu.Game/Input/Bindings/GlobalActionContainer.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs @@ -77,6 +77,8 @@ namespace osu.Game.Input.Bindings new KeyBinding(new[] { InputKey.K }, GlobalAction.EditorNudgeRight), new KeyBinding(new[] { InputKey.G }, GlobalAction.EditorCycleGridDisplayMode), new KeyBinding(new[] { InputKey.F5 }, GlobalAction.EditorTestGameplay), + new KeyBinding(new[] { InputKey.Control, InputKey.H }, GlobalAction.EditorFlipHorizontally), + new KeyBinding(new[] { InputKey.Control, InputKey.J }, GlobalAction.EditorFlipVertically), }; public IEnumerable InGameKeyBindings => new[] @@ -292,6 +294,12 @@ namespace osu.Game.Input.Bindings EditorCycleGridDisplayMode, [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorTestGameplay))] - EditorTestGameplay + EditorTestGameplay, + + [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorFlipHorizontally))] + EditorFlipHorizontally, + + [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorFlipVertically))] + EditorFlipVertically, } } diff --git a/osu.Game/Input/IdleTracker.cs b/osu.Game/Input/IdleTracker.cs index bfe21f650a..e2f13309cf 100644 --- a/osu.Game/Input/IdleTracker.cs +++ b/osu.Game/Input/IdleTracker.cs @@ -1,11 +1,13 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Input; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; +using osu.Framework.Platform; using osu.Game.Input.Bindings; namespace osu.Game.Input @@ -63,8 +65,17 @@ namespace osu.Game.Input public void OnReleased(KeyBindingReleaseEvent e) => updateLastInteractionTime(); + [Resolved] + private GameHost host { get; set; } + protected override bool Handle(UIEvent e) { + // Even when not active, `MouseMoveEvent`s will arrive. + // We don't want these to trigger a non-idle state as it's quite often the user interacting + // with other windows while osu! is in the background. + if (!host.IsActive.Value) + return base.Handle(e); + switch (e) { case KeyDownEvent _: diff --git a/osu.Game/Localisation/AudioSettingsStrings.cs b/osu.Game/Localisation/AudioSettingsStrings.cs index 008781c2e5..f298717c99 100644 --- a/osu.Game/Localisation/AudioSettingsStrings.cs +++ b/osu.Game/Localisation/AudioSettingsStrings.cs @@ -32,6 +32,11 @@ namespace osu.Game.Localisation /// /// "Master" /// + public static LocalisableString PositionalLevel => new TranslatableString(getKey(@"positional_hitsound_audio_level"), @"Hitsound stereo separation"); + + /// + /// "Level" + /// public static LocalisableString MasterVolume => new TranslatableString(getKey(@"master_volume"), @"Master"); /// diff --git a/osu.Game/Localisation/GameplaySettingsStrings.cs b/osu.Game/Localisation/GameplaySettingsStrings.cs index fa92187650..84c3704e26 100644 --- a/osu.Game/Localisation/GameplaySettingsStrings.cs +++ b/osu.Game/Localisation/GameplaySettingsStrings.cs @@ -84,11 +84,6 @@ namespace osu.Game.Localisation /// public static LocalisableString AlwaysShowKeyOverlay => new TranslatableString(getKey(@"key_overlay"), @"Always show key overlay"); - /// - /// "Positional hitsounds" - /// - public static LocalisableString PositionalHitsounds => new TranslatableString(getKey(@"positional_hitsounds"), @"Positional hitsounds"); - /// /// "Always play first combo break sound" /// diff --git a/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs b/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs index 35a0c2ae74..777e97d1e3 100644 --- a/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs +++ b/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs @@ -229,6 +229,16 @@ namespace osu.Game.Localisation /// public static LocalisableString EditorNudgeRight => new TranslatableString(getKey(@"editor_nudge_right"), @"Nudge selection right"); + /// + /// "Flip selection horizontally" + /// + public static LocalisableString EditorFlipHorizontally => new TranslatableString(getKey(@"editor_flip_horizontally"), @"Flip selection horizontally"); + + /// + /// "Flip selection vertically" + /// + public static LocalisableString EditorFlipVertically => new TranslatableString(getKey(@"editor_flip_vertically"), @"Flip selection vertically"); + /// /// "Toggle skin editor" /// diff --git a/osu.Game/Online/API/APIException.cs b/osu.Game/Online/API/APIException.cs index 97786bced9..54d68d8f0d 100644 --- a/osu.Game/Online/API/APIException.cs +++ b/osu.Game/Online/API/APIException.cs @@ -7,8 +7,8 @@ namespace osu.Game.Online.API { public class APIException : InvalidOperationException { - public APIException(string messsage, Exception innerException) - : base(messsage, innerException) + public APIException(string message, Exception innerException) + : base(message, innerException) { } } diff --git a/osu.Game/Online/API/Requests/Responses/APIUser.cs b/osu.Game/Online/API/Requests/Responses/APIUser.cs index 50f5d67796..e4a432b074 100644 --- a/osu.Game/Online/API/Requests/Responses/APIUser.cs +++ b/osu.Game/Online/API/Requests/Responses/APIUser.cs @@ -152,10 +152,10 @@ namespace osu.Game.Online.API.Requests.Responses public int ScoresRecentCount; [JsonProperty(@"beatmap_playcounts_count")] - public int BeatmapPlaycountsCount; + public int BeatmapPlayCountsCount; - [JsonProperty] - private string[] playstyle + [JsonProperty(@"playstyle")] + private string[] playStyle { set => PlayStyles = value?.Select(str => Enum.Parse(typeof(APIPlayStyle), str, true)).Cast().ToArray(); } @@ -213,7 +213,7 @@ namespace osu.Game.Online.API.Requests.Responses public APIUserAchievement[] Achievements; [JsonProperty("monthly_playcounts")] - public APIUserHistoryCount[] MonthlyPlaycounts; + public APIUserHistoryCount[] MonthlyPlayCounts; [JsonProperty("replays_watched_counts")] public APIUserHistoryCount[] ReplaysWatchedCounts; diff --git a/osu.Game/Online/Chat/ChannelManager.cs b/osu.Game/Online/Chat/ChannelManager.cs index edaf135e93..82e042ae4e 100644 --- a/osu.Game/Online/Chat/ChannelManager.cs +++ b/osu.Game/Online/Chat/ChannelManager.cs @@ -7,8 +7,10 @@ using System.Linq; using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions; using osu.Framework.Logging; using osu.Game.Database; +using osu.Game.Input; using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests.Responses; @@ -67,11 +69,34 @@ namespace osu.Game.Online.Chat public readonly BindableBool HighPollRate = new BindableBool(); + private readonly IBindable isIdle = new BindableBool(); + public ChannelManager() { CurrentChannel.ValueChanged += currentChannelChanged; + } - HighPollRate.BindValueChanged(enabled => TimeBetweenPolls.Value = enabled.NewValue ? 1000 : 6000, true); + [BackgroundDependencyLoader(permitNulls: true)] + private void load(IdleTracker idleTracker) + { + HighPollRate.BindValueChanged(updatePollRate); + isIdle.BindValueChanged(updatePollRate, true); + + if (idleTracker != null) + isIdle.BindTo(idleTracker.IsIdle); + } + + private void updatePollRate(ValueChangedEvent valueChangedEvent) + { + // Polling will eventually be replaced with websocket, but let's avoid doing these background operations as much as possible for now. + // The only loss will be delayed PM/message highlight notifications. + + if (HighPollRate.Value) + TimeBetweenPolls.Value = 1000; + else if (!isIdle.Value) + TimeBetweenPolls.Value = 60000; + else + TimeBetweenPolls.Value = 600000; } /// @@ -335,7 +360,7 @@ namespace osu.Game.Online.Chat /// right now it caps out at 50 messages and therefore only returns one channel's worth of content. /// /// The channel - private void fetchInitalMessages(Channel channel) + private void fetchInitialMessages(Channel channel) { if (channel.Id <= 0 || channel.MessagesLoaded) return; @@ -441,7 +466,7 @@ namespace osu.Game.Online.Chat else { if (fetchInitialMessages) - fetchInitalMessages(channel); + this.fetchInitialMessages(channel); } CurrentChannel.Value ??= channel; @@ -509,11 +534,12 @@ namespace osu.Game.Online.Chat else if (lastClosedChannel.Type == ChannelType.PM) { // Try to get user in order to open PM chat - users.GetUserAsync((int)lastClosedChannel.Id).ContinueWith(u => + users.GetUserAsync((int)lastClosedChannel.Id).ContinueWith(task => { - if (u.Result == null) return; + var user = task.GetResultSafely(); - Schedule(() => CurrentChannel.Value = JoinChannel(new Channel(u.Result))); + if (user != null) + Schedule(() => CurrentChannel.Value = JoinChannel(new Channel(user))); }); } diff --git a/osu.Game/Online/Chat/MessageFormatter.cs b/osu.Game/Online/Chat/MessageFormatter.cs index 92911f0f51..d7974004b1 100644 --- a/osu.Game/Online/Chat/MessageFormatter.cs +++ b/osu.Game/Online/Chat/MessageFormatter.cs @@ -57,6 +57,7 @@ namespace osu.Game.Online.Chat /// public static string WebsiteRootUrl { + get => websiteRootUrl; set => websiteRootUrl = value .Trim('/') // trim potential trailing slash/ .Split('/').Last(); // only keep domain name, ignoring protocol. @@ -134,7 +135,7 @@ namespace osu.Game.Online.Chat case "http": case "https": // length > 3 since all these links need another argument to work - if (args.Length > 3 && args[1].EndsWith(websiteRootUrl, StringComparison.OrdinalIgnoreCase)) + if (args.Length > 3 && args[1].EndsWith(WebsiteRootUrl, StringComparison.OrdinalIgnoreCase)) { string mainArg = args[3]; @@ -262,7 +263,7 @@ namespace osu.Game.Online.Chat handleMatches(old_link_regex, "{1}", "{2}", result, startIndex, escapeChars: new[] { '(', ')' }); // handle wiki links - handleMatches(wiki_regex, "{1}", "https://osu.ppy.sh/wiki/{1}", result, startIndex); + handleMatches(wiki_regex, "{1}", $"https://{WebsiteRootUrl}/wiki/{{1}}", result, startIndex); // handle bare links handleAdvanced(advanced_link_regex, result, startIndex); diff --git a/osu.Game/Online/Chat/MessageNotifier.cs b/osu.Game/Online/Chat/MessageNotifier.cs index a11af7b305..2c99e9f9b9 100644 --- a/osu.Game/Online/Chat/MessageNotifier.cs +++ b/osu.Game/Online/Chat/MessageNotifier.cs @@ -170,7 +170,7 @@ namespace osu.Game.Online.Chat [BackgroundDependencyLoader] private void load(OsuColour colours, ChatOverlay chatOverlay, NotificationOverlay notificationOverlay, ChannelManager channelManager) { - IconBackgound.Colour = colours.PurpleDark; + IconBackground.Colour = colours.PurpleDark; Activated = delegate { diff --git a/osu.Game/Online/Chat/StandAloneChatDisplay.cs b/osu.Game/Online/Chat/StandAloneChatDisplay.cs index 1f8d05fcd1..81fd5ad98c 100644 --- a/osu.Game/Online/Chat/StandAloneChatDisplay.cs +++ b/osu.Game/Online/Chat/StandAloneChatDisplay.cs @@ -23,27 +23,27 @@ namespace osu.Game.Online.Chat { public readonly Bindable Channel = new Bindable(); - protected readonly ChatTextBox Textbox; + protected readonly ChatTextBox TextBox; protected ChannelManager ChannelManager; private StandAloneDrawableChannel drawableChannel; - private readonly bool postingTextbox; + private readonly bool postingTextBox; protected readonly Box Background; - private const float textbox_height = 30; + private const float text_box_height = 30; /// /// Construct a new instance. /// - /// Whether a textbox for posting new messages should be displayed. - public StandAloneChatDisplay(bool postingTextbox = false) + /// Whether a textbox for posting new messages should be displayed. + public StandAloneChatDisplay(bool postingTextBox = false) { const float corner_radius = 10; - this.postingTextbox = postingTextbox; + this.postingTextBox = postingTextBox; CornerRadius = corner_radius; Masking = true; @@ -57,12 +57,12 @@ namespace osu.Game.Online.Chat }, }; - if (postingTextbox) + if (postingTextBox) { - AddInternal(Textbox = new ChatTextBox + AddInternal(TextBox = new ChatTextBox { RelativeSizeAxes = Axes.X, - Height = textbox_height, + Height = text_box_height, PlaceholderText = "type your message", CornerRadius = corner_radius, ReleaseFocusOnCommit = false, @@ -71,7 +71,7 @@ namespace osu.Game.Online.Chat Origin = Anchor.BottomLeft, }); - Textbox.OnCommit += postMessage; + TextBox.OnCommit += postMessage; } Channel.BindValueChanged(channelChanged); @@ -86,9 +86,9 @@ namespace osu.Game.Online.Chat protected virtual StandAloneDrawableChannel CreateDrawableChannel(Channel channel) => new StandAloneDrawableChannel(channel); - private void postMessage(TextBox sender, bool newtext) + private void postMessage(TextBox sender, bool newText) { - string text = Textbox.Text.Trim(); + string text = TextBox.Text.Trim(); if (string.IsNullOrWhiteSpace(text)) return; @@ -98,7 +98,7 @@ namespace osu.Game.Online.Chat else ChannelManager?.PostMessage(text, target: Channel.Value); - Textbox.Text = string.Empty; + TextBox.Text = string.Empty; } protected virtual ChatLine CreateMessage(Message message) => new StandAloneMessage(message); @@ -111,7 +111,7 @@ namespace osu.Game.Online.Chat drawableChannel = CreateDrawableChannel(e.NewValue); drawableChannel.CreateChatLineAction = CreateMessage; - drawableChannel.Padding = new MarginPadding { Bottom = postingTextbox ? textbox_height : 0 }; + drawableChannel.Padding = new MarginPadding { Bottom = postingTextBox ? text_box_height : 0 }; AddInternal(drawableChannel); } diff --git a/osu.Game/Online/Rooms/PlaylistItem.cs b/osu.Game/Online/Rooms/PlaylistItem.cs index b8700fd067..83a70c405b 100644 --- a/osu.Game/Online/Rooms/PlaylistItem.cs +++ b/osu.Game/Online/Rooms/PlaylistItem.cs @@ -4,6 +4,7 @@ using System; using System.Diagnostics; using System.Linq; +using JetBrains.Annotations; using Newtonsoft.Json; using osu.Framework.Bindables; using osu.Game.Beatmaps; @@ -112,9 +113,21 @@ namespace osu.Game.Online.Rooms } } + #region Newtonsoft.Json implicit ShouldSerialize() methods + + // The properties in this region are used implicitly by Newtonsoft.Json to not serialise certain fields in some cases. + // They rely on being named exactly the same as the corresponding fields (casing included) and as such should NOT be renamed + // unless the fields are also renamed. + + [UsedImplicitly] public bool ShouldSerializeID() => false; + + // ReSharper disable once IdentifierTypo + [UsedImplicitly] public bool ShouldSerializeapiBeatmap() => false; + #endregion + public bool Equals(PlaylistItem other) => ID == other?.ID && BeatmapID == other.BeatmapID diff --git a/osu.Game/Online/Rooms/Room.cs b/osu.Game/Online/Rooms/Room.cs index c87411c3c0..bbe854f2dd 100644 --- a/osu.Game/Online/Rooms/Room.cs +++ b/osu.Game/Online/Rooms/Room.cs @@ -3,6 +3,7 @@ using System; using System.Linq; +using JetBrains.Annotations; using Newtonsoft.Json; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -211,8 +212,21 @@ namespace osu.Game.Online.Rooms Playlist.RemoveAll(i => i.Expired); } + #region Newtonsoft.Json implicit ShouldSerialize() methods + + // The properties in this region are used implicitly by Newtonsoft.Json to not serialise certain fields in some cases. + // They rely on being named exactly the same as the corresponding fields (casing included) and as such should NOT be renamed + // unless the fields are also renamed. + + [UsedImplicitly] public bool ShouldSerializeRoomID() => false; + + [UsedImplicitly] public bool ShouldSerializeHost() => false; + + [UsedImplicitly] public bool ShouldSerializeEndDate() => false; + + #endregion } } diff --git a/osu.Game/Online/Solo/SubmitSoloScoreRequest.cs b/osu.Game/Online/Solo/SubmitSoloScoreRequest.cs index 99cf5ceff5..78ebddb2e6 100644 --- a/osu.Game/Online/Solo/SubmitSoloScoreRequest.cs +++ b/osu.Game/Online/Solo/SubmitSoloScoreRequest.cs @@ -12,17 +12,17 @@ namespace osu.Game.Online.Solo { public class SubmitSoloScoreRequest : APIRequest { + public readonly SubmittableScore Score; + private readonly long scoreId; private readonly int beatmapId; - private readonly SubmittableScore score; - public SubmitSoloScoreRequest(int beatmapId, long scoreId, ScoreInfo scoreInfo) { this.beatmapId = beatmapId; this.scoreId = scoreId; - score = new SubmittableScore(scoreInfo); + Score = new SubmittableScore(scoreInfo); } protected override WebRequest CreateWebRequest() @@ -33,7 +33,7 @@ namespace osu.Game.Online.Solo req.Method = HttpMethod.Put; req.Timeout = 30000; - req.AddRaw(JsonConvert.SerializeObject(score, new JsonSerializerSettings + req.AddRaw(JsonConvert.SerializeObject(Score, new JsonSerializerSettings { ReferenceLoopHandling = ReferenceLoopHandling.Ignore })); diff --git a/osu.Game/Overlays/AccountCreation/ScreenEntry.cs b/osu.Game/Overlays/AccountCreation/ScreenEntry.cs index 2ba8fc3ae2..a11b234cb1 100644 --- a/osu.Game/Overlays/AccountCreation/ScreenEntry.cs +++ b/osu.Game/Overlays/AccountCreation/ScreenEntry.cs @@ -152,12 +152,12 @@ namespace osu.Game.Overlays.AccountCreation loadingLayer.Hide(); if (host?.OnScreenKeyboardOverlapsGameWindow != true) - focusNextTextbox(); + focusNextTextBox(); } private void performRegistration() { - if (focusNextTextbox()) + if (focusNextTextBox()) { registerShake.Shake(); return; @@ -209,19 +209,19 @@ namespace osu.Game.Overlays.AccountCreation }); } - private bool focusNextTextbox() + private bool focusNextTextBox() { - var nextTextbox = nextUnfilledTextbox(); + var nextTextBox = nextUnfilledTextBox(); - if (nextTextbox != null) + if (nextTextBox != null) { - Schedule(() => GetContainingInputManager().ChangeFocus(nextTextbox)); + Schedule(() => GetContainingInputManager().ChangeFocus(nextTextBox)); return true; } return false; } - private OsuTextBox nextUnfilledTextbox() => textboxes.FirstOrDefault(t => string.IsNullOrEmpty(t.Text)); + private OsuTextBox nextUnfilledTextBox() => textboxes.FirstOrDefault(t => string.IsNullOrEmpty(t.Text)); } } diff --git a/osu.Game/Overlays/BeatmapListingOverlay.cs b/osu.Game/Overlays/BeatmapListingOverlay.cs index 0c2bad95d6..a8e5201aa3 100644 --- a/osu.Game/Overlays/BeatmapListingOverlay.cs +++ b/osu.Game/Overlays/BeatmapListingOverlay.cs @@ -79,7 +79,6 @@ namespace osu.Game.Overlays Padding = new MarginPadding { Horizontal = 20 }, Children = new Drawable[] { - foundContent = new FillFlowContainer(), notFoundContent = new NotFoundDrawable(), supporterRequiredContent = new SupporterRequiredDrawable(), } @@ -140,7 +139,7 @@ namespace osu.Game.Overlays if (searchResult.Type == BeatmapListingFilterControl.SearchResultType.SupporterOnlyFilters) { supporterRequiredContent.UpdateText(searchResult.SupporterOnlyFiltersUsed); - addContentToPlaceholder(supporterRequiredContent); + addContentToResultsArea(supporterRequiredContent); return; } @@ -151,13 +150,13 @@ namespace osu.Game.Overlays //No matches case if (!newCards.Any()) { - addContentToPlaceholder(notFoundContent); + addContentToResultsArea(notFoundContent); return; } var content = createCardContainerFor(newCards); - panelLoadTask = LoadComponentAsync(foundContent = content, addContentToPlaceholder, (cancellationToken = new CancellationTokenSource()).Token); + panelLoadTask = LoadComponentAsync(foundContent = content, addContentToResultsArea, (cancellationToken = new CancellationTokenSource()).Token); } else { @@ -186,13 +185,17 @@ namespace osu.Game.Overlays AutoSizeAxes = Axes.Y, Spacing = new Vector2(10), Alpha = 0, - Margin = new MarginPadding { Vertical = 15 }, + Margin = new MarginPadding + { + Vertical = 15, + Bottom = ExpandedContentScrollContainer.HEIGHT + }, ChildrenEnumerable = newCards }; return content; } - private void addContentToPlaceholder(Drawable content) + private void addContentToResultsArea(Drawable content) { Loading.Hide(); lastFetchDisplayedTime = Time.Current; @@ -204,37 +207,27 @@ namespace osu.Game.Overlays if (lastContent != null) { - lastContent.FadeOut(100, Easing.OutQuint); - - // Consider the case when the new content is smaller than the last content. - // If the auto-size computation is delayed until fade out completes, the background remain high for too long making the resulting transition to the smaller height look weird. - // At the same time, if the last content's height is bypassed immediately, there is a period where the new content is at Alpha = 0 when the auto-sized height will be 0. - // To resolve both of these issues, the bypass is delayed until a point when the content transitions (fade-in and fade-out) overlap and it looks good to do so. - var sequence = lastContent.Delay(25).Schedule(() => lastContent.BypassAutoSizeAxes = Axes.Y); - - if (lastContent == foundContent) - { - sequence.Then().Schedule(() => - { - foundContent.Expire(); - foundContent = null; - }); - } + lastContent.FadeOut(); + if (!isPlaceholderContent(lastContent)) + lastContent.Expire(); } if (!content.IsAlive) panelTarget.Add(content); - content.FadeInFromZero(200, Easing.OutQuint); + content.FadeInFromZero(); currentContent = content; - // currentContent may be one of the placeholders, and still have BypassAutoSizeAxes set to Y from the last fade-out. - // restore to the initial state. - currentContent.BypassAutoSizeAxes = Axes.None; } + /// + /// Whether is a static placeholder reused multiple times by this overlay. + /// + private bool isPlaceholderContent(Drawable drawable) + => drawable == notFoundContent || drawable == supporterRequiredContent; + private void onCardSizeChanged() { - if (foundContent == null || !foundContent.Any()) + if (foundContent?.IsAlive != true || !foundContent.Any()) return; Loading.Show(); @@ -259,10 +252,6 @@ namespace osu.Game.Overlays public class NotFoundDrawable : CompositeDrawable { - // required for scheduled tasks to complete correctly - // (see `addContentToPlaceholder()` and the scheduled `BypassAutoSizeAxes` set during fade-out in outer class above) - public override bool IsPresent => base.IsPresent || Scheduler.HasPendingTasks; - public NotFoundDrawable() { RelativeSizeAxes = Axes.X; @@ -307,10 +296,6 @@ namespace osu.Game.Overlays // (https://github.com/ppy/osu-framework/issues/4530) public class SupporterRequiredDrawable : CompositeDrawable { - // required for scheduled tasks to complete correctly - // (see `addContentToPlaceholder()` and the scheduled `BypassAutoSizeAxes` set during fade-out in outer class above) - public override bool IsPresent => base.IsPresent || Scheduler.HasPendingTasks; - private LinkFlowContainer supporterRequiredText; public SupporterRequiredDrawable() diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs index 018faf2011..2c78fa264e 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs @@ -128,6 +128,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores if (showPerformancePoints) columns.Add(new TableColumn(BeatmapsetsStrings.ShowScoreboardHeaderspp, Anchor.CentreLeft, new Dimension(GridSizeMode.Absolute, 30))); + columns.Add(new TableColumn(BeatmapsetsStrings.ShowScoreboardHeadersTime, Anchor.CentreLeft, new Dimension(GridSizeMode.AutoSize))); columns.Add(new TableColumn(BeatmapsetsStrings.ShowScoreboardHeadersMods, Anchor.CentreLeft, new Dimension(GridSizeMode.AutoSize))); return columns.ToArray(); @@ -202,6 +203,11 @@ namespace osu.Game.Overlays.BeatmapSet.Scores }); } + content.Add(new ScoreboardTime(score.Date, text_size) + { + Margin = new MarginPadding { Right = 10 } + }); + content.Add(new FillFlowContainer { Direction = FillDirection.Horizontal, diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoreboardTime.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoreboardTime.cs new file mode 100644 index 0000000000..ff1d3490b4 --- /dev/null +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoreboardTime.cs @@ -0,0 +1,56 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using Humanizer; +using osu.Game.Graphics; +using osu.Game.Resources.Localisation.Web; + +namespace osu.Game.Overlays.BeatmapSet.Scores +{ + public class ScoreboardTime : DrawableDate + { + public ScoreboardTime(DateTimeOffset date, float textSize = OsuFont.DEFAULT_FONT_SIZE, bool italic = true) + : base(date, textSize, italic) + { + } + + protected override string Format() + { + var now = DateTime.Now; + var difference = now - Date; + + // web uses momentjs's custom locales to format the date for the purposes of the scoreboard. + // this is intended to be a best-effort, more legible approximation of that. + // compare: + // * https://github.com/ppy/osu-web/blob/a8f5a68fb435cb19a4faa4c7c4bce08c4f096933/resources/assets/lib/scoreboard-time.tsx + // * https://momentjs.com/docs/#/customization/ (reference for the customisation format) + + // TODO: support localisation (probably via `CommonStrings.CountHours()` etc.) + // requires pluralisable string support framework-side + + if (difference.TotalHours < 1) + return CommonStrings.TimeNow.ToString(); + if (difference.TotalDays < 1) + return "hr".ToQuantity((int)difference.TotalHours); + + // this is where this gets more complicated because of how the calendar works. + // since there's no `TotalMonths` / `TotalYears`, we have to iteratively add months/years + // and test against cutoff dates to determine how many months/years to show. + + if (Date > now.AddMonths(-1)) + return difference.TotalDays < 2 ? "1dy" : $"{(int)difference.TotalDays}dys"; + + for (int months = 1; months <= 11; ++months) + { + if (Date > now.AddMonths(-(months + 1))) + return months == 1 ? "1mo" : $"{months}mos"; + } + + int years = 1; + while (Date <= now.AddYears(-(years + 1))) + years += 1; + return years == 1 ? "1yr" : $"{years}yrs"; + } + } +} diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs index 695661d5c9..a40f29abf2 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs @@ -7,6 +7,7 @@ using System.Threading; using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -79,14 +80,16 @@ namespace osu.Game.Overlays.BeatmapSet.Scores }; scoreManager.OrderByTotalScoreAsync(value.Scores.Select(s => s.CreateScoreInfo(rulesets, beatmapInfo)).ToArray(), loadCancellationSource.Token) - .ContinueWith(ordered => Schedule(() => + .ContinueWith(task => Schedule(() => { if (loadCancellationSource.IsCancellationRequested) return; - var topScore = ordered.Result.First(); + var scores = task.GetResultSafely(); - scoreTable.DisplayScores(ordered.Result, apiBeatmap.Status.GrantsPerformancePoints()); + var topScore = scores.First(); + + scoreTable.DisplayScores(scores, apiBeatmap.Status.GrantsPerformancePoints()); scoreTable.Show(); var userScore = value.UserScore; diff --git a/osu.Game/Overlays/ChatOverlay.cs b/osu.Game/Overlays/ChatOverlay.cs index 72473d5750..fde9d28b43 100644 --- a/osu.Game/Overlays/ChatOverlay.cs +++ b/osu.Game/Overlays/ChatOverlay.cs @@ -38,7 +38,7 @@ namespace osu.Game.Overlays public LocalisableString Title => ChatStrings.HeaderTitle; public LocalisableString Description => ChatStrings.HeaderDescription; - private const float textbox_height = 60; + private const float text_box_height = 60; private const float channel_selection_min_height = 0.3f; [Resolved] @@ -50,7 +50,7 @@ namespace osu.Game.Overlays private LoadingSpinner loading; - private FocusedTextBox textbox; + private FocusedTextBox textBox; private const int transition_length = 500; @@ -133,7 +133,7 @@ namespace osu.Game.Overlays RelativeSizeAxes = Axes.Both, Padding = new MarginPadding { - Bottom = textbox_height + Bottom = text_box_height }, }, new Container @@ -141,7 +141,7 @@ namespace osu.Game.Overlays Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft, RelativeSizeAxes = Axes.X, - Height = textbox_height, + Height = text_box_height, Padding = new MarginPadding { Top = padding * 2, @@ -151,7 +151,7 @@ namespace osu.Game.Overlays }, Children = new Drawable[] { - textbox = new FocusedTextBox + textBox = new FocusedTextBox { RelativeSizeAxes = Axes.Both, Height = 1, @@ -197,7 +197,7 @@ namespace osu.Game.Overlays }, }; - textbox.OnCommit += postMessage; + textBox.OnCommit += postMessage; ChannelTabControl.Current.ValueChanged += current => channelManager.CurrentChannel.Value = current.NewValue; ChannelTabControl.ChannelSelectorActive.ValueChanged += active => ChannelSelectionOverlay.State.Value = active.NewValue ? Visibility.Visible : Visibility.Hidden; @@ -208,12 +208,12 @@ namespace osu.Game.Overlays if (state.NewValue == Visibility.Visible) { - textbox.HoldFocus = false; + textBox.HoldFocus = false; if (1f - ChatHeight.Value < channel_selection_min_height) this.TransformBindableTo(ChatHeight, 1f - channel_selection_min_height, 800, Easing.OutQuint); } else - textbox.HoldFocus = true; + textBox.HoldFocus = true; }; ChannelSelectionOverlay.OnRequestJoin = channel => channelManager.JoinChannel(channel); @@ -253,7 +253,7 @@ namespace osu.Game.Overlays { if (e.NewValue == null) { - textbox.Current.Disabled = true; + textBox.Current.Disabled = true; currentChannelContainer.Clear(false); ChannelSelectionOverlay.Show(); return; @@ -262,7 +262,7 @@ namespace osu.Game.Overlays if (e.NewValue is ChannelSelectorTabItem.ChannelSelectorTabChannel) return; - textbox.Current.Disabled = e.NewValue.ReadOnly; + textBox.Current.Disabled = e.NewValue.ReadOnly; if (ChannelTabControl.Current.Value != e.NewValue) Scheduler.Add(() => ChannelTabControl.Current.Value = e.NewValue); @@ -402,7 +402,7 @@ namespace osu.Game.Overlays protected override void OnFocus(FocusEvent e) { // this is necessary as textbox is masked away and therefore can't get focus :( - textbox.TakeFocus(); + textBox.TakeFocus(); base.OnFocus(e); } @@ -411,7 +411,7 @@ namespace osu.Game.Overlays this.MoveToY(0, transition_length, Easing.OutQuint); this.FadeIn(transition_length, Easing.OutQuint); - textbox.HoldFocus = true; + textBox.HoldFocus = true; base.PopIn(); } @@ -423,7 +423,7 @@ namespace osu.Game.Overlays ChannelSelectionOverlay.Hide(); - textbox.HoldFocus = false; + textBox.HoldFocus = false; base.PopOut(); } @@ -481,9 +481,9 @@ namespace osu.Game.Overlays } } - private void postMessage(TextBox textbox, bool newText) + private void postMessage(TextBox textBox, bool newText) { - string text = textbox.Text.Trim(); + string text = textBox.Text.Trim(); if (string.IsNullOrWhiteSpace(text)) return; @@ -493,7 +493,7 @@ namespace osu.Game.Overlays else channelManager.PostMessage(text); - textbox.Text = string.Empty; + textBox.Text = string.Empty; } private class TabsArea : Container diff --git a/osu.Game/Overlays/Comments/DrawableComment.cs b/osu.Game/Overlays/Comments/DrawableComment.cs index 43f4177bd0..3286b6c5c0 100644 --- a/osu.Game/Overlays/Comments/DrawableComment.cs +++ b/osu.Game/Overlays/Comments/DrawableComment.cs @@ -362,15 +362,15 @@ namespace osu.Game.Overlays.Comments private void updateButtonsState() { - int loadedReplesCount = loadedReplies.Count; - bool hasUnloadedReplies = loadedReplesCount != Comment.RepliesCount; + int loadedRepliesCount = loadedReplies.Count; + bool hasUnloadedReplies = loadedRepliesCount != Comment.RepliesCount; - loadRepliesButton.FadeTo(hasUnloadedReplies && loadedReplesCount == 0 ? 1 : 0); - showMoreButton.FadeTo(hasUnloadedReplies && loadedReplesCount > 0 ? 1 : 0); - showRepliesButton.FadeTo(loadedReplesCount != 0 ? 1 : 0); + loadRepliesButton.FadeTo(hasUnloadedReplies && loadedRepliesCount == 0 ? 1 : 0); + showMoreButton.FadeTo(hasUnloadedReplies && loadedRepliesCount > 0 ? 1 : 0); + showRepliesButton.FadeTo(loadedRepliesCount != 0 ? 1 : 0); if (Comment.IsTopLevel) - chevronButton.FadeTo(loadedReplesCount != 0 ? 1 : 0); + chevronButton.FadeTo(loadedRepliesCount != 0 ? 1 : 0); showMoreButton.IsLoading = loadRepliesButton.IsLoading = false; } diff --git a/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs b/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs index 0844975906..fde20575fc 100644 --- a/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs +++ b/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs @@ -5,6 +5,7 @@ using System.Collections.Specialized; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Screens; @@ -61,17 +62,19 @@ namespace osu.Game.Overlays.Dashboard case NotifyCollectionChangedAction.Add: foreach (int id in e.NewItems.OfType().ToArray()) { - users.GetUserAsync(id).ContinueWith(u => + users.GetUserAsync(id).ContinueWith(task => { - if (u.Result == null) return; + var user = task.GetResultSafely(); + + if (user == null) return; Schedule(() => { // user may no longer be playing. - if (!playingUsers.Contains(u.Result.Id)) + if (!playingUsers.Contains(user.Id)) return; - userFlow.Add(createUserPanel(u.Result)); + userFlow.Add(createUserPanel(user)); }); }); } diff --git a/osu.Game/Overlays/Settings/Sidebar.cs b/osu.Game/Overlays/ExpandingButtonContainer.cs similarity index 60% rename from osu.Game/Overlays/Settings/Sidebar.cs rename to osu.Game/Overlays/ExpandingButtonContainer.cs index 93b1b19b17..4eb8c47a1f 100644 --- a/osu.Game/Overlays/Settings/Sidebar.cs +++ b/osu.Game/Overlays/ExpandingButtonContainer.cs @@ -1,47 +1,46 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using System; using System.Linq; using osu.Framework; -using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; +using osu.Framework.Testing; using osu.Framework.Threading; -using osu.Game.Graphics; using osu.Game.Graphics.Containers; +using osu.Game.Graphics.UserInterface; using osuTK; -namespace osu.Game.Overlays.Settings +namespace osu.Game.Overlays { - public class Sidebar : Container, IStateful + public abstract class ExpandingButtonContainer : Container, IStateful { - private readonly Box background; - private readonly FillFlowContainer content; - public const float DEFAULT_WIDTH = 70; - public const int EXPANDED_WIDTH = 200; + private readonly float contractedWidth; + private readonly float expandedWidth; public event Action StateChanged; - protected override Container Content => content; + protected override Container Content => FillFlow; - public Sidebar() + protected FillFlowContainer FillFlow { get; } + + protected ExpandingButtonContainer(float contractedWidth, float expandedWidth) { + this.contractedWidth = contractedWidth; + this.expandedWidth = expandedWidth; + RelativeSizeAxes = Axes.Y; + Width = contractedWidth; + InternalChildren = new Drawable[] { - background = new Box - { - Colour = OsuColour.Gray(0.02f), - RelativeSizeAxes = Axes.Both, - }, new SidebarScrollContainer { Children = new[] { - content = new FillFlowContainer + FillFlow = new FillFlowContainer { Origin = Anchor.CentreLeft, Anchor = Anchor.CentreLeft, @@ -54,12 +53,6 @@ namespace osu.Game.Overlays.Settings }; } - [BackgroundDependencyLoader] - private void load(OverlayColourProvider colourProvider) - { - background.Colour = colourProvider.Background5; - } - private ScheduledDelegate expandEvent; private ExpandedState state; @@ -72,7 +65,7 @@ namespace osu.Game.Overlays.Settings protected override void OnHoverLost(HoverLostEvent e) { expandEvent?.Cancel(); - lastHoveredButton = null; + hoveredButton = null; State = ExpandedState.Contracted; base.OnHoverLost(e); @@ -107,11 +100,11 @@ namespace osu.Game.Overlays.Settings switch (state) { default: - this.ResizeTo(new Vector2(DEFAULT_WIDTH, Height), 500, Easing.OutQuint); + this.ResizeTo(new Vector2(contractedWidth, Height), 500, Easing.OutQuint); break; case ExpandedState.Expanded: - this.ResizeTo(new Vector2(EXPANDED_WIDTH, Height), 500, Easing.OutQuint); + this.ResizeTo(new Vector2(expandedWidth, Height), 500, Easing.OutQuint); break; } @@ -119,24 +112,24 @@ namespace osu.Game.Overlays.Settings } } - private Drawable lastHoveredButton; - - private Drawable hoveredButton => content.Children.FirstOrDefault(c => c.IsHovered); + private Drawable hoveredButton; private void queueExpandIfHovering() { - // only expand when we hover a different button. - if (lastHoveredButton == hoveredButton) return; + // if the same button is hovered, let the scheduled expand play out.. + if (hoveredButton?.IsHovered == true) + return; - if (!IsHovered) return; + // ..otherwise check whether a new button is hovered, and if so, queue a new hover operation. - if (State != ExpandedState.Expanded) - { - expandEvent?.Cancel(); + // usually we wouldn't use ChildrenOfType in implementations, but this is the simplest way + // to handle cases like the editor where the buttons may be nested within a child hierarchy. + hoveredButton = FillFlow.ChildrenOfType().FirstOrDefault(c => c.IsHovered); + + expandEvent?.Cancel(); + + if (hoveredButton?.IsHovered == true && State != ExpandedState.Expanded) expandEvent = Scheduler.AddDelayed(() => State = ExpandedState.Expanded, 750); - } - - lastHoveredButton = hoveredButton; } } diff --git a/osu.Game/Overlays/Notifications/ProgressCompletionNotification.cs b/osu.Game/Overlays/Notifications/ProgressCompletionNotification.cs index feffb4fa66..754f9bd600 100644 --- a/osu.Game/Overlays/Notifications/ProgressCompletionNotification.cs +++ b/osu.Game/Overlays/Notifications/ProgressCompletionNotification.cs @@ -18,7 +18,7 @@ namespace osu.Game.Overlays.Notifications [BackgroundDependencyLoader] private void load(OsuColour colours) { - IconBackgound.Colour = ColourInfo.GradientVertical(colours.GreenDark, colours.GreenLight); + IconBackground.Colour = ColourInfo.GradientVertical(colours.GreenDark, colours.GreenLight); } } } diff --git a/osu.Game/Overlays/Notifications/SimpleNotification.cs b/osu.Game/Overlays/Notifications/SimpleNotification.cs index 17ec12a4ca..c32e40ffc8 100644 --- a/osu.Game/Overlays/Notifications/SimpleNotification.cs +++ b/osu.Game/Overlays/Notifications/SimpleNotification.cs @@ -43,13 +43,13 @@ namespace osu.Game.Overlays.Notifications private readonly TextFlowContainer textDrawable; private readonly SpriteIcon iconDrawable; - protected Box IconBackgound; + protected Box IconBackground; public SimpleNotification() { IconContent.AddRange(new Drawable[] { - IconBackgound = new Box + IconBackground = new Box { RelativeSizeAxes = Axes.Both, Colour = ColourInfo.GradientVertical(OsuColour.Gray(0.2f), OsuColour.Gray(0.6f)) diff --git a/osu.Game/Overlays/Profile/Header/Components/PreviousUsernames.cs b/osu.Game/Overlays/Profile/Header/Components/PreviousUsernames.cs index 229bbaef83..49744e885a 100644 --- a/osu.Game/Overlays/Profile/Header/Components/PreviousUsernames.cs +++ b/osu.Game/Overlays/Profile/Header/Components/PreviousUsernames.cs @@ -98,7 +98,7 @@ namespace osu.Game.Overlays.Profile.Header.Components [BackgroundDependencyLoader] private void load(OsuColour colours) { - background.Colour = colours.GreySeafoamDarker; + background.Colour = colours.GreySeaFoamDarker; } protected override void LoadComplete() diff --git a/osu.Game/Overlays/Profile/Sections/Historical/PaginatedMostPlayedBeatmapContainer.cs b/osu.Game/Overlays/Profile/Sections/Historical/PaginatedMostPlayedBeatmapContainer.cs index c943d129cc..ad1192a13a 100644 --- a/osu.Game/Overlays/Profile/Sections/Historical/PaginatedMostPlayedBeatmapContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Historical/PaginatedMostPlayedBeatmapContainer.cs @@ -28,7 +28,7 @@ namespace osu.Game.Overlays.Profile.Sections.Historical ItemsContainer.Direction = FillDirection.Vertical; } - protected override int GetCount(APIUser user) => user.BeatmapPlaycountsCount; + protected override int GetCount(APIUser user) => user.BeatmapPlayCountsCount; protected override APIRequest> CreateRequest() => new GetUserMostPlayedBeatmapsRequest(User.Value.Id, VisiblePages++, ItemsPerPage); diff --git a/osu.Game/Overlays/Profile/Sections/Historical/PlayHistorySubsection.cs b/osu.Game/Overlays/Profile/Sections/Historical/PlayHistorySubsection.cs index bca88318d5..51d704a6b0 100644 --- a/osu.Game/Overlays/Profile/Sections/Historical/PlayHistorySubsection.cs +++ b/osu.Game/Overlays/Profile/Sections/Historical/PlayHistorySubsection.cs @@ -17,6 +17,6 @@ namespace osu.Game.Overlays.Profile.Sections.Historical { } - protected override APIUserHistoryCount[] GetValues(APIUser user) => user?.MonthlyPlaycounts; + protected override APIUserHistoryCount[] GetValues(APIUser user) => user?.MonthlyPlayCounts; } } diff --git a/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs b/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs index d0cfe9fa54..ce05beffd1 100644 --- a/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs +++ b/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs @@ -50,7 +50,7 @@ namespace osu.Game.Overlays.Profile.Sections.Kudosu [BackgroundDependencyLoader] private void load() { - date.Colour = colours.GreySeafoamLighter; + date.Colour = colours.GreySeaFoamLighter; var formattedSource = MessageFormatter.FormatText(getString(historyItem)); linkFlowContainer.AddLinks(formattedSource.Text, formattedSource.Links); } diff --git a/osu.Game/Overlays/Rankings/SpotlightsLayout.cs b/osu.Game/Overlays/Rankings/SpotlightsLayout.cs index fb01656c24..7f5d096fe2 100644 --- a/osu.Game/Overlays/Rankings/SpotlightsLayout.cs +++ b/osu.Game/Overlays/Rankings/SpotlightsLayout.cs @@ -140,6 +140,7 @@ namespace osu.Game.Overlays.Rankings { AutoSizeAxes = Axes.Y, RelativeSizeAxes = Axes.X, + Margin = new MarginPadding { Bottom = ExpandedContentScrollContainer.HEIGHT }, Spacing = new Vector2(10), Children = response.BeatmapSets.Select(b => new BeatmapCardNormal(b) { diff --git a/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs b/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs index 6e6230f958..fd69b6c80a 100644 --- a/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs +++ b/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs @@ -54,13 +54,15 @@ namespace osu.Game.Overlays.Rankings.Tables Spacing = new Vector2(0, row_spacing), }); - rankings.ForEach(_ => backgroundFlow.Add(new TableRowBackground { Height = row_height })); + rankings.ForEach(s => backgroundFlow.Add(CreateRowBackground(s))); Columns = mainHeaders.Concat(CreateAdditionalHeaders()).Cast().ToArray(); - Content = rankings.Select((s, i) => createContent((page - 1) * items_per_page + i, s)).ToArray().ToRectangular(); + Content = rankings.Select((s, i) => CreateRowContent((page - 1) * items_per_page + i, s)).ToArray().ToRectangular(); } - private Drawable[] createContent(int index, TModel item) => new Drawable[] { createIndexDrawable(index), createMainContent(item) }.Concat(CreateAdditionalContent(item)).ToArray(); + protected virtual Drawable CreateRowBackground(TModel item) => new TableRowBackground { Height = row_height }; + + protected virtual Drawable[] CreateRowContent(int index, TModel item) => new Drawable[] { createIndexDrawable(index), createMainContent(item) }.Concat(CreateAdditionalContent(item)).ToArray(); private static RankingsTableColumn[] mainHeaders => new[] { diff --git a/osu.Game/Overlays/Rankings/Tables/UserBasedTable.cs b/osu.Game/Overlays/Rankings/Tables/UserBasedTable.cs index cc2ef55a2b..5d150c9535 100644 --- a/osu.Game/Overlays/Rankings/Tables/UserBasedTable.cs +++ b/osu.Game/Overlays/Rankings/Tables/UserBasedTable.cs @@ -24,6 +24,31 @@ namespace osu.Game.Overlays.Rankings.Tables protected virtual IEnumerable GradeColumns => new List { RankingsStrings.Statss, RankingsStrings.Stats, RankingsStrings.Stata }; + protected override Drawable CreateRowBackground(UserStatistics item) + { + var background = base.CreateRowBackground(item); + + // see: https://github.com/ppy/osu-web/blob/9de00a0b874c56893d98261d558d78d76259d81b/resources/views/multiplayer/rooms/_rankings_table.blade.php#L23 + if (!item.User.Active) + background.Alpha = 0.5f; + + return background; + } + + protected override Drawable[] CreateRowContent(int index, UserStatistics item) + { + var content = base.CreateRowContent(index, item); + + // see: https://github.com/ppy/osu-web/blob/9de00a0b874c56893d98261d558d78d76259d81b/resources/views/multiplayer/rooms/_rankings_table.blade.php#L23 + if (!item.User.Active) + { + foreach (var d in content) + d.Alpha = 0.5f; + } + + return content; + } + protected override RankingsTableColumn[] CreateAdditionalHeaders() => new[] { new RankingsTableColumn(RankingsStrings.StatAccuracy, Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), diff --git a/osu.Game/Overlays/Settings/Sections/Gameplay/AudioSettings.cs b/osu.Game/Overlays/Settings/Sections/Gameplay/AudioSettings.cs index dba64d695a..5029c6a617 100644 --- a/osu.Game/Overlays/Settings/Sections/Gameplay/AudioSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Gameplay/AudioSettings.cs @@ -14,20 +14,23 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay protected override LocalisableString Header => GameplaySettingsStrings.AudioHeader; [BackgroundDependencyLoader] - private void load(OsuConfigManager config) + private void load(OsuConfigManager config, OsuConfigManager osuConfig) { Children = new Drawable[] { - new SettingsCheckbox + new SettingsSlider { - LabelText = GameplaySettingsStrings.PositionalHitsounds, - Current = config.GetBindable(OsuSetting.PositionalHitSounds) + LabelText = AudioSettingsStrings.PositionalLevel, + Keywords = new[] { @"positional", @"balance" }, + Current = osuConfig.GetBindable(OsuSetting.PositionalHitsoundsLevel), + KeyboardStep = 0.01f, + DisplayAsPercentage = true }, new SettingsCheckbox { LabelText = GameplaySettingsStrings.AlwaysPlayFirstComboBreak, Current = config.GetBindable(OsuSetting.AlwaysPlayFirstComboBreak) - }, + } }; } } diff --git a/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs b/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs index 6bcb5ef715..158d8811b5 100644 --- a/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs @@ -4,6 +4,7 @@ using System.Threading.Tasks; using osu.Framework; using osu.Framework.Allocation; +using osu.Framework.Extensions; using osu.Framework.Graphics.Sprites; using osu.Framework.Localisation; using osu.Framework.Platform; @@ -45,9 +46,9 @@ namespace osu.Game.Overlays.Settings.Sections.General Action = () => { checkForUpdatesButton.Enabled.Value = false; - Task.Run(updateManager.CheckForUpdateAsync).ContinueWith(t => Schedule(() => + Task.Run(updateManager.CheckForUpdateAsync).ContinueWith(task => Schedule(() => { - if (!t.Result) + if (!task.GetResultSafely()) { notifications?.Post(new SimpleNotification { diff --git a/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs b/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs index c94b418331..802d442ced 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs @@ -107,8 +107,8 @@ namespace osu.Game.Overlays.Settings.Sections.Input t.NewLine(); t.AddText("If your tablet is not detected, please read "); t.AddLink("this FAQ", LinkAction.External, RuntimeInfo.OS == RuntimeInfo.Platform.Windows - ? @"https://github.com/OpenTabletDriver/OpenTabletDriver/wiki/Windows-FAQ" - : @"https://github.com/OpenTabletDriver/OpenTabletDriver/wiki/Linux-FAQ"); + ? @"https://opentabletdriver.net/Wiki/FAQ/Windows" + : @"https://opentabletdriver.net/Wiki/FAQ/Linux"); t.AddText(" for troubleshooting steps."); } }), diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/DirectorySelectScreen.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/DirectorySelectScreen.cs index 0814e3c824..98bc8d88be 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/DirectorySelectScreen.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/DirectorySelectScreen.cs @@ -63,7 +63,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance new Box { RelativeSizeAxes = Axes.Both, - Colour = colours.GreySeafoamDark + Colour = colours.GreySeaFoamDark }, new GridContainer { diff --git a/osu.Game/Overlays/Settings/SettingsSidebar.cs b/osu.Game/Overlays/Settings/SettingsSidebar.cs new file mode 100644 index 0000000000..e6ce90c33e --- /dev/null +++ b/osu.Game/Overlays/Settings/SettingsSidebar.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 osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Shapes; + +namespace osu.Game.Overlays.Settings +{ + public class SettingsSidebar : ExpandingButtonContainer + { + public const float DEFAULT_WIDTH = 70; + public const int EXPANDED_WIDTH = 200; + + public SettingsSidebar() + : base(DEFAULT_WIDTH, EXPANDED_WIDTH) + { + } + + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider) + { + AddInternal(new Box + { + Colour = colourProvider.Background5, + RelativeSizeAxes = Axes.Both, + Depth = float.MaxValue + }); + } + } +} diff --git a/osu.Game/Overlays/Settings/SidebarIconButton.cs b/osu.Game/Overlays/Settings/SidebarIconButton.cs index fd57996b1b..6f3d3d5d52 100644 --- a/osu.Game/Overlays/Settings/SidebarIconButton.cs +++ b/osu.Game/Overlays/Settings/SidebarIconButton.cs @@ -62,14 +62,14 @@ namespace osu.Game.Overlays.Settings { textIconContent = new Container { - Width = Sidebar.DEFAULT_WIDTH, + Width = SettingsSidebar.DEFAULT_WIDTH, RelativeSizeAxes = Axes.Y, Colour = OsuColour.Gray(0.6f), Children = new Drawable[] { headerText = new OsuSpriteText { - Position = new Vector2(Sidebar.DEFAULT_WIDTH + 10, 0), + Position = new Vector2(SettingsSidebar.DEFAULT_WIDTH + 10, 0), Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, }, diff --git a/osu.Game/Overlays/SettingsPanel.cs b/osu.Game/Overlays/SettingsPanel.cs index 0ceb7fc50d..ba7118cffe 100644 --- a/osu.Game/Overlays/SettingsPanel.cs +++ b/osu.Game/Overlays/SettingsPanel.cs @@ -27,7 +27,7 @@ namespace osu.Game.Overlays public const float TRANSITION_LENGTH = 600; - private const float sidebar_width = Sidebar.DEFAULT_WIDTH; + private const float sidebar_width = SettingsSidebar.DEFAULT_WIDTH; /// /// The width of the settings panel content, excluding the sidebar. @@ -43,7 +43,7 @@ namespace osu.Game.Overlays protected override Container Content => ContentContainer; - protected Sidebar Sidebar; + protected SettingsSidebar Sidebar; private SidebarIconButton selectedSidebarButton; public SettingsSectionsContainer SectionsContainer { get; private set; } @@ -129,7 +129,7 @@ namespace osu.Game.Overlays if (showSidebar) { - AddInternal(Sidebar = new Sidebar { Width = sidebar_width }); + AddInternal(Sidebar = new SettingsSidebar { Width = sidebar_width }); } CreateSections()?.ForEach(AddSection); @@ -244,7 +244,7 @@ namespace osu.Game.Overlays if (selectedSidebarButton != null) selectedSidebarButton.Selected = false; - selectedSidebarButton = Sidebar.Children.FirstOrDefault(b => b.Section == section.NewValue); + selectedSidebarButton = Sidebar.Children.OfType().FirstOrDefault(b => b.Section == section.NewValue); if (selectedSidebarButton != null) selectedSidebarButton.Selected = true; diff --git a/osu.Game/Overlays/SettingsSubPanel.cs b/osu.Game/Overlays/SettingsSubPanel.cs index a65d792a9f..da806c09d3 100644 --- a/osu.Game/Overlays/SettingsSubPanel.cs +++ b/osu.Game/Overlays/SettingsSubPanel.cs @@ -39,7 +39,7 @@ namespace osu.Game.Overlays [BackgroundDependencyLoader] private void load() { - Size = new Vector2(Sidebar.DEFAULT_WIDTH); + Size = new Vector2(SettingsSidebar.DEFAULT_WIDTH); AddRange(new Drawable[] { diff --git a/osu.Game/Overlays/SettingsToolboxGroup.cs b/osu.Game/Overlays/SettingsToolboxGroup.cs new file mode 100644 index 0000000000..ca0980a9c9 --- /dev/null +++ b/osu.Game/Overlays/SettingsToolboxGroup.cs @@ -0,0 +1,190 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Caching; +using osu.Framework.Extensions.EnumExtensions; +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.Layout; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Overlays +{ + public abstract class SettingsToolboxGroup : Container + { + private const float transition_duration = 250; + private const int container_width = 270; + private const int border_thickness = 2; + private const int header_height = 30; + private const int corner_radius = 5; + + private const float fade_duration = 800; + private const float inactive_alpha = 0.5f; + + private readonly Cached headerTextVisibilityCache = new Cached(); + + private readonly FillFlowContainer content; + private readonly IconButton button; + + private bool expanded = true; + + public bool Expanded + { + get => expanded; + set + { + if (expanded == value) return; + + expanded = value; + + content.ClearTransforms(); + + if (expanded) + content.AutoSizeAxes = Axes.Y; + else + { + content.AutoSizeAxes = Axes.None; + content.ResizeHeightTo(0, transition_duration, Easing.OutQuint); + } + + updateExpanded(); + } + } + + private Color4 expandedColour; + + private readonly OsuSpriteText headerText; + + /// + /// Create a new instance. + /// + /// The title to be displayed in the header of this group. + protected SettingsToolboxGroup(string title) + { + AutoSizeAxes = Axes.Y; + Width = container_width; + Masking = true; + CornerRadius = corner_radius; + BorderColour = Color4.Black; + BorderThickness = border_thickness; + + InternalChildren = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black, + Alpha = 0.5f, + }, + new FillFlowContainer + { + Direction = FillDirection.Vertical, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Children = new Drawable[] + { + new Container + { + Name = @"Header", + Origin = Anchor.TopCentre, + Anchor = Anchor.TopCentre, + RelativeSizeAxes = Axes.X, + Height = header_height, + Children = new Drawable[] + { + headerText = new OsuSpriteText + { + Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft, + Text = title.ToUpperInvariant(), + Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 17), + Padding = new MarginPadding { Left = 10, Right = 30 }, + }, + button = new IconButton + { + Origin = Anchor.Centre, + Anchor = Anchor.CentreRight, + Position = new Vector2(-15, 0), + Icon = FontAwesome.Solid.Bars, + Scale = new Vector2(0.75f), + Action = () => Expanded = !Expanded, + }, + } + }, + content = new FillFlowContainer + { + Name = @"Content", + Origin = Anchor.TopCentre, + Anchor = Anchor.TopCentre, + Direction = FillDirection.Vertical, + RelativeSizeAxes = Axes.X, + AutoSizeDuration = transition_duration, + AutoSizeEasing = Easing.OutQuint, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding(15), + Spacing = new Vector2(0, 15), + } + } + }, + }; + } + + protected override bool OnInvalidate(Invalidation invalidation, InvalidationSource source) + { + if (invalidation.HasFlagFast(Invalidation.DrawSize)) + headerTextVisibilityCache.Invalidate(); + + return base.OnInvalidate(invalidation, source); + } + + protected override void Update() + { + base.Update(); + + if (!headerTextVisibilityCache.IsValid) + // These toolbox grouped may be contracted to only show icons. + // For now, let's hide the header to avoid text truncation weirdness in such cases. + headerText.FadeTo(headerText.DrawWidth < DrawWidth ? 1 : 0, 150, Easing.OutQuint); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + this.Delay(600).FadeTo(inactive_alpha, fade_duration, Easing.OutQuint); + updateExpanded(); + } + + protected override bool OnHover(HoverEvent e) + { + this.FadeIn(fade_duration, Easing.OutQuint); + return false; + } + + protected override void OnHoverLost(HoverLostEvent e) + { + this.FadeTo(inactive_alpha, fade_duration, Easing.OutQuint); + base.OnHoverLost(e); + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + expandedColour = colours.Yellow; + } + + private void updateExpanded() => button.FadeColour(expanded ? expandedColour : Color4.White, 200, Easing.InOutQuint); + + protected override Container Content => content; + + protected override bool OnMouseDown(MouseDownEvent e) => true; + } +} diff --git a/osu.Game/Rulesets/Edit/ToolboxGroup.cs b/osu.Game/Rulesets/Edit/EditorToolboxGroup.cs similarity index 71% rename from osu.Game/Rulesets/Edit/ToolboxGroup.cs rename to osu.Game/Rulesets/Edit/EditorToolboxGroup.cs index 22b2b05657..bde426f56a 100644 --- a/osu.Game/Rulesets/Edit/ToolboxGroup.cs +++ b/osu.Game/Rulesets/Edit/EditorToolboxGroup.cs @@ -2,13 +2,13 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; -using osu.Game.Screens.Play.PlayerSettings; +using osu.Game.Overlays; namespace osu.Game.Rulesets.Edit { - public class ToolboxGroup : PlayerSettingsGroup + public class EditorToolboxGroup : SettingsToolboxGroup { - public ToolboxGroup(string title) + public EditorToolboxGroup(string title) : base(title) { RelativeSizeAxes = Axes.X; diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index 4bbfe26d7b..92ea2db338 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -13,6 +13,7 @@ using osu.Framework.Input; using osu.Framework.Input.Events; using osu.Framework.Logging; using osu.Game.Beatmaps; +using osu.Game.Overlays; using osu.Game.Rulesets.Configuration; using osu.Game.Rulesets.Edit.Tools; using osu.Game.Rulesets.Mods; @@ -98,14 +99,11 @@ namespace osu.Game.Rulesets.Edit dependencies.CacheAs(Playfield); - const float toolbar_width = 200; - InternalChildren = new Drawable[] { new Container { Name = "Content", - Padding = new MarginPadding { Left = toolbar_width }, RelativeSizeAxes = Axes.Both, Children = new Drawable[] { @@ -117,20 +115,15 @@ namespace osu.Game.Rulesets.Edit .WithChild(BlueprintContainer = CreateBlueprintContainer()) } }, - new FillFlowContainer + new LeftToolboxFlow { - Name = "Sidebar", - RelativeSizeAxes = Axes.Y, - Width = toolbar_width, - Padding = new MarginPadding { Right = 10 }, - Spacing = new Vector2(10), Children = new Drawable[] { - new ToolboxGroup("toolbox (1-9)") + new EditorToolboxGroup("toolbox (1-9)") { Child = toolboxCollection = new EditorRadioButtonCollection { RelativeSizeAxes = Axes.X } }, - new ToolboxGroup("toggles (Q~P)") + new EditorToolboxGroup("toggles (Q~P)") { Child = togglesCollection = new FillFlowContainer { @@ -427,6 +420,18 @@ namespace osu.Game.Rulesets.Edit } #endregion + + private class LeftToolboxFlow : ExpandingButtonContainer + { + public LeftToolboxFlow() + : base(80, 200) + { + RelativeSizeAxes = Axes.Y; + Padding = new MarginPadding { Right = 10 }; + + FillFlow.Spacing = new Vector2(10); + } + } } /// diff --git a/osu.Game/Rulesets/Edit/ScrollingToolboxGroup.cs b/osu.Game/Rulesets/Edit/ScrollingToolboxGroup.cs index a54f574bff..9998a997b3 100644 --- a/osu.Game/Rulesets/Edit/ScrollingToolboxGroup.cs +++ b/osu.Game/Rulesets/Edit/ScrollingToolboxGroup.cs @@ -7,7 +7,7 @@ using osu.Game.Graphics.Containers; namespace osu.Game.Rulesets.Edit { - public class ScrollingToolboxGroup : ToolboxGroup + public class ScrollingToolboxGroup : EditorToolboxGroup { protected readonly OsuScrollContainer Scroll; diff --git a/osu.Game/Rulesets/Mods/ModBlockFail.cs b/osu.Game/Rulesets/Mods/ModBlockFail.cs index 1fde5abad4..8a9b0cddc8 100644 --- a/osu.Game/Rulesets/Mods/ModBlockFail.cs +++ b/osu.Game/Rulesets/Mods/ModBlockFail.cs @@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Mods public void ApplyToHUD(HUDOverlay overlay) { - overlay.ShowHealthbar.BindTo(showHealthBar); + overlay.ShowHealthBar.BindTo(showHealthBar); } } } diff --git a/osu.Game/Rulesets/Mods/ModDoubleTime.cs b/osu.Game/Rulesets/Mods/ModDoubleTime.cs index d12f48e973..d4c4dce0f5 100644 --- a/osu.Game/Rulesets/Mods/ModDoubleTime.cs +++ b/osu.Game/Rulesets/Mods/ModDoubleTime.cs @@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Mods { public override string Name => "Double Time"; public override string Acronym => "DT"; - public override IconUsage? Icon => OsuIcon.ModDoubletime; + public override IconUsage? Icon => OsuIcon.ModDoubleTime; public override ModType Type => ModType.DifficultyIncrease; public override string Description => "Zoooooooooom..."; diff --git a/osu.Game/Rulesets/Mods/ModHardRock.cs b/osu.Game/Rulesets/Mods/ModHardRock.cs index da838f9ea6..0a5348a8cf 100644 --- a/osu.Game/Rulesets/Mods/ModHardRock.cs +++ b/osu.Game/Rulesets/Mods/ModHardRock.cs @@ -12,7 +12,7 @@ namespace osu.Game.Rulesets.Mods { public override string Name => "Hard Rock"; public override string Acronym => "HR"; - public override IconUsage? Icon => OsuIcon.ModHardrock; + public override IconUsage? Icon => OsuIcon.ModHardRock; public override ModType Type => ModType.DifficultyIncrease; public override string Description => "Everything just got a bit harder..."; public override Type[] IncompatibleMods => new[] { typeof(ModEasy), typeof(ModDifficultyAdjust) }; diff --git a/osu.Game/Rulesets/Mods/ModNoFail.cs b/osu.Game/Rulesets/Mods/ModNoFail.cs index abf67c2e2d..5ebae17228 100644 --- a/osu.Game/Rulesets/Mods/ModNoFail.cs +++ b/osu.Game/Rulesets/Mods/ModNoFail.cs @@ -11,7 +11,7 @@ namespace osu.Game.Rulesets.Mods { public override string Name => "No Fail"; public override string Acronym => "NF"; - public override IconUsage? Icon => OsuIcon.ModNofail; + public override IconUsage? Icon => OsuIcon.ModNoFail; public override ModType Type => ModType.DifficultyReduction; public override string Description => "You can't fail, no matter what."; public override double ScoreMultiplier => 0.5; diff --git a/osu.Game/Rulesets/Mods/ModSuddenDeath.cs b/osu.Game/Rulesets/Mods/ModSuddenDeath.cs index 1abd353d20..c8b835f78a 100644 --- a/osu.Game/Rulesets/Mods/ModSuddenDeath.cs +++ b/osu.Game/Rulesets/Mods/ModSuddenDeath.cs @@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Mods { public override string Name => "Sudden Death"; public override string Acronym => "SD"; - public override IconUsage? Icon => OsuIcon.ModSuddendeath; + public override IconUsage? Icon => OsuIcon.ModSuddenDeath; public override ModType Type => ModType.DifficultyIncrease; public override string Description => "Miss and fail."; public override double ScoreMultiplier => 1; diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 01817147ae..4ab513bf19 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -123,9 +123,9 @@ namespace osu.Game.Rulesets.Objects.Drawables public readonly Bindable StartTimeBindable = new Bindable(); private readonly BindableList samplesBindable = new BindableList(); - private readonly Bindable userPositionalHitSounds = new Bindable(); - private readonly Bindable comboIndexBindable = new Bindable(); + + private readonly Bindable positionalHitsoundsLevel = new Bindable(); private readonly Bindable comboIndexWithOffsetsBindable = new Bindable(); protected override bool RequiresChildrenUpdate => true; @@ -168,7 +168,7 @@ namespace osu.Game.Rulesets.Objects.Drawables [BackgroundDependencyLoader] private void load(OsuConfigManager config, ISkinSource skinSource) { - config.BindWith(OsuSetting.PositionalHitSounds, userPositionalHitSounds); + config.BindWith(OsuSetting.PositionalHitsoundsLevel, positionalHitsoundsLevel); // Explicit non-virtual function call. base.AddInternal(Samples = new PausableSkinnableSound()); @@ -532,9 +532,10 @@ namespace osu.Game.Rulesets.Objects.Drawables /// The lookup X position. Generally should be . protected double CalculateSamplePlaybackBalance(double position) { - const float balance_adjust_amount = 0.4f; + float balanceAdjustAmount = positionalHitsoundsLevel.Value * 2; + double returnedvalue = balanceAdjustAmount * (position - 0.5f); - return balance_adjust_amount * (userPositionalHitSounds.Value ? position - 0.5f : 0); + return returnedvalue; } /// diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs index cf19d64080..b091803406 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs @@ -188,12 +188,12 @@ namespace osu.Game.Rulesets.Objects.Legacy string[] split = str.Split(':'); var bank = (LegacySampleBank)Parsing.ParseInt(split[0]); - var addbank = (LegacySampleBank)Parsing.ParseInt(split[1]); + var addBank = (LegacySampleBank)Parsing.ParseInt(split[1]); string stringBank = bank.ToString().ToLowerInvariant(); if (stringBank == @"none") stringBank = null; - string stringAddBank = addbank.ToString().ToLowerInvariant(); + string stringAddBank = addBank.ToString().ToLowerInvariant(); if (stringAddBank == @"none") stringAddBank = null; diff --git a/osu.Game/Rulesets/Objects/SliderPath.cs b/osu.Game/Rulesets/Objects/SliderPath.cs index 0dec0655b9..e0c62fe359 100644 --- a/osu.Game/Rulesets/Objects/SliderPath.cs +++ b/osu.Game/Rulesets/Objects/SliderPath.cs @@ -250,13 +250,13 @@ namespace osu.Game.Rulesets.Objects if (subControlPoints.Length != 3) break; - List subpath = PathApproximator.ApproximateCircularArc(subControlPoints); + List subPath = PathApproximator.ApproximateCircularArc(subControlPoints); // If for some reason a circular arc could not be fit to the 3 given points, fall back to a numerically stable bezier approximation. - if (subpath.Count == 0) + if (subPath.Count == 0) break; - return subpath; + return subPath; case PathType.Catmull: return PathApproximator.ApproximateCatmull(subControlPoints); diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index 6de6b57066..39cd28cad2 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -9,6 +9,7 @@ using System.Threading; using System.Threading.Tasks; using JetBrains.Annotations; using osu.Framework.Bindables; +using osu.Framework.Extensions; using osu.Framework.Platform; using osu.Framework.Threading; using osu.Game.Beatmaps; @@ -112,7 +113,7 @@ namespace osu.Game.Scoring public void GetTotalScore([NotNull] ScoreInfo score, [NotNull] Action callback, ScoringMode mode = ScoringMode.Standardised, CancellationToken cancellationToken = default) { GetTotalScoreAsync(score, mode, cancellationToken) - .ContinueWith(s => scheduler.Add(() => callback(s.Result)), TaskContinuationOptions.OnlyOnRanToCompletion); + .ContinueWith(task => scheduler.Add(() => callback(task.GetResultSafely())), TaskContinuationOptions.OnlyOnRanToCompletion); } /// diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs index be52a968bb..cda986c7cd 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs @@ -15,13 +15,14 @@ using osuTK.Input; namespace osu.Game.Screens.Edit.Compose.Components { + [Cached] public class SelectionBox : CompositeDrawable { public const float BORDER_RADIUS = 3; public Func OnRotation; public Func OnScale; - public Func OnFlip; + public Func OnFlip; public Func OnReverse; public Action OperationStarted; @@ -174,12 +175,6 @@ namespace osu.Game.Screens.Edit.Compose.Components { case Key.G: return CanReverse && runOperationFromHotkey(OnReverse); - - case Key.H: - return CanFlipX && runOperationFromHotkey(() => OnFlip?.Invoke(Direction.Horizontal) ?? false); - - case Key.J: - return CanFlipY && runOperationFromHotkey(() => OnFlip?.Invoke(Direction.Vertical) ?? false); } return base.OnKeyDown(e); @@ -287,12 +282,12 @@ namespace osu.Game.Screens.Edit.Compose.Components private void addXFlipComponents() { - addButton(FontAwesome.Solid.ArrowsAltH, "Flip horizontally (Ctrl-H)", () => OnFlip?.Invoke(Direction.Horizontal)); + addButton(FontAwesome.Solid.ArrowsAltH, "Flip horizontally", () => OnFlip?.Invoke(Direction.Horizontal, false)); } private void addYFlipComponents() { - addButton(FontAwesome.Solid.ArrowsAltV, "Flip vertically (Ctrl-J)", () => OnFlip?.Invoke(Direction.Vertical)); + addButton(FontAwesome.Solid.ArrowsAltV, "Flip vertically", () => OnFlip?.Invoke(Direction.Vertical, false)); } private void addButton(IconUsage icon, string tooltip, Action action) @@ -312,7 +307,7 @@ namespace osu.Game.Screens.Edit.Compose.Components var handle = new SelectionBoxScaleHandle { Anchor = anchor, - HandleDrag = e => OnScale?.Invoke(e.Delta, anchor) + HandleScale = (delta, a) => OnScale?.Invoke(delta, a) }; handle.OperationStarted += operationStarted; @@ -325,7 +320,7 @@ namespace osu.Game.Screens.Edit.Compose.Components var handle = new SelectionBoxRotationHandle { Anchor = anchor, - HandleDrag = e => OnRotation?.Invoke(convertDragEventToAngleOfRotation(e)) + HandleRotate = angle => OnRotation?.Invoke(angle) }; handle.OperationStarted += operationStarted; diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxButton.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxButton.cs index 3ac40fda0f..346bb2b508 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxButton.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxButton.cs @@ -49,7 +49,7 @@ namespace osu.Game.Screens.Edit.Compose.Components { TriggerOperationStarted(); Action?.Invoke(); - TriggerOperatoinEnded(); + TriggerOperationEnded(); return true; } diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxControl.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxControl.cs index 40d367bb80..8f22e67922 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxControl.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxControl.cs @@ -92,6 +92,6 @@ namespace osu.Game.Screens.Edit.Compose.Components protected void TriggerOperationStarted() => OperationStarted?.Invoke(); - protected void TriggerOperatoinEnded() => OperationEnded?.Invoke(); + protected void TriggerOperationEnded() => OperationEnded?.Invoke(); } } diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxDragHandle.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxDragHandle.cs index 65a95951cf..c37fefeed4 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxDragHandle.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxDragHandle.cs @@ -8,23 +8,15 @@ namespace osu.Game.Screens.Edit.Compose.Components { public abstract class SelectionBoxDragHandle : SelectionBoxControl { - public Action HandleDrag { get; set; } - protected override bool OnDragStart(DragStartEvent e) { TriggerOperationStarted(); return true; } - protected override void OnDrag(DragEvent e) - { - HandleDrag?.Invoke(e); - base.OnDrag(e); - } - protected override void OnDragEnd(DragEndEvent e) { - TriggerOperatoinEnded(); + TriggerOperationEnded(); UpdateHoverState(); base.OnDragEnd(e); diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxRotationHandle.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxRotationHandle.cs index 65a54292ab..22479bd9b3 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxRotationHandle.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxRotationHandle.cs @@ -1,19 +1,34 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Extensions.EnumExtensions; +using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics; +using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Sprites; +using osu.Framework.Input.Events; +using osu.Framework.Localisation; using osuTK; using osuTK.Graphics; namespace osu.Game.Screens.Edit.Compose.Components { - public class SelectionBoxRotationHandle : SelectionBoxDragHandle + public class SelectionBoxRotationHandle : SelectionBoxDragHandle, IHasTooltip { + public Action HandleRotate { get; set; } + + public LocalisableString TooltipText { get; private set; } + private SpriteIcon icon; + private readonly Bindable cumulativeRotation = new Bindable(); + + [Resolved] + private SelectionBox selectionBox { get; set; } + [BackgroundDependencyLoader] private void load() { @@ -33,10 +48,59 @@ namespace osu.Game.Screens.Edit.Compose.Components }); } + protected override void LoadComplete() + { + base.LoadComplete(); + cumulativeRotation.BindValueChanged(_ => updateTooltipText(), true); + } + protected override void UpdateHoverState() { base.UpdateHoverState(); icon.FadeColour(!IsHeld && IsHovered ? Color4.White : Color4.Black, TRANSFORM_DURATION, Easing.OutQuint); } + + protected override bool OnDragStart(DragStartEvent e) + { + bool handle = base.OnDragStart(e); + if (handle) + cumulativeRotation.Value = 0; + return handle; + } + + protected override void OnDrag(DragEvent e) + { + base.OnDrag(e); + + float instantaneousAngle = convertDragEventToAngleOfRotation(e); + cumulativeRotation.Value += instantaneousAngle; + + if (cumulativeRotation.Value < -180) + cumulativeRotation.Value += 360; + else if (cumulativeRotation.Value > 180) + cumulativeRotation.Value -= 360; + + HandleRotate?.Invoke(instantaneousAngle); + } + + protected override void OnDragEnd(DragEndEvent e) + { + base.OnDragEnd(e); + cumulativeRotation.Value = null; + } + + private float convertDragEventToAngleOfRotation(DragEvent e) + { + // Adjust coordinate system to the center of SelectionBox + float startAngle = MathF.Atan2(e.LastMousePosition.Y - selectionBox.DrawHeight / 2, e.LastMousePosition.X - selectionBox.DrawWidth / 2); + float endAngle = MathF.Atan2(e.MousePosition.Y - selectionBox.DrawHeight / 2, e.MousePosition.X - selectionBox.DrawWidth / 2); + + return (endAngle - startAngle) * 180 / MathF.PI; + } + + private void updateTooltipText() + { + TooltipText = cumulativeRotation.Value?.ToLocalisableString("0.0°") ?? default(LocalisableString); + } } } diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxScaleHandle.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxScaleHandle.cs index a87c661f45..1f82f28380 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxScaleHandle.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxScaleHandle.cs @@ -1,17 +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 osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Input.Events; using osuTK; namespace osu.Game.Screens.Edit.Compose.Components { public class SelectionBoxScaleHandle : SelectionBoxDragHandle { + public Action HandleScale { get; set; } + [BackgroundDependencyLoader] private void load() { Size = new Vector2(10); } + + protected override void OnDrag(DragEvent e) + { + HandleScale?.Invoke(e.Delta, Anchor); + base.OnDrag(e); + } } } diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index ee35b6a47c..39de13899d 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -17,6 +17,7 @@ using osu.Framework.Input.Events; using osu.Framework.Utils; using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; +using osu.Game.Input.Bindings; using osu.Game.Rulesets.Edit; using osuTK; using osuTK.Input; @@ -26,7 +27,7 @@ namespace osu.Game.Screens.Edit.Compose.Components /// /// A component which outlines items and handles movement of selections. /// - public abstract class SelectionHandler : CompositeDrawable, IKeyBindingHandler, IHasContextMenu + public abstract class SelectionHandler : CompositeDrawable, IKeyBindingHandler, IKeyBindingHandler, IHasContextMenu { /// /// The currently selected blueprints. @@ -127,9 +128,10 @@ namespace osu.Game.Screens.Edit.Compose.Components /// /// Handles the selected items being flipped. /// - /// The direction to flip + /// The direction to flip. + /// Whether the flip operation should be global to the playfield's origin or local to the selected pattern. /// Whether any items could be flipped. - public virtual bool HandleFlip(Direction direction) => false; + public virtual bool HandleFlip(Direction direction, bool flipOverOrigin) => false; /// /// Handles the selected items being reversed pattern-wise. @@ -137,6 +139,27 @@ namespace osu.Game.Screens.Edit.Compose.Components /// Whether any items could be reversed. public virtual bool HandleReverse() => false; + public virtual bool OnPressed(KeyBindingPressEvent e) + { + if (e.Repeat) + return false; + + switch (e.Action) + { + case GlobalAction.EditorFlipHorizontally: + return HandleFlip(Direction.Horizontal, true); + + case GlobalAction.EditorFlipVertically: + return HandleFlip(Direction.Vertical, true); + } + + return false; + } + + public void OnReleased(KeyBindingReleaseEvent e) + { + } + public bool OnPressed(KeyBindingPressEvent e) { switch (e.Action) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineSelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineSelectionHandler.cs index 845a671e2c..e98cf8332f 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineSelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineSelectionHandler.cs @@ -7,7 +7,6 @@ using System.Diagnostics; using System.Linq; using JetBrains.Annotations; using osu.Framework.Allocation; -using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Game.Input.Bindings; using osu.Game.Rulesets.Edit; @@ -17,7 +16,7 @@ using osuTK.Input; namespace osu.Game.Screens.Edit.Compose.Components.Timeline { - internal class TimelineSelectionHandler : EditorSelectionHandler, IKeyBindingHandler + internal class TimelineSelectionHandler : EditorSelectionHandler { [Resolved] private Timeline timeline { get; set; } @@ -27,7 +26,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline // for now we always allow movement. snapping is provided by the Timeline's "distance" snap implementation public override bool HandleMovement(MoveSelectionEvent moveEvent) => true; - public bool OnPressed(KeyBindingPressEvent e) + public override bool OnPressed(KeyBindingPressEvent e) { switch (e.Action) { @@ -40,11 +39,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline return true; } - return false; - } - - public void OnReleased(KeyBindingReleaseEvent e) - { + return base.OnPressed(e); } /// diff --git a/osu.Game/Screens/Edit/Setup/SetupScreenHeaderBackground.cs b/osu.Game/Screens/Edit/Setup/SetupScreenHeaderBackground.cs index 7304323004..17fb97d41f 100644 --- a/osu.Game/Screens/Edit/Setup/SetupScreenHeaderBackground.cs +++ b/osu.Game/Screens/Edit/Setup/SetupScreenHeaderBackground.cs @@ -56,7 +56,7 @@ namespace osu.Game.Screens.Edit.Setup { new Box { - Colour = colours.GreySeafoamDarker, + Colour = colours.GreySeaFoamDarker, RelativeSizeAxes = Axes.Both, }, new OsuTextFlowContainer(t => t.Font = OsuFont.Default.With(size: 24)) diff --git a/osu.Game/Screens/Edit/Timing/IndeterminateSliderWithTextBoxInput.cs b/osu.Game/Screens/Edit/Timing/IndeterminateSliderWithTextBoxInput.cs index 14b8c4c9de..e25d83cfb0 100644 --- a/osu.Game/Screens/Edit/Timing/IndeterminateSliderWithTextBoxInput.cs +++ b/osu.Game/Screens/Edit/Timing/IndeterminateSliderWithTextBoxInput.cs @@ -41,7 +41,7 @@ namespace osu.Game.Screens.Edit.Timing } private readonly SettingsSlider slider; - private readonly LabelledTextBox textbox; + private readonly LabelledTextBox textBox; /// /// Creates an . @@ -66,7 +66,7 @@ namespace osu.Game.Screens.Edit.Timing Spacing = new Vector2(0, 5), Children = new Drawable[] { - textbox = new LabelledTextBox + textBox = new LabelledTextBox { Label = labelText, }, @@ -80,7 +80,7 @@ namespace osu.Game.Screens.Edit.Timing }, }; - textbox.OnCommit += (t, isNew) => + textBox.OnCommit += (t, isNew) => { if (!isNew) return; @@ -110,13 +110,13 @@ namespace osu.Game.Screens.Edit.Timing // use the value from the slider to ensure that any precision/min/max set on it via the initial indeterminate value have been applied correctly. decimal decimalValue = slider.Current.Value.ToDecimal(NumberFormatInfo.InvariantInfo); - textbox.Text = decimalValue.ToString($@"N{FormatUtils.FindPrecision(decimalValue)}"); - textbox.PlaceholderText = string.Empty; + textBox.Text = decimalValue.ToString($@"N{FormatUtils.FindPrecision(decimalValue)}"); + textBox.PlaceholderText = string.Empty; } else { - textbox.Text = null; - textbox.PlaceholderText = "(multiple)"; + textBox.Text = null; + textBox.PlaceholderText = "(multiple)"; } } } diff --git a/osu.Game/Screens/Edit/Timing/SliderWithTextBoxInput.cs b/osu.Game/Screens/Edit/Timing/SliderWithTextBoxInput.cs index 6c004a7c8b..67f1dacec4 100644 --- a/osu.Game/Screens/Edit/Timing/SliderWithTextBoxInput.cs +++ b/osu.Game/Screens/Edit/Timing/SliderWithTextBoxInput.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Globalization; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -9,6 +10,7 @@ using osu.Framework.Graphics.UserInterface; using osu.Framework.Localisation; using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Overlays.Settings; +using osu.Game.Utils; namespace osu.Game.Screens.Edit.Timing { @@ -19,7 +21,7 @@ namespace osu.Game.Screens.Edit.Timing public SliderWithTextBoxInput(LocalisableString labelText) { - LabelledTextBox textbox; + LabelledTextBox textBox; RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; @@ -33,7 +35,7 @@ namespace osu.Game.Screens.Edit.Timing Direction = FillDirection.Vertical, Children = new Drawable[] { - textbox = new LabelledTextBox + textBox = new LabelledTextBox { Label = labelText, }, @@ -46,7 +48,7 @@ namespace osu.Game.Screens.Edit.Timing }, }; - textbox.OnCommit += (t, isNew) => + textBox.OnCommit += (t, isNew) => { if (!isNew) return; @@ -66,7 +68,8 @@ namespace osu.Game.Screens.Edit.Timing Current.BindValueChanged(val => { - textbox.Text = val.NewValue.ToString(); + decimal decimalValue = slider.Current.Value.ToDecimal(NumberFormatInfo.InvariantInfo); + textBox.Text = decimalValue.ToString($@"N{FormatUtils.FindPrecision(decimalValue)}"); }, true); } diff --git a/osu.Game/Screens/Import/FileImportScreen.cs b/osu.Game/Screens/Import/FileImportScreen.cs index 7e1d55b3e2..c32e230e11 100644 --- a/osu.Game/Screens/Import/FileImportScreen.cs +++ b/osu.Game/Screens/Import/FileImportScreen.cs @@ -54,7 +54,7 @@ namespace osu.Game.Screens.Import { new Box { - Colour = colours.GreySeafoamDark, + Colour = colours.GreySeaFoamDark, RelativeSizeAxes = Axes.Both, }, fileSelector = new OsuFileSelector(validFileExtensions: game.HandledExtensions.ToArray()) @@ -72,7 +72,7 @@ namespace osu.Game.Screens.Import { new Box { - Colour = colours.GreySeafoamDarker, + Colour = colours.GreySeaFoamDarker, RelativeSizeAxes = Axes.Both }, new Container diff --git a/osu.Game/Screens/Menu/IntroScreen.cs b/osu.Game/Screens/Menu/IntroScreen.cs index eb8d3dfea6..948e3a7d88 100644 --- a/osu.Game/Screens/Menu/IntroScreen.cs +++ b/osu.Game/Screens/Menu/IntroScreen.cs @@ -9,6 +9,7 @@ using osu.Framework.Audio; using osu.Framework.Audio.Sample; using osu.Framework.Audio.Track; using osu.Framework.Bindables; +using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Screens; using osu.Framework.Utils; @@ -110,7 +111,7 @@ namespace osu.Game.Screens.Menu { // if we detect that the theme track or beatmap is unavailable this is either first startup or things are in a bad state. // this could happen if a user has nuked their files store. for now, reimport to repair this. - var import = beatmaps.Import(new ZipArchiveReader(game.Resources.GetStream($"Tracks/{BeatmapFile}"), BeatmapFile)).Result; + var import = beatmaps.Import(new ZipArchiveReader(game.Resources.GetStream($"Tracks/{BeatmapFile}"), BeatmapFile)).GetResultSafely(); import.PerformWrite(b => { diff --git a/osu.Game/Screens/Menu/LogoVisualisation.cs b/osu.Game/Screens/Menu/LogoVisualisation.cs index 01b2a98c6e..4acd73bfa0 100644 --- a/osu.Game/Screens/Menu/LogoVisualisation.cs +++ b/osu.Game/Screens/Menu/LogoVisualisation.cs @@ -53,7 +53,7 @@ namespace osu.Game.Screens.Menu /// /// How much should each bar go down each millisecond (based on a full bar). /// - private const float decay_per_milisecond = 0.0024f; + private const float decay_per_millisecond = 0.0024f; /// /// Number of milliseconds between each amplitude update. @@ -136,7 +136,7 @@ namespace osu.Game.Screens.Menu { base.Update(); - float decayFactor = (float)Time.Elapsed * decay_per_milisecond; + float decayFactor = (float)Time.Elapsed * decay_per_millisecond; for (int i = 0; i < bars_per_visualiser; i++) { diff --git a/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs b/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs index 0d2b2249ef..3fd56ece58 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs @@ -193,7 +193,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge this.room = room; } - private OsuPasswordTextBox passwordTextbox; + private OsuPasswordTextBox passwordTextBox; private TriangleButton joinButton; private OsuSpriteText errorText; private Sample sampleJoinFail; @@ -218,7 +218,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge AutoSizeAxes = Axes.Both, Children = new Drawable[] { - passwordTextbox = new OsuPasswordTextBox + passwordTextBox = new OsuPasswordTextBox { Width = 200, PlaceholderText = "password", @@ -246,21 +246,21 @@ namespace osu.Game.Screens.OnlinePlay.Lounge { base.LoadComplete(); - Schedule(() => GetContainingInputManager().ChangeFocus(passwordTextbox)); - passwordTextbox.OnCommit += (_, __) => performJoin(); + Schedule(() => GetContainingInputManager().ChangeFocus(passwordTextBox)); + passwordTextBox.OnCommit += (_, __) => performJoin(); } private void performJoin() { - lounge?.Join(room, passwordTextbox.Text, null, joinFailed); - GetContainingInputManager().TriggerFocusContention(passwordTextbox); + lounge?.Join(room, passwordTextBox.Text, null, joinFailed); + GetContainingInputManager().TriggerFocusContention(passwordTextBox); } private void joinFailed(string error) => Schedule(() => { - passwordTextbox.Text = string.Empty; + passwordTextBox.Text = string.Empty; - GetContainingInputManager().ChangeFocus(passwordTextbox); + GetContainingInputManager().ChangeFocus(passwordTextBox); errorText.Text = error; errorText diff --git a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs index a560d85b7d..c31239616c 100644 --- a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs @@ -300,6 +300,7 @@ namespace osu.Game.Screens.OnlinePlay.Match updateWorkingBeatmap(); beginHandlingTrack(); Scheduler.AddOnce(UpdateMods); + Scheduler.AddOnce(updateRuleset); } public override bool OnExiting(IScreen next) @@ -353,8 +354,7 @@ namespace osu.Game.Screens.OnlinePlay.Match .ToList(); UpdateMods(); - - Ruleset.Value = rulesets.GetRuleset(selected.RulesetID); + updateRuleset(); if (!selected.AllowedMods.Any()) { @@ -381,12 +381,20 @@ namespace osu.Game.Screens.OnlinePlay.Match protected virtual void UpdateMods() { - if (SelectedItem.Value == null) + if (SelectedItem.Value == null || !this.IsCurrentScreen()) return; Mods.Value = UserMods.Value.Concat(SelectedItem.Value.RequiredMods).ToList(); } + private void updateRuleset() + { + if (SelectedItem.Value == null || !this.IsCurrentScreen()) + return; + + Ruleset.Value = rulesets.GetRuleset(SelectedItem.Value.RulesetID); + } + private void beginHandlingTrack() { Beatmap.BindValueChanged(applyLoopingToTrack, true); diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/GameplayChatDisplay.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/GameplayChatDisplay.cs index 0e73f65f8b..53fd111c27 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/GameplayChatDisplay.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/GameplayChatDisplay.cs @@ -24,7 +24,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer public Bindable Expanded = new Bindable(); - private readonly Bindable expandedFromTextboxFocus = new Bindable(); + private readonly Bindable expandedFromTextBoxFocus = new Bindable(); private const float height = 100; @@ -37,7 +37,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer Background.Alpha = 0.2f; - Textbox.FocusLost = () => expandedFromTextboxFocus.Value = false; + TextBox.FocusLost = () => expandedFromTextBoxFocus.Value = false; } protected override bool OnHover(HoverEvent e) => true; // use UI mouse cursor. @@ -51,14 +51,14 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer { // for now let's never hold focus. this avoid misdirected gameplay keys entering chat. // note that this is done within this callback as it triggers an un-focus as well. - Textbox.HoldFocus = false; + TextBox.HoldFocus = false; // only hold focus (after sending a message) during breaks - Textbox.ReleaseFocusOnCommit = playing.NewValue; + TextBox.ReleaseFocusOnCommit = playing.NewValue; }, true); Expanded.BindValueChanged(_ => updateExpandedState(), true); - expandedFromTextboxFocus.BindValueChanged(focus => + expandedFromTextBoxFocus.BindValueChanged(focus => { if (focus.NewValue) updateExpandedState(); @@ -76,25 +76,25 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer switch (e.Action) { case GlobalAction.Back: - if (Textbox.HasFocus) + if (TextBox.HasFocus) { - Schedule(() => Textbox.KillFocus()); + Schedule(() => TextBox.KillFocus()); return true; } break; case GlobalAction.ToggleChatFocus: - if (Textbox.HasFocus) + if (TextBox.HasFocus) { - Schedule(() => Textbox.KillFocus()); + Schedule(() => TextBox.KillFocus()); } else { - expandedFromTextboxFocus.Value = true; + expandedFromTextBoxFocus.Value = true; // schedule required to ensure the textbox has become present from above bindable update. - Schedule(() => Textbox.TakeFocus()); + Schedule(() => TextBox.TakeFocus()); } return true; @@ -109,7 +109,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer private void updateExpandedState() { - if (Expanded.Value || expandedFromTextboxFocus.Value) + if (Expanded.Value || expandedFromTextBoxFocus.Value) { this.FadeIn(300, Easing.OutQuint); this.ResizeHeightTo(height, 500, Easing.OutQuint); diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index c4dd200614..4bd68f2034 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -241,7 +241,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer protected override void UpdateMods() { - if (SelectedItem.Value == null || client.LocalUser == null) + if (SelectedItem.Value == null || client.LocalUser == null || !this.IsCurrentScreen()) return; // update local mods based on room's reported status for the local user (omitting the base call implementation). diff --git a/osu.Game/Screens/Play/HUD/HealthDisplay.cs b/osu.Game/Screens/Play/HUD/HealthDisplay.cs index 1f0fafa636..94f1b99b37 100644 --- a/osu.Game/Screens/Play/HUD/HealthDisplay.cs +++ b/osu.Game/Screens/Play/HUD/HealthDisplay.cs @@ -17,7 +17,7 @@ namespace osu.Game.Screens.Play.HUD /// public abstract class HealthDisplay : CompositeDrawable { - private readonly Bindable showHealthbar = new Bindable(true); + private readonly Bindable showHealthBar = new Bindable(true); [Resolved] protected HealthProcessor HealthProcessor { get; private set; } @@ -43,10 +43,10 @@ namespace osu.Game.Screens.Play.HUD HealthProcessor.NewJudgement += onNewJudgement; if (hudOverlay != null) - showHealthbar.BindTo(hudOverlay.ShowHealthbar); + showHealthBar.BindTo(hudOverlay.ShowHealthBar); // this probably shouldn't be operating on `this.` - showHealthbar.BindValueChanged(healthBar => this.FadeTo(healthBar.NewValue ? 1 : 0, HUDOverlay.FADE_DURATION, HUDOverlay.FADE_EASING), true); + showHealthBar.BindValueChanged(healthBar => this.FadeTo(healthBar.NewValue ? 1 : 0, HUDOverlay.FADE_DURATION, HUDOverlay.FADE_EASING), true); } private void onNewJudgement(JudgementResult judgement) diff --git a/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs b/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs index e019ee9a3d..83c73e5a70 100644 --- a/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs +++ b/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs @@ -7,6 +7,7 @@ using System.Collections.Specialized; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions; using osu.Framework.Extensions.Color4Extensions; using osu.Game.Configuration; using osu.Game.Database; @@ -76,9 +77,11 @@ namespace osu.Game.Screens.Play.HUD TeamScores.Add(team, new BindableInt()); } - userLookupCache.GetUsersAsync(playingUsers.Select(u => u.UserID).ToArray()).ContinueWith(users => Schedule(() => + userLookupCache.GetUsersAsync(playingUsers.Select(u => u.UserID).ToArray()).ContinueWith(task => Schedule(() => { - foreach (var user in users.Result) + var users = task.GetResultSafely(); + + foreach (var user in users) { if (user == null) continue; diff --git a/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs b/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs index 908e6a27b8..21a7698248 100644 --- a/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs +++ b/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs @@ -10,6 +10,7 @@ using System.Threading.Tasks; using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Audio.Track; +using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; @@ -73,10 +74,12 @@ namespace osu.Game.Screens.Play.HUD var gameplayWorkingBeatmap = new GameplayWorkingBeatmap(gameplayState.Beatmap); difficultyCache.GetTimedDifficultyAttributesAsync(gameplayWorkingBeatmap, gameplayState.Ruleset, clonedMods, loadCancellationSource.Token) - .ContinueWith(r => Schedule(() => + .ContinueWith(task => Schedule(() => { - timedAttributes = r.Result; + timedAttributes = task.GetResultSafely(); + IsValid = true; + if (lastJudgement != null) onJudgementChanged(lastJudgement); }), TaskContinuationOptions.OnlyOnRanToCompletion); diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index b5c4433719..fdb5d418f3 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -46,7 +46,7 @@ namespace osu.Game.Screens.Play public readonly HoldForMenuButton HoldToQuit; public readonly PlayerSettingsOverlay PlayerSettingsOverlay; - public Bindable ShowHealthbar = new Bindable(true); + public Bindable ShowHealthBar = new Bindable(true); private readonly DrawableRuleset drawableRuleset; private readonly IReadOnlyList mods; @@ -258,7 +258,7 @@ namespace osu.Game.Screens.Play protected FailingLayer CreateFailingLayer() => new FailingLayer { - ShowHealth = { BindTarget = ShowHealthbar } + ShowHealth = { BindTarget = ShowHealthBar } }; protected KeyCounterDisplay CreateKeyCounter() => new KeyCounterDisplay diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 77a6b27114..2a6f5e2398 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -9,6 +9,7 @@ using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; using osu.Framework.Bindables; +using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Events; @@ -770,7 +771,7 @@ namespace osu.Game.Screens.Play // This player instance may already be in the process of exiting. return; - this.Push(CreateResults(prepareScoreForDisplayTask.Result)); + this.Push(CreateResults(prepareScoreForDisplayTask.GetResultSafely())); }, Time.Current + delay, 50); Scheduler.Add(resultsDisplayDelegate); diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index 60843acb4f..6009c85583 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -496,7 +496,7 @@ namespace osu.Game.Screens.Play private void load(OsuColour colours, AudioManager audioManager, NotificationOverlay notificationOverlay, VolumeOverlay volumeOverlay) { Icon = FontAwesome.Solid.VolumeMute; - IconBackgound.Colour = colours.RedDark; + IconBackground.Colour = colours.RedDark; Activated = delegate { @@ -548,7 +548,7 @@ namespace osu.Game.Screens.Play private void load(OsuColour colours, NotificationOverlay notificationOverlay) { Icon = FontAwesome.Solid.BatteryQuarter; - IconBackgound.Colour = colours.RedDark; + IconBackground.Colour = colours.RedDark; Activated = delegate { diff --git a/osu.Game/Screens/Play/PlayerSettings/PlayerSettingsGroup.cs b/osu.Game/Screens/Play/PlayerSettings/PlayerSettingsGroup.cs index 7928d41e3b..0bbe6902f4 100644 --- a/osu.Game/Screens/Play/PlayerSettings/PlayerSettingsGroup.cs +++ b/osu.Game/Screens/Play/PlayerSettings/PlayerSettingsGroup.cs @@ -1,165 +1,24 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; -using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; -using osu.Game.Graphics.UserInterface; -using osuTK; -using osuTK.Graphics; +using osu.Game.Overlays; namespace osu.Game.Screens.Play.PlayerSettings { - public abstract class PlayerSettingsGroup : Container + public class PlayerSettingsGroup : SettingsToolboxGroup { - private const float transition_duration = 250; - private const int container_width = 270; - private const int border_thickness = 2; - private const int header_height = 30; - private const int corner_radius = 5; - - private readonly FillFlowContainer content; - private readonly IconButton button; - - private bool expanded = true; - - public bool Expanded + public PlayerSettingsGroup(string title) + : base(title) { - get => expanded; - set - { - if (expanded == value) return; - - expanded = value; - - content.ClearTransforms(); - - if (expanded) - content.AutoSizeAxes = Axes.Y; - else - { - content.AutoSizeAxes = Axes.None; - content.ResizeHeightTo(0, transition_duration, Easing.OutQuint); - } - - updateExpanded(); - } - } - - private Color4 expandedColour; - - /// - /// Create a new instance. - /// - /// The title to be displayed in the header of this group. - protected PlayerSettingsGroup(string title) - { - AutoSizeAxes = Axes.Y; - Width = container_width; - Masking = true; - CornerRadius = corner_radius; - BorderColour = Color4.Black; - BorderThickness = border_thickness; - - InternalChildren = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.Black, - Alpha = 0.5f, - }, - new FillFlowContainer - { - Direction = FillDirection.Vertical, - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Children = new Drawable[] - { - new Container - { - Name = @"Header", - Origin = Anchor.TopCentre, - Anchor = Anchor.TopCentre, - RelativeSizeAxes = Axes.X, - Height = header_height, - Children = new Drawable[] - { - new OsuSpriteText - { - Origin = Anchor.CentreLeft, - Anchor = Anchor.CentreLeft, - Text = title.ToUpperInvariant(), - Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 17), - Margin = new MarginPadding { Left = 10 }, - }, - button = new IconButton - { - Origin = Anchor.Centre, - Anchor = Anchor.CentreRight, - Position = new Vector2(-15, 0), - Icon = FontAwesome.Solid.Bars, - Scale = new Vector2(0.75f), - Action = () => Expanded = !Expanded, - }, - } - }, - content = new FillFlowContainer - { - Name = @"Content", - Origin = Anchor.TopCentre, - Anchor = Anchor.TopCentre, - Direction = FillDirection.Vertical, - RelativeSizeAxes = Axes.X, - AutoSizeDuration = transition_duration, - AutoSizeEasing = Easing.OutQuint, - AutoSizeAxes = Axes.Y, - Padding = new MarginPadding(15), - Spacing = new Vector2(0, 15), - } - } - }, - }; - } - - private const float fade_duration = 800; - private const float inactive_alpha = 0.5f; - - protected override void LoadComplete() - { - base.LoadComplete(); - this.Delay(600).FadeTo(inactive_alpha, fade_duration, Easing.OutQuint); } protected override bool OnHover(HoverEvent e) { - this.FadeIn(fade_duration, Easing.OutQuint); + base.OnHover(e); + + // Importantly, return true to correctly take focus away from PlayerLoader. return true; } - - protected override void OnHoverLost(HoverLostEvent e) - { - this.FadeTo(inactive_alpha, fade_duration, Easing.OutQuint); - base.OnHoverLost(e); - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - expandedColour = colours.Yellow; - - updateExpanded(); - } - - private void updateExpanded() => button.FadeColour(expanded ? expandedColour : Color4.White, 200, Easing.InOutQuint); - - protected override Container Content => content; - - protected override bool OnMouseDown(MouseDownEvent e) => true; } } diff --git a/osu.Game/Screens/Play/PlayerSettings/PlayerSliderBar.cs b/osu.Game/Screens/Play/PlayerSettings/PlayerSliderBar.cs index 9903a74043..57ffe16f76 100644 --- a/osu.Game/Screens/Play/PlayerSettings/PlayerSliderBar.cs +++ b/osu.Game/Screens/Play/PlayerSettings/PlayerSliderBar.cs @@ -15,12 +15,12 @@ namespace osu.Game.Screens.Play.PlayerSettings { public OsuSliderBar Bar => (OsuSliderBar)Control; - protected override Drawable CreateControl() => new Sliderbar + protected override Drawable CreateControl() => new SliderBar { RelativeSizeAxes = Axes.X }; - private class Sliderbar : OsuSliderBar + private class SliderBar : OsuSliderBar { [BackgroundDependencyLoader] private void load(OsuColour colours) diff --git a/osu.Game/Screens/Play/SubmittingPlayer.cs b/osu.Game/Screens/Play/SubmittingPlayer.cs index c613167908..2cf56be659 100644 --- a/osu.Game/Screens/Play/SubmittingPlayer.cs +++ b/osu.Game/Screens/Play/SubmittingPlayer.cs @@ -6,6 +6,7 @@ using System.Linq; using System.Threading.Tasks; using JetBrains.Annotations; using osu.Framework.Allocation; +using osu.Framework.Extensions; using osu.Framework.Logging; using osu.Framework.Screens; using osu.Game.Online.API; @@ -75,7 +76,7 @@ namespace osu.Game.Screens.Play api.Queue(req); - tcs.Task.Wait(); + tcs.Task.WaitSafely(); return true; void handleTokenFailure(Exception exception) diff --git a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs index 3ab2658f97..7e39708e65 100644 --- a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs +++ b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Localisation; @@ -79,7 +80,7 @@ namespace osu.Game.Screens.Ranking.Expanded statisticDisplays.AddRange(topStatistics); statisticDisplays.AddRange(bottomStatistics); - var starDifficulty = beatmapDifficultyCache.GetDifficultyAsync(beatmap, score.Ruleset, score.Mods).Result; + var starDifficulty = beatmapDifficultyCache.GetDifficultyAsync(beatmap, score.Ruleset, score.Mods).GetResultSafely(); AddInternal(new FillFlowContainer { diff --git a/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatistic.cs b/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatistic.cs index 68da4ec724..d6e4cfbe51 100644 --- a/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatistic.cs +++ b/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatistic.cs @@ -5,6 +5,7 @@ using System; using System.Threading; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Game.Graphics.UserInterface; using osu.Game.Scoring; @@ -37,7 +38,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics else { performanceCache.CalculatePerformanceAsync(score, cancellationTokenSource.Token) - .ContinueWith(t => Schedule(() => setPerformanceValue(t.Result)), cancellationTokenSource.Token); + .ContinueWith(t => Schedule(() => setPerformanceValue(t.GetResultSafely())), cancellationTokenSource.Token); } } diff --git a/osu.Game/Screens/Ranking/ScorePanelList.cs b/osu.Game/Screens/Ranking/ScorePanelList.cs index f3de48dcf0..f8e9d08350 100644 --- a/osu.Game/Screens/Ranking/ScorePanelList.cs +++ b/osu.Game/Screens/Ranking/ScorePanelList.cs @@ -10,6 +10,7 @@ using System.Threading.Tasks; using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Events; @@ -151,9 +152,9 @@ namespace osu.Game.Screens.Ranking // Calculating score can take a while in extreme scenarios, so only display scores after the process completes. scoreManager.GetTotalScoreAsync(score) - .ContinueWith(totalScore => Schedule(() => + .ContinueWith(task => Schedule(() => { - flow.SetLayoutPosition(trackingContainer, totalScore.Result); + flow.SetLayoutPosition(trackingContainer, task.GetResultSafely()); trackingContainer.Show(); diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs index cc2db6ed31..d2c7c75da8 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs @@ -28,8 +28,8 @@ namespace osu.Game.Screens.Select.Carousel bool match = criteria.Ruleset == null || - BeatmapInfo.RulesetID == criteria.Ruleset.ID || - (BeatmapInfo.RulesetID == 0 && criteria.Ruleset.ID > 0 && criteria.AllowConvertedBeatmaps); + BeatmapInfo.RulesetID == criteria.Ruleset.OnlineID || + (BeatmapInfo.RulesetID == 0 && criteria.Ruleset.OnlineID > 0 && criteria.AllowConvertedBeatmaps); if (BeatmapInfo.BeatmapSet?.Equals(criteria.SelectedBeatmapSet) == true) { diff --git a/osu.Game/Screens/Select/Details/AdvancedStats.cs b/osu.Game/Screens/Select/Details/AdvancedStats.cs index edbaba40bc..adaaa6425c 100644 --- a/osu.Game/Screens/Select/Details/AdvancedStats.cs +++ b/osu.Game/Screens/Select/Details/AdvancedStats.cs @@ -16,6 +16,7 @@ using osu.Game.Rulesets.Mods; using System.Linq; using System.Threading; using System.Threading.Tasks; +using osu.Framework.Extensions; using osu.Framework.Localisation; using osu.Framework.Threading; using osu.Framework.Utils; @@ -147,15 +148,18 @@ namespace osu.Game.Screens.Select.Details starDifficultyCancellationSource = new CancellationTokenSource(); - var normalStarDifficulty = difficultyCache.GetDifficultyAsync(BeatmapInfo, ruleset.Value, null, starDifficultyCancellationSource.Token); - var moddedStarDifficulty = difficultyCache.GetDifficultyAsync(BeatmapInfo, ruleset.Value, mods.Value, starDifficultyCancellationSource.Token); + var normalStarDifficultyTask = difficultyCache.GetDifficultyAsync(BeatmapInfo, ruleset.Value, null, starDifficultyCancellationSource.Token); + var moddedStarDifficultyTask = difficultyCache.GetDifficultyAsync(BeatmapInfo, ruleset.Value, mods.Value, starDifficultyCancellationSource.Token); - Task.WhenAll(normalStarDifficulty, moddedStarDifficulty).ContinueWith(_ => Schedule(() => + Task.WhenAll(normalStarDifficultyTask, moddedStarDifficultyTask).ContinueWith(_ => Schedule(() => { - if (normalStarDifficulty.Result == null || moddedStarDifficulty.Result == null) + var normalDifficulty = normalStarDifficultyTask.GetResultSafely(); + var moddedDifficulty = moddedStarDifficultyTask.GetResultSafely(); + + if (normalDifficulty == null || moddedDifficulty == null) return; - starDifficulty.Value = ((float)normalStarDifficulty.Result.Value.Stars, (float)moddedStarDifficulty.Result.Value.Stars); + starDifficulty.Value = ((float)normalDifficulty.Value.Stars, (float)moddedDifficulty.Value.Stars); }), starDifficultyCancellationSource.Token, TaskContinuationOptions.OnlyOnRanToCompletion, TaskScheduler.Current); } diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs index 29c8907526..0102986070 100644 --- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs @@ -8,6 +8,7 @@ using System.Threading; using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions; using osu.Game.Beatmaps; using osu.Game.Online.API; using osu.Game.Online.API.Requests; @@ -142,7 +143,7 @@ namespace osu.Game.Screens.Select.Leaderboards } scoreManager.OrderByTotalScoreAsync(scores.ToArray(), cancellationToken) - .ContinueWith(ordered => scoresCallback?.Invoke(ordered.Result), TaskContinuationOptions.OnlyOnRanToCompletion); + .ContinueWith(task => scoresCallback?.Invoke(task.GetResultSafely()), TaskContinuationOptions.OnlyOnRanToCompletion); return null; } @@ -178,12 +179,12 @@ namespace osu.Game.Screens.Select.Leaderboards req.Success += r => { scoreManager.OrderByTotalScoreAsync(r.Scores.Select(s => s.CreateScoreInfo(rulesets, fetchBeatmapInfo)).ToArray(), cancellationToken) - .ContinueWith(ordered => Schedule(() => + .ContinueWith(task => Schedule(() => { if (cancellationToken.IsCancellationRequested) return; - scoresCallback?.Invoke(ordered.Result); + scoresCallback?.Invoke(task.GetResultSafely()); TopScore = r.UserScore?.CreateScoreInfo(rulesets, fetchBeatmapInfo); }), TaskContinuationOptions.OnlyOnRanToCompletion); }; diff --git a/osu.Game/Screens/Spectate/SpectatorScreen.cs b/osu.Game/Screens/Spectate/SpectatorScreen.cs index ca56366927..c4e75cc413 100644 --- a/osu.Game/Screens/Spectate/SpectatorScreen.cs +++ b/osu.Game/Screens/Spectate/SpectatorScreen.cs @@ -7,6 +7,7 @@ using System.Linq; using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions; using osu.Framework.Extensions.ObjectExtensions; using osu.Game.Beatmaps; using osu.Game.Database; @@ -57,9 +58,11 @@ namespace osu.Game.Screens.Spectate { base.LoadComplete(); - userLookupCache.GetUsersAsync(users.ToArray()).ContinueWith(users => Schedule(() => + userLookupCache.GetUsersAsync(users.ToArray()).ContinueWith(task => Schedule(() => { - foreach (var u in users.Result) + var foundUsers = task.GetResultSafely(); + + foreach (var u in foundUsers) { if (u == null) continue; diff --git a/osu.Game/Skinning/Editor/SkinEditorOverlay.cs b/osu.Game/Skinning/Editor/SkinEditorOverlay.cs index 340c6ed931..86854ab6ff 100644 --- a/osu.Game/Skinning/Editor/SkinEditorOverlay.cs +++ b/osu.Game/Skinning/Editor/SkinEditorOverlay.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System.Diagnostics; using JetBrains.Annotations; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -67,18 +66,34 @@ namespace osu.Game.Skinning.Editor public override void Show() { - // base call intentionally omitted. - if (skinEditor == null) + // base call intentionally omitted as we have custom behaviour. + + if (skinEditor != null) { - skinEditor = new SkinEditor(target); - skinEditor.State.BindValueChanged(editorVisibilityChanged); - - Debug.Assert(skinEditor != null); - - LoadComponentAsync(skinEditor, AddInternal); - } - else skinEditor.Show(); + return; + } + + var editor = new SkinEditor(target); + editor.State.BindValueChanged(editorVisibilityChanged); + + skinEditor = editor; + + // Schedule ensures that if `Show` is called before this overlay is loaded, + // it will not throw (LoadComponentAsync requires the load target to be in a loaded state). + Schedule(() => + { + if (editor != skinEditor) + return; + + LoadComponentAsync(editor, _ => + { + if (editor != skinEditor) + return; + + AddInternal(editor); + }); + }); } private void editorVisibilityChanged(ValueChangedEvent visibility) diff --git a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs index 01bd5e8196..bd6d097eb2 100644 --- a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs +++ b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs @@ -126,7 +126,7 @@ namespace osu.Game.Skinning.Editor return true; } - public override bool HandleFlip(Direction direction) + public override bool HandleFlip(Direction direction, bool flipOverOrigin) { var selectionQuad = getSelectionQuad(); Vector2 scaleFactor = direction == Direction.Horizontal ? new Vector2(-1, 1) : new Vector2(1, -1); @@ -135,7 +135,7 @@ namespace osu.Game.Skinning.Editor { var drawableItem = (Drawable)b.Item; - var flippedPosition = GetFlippedPosition(direction, selectionQuad, b.ScreenSpaceSelectionPoint); + var flippedPosition = GetFlippedPosition(direction, flipOverOrigin ? drawableItem.Parent.ScreenSpaceDrawQuad : selectionQuad, b.ScreenSpaceSelectionPoint); updateDrawablePosition(drawableItem, flippedPosition); diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index bb2f0a37b4..cde21b78c1 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -11,6 +11,7 @@ using JetBrains.Annotations; using osu.Framework.Audio; using osu.Framework.Audio.Sample; using osu.Framework.Bindables; +using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.OpenGL.Textures; using osu.Framework.Graphics.Textures; @@ -153,7 +154,7 @@ namespace osu.Game.Skinning Name = s.Name + @" (modified)", Creator = s.Creator, InstantiationInfo = s.InstantiationInfo, - }).Result; + }).GetResultSafely(); if (result != null) { diff --git a/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs b/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs index 651874e4de..897d4363f1 100644 --- a/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs +++ b/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs @@ -6,9 +6,11 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; +using System.Threading.Tasks; using Newtonsoft.Json; using NUnit.Framework; using osu.Framework.Audio.Track; +using osu.Framework.Extensions; using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics.Textures; using osu.Game.Beatmaps; @@ -108,37 +110,45 @@ namespace osu.Game.Tests.Beatmaps private ConvertResult convert(string name, Mod[] mods) { - var beatmap = GetBeatmap(name); - - string beforeConversion = beatmap.Serialize(); - - var converterResult = new Dictionary>(); - - var working = new ConversionWorkingBeatmap(beatmap) + var conversionTask = Task.Factory.StartNew(() => { - ConversionGenerated = (o, r, c) => + var beatmap = GetBeatmap(name); + + string beforeConversion = beatmap.Serialize(); + + var converterResult = new Dictionary>(); + + var working = new ConversionWorkingBeatmap(beatmap) { - converterResult[o] = r; - OnConversionGenerated(o, r, c); - } - }; + ConversionGenerated = (o, r, c) => + { + converterResult[o] = r; + OnConversionGenerated(o, r, c); + } + }; - working.GetPlayableBeatmap(CreateRuleset().RulesetInfo, mods); + working.GetPlayableBeatmap(CreateRuleset().RulesetInfo, mods); - string afterConversion = beatmap.Serialize(); + string afterConversion = beatmap.Serialize(); - Assert.AreEqual(beforeConversion, afterConversion, "Conversion altered original beatmap"); + Assert.AreEqual(beforeConversion, afterConversion, "Conversion altered original beatmap"); - return new ConvertResult - { - Mappings = converterResult.Select(r => + return new ConvertResult { - var mapping = CreateConvertMapping(r.Key); - mapping.StartTime = r.Key.StartTime; - mapping.Objects.AddRange(r.Value.SelectMany(CreateConvertValue)); - return mapping; - }).ToList() - }; + Mappings = converterResult.Select(r => + { + var mapping = CreateConvertMapping(r.Key); + mapping.StartTime = r.Key.StartTime; + mapping.Objects.AddRange(r.Value.SelectMany(CreateConvertValue)); + return mapping; + }).ToList() + }; + }, TaskCreationOptions.LongRunning); + + if (!conversionTask.Wait(10000)) + Assert.Fail("Conversion timed out"); + + return conversionTask.GetResultSafely(); } protected virtual void OnConversionGenerated(HitObject original, IEnumerable result, IBeatmapConverter beatmapConverter) diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs index 571c3e759f..76acac3f8b 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs @@ -11,6 +11,7 @@ using System.Threading; using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions; using osu.Game.Beatmaps; using osu.Game.Online.API; using osu.Game.Online.API.Requests.Responses; @@ -77,7 +78,7 @@ namespace osu.Game.Tests.Visual.Multiplayer private void addUser(MultiplayerRoomUser user) { - ((IMultiplayerClient)this).UserJoined(user).Wait(); + ((IMultiplayerClient)this).UserJoined(user).WaitSafely(); // We want the user to be immediately available for testing, so force a scheduler update to run the update-bound continuation. Scheduler.Update(); @@ -93,7 +94,7 @@ namespace osu.Game.Tests.Visual.Multiplayer .Select(team => (teamID: team.ID, userCount: Room.Users.Count(u => (u.MatchState as TeamVersusUserState)?.TeamID == team.ID))) .OrderBy(pair => pair.userCount) .First().teamID; - ((IMultiplayerClient)this).MatchUserStateChanged(user.UserID, new TeamVersusUserState { TeamID = bestTeam }).Wait(); + ((IMultiplayerClient)this).MatchUserStateChanged(user.UserID, new TeamVersusUserState { TeamID = bestTeam }).WaitSafely(); break; } } @@ -156,7 +157,7 @@ namespace osu.Game.Tests.Visual.Multiplayer ChangeRoomState(MultiplayerRoomState.Open); ((IMultiplayerClient)this).ResultsReady(); - FinishCurrentItem().Wait(); + FinishCurrentItem().WaitSafely(); } break; @@ -216,7 +217,7 @@ namespace osu.Game.Tests.Visual.Multiplayer Debug.Assert(Room != null); // emulate the server sending this after the join room. scheduler required to make sure the join room event is fired first (in Join). - changeMatchType(Room.Settings.MatchType).Wait(); + changeMatchType(Room.Settings.MatchType).WaitSafely(); RoomJoined = true; } diff --git a/osu.Game/Updater/UpdateManager.cs b/osu.Game/Updater/UpdateManager.cs index 28b828804c..9b9f354d23 100644 --- a/osu.Game/Updater/UpdateManager.cs +++ b/osu.Game/Updater/UpdateManager.cs @@ -97,7 +97,7 @@ namespace osu.Game.Updater private void load(OsuColour colours, ChangelogOverlay changelog, NotificationOverlay notificationOverlay) { Icon = FontAwesome.Solid.CheckSquare; - IconBackgound.Colour = colours.BlueDark; + IconBackground.Colour = colours.BlueDark; Activated = delegate { diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 2c97bae360..1430320c87 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -36,8 +36,8 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - - + + diff --git a/osu.iOS.props b/osu.iOS.props index de084c0297..3cef888447 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -60,8 +60,8 @@ - - + + @@ -83,10 +83,9 @@ - + - diff --git a/osu.sln.DotSettings b/osu.sln.DotSettings index a96f64cdc3..44d75f265c 100644 --- a/osu.sln.DotSettings +++ b/osu.sln.DotSettings @@ -19,6 +19,7 @@ HINT DO_NOT_SHOW WARNING + WARNING HINT HINT WARNING @@ -231,6 +232,7 @@ HINT DO_NOT_SHOW WARNING + WARNING WARNING WARNING WARNING @@ -920,26 +922,65 @@ private void load() { $END$ }; + True True True + True True True True True + True + True + True True + True + True True + True + True True True + True + True + True + True + True True True + True True True + True + True + True + True + True True True + True + True + True + True + True + True + True + True True True True True + True + True + True + True + True True True + True + True + True + True + True + True True