diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index 007e4341b8..1b06aa4d17 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -15,7 +15,7 @@ ] }, "smoogipoo.nvika": { - "version": "1.0.1", + "version": "1.0.3", "commands": [ "nvika" ] @@ -33,4 +33,4 @@ ] } } -} +} \ No newline at end of file diff --git a/.editorconfig b/.editorconfig index 3c4997c88d..be5652954b 100644 --- a/.editorconfig +++ b/.editorconfig @@ -113,7 +113,7 @@ dotnet_style_qualification_for_event = false:warning dotnet_style_predefined_type_for_locals_parameters_members = true:warning dotnet_style_predefined_type_for_member_access = true:warning csharp_style_var_when_type_is_apparent = true:none -csharp_style_var_for_built_in_types = true:none +csharp_style_var_for_built_in_types = false:warning csharp_style_var_elsewhere = true:silent #Style - modifiers diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0da1f9636b..2a3b2fd978 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -50,6 +50,48 @@ jobs: name: osu-test-results-${{matrix.os.prettyname}}-${{matrix.threadingMode}} path: ${{github.workspace}}/TestResults/TestResults-${{matrix.os.prettyname}}-${{matrix.threadingMode}}.trx + build-only-android: + name: Build only (Android) + runs-on: windows-latest + timeout-minutes: 60 + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Install .NET 5.0.x + uses: actions/setup-dotnet@v1 + with: + dotnet-version: "5.0.x" + + - name: Setup MSBuild + uses: microsoft/setup-msbuild@v1 + + - name: Build + run: msbuild osu.Android.slnf /restore /p:Configuration=Debug + + build-only-ios: + # While this workflow technically *can* run, it fails as iOS builds are blocked by multiple issues. + # See https://github.com/ppy/osu-framework/issues/4677 for the details. + # The job can be unblocked once those issues are resolved and game deployments can happen again. + if: false + name: Build only (iOS) + runs-on: macos-latest + timeout-minutes: 60 + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Install .NET 5.0.x + uses: actions/setup-dotnet@v1 + with: + dotnet-version: "5.0.x" + + # Contrary to seemingly any other msbuild, msbuild running on macOS/Mono + # cannot accept .sln(f) files as arguments. + # Build just the main game for now. + - name: Build + run: msbuild osu.iOS/osu.iOS.csproj /restore /p:Configuration=Debug + inspect-code: name: Code Quality runs-on: ubuntu-latest diff --git a/Directory.Build.props b/Directory.Build.props index 53ad973e47..894ea25c8b 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -16,7 +16,7 @@ - + diff --git a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/osu.Game.Rulesets.EmptyFreeform.Tests.csproj b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/osu.Game.Rulesets.EmptyFreeform.Tests.csproj index e28053d0ca..e9b92be0c3 100644 --- a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/osu.Game.Rulesets.EmptyFreeform.Tests.csproj +++ b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/osu.Game.Rulesets.EmptyFreeform.Tests.csproj @@ -10,7 +10,7 @@ - + diff --git a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj index 027bd0b7e2..e145dd7b69 100644 --- a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj +++ b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj @@ -10,7 +10,7 @@ - + diff --git a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/osu.Game.Rulesets.EmptyScrolling.Tests.csproj b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/osu.Game.Rulesets.EmptyScrolling.Tests.csproj index e2c715d385..a301432a6c 100644 --- a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/osu.Game.Rulesets.EmptyScrolling.Tests.csproj +++ b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/osu.Game.Rulesets.EmptyScrolling.Tests.csproj @@ -10,7 +10,7 @@ - + diff --git a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj index 027bd0b7e2..e145dd7b69 100644 --- a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj +++ b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj @@ -10,7 +10,7 @@ - + diff --git a/osu.Android.props b/osu.Android.props index f552aff2f2..dec994bcb2 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -51,8 +51,8 @@ - - + + diff --git a/osu.Android/OsuGameActivity.cs b/osu.Android/OsuGameActivity.cs index 0bcbfc4baf..fec96c9165 100644 --- a/osu.Android/OsuGameActivity.cs +++ b/osu.Android/OsuGameActivity.cs @@ -20,6 +20,7 @@ namespace osu.Android [Activity(Theme = "@android:style/Theme.NoTitleBar", MainLauncher = true, ScreenOrientation = ScreenOrientation.FullUser, SupportsPictureInPicture = false, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize, HardwareAccelerated = false, LaunchMode = LaunchMode.SingleInstance, Exported = true)] [IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryDefault }, DataScheme = "content", DataPathPattern = ".*\\\\.osz", DataHost = "*", DataMimeType = "*/*")] [IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryDefault }, DataScheme = "content", DataPathPattern = ".*\\\\.osk", DataHost = "*", DataMimeType = "*/*")] + [IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryDefault }, DataScheme = "content", DataPathPattern = ".*\\\\.osr", DataHost = "*", DataMimeType = "*/*")] [IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryDefault }, DataScheme = "content", DataMimeType = "application/x-osu-beatmap-archive")] [IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryDefault }, DataScheme = "content", DataMimeType = "application/x-osu-skin-archive")] [IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryDefault }, DataScheme = "content", DataMimeType = "application/x-osu-replay")] diff --git a/osu.Desktop/OsuGameDesktop.cs b/osu.Desktop/OsuGameDesktop.cs index 4de1e84fbf..19e1252a4d 100644 --- a/osu.Desktop/OsuGameDesktop.cs +++ b/osu.Desktop/OsuGameDesktop.cs @@ -156,7 +156,7 @@ namespace osu.Desktop { lock (importableFiles) { - var firstExtension = Path.GetExtension(filePaths.First()); + string firstExtension = Path.GetExtension(filePaths.First()); if (filePaths.Any(f => Path.GetExtension(f) != firstExtension)) return; @@ -177,7 +177,7 @@ namespace osu.Desktop { Logger.Log($"Handling batch import of {importableFiles.Count} files"); - var paths = importableFiles.ToArray(); + string[] paths = importableFiles.ToArray(); importableFiles.Clear(); Task.Factory.StartNew(() => Import(paths), TaskCreationOptions.LongRunning); diff --git a/osu.Desktop/Program.cs b/osu.Desktop/Program.cs index 8ccd23b418..898f7d5105 100644 --- a/osu.Desktop/Program.cs +++ b/osu.Desktop/Program.cs @@ -22,17 +22,17 @@ namespace osu.Desktop public static int Main(string[] args) { // Back up the cwd before DesktopGameHost changes it - var cwd = Environment.CurrentDirectory; + string cwd = Environment.CurrentDirectory; string gameName = base_game_name; bool tournamentClient = false; - foreach (var arg in args) + foreach (string arg in args) { - var split = arg.Split('='); + string[] split = arg.Split('='); - var key = split[0]; - var val = split.Length > 1 ? split[1] : string.Empty; + string key = split[0]; + string val = split.Length > 1 ? split[1] : string.Empty; switch (key) { @@ -62,7 +62,7 @@ namespace osu.Desktop { var importer = new ArchiveImportIPCChannel(host); - foreach (var file in args) + foreach (string file in args) { Console.WriteLine(@"Importing {0}", file); if (!importer.ImportAsync(Path.GetFullPath(file, cwd)).Wait(3000)) diff --git a/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneCatchDistanceSnapGrid.cs b/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneCatchDistanceSnapGrid.cs new file mode 100644 index 0000000000..2be0b7e9b2 --- /dev/null +++ b/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneCatchDistanceSnapGrid.cs @@ -0,0 +1,91 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Input.Events; +using osu.Framework.Timing; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Catch.Edit; +using osu.Game.Rulesets.Catch.Edit.Blueprints.Components; +using osu.Game.Rulesets.Catch.Objects; +using osu.Game.Rulesets.Catch.UI; +using osu.Game.Rulesets.UI; +using osu.Game.Rulesets.UI.Scrolling; +using osu.Game.Tests.Visual; +using osuTK; + +namespace osu.Game.Rulesets.Catch.Tests.Editor +{ + public class TestSceneCatchDistanceSnapGrid : OsuManualInputManagerTestScene + { + private readonly ManualClock manualClock = new ManualClock(); + + [Cached(typeof(Playfield))] + private readonly CatchPlayfield playfield; + + private ScrollingHitObjectContainer hitObjectContainer => playfield.HitObjectContainer; + + private readonly CatchDistanceSnapGrid distanceGrid; + + private readonly FruitOutline fruitOutline; + + private readonly Fruit fruit = new Fruit(); + + public TestSceneCatchDistanceSnapGrid() + { + Child = new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Y, + Width = 500, + + Children = new Drawable[] + { + new ScrollingTestContainer(ScrollingDirection.Down) + { + RelativeSizeAxes = Axes.Both, + Child = playfield = new CatchPlayfield(new BeatmapDifficulty()) + { + RelativeSizeAxes = Axes.Both, + Clock = new FramedClock(manualClock) + } + }, + distanceGrid = new CatchDistanceSnapGrid(new double[] { 0, -1, 1 }), + fruitOutline = new FruitOutline() + }, + }; + } + + protected override void Update() + { + base.Update(); + + distanceGrid.StartTime = 100; + distanceGrid.StartX = 250; + + Vector2 screenSpacePosition = InputManager.CurrentState.Mouse.Position; + + var result = distanceGrid.GetSnappedPosition(screenSpacePosition); + + if (result != null) + { + fruit.OriginalX = hitObjectContainer.ToLocalSpace(result.ScreenSpacePosition).X; + + if (result.Time != null) + fruit.StartTime = result.Time.Value; + } + + fruitOutline.Position = CatchHitObjectUtils.GetStartPosition(hitObjectContainer, fruit); + fruitOutline.UpdateFrom(fruit); + } + + protected override bool OnScroll(ScrollEvent e) + { + manualClock.CurrentTime -= e.ScrollDelta.Y * 50; + return true; + } + } +} diff --git a/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneJuiceStreamSelectionBlueprint.cs b/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneJuiceStreamSelectionBlueprint.cs index 155d033dd0..fb77fb1efd 100644 --- a/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneJuiceStreamSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneJuiceStreamSelectionBlueprint.cs @@ -49,7 +49,7 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor AddAssert("correct outline count", () => { - var expected = hitObject.NestedHitObjects.Count(h => !(h is TinyDroplet)); + int expected = hitObject.NestedHitObjects.Count(h => !(h is TinyDroplet)); return this.ChildrenOfType().Count() == expected; }); AddAssert("correct vertex piece count", () => diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs index f291bfed13..29a7b03ad3 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs @@ -103,7 +103,7 @@ namespace osu.Game.Rulesets.Catch.Tests [Test] public void TestCatcherCatchWidth() { - var halfWidth = Catcher.CalculateCatchWidth(new BeatmapDifficulty { CircleSize = 0 }) / 2; + float halfWidth = Catcher.CalculateCatchWidth(new BeatmapDifficulty { CircleSize = 0 }) / 2; AddStep("catch fruit", () => { attemptCatch(new Fruit { X = -halfWidth + 1 }); @@ -237,7 +237,7 @@ namespace osu.Game.Rulesets.Catch.Tests private void attemptCatch(Func hitObject, int count) { - for (var i = 0; i < count; i++) + for (int i = 0; i < count; i++) attemptCatch(hitObject(), out _, out _); } diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjects.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjects.cs index e7c7dc3c98..3c61eb19e5 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjects.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjects.cs @@ -118,7 +118,7 @@ namespace osu.Game.Rulesets.Catch.Tests private void spawnJuiceStream(bool hit = false) { - var xCoords = getXCoords(hit); + float xCoords = getXCoords(hit); var juice = new JuiceStream { diff --git a/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj b/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj index 6457ec92da..4c8b9b2b08 100644 --- a/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj +++ b/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj @@ -2,7 +2,7 @@ - + diff --git a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs index 87cc2c45e8..346a09cac8 100644 --- a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs +++ b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs @@ -233,7 +233,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps int thisDirection = nextObject.EffectiveX > currentObject.EffectiveX ? 1 : -1; double timeToNext = nextObject.StartTime - currentObject.StartTime - 1000f / 60f / 4; // 1/4th of a frame of grace time, taken from osu-stable double distanceToNext = Math.Abs(nextObject.EffectiveX - currentObject.EffectiveX) - (lastDirection == thisDirection ? lastExcess : halfCatcherWidth); - float distanceToHyper = (float)(timeToNext * Catcher.BASE_SPEED - distanceToNext); + float distanceToHyper = (float)(timeToNext * Catcher.BASE_DASH_SPEED - distanceToNext); if (distanceToHyper < 0) { diff --git a/osu.Game.Rulesets.Catch/Difficulty/Preprocessing/CatchDifficultyHitObject.cs b/osu.Game.Rulesets.Catch/Difficulty/Preprocessing/CatchDifficultyHitObject.cs index e19098c580..b22ec93b43 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/Preprocessing/CatchDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/Preprocessing/CatchDifficultyHitObject.cs @@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty.Preprocessing : base(hitObject, lastObject, clockRate) { // We will scale everything by this factor, so we can assume a uniform CircleSize among beatmaps. - var scalingFactor = normalized_hitobject_radius / halfCatcherWidth; + float scalingFactor = normalized_hitobject_radius / halfCatcherWidth; NormalizedPosition = BaseObject.EffectiveX * scalingFactor; LastNormalizedPosition = LastObject.EffectiveX * scalingFactor; diff --git a/osu.Game.Rulesets.Catch/Edit/CatchDistanceSnapGrid.cs b/osu.Game.Rulesets.Catch/Edit/CatchDistanceSnapGrid.cs new file mode 100644 index 0000000000..9a78c7ff86 --- /dev/null +++ b/osu.Game.Rulesets.Catch/Edit/CatchDistanceSnapGrid.cs @@ -0,0 +1,141 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using System.Linq; +using JetBrains.Annotations; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Lines; +using osu.Framework.Graphics.Primitives; +using osu.Game.Rulesets.Catch.UI; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.UI; +using osu.Game.Rulesets.UI.Scrolling; +using osuTK; + +namespace osu.Game.Rulesets.Catch.Edit +{ + /// + /// The guide lines used in the osu!catch editor to compose patterns that can be caught with constant speed. + /// Currently, only forward placement (an object is snapped based on the previous object, not the opposite) is supported. + /// + public class CatchDistanceSnapGrid : CompositeDrawable + { + public double StartTime { get; set; } + + public float StartX { get; set; } + + private const double max_vertical_line_length_in_time = CatchPlayfield.WIDTH / Catcher.BASE_WALK_SPEED; + + private readonly double[] velocities; + + private readonly List verticalPaths = new List(); + + private readonly List verticalLineVertices = new List(); + + [Resolved] + private Playfield playfield { get; set; } + + private ScrollingHitObjectContainer hitObjectContainer => (ScrollingHitObjectContainer)playfield.HitObjectContainer; + + public CatchDistanceSnapGrid(double[] velocities) + { + RelativeSizeAxes = Axes.Both; + Anchor = Anchor.BottomLeft; + + this.velocities = velocities; + + for (int i = 0; i < velocities.Length; i++) + { + verticalPaths.Add(new SmoothPath + { + PathRadius = 2, + Alpha = 0.5f, + }); + + verticalLineVertices.Add(new[] { Vector2.Zero, Vector2.Zero }); + } + + AddRangeInternal(verticalPaths); + } + + protected override void Update() + { + base.Update(); + + double currentTime = hitObjectContainer.Time.Current; + + for (int i = 0; i < velocities.Length; i++) + { + double velocity = velocities[i]; + + // The line ends at the top of the playfield. + double endTime = hitObjectContainer.TimeAtPosition(-hitObjectContainer.DrawHeight, currentTime); + + // Non-vertical lines are cut at the sides of the playfield. + // Vertical lines are cut at some reasonable length. + if (velocity > 0) + endTime = Math.Min(endTime, StartTime + (CatchPlayfield.WIDTH - StartX) / velocity); + else if (velocity < 0) + endTime = Math.Min(endTime, StartTime + StartX / -velocity); + else + endTime = Math.Min(endTime, StartTime + max_vertical_line_length_in_time); + + Vector2[] lineVertices = verticalLineVertices[i]; + lineVertices[0] = calculatePosition(velocity, StartTime); + lineVertices[1] = calculatePosition(velocity, endTime); + + var verticalPath = verticalPaths[i]; + verticalPath.Vertices = verticalLineVertices[i]; + verticalPath.OriginPosition = verticalPath.PositionInBoundingBox(Vector2.Zero); + } + + Vector2 calculatePosition(double velocity, double time) + { + // Don't draw inverted lines. + time = Math.Max(time, StartTime); + + float x = StartX + (float)((time - StartTime) * velocity); + float y = hitObjectContainer.PositionAtTime(time, currentTime); + return new Vector2(x, y); + } + } + + [CanBeNull] + public SnapResult GetSnappedPosition(Vector2 screenSpacePosition) + { + double time = hitObjectContainer.TimeAtScreenSpacePosition(screenSpacePosition); + + // If the cursor is below the distance snap grid, snap to the origin. + // Not returning `null` to retain the continuous snapping behavior when the cursor is slightly below the origin. + // This behavior is not currently visible in the editor because editor chooses the snap start time based on the mouse position. + if (time <= StartTime) + { + float y = hitObjectContainer.PositionAtTime(StartTime); + Vector2 originPosition = hitObjectContainer.ToScreenSpace(new Vector2(StartX, y)); + return new SnapResult(originPosition, StartTime); + } + + return enumerateSnappingCandidates(time) + .OrderBy(pos => Vector2.DistanceSquared(screenSpacePosition, pos.ScreenSpacePosition)) + .FirstOrDefault(); + } + + private IEnumerable enumerateSnappingCandidates(double time) + { + float y = hitObjectContainer.PositionAtTime(time); + + foreach (double velocity in velocities) + { + float x = (float)(StartX + (time - StartTime) * velocity); + Vector2 screenSpacePosition = hitObjectContainer.ToScreenSpace(new Vector2(x, y + hitObjectContainer.DrawHeight)); + yield return new SnapResult(screenSpacePosition, time); + } + } + + protected override bool ComputeIsMaskedAway(RectangleF maskingBounds) => false; + } +} diff --git a/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs b/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs index 050c2f625d..164f465438 100644 --- a/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs +++ b/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs @@ -2,14 +2,23 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using System.Linq; +using JetBrains.Annotations; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Input; using osu.Game.Beatmaps; +using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Catch.Objects; +using osu.Game.Rulesets.Catch.UI; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Tools; using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.UI; +using osu.Game.Screens.Edit.Components.TernaryButtons; using osu.Game.Screens.Edit.Compose.Components; using osuTK; @@ -17,6 +26,14 @@ namespace osu.Game.Rulesets.Catch.Edit { public class CatchHitObjectComposer : HitObjectComposer { + private const float distance_snap_radius = 50; + + private CatchDistanceSnapGrid distanceSnapGrid; + + private readonly Bindable distanceSnapToggle = new Bindable(); + + private InputManager inputManager; + public CatchHitObjectComposer(CatchRuleset ruleset) : base(ruleset) { @@ -30,6 +47,27 @@ namespace osu.Game.Rulesets.Catch.Edit RelativeSizeAxes = Axes.Both, PlayfieldBorderStyle = { Value = PlayfieldBorderStyle.Corners } }); + + LayerBelowRuleset.Add(distanceSnapGrid = new CatchDistanceSnapGrid(new[] + { + 0.0, + Catcher.BASE_DASH_SPEED, -Catcher.BASE_DASH_SPEED, + Catcher.BASE_WALK_SPEED, -Catcher.BASE_WALK_SPEED, + })); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + inputManager = GetContainingInputManager(); + } + + protected override void Update() + { + base.Update(); + + updateDistanceSnapGrid(); } protected override DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList mods = null) => @@ -42,14 +80,95 @@ namespace osu.Game.Rulesets.Catch.Edit new BananaShowerCompositionTool() }; + protected override IEnumerable CreateTernaryButtons() => base.CreateTernaryButtons().Concat(new[] + { + new TernaryButton(distanceSnapToggle, "Distance Snap", () => new SpriteIcon { Icon = FontAwesome.Solid.Ruler }) + }); + public override SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition) { var result = base.SnapScreenSpacePositionToValidTime(screenSpacePosition); - // TODO: implement position snap result.ScreenSpacePosition.X = screenSpacePosition.X; + + if (distanceSnapGrid.IsPresent && distanceSnapGrid.GetSnappedPosition(result.ScreenSpacePosition) is SnapResult snapResult && + Vector2.Distance(snapResult.ScreenSpacePosition, result.ScreenSpacePosition) < distance_snap_radius) + { + result = snapResult; + } + return result; } protected override ComposeBlueprintContainer CreateBlueprintContainer() => new CatchBlueprintContainer(this); + + [CanBeNull] + private PalpableCatchHitObject getLastSnappableHitObject(double time) + { + var hitObject = EditorBeatmap.HitObjects.OfType().LastOrDefault(h => h.GetEndTime() < time && !(h is BananaShower)); + + switch (hitObject) + { + case Fruit fruit: + return fruit; + + case JuiceStream juiceStream: + return juiceStream.NestedHitObjects.OfType().LastOrDefault(h => !(h is TinyDroplet)); + + default: + return null; + } + } + + [CanBeNull] + private PalpableCatchHitObject getDistanceSnapGridSourceHitObject() + { + switch (BlueprintContainer.CurrentTool) + { + case SelectTool _: + if (EditorBeatmap.SelectedHitObjects.Count == 0) + return null; + + double minTime = EditorBeatmap.SelectedHitObjects.Min(hitObject => hitObject.StartTime); + return getLastSnappableHitObject(minTime); + + case FruitCompositionTool _: + case JuiceStreamCompositionTool _: + if (!CursorInPlacementArea) + return null; + + if (EditorBeatmap.PlacementObject.Value is JuiceStream) + { + // Juice stream path is not subject to snapping. + return null; + } + + double timeAtCursor = ((CatchPlayfield)Playfield).TimeAtScreenSpacePosition(inputManager.CurrentState.Mouse.Position); + return getLastSnappableHitObject(timeAtCursor); + + default: + return null; + } + } + + private void updateDistanceSnapGrid() + { + if (distanceSnapToggle.Value != TernaryState.True) + { + distanceSnapGrid.Hide(); + return; + } + + var sourceHitObject = getDistanceSnapGridSourceHitObject(); + + if (sourceHitObject == null) + { + distanceSnapGrid.Hide(); + return; + } + + distanceSnapGrid.Show(); + distanceSnapGrid.StartTime = sourceHitObject.GetEndTime(); + distanceSnapGrid.StartX = sourceHitObject.EffectiveX; + } } } diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModAutoplay.cs b/osu.Game.Rulesets.Catch/Mods/CatchModAutoplay.cs index f1b51e51d0..6f3e6763bd 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModAutoplay.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModAutoplay.cs @@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Catch.Mods { public override Score CreateReplayScore(IBeatmap beatmap, IReadOnlyList mods) => new Score { - ScoreInfo = new ScoreInfo { User = new User { Username = "osu!salad!" } }, + ScoreInfo = new ScoreInfo { User = new User { Username = "osu!salad" } }, Replay = new CatchAutoGenerator(beatmap).Generate(), }; } diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModCinema.cs b/osu.Game.Rulesets.Catch/Mods/CatchModCinema.cs index d53d019e90..1b7d254321 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModCinema.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModCinema.cs @@ -15,7 +15,7 @@ namespace osu.Game.Rulesets.Catch.Mods { public override Score CreateReplayScore(IBeatmap beatmap, IReadOnlyList mods) => new Score { - ScoreInfo = new ScoreInfo { User = new User { Username = "osu!salad!" } }, + ScoreInfo = new ScoreInfo { User = new User { Username = "osu!salad" } }, Replay = new CatchAutoGenerator(beatmap).Generate(), }; } diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModHidden.cs b/osu.Game.Rulesets.Catch/Mods/CatchModHidden.cs index d48edbcd74..4b6f79df88 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModHidden.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModHidden.cs @@ -52,8 +52,8 @@ namespace osu.Game.Rulesets.Catch.Mods { var hitObject = drawable.HitObject; - var offset = hitObject.TimePreempt * fade_out_offset_multiplier; - var duration = offset - hitObject.TimePreempt * fade_out_duration_multiplier; + double offset = hitObject.TimePreempt * fade_out_offset_multiplier; + double duration = offset - hitObject.TimePreempt * fade_out_duration_multiplier; using (drawable.BeginAbsoluteSequence(hitObject.StartTime - offset)) drawable.FadeOut(duration); diff --git a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs index 6d5a960f06..282afb6343 100644 --- a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs +++ b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs @@ -145,7 +145,7 @@ namespace osu.Game.Rulesets.Catch.Objects public double Distance => Path.Distance; - public List> NodeSamples { get; set; } = new List>(); + public IList> NodeSamples { get; set; } = new List>(); public double? LegacyLastTickOffset { get; set; } } diff --git a/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs b/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs index 2fc05701db..7c84cb24f3 100644 --- a/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs +++ b/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs @@ -26,9 +26,6 @@ namespace osu.Game.Rulesets.Catch.Replays if (Beatmap.HitObjects.Count == 0) return; - // todo: add support for HT DT - const double dash_speed = Catcher.BASE_SPEED; - const double movement_speed = dash_speed / 2; float lastPosition = CatchPlayfield.CENTER_X; double lastTime = 0; @@ -47,8 +44,8 @@ namespace osu.Game.Rulesets.Catch.Replays // The case where positionChange > 0 and timeAvailable == 0 results in PositiveInfinity which provides expected beheaviour. double speedRequired = positionChange == 0 ? 0 : positionChange / timeAvailable; - bool dashRequired = speedRequired > movement_speed; - bool impossibleJump = speedRequired > movement_speed * 2; + bool dashRequired = speedRequired > Catcher.BASE_WALK_SPEED; + bool impossibleJump = speedRequired > Catcher.BASE_DASH_SPEED; // todo: get correct catcher size, based on difficulty CS. const float catcher_width_half = Catcher.BASE_SIZE * 0.3f * 0.5f; @@ -73,7 +70,7 @@ namespace osu.Game.Rulesets.Catch.Replays else if (dashRequired) { // we do a movement in two parts - the dash part then the normal part... - double timeAtNormalSpeed = positionChange / movement_speed; + double timeAtNormalSpeed = positionChange / Catcher.BASE_WALK_SPEED; double timeWeNeedToSave = timeAtNormalSpeed - timeAvailable; double timeAtDashSpeed = timeWeNeedToSave / 2; @@ -86,7 +83,7 @@ namespace osu.Game.Rulesets.Catch.Replays } else { - double timeBefore = positionChange / movement_speed; + double timeBefore = positionChange / Catcher.BASE_WALK_SPEED; addFrame(h.StartTime - timeBefore, lastPosition); addFrame(h.StartTime, h.EffectiveX); diff --git a/osu.Game.Rulesets.Catch/Replays/CatchFramedReplayInputHandler.cs b/osu.Game.Rulesets.Catch/Replays/CatchFramedReplayInputHandler.cs index 137328b1c3..bd742ce6a6 100644 --- a/osu.Game.Rulesets.Catch/Replays/CatchFramedReplayInputHandler.cs +++ b/osu.Game.Rulesets.Catch/Replays/CatchFramedReplayInputHandler.cs @@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Catch.Replays public override void CollectPendingInputs(List inputs) { - var position = Interpolation.ValueAt(CurrentTime, StartFrame.Position, EndFrame.Position, StartFrame.Time, EndFrame.Time); + float position = Interpolation.ValueAt(CurrentTime, StartFrame.Position, EndFrame.Position, StartFrame.Time, EndFrame.Time); inputs.Add(new CatchReplayState { diff --git a/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs b/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs index 10fc4e78b2..faad95e386 100644 --- a/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs @@ -66,7 +66,7 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy return null; case CatchSkinComponents.Catcher: - var version = GetConfig(LegacySkinConfiguration.LegacySetting.Version)?.Value ?? 1; + decimal version = GetConfig(SkinConfiguration.LegacySetting.Version)?.Value ?? 1; if (version < 2.3m) { diff --git a/osu.Game.Rulesets.Catch/UI/Catcher.cs b/osu.Game.Rulesets.Catch/UI/Catcher.cs index 3745099010..2a13190cc5 100644 --- a/osu.Game.Rulesets.Catch/UI/Catcher.cs +++ b/osu.Game.Rulesets.Catch/UI/Catcher.cs @@ -57,14 +57,19 @@ namespace osu.Game.Rulesets.Catch.UI public bool CatchFruitOnPlate { get; set; } = true; /// - /// The relative space to cover in 1 millisecond. based on 1 game pixel per millisecond as in osu-stable. + /// The speed of the catcher when the catcher is dashing. /// - public const double BASE_SPEED = 1.0; + public const double BASE_DASH_SPEED = 1.0; /// - /// The current speed of the catcher. + /// The speed of the catcher when the catcher is not dashing. /// - public double Speed => (Dashing ? 1 : 0.5) * BASE_SPEED * hyperDashModifier; + public const double BASE_WALK_SPEED = 0.5; + + /// + /// The current speed of the catcher with the hyper-dash modifier applied. + /// + public double Speed => (Dashing ? BASE_DASH_SPEED : BASE_WALK_SPEED) * hyperDashModifier; /// /// The amount by which caught fruit should be scaled down to fit on the plate. @@ -226,11 +231,11 @@ namespace osu.Game.Rulesets.Catch.UI if (result.IsHit && hitObject.HyperDash) { var target = hitObject.HyperDashTarget; - var timeDifference = target.StartTime - hitObject.StartTime; + double timeDifference = target.StartTime - hitObject.StartTime; double positionDifference = target.EffectiveX - X; - var velocity = positionDifference / Math.Max(1.0, timeDifference - 1000.0 / 60.0); + double velocity = positionDifference / Math.Max(1.0, timeDifference - 1000.0 / 60.0); - SetHyperDashState(Math.Abs(velocity), target.EffectiveX); + SetHyperDashState(Math.Abs(velocity) / BASE_DASH_SPEED, target.EffectiveX); } else SetHyperDashState(); @@ -266,7 +271,7 @@ namespace osu.Game.Rulesets.Catch.UI /// When this catcher crosses this position, this catcher ends hyper-dashing. public void SetHyperDashState(double modifier = 1, float targetPosition = -1) { - var wasHyperDashing = HyperDashing; + bool wasHyperDashing = HyperDashing; if (modifier <= 1 || X == targetPosition) { diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/ManiaPlacementBlueprintTestScene.cs b/osu.Game.Rulesets.Mania.Tests/Editor/ManiaPlacementBlueprintTestScene.cs index ece523e84c..94f385bbf1 100644 --- a/osu.Game.Rulesets.Mania.Tests/Editor/ManiaPlacementBlueprintTestScene.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/ManiaPlacementBlueprintTestScene.cs @@ -43,7 +43,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor protected override SnapResult SnapForBlueprint(PlacementBlueprint blueprint) { - var time = column.TimeAtScreenSpacePosition(InputManager.CurrentState.Mouse.Position); + double time = column.TimeAtScreenSpacePosition(InputManager.CurrentState.Mouse.Position); var pos = column.ScreenSpacePositionAtTime(time); return new SnapResult(pos, time, column); diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs b/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs index 3d4bc4748b..948f088b4e 100644 --- a/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs +++ b/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs @@ -111,7 +111,7 @@ namespace osu.Game.Rulesets.Mania.Tests public int CompareTo(ConvertValue other) { - var result = StartTime.CompareTo(other.StartTime); + int result = StartTime.CompareTo(other.StartTime); if (result != 0) return result; diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapSampleConversionTest.cs b/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapSampleConversionTest.cs index c8feb4ae24..9c690f360a 100644 --- a/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapSampleConversionTest.cs +++ b/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapSampleConversionTest.cs @@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Mania.Tests private IList getSampleNames(IList hitSampleInfo) => hitSampleInfo.Select(sample => sample.LookupNames.First()).ToList(); - private IList> getNodeSampleNames(List> hitSampleInfo) + private IList> getNodeSampleNames(IList> hitSampleInfo) => hitSampleInfo?.Select(getSampleNames) .ToList(); diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaColumnTypeTest.cs b/osu.Game.Rulesets.Mania.Tests/ManiaColumnTypeTest.cs index 40a6e1fdae..66fe6d8cc5 100644 --- a/osu.Game.Rulesets.Mania.Tests/ManiaColumnTypeTest.cs +++ b/osu.Game.Rulesets.Mania.Tests/ManiaColumnTypeTest.cs @@ -43,7 +43,7 @@ namespace osu.Game.Rulesets.Mania.Tests private IEnumerable getResults(StageDefinition definition) { - for (var i = 0; i < definition.Columns; i++) + for (int i = 0; i < definition.Columns; i++) yield return definition.GetTypeOfColumn(i); } } diff --git a/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj b/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj index 674a22df98..fad39ef9d6 100644 --- a/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj +++ b/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj @@ -2,7 +2,7 @@ - + diff --git a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs index ebfbaccd31..9d0aaec2ba 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs @@ -42,8 +42,8 @@ namespace osu.Game.Rulesets.Mania.Beatmaps { IsForCurrentRuleset = beatmap.BeatmapInfo.Ruleset.Equals(ruleset.RulesetInfo); - var roundedCircleSize = Math.Round(beatmap.Difficulty.CircleSize); - var roundedOverallDifficulty = Math.Round(beatmap.Difficulty.OverallDifficulty); + double roundedCircleSize = Math.Round(beatmap.Difficulty.CircleSize); + double roundedOverallDifficulty = Math.Round(beatmap.Difficulty.OverallDifficulty); if (IsForCurrentRuleset) { @@ -73,7 +73,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps public static int GetColumnCountForNonConvert(BeatmapInfo beatmapInfo) { - var roundedCircleSize = Math.Round(beatmapInfo.BaseDifficulty.CircleSize); + double roundedCircleSize = Math.Round(beatmapInfo.BaseDifficulty.CircleSize); return (int)Math.Max(1, roundedCircleSize); } diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs index 1ed045f7e0..5f8b58d94d 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs @@ -488,12 +488,12 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy /// Retrieves the list of node samples that occur at time greater than or equal to . /// /// The time to retrieve node samples at. - private List> nodeSamplesAt(int time) + private IList> nodeSamplesAt(int time) { if (!(HitObject is IHasPathWithRepeats curveData)) return null; - var index = SegmentDuration == 0 ? 0 : (time - StartTime) / SegmentDuration; + int index = SegmentDuration == 0 ? 0 : (time - StartTime) / SegmentDuration; // avoid slicing the list & creating copies, if at all possible. return index == 0 ? curveData.NodeSamples : curveData.NodeSamples.Skip(index).ToList(); diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs index 54c37e9742..53b059b4e2 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs @@ -302,7 +302,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy var pattern = new Pattern(); - int noteCount = getRandomNoteCountMirrored(centreProbability, p2, p3, out var addToCentre); + int noteCount = getRandomNoteCountMirrored(centreProbability, p2, p3, out bool addToCentre); int columnLimit = (TotalColumns % 2 == 0 ? TotalColumns : TotalColumns - 1) / 2; int nextColumn = GetRandomColumn(upperBound: columnLimit); diff --git a/osu.Game.Rulesets.Mania/Difficulty/Skills/Strain.cs b/osu.Game.Rulesets.Mania/Difficulty/Skills/Strain.cs index 01d930d585..ab6bd78ece 100644 --- a/osu.Game.Rulesets.Mania/Difficulty/Skills/Strain.cs +++ b/osu.Game.Rulesets.Mania/Difficulty/Skills/Strain.cs @@ -35,8 +35,8 @@ namespace osu.Game.Rulesets.Mania.Difficulty.Skills protected override double StrainValueOf(DifficultyHitObject current) { var maniaCurrent = (ManiaDifficultyHitObject)current; - var endTime = maniaCurrent.EndTime; - var column = maniaCurrent.BaseObject.Column; + double endTime = maniaCurrent.EndTime; + int column = maniaCurrent.BaseObject.Column; double holdFactor = 1.0; // Factor to all additional strains in case something else is held double holdAddition = 0; // Addition to the current note in case it's a hold and has to be released awkwardly diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index 1f79dae280..a9b16c61d4 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -316,7 +316,7 @@ namespace osu.Game.Rulesets.Mania case PlayfieldType.Dual: { - var keys = getDualStageKeyCount(variant); + int keys = getDualStageKeyCount(variant); return $"{keys}K + {keys}K"; } } diff --git a/osu.Game.Rulesets.Mania/MathUtils/LegacySortHelper.cs b/osu.Game.Rulesets.Mania/MathUtils/LegacySortHelper.cs index 0f4829028f..5c595323c3 100644 --- a/osu.Game.Rulesets.Mania/MathUtils/LegacySortHelper.cs +++ b/osu.Game.Rulesets.Mania/MathUtils/LegacySortHelper.cs @@ -62,9 +62,7 @@ namespace osu.Game.Rulesets.Mania.MathUtils if (i < j) { - T key = keys[i]; - keys[i] = keys[j]; - keys[j] = key; + (keys[i], keys[j]) = (keys[j], keys[i]); } i++; @@ -122,7 +120,7 @@ namespace osu.Game.Rulesets.Mania.MathUtils while (i <= n / 2) { - var child = 2 * i; + int child = 2 * i; if (child < n && comparer.Compare(keys[lo + child - 1], keys[lo + child]) < 0) { @@ -142,11 +140,7 @@ namespace osu.Game.Rulesets.Mania.MathUtils private static void swap(T[] a, int i, int j) { if (i != j) - { - T t = a[i]; - a[i] = a[j]; - a[j] = t; - } + (a[i], a[j]) = (a[j], a[i]); } private static void swapIfGreater(T[] keys, IComparer comparer, int a, int b) @@ -154,11 +148,7 @@ namespace osu.Game.Rulesets.Mania.MathUtils if (a != b) { if (comparer.Compare(keys[a], keys[b]) > 0) - { - T key = keys[a]; - keys[a] = keys[b]; - keys[b] = key; - } + (keys[a], keys[b]) = (keys[b], keys[a]); } } } diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModAutoplay.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModAutoplay.cs index 6ae854e7f3..86f667466f 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModAutoplay.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModAutoplay.cs @@ -15,7 +15,7 @@ namespace osu.Game.Rulesets.Mania.Mods { public override Score CreateReplayScore(IBeatmap beatmap, IReadOnlyList mods) => new Score { - ScoreInfo = new ScoreInfo { User = new User { Username = "osu!topus!" } }, + ScoreInfo = new ScoreInfo { User = new User { Username = "osu!topus" } }, Replay = new ManiaAutoGenerator((ManiaBeatmap)beatmap).Generate(), }; } diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModCinema.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModCinema.cs index 064c55ed8d..1c06bb389b 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModCinema.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModCinema.cs @@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Mania.Mods { public override Score CreateReplayScore(IBeatmap beatmap, IReadOnlyList mods) => new Score { - ScoreInfo = new ScoreInfo { User = new User { Username = "osu!topus!" } }, + ScoreInfo = new ScoreInfo { User = new User { Username = "osu!topus" } }, Replay = new ManiaAutoGenerator((ManiaBeatmap)beatmap).Generate(), }; } diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModMirror.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModMirror.cs index f01884c97f..9c3744ea98 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModMirror.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModMirror.cs @@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Mania.Mods public void ApplyToBeatmap(IBeatmap beatmap) { - var availableColumns = ((ManiaBeatmap)beatmap).TotalColumns; + int availableColumns = ((ManiaBeatmap)beatmap).TotalColumns; beatmap.HitObjects.OfType().ForEach(h => h.Column = availableColumns - 1 - h.Column); } diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModRandom.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModRandom.cs index 6f2d4fe91e..f5d1a34d73 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModRandom.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModRandom.cs @@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Mania.Mods Seed.Value ??= RNG.Next(); var rng = new Random((int)Seed.Value); - var availableColumns = ((ManiaBeatmap)beatmap).TotalColumns; + int availableColumns = ((ManiaBeatmap)beatmap).TotalColumns; var shuffledColumns = Enumerable.Range(0, availableColumns).OrderBy(item => rng.Next()).ToList(); beatmap.HitObjects.OfType().ForEach(h => h.Column = shuffledColumns[h.Column]); diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTick.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTick.cs index f040dad135..cffa79322e 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTick.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTick.cs @@ -97,7 +97,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables if (Time.Current < HitObject.StartTime) return; - var startTime = holdStartTime?.Invoke(); + double? startTime = holdStartTime?.Invoke(); if (startTime == null || startTime > HitObject.StartTime) ApplyResult(r => r.Type = r.Judgement.MinResult); diff --git a/osu.Game.Rulesets.Mania/Objects/HoldNote.cs b/osu.Game.Rulesets.Mania/Objects/HoldNote.cs index c1937af7e4..db0d3e2c5a 100644 --- a/osu.Game.Rulesets.Mania/Objects/HoldNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/HoldNote.cs @@ -67,7 +67,7 @@ namespace osu.Game.Rulesets.Mania.Objects } } - public List> NodeSamples { get; set; } + public IList> NodeSamples { get; set; } /// /// The head note of the hold. diff --git a/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs b/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs index 517b708691..efe144ac03 100644 --- a/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs +++ b/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs @@ -75,7 +75,7 @@ namespace osu.Game.Rulesets.Mania.Replays { var currentObject = Beatmap.HitObjects[i]; var nextObjectInColumn = GetNextObject(i); // Get the next object that requires pressing the same button - var releaseTime = calculateReleaseTime(currentObject, nextObjectInColumn); + double releaseTime = calculateReleaseTime(currentObject, nextObjectInColumn); yield return new HitPoint { Time = currentObject.StartTime, Column = currentObject.Column }; diff --git a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyStageBackground.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyStageBackground.cs index fec3e9493e..952fc7ddd6 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyStageBackground.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyStageBackground.cs @@ -98,7 +98,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy float rightLineWidth = skin.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.RightLineWidth, columnIndex)?.Value ?? 1; bool hasLeftLine = leftLineWidth > 0; - bool hasRightLine = rightLineWidth > 0 && skin.GetConfig(LegacySkinConfiguration.LegacySetting.Version)?.Value >= 2.4m + bool hasRightLine = rightLineWidth > 0 && skin.GetConfig(SkinConfiguration.LegacySetting.Version)?.Value >= 2.4m || isLastColumn; Color4 lineColour = skin.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.ColumnLineColour, columnIndex)?.Value ?? Color4.White; diff --git a/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs index 814a737034..0588ae57d7 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs @@ -63,10 +63,10 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy { this.beatmap = (ManiaBeatmap)beatmap; - isLegacySkin = new Lazy(() => GetConfig(LegacySkinConfiguration.LegacySetting.Version) != null); + isLegacySkin = new Lazy(() => GetConfig(SkinConfiguration.LegacySetting.Version) != null); hasKeyTexture = new Lazy(() => { - var keyImage = this.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.KeyImage, 0)?.Value ?? "mania-key1"; + string keyImage = this.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.KeyImage, 0)?.Value ?? "mania-key1"; return this.GetAnimation(keyImage, true, true) != null; }); } diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModHidden.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModHidden.cs index ed9da36b05..71b575abe2 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModHidden.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModHidden.cs @@ -117,6 +117,42 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods PassCondition = checkSomeHit }); + [Test] + public void TestApproachCirclesOnly() => CreateModTest(new ModTestData + { + Mod = new OsuModHidden { OnlyFadeApproachCircles = { Value = true } }, + Autoplay = true, + Beatmap = new Beatmap + { + HitObjects = new List + { + new HitCircle + { + StartTime = 1000, + Position = new Vector2(206, 142) + }, + new HitCircle + { + StartTime = 2000, + Position = new Vector2(306, 142) + }, + new Slider + { + StartTime = 3000, + Position = new Vector2(156, 242), + Path = new SliderPath(PathType.Linear, new[] { Vector2.Zero, new Vector2(200, 0), }) + }, + new Spinner + { + Position = new Vector2(256, 192), + StartTime = 7000, + EndTime = 9000 + } + } + }, + PassCondition = checkSomeHit + }); + private bool checkSomeHit() => Player.ScoreProcessor.JudgedHits >= 4; private bool objectWithIncreasedVisibilityHasIndex(int index) diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModNoScope.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModNoScope.cs new file mode 100644 index 0000000000..0d0fefe0ff --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModNoScope.cs @@ -0,0 +1,150 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using osu.Framework.Utils; +using osu.Framework.Testing; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.Timing; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Osu.Mods; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Objects.Drawables; +using osuTK; + +namespace osu.Game.Rulesets.Osu.Tests.Mods +{ + public class TestSceneOsuModNoScope : OsuModTestScene + { + [Test] + public void TestVisibleDuringBreak() + { + CreateModTest(new ModTestData + { + Mod = new OsuModNoScope + { + HiddenComboCount = { Value = 0 }, + }, + Autoplay = true, + PassCondition = () => true, + Beatmap = new Beatmap + { + HitObjects = new List + { + new HitCircle + { + Position = new Vector2(300, 192), + StartTime = 1000, + }, + new HitCircle + { + Position = new Vector2(300, 192), + StartTime = 5000, + } + }, + Breaks = new List + { + new BreakPeriod(2000, 4000), + } + } + }); + + AddUntilStep("wait for cursor to hide", () => cursorAlphaAlmostEquals(0)); + AddUntilStep("wait for start of break", isBreak); + AddUntilStep("wait for cursor to show", () => cursorAlphaAlmostEquals(1)); + AddUntilStep("wait for end of break", () => !isBreak()); + AddUntilStep("wait for cursor to hide", () => cursorAlphaAlmostEquals(0)); + } + + [Test] + public void TestVisibleDuringSpinner() + { + CreateModTest(new ModTestData + { + Mod = new OsuModNoScope + { + HiddenComboCount = { Value = 0 }, + }, + Autoplay = true, + PassCondition = () => true, + Beatmap = new Beatmap + { + HitObjects = new List + { + new HitCircle + { + Position = new Vector2(300, 192), + StartTime = 1000, + }, + new Spinner + { + Position = new Vector2(256, 192), + StartTime = 2000, + Duration = 2000, + }, + new HitCircle + { + Position = new Vector2(300, 192), + StartTime = 5000, + } + } + } + }); + + AddUntilStep("wait for cursor to hide", () => cursorAlphaAlmostEquals(0)); + AddUntilStep("wait for start of spinner", isSpinning); + AddUntilStep("wait for cursor to show", () => cursorAlphaAlmostEquals(1)); + AddUntilStep("wait for end of spinner", () => !isSpinning()); + AddUntilStep("wait for cursor to hide", () => cursorAlphaAlmostEquals(0)); + } + + [Test] + public void TestVisibleAfterComboBreak() + { + CreateModTest(new ModTestData + { + Mod = new OsuModNoScope + { + HiddenComboCount = { Value = 2 }, + }, + Autoplay = true, + PassCondition = () => true, + Beatmap = new Beatmap + { + HitObjects = new List + { + new HitCircle + { + Position = new Vector2(100, 192), + StartTime = 1000, + }, + new HitCircle + { + Position = new Vector2(150, 192), + StartTime = 3000, + }, + new HitCircle + { + Position = new Vector2(200, 192), + StartTime = 5000, + }, + } + } + }); + + AddAssert("cursor must start visible", () => cursorAlphaAlmostEquals(1)); + AddUntilStep("wait for combo", () => Player.ScoreProcessor.Combo.Value >= 2); + AddAssert("cursor must dim after combo", () => !cursorAlphaAlmostEquals(1)); + AddStep("break combo", () => Player.ScoreProcessor.Combo.Set(0)); + AddUntilStep("wait for cursor to show", () => cursorAlphaAlmostEquals(1)); + } + + private bool isSpinning() => Player.ChildrenOfType().SingleOrDefault()?.Progress > 0; + + private bool isBreak() => Player.IsBreakTime.Value; + + private bool cursorAlphaAlmostEquals(float alpha) => Precision.AlmostEquals(Player.DrawableRuleset.Cursor.Alpha, alpha); + } +} diff --git a/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs b/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs index 15675e74d1..7cd06c5225 100644 --- a/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs +++ b/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs @@ -15,13 +15,13 @@ namespace osu.Game.Rulesets.Osu.Tests { protected override string ResourceAssembly => "osu.Game.Rulesets.Osu"; - [TestCase(6.5867229481955389d, "diffcalc-test")] - [TestCase(1.0416315570967911d, "zero-length-sliders")] + [TestCase(6.5295339534769958d, "diffcalc-test")] + [TestCase(1.1514260533755143d, "zero-length-sliders")] public void Test(double expected, string name) => base.Test(expected, name); - [TestCase(8.2730989071947896d, "diffcalc-test")] - [TestCase(1.2726413186221039d, "zero-length-sliders")] + [TestCase(9.047752485219954d, "diffcalc-test")] + [TestCase(1.3985711787077566d, "zero-length-sliders")] public void TestClockRateAdjusted(double expected, string name) => Test(expected, name, new OsuModDoubleTime()); diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleArea.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleArea.cs index 575523b168..07bbd6379a 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleArea.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleArea.cs @@ -97,7 +97,7 @@ namespace osu.Game.Rulesets.Osu.Tests private void scheduleHit() => AddStep("schedule action", () => { - var delay = hitCircle.StartTime - hitCircle.HitWindows.WindowFor(HitResult.Great) - Time.Current; + double delay = hitCircle.StartTime - hitCircle.HitWindows.WindowFor(HitResult.Great) - Time.Current; Scheduler.AddDelayed(() => hitAreaReceptor.OnPressed(new KeyBindingPressEvent(GetContainingInputManager().CurrentState, OsuAction.LeftButton)), delay); }); } diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneNoSpinnerStacking.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneNoSpinnerStacking.cs new file mode 100644 index 0000000000..ef05bcd320 --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneNoSpinnerStacking.cs @@ -0,0 +1,34 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Osu.Objects; +using osuTK; + +namespace osu.Game.Rulesets.Osu.Tests +{ + [TestFixture] + public class TestSceneNoSpinnerStacking : TestSceneOsuPlayer + { + protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) + { + var beatmap = new Beatmap + { + BeatmapInfo = new BeatmapInfo + { + BaseDifficulty = new BeatmapDifficulty { OverallDifficulty = 10 }, + Ruleset = ruleset + } + }; + + for (int i = 0; i < 512; i++) + { + if (i % 32 < 20) + beatmap.HitObjects.Add(new Spinner { Position = new Vector2(256, 192), StartTime = i * 200, EndTime = (i * 200) + 100 }); + } + + return beatmap; + } + } +} diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs index 52ab39cfbd..de795241bf 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs @@ -148,7 +148,7 @@ namespace osu.Game.Rulesets.Osu.Tests AddAssert("player score matching expected bonus score", () => { // multipled by 2 to nullify the score multiplier. (autoplay mod selected) - var totalScore = ((ScoreExposedPlayer)Player).ScoreProcessor.TotalScore.Value * 2; + double totalScore = ((ScoreExposedPlayer)Player).ScoreProcessor.TotalScore.Value * 2; return totalScore == (int)(drawableSpinner.Result.RateAdjustedRotation / 360) * new SpinnerTick().CreateJudgement().MaxNumericResult; }); diff --git a/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj b/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj index f5f1159542..66f4ad3d3f 100644 --- a/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj +++ b/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj @@ -2,7 +2,7 @@ - + diff --git a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs index 8722b85839..7db14e3e77 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs @@ -39,12 +39,12 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing public double? Angle { get; private set; } /// - /// Milliseconds elapsed since the end time of the Previous , with a minimum of 25ms. + /// Milliseconds elapsed since the end time of the previous , with a minimum of 25ms. /// public double MovementTime { get; private set; } /// - /// Milliseconds elapsed since from the start time of the Previous to the end time of the same Previous , with a minimum of 25ms. + /// Milliseconds elapsed since the start time of the previous to the end time of the same previous , with a minimum of 25ms. /// public double TravelTime { get; private set; } @@ -62,7 +62,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing this.lastLastObject = (OsuHitObject)lastLastObject; this.lastObject = (OsuHitObject)lastObject; - // Capped to 25ms to prevent difficulty calculation breaking from simulatenous objects. + // Capped to 25ms to prevent difficulty calculation breaking from simultaneous objects. StrainTime = Math.Max(DeltaTime, min_delta_time); setDistances(clockRate); @@ -83,53 +83,87 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing scalingFactor *= 1 + smallCircleBonus; } + Vector2 lastCursorPosition = getEndCursorPosition(lastObject); + JumpDistance = (BaseObject.StackedPosition * scalingFactor - lastCursorPosition * scalingFactor).Length; + if (lastObject is Slider lastSlider) { computeSliderCursorPosition(lastSlider); TravelDistance = 0; TravelTime = Math.Max(lastSlider.LazyTravelTime / clockRate, min_delta_time); - MovementTime = Math.Max(StrainTime - lastSlider.LazyTravelTime / clockRate, min_delta_time); + MovementTime = Math.Max(StrainTime - TravelTime, min_delta_time); MovementDistance = Vector2.Subtract(lastSlider.TailCircle.StackedPosition, BaseObject.StackedPosition).Length * scalingFactor; int repeatCount = 0; + Vector2 currSliderPosition = ((OsuHitObject)lastSlider.NestedHitObjects[0]).StackedPosition; + for (int i = 1; i < lastSlider.NestedHitObjects.Count; i++) { - Vector2 currSlider = Vector2.Subtract(((OsuHitObject)lastSlider.NestedHitObjects[i]).StackedPosition, ((OsuHitObject)lastSlider.NestedHitObjects[i - 1]).StackedPosition); + Vector2 currSlider = Vector2.Subtract(((OsuHitObject)lastSlider.NestedHitObjects[i]).StackedPosition, currSliderPosition); + double currSliderLength = currSlider.Length * scalingFactor; - if ((OsuHitObject)lastSlider.NestedHitObjects[i] is SliderRepeat && (OsuHitObject)lastSlider.NestedHitObjects[i - 1] is SliderRepeat) + if ((OsuHitObject)lastSlider.NestedHitObjects[i] is SliderEndCircle && !((OsuHitObject)lastSlider.NestedHitObjects[i] is SliderRepeat)) { - repeatCount++; - TravelDistance += Math.Max(0, currSlider.Length * scalingFactor - 240); // remove 240 distance to avoid buffing overlapping followcircles. + Vector2 possSlider = Vector2.Subtract((Vector2)lastSlider.LazyEndPosition, currSliderPosition); + if (possSlider.Length < currSlider.Length) + currSlider = possSlider; // Take the least distance from slider end vs lazy end. + + currSliderLength = currSlider.Length * scalingFactor; + } + + if ((OsuHitObject)lastSlider.NestedHitObjects[i] is SliderTick) + { + if (currSliderLength > 120) + { + currSliderPosition = Vector2.Add(currSliderPosition, Vector2.Multiply(currSlider, (float)((currSliderLength - 120) / currSliderLength))); + currSliderLength *= (currSliderLength - 120) / currSliderLength; + } + else + currSliderLength = 0; } else if ((OsuHitObject)lastSlider.NestedHitObjects[i] is SliderRepeat) { - repeatCount++; - TravelDistance += Math.Max(0, currSlider.Length * scalingFactor - 120); // remove 120 distance to avoid buffing overlapping followcircles. - } - else if ((OsuHitObject)lastSlider.NestedHitObjects[i] is SliderEndCircle) - { - Vector2 possSlider = Vector2.Subtract((Vector2)lastSlider.LazyEndPosition, ((OsuHitObject)lastSlider.NestedHitObjects[i - 1]).StackedPosition); - TravelDistance += Math.Min(possSlider.Length, currSlider.Length) * scalingFactor; // Take the least distance from slider end vs lazy end. + if (currSliderLength > 50) + { + currSliderPosition = Vector2.Add(currSliderPosition, Vector2.Multiply(currSlider, (float)((currSliderLength - 50) / currSliderLength))); + currSliderLength *= (currSliderLength - 50) / currSliderLength; + } + else + currSliderLength = 0; } else - TravelDistance += Math.Max(0, currSlider.Length * scalingFactor - 100); // remove 100 distance to avoid buffing overlapping ticks, mostly needed to prevent buffing slow sliders with high tick rate. + { + if (currSliderLength > 0) + { + currSliderPosition = Vector2.Add(currSliderPosition, Vector2.Multiply(currSlider, (float)((currSliderLength - 0) / currSliderLength))); + currSliderLength *= (currSliderLength - 0) / currSliderLength; + } + else + currSliderLength = 0; + } + + if ((OsuHitObject)lastSlider.NestedHitObjects[i] is SliderRepeat) + repeatCount++; + + TravelDistance += currSliderLength; } - if (repeatCount == 0) - { - TravelDistance = Math.Max(TravelDistance, scalingFactor * // idea here is to prevent ticks from dropping difficulty of slider by removing distance in calculation. - Math.Min(Vector2.Subtract(((OsuHitObject)lastSlider.NestedHitObjects[lastSlider.NestedHitObjects.Count - 1]).StackedPosition, ((OsuHitObject)lastSlider.NestedHitObjects[0]).StackedPosition).Length, - Vector2.Subtract((Vector2)lastSlider.LazyEndPosition, ((OsuHitObject)lastSlider.NestedHitObjects[0]).StackedPosition).Length)); - } - else - TravelDistance *= Math.Pow(1 + repeatCount / 2.0, 1.0 / 2.0); // Bonus for repeat sliders until a better per nested object strain system can be achieved. + TravelDistance *= Math.Pow(1 + repeatCount / 2.5, 1.0 / 2.5); // Bonus for repeat sliders until a better per nested object strain system can be achieved. + + // Jump distance from the slider tail to the next object, as opposed to the lazy position of JumpDistance. + float tailJumpDistance = Vector2.Subtract(lastSlider.TailCircle.StackedPosition, BaseObject.StackedPosition).Length * scalingFactor; + + // For hitobjects which continue in the direction of the slider, the player will normally follow through the slider, + // such that they're not jumping from the lazy position but rather from very close to (or the end of) the slider. + // In such cases, a leniency is applied by also considering the jump distance from the tail of the slider, and taking the minimum jump distance. + MovementDistance = Math.Min(JumpDistance, tailJumpDistance); + } + else + { + MovementTime = StrainTime; + MovementDistance = JumpDistance; } - - Vector2 lastCursorPosition = getEndCursorPosition(lastObject); - - JumpDistance = (BaseObject.StackedPosition * scalingFactor - lastCursorPosition * scalingFactor).Length; - MovementDistance = Math.Max(0, Math.Min(JumpDistance - 50, MovementDistance - 120)); // radius for jumpdistance is within 50 of maximum possible sliderLeniency, 120 for movement distance. if (lastLastObject != null && !(lastLastObject is Spinner)) { @@ -179,7 +213,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing // Skip the head circle var scoringTimes = slider.NestedHitObjects.Skip(1).Select(t => t.StartTime); - foreach (var time in scoringTimes) + foreach (double time in scoringTimes) computeVertex(time); } diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs index 560863608d..8f6a92a3a9 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs @@ -6,7 +6,6 @@ using osu.Game.Rulesets.Difficulty.Preprocessing; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Difficulty.Preprocessing; using osu.Game.Rulesets.Osu.Objects; -using osu.Framework.Utils; namespace osu.Game.Rulesets.Osu.Difficulty.Skills { @@ -38,25 +37,28 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills return 0; var osuCurrObj = (OsuDifficultyHitObject)current; - var osuPrevObj = (OsuDifficultyHitObject)Previous[0]; - var osuLastObj = (OsuDifficultyHitObject)Previous[1]; + var osuLastObj = (OsuDifficultyHitObject)Previous[0]; + var osuLastLastObj = (OsuDifficultyHitObject)Previous[1]; - double currVelocity = (osuCurrObj.JumpDistance + osuCurrObj.TravelDistance) / osuCurrObj.StrainTime; // Start with the base distance / time + // Calculate the velocity to the current hitobject, which starts with a base distance / time assuming the last object is a hitcircle. + double currVelocity = osuCurrObj.JumpDistance / osuCurrObj.StrainTime; - if (osuPrevObj.BaseObject is Slider) // If object is a slider + // But if the last object is a slider, then we extend the travel velocity through the slider into the current object. + if (osuLastObj.BaseObject is Slider) { - double movementVelocity = osuCurrObj.MovementDistance / osuCurrObj.MovementTime; // calculate the movement velocity from slider end to next note - double travelVelocity = osuCurrObj.TravelDistance / osuCurrObj.TravelTime; // calculate the slider velocity from slider head to lazy end. + double movementVelocity = osuCurrObj.MovementDistance / osuCurrObj.MovementTime; // calculate the movement velocity from slider end to current object + double travelVelocity = osuCurrObj.TravelDistance / osuCurrObj.TravelTime; // calculate the slider velocity from slider head to slider end. currVelocity = Math.Max(currVelocity, movementVelocity + travelVelocity); // take the larger total combined velocity. } - double prevVelocity = (osuPrevObj.JumpDistance + osuPrevObj.TravelDistance) / osuPrevObj.StrainTime; // do the same for the previous velocity. + // As above, do the same for the previous hitobject. + double prevVelocity = osuLastObj.JumpDistance / osuLastObj.StrainTime; - if (osuLastObj.BaseObject is Slider) + if (osuLastLastObj.BaseObject is Slider) { - double movementVelocity = osuPrevObj.MovementDistance / osuPrevObj.MovementTime; - double travelVelocity = osuPrevObj.TravelDistance / osuPrevObj.TravelTime; + double movementVelocity = osuLastObj.MovementDistance / osuLastObj.MovementTime; + double travelVelocity = osuLastObj.TravelDistance / osuLastObj.TravelTime; prevVelocity = Math.Max(prevVelocity, movementVelocity + travelVelocity); } @@ -67,13 +69,13 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills double aimStrain = currVelocity; // Start strain with regular velocity. - if (Precision.AlmostEquals(osuCurrObj.StrainTime, osuPrevObj.StrainTime, 10)) // If rhythms are the same. + if (Math.Max(osuCurrObj.StrainTime, osuLastObj.StrainTime) < 1.25 * Math.Min(osuCurrObj.StrainTime, osuLastObj.StrainTime)) // If rhythms are the same. { - if (osuCurrObj.Angle != null && osuPrevObj.Angle != null && osuLastObj.Angle != null) + if (osuCurrObj.Angle != null && osuLastObj.Angle != null && osuLastLastObj.Angle != null) { double currAngle = osuCurrObj.Angle.Value; - double prevAngle = osuPrevObj.Angle.Value; double lastAngle = osuLastObj.Angle.Value; + double lastLastAngle = osuLastLastObj.Angle.Value; // Rewarding angles, take the smaller velocity as base. angleBonus = Math.Min(currVelocity, prevVelocity); @@ -84,15 +86,17 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills if (osuCurrObj.StrainTime > 100) // Only buff deltaTime exceeding 300 bpm 1/2. acuteAngleBonus = 0; else - acuteAngleBonus *= calcAcuteAngleBonus(prevAngle) // Multiply by previous angle, we don't want to buff unless this is a wiggle type pattern. - * Math.Min(angleBonus, 125 / osuCurrObj.StrainTime) // The maximum velocity we buff is equal to 125 / strainTime - * Math.Pow(Math.Sin(Math.PI / 2 * Math.Min(1, (100 - osuCurrObj.StrainTime) / 25)), 2) // scale buff from 150 bpm 1/4 to 200 bpm 1/4 - * Math.Pow(Math.Sin(Math.PI / 2 * (Math.Min(100, osuCurrObj.JumpDistance) - 50) / 50), 2); // Buff distance exceeding 50 (radius) up to 100 (diameter). + { + acuteAngleBonus *= calcAcuteAngleBonus(lastAngle) // Multiply by previous angle, we don't want to buff unless this is a wiggle type pattern. + * Math.Min(angleBonus, 125 / osuCurrObj.StrainTime) // The maximum velocity we buff is equal to 125 / strainTime + * Math.Pow(Math.Sin(Math.PI / 2 * Math.Min(1, (100 - osuCurrObj.StrainTime) / 25)), 2) // scale buff from 150 bpm 1/4 to 200 bpm 1/4 + * Math.Pow(Math.Sin(Math.PI / 2 * (Math.Clamp(osuCurrObj.JumpDistance, 50, 100) - 50) / 50), 2); // Buff distance exceeding 50 (radius) up to 100 (diameter). + } - wideAngleBonus *= angleBonus * (1 - Math.Min(wideAngleBonus, Math.Pow(calcWideAngleBonus(prevAngle), 3))); // Penalize wide angles if they're repeated, reducing the penalty as the prevAngle gets more acute. - acuteAngleBonus *= 0.5 + 0.5 * (1 - Math.Min(acuteAngleBonus, Math.Pow(calcAcuteAngleBonus(lastAngle), 3))); // Penalize acute angles if they're repeated, reducing the penalty as the lastAngle gets more obtuse. + wideAngleBonus *= angleBonus * (1 - Math.Min(wideAngleBonus, Math.Pow(calcWideAngleBonus(lastAngle), 3))); // Penalize wide angles if they're repeated, reducing the penalty as the lastAngle gets more acute. + acuteAngleBonus *= 0.5 + 0.5 * (1 - Math.Min(acuteAngleBonus, Math.Pow(calcAcuteAngleBonus(lastLastAngle), 3))); // Penalize acute angles if they're repeated, reducing the penalty as the lastLastAngle gets more obtuse. - angleBonus = acuteAngleBonus * acute_angle_multiplier + wideAngleBonus * wide_angle_multiplier; // add the anglebuffs together. + angleBonus = acuteAngleBonus * acute_angle_multiplier + wideAngleBonus * wide_angle_multiplier; // add the angle buffs together. } } 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 2cc95e1891..b3e0566a98 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs @@ -184,7 +184,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components { Vector2[] oldControlPoints = slider.Path.ControlPoints.Select(cp => cp.Position).ToArray(); var oldPosition = slider.Position; - var oldStartTime = slider.StartTime; + double oldStartTime = slider.StartTime; if (ControlPoint == slider.Path.ControlPoints[0]) { @@ -205,7 +205,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components if (!slider.Path.HasValidLength) { - for (var i = 0; i < slider.Path.ControlPoints.Count; i++) + for (int i = 0; i < slider.Path.ControlPoints.Count; i++) slider.Path.ControlPoints[i].Position = oldControlPoints[i]; slider.Position = oldPosition; diff --git a/osu.Game.Rulesets.Osu/Edit/Checks/CheckLowDiffOverlaps.cs b/osu.Game.Rulesets.Osu/Edit/Checks/CheckLowDiffOverlaps.cs index 1dd859b5b8..084a3e5ea1 100644 --- a/osu.Game.Rulesets.Osu/Edit/Checks/CheckLowDiffOverlaps.cs +++ b/osu.Game.Rulesets.Osu/Edit/Checks/CheckLowDiffOverlaps.cs @@ -47,14 +47,14 @@ namespace osu.Game.Rulesets.Osu.Edit.Checks if (!(hitObjects[i + 1] is OsuHitObject nextHitObject) || nextHitObject is Spinner) continue; - var deltaTime = nextHitObject.StartTime - hitObject.GetEndTime(); + double deltaTime = nextHitObject.StartTime - hitObject.GetEndTime(); if (deltaTime >= hitObject.TimeFadeIn + hitObject.TimePreempt) // The objects are not visible at the same time (without mods), hence skipping. continue; - var distanceSq = (hitObject.StackedEndPosition - nextHitObject.StackedPosition).LengthSquared; - var diameter = (hitObject.Radius - overlap_leniency) * 2; - var diameterSq = diameter * diameter; + float distanceSq = (hitObject.StackedEndPosition - nextHitObject.StackedPosition).LengthSquared; + double diameter = (hitObject.Radius - overlap_leniency) * 2; + double diameterSq = diameter * diameter; bool areOverlapping = distanceSq < diameterSq; diff --git a/osu.Game.Rulesets.Osu/Edit/Checks/CheckTimeDistanceEquality.cs b/osu.Game.Rulesets.Osu/Edit/Checks/CheckTimeDistanceEquality.cs index 6420d9558e..585bd35bd9 100644 --- a/osu.Game.Rulesets.Osu/Edit/Checks/CheckTimeDistanceEquality.cs +++ b/osu.Game.Rulesets.Osu/Edit/Checks/CheckTimeDistanceEquality.cs @@ -88,21 +88,21 @@ namespace osu.Game.Rulesets.Osu.Edit.Checks if (!(hitObjects[i + 1] is OsuHitObject nextHitObject) || nextHitObject is Spinner) continue; - var deltaTime = nextHitObject.StartTime - hitObject.GetEndTime(); + double deltaTime = nextHitObject.StartTime - hitObject.GetEndTime(); // Ignore objects that are far enough apart in time to not be considered the same pattern. if (deltaTime > pattern_lifetime) continue; // Relying on FastInvSqrt is probably good enough here. We'll be taking the difference between distances later, hence square not being sufficient. - var distance = (hitObject.StackedEndPosition - nextHitObject.StackedPosition).LengthFast; + float distance = (hitObject.StackedEndPosition - nextHitObject.StackedPosition).LengthFast; // Ignore stacks and half-stacks, as these are close enough to where they can't be confused for being time-distanced. if (distance < stack_leniency) continue; var observedTimeDistance = new ObservedTimeDistance(nextHitObject.StartTime, deltaTime, distance); - var expectedDistance = getExpectedDistance(prevObservedTimeDistances, observedTimeDistance); + double expectedDistance = getExpectedDistance(prevObservedTimeDistances, observedTimeDistance); if (expectedDistance == 0) { @@ -125,7 +125,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Checks private double getExpectedDistance(IEnumerable prevObservedTimeDistances, ObservedTimeDistance observedTimeDistance) { - var observations = prevObservedTimeDistances.Count(); + int observations = prevObservedTimeDistances.Count(); int count = 0; double sum = 0; diff --git a/osu.Game.Rulesets.Osu/Edit/OsuRectangularPositionSnapGrid.cs b/osu.Game.Rulesets.Osu/Edit/OsuRectangularPositionSnapGrid.cs index b8ff92bd37..d1c81b51bc 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuRectangularPositionSnapGrid.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuRectangularPositionSnapGrid.cs @@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Osu.Edit [BackgroundDependencyLoader] private void load() { - var gridSizeIndex = Array.IndexOf(grid_sizes, editorBeatmap.BeatmapInfo.GridSize); + int gridSizeIndex = Array.IndexOf(grid_sizes, editorBeatmap.BeatmapInfo.GridSize); if (gridSizeIndex >= 0) currentGridSizeIndex = gridSizeIndex; updateSpacing(); diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs b/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs index 3102db270e..ad4c5dfd5d 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs @@ -170,7 +170,7 @@ namespace osu.Game.Rulesets.Osu.Mods base.LoadComplete(); var firstObj = beatmap.HitObjects[0]; - var startDelay = firstObj.StartTime - firstObj.TimePreempt; + double startDelay = firstObj.StartTime - firstObj.TimePreempt; using (BeginAbsoluteSequence(startDelay + break_close_late)) leaveBreak(); diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs index 9c7784a00a..d602fe67ee 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs @@ -5,6 +5,8 @@ using System; using System.Diagnostics; using System.Linq; using osu.Framework.Graphics; +using osu.Framework.Bindables; +using osu.Game.Configuration; using osu.Game.Beatmaps; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; @@ -17,6 +19,9 @@ namespace osu.Game.Rulesets.Osu.Mods { public class OsuModHidden : ModHidden, IHidesApproachCircles { + [SettingSource("Only fade approach circles", "The main object body will not fade when enabled.")] + public Bindable OnlyFadeApproachCircles { get; } = new BindableBool(); + public override string Description => @"Play with no approach circles and fading circles/sliders."; public override double ScoreMultiplier => 1.06; @@ -44,15 +49,15 @@ namespace osu.Game.Rulesets.Osu.Mods protected override void ApplyIncreasedVisibilityState(DrawableHitObject hitObject, ArmedState state) { - applyState(hitObject, true); + applyHiddenState(hitObject, true); } protected override void ApplyNormalVisibilityState(DrawableHitObject hitObject, ArmedState state) { - applyState(hitObject, false); + applyHiddenState(hitObject, false); } - private void applyState(DrawableHitObject drawableObject, bool increaseVisibility) + private void applyHiddenState(DrawableHitObject drawableObject, bool increaseVisibility) { if (!(drawableObject is DrawableOsuHitObject drawableOsuObject)) return; @@ -61,6 +66,24 @@ namespace osu.Game.Rulesets.Osu.Mods (double fadeStartTime, double fadeDuration) = getFadeOutParameters(drawableOsuObject); + // process approach circle hiding first (to allow for early return below). + if (!increaseVisibility) + { + if (drawableObject is DrawableHitCircle circle) + { + using (circle.BeginAbsoluteSequence(hitObject.StartTime - hitObject.TimePreempt)) + circle.ApproachCircle.Hide(); + } + else if (drawableObject is DrawableSpinner spinner) + { + spinner.Body.OnSkinChanged += () => hideSpinnerApproachCircle(spinner); + hideSpinnerApproachCircle(spinner); + } + } + + if (OnlyFadeApproachCircles.Value) + return; + switch (drawableObject) { case DrawableSliderTail _: @@ -84,12 +107,6 @@ namespace osu.Game.Rulesets.Osu.Mods // only fade the circle piece (not the approach circle) for the increased visibility object. fadeTarget = circle.CirclePiece; } - else - { - // we don't want to see the approach circle - using (circle.BeginAbsoluteSequence(hitObject.StartTime - hitObject.TimePreempt)) - circle.ApproachCircle.Hide(); - } using (drawableObject.BeginAbsoluteSequence(fadeStartTime)) fadeTarget.FadeOut(fadeDuration); @@ -111,9 +128,6 @@ namespace osu.Game.Rulesets.Osu.Mods // hide elements we don't care about. // todo: hide background - spinner.Body.OnSkinChanged += () => hideSpinnerApproachCircle(spinner); - hideSpinnerApproachCircle(spinner); - using (spinner.BeginAbsoluteSequence(fadeStartTime)) spinner.FadeOut(fadeDuration); @@ -141,11 +155,11 @@ namespace osu.Game.Rulesets.Osu.Mods static (double fadeStartTime, double fadeDuration) getParameters(OsuHitObject hitObject) { - var fadeOutStartTime = hitObject.StartTime - hitObject.TimePreempt + hitObject.TimeFadeIn; - var fadeOutDuration = hitObject.TimePreempt * fade_out_duration_multiplier; + double fadeOutStartTime = hitObject.StartTime - hitObject.TimePreempt + hitObject.TimeFadeIn; + double fadeOutDuration = hitObject.TimePreempt * fade_out_duration_multiplier; // new duration from completed fade in to end (before fading out) - var longFadeDuration = hitObject.GetEndTime() - fadeOutStartTime; + double longFadeDuration = hitObject.GetEndTime() - fadeOutStartTime; switch (hitObject) { @@ -153,7 +167,7 @@ namespace osu.Game.Rulesets.Osu.Mods return (fadeOutStartTime, longFadeDuration); case SliderTick _: - var tickFadeOutDuration = Math.Min(hitObject.TimePreempt - DrawableSliderTick.ANIM_DURATION, 1000); + double tickFadeOutDuration = Math.Min(hitObject.TimePreempt - DrawableSliderTick.ANIM_DURATION, 1000); return (hitObject.StartTime - tickFadeOutDuration, tickFadeOutDuration); case Spinner _: diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModNoScope.cs b/osu.Game.Rulesets.Osu/Mods/OsuModNoScope.cs index c48cbd9992..501c0a55bd 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModNoScope.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModNoScope.cs @@ -2,22 +2,27 @@ // See the LICENCE file in the repository root for full licence text. using System; -using osu.Game.Rulesets.UI; -using osu.Game.Rulesets.Mods; +using System.Linq; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; -using osu.Framework.Bindables; using osu.Framework.Localisation; using osu.Framework.Utils; -using osu.Game.Graphics.UserInterface; +using osu.Game.Beatmaps; using osu.Game.Configuration; +using osu.Game.Graphics.UserInterface; using osu.Game.Overlays.Settings; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.UI; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; +using osu.Game.Screens.Play; +using osu.Game.Utils; namespace osu.Game.Rulesets.Osu.Mods { - public class OsuModNoScope : Mod, IUpdatableByPlayfield, IApplicableToScoreProcessor + public class OsuModNoScope : Mod, IUpdatableByPlayfield, IApplicableToScoreProcessor, IApplicableToPlayer, IApplicableToBeatmap { /// /// Slightly higher than the cutoff for . @@ -34,8 +39,10 @@ namespace osu.Game.Rulesets.Osu.Mods public override double ScoreMultiplier => 1; private BindableNumber currentCombo; + private IBindable isBreakTime; + private PeriodTracker spinnerPeriods; - private float targetAlpha; + private float comboBasedAlpha; [SettingSource( "Hidden at combo", @@ -52,6 +59,16 @@ namespace osu.Game.Rulesets.Osu.Mods public ScoreRank AdjustRank(ScoreRank rank, double accuracy) => rank; + public void ApplyToPlayer(Player player) + { + isBreakTime = player.IsBreakTime.GetBoundCopy(); + } + + public void ApplyToBeatmap(IBeatmap beatmap) + { + spinnerPeriods = new PeriodTracker(beatmap.HitObjects.OfType().Select(b => new Period(b.StartTime - transition_duration, b.EndTime))); + } + public void ApplyToScoreProcessor(ScoreProcessor scoreProcessor) { if (HiddenComboCount.Value == 0) return; @@ -59,12 +76,14 @@ namespace osu.Game.Rulesets.Osu.Mods currentCombo = scoreProcessor.Combo.GetBoundCopy(); currentCombo.BindValueChanged(combo => { - targetAlpha = Math.Max(min_alpha, 1 - (float)combo.NewValue / HiddenComboCount.Value); + comboBasedAlpha = Math.Max(min_alpha, 1 - (float)combo.NewValue / HiddenComboCount.Value); }, true); } public virtual void Update(Playfield playfield) { + bool shouldAlwaysShowCursor = isBreakTime.Value || spinnerPeriods.IsInAny(playfield.Clock.CurrentTime); + float targetAlpha = shouldAlwaysShowCursor ? 1 : comboBasedAlpha; playfield.Cursor.Alpha = (float)Interpolation.Lerp(playfield.Cursor.Alpha, targetAlpha, Math.Clamp(playfield.Time.Elapsed / transition_duration, 0, 1)); } } diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs index 1a2e5d92b4..9b9ebcad04 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs @@ -124,7 +124,7 @@ namespace osu.Game.Rulesets.Osu.Mods // to allow jumps and prevent too sharp turns during streams. // Allow maximum jump angle when jump distance is more than half of playfield diagonal length - var randomAngleRad = rateOfChangeMultiplier * 2 * Math.PI * Math.Min(1f, distanceToPrev / (playfield_diagonal * 0.5f)); + double randomAngleRad = rateOfChangeMultiplier * 2 * Math.PI * Math.Min(1f, distanceToPrev / (playfield_diagonal * 0.5f)); current.AngleRad = (float)randomAngleRad + previous.AngleRad; if (current.AngleRad < 0) @@ -171,11 +171,11 @@ namespace osu.Game.Rulesets.Osu.Mods // Clamp slider position to the placement area // If the slider is larger than the playfield, force it to stay at the original position - var newX = possibleMovementBounds.Width < 0 + float newX = possibleMovementBounds.Width < 0 ? objectInfo.PositionOriginal.X : Math.Clamp(previousPosition.X, possibleMovementBounds.Left, possibleMovementBounds.Right); - var newY = possibleMovementBounds.Height < 0 + float newY = possibleMovementBounds.Height < 0 ? objectInfo.PositionOriginal.Y : Math.Clamp(previousPosition.Y, possibleMovementBounds.Top, possibleMovementBounds.Bottom); @@ -235,7 +235,7 @@ namespace osu.Game.Rulesets.Osu.Mods } // Take the circle radius into account. - var radius = (float)slider.Radius; + float radius = (float)slider.Radius; minX -= radius; minY -= radius; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs b/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs index c7f4811701..31474e8fbb 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs @@ -44,7 +44,7 @@ namespace osu.Game.Rulesets.Osu.Mods // because the spinner is under the gameplay clock, it is affected by rate adjustments on the track; // for that reason using ElapsedFrameTime directly leads to fewer SPM with Half Time and more SPM with Double Time. // for spinners we want the real (wall clock) elapsed time; to achieve that, unapply the clock rate locally here. - var rateIndependentElapsedTime = spinner.Clock.ElapsedFrameTime / spinner.Clock.Rate; + double rateIndependentElapsedTime = spinner.Clock.ElapsedFrameTime / spinner.Clock.Rate; spinner.RotationTracker.AddRotation(MathUtils.RadiansToDegrees((float)rateIndependentElapsedTime * 0.03f)); } } diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs index bf70a63ab5..5285380097 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs @@ -193,8 +193,8 @@ namespace osu.Game.Rulesets.Osu.Mods private IEnumerable generateBeats(IBeatmap beatmap) { - var startTime = originalHitObjects.First().StartTime; - var endTime = originalHitObjects.Last().GetEndTime(); + double startTime = originalHitObjects.First().StartTime; + double endTime = originalHitObjects.Last().GetEndTime(); var beats = beatmap.ControlPointInfo.TimingPoints // Ignore timing points after endTime @@ -208,9 +208,9 @@ namespace osu.Game.Rulesets.Osu.Mods .ToList(); // Remove beats that are too close to the next one (e.g. due to timing point changes) - for (var i = beats.Count - 2; i >= 0; i--) + for (int i = beats.Count - 2; i >= 0; i--) { - var beat = beats[i]; + double beat = beats[i]; if (!definitelyBigger(beats[i + 1] - beat, beatmap.ControlPointInfo.TimingPointAt(beat).BeatLength / 2)) beats.RemoveAt(i); @@ -250,13 +250,13 @@ namespace osu.Game.Rulesets.Osu.Mods // Other kinds of combo info are also added in the process var combos = hitObjects.GroupBy(x => x.ComboIndex).ToList(); - for (var i = 0; i < combos.Count; i++) + for (int i = 0; i < combos.Count; i++) { var group = combos[i].ToList(); group.First().NewCombo = true; group.Last().LastInCombo = true; - for (var j = 0; j < group.Count; j++) + for (int j = 0; j < group.Count; j++) { var x = group[j]; x.ComboIndex = i; @@ -273,17 +273,17 @@ namespace osu.Game.Rulesets.Osu.Mods const float two_pi = MathF.PI * 2; - var direction = two_pi * nextSingle(); - var maxComboIndex = hitObjects.Last().ComboIndex; + float direction = two_pi * nextSingle(); + int maxComboIndex = hitObjects.Last().ComboIndex; - for (var i = 0; i < hitObjects.Count; i++) + for (int i = 0; i < hitObjects.Count; i++) { var obj = hitObjects[i]; var lastPos = i == 0 ? Vector2.Divide(OsuPlayfield.BASE_SIZE, 2) : hitObjects[i - 1].Position; - var distance = maxComboIndex == 0 + float distance = maxComboIndex == 0 ? (float)obj.Radius : mapRange(obj.ComboIndex, 0, maxComboIndex, (float)obj.Radius, max_base_distance); if (obj.NewCombo) distance *= 1.5f; @@ -292,7 +292,7 @@ namespace osu.Game.Rulesets.Osu.Mods // Attempt to place the circle at a place that does not overlap with previous ones - var tryCount = 0; + int tryCount = 0; // for checking overlap var precedingObjects = hitObjects.SkipLast(hitObjects.Count - i).TakeLast(overlap_check_count).ToList(); @@ -363,7 +363,7 @@ namespace osu.Game.Rulesets.Osu.Mods { var beats = new List(); int i = 0; - var currentTime = timingPoint.Time; + double currentTime = timingPoint.Time; while (!definitelyBigger(currentTime, mapEndTime) && controlPointInfo.TimingPointAt(currentTime) == timingPoint) { @@ -377,7 +377,7 @@ namespace osu.Game.Rulesets.Osu.Mods private OsuHitObject getClosestHitObject(List hitObjects, double time) { - var precedingIndex = hitObjects.FindLastIndex(h => h.StartTime < time); + int precedingIndex = hitObjects.FindLastIndex(h => h.StartTime < time); if (precedingIndex == hitObjects.Count - 1) return hitObjects[precedingIndex]; @@ -457,7 +457,7 @@ namespace osu.Game.Rulesets.Osu.Mods private void clampToPlayfield(OsuHitObject obj) { var position = obj.Position; - var radius = (float)obj.Radius; + float radius = (float)obj.Radius; if (position.Y < radius) position.Y = radius; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs index 001ea6c4ad..e88d09e6d9 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs @@ -67,7 +67,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections Vector2 pointStartPosition = startPosition + (fraction - 0.1f) * distanceVector; Vector2 pointEndPosition = startPosition + fraction * distanceVector; - GetFadeTimes(start, end, (float)d / distance, out var fadeInTime, out var fadeOutTime); + GetFadeTimes(start, end, (float)d / distance, out double fadeInTime, out double fadeOutTime); FollowPoint fp; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointLifetimeEntry.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointLifetimeEntry.cs index 30ff6b8984..ad656d2085 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointLifetimeEntry.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointLifetimeEntry.cs @@ -93,7 +93,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections // The lifetime start will match the fade-in time of the first follow point. float fraction = (int)(FollowPointConnection.SPACING * 1.5) / distanceVector.Length; - FollowPointConnection.GetFadeTimes(Start, End, fraction, out var fadeInTime, out _); + FollowPointConnection.GetFadeTimes(Start, End, fraction, out double fadeInTime, out _); LifetimeStart = fadeInTime; LifetimeEnd = double.MaxValue; // This will be set by the connection. diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs index 21e6619444..011e9fa85d 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs @@ -56,7 +56,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections { var newEntry = new FollowPointLifetimeEntry(hitObject); - var index = lifetimeEntries.AddInPlace(newEntry, Comparer.Create((e1, e2) => + int index = lifetimeEntries.AddInPlace(newEntry, Comparer.Create((e1, e2) => { int comp = e1.Start.StartTime.CompareTo(e2.Start.StartTime); diff --git a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs index 7c45b2bc07..8b7de9e109 100644 --- a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs +++ b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs @@ -59,7 +59,7 @@ namespace osu.Game.Rulesets.Osu.Objects set => StackHeightBindable.Value = value; } - public Vector2 StackOffset => new Vector2(StackHeight * Scale * -6.4f); + public virtual Vector2 StackOffset => new Vector2(StackHeight * Scale * -6.4f); public double Radius => OBJECT_RADIUS * Scale; diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index 9e9c75cf31..5c1c3fd253 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -85,7 +85,7 @@ namespace osu.Game.Rulesets.Osu.Objects /// internal double LazyTravelTime; - public List> NodeSamples { get; set; } = new List>(); + public IList> NodeSamples { get; set; } = new List>(); [JsonIgnore] public IList TailSamples { get; private set; } @@ -157,8 +157,9 @@ namespace osu.Game.Rulesets.Osu.Objects { base.CreateNestedHitObjects(cancellationToken); - foreach (var e in - SliderEventGenerator.Generate(StartTime, SpanDuration, Velocity, TickDistance, Path.Distance, this.SpanCount(), LegacyLastTickOffset, cancellationToken)) + var sliderEvents = SliderEventGenerator.Generate(StartTime, SpanDuration, Velocity, TickDistance, Path.Distance, this.SpanCount(), LegacyLastTickOffset, cancellationToken); + + foreach (var e in sliderEvents) { switch (e.Type) { diff --git a/osu.Game.Rulesets.Osu/Objects/Spinner.cs b/osu.Game.Rulesets.Osu/Objects/Spinner.cs index f85dc0d391..0ad8e4ea68 100644 --- a/osu.Game.Rulesets.Osu/Objects/Spinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Spinner.cs @@ -8,6 +8,7 @@ using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Judgements; using osu.Game.Rulesets.Scoring; +using osuTK; namespace osu.Game.Rulesets.Osu.Objects { @@ -31,6 +32,8 @@ namespace osu.Game.Rulesets.Osu.Objects /// public int MaximumBonusSpins { get; protected set; } = 1; + public override Vector2 StackOffset => Vector2.Zero; + protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, IBeatmapDifficultyInfo difficulty) { base.ApplyDefaultsToSelf(controlPointInfo, difficulty); diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/SnakingSliderBody.cs b/osu.Game.Rulesets.Osu/Skinning/Default/SnakingSliderBody.cs index 7b7a89d5e2..42d3840158 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/SnakingSliderBody.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/SnakingSliderBody.cs @@ -70,8 +70,8 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default Slider slider = drawableSlider.HitObject; - var span = slider.SpanAt(completionProgress); - var spanProgress = slider.ProgressAt(completionProgress); + int span = slider.SpanAt(completionProgress); + double spanProgress = slider.ProgressAt(completionProgress); double start = 0; double end = SnakingIn.Value ? Math.Clamp((Time.Current - (slider.StartTime - slider.TimePreempt)) / (slider.TimePreempt / 3), 0, 1) : 1; @@ -110,8 +110,8 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default snakedPosition = Path.PositionInBoundingBox(Vector2.Zero); snakedPathOffset = Path.PositionInBoundingBox(Path.Vertices[0]); - var lastSnakedStart = SnakedStart ?? 0; - var lastSnakedEnd = SnakedEnd ?? 0; + double lastSnakedStart = SnakedStart ?? 0; + double lastSnakedEnd = SnakedEnd ?? 0; SnakedStart = null; SnakedEnd = null; diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/SpinnerRotationTracker.cs b/osu.Game.Rulesets.Osu/Skinning/Default/SpinnerRotationTracker.cs index 9393a589eb..4313e99b13 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/SpinnerRotationTracker.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/SpinnerRotationTracker.cs @@ -62,9 +62,9 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default protected override void Update() { base.Update(); - var thisAngle = -MathUtils.RadiansToDegrees(MathF.Atan2(mousePosition.X - DrawSize.X / 2, mousePosition.Y - DrawSize.Y / 2)); + float thisAngle = -MathUtils.RadiansToDegrees(MathF.Atan2(mousePosition.X - DrawSize.X / 2, mousePosition.Y - DrawSize.Y / 2)); - var delta = thisAngle - lastAngle; + float delta = thisAngle - lastAngle; if (Tracking) AddRotation(delta); diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyMainCirclePiece.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyMainCirclePiece.cs index d1c9b1bf92..d2f84dcf84 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyMainCirclePiece.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyMainCirclePiece.cs @@ -14,7 +14,6 @@ using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Skinning; using osuTK; using osuTK.Graphics; -using static osu.Game.Skinning.LegacySkinConfiguration; namespace osu.Game.Rulesets.Osu.Skinning.Legacy { @@ -158,7 +157,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy if (hasNumber) { - var legacyVersion = skin.GetConfig(LegacySetting.Version)?.Value; + decimal? legacyVersion = skin.GetConfig(SkinConfiguration.LegacySetting.Version)?.Value; if (legacyVersion >= 2.0m) // legacy skins of version 2.0 and newer only apply very short fade out to the number piece. diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyOldStyleSpinner.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyOldStyleSpinner.cs index e3e8f3ce88..ea122d47bb 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyOldStyleSpinner.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyOldStyleSpinner.cs @@ -103,7 +103,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy // careful: need to call this exactly once for all calculations in a frame // as the function has a random factor in it - var metreHeight = getMetreHeight(DrawableSpinner.Progress); + float metreHeight = getMetreHeight(DrawableSpinner.Progress); // hack to make the metre blink up from below than down from above. // move down the container to be able to apply masking for the metre, diff --git a/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs b/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs index 24a660e69e..3c2077b3c8 100644 --- a/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs +++ b/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs @@ -216,7 +216,7 @@ namespace osu.Game.Rulesets.Osu.Statistics // Likewise sin(pi/2)=1 and sin(3pi/2)=-1, whereas we actually want these values to appear on the bottom/top respectively, so the y-coordinate also needs to be inverted. // // We also need to apply the anti-clockwise rotation. - var rotatedAngle = finalAngle - MathUtils.DegreesToRadians(rotation); + double rotatedAngle = finalAngle - MathUtils.DegreesToRadians(rotation); var rotatedCoordinate = -1 * new Vector2((float)Math.Cos(rotatedAngle), (float)Math.Sin(rotatedAngle)); Vector2 localCentre = new Vector2(points_per_dimension - 1) / 2; diff --git a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs index bfd6ac3ad3..97a4b14a62 100644 --- a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs +++ b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs @@ -40,7 +40,7 @@ namespace osu.Game.Rulesets.Osu.Utils /// The new position of the hit object, relative to the previous one. public static Vector2 RotateAwayFromEdge(Vector2 prevObjectPos, Vector2 posRelativeToPrev, float rotationRatio = 0.5f) { - var relativeRotationDistance = 0f; + float relativeRotationDistance = 0f; if (prevObjectPos.X < playfield_middle.X) { @@ -88,16 +88,16 @@ namespace osu.Game.Rulesets.Osu.Utils /// The rotated vector. public static Vector2 RotateVectorTowardsVector(Vector2 initial, Vector2 destination, float rotationRatio) { - var initialAngleRad = MathF.Atan2(initial.Y, initial.X); - var destAngleRad = MathF.Atan2(destination.Y, destination.X); + float initialAngleRad = MathF.Atan2(initial.Y, initial.X); + float destAngleRad = MathF.Atan2(destination.Y, destination.X); - var diff = destAngleRad - initialAngleRad; + float diff = destAngleRad - initialAngleRad; while (diff < -MathF.PI) diff += 2 * MathF.PI; while (diff > MathF.PI) diff -= 2 * MathF.PI; - var finalAngleRad = initialAngleRad + rotationRatio * diff; + float finalAngleRad = initialAngleRad + rotationRatio * diff; return new Vector2( initial.Length * MathF.Cos(finalAngleRad), diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneSampleOutput.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneSampleOutput.cs index 296468d98d..e5c9358c26 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TestSceneSampleOutput.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneSampleOutput.cs @@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Taiko.Tests { base.SetUpSteps(); - var expectedSampleNames = new[] + string[] expectedSampleNames = { string.Empty, string.Empty, @@ -31,6 +31,7 @@ namespace osu.Game.Rulesets.Taiko.Tests HitSampleInfo.HIT_WHISTLE, HitSampleInfo.HIT_WHISTLE, }; + var actualSampleNames = new List(); // due to pooling we can't access all samples right away due to object re-use, diff --git a/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj b/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj index b9b295767e..568e35c221 100644 --- a/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj +++ b/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj @@ -2,7 +2,7 @@ - + diff --git a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs index 32aad6c36a..94dfb67d93 100644 --- a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs +++ b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs @@ -79,9 +79,9 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps { case IHasDistance distanceData: { - if (shouldConvertSliderToHits(obj, beatmap, distanceData, out var taikoDuration, out var tickSpacing)) + if (shouldConvertSliderToHits(obj, beatmap, distanceData, out int taikoDuration, out double tickSpacing)) { - List> allSamples = obj is IHasPathWithRepeats curveData ? curveData.NodeSamples : new List>(new[] { samples }); + IList> allSamples = obj is IHasPathWithRepeats curveData ? curveData.NodeSamples : new List>(new[] { samples }); int i = 0; diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs index 97adc5f197..863672b3fa 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs @@ -109,7 +109,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables var corrected = samples.ToList(); - for (var i = 0; i < corrected.Count; i++) + for (int i = 0; i < corrected.Count; i++) { var s = corrected[i]; @@ -156,7 +156,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables validActionPressed = HitActions.Contains(e.Action); // Only count this as handled if the new judgement is a hit - var result = UpdateResult(true); + bool result = UpdateResult(true); if (IsHit) HitAction = e.Action; diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs index 2d19296d06..77243218ce 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs @@ -185,9 +185,9 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables nextTick?.TriggerResult(true); - var numHits = ticks.Count(r => r.IsHit); + int numHits = ticks.Count(r => r.IsHit); - var completion = (float)numHits / HitObject.RequiredHits; + float completion = (float)numHits / HitObject.RequiredHits; expandingRing .FadeTo(expandingRing.Alpha + Math.Clamp(completion / 16, 0.1f, 0.6f), 50) @@ -273,7 +273,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables if (Time.Current < HitObject.StartTime) return false; - var isCentre = e.Action == TaikoAction.LeftCentre || e.Action == TaikoAction.RightCentre; + bool isCentre = e.Action == TaikoAction.LeftCentre || e.Action == TaikoAction.RightCentre; // Ensure alternating centre and rim hits if (lastWasCentre == isCentre) diff --git a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs index 86be40dea8..43c5c07f80 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs @@ -69,7 +69,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy // because the right half is flipped, we need to position using width - position to get the true "topleft" origin position float negativeScaleAdjust = content.Width / ratio; - if (skin.GetConfig(LegacySkinConfiguration.LegacySetting.Version)?.Value >= 2.1m) + if (skin.GetConfig(SkinConfiguration.LegacySetting.Version)?.Value >= 2.1m) { left.Centre.Position = new Vector2(0, taiko_bar_y) * ratio; right.Centre.Position = new Vector2(negativeScaleAdjust - 56, taiko_bar_y) * ratio; diff --git a/osu.Game.Rulesets.Taiko/Skinning/Legacy/TaikoLegacySkinTransformer.cs b/osu.Game.Rulesets.Taiko/Skinning/Legacy/TaikoLegacySkinTransformer.cs index a3ecbbc436..bbc8f0abea 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/Legacy/TaikoLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/Legacy/TaikoLegacySkinTransformer.cs @@ -91,7 +91,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy case TaikoSkinComponents.TaikoExplosionOk: case TaikoSkinComponents.TaikoExplosionGreat: - var hitName = getHitName(taikoComponent.Component); + string hitName = getHitName(taikoComponent.Component); var hitSprite = this.GetAnimation(hitName, true, false); if (hitSprite != null) @@ -162,10 +162,10 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy { get { - foreach (var name in base.LookupNames) + foreach (string name in base.LookupNames) yield return name.Insert(name.LastIndexOf('/') + 1, "taiko-"); - foreach (var name in base.LookupNames) + foreach (string name in base.LookupNames) yield return name; } } diff --git a/osu.Game.Rulesets.Taiko/UI/DrumRollHitContainer.cs b/osu.Game.Rulesets.Taiko/UI/DrumRollHitContainer.cs index 263454c78a..ae37840825 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrumRollHitContainer.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrumRollHitContainer.cs @@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Taiko.UI base.Update(); // Remove any auxiliary hit notes that were spawned during a drum roll but subsequently rewound. - for (var i = AliveInternalChildren.Count - 1; i >= 0; i--) + for (int i = AliveInternalChildren.Count - 1; i >= 0; i--) { var flyingHit = (DrawableFlyingHit)AliveInternalChildren[i]; if (Time.Current <= flyingHit.HitObject.StartTime) diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoMascotAnimation.cs b/osu.Game.Rulesets.Taiko/UI/TaikoMascotAnimation.cs index 3706acbe23..c496c05236 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoMascotAnimation.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoMascotAnimation.cs @@ -122,7 +122,7 @@ namespace osu.Game.Rulesets.Taiko.UI if (skin == null) return; - foreach (var frameIndex in clear_animation_sequence) + foreach (int frameIndex in clear_animation_sequence) { var texture = getAnimationFrame(skin, TaikoMascotAnimationState.Clear, frameIndex); diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index cb12d03620..d37e09aa29 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -775,5 +775,22 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.That(seventh.ControlPoints[4].Type == null); } } + + [Test] + public void TestSliderLengthExtensionEdgeCase() + { + var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false }; + + using (var resStream = TestResources.OpenResource("duplicate-last-position-slider.osu")) + using (var stream = new LineBufferedReader(resStream)) + { + var decoded = decoder.Decode(stream); + + var path = ((IHasPath)decoded.HitObjects[0]).Path; + + Assert.That(path.ExpectedDistance.Value, Is.EqualTo(2)); + Assert.That(path.Distance, Is.EqualTo(1)); + } + } } } diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyDecoderTest.cs index 335a6aeeb0..4334c4d7a2 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyDecoderTest.cs @@ -40,7 +40,7 @@ namespace osu.Game.Tests.Beatmaps.Formats protected override bool ShouldSkipLine(string line) { - var result = base.ShouldSkipLine(line); + bool result = base.ShouldSkipLine(line); if (!result) ParsedLines.Add(line); diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs index 4cc71717ff..935194db58 100644 --- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs @@ -82,7 +82,7 @@ namespace osu.Game.Tests.Beatmaps.IO { var osu = LoadOsuIntoHost(host); - var tempPath = TestResources.GetTestBeatmapForImport(); + string tempPath = TestResources.GetTestBeatmapForImport(); var manager = osu.Dependencies.Get(); @@ -144,7 +144,7 @@ namespace osu.Game.Tests.Beatmaps.IO { var osu = LoadOsuIntoHost(host); - var temp = TestResources.GetTestBeatmapForImport(); + string temp = TestResources.GetTestBeatmapForImport(); string extractedFolder = $"{temp}_extracted"; Directory.CreateDirectory(extractedFolder); @@ -196,7 +196,7 @@ namespace osu.Game.Tests.Beatmaps.IO { var osu = LoadOsuIntoHost(host); - var temp = TestResources.GetTestBeatmapForImport(); + string temp = TestResources.GetTestBeatmapForImport(); string extractedFolder = $"{temp}_extracted"; Directory.CreateDirectory(extractedFolder); @@ -251,7 +251,7 @@ namespace osu.Game.Tests.Beatmaps.IO { var osu = LoadOsuIntoHost(host); - var temp = TestResources.GetTestBeatmapForImport(); + string temp = TestResources.GetTestBeatmapForImport(); string extractedFolder = $"{temp}_extracted"; Directory.CreateDirectory(extractedFolder); @@ -302,7 +302,7 @@ namespace osu.Game.Tests.Beatmaps.IO { var osu = LoadOsuIntoHost(host); - var temp = TestResources.GetTestBeatmapForImport(); + string temp = TestResources.GetTestBeatmapForImport(); string extractedFolder = $"{temp}_extracted"; Directory.CreateDirectory(extractedFolder); @@ -424,7 +424,7 @@ namespace osu.Game.Tests.Beatmaps.IO checkBeatmapCount(osu, 12); checkSingleReferencedFileCount(osu, 18); - var brokenTempFilename = TestResources.GetTestBeatmapForImport(); + string brokenTempFilename = TestResources.GetTestBeatmapForImport(); MemoryStream brokenOsu = new MemoryStream(); MemoryStream brokenOsz = new MemoryStream(await File.ReadAllBytesAsync(brokenTempFilename)); @@ -594,7 +594,7 @@ namespace osu.Game.Tests.Beatmaps.IO var osu = LoadOsuIntoHost(host); - var temp = TestResources.GetTestBeatmapForImport(); + string temp = TestResources.GetTestBeatmapForImport(); var importer = new ArchiveImportIPCChannel(client); if (!importer.ImportAsync(temp).Wait(10000)) @@ -619,7 +619,7 @@ namespace osu.Game.Tests.Beatmaps.IO try { var osu = LoadOsuIntoHost(host); - var temp = TestResources.GetTestBeatmapForImport(); + string temp = TestResources.GetTestBeatmapForImport(); using (File.OpenRead(temp)) await osu.Dependencies.Get().Import(temp); ensureLoaded(osu); @@ -642,7 +642,7 @@ namespace osu.Game.Tests.Beatmaps.IO { var osu = LoadOsuIntoHost(host); - var temp = TestResources.GetTestBeatmapForImport(); + string temp = TestResources.GetTestBeatmapForImport(); string extractedFolder = $"{temp}_extracted"; Directory.CreateDirectory(extractedFolder); @@ -684,7 +684,7 @@ namespace osu.Game.Tests.Beatmaps.IO { var osu = LoadOsuIntoHost(host); - var temp = TestResources.GetTestBeatmapForImport(); + string temp = TestResources.GetTestBeatmapForImport(); string extractedFolder = $"{temp}_extracted"; string subfolder = Path.Combine(extractedFolder, "subfolder"); @@ -729,7 +729,7 @@ namespace osu.Game.Tests.Beatmaps.IO { var osu = LoadOsuIntoHost(host); - var temp = TestResources.GetTestBeatmapForImport(); + string temp = TestResources.GetTestBeatmapForImport(); string extractedFolder = $"{temp}_extracted"; string dataFolder = Path.Combine(extractedFolder, "actual_data"); @@ -784,7 +784,7 @@ namespace osu.Game.Tests.Beatmaps.IO var osu = LoadOsuIntoHost(host); var manager = osu.Dependencies.Get(); - var temp = TestResources.GetTestBeatmapForImport(); + string temp = TestResources.GetTestBeatmapForImport(); await osu.Dependencies.Get().Import(temp); // Update via the beatmap, not the beatmap info, to ensure correct linking @@ -814,7 +814,7 @@ namespace osu.Game.Tests.Beatmaps.IO var osu = LoadOsuIntoHost(host); var manager = osu.Dependencies.Get(); - var temp = TestResources.GetTestBeatmapForImport(); + string temp = TestResources.GetTestBeatmapForImport(); await osu.Dependencies.Get().Import(temp); BeatmapSetInfo setToUpdate = manager.GetAllUsableBeatmapSets()[0]; @@ -905,7 +905,7 @@ namespace osu.Game.Tests.Beatmaps.IO public static async Task LoadQuickOszIntoOsu(OsuGameBase osu) { - var temp = TestResources.GetQuickTestBeatmapForImport(); + string temp = TestResources.GetQuickTestBeatmapForImport(); var manager = osu.Dependencies.Get(); @@ -920,7 +920,7 @@ namespace osu.Game.Tests.Beatmaps.IO public static async Task LoadOszIntoOsu(OsuGameBase osu, string path = null, bool virtualTrack = false) { - var temp = path ?? TestResources.GetTestBeatmapForImport(virtualTrack); + string temp = path ?? TestResources.GetTestBeatmapForImport(virtualTrack); var manager = osu.Dependencies.Get(); diff --git a/osu.Game.Tests/Beatmaps/IO/LineBufferedReaderTest.cs b/osu.Game.Tests/Beatmaps/IO/LineBufferedReaderTest.cs index 25517ad615..5e37f01c81 100644 --- a/osu.Game.Tests/Beatmaps/IO/LineBufferedReaderTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/LineBufferedReaderTest.cs @@ -114,7 +114,7 @@ namespace osu.Game.Tests.Beatmaps.IO Assert.AreEqual("this line is gone", bufferedReader.ReadLine()); Assert.AreEqual("this one shouldn't be", bufferedReader.PeekLine()); - var endingLines = bufferedReader.ReadToEnd().Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries); + string[] endingLines = bufferedReader.ReadToEnd().Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries); Assert.AreEqual(3, endingLines.Length); Assert.AreEqual("this one shouldn't be", endingLines[0]); Assert.AreEqual("these ones", endingLines[1]); diff --git a/osu.Game.Tests/Beatmaps/IO/OszArchiveReaderTest.cs b/osu.Game.Tests/Beatmaps/IO/OszArchiveReaderTest.cs index 022b2c1a59..d11da5e2a3 100644 --- a/osu.Game.Tests/Beatmaps/IO/OszArchiveReaderTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/OszArchiveReaderTest.cs @@ -36,8 +36,8 @@ namespace osu.Game.Tests.Beatmaps.IO "Soleily - Renatus (MMzz) [Muzukashii].osu", "Soleily - Renatus (MMzz) [Oni].osu" }; - var maps = reader.Filenames.ToArray(); - foreach (var map in expected) + string[] maps = reader.Filenames.ToArray(); + foreach (string map in expected) Assert.Contains(map, maps); } } @@ -77,8 +77,7 @@ namespace osu.Game.Tests.Beatmaps.IO { var reader = new ZipArchiveReader(osz); - using (var stream = new StreamReader( - reader.GetStream("Soleily - Renatus (Deif) [Platter].osu"))) + using (var stream = new StreamReader(reader.GetStream("Soleily - Renatus (Deif) [Platter].osu"))) { Assert.AreEqual("osu file format v13", stream.ReadLine()?.Trim()); } diff --git a/osu.Game.Tests/Database/BeatmapImporterTests.cs b/osu.Game.Tests/Database/BeatmapImporterTests.cs index 4cdcf507b6..a21f935f6b 100644 --- a/osu.Game.Tests/Database/BeatmapImporterTests.cs +++ b/osu.Game.Tests/Database/BeatmapImporterTests.cs @@ -94,7 +94,7 @@ namespace osu.Game.Tests.Database using var importer = new BeatmapImporter(realmFactory, storage); using var store = new RealmRulesetStore(realmFactory, storage); - var tempPath = TestResources.GetTestBeatmapForImport(); + string? tempPath = TestResources.GetTestBeatmapForImport(); ILive? importedSet; @@ -144,7 +144,7 @@ namespace osu.Game.Tests.Database using var importer = new BeatmapImporter(realmFactory, storage); using var store = new RealmRulesetStore(realmFactory, storage); - var temp = TestResources.GetTestBeatmapForImport(); + string? temp = TestResources.GetTestBeatmapForImport(); string extractedFolder = $"{temp}_extracted"; Directory.CreateDirectory(extractedFolder); @@ -193,7 +193,7 @@ namespace osu.Game.Tests.Database using var importer = new BeatmapImporter(realmFactory, storage); using var store = new RealmRulesetStore(realmFactory, storage); - var temp = TestResources.GetTestBeatmapForImport(); + string? temp = TestResources.GetTestBeatmapForImport(); string extractedFolder = $"{temp}_extracted"; Directory.CreateDirectory(extractedFolder); @@ -245,7 +245,7 @@ namespace osu.Game.Tests.Database using var importer = new BeatmapImporter(realmFactory, storage); using var store = new RealmRulesetStore(realmFactory, storage); - var temp = TestResources.GetTestBeatmapForImport(); + string? temp = TestResources.GetTestBeatmapForImport(); string extractedFolder = $"{temp}_extracted"; Directory.CreateDirectory(extractedFolder); @@ -293,7 +293,7 @@ namespace osu.Game.Tests.Database using var importer = new BeatmapImporter(realmFactory, storage); using var store = new RealmRulesetStore(realmFactory, storage); - var temp = TestResources.GetTestBeatmapForImport(); + string? temp = TestResources.GetTestBeatmapForImport(); string extractedFolder = $"{temp}_extracted"; Directory.CreateDirectory(extractedFolder); @@ -391,7 +391,7 @@ namespace osu.Game.Tests.Database checkBeatmapCount(realmFactory.Context, 12); checkSingleReferencedFileCount(realmFactory.Context, 18); - var brokenTempFilename = TestResources.GetTestBeatmapForImport(); + string? brokenTempFilename = TestResources.GetTestBeatmapForImport(); MemoryStream brokenOsu = new MemoryStream(); MemoryStream brokenOsz = new MemoryStream(await File.ReadAllBytesAsync(brokenTempFilename)); @@ -522,7 +522,7 @@ namespace osu.Game.Tests.Database using var importer = new BeatmapImporter(realmFactory, storage); using var store = new RealmRulesetStore(realmFactory, storage); - var temp = TestResources.GetTestBeatmapForImport(); + string? temp = TestResources.GetTestBeatmapForImport(); using (File.OpenRead(temp)) await importer.Import(temp); ensureLoaded(realmFactory.Context); @@ -539,7 +539,7 @@ namespace osu.Game.Tests.Database using var importer = new BeatmapImporter(realmFactory, storage); using var store = new RealmRulesetStore(realmFactory, storage); - var temp = TestResources.GetTestBeatmapForImport(); + string? temp = TestResources.GetTestBeatmapForImport(); string extractedFolder = $"{temp}_extracted"; Directory.CreateDirectory(extractedFolder); @@ -575,7 +575,7 @@ namespace osu.Game.Tests.Database using var importer = new BeatmapImporter(realmFactory, storage); using var store = new RealmRulesetStore(realmFactory, storage); - var temp = TestResources.GetTestBeatmapForImport(); + string? temp = TestResources.GetTestBeatmapForImport(); string extractedFolder = $"{temp}_extracted"; string subfolder = Path.Combine(extractedFolder, "subfolder"); @@ -617,7 +617,7 @@ namespace osu.Game.Tests.Database using var importer = new BeatmapImporter(realmFactory, storage); using var store = new RealmRulesetStore(realmFactory, storage); - var temp = TestResources.GetTestBeatmapForImport(); + string? temp = TestResources.GetTestBeatmapForImport(); string extractedFolder = $"{temp}_extracted"; string dataFolder = Path.Combine(extractedFolder, "actual_data"); @@ -668,7 +668,7 @@ namespace osu.Game.Tests.Database using var importer = new BeatmapImporter(realmFactory, storage); using var store = new RealmRulesetStore(realmFactory, storage); - var temp = TestResources.GetTestBeatmapForImport(); + string? temp = TestResources.GetTestBeatmapForImport(); await importer.Import(temp); // Update via the beatmap, not the beatmap info, to ensure correct linking @@ -685,7 +685,7 @@ namespace osu.Game.Tests.Database public static async Task LoadQuickOszIntoOsu(BeatmapImporter importer, Realm realm) { - var temp = TestResources.GetQuickTestBeatmapForImport(); + string? temp = TestResources.GetQuickTestBeatmapForImport(); var importedSet = await importer.Import(new ImportTask(temp)); @@ -700,7 +700,7 @@ namespace osu.Game.Tests.Database public static async Task LoadOszIntoStore(BeatmapImporter importer, Realm realm, string? path = null, bool virtualTrack = false) { - var temp = path ?? TestResources.GetTestBeatmapForImport(virtualTrack); + string? temp = path ?? TestResources.GetTestBeatmapForImport(virtualTrack); var importedSet = await importer.Import(new ImportTask(temp)); diff --git a/osu.Game.Tests/Gameplay/TestSceneHitObjectSamples.cs b/osu.Game.Tests/Gameplay/TestSceneHitObjectSamples.cs index fc420e22a1..153d5b8e36 100644 --- a/osu.Game.Tests/Gameplay/TestSceneHitObjectSamples.cs +++ b/osu.Game.Tests/Gameplay/TestSceneHitObjectSamples.cs @@ -7,7 +7,7 @@ using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; using osu.Game.Tests.Beatmaps; using osu.Game.Tests.Resources; -using static osu.Game.Skinning.LegacySkinConfiguration; +using static osu.Game.Skinning.SkinConfiguration; namespace osu.Game.Tests.Gameplay { diff --git a/osu.Game.Tests/NonVisual/BarLineGeneratorTest.cs b/osu.Game.Tests/NonVisual/BarLineGeneratorTest.cs index e663e1128e..834c05fd08 100644 --- a/osu.Game.Tests/NonVisual/BarLineGeneratorTest.cs +++ b/osu.Game.Tests/NonVisual/BarLineGeneratorTest.cs @@ -49,7 +49,7 @@ namespace osu.Game.Tests.NonVisual for (int i = 0; i * beat_length_denominator < barLines.Count; i++) { var barLine = barLines[i * beat_length_denominator]; - var expectedTime = beat_length_numerator * (int)signature * i; + int expectedTime = beat_length_numerator * (int)signature * i; // every seventh bar's start time should be at least greater than the whole number we expect. // It cannot be less, as that can affect overlapping scroll algorithms diff --git a/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs b/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs index 5e14af5c27..8a063b3c6e 100644 --- a/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs +++ b/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs @@ -87,7 +87,7 @@ namespace osu.Game.Tests.NonVisual File.WriteAllText(actualTestFile, "test"); var rulesetStorage = storage.GetStorageForDirectory("rulesets"); - var lookupPath = rulesetStorage.GetFiles(".").Single(); + string lookupPath = rulesetStorage.GetFiles(".").Single(); Assert.That(lookupPath, Is.EqualTo("test")); } @@ -140,7 +140,7 @@ namespace osu.Game.Tests.NonVisual Assert.That(osuStorage, Is.Not.Null); - foreach (var file in osuStorage.IgnoreFiles) + foreach (string file in osuStorage.IgnoreFiles) { // avoid touching realm files which may be a pipe and break everything. // this is also done locally inside OsuStorage via the IgnoreFiles list. @@ -149,7 +149,7 @@ namespace osu.Game.Tests.NonVisual Assert.That(storage.Exists(file), Is.False); } - foreach (var dir in osuStorage.IgnoreDirectories) + foreach (string dir in osuStorage.IgnoreDirectories) { Assert.That(Directory.Exists(Path.Combine(originalDirectory, dir))); Assert.That(storage.ExistsDirectory(dir), Is.False); diff --git a/osu.Game.Tests/NonVisual/FramedReplayInputHandlerTest.cs b/osu.Game.Tests/NonVisual/FramedReplayInputHandlerTest.cs index 407dec936b..99d394b454 100644 --- a/osu.Game.Tests/NonVisual/FramedReplayInputHandlerTest.cs +++ b/osu.Game.Tests/NonVisual/FramedReplayInputHandlerTest.cs @@ -347,7 +347,7 @@ namespace osu.Game.Tests.NonVisual { for (int i = 0; i < 1000; i++) { - var time = handler.SetFrameFromTime(destination); + double? time = handler.SetFrameFromTime(destination); if (time == null || time == destination) return; } diff --git a/osu.Game.Tests/NonVisual/LimitedCapacityQueueTest.cs b/osu.Game.Tests/NonVisual/LimitedCapacityQueueTest.cs index a04415bc7f..e1eaf213d6 100644 --- a/osu.Game.Tests/NonVisual/LimitedCapacityQueueTest.cs +++ b/osu.Game.Tests/NonVisual/LimitedCapacityQueueTest.cs @@ -30,7 +30,7 @@ namespace osu.Game.Tests.NonVisual Assert.Throws(() => _ = queue.Dequeue()); int count = 0; - foreach (var _ in queue) + foreach (int _ in queue) count++; Assert.AreEqual(0, count); @@ -50,7 +50,7 @@ namespace osu.Game.Tests.NonVisual Assert.AreEqual(i, queue[i]); int j = 0; - foreach (var item in queue) + foreach (int item in queue) Assert.AreEqual(j++, item); for (int i = queue.Count; i < queue.Count + capacity; i++) @@ -71,7 +71,7 @@ namespace osu.Game.Tests.NonVisual Assert.AreEqual(count - capacity + i, queue[i]); int j = count - capacity; - foreach (var item in queue) + foreach (int item in queue) Assert.AreEqual(j++, item); for (int i = queue.Count; i < queue.Count + capacity; i++) diff --git a/osu.Game.Tests/NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs b/osu.Game.Tests/NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs index 07ec86b0e7..8e2259bce8 100644 --- a/osu.Game.Tests/NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs +++ b/osu.Game.Tests/NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs @@ -84,7 +84,7 @@ namespace osu.Game.Tests.NonVisual.Multiplayer { for (int i = 0; i < userCount; ++i) { - var userId = Client.Room?.Users[i].UserID ?? throw new AssertionException("Room cannot be null!"); + int userId = Client.Room?.Users[i].UserID ?? throw new AssertionException("Room cannot be null!"); Client.ChangeUserState(userId, state); } }); diff --git a/osu.Game.Tests/NonVisual/ReverseQueueTest.cs b/osu.Game.Tests/NonVisual/ReverseQueueTest.cs index 93cd9403ce..808c8d14f0 100644 --- a/osu.Game.Tests/NonVisual/ReverseQueueTest.cs +++ b/osu.Game.Tests/NonVisual/ReverseQueueTest.cs @@ -29,7 +29,7 @@ namespace osu.Game.Tests.NonVisual }); int count = 0; - foreach (var unused in queue) + foreach (char unused in queue) count++; Assert.AreEqual(0, count); @@ -72,7 +72,7 @@ namespace osu.Game.Tests.NonVisual // Assert correct item return and no longer in queue after dequeueing Assert.AreEqual('a', queue[5]); - var dequeuedItem = queue.Dequeue(); + char dequeuedItem = queue.Dequeue(); Assert.AreEqual('a', dequeuedItem); Assert.AreEqual(5, queue.Count); @@ -133,7 +133,7 @@ namespace osu.Game.Tests.NonVisual int expectedValueIndex = 0; // Assert items are enumerated in correct order - foreach (var item in queue) + foreach (char item in queue) { Assert.AreEqual(expectedValues[expectedValueIndex], item); expectedValueIndex++; diff --git a/osu.Game.Tests/Online/TestAPIModJsonSerialization.cs b/osu.Game.Tests/Online/TestAPIModJsonSerialization.cs index ad2007f202..c8848ab7d8 100644 --- a/osu.Game.Tests/Online/TestAPIModJsonSerialization.cs +++ b/osu.Game.Tests/Online/TestAPIModJsonSerialization.cs @@ -8,6 +8,7 @@ using osu.Framework.Bindables; using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Online.API; +using osu.Game.Online.Solo; using osu.Game.Rulesets; using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Mods; @@ -88,33 +89,27 @@ namespace osu.Game.Tests.Online } [Test] - public void TestDeserialiseScoreInfoWithEmptyMods() + public void TestDeserialiseSubmittableScoreWithEmptyMods() { - var score = new ScoreInfo { Ruleset = new OsuRuleset().RulesetInfo }; + var score = new SubmittableScore(new ScoreInfo { Ruleset = new OsuRuleset().RulesetInfo }); - var deserialised = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(score)); - - if (deserialised != null) - deserialised.Ruleset = new OsuRuleset().RulesetInfo; + var deserialised = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(score)); Assert.That(deserialised?.Mods.Length, Is.Zero); } [Test] - public void TestDeserialiseScoreInfoWithCustomModSetting() + public void TestDeserialiseSubmittableScoreWithCustomModSetting() { - var score = new ScoreInfo + var score = new SubmittableScore(new ScoreInfo { Ruleset = new OsuRuleset().RulesetInfo, Mods = new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 2 } } } - }; + }); - var deserialised = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(score)); + var deserialised = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(score)); - if (deserialised != null) - deserialised.Ruleset = new OsuRuleset().RulesetInfo; - - Assert.That(((OsuModDoubleTime)deserialised?.Mods[0])?.SpeedChange.Value, Is.EqualTo(2)); + Assert.That((deserialised?.Mods[0])?.Settings["speed_change"], Is.EqualTo(2)); } private class TestRuleset : Ruleset diff --git a/osu.Game.Tests/Online/TestMultiplayerMessagePackSerialization.cs b/osu.Game.Tests/Online/TestMultiplayerMessagePackSerialization.cs index 5491774e26..1027b722d1 100644 --- a/osu.Game.Tests/Online/TestMultiplayerMessagePackSerialization.cs +++ b/osu.Game.Tests/Online/TestMultiplayerMessagePackSerialization.cs @@ -20,7 +20,7 @@ namespace osu.Game.Tests.Online MatchState = new TeamVersusRoomState() }; - var serialized = MessagePackSerializer.Serialize(room); + byte[] serialized = MessagePackSerializer.Serialize(room); var deserialized = MessagePackSerializer.Deserialize(serialized); @@ -32,7 +32,7 @@ namespace osu.Game.Tests.Online { var state = new TeamVersusUserState(); - var serialized = MessagePackSerializer.Serialize(typeof(MatchUserState), state); + byte[] serialized = MessagePackSerializer.Serialize(typeof(MatchUserState), state); var deserialized = MessagePackSerializer.Deserialize(serialized); Assert.IsTrue(deserialized is TeamVersusUserState); @@ -44,7 +44,7 @@ namespace osu.Game.Tests.Online var state = new TeamVersusUserState(); // SignalR serialises using the actual type, rather than a base specification. - var serialized = MessagePackSerializer.Serialize(typeof(TeamVersusUserState), state); + byte[] serialized = MessagePackSerializer.Serialize(typeof(TeamVersusUserState), state); // works with explicit type specified. MessagePackSerializer.Deserialize(serialized); @@ -59,7 +59,7 @@ namespace osu.Game.Tests.Online var state = new TeamVersusUserState(); // SignalR serialises using the actual type, rather than a base specification. - var serialized = MessagePackSerializer.Serialize(typeof(TeamVersusUserState), state, SignalRUnionWorkaroundResolver.OPTIONS); + byte[] serialized = MessagePackSerializer.Serialize(typeof(TeamVersusUserState), state, SignalRUnionWorkaroundResolver.OPTIONS); // works with explicit type specified. MessagePackSerializer.Deserialize(serialized); diff --git a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs index 558b874234..5e7ce3abf5 100644 --- a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs +++ b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs @@ -128,7 +128,7 @@ namespace osu.Game.Tests.Online private void addAvailabilityCheckStep(string description, Func expected) { - AddAssert(description, () => availabilityTracker.Availability.Value.Equals(expected.Invoke())); + AddUntilStep(description, () => availabilityTracker.Availability.Value.Equals(expected.Invoke())); } private static BeatmapInfo getTestBeatmapInfo(string archiveFile) diff --git a/osu.Game.Tests/Resources/TestResources.cs b/osu.Game.Tests/Resources/TestResources.cs index 839366d98e..dff9478852 100644 --- a/osu.Game.Tests/Resources/TestResources.cs +++ b/osu.Game.Tests/Resources/TestResources.cs @@ -29,7 +29,7 @@ namespace osu.Game.Tests.Resources /// A path to a copy of a beatmap archive (osz). Should be deleted after use. public static string GetQuickTestBeatmapForImport() { - var tempPath = getTempFilename(); + string tempPath = getTempFilename(); using (var stream = OpenResource("Archives/241526 Soleily - Renatus_virtual_quick.osz")) using (var newFile = File.Create(tempPath)) stream.CopyTo(newFile); @@ -45,7 +45,7 @@ namespace osu.Game.Tests.Resources /// A path to a copy of a beatmap archive (osz). Should be deleted after use. public static string GetTestBeatmapForImport(bool virtualTrack = false) { - var tempPath = getTempFilename(); + string tempPath = getTempFilename(); using (var stream = GetTestBeatmapStream(virtualTrack)) using (var newFile = File.Create(tempPath)) diff --git a/osu.Game.Tests/Resources/duplicate-last-position-slider.osu b/osu.Game.Tests/Resources/duplicate-last-position-slider.osu new file mode 100644 index 0000000000..782dd4263e --- /dev/null +++ b/osu.Game.Tests/Resources/duplicate-last-position-slider.osu @@ -0,0 +1,19 @@ +osu file format v14 + +[Difficulty] +HPDrainRate:7 +CircleSize:10 +OverallDifficulty:9 +ApproachRate:10 +SliderMultiplier:0.4 +SliderTickRate:1 + +[TimingPoints] +382,923.076923076923,3,2,1,75,1,0 +382,-1000,3,2,1,75,0,0 + +[HitObjects] + +// Importantly, the last position is specified twice. +// In this case, osu-stable doesn't extend the slider length even when the "expected" length is higher than the actual. +261,171,25305,6,0,B|262:171|262:171|262:171,1,2,8|0,0:0|0:0,0:0:0:0: diff --git a/osu.Game.Tests/Skins/IO/ImportSkinTest.cs b/osu.Game.Tests/Skins/IO/ImportSkinTest.cs index b2600bb887..ecc9c92025 100644 --- a/osu.Game.Tests/Skins/IO/ImportSkinTest.cs +++ b/osu.Game.Tests/Skins/IO/ImportSkinTest.cs @@ -4,10 +4,12 @@ using System; using System.IO; using System.Linq; +using System.Runtime.CompilerServices; using System.Threading.Tasks; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Platform; +using osu.Game.IO; using osu.Game.IO.Archives; using osu.Game.Skinning; using SharpCompress.Archives.Zip; @@ -16,169 +18,213 @@ namespace osu.Game.Tests.Skins.IO { public class ImportSkinTest : ImportTest { - [Test] - public async Task TestBasicImport() - { - using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportSkinTest))) - { - try - { - var osu = LoadOsuIntoHost(host); - - var imported = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOsk("test skin", "skinner"), "skin.osk")); - - Assert.That(imported.Name, Is.EqualTo("test skin")); - Assert.That(imported.Creator, Is.EqualTo("skinner")); - } - finally - { - host.Exit(); - } - } - } + #region Testing filename metadata inclusion [Test] - public async Task TestImportTwiceWithSameMetadata() + public Task TestSingleImportDifferentFilename() => runSkinTest(async osu => { - using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportSkinTest))) - { - try - { - var osu = LoadOsuIntoHost(host); + var import1 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOskWithIni("test skin", "skinner"), "skin.osk")); - var imported = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOsk("test skin", "skinner"), "skin.osk")); - var imported2 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOsk("test skin", "skinner"), "skin2.osk")); - - Assert.That(imported2.ID, Is.Not.EqualTo(imported.ID)); - Assert.That(osu.Dependencies.Get().GetAllUserSkins(true).Count, Is.EqualTo(1)); - - // the first should be overwritten by the second import. - Assert.That(osu.Dependencies.Get().GetAllUserSkins(true).First().Files.First().FileInfoID, Is.EqualTo(imported2.Files.First().FileInfoID)); - } - finally - { - host.Exit(); - } - } - } + // When the import filename doesn't match, it should be appended (and update the skin.ini). + assertCorrectMetadata(import1, "test skin [skin]", "skinner", osu); + }); [Test] - public async Task TestImportTwiceWithNoMetadata() + public Task TestSingleImportWeirdIniFileCase() => runSkinTest(async osu => { - using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportSkinTest))) - { - try - { - var osu = LoadOsuIntoHost(host); + var import1 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOskWithIni("test skin", "skinner", iniFilename: "Skin.InI"), "skin.osk")); - // if a user downloads two skins that do have skin.ini files but don't have any creator metadata in the skin.ini, they should both import separately just for safety. - var imported = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOsk(string.Empty, string.Empty), "download.osk")); - var imported2 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOsk(string.Empty, string.Empty), "download.osk")); - - Assert.That(imported2.ID, Is.Not.EqualTo(imported.ID)); - Assert.That(osu.Dependencies.Get().GetAllUserSkins(true).Count, Is.EqualTo(2)); - - Assert.That(osu.Dependencies.Get().GetAllUserSkins(true).First().Files.First().FileInfoID, Is.EqualTo(imported.Files.First().FileInfoID)); - Assert.That(osu.Dependencies.Get().GetAllUserSkins(true).Last().Files.First().FileInfoID, Is.EqualTo(imported2.Files.First().FileInfoID)); - } - finally - { - host.Exit(); - } - } - } + // When the import filename doesn't match, it should be appended (and update the skin.ini). + assertCorrectMetadata(import1, "test skin [skin]", "skinner", osu); + }); [Test] - public async Task TestImportTwiceWithDifferentMetadata() + public Task TestSingleImportMissingSectionHeader() => runSkinTest(async osu => { - using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportSkinTest))) - { - try - { - var osu = LoadOsuIntoHost(host); + var import1 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOskWithIni("test skin", "skinner", includeSectionHeader: false), "skin.osk")); - var imported = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOsk("test skin v2", "skinner"), "skin.osk")); - var imported2 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOsk("test skin v2.1", "skinner"), "skin2.osk")); - - Assert.That(imported2.ID, Is.Not.EqualTo(imported.ID)); - Assert.That(osu.Dependencies.Get().GetAllUserSkins(true).Count, Is.EqualTo(2)); - - Assert.That(osu.Dependencies.Get().GetAllUserSkins(true).First().Files.First().FileInfoID, Is.EqualTo(imported.Files.First().FileInfoID)); - Assert.That(osu.Dependencies.Get().GetAllUserSkins(true).Last().Files.First().FileInfoID, Is.EqualTo(imported2.Files.First().FileInfoID)); - } - finally - { - host.Exit(); - } - } - } + // When the import filename doesn't match, it should be appended (and update the skin.ini). + assertCorrectMetadata(import1, "test skin [skin]", "skinner", osu); + }); [Test] - public async Task TestImportUpperCasedOskArchive() + public Task TestSingleImportMatchingFilename() => runSkinTest(async osu => { - using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportSkinTest))) - { - try - { - var osu = LoadOsuIntoHost(host); + var import1 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOskWithIni("test skin", "skinner"), "test skin.osk")); - var imported = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOsk("name 1", "author 1"), "skin1.OsK")); - - Assert.That(imported.Name, Is.EqualTo("name 1")); - Assert.That(imported.Creator, Is.EqualTo("author 1")); - - var imported2 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOsk("name 1", "author 1"), "skin1.oSK")); - - Assert.That(imported2.Hash, Is.EqualTo(imported.Hash)); - } - finally - { - host.Exit(); - } - } - } + // When the import filename matches it shouldn't be appended. + assertCorrectMetadata(import1, "test skin", "skinner", osu); + }); [Test] - public async Task TestSameMetadataNameDifferentFolderName() + public Task TestSingleImportNoIniFile() => runSkinTest(async osu => { - using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportSkinTest))) - { - try - { - var osu = LoadOsuIntoHost(host); + var import1 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOskWithNonIniFile(), "test skin.osk")); - var imported = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOsk("name 1", "author 1", false), "my custom skin 1")); - Assert.That(imported.Name, Is.EqualTo("name 1 [my custom skin 1]")); - Assert.That(imported.Creator, Is.EqualTo("author 1")); + // When the import filename matches it shouldn't be appended. + assertCorrectMetadata(import1, "test skin", "Unknown", osu); + }); - var imported2 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOsk("name 1", "author 1", false), "my custom skin 2")); - Assert.That(imported2.Name, Is.EqualTo("name 1 [my custom skin 2]")); - Assert.That(imported2.Creator, Is.EqualTo("author 1")); + [Test] + public Task TestEmptyImportImportsWithFilename() => runSkinTest(async osu => + { + var import1 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createEmptyOsk(), "test skin.osk")); - Assert.That(imported2.Hash, Is.Not.EqualTo(imported.Hash)); - } - finally - { - host.Exit(); - } - } + // When the import filename matches it shouldn't be appended. + assertCorrectMetadata(import1, "test skin", "Unknown", osu); + }); + + #endregion + + #region Cases where imports should match existing + + [Test] + public Task TestImportTwiceWithSameMetadataAndFilename() => runSkinTest(async osu => + { + var import1 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOskWithIni("test skin", "skinner"), "skin.osk")); + var import2 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOskWithIni("test skin", "skinner"), "skin.osk")); + + assertImportedOnce(import1, import2); + }); + + [Test] + public Task TestImportTwiceWithNoMetadataSameDownloadFilename() => runSkinTest(async osu => + { + // if a user downloads two skins that do have skin.ini files but don't have any creator metadata in the skin.ini, they should both import separately just for safety. + var import1 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOskWithIni(string.Empty, string.Empty), "download.osk")); + var import2 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOskWithIni(string.Empty, string.Empty), "download.osk")); + + assertImportedOnce(import1, import2); + }); + + [Test] + public Task TestImportUpperCasedOskArchive() => runSkinTest(async osu => + { + var import1 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOskWithIni("name 1", "author 1"), "name 1.OsK")); + assertCorrectMetadata(import1, "name 1", "author 1", osu); + + var import2 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOskWithIni("name 1", "author 1"), "name 1.oSK")); + + assertImportedOnce(import1, import2); + }); + + [Test] + public Task TestSameMetadataNameSameFolderName() => runSkinTest(async osu => + { + var import1 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOskWithIni("name 1", "author 1"), "my custom skin 1")); + var import2 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOskWithIni("name 1", "author 1"), "my custom skin 1")); + + assertImportedOnce(import1, import2); + assertCorrectMetadata(import1, "name 1 [my custom skin 1]", "author 1", osu); + }); + + #endregion + + #region Cases where imports should be uniquely imported + + [Test] + public Task TestImportTwiceWithSameMetadataButDifferentFilename() => runSkinTest(async osu => + { + var import1 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOskWithIni("test skin", "skinner"), "skin.osk")); + var import2 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOskWithIni("test skin", "skinner"), "skin2.osk")); + + assertImportedBoth(import1, import2); + }); + + [Test] + public Task TestImportTwiceWithNoMetadataDifferentDownloadFilename() => runSkinTest(async osu => + { + // if a user downloads two skins that do have skin.ini files but don't have any creator metadata in the skin.ini, they should both import separately just for safety. + var import1 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOskWithIni(string.Empty, string.Empty), "download.osk")); + var import2 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOskWithIni(string.Empty, string.Empty), "download2.osk")); + + assertImportedBoth(import1, import2); + }); + + [Test] + public Task TestImportTwiceWithSameFilenameDifferentMetadata() => runSkinTest(async osu => + { + var import1 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOskWithIni("test skin v2", "skinner"), "skin.osk")); + var import2 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOskWithIni("test skin v2.1", "skinner"), "skin.osk")); + + assertImportedBoth(import1, import2); + assertCorrectMetadata(import1, "test skin v2 [skin]", "skinner", osu); + assertCorrectMetadata(import2, "test skin v2.1 [skin]", "skinner", osu); + }); + + [Test] + public Task TestSameMetadataNameDifferentFolderName() => runSkinTest(async osu => + { + var import1 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOskWithIni("name 1", "author 1"), "my custom skin 1")); + var import2 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOskWithIni("name 1", "author 1"), "my custom skin 2")); + + assertImportedBoth(import1, import2); + assertCorrectMetadata(import1, "name 1 [my custom skin 1]", "author 1", osu); + assertCorrectMetadata(import2, "name 1 [my custom skin 2]", "author 1", osu); + }); + + #endregion + + private void assertCorrectMetadata(SkinInfo import1, string name, string creator, OsuGameBase osu) + { + Assert.That(import1.Name, Is.EqualTo(name)); + Assert.That(import1.Creator, Is.EqualTo(creator)); + + // for extra safety let's reconstruct the skin, reading from the skin.ini. + var instance = import1.CreateInstance((IStorageResourceProvider)osu.Dependencies.Get(typeof(SkinManager))); + + Assert.That(instance.Configuration.SkinInfo.Name, Is.EqualTo(name)); + Assert.That(instance.Configuration.SkinInfo.Creator, Is.EqualTo(creator)); } - private MemoryStream createOsk(string name, string author, bool makeUnique = true) + private void assertImportedBoth(SkinInfo import1, SkinInfo import2) + { + Assert.That(import2.ID, Is.Not.EqualTo(import1.ID)); + Assert.That(import2.Hash, Is.Not.EqualTo(import1.Hash)); + Assert.That(import2.Files.Select(f => f.FileInfoID), Is.Not.EquivalentTo(import1.Files.Select(f => f.FileInfoID))); + } + + private void assertImportedOnce(SkinInfo import1, SkinInfo import2) + { + Assert.That(import2.ID, Is.EqualTo(import1.ID)); + Assert.That(import2.Hash, Is.EqualTo(import1.Hash)); + Assert.That(import2.Files.Select(f => f.FileInfoID), Is.EquivalentTo(import1.Files.Select(f => f.FileInfoID))); + } + + private MemoryStream createEmptyOsk() { var zipStream = new MemoryStream(); using var zip = ZipArchive.Create(); - zip.AddEntry("skin.ini", generateSkinIni(name, author, makeUnique)); zip.SaveTo(zipStream); return zipStream; } - private MemoryStream generateSkinIni(string name, string author, bool makeUnique = true) + private MemoryStream createOskWithNonIniFile() + { + var zipStream = new MemoryStream(); + using var zip = ZipArchive.Create(); + zip.AddEntry("hitcircle.png", new MemoryStream(new byte[] { 0, 1, 2, 3 })); + zip.SaveTo(zipStream); + return zipStream; + } + + private MemoryStream createOskWithIni(string name, string author, bool makeUnique = false, string iniFilename = @"skin.ini", bool includeSectionHeader = true) + { + var zipStream = new MemoryStream(); + using var zip = ZipArchive.Create(); + zip.AddEntry(iniFilename, generateSkinIni(name, author, makeUnique, includeSectionHeader)); + zip.SaveTo(zipStream); + return zipStream; + } + + private MemoryStream generateSkinIni(string name, string author, bool makeUnique = true, bool includeSectionHeader = true) { var stream = new MemoryStream(); var writer = new StreamWriter(stream); - writer.WriteLine("[General]"); + if (includeSectionHeader) + writer.WriteLine("[General]"); + writer.WriteLine($"Name: {name}"); writer.WriteLine($"Author: {author}"); @@ -193,6 +239,22 @@ namespace osu.Game.Tests.Skins.IO return stream; } + private async Task runSkinTest(Func action, [CallerMemberName] string callingMethodName = @"") + { + using (HeadlessGameHost host = new CleanRunHeadlessGameHost(callingMethodName)) + { + try + { + var osu = LoadOsuIntoHost(host); + await action(osu); + } + finally + { + host.Exit(); + } + } + } + private async Task loadSkinIntoOsu(OsuGameBase osu, ArchiveReader archive = null) { var skinManager = osu.Dependencies.Get(); diff --git a/osu.Game.Tests/Skins/LegacySkinDecoderTest.cs b/osu.Game.Tests/Skins/LegacySkinDecoderTest.cs index dcb866c99f..cfc140ce39 100644 --- a/osu.Game.Tests/Skins/LegacySkinDecoderTest.cs +++ b/osu.Game.Tests/Skins/LegacySkinDecoderTest.cs @@ -106,7 +106,7 @@ namespace osu.Game.Tests.Skins var decoder = new LegacySkinDecoder(); using (var resStream = TestResources.OpenResource("skin-latest.ini")) using (var stream = new LineBufferedReader(resStream)) - Assert.AreEqual(LegacySkinConfiguration.LATEST_VERSION, decoder.Decode(stream).LegacyVersion); + Assert.AreEqual(SkinConfiguration.LATEST_VERSION, decoder.Decode(stream).LegacyVersion); } [Test] diff --git a/osu.Game.Tests/Skins/TestSceneSkinConfigurationLookup.cs b/osu.Game.Tests/Skins/TestSceneSkinConfigurationLookup.cs index aadabec100..870d6d8f57 100644 --- a/osu.Game.Tests/Skins/TestSceneSkinConfigurationLookup.cs +++ b/osu.Game.Tests/Skins/TestSceneSkinConfigurationLookup.cs @@ -151,7 +151,7 @@ namespace osu.Game.Tests.Skins { AddStep("Set user skin version 2.3", () => userSource.Configuration.LegacyVersion = 2.3m); AddStep("Set beatmap skin version null", () => beatmapSource.Configuration.LegacyVersion = null); - AddAssert("Check legacy version lookup", () => requester.GetConfig(LegacySkinConfiguration.LegacySetting.Version)?.Value == 2.3m); + AddAssert("Check legacy version lookup", () => requester.GetConfig(SkinConfiguration.LegacySetting.Version)?.Value == 2.3m); } [Test] @@ -160,7 +160,7 @@ namespace osu.Game.Tests.Skins // completely ignoring beatmap versions for simplicity. AddStep("Set user skin version 2.3", () => userSource.Configuration.LegacyVersion = 2.3m); AddStep("Set beatmap skin version null", () => beatmapSource.Configuration.LegacyVersion = 1.7m); - AddAssert("Check legacy version lookup", () => requester.GetConfig(LegacySkinConfiguration.LegacySetting.Version)?.Value == 2.3m); + AddAssert("Check legacy version lookup", () => requester.GetConfig(SkinConfiguration.LegacySetting.Version)?.Value == 2.3m); } [Test] @@ -169,14 +169,14 @@ namespace osu.Game.Tests.Skins AddStep("Set user skin version 2.3", () => userSource.Configuration.LegacyVersion = null); AddStep("Set beatmap skin version null", () => beatmapSource.Configuration.LegacyVersion = null); AddAssert("Check legacy version lookup", - () => requester.GetConfig(LegacySkinConfiguration.LegacySetting.Version)?.Value == LegacySkinConfiguration.LATEST_VERSION); + () => requester.GetConfig(SkinConfiguration.LegacySetting.Version)?.Value == SkinConfiguration.LATEST_VERSION); } [Test] public void TestIniWithNoVersionFallsBackTo1() { AddStep("Parse skin with no version", () => userSource.Configuration = new LegacySkinDecoder().Decode(new LineBufferedReader(new MemoryStream()))); - AddAssert("Check legacy version lookup", () => requester.GetConfig(LegacySkinConfiguration.LegacySetting.Version)?.Value == 1.0m); + AddAssert("Check legacy version lookup", () => requester.GetConfig(SkinConfiguration.LegacySetting.Version)?.Value == 1.0m); } public enum LookupType diff --git a/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapSetOnlineStatusPill.cs b/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapSetOnlineStatusPill.cs new file mode 100644 index 0000000000..c48b63ac89 --- /dev/null +++ b/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapSetOnlineStatusPill.cs @@ -0,0 +1,53 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Testing; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.Drawables; +using osu.Game.Overlays; +using osu.Game.Tests.Visual.UserInterface; +using osuTK; + +namespace osu.Game.Tests.Visual.Beatmaps +{ + public class TestSceneBeatmapSetOnlineStatusPill : ThemeComparisonTestScene + { + protected override Drawable CreateContent() => new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 10), + ChildrenEnumerable = Enum.GetValues(typeof(BeatmapSetOnlineStatus)).Cast().Select(status => new BeatmapSetOnlineStatusPill + { + AutoSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Status = status + }) + }; + + private IEnumerable statusPills => this.ChildrenOfType(); + + [Test] + public void TestFixedWidth() + { + AddStep("create themed content", () => CreateThemedContent(OverlayColourScheme.Red)); + + AddStep("set fixed width", () => statusPills.ForEach(pill => + { + pill.AutoSizeAxes = Axes.Y; + pill.Width = 90; + })); + AddStep("unset fixed width", () => statusPills.ForEach(pill => pill.AutoSizeAxes = Axes.Both)); + } + } +} diff --git a/osu.Game.Tests/Visual/Beatmaps/TestSceneDifficultySpectrumDisplay.cs b/osu.Game.Tests/Visual/Beatmaps/TestSceneDifficultySpectrumDisplay.cs new file mode 100644 index 0000000000..1f38b05879 --- /dev/null +++ b/osu.Game.Tests/Visual/Beatmaps/TestSceneDifficultySpectrumDisplay.cs @@ -0,0 +1,110 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using NUnit.Framework; +using osu.Framework.Graphics; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.Drawables; +using osu.Game.Online.API.Requests.Responses; +using osuTK; + +namespace osu.Game.Tests.Visual.Beatmaps +{ + public class TestSceneDifficultySpectrumDisplay : OsuTestScene + { + private DifficultySpectrumDisplay display; + + private static APIBeatmapSet createBeatmapSetWith(params (int rulesetId, double stars)[] difficulties) => new APIBeatmapSet + { + Beatmaps = difficulties.Select(difficulty => new APIBeatmap + { + RulesetID = difficulty.rulesetId, + StarRating = difficulty.stars + }).ToList() + }; + + [Test] + public void TestSingleRuleset() + { + var beatmapSet = createBeatmapSetWith( + (rulesetId: 0, stars: 2.0), + (rulesetId: 0, stars: 3.2), + (rulesetId: 0, stars: 5.6)); + + createDisplay(beatmapSet); + } + + [Test] + public void TestMultipleRulesets() + { + var beatmapSet = createBeatmapSetWith( + (rulesetId: 0, stars: 2.0), + (rulesetId: 3, stars: 2.3), + (rulesetId: 0, stars: 3.2), + (rulesetId: 1, stars: 4.3), + (rulesetId: 0, stars: 5.6)); + + createDisplay(beatmapSet); + } + + [Test] + public void TestUnknownRuleset() + { + var beatmapSet = createBeatmapSetWith( + (rulesetId: 0, stars: 2.0), + (rulesetId: 3, stars: 2.3), + (rulesetId: 0, stars: 3.2), + (rulesetId: 1, stars: 4.3), + (rulesetId: 0, stars: 5.6), + (rulesetId: 15, stars: 7.8)); + + createDisplay(beatmapSet); + } + + [Test] + public void TestMaximumUncollapsed() + { + var beatmapSet = createBeatmapSetWith(Enumerable.Range(0, 12).Select(i => (rulesetId: i % 4, stars: 2.5 + i * 0.25)).ToArray()); + createDisplay(beatmapSet); + } + + [Test] + public void TestMinimumCollapsed() + { + var beatmapSet = createBeatmapSetWith(Enumerable.Range(0, 13).Select(i => (rulesetId: i % 4, stars: 2.5 + i * 0.25)).ToArray()); + createDisplay(beatmapSet); + } + + [Test] + public void TestAdjustableDotSize() + { + var beatmapSet = createBeatmapSetWith( + (rulesetId: 0, stars: 2.0), + (rulesetId: 3, stars: 2.3), + (rulesetId: 0, stars: 3.2), + (rulesetId: 1, stars: 4.3), + (rulesetId: 0, stars: 5.6)); + + createDisplay(beatmapSet); + + AddStep("change dot dimensions", () => + { + display.DotSize = new Vector2(8, 12); + display.DotSpacing = 2; + }); + AddStep("change dot dimensions back", () => + { + display.DotSize = new Vector2(4, 8); + display.DotSpacing = 1; + }); + } + + private void createDisplay(IBeatmapSetInfo beatmapSetInfo) => AddStep("create spectrum display", () => Child = display = new DifficultySpectrumDisplay(beatmapSetInfo) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Scale = new Vector2(3) + }); + } +} diff --git a/osu.Game.Tests/Visual/Components/TestScenePreviewTrackManager.cs b/osu.Game.Tests/Visual/Components/TestScenePreviewTrackManager.cs index 9a999a4931..89e20043fb 100644 --- a/osu.Game.Tests/Visual/Components/TestScenePreviewTrackManager.cs +++ b/osu.Game.Tests/Visual/Components/TestScenePreviewTrackManager.cs @@ -224,7 +224,7 @@ namespace osu.Game.Tests.Visual.Components public new PreviewTrack CurrentTrack => base.CurrentTrack; - protected override TrackManagerPreviewTrack CreatePreviewTrack(BeatmapSetInfo beatmapSetInfo, ITrackStore trackStore) => new TestPreviewTrack(beatmapSetInfo, trackStore); + protected override TrackManagerPreviewTrack CreatePreviewTrack(IBeatmapSetInfo beatmapSetInfo, ITrackStore trackStore) => new TestPreviewTrack(beatmapSetInfo, trackStore); public override bool UpdateSubTree() { @@ -240,7 +240,7 @@ namespace osu.Game.Tests.Visual.Components public new Track Track => base.Track; - public TestPreviewTrack(BeatmapSetInfo beatmapSetInfo, ITrackStore trackManager) + public TestPreviewTrack(IBeatmapSetInfo beatmapSetInfo, ITrackStore trackManager) : base(beatmapSetInfo, trackManager) { this.trackManager = trackManager; diff --git a/osu.Game.Tests/Visual/Editing/TestSceneBeatDivisorControl.cs b/osu.Game.Tests/Visual/Editing/TestSceneBeatDivisorControl.cs index 6cf5e6a987..ed7bb9e301 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneBeatDivisorControl.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneBeatDivisorControl.cs @@ -72,7 +72,7 @@ namespace osu.Game.Tests.Visual.Editing private Vector2 getPositionForDivisor(int divisor) { - var relativePosition = (float)Math.Clamp(divisor, 0, 16) / 16; + float relativePosition = (float)Math.Clamp(divisor, 0, 16) / 16; var sliderDrawQuad = tickSliderBar.ScreenSpaceDrawQuad; return new Vector2( sliderDrawQuad.TopLeft.X + sliderDrawQuad.Width * relativePosition, diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs index 440d66ff9f..92c8131568 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs @@ -76,7 +76,7 @@ namespace osu.Game.Tests.Visual.Editing { var setup = Editor.ChildrenOfType().First(); - var temp = TestResources.GetTestBeatmapForImport(); + string temp = TestResources.GetTestBeatmapForImport(); string extractedFolder = $"{temp}_extracted"; Directory.CreateDirectory(extractedFolder); diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs index ab2bc4649a..af3d9beb69 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs @@ -56,6 +56,11 @@ namespace osu.Game.Tests.Visual.Editing checkMutations(); + // After placement these must be non-default as defaults are read-only. + AddAssert("Placed object has non-default control points", () => + editorBeatmap.HitObjects[0].SampleControlPoint != SampleControlPoint.DEFAULT && + editorBeatmap.HitObjects[0].DifficultyControlPoint != DifficultyControlPoint.DEFAULT); + AddStep("Save", () => InputManager.Keys(PlatformAction.Save)); checkMutations(); diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayMenuOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayMenuOverlay.cs index 477ac70501..abd43e7427 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayMenuOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayMenuOverlay.cs @@ -57,7 +57,7 @@ namespace osu.Game.Tests.Visual.Gameplay { showOverlay(); - var retryCount = 0; + int retryCount = 0; AddRepeatStep("Add retry", () => { diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs index 5e2374cbcb..64d9addc77 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs @@ -13,6 +13,7 @@ using osu.Framework.Bindables; using osu.Framework.Testing; using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets; +using osu.Game.Rulesets.Osu; using osu.Game.Screens.Ranking; using osuTK.Input; @@ -132,11 +133,12 @@ namespace osu.Game.Tests.Visual.Gameplay private ScoreInfo getScoreInfo(bool replayAvailable) { - return new APILegacyScoreInfo + return new APIScoreInfo { - OnlineScoreID = 2553163309, - OnlineRulesetID = 0, - Replay = replayAvailable, + OnlineID = 2553163309, + RulesetID = 0, + Beatmap = CreateAPIBeatmapSet(new OsuRuleset().RulesetInfo).Beatmaps.First(), + HasReplay = replayAvailable, User = new User { Id = 39828, diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableLoungeRoom.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableLoungeRoom.cs new file mode 100644 index 0000000000..ea895a23d2 --- /dev/null +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableLoungeRoom.cs @@ -0,0 +1,167 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Moq; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Cursor; +using osu.Framework.Testing; +using osu.Game.Graphics.UserInterface; +using osu.Game.Online.Rooms; +using osu.Game.Overlays; +using osu.Game.Screens.OnlinePlay.Lounge; +using osuTK.Input; + +namespace osu.Game.Tests.Visual.Multiplayer +{ + public class TestSceneDrawableLoungeRoom : OsuManualInputManagerTestScene + { + private readonly Room room = new Room + { + HasPassword = { Value = true } + }; + + [Cached] + protected readonly OverlayColourProvider ColourProvider = new OverlayColourProvider(OverlayColourScheme.Pink); + + private DrawableLoungeRoom drawableRoom; + private SearchTextBox searchTextBox; + + private readonly ManualResetEventSlim allowResponseCallback = new ManualResetEventSlim(); + + [BackgroundDependencyLoader] + private void load() + { + var mockLounge = new Mock(); + mockLounge + .Setup(l => l.Join(It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny>())) + .Callback, Action>((a, b, c, d) => + { + Task.Run(() => + { + allowResponseCallback.Wait(); + allowResponseCallback.Reset(); + Schedule(() => d?.Invoke("Incorrect password")); + }); + }); + + Dependencies.CacheAs(mockLounge.Object); + } + + [SetUpSteps] + public void SetUpSteps() + { + AddStep("create drawable", () => + { + Child = new PopoverContainer + { + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + searchTextBox = new SearchTextBox + { + HoldFocus = true, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Margin = new MarginPadding(50), + Width = 500, + Depth = float.MaxValue + }, + drawableRoom = new DrawableLoungeRoom(room) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + } + } + }; + }); + } + + [Test] + public void TestFocusViaKeyboardCommit() + { + DrawableLoungeRoom.PasswordEntryPopover popover = null; + + AddAssert("search textbox has focus", () => checkFocus(searchTextBox)); + AddStep("click room twice", () => + { + InputManager.MoveMouseTo(drawableRoom); + InputManager.Click(MouseButton.Left); + InputManager.Click(MouseButton.Left); + }); + AddUntilStep("wait for popover", () => (popover = InputManager.ChildrenOfType().SingleOrDefault()) != null); + + AddAssert("textbox has focus", () => checkFocus(popover.ChildrenOfType().Single())); + + AddStep("enter password", () => popover.ChildrenOfType().Single().Text = "password"); + AddStep("commit via enter", () => InputManager.Key(Key.Enter)); + + AddAssert("popover has focus", () => checkFocus(popover)); + + AddStep("attempt another enter", () => InputManager.Key(Key.Enter)); + + AddAssert("popover still has focus", () => checkFocus(popover)); + + AddStep("unblock response", () => allowResponseCallback.Set()); + + AddUntilStep("wait for textbox refocus", () => checkFocus(popover.ChildrenOfType().Single())); + + AddStep("press escape", () => InputManager.Key(Key.Escape)); + AddStep("press escape", () => InputManager.Key(Key.Escape)); + + AddUntilStep("search textbox has focus", () => checkFocus(searchTextBox)); + } + + [Test] + public void TestFocusViaMouseCommit() + { + DrawableLoungeRoom.PasswordEntryPopover popover = null; + + AddAssert("search textbox has focus", () => checkFocus(searchTextBox)); + AddStep("click room twice", () => + { + InputManager.MoveMouseTo(drawableRoom); + InputManager.Click(MouseButton.Left); + InputManager.Click(MouseButton.Left); + }); + AddUntilStep("wait for popover", () => (popover = InputManager.ChildrenOfType().SingleOrDefault()) != null); + + AddAssert("textbox has focus", () => checkFocus(popover.ChildrenOfType().Single())); + + AddStep("enter password", () => popover.ChildrenOfType().Single().Text = "password"); + + AddStep("commit via click button", () => + { + var button = popover.ChildrenOfType().Single(); + InputManager.MoveMouseTo(button); + InputManager.Click(MouseButton.Left); + }); + + AddAssert("popover has focus", () => checkFocus(popover)); + + AddStep("attempt another click", () => InputManager.Click(MouseButton.Left)); + + AddAssert("popover still has focus", () => checkFocus(popover)); + + AddStep("unblock response", () => allowResponseCallback.Set()); + + AddUntilStep("wait for textbox refocus", () => checkFocus(popover.ChildrenOfType().Single())); + + AddStep("click away", () => + { + InputManager.MoveMouseTo(searchTextBox); + InputManager.Click(MouseButton.Left); + }); + + AddUntilStep("search textbox has focus", () => checkFocus(searchTextBox)); + } + + private bool checkFocus(Drawable expected) => + InputManager.FocusedDrawable == expected; + } +} diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs index 93bdbb79f4..f44b0c9716 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs @@ -38,8 +38,6 @@ namespace osu.Game.Tests.Visual.Multiplayer { Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); Dependencies.Cache(manager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, Resources, host, Beatmap.Default)); - - manager.Import(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo.BeatmapSet).Wait(); } [Test] @@ -204,7 +202,11 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestDownloadButtonHiddenWhenBeatmapExists() { - createPlaylist(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo); + var beatmap = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo; + + AddStep("import beatmap", () => manager.Import(beatmap.BeatmapSet).Wait()); + + createPlaylist(beatmap); assertDownloadButtonVisible(false); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneGameplayChatDisplay.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneGameplayChatDisplay.cs index a3a1cacb0d..512d206a06 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneGameplayChatDisplay.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneGameplayChatDisplay.cs @@ -92,6 +92,18 @@ namespace osu.Game.Tests.Visual.Multiplayer assertChatFocused(true); } + [Test] + public void TestFocusLostOnBackKey() + { + setLocalUserPlaying(true); + + assertChatFocused(false); + AddStep("press tab", () => InputManager.Key(Key.Tab)); + assertChatFocused(true); + AddStep("press escape", () => InputManager.Key(Key.Escape)); + assertChatFocused(false); + } + [Test] public void TestFocusOnTabKeyWhenNotExpanded() { diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs index 99b530c2a2..c3d5f7ec23 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs @@ -5,6 +5,7 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; using osu.Framework.Testing; using osu.Game.Online.Rooms; using osu.Game.Rulesets.Catch; @@ -18,19 +19,25 @@ namespace osu.Game.Tests.Visual.Multiplayer { public class TestSceneLoungeRoomsContainer : OnlinePlayTestScene { - protected new TestRequestHandlingRoomManager RoomManager => (TestRequestHandlingRoomManager)base.RoomManager; + protected new TestRoomManager RoomManager => (TestRoomManager)base.RoomManager; private RoomsContainer container; [SetUp] public new void Setup() => Schedule(() => { - Child = container = new RoomsContainer + Child = new PopoverContainer { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, Anchor = Anchor.Centre, Origin = Anchor.Centre, Width = 0.5f, - SelectedRoom = { BindTarget = SelectedRoom } + + Child = container = new RoomsContainer + { + SelectedRoom = { BindTarget = SelectedRoom } + } }; }); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs index ade24b8740..bd5320354e 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs @@ -32,7 +32,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { PLAYER_2_ID, new ManualClock() } }; - foreach (var (userId, _) in clocks) + foreach ((int userId, var _) in clocks) { SpectatorClient.StartPlay(userId, 0); OnlinePlayDependencies.Client.AddUser(new User { Id = userId }); @@ -53,7 +53,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("add clock sources", () => { - foreach (var (userId, clock) in clocks) + foreach ((int userId, var clock) in clocks) leaderboard.AddClock(userId, clock); }); } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs index bfcb55ce33..7ff8c82145 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs @@ -289,7 +289,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestSpectatingDuringGameplay() { - var players = new[] { PLAYER_1_ID, PLAYER_2_ID }; + int[] players = { PLAYER_1_ID, PLAYER_2_ID }; start(players); sendFrames(players, 300); @@ -326,7 +326,7 @@ namespace osu.Game.Tests.Visual.Multiplayer for (int count = 3; count >= 0; count--) { - var id = PLAYER_1_ID + count; + int id = PLAYER_1_ID + count; end(id); AddUntilStep($"{id} area grayed", () => getInstance(id).Colour != Color4.White); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs index 2bb77395ef..38cf9d662f 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs @@ -23,8 +23,6 @@ using osu.Game.Overlays.Mods; using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Mods; -using osu.Game.Screens; -using osu.Game.Screens.OnlinePlay.Components; using osu.Game.Screens.OnlinePlay.Lounge; using osu.Game.Screens.OnlinePlay.Lounge.Components; using osu.Game.Screens.OnlinePlay.Match; @@ -32,6 +30,7 @@ using osu.Game.Screens.OnlinePlay.Multiplayer; using osu.Game.Screens.OnlinePlay.Multiplayer.Match; using osu.Game.Screens.Play; using osu.Game.Screens.Ranking; +using osu.Game.Screens.Spectate; using osu.Game.Tests.Resources; using osu.Game.Users; using osuTK.Input; @@ -44,11 +43,10 @@ namespace osu.Game.Tests.Visual.Multiplayer private RulesetStore rulesets; private BeatmapSetInfo importedSet; - private DependenciesScreen dependenciesScreen; - private TestMultiplayer multiplayerScreen; - private TestMultiplayerClient client; + private TestMultiplayerScreenStack multiplayerScreenStack; - private TestRequestHandlingMultiplayerRoomManager roomManager => multiplayerScreen.RoomManager; + private TestMultiplayerClient client => multiplayerScreenStack.Client; + private TestMultiplayerRoomManager roomManager => multiplayerScreenStack.RoomManager; [Cached(typeof(UserLookupCache))] private UserLookupCache lookupCache = new TestUserLookupCache(); @@ -70,22 +68,8 @@ namespace osu.Game.Tests.Visual.Multiplayer importedSet = beatmaps.GetAllUsableBeatmapSetsEnumerable(IncludedDetails.All).First(); }); - AddStep("create multiplayer screen", () => multiplayerScreen = new TestMultiplayer()); - - AddStep("load dependencies", () => - { - client = new TestMultiplayerClient(roomManager); - - // The screen gets suspended so it stops receiving updates. - Child = client; - - LoadScreen(dependenciesScreen = new DependenciesScreen(client)); - }); - - AddUntilStep("wait for dependencies to load", () => dependenciesScreen.IsLoaded); - - AddStep("load multiplayer", () => LoadScreen(multiplayerScreen)); - AddUntilStep("wait for multiplayer to load", () => multiplayerScreen.IsLoaded); + AddStep("load multiplayer", () => LoadScreen(multiplayerScreenStack = new TestMultiplayerScreenStack())); + AddUntilStep("wait for multiplayer to load", () => multiplayerScreenStack.IsLoaded); AddUntilStep("wait for lounge to load", () => this.ChildrenOfType().FirstOrDefault()?.IsLoaded == true); } @@ -247,6 +231,9 @@ namespace osu.Game.Tests.Visual.Multiplayer } } }); + + AddAssert("Check participant count correct", () => client.APIRoom?.ParticipantCount.Value == 1); + AddAssert("Check participant list contains user", () => client.APIRoom?.RecentParticipants.Count(u => u.Id == API.LocalUser.Value.Id) == 1); } [Test] @@ -306,6 +293,9 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("wait for room open", () => this.ChildrenOfType().FirstOrDefault()?.IsLoaded == true); AddUntilStep("wait for join", () => client.Room != null); + + AddAssert("Check participant count correct", () => client.APIRoom?.ParticipantCount.Value == 1); + AddAssert("Check participant list contains user", () => client.APIRoom?.RecentParticipants.Count(u => u.Id == API.LocalUser.Value.Id) == 1); } [Test] @@ -441,7 +431,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("start match externally", () => client.StartMatch()); - AddAssert("play not started", () => multiplayerScreen.IsCurrentScreen()); + AddAssert("play not started", () => multiplayerScreenStack.IsCurrentScreen()); } [Test] @@ -485,7 +475,7 @@ namespace osu.Game.Tests.Visual.Multiplayer importedSet = beatmaps.GetAllUsableBeatmapSetsEnumerable(IncludedDetails.All).First(); }); - AddUntilStep("play started", () => !multiplayerScreen.IsCurrentScreen()); + AddUntilStep("play started", () => multiplayerScreenStack.CurrentScreen is SpectatorScreen); } [Test] @@ -527,16 +517,16 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("open mod overlay", () => this.ChildrenOfType().Single().TriggerClick()); - AddStep("invoke on back button", () => multiplayerScreen.OnBackButton()); + AddStep("invoke on back button", () => multiplayerScreenStack.OnBackButton()); AddAssert("mod overlay is hidden", () => this.ChildrenOfType().Single().State.Value == Visibility.Hidden); AddAssert("dialog overlay is hidden", () => DialogOverlay.State.Value == Visibility.Hidden); - testLeave("back button", () => multiplayerScreen.OnBackButton()); + testLeave("back button", () => multiplayerScreenStack.OnBackButton()); // mimics home button and OS window close - testLeave("forced exit", () => multiplayerScreen.Exit()); + testLeave("forced exit", () => multiplayerScreenStack.Exit()); void testLeave(string actionName, Action action) { @@ -577,24 +567,24 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("click start button", () => InputManager.Click(MouseButton.Left)); - AddUntilStep("wait for player", () => Stack.CurrentScreen is Player); + AddUntilStep("wait for player", () => multiplayerScreenStack.CurrentScreen is Player); // Gameplay runs in real-time, so we need to incrementally check if gameplay has finished in order to not time out. for (double i = 1000; i < TestResources.QUICK_BEATMAP_LENGTH; i += 1000) { - var time = i; + double time = i; AddUntilStep($"wait for time > {i}", () => this.ChildrenOfType().SingleOrDefault()?.GameplayClock.CurrentTime > time); } - AddUntilStep("wait for results", () => Stack.CurrentScreen is ResultsScreen); + AddUntilStep("wait for results", () => multiplayerScreenStack.CurrentScreen is ResultsScreen); } private MultiplayerReadyButton readyButton => this.ChildrenOfType().Single(); private void createRoom(Func room) { - AddUntilStep("wait for lounge", () => multiplayerScreen.ChildrenOfType().SingleOrDefault()?.IsLoaded == true); - AddStep("open room", () => multiplayerScreen.ChildrenOfType().Single().Open(room())); + AddUntilStep("wait for lounge", () => multiplayerScreenStack.ChildrenOfType().SingleOrDefault()?.IsLoaded == true); + AddStep("open room", () => multiplayerScreenStack.ChildrenOfType().Single().Open(room())); AddUntilStep("wait for room open", () => this.ChildrenOfType().FirstOrDefault()?.IsLoaded == true); AddWaitStep("wait for transition", 2); @@ -607,26 +597,5 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("wait for join", () => client.Room != null); } - - /// - /// Used for the sole purpose of adding as a resolvable dependency. - /// - private class DependenciesScreen : OsuScreen - { - [Cached(typeof(MultiplayerClient))] - public readonly TestMultiplayerClient Client; - - public DependenciesScreen(TestMultiplayerClient client) - { - Client = client; - } - } - - private class TestMultiplayer : Screens.OnlinePlay.Multiplayer.Multiplayer - { - public new TestRequestHandlingMultiplayerRoomManager RoomManager { get; private set; } - - protected override RoomManager CreateRoomManager() => RoomManager = new TestRequestHandlingMultiplayerRoomManager(); - } } } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs index 3317ddc767..832998d5d3 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs @@ -8,7 +8,6 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; -using osu.Framework.Testing; using osu.Framework.Utils; using osu.Game.Configuration; using osu.Game.Online.API; @@ -40,9 +39,10 @@ namespace osu.Game.Tests.Visual.Multiplayer Dependencies.Cache(config = new OsuConfigManager(LocalStorage)); } - [SetUpSteps] public override void SetUpSteps() { + base.SetUpSteps(); + AddStep("set local user", () => ((DummyAPIAccess)API).LocalUser.Value = LookupCache.GetUserAsync(1).Result); AddStep("create leaderboard", () => @@ -55,7 +55,7 @@ namespace osu.Game.Tests.Visual.Multiplayer var playableBeatmap = Beatmap.Value.GetPlayableBeatmap(Ruleset.Value); var multiplayerUsers = new List(); - foreach (var user in users) + foreach (int user in users) { SpectatorClient.StartPlay(user, Beatmap.Value.BeatmapInfo.OnlineBeatmapID ?? 0); multiplayerUsers.Add(OnlinePlayDependencies.Client.AddUser(new User { Id = user }, true)); @@ -89,7 +89,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestUserQuit() { - foreach (var user in users) + foreach (int user in users) AddStep($"mark user {user} quit", () => Client.RemoveUser(LookupCache.GetUserAsync(user).Result.AsNonNull())); } @@ -114,7 +114,7 @@ namespace osu.Game.Tests.Visual.Multiplayer public void RandomlyUpdateState() { - foreach (var userId in PlayingUsers) + foreach (int userId in PlayingUsers) { if (RNG.NextBool()) continue; diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboardTeams.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboardTeams.cs index dfaf2f1dc3..3d48ddc7ca 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboardTeams.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboardTeams.cs @@ -5,7 +5,6 @@ using System.Collections.Generic; using System.Linq; using NUnit.Framework; using osu.Framework.Graphics; -using osu.Framework.Testing; using osu.Framework.Utils; using osu.Game.Online.API; using osu.Game.Online.Multiplayer; @@ -44,9 +43,10 @@ namespace osu.Game.Tests.Visual.Multiplayer return room; } - [SetUpSteps] public override void SetUpSteps() { + base.SetUpSteps(); + AddStep("set local user", () => ((DummyAPIAccess)API).LocalUser.Value = LookupCache.GetUserAsync(1).Result); AddStep("create leaderboard", () => @@ -59,7 +59,7 @@ namespace osu.Game.Tests.Visual.Multiplayer var playableBeatmap = Beatmap.Value.GetPlayableBeatmap(Ruleset.Value); var multiplayerUsers = new List(); - foreach (var user in users) + foreach (int user in users) { SpectatorClient.StartPlay(user, Beatmap.Value.BeatmapInfo.OnlineBeatmapID ?? 0); var roomUser = OnlinePlayDependencies.Client.AddUser(new User { Id = user }, true); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerLoungeSubScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerLoungeSubScreen.cs index b7da31a2b5..de3df754a2 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerLoungeSubScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerLoungeSubScreen.cs @@ -18,7 +18,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { public class TestSceneMultiplayerLoungeSubScreen : OnlinePlayTestScene { - protected new TestRequestHandlingRoomManager RoomManager => (TestRequestHandlingRoomManager)base.RoomManager; + protected new TestRoomManager RoomManager => (TestRoomManager)base.RoomManager; private LoungeSubScreen loungeScreen; diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchFooter.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchFooter.cs index 44a8d7b439..6536ef2ca1 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchFooter.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchFooter.cs @@ -4,7 +4,6 @@ using NUnit.Framework; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Game.Online.Rooms; using osu.Game.Screens.OnlinePlay.Multiplayer.Match; namespace osu.Game.Tests.Visual.Multiplayer @@ -14,8 +13,6 @@ namespace osu.Game.Tests.Visual.Multiplayer [SetUp] public new void Setup() => Schedule(() => { - SelectedRoom.Value = new Room(); - Child = new Container { Anchor = Anchor.Centre, diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs index d1980b03c7..2549681519 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs @@ -45,11 +45,11 @@ namespace osu.Game.Tests.Visual.Multiplayer } [Test] - public void TestAddNullUser() + public void TestAddUnresolvedUser() { AddAssert("one unique panel", () => this.ChildrenOfType().Select(p => p.User).Distinct().Count() == 1); - AddStep("add non-resolvable user", () => Client.AddNullUser()); + AddStep("add non-resolvable user", () => Client.TestAddUnresolvedUser()); AddAssert("null user added", () => Client.Room.AsNonNull().Users.Count(u => u.User == null) == 1); AddUntilStep("two unique panels", () => this.ChildrenOfType().Select(p => p.User).Distinct().Count() == 2); @@ -112,7 +112,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddRepeatStep("increment progress", () => { - var progress = this.ChildrenOfType().Single().User.BeatmapAvailability.DownloadProgress ?? 0; + float progress = this.ChildrenOfType().Single().User.BeatmapAvailability.DownloadProgress ?? 0; Client.ChangeBeatmapAvailability(BeatmapAvailability.Downloading(progress + RNG.NextSingle(0.1f))); }, 25); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs index 80217a7726..ad92886bab 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs @@ -6,18 +6,14 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; -using osu.Framework.Graphics; using osu.Framework.Platform; using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Database; -using osu.Game.Online.Multiplayer; using osu.Game.Online.Multiplayer.MatchTypes.TeamVersus; using osu.Game.Online.Rooms; using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; -using osu.Game.Screens; -using osu.Game.Screens.OnlinePlay.Components; using osu.Game.Screens.OnlinePlay.Lounge; using osu.Game.Screens.OnlinePlay.Multiplayer; using osu.Game.Screens.OnlinePlay.Multiplayer.Match; @@ -33,9 +29,9 @@ namespace osu.Game.Tests.Visual.Multiplayer private RulesetStore rulesets; private BeatmapSetInfo importedSet; - private DependenciesScreen dependenciesScreen; - private TestMultiplayer multiplayerScreen; - private TestMultiplayerClient client; + private TestMultiplayerScreenStack multiplayerScreenStack; + + private TestMultiplayerClient client => multiplayerScreenStack.Client; [Cached(typeof(UserLookupCache))] private UserLookupCache lookupCache = new TestUserLookupCache(); @@ -57,24 +53,8 @@ namespace osu.Game.Tests.Visual.Multiplayer importedSet = beatmaps.GetAllUsableBeatmapSetsEnumerable(IncludedDetails.All).First(); }); - AddStep("create multiplayer screen", () => multiplayerScreen = new TestMultiplayer()); - - AddStep("load dependencies", () => - { - client = new TestMultiplayerClient(multiplayerScreen.RoomManager); - - // The screen gets suspended so it stops receiving updates. - Child = client; - - LoadScreen(dependenciesScreen = new DependenciesScreen(client)); - }); - - AddUntilStep("wait for dependencies screen", () => Stack.CurrentScreen is DependenciesScreen); - AddUntilStep("wait for dependencies to start load", () => dependenciesScreen.LoadState > LoadState.NotLoaded); - AddUntilStep("wait for dependencies to load", () => dependenciesScreen.IsLoaded); - - AddStep("load multiplayer", () => LoadScreen(multiplayerScreen)); - AddUntilStep("wait for multiplayer to load", () => multiplayerScreen.IsLoaded); + AddStep("load multiplayer", () => LoadScreen(multiplayerScreenStack = new TestMultiplayerScreenStack())); + AddUntilStep("wait for multiplayer to load", () => multiplayerScreenStack.IsLoaded); AddUntilStep("wait for lounge to load", () => this.ChildrenOfType().FirstOrDefault()?.IsLoaded == true); } @@ -120,7 +100,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("press button", () => { - InputManager.MoveMouseTo(multiplayerScreen.ChildrenOfType().First()); + InputManager.MoveMouseTo(multiplayerScreenStack.ChildrenOfType().First()); InputManager.Click(MouseButton.Left); }); AddAssert("user on team 1", () => (client.Room?.Users.FirstOrDefault()?.MatchState as TeamVersusUserState)?.TeamID == 1); @@ -154,7 +134,7 @@ namespace osu.Game.Tests.Visual.Multiplayer private void createRoom(Func room) { - AddStep("open room", () => multiplayerScreen.ChildrenOfType().Single().Open(room())); + AddStep("open room", () => multiplayerScreenStack.ChildrenOfType().Single().Open(room())); AddUntilStep("wait for room open", () => this.ChildrenOfType().FirstOrDefault()?.IsLoaded == true); AddWaitStep("wait for transition", 2); @@ -167,26 +147,5 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("wait for join", () => client.Room != null); } - - /// - /// Used for the sole purpose of adding as a resolvable dependency. - /// - private class DependenciesScreen : OsuScreen - { - [Cached(typeof(MultiplayerClient))] - public readonly TestMultiplayerClient Client; - - public DependenciesScreen(TestMultiplayerClient client) - { - Client = client; - } - } - - private class TestMultiplayer : Screens.OnlinePlay.Multiplayer.Multiplayer - { - public new TestRequestHandlingMultiplayerRoomManager RoomManager { get; private set; } - - protected override RoomManager CreateRoomManager() => RoomManager = new TestRequestHandlingMultiplayerRoomManager(); - } } } diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs index ce437e7299..5d4594c415 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs @@ -9,21 +9,18 @@ using osu.Framework.Screens; using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Graphics.UserInterface; -using osu.Game.Online.Multiplayer; using osu.Game.Overlays; using osu.Game.Overlays.Mods; using osu.Game.Overlays.Toolbar; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Screens.Menu; -using osu.Game.Screens.OnlinePlay.Components; using osu.Game.Screens.OnlinePlay.Lounge; using osu.Game.Screens.Play; using osu.Game.Screens.Ranking; using osu.Game.Screens.Select; using osu.Game.Screens.Select.Options; using osu.Game.Tests.Beatmaps.IO; -using osu.Game.Tests.Visual.Multiplayer; using osuTK; using osuTK.Input; @@ -333,12 +330,12 @@ namespace osu.Game.Tests.Visual.Navigation [Test] public void TestPushMatchSubScreenAndPressBackButtonImmediately() { - TestMultiplayer multiplayer = null; + TestMultiplayerScreenStack multiplayerScreenStack = null; - PushAndConfirm(() => multiplayer = new TestMultiplayer()); + PushAndConfirm(() => multiplayerScreenStack = new TestMultiplayerScreenStack()); - AddUntilStep("wait for lounge", () => multiplayer.ChildrenOfType().SingleOrDefault()?.IsLoaded == true); - AddStep("open room", () => multiplayer.ChildrenOfType().Single().Open()); + AddUntilStep("wait for lounge", () => multiplayerScreenStack.ChildrenOfType().SingleOrDefault()?.IsLoaded == true); + AddStep("open room", () => multiplayerScreenStack.ChildrenOfType().Single().Open()); AddStep("press back button", () => Game.ChildrenOfType().First().Action()); AddWaitStep("wait two frames", 2); } @@ -453,18 +450,5 @@ namespace osu.Game.Tests.Visual.Navigation protected override bool DisplayStableImportPrompt => false; } - - private class TestMultiplayer : Screens.OnlinePlay.Multiplayer.Multiplayer - { - [Cached(typeof(MultiplayerClient))] - public readonly TestMultiplayerClient Client; - - public TestMultiplayer() - { - Client = new TestMultiplayerClient((TestRequestHandlingMultiplayerRoomManager)RoomManager); - } - - protected override RoomManager CreateRoomManager() => new TestRequestHandlingMultiplayerRoomManager(); - } } } diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapAvailability.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapAvailability.cs index 6f9744ca73..176e0592ef 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneBeatmapAvailability.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapAvailability.cs @@ -21,15 +21,12 @@ namespace osu.Game.Tests.Visual.Online [Test] public void TestUndownloadableWithLink() { - AddStep("set undownloadable beatmapset with link", () => container.BeatmapSet = new BeatmapSetInfo + AddStep("set undownloadable beatmapset with link", () => container.BeatmapSet = new APIBeatmapSet { - OnlineInfo = new APIBeatmapSet + Availability = new BeatmapSetOnlineAvailability { - Availability = new BeatmapSetOnlineAvailability - { - DownloadDisabled = true, - ExternalLink = @"https://osu.ppy.sh", - }, + DownloadDisabled = true, + ExternalLink = @"https://osu.ppy.sh", }, }); @@ -39,14 +36,11 @@ namespace osu.Game.Tests.Visual.Online [Test] public void TestUndownloadableNoLink() { - AddStep("set undownloadable beatmapset without link", () => container.BeatmapSet = new BeatmapSetInfo + AddStep("set undownloadable beatmapset without link", () => container.BeatmapSet = new APIBeatmapSet { - OnlineInfo = new APIBeatmapSet + Availability = new BeatmapSetOnlineAvailability { - Availability = new BeatmapSetOnlineAvailability - { - DownloadDisabled = true, - }, + DownloadDisabled = true, }, }); @@ -56,15 +50,12 @@ namespace osu.Game.Tests.Visual.Online [Test] public void TestPartsRemovedWithLink() { - AddStep("set parts-removed beatmapset with link", () => container.BeatmapSet = new BeatmapSetInfo + AddStep("set parts-removed beatmapset with link", () => container.BeatmapSet = new APIBeatmapSet { - OnlineInfo = new APIBeatmapSet + Availability = new BeatmapSetOnlineAvailability { - Availability = new BeatmapSetOnlineAvailability - { - DownloadDisabled = false, - ExternalLink = @"https://osu.ppy.sh", - }, + DownloadDisabled = false, + ExternalLink = @"https://osu.ppy.sh", }, }); @@ -74,14 +65,11 @@ namespace osu.Game.Tests.Visual.Online [Test] public void TestNormal() { - AddStep("set normal beatmapset", () => container.BeatmapSet = new BeatmapSetInfo + AddStep("set normal beatmapset", () => container.BeatmapSet = new APIBeatmapSet { - OnlineInfo = new APIBeatmapSet + Availability = new BeatmapSetOnlineAvailability { - Availability = new BeatmapSetOnlineAvailability - { - DownloadDisabled = false, - }, + DownloadDisabled = false, }, }); diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs index 963809ebe1..c7a065fdd7 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs @@ -7,14 +7,12 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Graphics.Containers; using osu.Framework.Testing; -using osu.Game.Beatmaps; using osu.Game.Graphics.UserInterface; using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays; using osu.Game.Overlays.BeatmapListing; -using osu.Game.Rulesets; using osu.Game.Scoring; using osu.Game.Users; using osuTK.Input; @@ -92,7 +90,7 @@ namespace osu.Game.Tests.Visual.Online { AddAssert("is visible", () => overlay.State.Value == Visibility.Visible); - AddStep("show many results", () => fetchFor(Enumerable.Repeat(CreateBeatmap(Ruleset.Value).BeatmapInfo.BeatmapSet, 100).ToArray())); + AddStep("show many results", () => fetchFor(Enumerable.Repeat(CreateAPIBeatmapSet(Ruleset.Value), 100).ToArray())); AddUntilStep("placeholder hidden", () => !overlay.ChildrenOfType().Any(d => d.IsPresent)); @@ -114,7 +112,7 @@ namespace osu.Game.Tests.Visual.Online AddStep("fetch for 0 beatmaps", () => fetchFor()); AddUntilStep("placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); - AddStep("fetch for 1 beatmap", () => fetchFor(CreateBeatmap(Ruleset.Value).BeatmapInfo.BeatmapSet)); + AddStep("fetch for 1 beatmap", () => fetchFor(CreateAPIBeatmapSet(Ruleset.Value))); AddUntilStep("placeholder hidden", () => !overlay.ChildrenOfType().Any(d => d.IsPresent)); AddStep("fetch for 0 beatmaps", () => fetchFor()); @@ -188,7 +186,7 @@ namespace osu.Game.Tests.Visual.Online [Test] public void TestUserWithoutSupporterUsesSupporterOnlyFiltersWithResults() { - AddStep("fetch for 1 beatmap", () => fetchFor(CreateBeatmap(Ruleset.Value).BeatmapInfo.BeatmapSet)); + AddStep("fetch for 1 beatmap", () => fetchFor(CreateAPIBeatmapSet(Ruleset.Value))); AddStep("set dummy as non-supporter", () => ((DummyAPIAccess)API).LocalUser.Value.IsSupporter = false); // only Rank Achieved filter @@ -218,7 +216,7 @@ namespace osu.Game.Tests.Visual.Online [Test] public void TestUserWithSupporterUsesSupporterOnlyFiltersWithResults() { - AddStep("fetch for 1 beatmap", () => fetchFor(CreateBeatmap(Ruleset.Value).BeatmapInfo.BeatmapSet)); + AddStep("fetch for 1 beatmap", () => fetchFor(CreateAPIBeatmapSet(Ruleset.Value))); AddStep("set dummy as supporter", () => ((DummyAPIAccess)API).LocalUser.Value.IsSupporter = true); // only Rank Achieved filter @@ -247,10 +245,10 @@ namespace osu.Game.Tests.Visual.Online private static int searchCount; - private void fetchFor(params BeatmapSetInfo[] beatmaps) + private void fetchFor(params APIBeatmapSet[] beatmaps) { setsForResponse.Clear(); - setsForResponse.AddRange(beatmaps.Select(b => new TestAPIBeatmapSet(b))); + setsForResponse.AddRange(beatmaps); // trigger arbitrary change for fetching. searchControl.Query.Value = $"search {searchCount++}"; @@ -286,17 +284,5 @@ namespace osu.Game.Tests.Visual.Online !overlay.ChildrenOfType().Any(d => d.IsPresent) && !overlay.ChildrenOfType().Any(d => d.IsPresent)); } - - private class TestAPIBeatmapSet : APIBeatmapSet - { - private readonly BeatmapSetInfo beatmapSet; - - public TestAPIBeatmapSet(BeatmapSetInfo beatmapSet) - { - this.beatmapSet = beatmapSet; - } - - public override BeatmapSetInfo ToBeatmapSet(RulesetStore rulesets) => beatmapSet; - } } } diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapRulesetSelector.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapRulesetSelector.cs index eb34187cd6..b880633559 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneBeatmapRulesetSelector.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapRulesetSelector.cs @@ -1,15 +1,15 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Collections.Generic; +using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics.UserInterface; -using osu.Game.Beatmaps; +using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays; using osu.Game.Overlays.BeatmapSet; using osu.Game.Rulesets; -using System.Collections.Generic; -using System.Linq; namespace osu.Game.Tests.Visual.Online { @@ -35,9 +35,9 @@ namespace osu.Game.Tests.Visual.Online AddStep("load multiple rulesets beatmapset", () => { - selector.BeatmapSet = new BeatmapSetInfo + selector.BeatmapSet = new APIBeatmapSet { - Beatmaps = enabledRulesets.Select(r => new BeatmapInfo { Ruleset = r }).ToList() + Beatmaps = enabledRulesets.Select(r => new APIBeatmap { RulesetID = r.OnlineID }).ToList() }; }); @@ -53,13 +53,13 @@ namespace osu.Game.Tests.Visual.Online AddStep("load single ruleset beatmapset", () => { - selector.BeatmapSet = new BeatmapSetInfo + selector.BeatmapSet = new APIBeatmapSet { - Beatmaps = new List + Beatmaps = new List { - new BeatmapInfo + new APIBeatmap { - Ruleset = enabledRuleset + RulesetID = enabledRuleset.OnlineID } } }; @@ -71,9 +71,9 @@ namespace osu.Game.Tests.Visual.Online [Test] public void TestEmptyBeatmapSet() { - AddStep("load empty beatmapset", () => selector.BeatmapSet = new BeatmapSetInfo + AddStep("load empty beatmapset", () => selector.BeatmapSet = new APIBeatmapSet { - Beatmaps = new List() + Beatmaps = new List() }); AddAssert("no ruleset selected", () => selector.SelectedTab == null); diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs index ef89a86e79..bae9af1897 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs @@ -49,57 +49,45 @@ namespace osu.Game.Tests.Visual.Online { AddStep(@"show first", () => { - overlay.ShowBeatmapSet(new BeatmapSetInfo + overlay.ShowBeatmapSet(new APIBeatmapSet { - OnlineBeatmapSetID = 1235, - Metadata = new BeatmapMetadata + OnlineID = 1235, + Title = @"an awesome beatmap", + Artist = @"naru narusegawa", + Source = @"hinata sou", + Tags = @"test tag tag more tag", + Author = new User { - Title = @"an awesome beatmap", - Artist = @"naru narusegawa", - Source = @"hinata sou", - Tags = @"test tag tag more tag", - Author = new User - { - Username = @"BanchoBot", - Id = 3, - }, + Username = @"BanchoBot", + Id = 3, }, - OnlineInfo = new APIBeatmapSet + Preview = @"https://b.ppy.sh/preview/12345.mp3", + PlayCount = 123, + FavouriteCount = 456, + Submitted = DateTime.Now, + Ranked = DateTime.Now, + BPM = 111, + HasVideo = true, + Ratings = Enumerable.Range(0, 11).ToArray(), + HasStoryboard = true, + Covers = new BeatmapSetOnlineCovers(), + Beatmaps = new List { - Preview = @"https://b.ppy.sh/preview/12345.mp3", - PlayCount = 123, - FavouriteCount = 456, - Submitted = DateTime.Now, - Ranked = DateTime.Now, - BPM = 111, - HasVideo = true, - HasStoryboard = true, - Covers = new BeatmapSetOnlineCovers(), - }, - Metrics = new BeatmapSetMetrics { Ratings = Enumerable.Range(0, 11).ToArray() }, - Beatmaps = new List - { - new BeatmapInfo + new APIBeatmap { - StarDifficulty = 9.99, - Version = @"TEST", + StarRating = 9.99, + DifficultyName = @"TEST", Length = 456000, - Ruleset = rulesets.GetRuleset(3), - BaseDifficulty = new BeatmapDifficulty - { - CircleSize = 1, - DrainRate = 2.3f, - OverallDifficulty = 4.5f, - ApproachRate = 6, - }, - OnlineInfo = new BeatmapOnlineInfo - { - CircleCount = 111, - SliderCount = 12, - PlayCount = 222, - PassCount = 21, - }, - Metrics = new BeatmapMetrics + RulesetID = 3, + CircleSize = 1, + DrainRate = 2.3f, + OverallDifficulty = 4.5f, + ApproachRate = 6, + CircleCount = 111, + SliderCount = 12, + PlayCount = 222, + PassCount = 21, + FailTimes = new APIFailTimes { Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6).ToArray(), Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6).ToArray(), @@ -120,71 +108,15 @@ namespace osu.Game.Tests.Visual.Online { AddStep(@"show undownloadable", () => { - overlay.ShowBeatmapSet(new BeatmapSetInfo + var set = getBeatmapSet(); + + set.Availability = new BeatmapSetOnlineAvailability { - OnlineBeatmapSetID = 1234, - Metadata = new BeatmapMetadata - { - Title = @"undownloadable beatmap", - Artist = @"no one", - Source = @"some source", - Tags = @"another test tag tag more test tags", - Author = new User - { - Username = @"BanchoBot", - Id = 3, - }, - }, - OnlineInfo = new APIBeatmapSet - { - Availability = new BeatmapSetOnlineAvailability - { - DownloadDisabled = true, - ExternalLink = "https://osu.ppy.sh", - }, - Preview = @"https://b.ppy.sh/preview/1234.mp3", - PlayCount = 123, - FavouriteCount = 456, - Submitted = DateTime.Now, - Ranked = DateTime.Now, - BPM = 111, - HasVideo = true, - HasStoryboard = true, - Covers = new BeatmapSetOnlineCovers(), - Language = new BeatmapSetOnlineLanguage { Id = 3, Name = "English" }, - Genre = new BeatmapSetOnlineGenre { Id = 4, Name = "Rock" }, - }, - Metrics = new BeatmapSetMetrics { Ratings = Enumerable.Range(0, 11).ToArray() }, - Beatmaps = new List - { - new BeatmapInfo - { - StarDifficulty = 5.67, - Version = @"ANOTHER TEST", - Length = 123000, - Ruleset = rulesets.GetRuleset(1), - BaseDifficulty = new BeatmapDifficulty - { - CircleSize = 9, - DrainRate = 8, - OverallDifficulty = 7, - ApproachRate = 6, - }, - OnlineInfo = new BeatmapOnlineInfo - { - CircleCount = 123, - SliderCount = 45, - PlayCount = 567, - PassCount = 89, - }, - Metrics = new BeatmapMetrics - { - Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6).ToArray(), - Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6).ToArray(), - }, - }, - }, - }); + DownloadDisabled = true, + ExternalLink = "https://osu.ppy.sh", + }; + + overlay.ShowBeatmapSet(set); }); downloadAssert(false); @@ -195,17 +127,15 @@ namespace osu.Game.Tests.Visual.Online { AddStep("show multiple rulesets beatmap", () => { - var beatmaps = new List(); + var beatmaps = new List(); foreach (var ruleset in rulesets.AvailableRulesets.Skip(1)) { - beatmaps.Add(new BeatmapInfo + beatmaps.Add(new APIBeatmap { - Version = ruleset.Name, - Ruleset = ruleset, - BaseDifficulty = new BeatmapDifficulty(), - OnlineInfo = new BeatmapOnlineInfo(), - Metrics = new BeatmapMetrics + DifficultyName = ruleset.Name, + RulesetID = ruleset.OnlineID, + FailTimes = new APIFailTimes { Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6).ToArray(), Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6).ToArray(), @@ -213,28 +143,14 @@ namespace osu.Game.Tests.Visual.Online }); } - overlay.ShowBeatmapSet(new BeatmapSetInfo - { - Metadata = new BeatmapMetadata - { - Title = @"multiple rulesets beatmap", - Artist = @"none", - Author = new User - { - Username = "BanchoBot", - Id = 3, - } - }, - OnlineInfo = new APIBeatmapSet - { - Covers = new BeatmapSetOnlineCovers(), - }, - Metrics = new BeatmapSetMetrics { Ratings = Enumerable.Range(0, 11).ToArray() }, - Beatmaps = beatmaps - }); + var set = getBeatmapSet(); + + set.Beatmaps = beatmaps; + + overlay.ShowBeatmapSet(set); }); - AddAssert("shown beatmaps of current ruleset", () => overlay.Header.HeaderContent.Picker.Difficulties.All(b => b.BeatmapInfo.Ruleset.Equals(overlay.Header.RulesetSelector.Current.Value))); + AddAssert("shown beatmaps of current ruleset", () => overlay.Header.HeaderContent.Picker.Difficulties.All(b => b.Beatmap.Ruleset.OnlineID == overlay.Header.RulesetSelector.Current.Value.OnlineID)); AddAssert("left-most beatmap selected", () => overlay.Header.HeaderContent.Picker.Difficulties.First().State == BeatmapPicker.DifficultySelectorState.Selected); } @@ -244,7 +160,7 @@ namespace osu.Game.Tests.Visual.Online AddStep("show explicit map", () => { var beatmapSet = getBeatmapSet(); - beatmapSet.OnlineInfo.HasExplicitContent = true; + beatmapSet.HasExplicitContent = true; overlay.ShowBeatmapSet(beatmapSet); }); } @@ -255,7 +171,7 @@ namespace osu.Game.Tests.Visual.Online AddStep("show featured map", () => { var beatmapSet = getBeatmapSet(); - beatmapSet.OnlineInfo.TrackId = 1; + beatmapSet.TrackId = 1; overlay.ShowBeatmapSet(beatmapSet); }); } @@ -272,24 +188,22 @@ namespace osu.Game.Tests.Visual.Online AddStep(@"show without reload", overlay.Show); } - private BeatmapSetInfo createManyDifficultiesBeatmapSet() + private APIBeatmapSet createManyDifficultiesBeatmapSet() { - var beatmaps = new List(); + var set = getBeatmapSet(); + + var beatmaps = new List(); for (int i = 1; i < 41; i++) { - beatmaps.Add(new BeatmapInfo + beatmaps.Add(new APIBeatmap { - OnlineBeatmapID = i * 10, - Version = $"Test #{i}", - Ruleset = Ruleset.Value, - StarDifficulty = 2 + i * 0.1, - BaseDifficulty = new BeatmapDifficulty - { - OverallDifficulty = 3.5f, - }, - OnlineInfo = new BeatmapOnlineInfo(), - Metrics = new BeatmapMetrics + OnlineID = i * 10, + DifficultyName = $"Test #{i}", + RulesetID = Ruleset.Value.ID ?? -1, + StarRating = 2 + i * 0.1, + OverallDifficulty = 3.5f, + FailTimes = new APIFailTimes { Fails = Enumerable.Range(1, 100).Select(j => j % 12 - 6).ToArray(), Retries = Enumerable.Range(-2, 100).Select(j => j % 12 - 6).ToArray(), @@ -297,36 +211,18 @@ namespace osu.Game.Tests.Visual.Online }); } - return new BeatmapSetInfo - { - OnlineBeatmapSetID = 123, - Metadata = new BeatmapMetadata - { - Title = @"many difficulties beatmap", - Artist = @"none", - Author = new User - { - Username = @"BanchoBot", - Id = 3, - }, - }, - OnlineInfo = new APIBeatmapSet - { - Preview = @"https://b.ppy.sh/preview/123.mp3", - HasVideo = true, - HasStoryboard = true, - Covers = new BeatmapSetOnlineCovers(), - }, - Metrics = new BeatmapSetMetrics { Ratings = Enumerable.Range(0, 11).ToArray() }, - Beatmaps = beatmaps, - }; + set.Beatmaps = beatmaps; + + return set; } - private BeatmapSetInfo getBeatmapSet() + private APIBeatmapSet getBeatmapSet() { - var beatmapSet = CreateBeatmap(Ruleset.Value).BeatmapInfo.BeatmapSet; + var beatmapSet = CreateAPIBeatmapSet(Ruleset.Value); + // Make sure the overlay is reloaded (see `BeatmapSetInfo.Equals`). - beatmapSet.OnlineBeatmapSetID = nextBeatmapSetId++; + beatmapSet.OnlineID = nextBeatmapSetId++; + return beatmapSet; } diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlayDetails.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlayDetails.cs index c15c9f44e4..491a2d5d90 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlayDetails.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlayDetails.cs @@ -39,29 +39,26 @@ namespace osu.Game.Tests.Visual.Online var secondSet = createSet(); AddStep("set first set", () => details.BeatmapSet = firstSet); - AddAssert("ratings set", () => details.Ratings.Metrics == firstSet.Metrics); + AddAssert("ratings set", () => details.Ratings.Ratings == firstSet.Ratings); AddStep("set second set", () => details.BeatmapSet = secondSet); - AddAssert("ratings set", () => details.Ratings.Metrics == secondSet.Metrics); + AddAssert("ratings set", () => details.Ratings.Ratings == secondSet.Ratings); - static BeatmapSetInfo createSet() => new BeatmapSetInfo + static APIBeatmapSet createSet() => new APIBeatmapSet { - Metrics = new BeatmapSetMetrics { Ratings = Enumerable.Range(0, 11).Select(_ => RNG.Next(10)).ToArray() }, - Beatmaps = new List + Beatmaps = new List { - new BeatmapInfo + new APIBeatmap { - Metrics = new BeatmapMetrics + FailTimes = new APIFailTimes { Fails = Enumerable.Range(1, 100).Select(_ => RNG.Next(10)).ToArray(), Retries = Enumerable.Range(-2, 100).Select(_ => RNG.Next(10)).ToArray(), }, } }, - OnlineInfo = new APIBeatmapSet - { - Status = BeatmapSetOnlineStatus.Ranked - } + Ratings = Enumerable.Range(0, 11).Select(_ => RNG.Next(10)).ToArray(), + Status = BeatmapSetOnlineStatus.Ranked }; } diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlaySuccessRate.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlaySuccessRate.cs index fe8e33f783..be3fc7aff9 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlaySuccessRate.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlaySuccessRate.cs @@ -11,6 +11,7 @@ using osu.Framework.Testing; using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Graphics.UserInterface; +using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays; using osu.Game.Overlays.BeatmapSet; using osu.Game.Screens.Select.Details; @@ -58,15 +59,15 @@ namespace osu.Game.Tests.Visual.Online var firstBeatmap = createBeatmap(); var secondBeatmap = createBeatmap(); - AddStep("set first set", () => successRate.BeatmapInfo = firstBeatmap); - AddAssert("ratings set", () => successRate.Graph.Metrics == firstBeatmap.Metrics); + AddStep("set first set", () => successRate.Beatmap = firstBeatmap); + AddAssert("ratings set", () => successRate.Graph.FailTimes == firstBeatmap.FailTimes); - AddStep("set second set", () => successRate.BeatmapInfo = secondBeatmap); - AddAssert("ratings set", () => successRate.Graph.Metrics == secondBeatmap.Metrics); + AddStep("set second set", () => successRate.Beatmap = secondBeatmap); + AddAssert("ratings set", () => successRate.Graph.FailTimes == secondBeatmap.FailTimes); - static BeatmapInfo createBeatmap() => new BeatmapInfo + static APIBeatmap createBeatmap() => new APIBeatmap { - Metrics = new BeatmapMetrics + FailTimes = new APIFailTimes { Fails = Enumerable.Range(1, 100).Select(_ => RNG.Next(10)).ToArray(), Retries = Enumerable.Range(-2, 100).Select(_ => RNG.Next(10)).ToArray(), @@ -77,27 +78,26 @@ namespace osu.Game.Tests.Visual.Online [Test] public void TestOnlyFailMetrics() { - AddStep("set beatmap", () => successRate.BeatmapInfo = new BeatmapInfo + AddStep("set beatmap", () => successRate.Beatmap = new APIBeatmap { - Metrics = new BeatmapMetrics + FailTimes = new APIFailTimes { Fails = Enumerable.Range(1, 100).ToArray(), } }); - AddAssert("graph max values correct", - () => successRate.ChildrenOfType().All(graph => graph.MaxValue == 100)); + + AddAssert("graph max values correct", () => successRate.ChildrenOfType().All(graph => graph.MaxValue == 100)); } [Test] public void TestEmptyMetrics() { - AddStep("set beatmap", () => successRate.BeatmapInfo = new BeatmapInfo + AddStep("set beatmap", () => successRate.Beatmap = new APIBeatmap { - Metrics = new BeatmapMetrics() + FailTimes = new APIFailTimes() }); - AddAssert("graph max values correct", - () => successRate.ChildrenOfType().All(graph => graph.MaxValue == 0)); + AddAssert("graph max values correct", () => successRate.ChildrenOfType().All(graph => graph.MaxValue == 0)); } private class GraphExposingSuccessRate : SuccessRate diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs index 9562b41363..99c3b398ab 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Net; using JetBrains.Annotations; using NUnit.Framework; using osu.Framework.Allocation; @@ -105,7 +106,7 @@ namespace osu.Game.Tests.Visual.Online } else { - getUser.TriggerFailure(new Exception()); + getUser.TriggerFailure(new WebException()); } return true; @@ -189,8 +190,8 @@ namespace osu.Game.Tests.Visual.Online for (int zeroBasedIndex = 0; zeroBasedIndex < 10; ++zeroBasedIndex) { - var oneBasedIndex = zeroBasedIndex + 1; - var targetNumberKey = oneBasedIndex % 10; + int oneBasedIndex = zeroBasedIndex + 1; + int targetNumberKey = oneBasedIndex % 10; var targetChannel = channels[zeroBasedIndex]; AddStep($"Press Alt+{targetNumberKey}", () => pressChannelHotkey(targetNumberKey)); AddAssert($"Channel #{oneBasedIndex} is selected", () => currentChannel == targetChannel); diff --git a/osu.Game.Tests/Visual/Online/TestSceneDirectDownloadButton.cs b/osu.Game.Tests/Visual/Online/TestSceneDirectDownloadButton.cs index bb7fcc2fce..f0cf88840a 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneDirectDownloadButton.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneDirectDownloadButton.cs @@ -69,24 +69,7 @@ namespace osu.Game.Tests.Visual.Online AddAssert($"button {(enabled ? "enabled" : "disabled")}", () => downloadButton.DownloadEnabled == enabled); } - private BeatmapSetInfo createSoleily() - { - return new BeatmapSetInfo - { - ID = 1, - OnlineBeatmapSetID = 241526, - OnlineInfo = new APIBeatmapSet - { - Availability = new BeatmapSetOnlineAvailability - { - DownloadDisabled = false, - ExternalLink = string.Empty, - }, - }, - }; - } - - private void createButtonWithBeatmap(BeatmapSetInfo beatmap) + private void createButtonWithBeatmap(IBeatmapSetInfo beatmap) { AddStep("create button", () => { @@ -112,32 +95,47 @@ namespace osu.Game.Tests.Visual.Online }); } - private BeatmapSetInfo getDownloadableBeatmapSet() + private IBeatmapSetInfo createSoleily() { - var normal = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo).BeatmapSetInfo; - normal.OnlineInfo.HasVideo = true; - normal.OnlineInfo.HasStoryboard = true; - - return normal; + return new APIBeatmapSet + { + OnlineID = 241526, + Availability = new BeatmapSetOnlineAvailability + { + DownloadDisabled = false, + ExternalLink = string.Empty, + }, + }; } - private BeatmapSetInfo getUndownloadableBeatmapSet() + private IBeatmapSetInfo getDownloadableBeatmapSet() { - var beatmap = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo).BeatmapSetInfo; - beatmap.Metadata.Artist = "test"; - beatmap.Metadata.Title = "undownloadable"; - beatmap.Metadata.AuthorString = "test"; + var apiBeatmapSet = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo).BeatmapSetInfo.OnlineInfo; - beatmap.OnlineInfo.HasVideo = true; - beatmap.OnlineInfo.HasStoryboard = true; + apiBeatmapSet.HasVideo = true; + apiBeatmapSet.HasStoryboard = true; - beatmap.OnlineInfo.Availability = new BeatmapSetOnlineAvailability + return apiBeatmapSet; + } + + private IBeatmapSetInfo getUndownloadableBeatmapSet() + { + var apiBeatmapSet = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo).BeatmapSetInfo.OnlineInfo; + + apiBeatmapSet.Artist = "test"; + apiBeatmapSet.Title = "undownloadable"; + apiBeatmapSet.AuthorString = "test"; + + apiBeatmapSet.HasVideo = true; + apiBeatmapSet.HasStoryboard = true; + + apiBeatmapSet.Availability = new BeatmapSetOnlineAvailability { DownloadDisabled = true, ExternalLink = "http://osu.ppy.sh", }; - return beatmap; + return apiBeatmapSet; } private class TestDownloadButton : BeatmapPanelDownloadButton @@ -146,7 +144,7 @@ namespace osu.Game.Tests.Visual.Online public DownloadState DownloadState => State.Value; - public TestDownloadButton(BeatmapSetInfo beatmapSet) + public TestDownloadButton(IBeatmapSetInfo beatmapSet) : base(beatmapSet) { } diff --git a/osu.Game.Tests/Visual/Online/TestSceneDirectPanel.cs b/osu.Game.Tests/Visual/Online/TestSceneDirectPanel.cs index 6caca2a67c..9a3d998966 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneDirectPanel.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneDirectPanel.cs @@ -18,104 +18,25 @@ namespace osu.Game.Tests.Visual.Online [Cached(typeof(IPreviewTrackOwner))] public class TestSceneDirectPanel : OsuTestScene, IPreviewTrackOwner { - private BeatmapSetInfo getUndownloadableBeatmapSet() => new BeatmapSetInfo - { - OnlineBeatmapSetID = 123, - Metadata = new BeatmapMetadata - { - Title = "undownloadable beatmap", - Artist = "test", - Source = "more tests", - Author = new User - { - Username = "BanchoBot", - Id = 3, - }, - }, - OnlineInfo = new APIBeatmapSet - { - Availability = new BeatmapSetOnlineAvailability - { - DownloadDisabled = true, - }, - Preview = @"https://b.ppy.sh/preview/12345.mp3", - PlayCount = 123, - FavouriteCount = 456, - BPM = 111, - HasVideo = true, - HasStoryboard = true, - Covers = new BeatmapSetOnlineCovers(), - }, - Beatmaps = new List - { - new BeatmapInfo - { - Ruleset = Ruleset.Value, - Version = "Test", - StarDifficulty = 6.42, - } - } - }; - - private BeatmapSetInfo getManyDifficultiesBeatmapSet(RulesetStore rulesets) - { - var beatmaps = new List(); - - for (int i = 0; i < 100; i++) - { - beatmaps.Add(new BeatmapInfo - { - Ruleset = rulesets.GetRuleset(i % 4), - StarDifficulty = 2 + i % 4 * 2, - BaseDifficulty = new BeatmapDifficulty - { - OverallDifficulty = 3.5f, - } - }); - } - - return new BeatmapSetInfo - { - OnlineBeatmapSetID = 1, - Metadata = new BeatmapMetadata - { - Title = "many difficulties beatmap", - Artist = "test", - Author = new User - { - Username = "BanchoBot", - Id = 3, - } - }, - OnlineInfo = new APIBeatmapSet - { - HasVideo = true, - HasStoryboard = true, - Covers = new BeatmapSetOnlineCovers(), - }, - Beatmaps = beatmaps, - }; - } - [BackgroundDependencyLoader] private void load(RulesetStore rulesets) { var normal = getBeatmapSet(); - normal.OnlineInfo.HasVideo = true; - normal.OnlineInfo.HasStoryboard = true; + normal.HasVideo = true; + normal.HasStoryboard = true; var undownloadable = getUndownloadableBeatmapSet(); - var manyDifficulties = getManyDifficultiesBeatmapSet(rulesets); + var manyDifficulties = getManyDifficultiesBeatmapSet(); var explicitMap = getBeatmapSet(); - explicitMap.OnlineInfo.HasExplicitContent = true; + explicitMap.HasExplicitContent = true; var featuredMap = getBeatmapSet(); - featuredMap.OnlineInfo.TrackId = 1; + featuredMap.TrackId = 1; var explicitFeaturedMap = getBeatmapSet(); - explicitFeaturedMap.OnlineInfo.HasExplicitContent = true; - explicitFeaturedMap.OnlineInfo.TrackId = 2; + explicitFeaturedMap.HasExplicitContent = true; + explicitFeaturedMap.TrackId = 2; Child = new BasicScrollContainer { @@ -145,7 +66,72 @@ namespace osu.Game.Tests.Visual.Online }, }; - BeatmapSetInfo getBeatmapSet() => CreateBeatmap(Ruleset.Value).BeatmapInfo.BeatmapSet; + APIBeatmapSet getBeatmapSet() => CreateAPIBeatmapSet(Ruleset.Value); + + APIBeatmapSet getUndownloadableBeatmapSet() => new APIBeatmapSet + { + OnlineID = 123, + Title = "undownloadable beatmap", + Artist = "test", + Source = "more tests", + Author = new User + { + Username = "BanchoBot", + Id = 3, + }, + Availability = new BeatmapSetOnlineAvailability + { + DownloadDisabled = true, + }, + Preview = @"https://b.ppy.sh/preview/12345.mp3", + PlayCount = 123, + FavouriteCount = 456, + BPM = 111, + HasVideo = true, + HasStoryboard = true, + Covers = new BeatmapSetOnlineCovers(), + Beatmaps = new List + { + new APIBeatmap + { + RulesetID = Ruleset.Value.ID ?? 0, + DifficultyName = "Test", + StarRating = 6.42, + } + } + }; + + APIBeatmapSet getManyDifficultiesBeatmapSet() + { + var beatmaps = new List(); + + for (int i = 0; i < 100; i++) + { + beatmaps.Add(new APIBeatmap + { + RulesetID = i % 4, + StarRating = 2 + i % 4 * 2, + OverallDifficulty = 3.5f, + }); + } + + return new APIBeatmapSet + { + OnlineID = 1, + Title = "undownloadable beatmap", + Artist = "test", + Source = "more tests", + Author = new User + { + Username = "BanchoBot", + Id = 3, + }, + HasVideo = true, + HasStoryboard = true, + Covers = new BeatmapSetOnlineCovers(), + Beatmaps = beatmaps, + }; + } } } } diff --git a/osu.Game.Tests/Visual/Online/TestSceneFavouriteButton.cs b/osu.Game.Tests/Visual/Online/TestSceneFavouriteButton.cs index 8e2ee4e28d..87458da578 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneFavouriteButton.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneFavouriteButton.cs @@ -4,7 +4,7 @@ using NUnit.Framework; using osu.Framework.Graphics; using osu.Framework.Testing; -using osu.Game.Beatmaps; +using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays.BeatmapSet.Buttons; using osuTK; @@ -29,7 +29,7 @@ namespace osu.Game.Tests.Visual.Online [Test] public void TestLoggedOutIn() { - AddStep("set valid beatmap", () => favourite.BeatmapSet.Value = new BeatmapSetInfo { OnlineBeatmapSetID = 88 }); + AddStep("set valid beatmap", () => favourite.BeatmapSet.Value = new APIBeatmapSet { OnlineID = 88 }); AddStep("log out", () => API.Logout()); checkEnabled(false); AddStep("log in", () => API.Login("test", "test")); @@ -40,9 +40,9 @@ namespace osu.Game.Tests.Visual.Online public void TestBeatmapChange() { AddStep("log in", () => API.Login("test", "test")); - AddStep("set valid beatmap", () => favourite.BeatmapSet.Value = new BeatmapSetInfo { OnlineBeatmapSetID = 88 }); + AddStep("set valid beatmap", () => favourite.BeatmapSet.Value = new APIBeatmapSet { OnlineID = 88 }); checkEnabled(true); - AddStep("set invalid beatmap", () => favourite.BeatmapSet.Value = new BeatmapSetInfo()); + AddStep("set invalid beatmap", () => favourite.BeatmapSet.Value = new APIBeatmapSet()); checkEnabled(false); } diff --git a/osu.Game.Tests/Visual/Online/TestSceneLeaderboardModSelector.cs b/osu.Game.Tests/Visual/Online/TestSceneLeaderboardModSelector.cs index fc438ce6dd..aa442ded02 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneLeaderboardModSelector.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneLeaderboardModSelector.cs @@ -26,7 +26,8 @@ namespace osu.Game.Tests.Visual.Online { LeaderboardModSelector modSelector; FillFlowContainer selectedMods; - var ruleset = new Bindable(); + + var ruleset = new Bindable(); Add(selectedMods = new FillFlowContainer { diff --git a/osu.Game.Tests/Visual/Online/TestSceneRankGraph.cs b/osu.Game.Tests/Visual/Online/TestSceneRankGraph.cs index 5bf9e31309..f577140e17 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneRankGraph.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneRankGraph.cs @@ -24,10 +24,10 @@ namespace osu.Game.Tests.Visual.Online { RankGraph graph; - var data = new int[89]; - var dataWithZeros = new int[89]; - var smallData = new int[89]; - var edgyData = new int[89]; + int[] data = new int[89]; + int[] dataWithZeros = new int[89]; + int[] smallData = new int[89]; + int[] edgyData = new int[89]; for (int i = 0; i < 89; i++) data[i] = dataWithZeros[i] = (i + 1) * 1000; diff --git a/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs b/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs index 0cb8cc22ec..23899154c4 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs @@ -7,6 +7,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Utils; +using osu.Game.Online.API; using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays; using osu.Game.Overlays.BeatmapSet.Scores; @@ -43,11 +44,11 @@ namespace osu.Game.Tests.Visual.Online } }; - var allScores = new APILegacyScores + var allScores = new APIScoresCollection { - Scores = new List + Scores = new List { - new APILegacyScoreInfo + new APIScoreInfo { User = new User { @@ -61,10 +62,10 @@ namespace osu.Game.Tests.Visual.Online }, Mods = new[] { - new OsuModDoubleTime().Acronym, - new OsuModHidden().Acronym, - new OsuModFlashlight().Acronym, - new OsuModHardRock().Acronym, + new APIMod { Acronym = new OsuModDoubleTime().Acronym }, + new APIMod { Acronym = new OsuModHidden().Acronym }, + new APIMod { Acronym = new OsuModFlashlight().Acronym }, + new APIMod { Acronym = new OsuModHardRock().Acronym }, }, Rank = ScoreRank.XH, PP = 200, @@ -72,7 +73,7 @@ namespace osu.Game.Tests.Visual.Online TotalScore = 1234567890, Accuracy = 1, }, - new APILegacyScoreInfo + new APIScoreInfo { User = new User { @@ -86,9 +87,9 @@ namespace osu.Game.Tests.Visual.Online }, Mods = new[] { - new OsuModDoubleTime().Acronym, - new OsuModHidden().Acronym, - new OsuModFlashlight().Acronym, + new APIMod { Acronym = new OsuModDoubleTime().Acronym }, + new APIMod { Acronym = new OsuModHidden().Acronym }, + new APIMod { Acronym = new OsuModFlashlight().Acronym }, }, Rank = ScoreRank.S, PP = 190, @@ -96,7 +97,7 @@ namespace osu.Game.Tests.Visual.Online TotalScore = 1234789, Accuracy = 0.9997, }, - new APILegacyScoreInfo + new APIScoreInfo { User = new User { @@ -110,8 +111,8 @@ namespace osu.Game.Tests.Visual.Online }, Mods = new[] { - new OsuModDoubleTime().Acronym, - new OsuModHidden().Acronym, + new APIMod { Acronym = new OsuModDoubleTime().Acronym }, + new APIMod { Acronym = new OsuModHidden().Acronym }, }, Rank = ScoreRank.B, PP = 180, @@ -119,7 +120,7 @@ namespace osu.Game.Tests.Visual.Online TotalScore = 12345678, Accuracy = 0.9854, }, - new APILegacyScoreInfo + new APIScoreInfo { User = new User { @@ -133,7 +134,7 @@ namespace osu.Game.Tests.Visual.Online }, Mods = new[] { - new OsuModDoubleTime().Acronym, + new APIMod { Acronym = new OsuModDoubleTime().Acronym }, }, Rank = ScoreRank.C, PP = 170, @@ -141,7 +142,7 @@ namespace osu.Game.Tests.Visual.Online TotalScore = 1234567, Accuracy = 0.8765, }, - new APILegacyScoreInfo + new APIScoreInfo { User = new User { @@ -162,9 +163,9 @@ namespace osu.Game.Tests.Visual.Online } }; - var myBestScore = new APILegacyUserTopScoreInfo + var myBestScore = new APIScoreWithPosition { - Score = new APILegacyScoreInfo + Score = new APIScoreInfo { User = new User { @@ -185,9 +186,9 @@ namespace osu.Game.Tests.Visual.Online Position = 1337, }; - var myBestScoreWithNullPosition = new APILegacyUserTopScoreInfo + var myBestScoreWithNullPosition = new APIScoreWithPosition { - Score = new APILegacyScoreInfo + Score = new APIScoreInfo { User = new User { @@ -208,11 +209,11 @@ namespace osu.Game.Tests.Visual.Online Position = null, }; - var oneScore = new APILegacyScores + var oneScore = new APIScoresCollection { - Scores = new List + Scores = new List { - new APILegacyScoreInfo + new APIScoreInfo { User = new User { @@ -226,10 +227,10 @@ namespace osu.Game.Tests.Visual.Online }, Mods = new[] { - new OsuModDoubleTime().Acronym, - new OsuModHidden().Acronym, - new OsuModFlashlight().Acronym, - new OsuModHardRock().Acronym, + new APIMod { Acronym = new OsuModDoubleTime().Acronym }, + new APIMod { Acronym = new OsuModHidden().Acronym }, + new APIMod { Acronym = new OsuModFlashlight().Acronym }, + new APIMod { Acronym = new OsuModHardRock().Acronym }, }, Rank = ScoreRank.XH, PP = 200, @@ -273,7 +274,7 @@ namespace osu.Game.Tests.Visual.Online private class TestScoresContainer : ScoresContainer { - public new APILegacyScores Scores + public new APIScoresCollection Scores { set => base.Scores = value; } diff --git a/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs b/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs index 165fff99dd..b3b8d75c46 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs @@ -171,7 +171,7 @@ namespace osu.Game.Tests.Visual.Online { var indices = chatDisplay.FillFlow.OfType().Select(ds => chatDisplay.FillFlow.IndexOf(ds)); - foreach (var i in indices) + foreach (int i in indices) { if (i < chatDisplay.FillFlow.Count && chatDisplay.FillFlow[i + 1] is DrawableChannel.DaySeparator) return false; diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserProfileScores.cs b/osu.Game.Tests/Visual/Online/TestSceneUserProfileScores.cs index 513631a221..9c2cc13416 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserProfileScores.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserProfileScores.cs @@ -2,16 +2,16 @@ // See the LICENCE file in the repository root for full licence text. using System; -using osu.Game.Overlays.Profile.Sections.Ranks; -using osu.Framework.Graphics; -using osu.Game.Scoring; -using osu.Framework.Graphics.Containers; -using osuTK; -using osu.Game.Beatmaps; -using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Osu.Mods; -using osu.Game.Overlays; using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests.Responses; +using osu.Game.Overlays; +using osu.Game.Overlays.Profile.Sections.Ranks; +using osu.Game.Rulesets.Osu.Mods; +using osu.Game.Scoring; +using osuTK; namespace osu.Game.Tests.Visual.Online { @@ -19,79 +19,79 @@ namespace osu.Game.Tests.Visual.Online { public TestSceneUserProfileScores() { - var firstScore = new ScoreInfo + var firstScore = new APIScoreInfo { PP = 1047.21, Rank = ScoreRank.SH, - BeatmapInfo = new BeatmapInfo + Beatmap = new APIBeatmap { - Metadata = new BeatmapMetadata + BeatmapSet = new APIBeatmapSet { Title = "JUSTadICE (TV Size)", - Artist = "Oomori Seiko" + Artist = "Oomori Seiko", }, - Version = "Extreme" + DifficultyName = "Extreme" }, Date = DateTimeOffset.Now, - Mods = new Mod[] + Mods = new[] { - new OsuModHidden(), - new OsuModHardRock(), - new OsuModDoubleTime() + new APIMod { Acronym = new OsuModHidden().Acronym }, + new APIMod { Acronym = new OsuModHardRock().Acronym }, + new APIMod { Acronym = new OsuModDoubleTime().Acronym }, }, Accuracy = 0.9813 }; - var secondScore = new ScoreInfo + var secondScore = new APIScoreInfo { PP = 134.32, Rank = ScoreRank.A, - BeatmapInfo = new BeatmapInfo + Beatmap = new APIBeatmap { - Metadata = new BeatmapMetadata + BeatmapSet = new APIBeatmapSet { Title = "Triumph & Regret", - Artist = "typeMARS" + Artist = "typeMARS", }, - Version = "[4K] Regret" + DifficultyName = "[4K] Regret" }, Date = DateTimeOffset.Now, - Mods = new Mod[] + Mods = new[] { - new OsuModHardRock(), - new OsuModDoubleTime(), + new APIMod { Acronym = new OsuModHardRock().Acronym }, + new APIMod { Acronym = new OsuModDoubleTime().Acronym }, }, Accuracy = 0.998546 }; - var thirdScore = new ScoreInfo + var thirdScore = new APIScoreInfo { PP = 96.83, Rank = ScoreRank.S, - BeatmapInfo = new BeatmapInfo + Beatmap = new APIBeatmap { - Metadata = new BeatmapMetadata + BeatmapSet = new APIBeatmapSet { Title = "Idolize", - Artist = "Creo" + Artist = "Creo", }, - Version = "Insane" + DifficultyName = "Insane" }, Date = DateTimeOffset.Now, Accuracy = 0.9726 }; - var noPPScore = new ScoreInfo + var noPPScore = new APIScoreInfo { Rank = ScoreRank.B, - BeatmapInfo = new BeatmapInfo + Beatmap = new APIBeatmap { - Metadata = new BeatmapMetadata + BeatmapSet = new APIBeatmapSet { Title = "C18H27NO3(extend)", - Artist = "Team Grimoire" + Artist = "Team Grimoire", }, - Version = "[4K] Cataclysmic Hypernova" + DifficultyName = "[4K] Cataclysmic Hypernova" }, Date = DateTimeOffset.Now, Accuracy = 0.55879 diff --git a/osu.Game.Tests/Visual/Online/TestSceneWikiHeader.cs b/osu.Game.Tests/Visual/Online/TestSceneWikiHeader.cs index 08e61d19f4..27e989df76 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneWikiHeader.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneWikiHeader.cs @@ -75,7 +75,7 @@ namespace osu.Game.Tests.Visual.Online private bool checkBreadcrumb() { - var result = header.TabControlItems.Contains(wikiPageData.Value.Title); + bool result = header.TabControlItems.Contains(wikiPageData.Value.Title); if (wikiPageData.Value.Subtitle != null) result = header.TabControlItems.Contains(wikiPageData.Value.Subtitle) && result; diff --git a/osu.Game.Tests/Visual/Online/TestSceneWikiSidebar.cs b/osu.Game.Tests/Visual/Online/TestSceneWikiSidebar.cs index b4f1997bb0..862b3667b1 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneWikiSidebar.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneWikiSidebar.cs @@ -36,7 +36,7 @@ namespace osu.Game.Tests.Visual.Online { AddStep("Add TOC", () => { - for (var i = 0; i < 10; i++) + for (int i = 0; i < 10; i++) addTitle($"This is a very long title {i + 1}"); }); } @@ -46,7 +46,7 @@ namespace osu.Game.Tests.Visual.Online { AddStep("Add TOC", () => { - for (var i = 0; i < 10; i++) + for (int i = 0; i < 10; i++) addTitle($"This is a very long title {i + 1}", i % 4 != 0); }); } diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsLoungeSubScreen.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsLoungeSubScreen.cs index 5c248163d7..9ba0da1911 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsLoungeSubScreen.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsLoungeSubScreen.cs @@ -17,7 +17,7 @@ namespace osu.Game.Tests.Visual.Playlists { public class TestScenePlaylistsLoungeSubScreen : OnlinePlayTestScene { - protected new TestRequestHandlingRoomManager RoomManager => (TestRequestHandlingRoomManager)base.RoomManager; + protected new TestRoomManager RoomManager => (TestRoomManager)base.RoomManager; private TestLoungeSubScreen loungeScreen; diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs similarity index 57% rename from osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs rename to osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs index deb9a22184..cda7e95a46 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs @@ -11,24 +11,27 @@ using osu.Framework.Platform; using osu.Framework.Screens; using osu.Framework.Testing; using osu.Game.Beatmaps; +using osu.Game.Database; using osu.Game.Online.Rooms; using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; +using osu.Game.Screens.OnlinePlay.Components; using osu.Game.Screens.OnlinePlay.Playlists; using osu.Game.Screens.Play; -using osu.Game.Tests.Beatmaps; using osu.Game.Tests.Visual.OnlinePlay; using osuTK.Input; namespace osu.Game.Tests.Visual.Playlists { - public class TestScenePlaylistsRoomSubScreen : OnlinePlayTestScene + public class TestScenePlaylistsRoomCreation : OnlinePlayTestScene { private BeatmapManager manager; private RulesetStore rulesets; private TestPlaylistsRoomSubScreen match; + private ILive importedBeatmap; + [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { @@ -40,7 +43,9 @@ namespace osu.Game.Tests.Visual.Playlists public void SetupSteps() { AddStep("set room", () => SelectedRoom.Value = new Room()); - AddStep("ensure has beatmap", () => manager.Import(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo.BeatmapSet).Wait()); + + importBeatmap(); + AddStep("load match", () => LoadScreen(match = new TestPlaylistsRoomSubScreen(SelectedRoom.Value))); AddUntilStep("wait for load", () => match.IsCurrentScreen()); } @@ -48,44 +53,58 @@ namespace osu.Game.Tests.Visual.Playlists [Test] public void TestLoadSimpleMatch() { - AddStep("set room properties", () => + setupAndCreateRoom(room => { - SelectedRoom.Value.RoomID.Value = 1; - SelectedRoom.Value.Name.Value = "my awesome room"; - SelectedRoom.Value.Host.Value = API.LocalUser.Value; - SelectedRoom.Value.RecentParticipants.Add(SelectedRoom.Value.Host.Value); - SelectedRoom.Value.EndDate.Value = DateTimeOffset.Now.AddMinutes(5); - SelectedRoom.Value.Playlist.Add(new PlaylistItem + room.Name.Value = "my awesome room"; + room.Host.Value = API.LocalUser.Value; + room.RecentParticipants.Add(room.Host.Value); + room.EndDate.Value = DateTimeOffset.Now.AddMinutes(5); + room.Playlist.Add(new PlaylistItem { - Beatmap = { Value = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo }, + Beatmap = { Value = importedBeatmap.Value.Beatmaps.First() }, Ruleset = { Value = new OsuRuleset().RulesetInfo } }); }); + AddUntilStep("Progress details are hidden", () => match.ChildrenOfType().FirstOrDefault()?.Parent.Alpha == 0); + AddStep("start match", () => match.ChildrenOfType().First().TriggerClick()); AddUntilStep("player loader loaded", () => Stack.CurrentScreen is PlayerLoader); } [Test] - public void TestPlaylistItemSelectedOnCreate() + public void TestAttemptLimitedMatch() { - AddStep("set room properties", () => + setupAndCreateRoom(room => { - SelectedRoom.Value.Name.Value = "my awesome room"; - SelectedRoom.Value.Host.Value = API.LocalUser.Value; - SelectedRoom.Value.Playlist.Add(new PlaylistItem + room.Name.Value = "my awesome room"; + room.MaxAttempts.Value = 5; + room.Host.Value = API.LocalUser.Value; + room.RecentParticipants.Add(room.Host.Value); + room.EndDate.Value = DateTimeOffset.Now.AddMinutes(5); + room.Playlist.Add(new PlaylistItem { - Beatmap = { Value = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo }, + Beatmap = { Value = importedBeatmap.Value.Beatmaps.First() }, Ruleset = { Value = new OsuRuleset().RulesetInfo } }); }); - AddStep("move mouse to create button", () => - { - InputManager.MoveMouseTo(this.ChildrenOfType().Single()); - }); + AddUntilStep("Progress details are visible", () => match.ChildrenOfType().FirstOrDefault()?.Parent.Alpha == 1); + } - AddStep("click", () => InputManager.Click(MouseButton.Left)); + [Test] + public void TestPlaylistItemSelectedOnCreate() + { + setupAndCreateRoom(room => + { + room.Name.Value = "my awesome room"; + room.Host.Value = API.LocalUser.Value; + room.Playlist.Add(new PlaylistItem + { + Beatmap = { Value = importedBeatmap.Value.Beatmaps.First() }, + Ruleset = { Value = new OsuRuleset().RulesetInfo } + }); + }); AddAssert("first playlist item selected", () => match.SelectedItem.Value == SelectedRoom.Value.Playlist[0]); } @@ -94,56 +113,51 @@ namespace osu.Game.Tests.Visual.Playlists public void TestBeatmapUpdatedOnReImport() { BeatmapSetInfo importedSet = null; - TestBeatmap beatmap = null; - - // this step is required to make sure the further imports actually get online IDs. - // all the playlist logic relies on online ID matching. - AddStep("remove all matching online IDs", () => - { - beatmap = new TestBeatmap(new OsuRuleset().RulesetInfo); - - var existing = manager.QueryBeatmapSets(s => s.OnlineBeatmapSetID == beatmap.BeatmapInfo.BeatmapSet.OnlineBeatmapSetID).ToList(); - - foreach (var s in existing) - { - s.OnlineBeatmapSetID = null; - foreach (var b in s.Beatmaps) - b.OnlineBeatmapID = null; - manager.Update(s); - } - }); AddStep("import altered beatmap", () => { + IBeatmap beatmap = CreateBeatmap(new OsuRuleset().RulesetInfo); + beatmap.BeatmapInfo.BaseDifficulty.CircleSize = 1; + // intentionally increment online IDs to clash with import below. + beatmap.BeatmapInfo.OnlineBeatmapID++; + beatmap.BeatmapInfo.BeatmapSet.OnlineBeatmapSetID++; + importedSet = manager.Import(beatmap.BeatmapInfo.BeatmapSet).Result.Value; }); - AddStep("load room", () => + setupAndCreateRoom(room => { - SelectedRoom.Value.Name.Value = "my awesome room"; - SelectedRoom.Value.Host.Value = API.LocalUser.Value; - SelectedRoom.Value.Playlist.Add(new PlaylistItem + room.Name.Value = "my awesome room"; + room.Host.Value = API.LocalUser.Value; + room.Playlist.Add(new PlaylistItem { Beatmap = { Value = importedSet.Beatmaps[0] }, Ruleset = { Value = new OsuRuleset().RulesetInfo } }); }); - AddStep("create room", () => - { - InputManager.MoveMouseTo(match.ChildrenOfType().Single()); - InputManager.Click(MouseButton.Left); - }); - AddAssert("match has altered beatmap", () => match.Beatmap.Value.Beatmap.Difficulty.CircleSize == 1); - AddStep("re-import original beatmap", () => manager.Import(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo.BeatmapSet).Wait()); + importBeatmap(); AddAssert("match has original beatmap", () => match.Beatmap.Value.Beatmap.Difficulty.CircleSize != 1); } + private void setupAndCreateRoom(Action room) + { + AddStep("setup room", () => room(SelectedRoom.Value)); + + AddStep("click create button", () => + { + InputManager.MoveMouseTo(this.ChildrenOfType().Single()); + InputManager.Click(MouseButton.Left); + }); + } + + private void importBeatmap() => AddStep("import beatmap", () => importedBeatmap = manager.Import(CreateBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo.BeatmapSet).Result); + private class TestPlaylistsRoomSubScreen : PlaylistsRoomSubScreen { public new Bindable SelectedItem => base.SelectedItem; diff --git a/osu.Game.Tests/Visual/Settings/TestSceneSettingsSource.cs b/osu.Game.Tests/Visual/Settings/TestSceneSettingsSource.cs index d530e1f796..fc81d9792e 100644 --- a/osu.Game.Tests/Visual/Settings/TestSceneSettingsSource.cs +++ b/osu.Game.Tests/Visual/Settings/TestSceneSettingsSource.cs @@ -6,6 +6,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Configuration; +using osu.Game.Overlays.Settings; using osuTK; namespace osu.Game.Tests.Visual.Settings @@ -58,6 +59,13 @@ namespace osu.Game.Tests.Visual.Settings Default = string.Empty, Value = "Sample text" }; + + [SettingSource("Sample number textbox", "Textbox number entry", SettingControlType = typeof(SettingsNumberBox))] + public Bindable IntTextboxBindable { get; } = new Bindable + { + Default = null, + Value = null + }; } private enum TestEnum diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneAdvancedStats.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneAdvancedStats.cs index 4538e36c5e..07e68ef509 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneAdvancedStats.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneAdvancedStats.cs @@ -5,6 +5,7 @@ using System; using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics.Sprites; using osu.Framework.Testing; using osu.Game.Beatmaps; @@ -88,7 +89,7 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("select EZ mod", () => { - var ruleset = advancedStats.BeatmapInfo.Ruleset.CreateInstance(); + var ruleset = advancedStats.BeatmapInfo.Ruleset.CreateInstance().AsNonNull(); SelectedMods.Value = new[] { ruleset.CreateMod() }; }); @@ -105,7 +106,7 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("select HR mod", () => { - var ruleset = advancedStats.BeatmapInfo.Ruleset.CreateInstance(); + var ruleset = advancedStats.BeatmapInfo.Ruleset.CreateInstance().AsNonNull(); SelectedMods.Value = new[] { ruleset.CreateMod() }; }); @@ -122,9 +123,9 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("select unchanged Difficulty Adjust mod", () => { - var ruleset = advancedStats.BeatmapInfo.Ruleset.CreateInstance(); + var ruleset = advancedStats.BeatmapInfo.Ruleset.CreateInstance().AsNonNull(); var difficultyAdjustMod = ruleset.CreateMod(); - difficultyAdjustMod.ReadFromDifficulty(advancedStats.BeatmapInfo.BaseDifficulty); + difficultyAdjustMod.ReadFromDifficulty(advancedStats.BeatmapInfo.Difficulty); SelectedMods.Value = new[] { difficultyAdjustMod }; }); @@ -141,9 +142,9 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("select changed Difficulty Adjust mod", () => { - var ruleset = advancedStats.BeatmapInfo.Ruleset.CreateInstance(); + var ruleset = advancedStats.BeatmapInfo.Ruleset.CreateInstance().AsNonNull(); var difficultyAdjustMod = ruleset.CreateMod(); - var originalDifficulty = advancedStats.BeatmapInfo.BaseDifficulty; + var originalDifficulty = advancedStats.BeatmapInfo.Difficulty; difficultyAdjustMod.ReadFromDifficulty(originalDifficulty); difficultyAdjustMod.DrainRate.Value = originalDifficulty.DrainRate - 0.5f; diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs index 66f15670f5..7a38d213d9 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs @@ -15,6 +15,7 @@ using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Rulesets; +using osu.Game.Rulesets.Osu; using osu.Game.Screens.Select; using osu.Game.Screens.Select.Carousel; using osu.Game.Screens.Select.Filter; @@ -684,6 +685,7 @@ namespace osu.Game.Tests.Visual.SongSelect set.Beatmaps.Add(new BeatmapInfo { Version = $"Stars: {i}", + Ruleset = new OsuRuleset().RulesetInfo, StarDifficulty = i, }); } @@ -868,6 +870,7 @@ namespace osu.Game.Tests.Visual.SongSelect OnlineBeatmapID = id++ * 10, Version = version, StarDifficulty = diff, + Ruleset = new OsuRuleset().RulesetInfo, BaseDifficulty = new BeatmapDifficulty { OverallDifficulty = diff, diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapDetails.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapDetails.cs index d5b4fb9a80..9856b28b00 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapDetails.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapDetails.cs @@ -6,6 +6,8 @@ using NUnit.Framework; using osu.Framework.Graphics; using osu.Game.Beatmaps; using osu.Game.Online.API; +using osu.Game.Online.API.Requests.Responses; +using osu.Game.Rulesets.Osu; using osu.Game.Screens.Select; namespace osu.Game.Tests.Visual.SongSelect @@ -34,7 +36,10 @@ namespace osu.Game.Tests.Visual.SongSelect { BeatmapSet = new BeatmapSetInfo { - Metrics = new BeatmapSetMetrics { Ratings = Enumerable.Range(0, 11).ToArray() } + OnlineInfo = new APIBeatmapSet + { + Ratings = Enumerable.Range(0, 11).ToArray(), + } }, Version = "All Metrics", Metadata = new BeatmapMetadata @@ -50,11 +55,15 @@ namespace osu.Game.Tests.Visual.SongSelect ApproachRate = 3.5f, }, StarDifficulty = 5.3f, - Metrics = new BeatmapMetrics + Ruleset = new OsuRuleset().RulesetInfo, + OnlineInfo = new APIBeatmap { - Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6).ToArray(), - Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6).ToArray(), - }, + FailTimes = new APIFailTimes + { + Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6).ToArray(), + Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6).ToArray(), + }, + } }); } @@ -65,7 +74,10 @@ namespace osu.Game.Tests.Visual.SongSelect { BeatmapSet = new BeatmapSetInfo { - Metrics = new BeatmapSetMetrics { Ratings = Enumerable.Range(0, 11).ToArray() } + OnlineInfo = new APIBeatmapSet + { + Ratings = Enumerable.Range(0, 11).ToArray(), + } }, Version = "All Metrics", Metadata = new BeatmapMetadata @@ -80,11 +92,15 @@ namespace osu.Game.Tests.Visual.SongSelect ApproachRate = 3.5f, }, StarDifficulty = 5.3f, - Metrics = new BeatmapMetrics + Ruleset = new OsuRuleset().RulesetInfo, + OnlineInfo = new APIBeatmap { - Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6).ToArray(), - Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6).ToArray(), - }, + FailTimes = new APIFailTimes + { + Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6).ToArray(), + Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6).ToArray(), + }, + } }); } @@ -95,7 +111,10 @@ namespace osu.Game.Tests.Visual.SongSelect { BeatmapSet = new BeatmapSetInfo { - Metrics = new BeatmapSetMetrics { Ratings = Enumerable.Range(0, 11).ToArray() } + OnlineInfo = new APIBeatmapSet + { + Ratings = Enumerable.Range(0, 11).ToArray(), + } }, Version = "Only Ratings", Metadata = new BeatmapMetadata @@ -103,6 +122,7 @@ namespace osu.Game.Tests.Visual.SongSelect Source = "osu!", Tags = "this beatmap has ratings metrics but not retries or fails", }, + Ruleset = new OsuRuleset().RulesetInfo, BaseDifficulty = new BeatmapDifficulty { CircleSize = 6, @@ -132,12 +152,16 @@ namespace osu.Game.Tests.Visual.SongSelect OverallDifficulty = 6, ApproachRate = 7, }, + Ruleset = new OsuRuleset().RulesetInfo, StarDifficulty = 2.91f, - Metrics = new BeatmapMetrics + OnlineInfo = new APIBeatmap { - Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6).ToArray(), - Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6).ToArray(), - }, + FailTimes = new APIFailTimes + { + Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6).ToArray(), + Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6).ToArray(), + }, + } }); } @@ -152,6 +176,7 @@ namespace osu.Game.Tests.Visual.SongSelect Source = "osu!", Tags = "this beatmap has no metrics", }, + Ruleset = new OsuRuleset().RulesetInfo, BaseDifficulty = new BeatmapDifficulty { CircleSize = 5, @@ -175,6 +200,7 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("online ratings/retries/fails", () => details.BeatmapInfo = new BeatmapInfo { OnlineBeatmapID = 162, + Ruleset = new OsuRuleset().RulesetInfo }); AddStep("set online", () => api.SetState(APIState.Online)); AddStep("set offline", () => api.SetState(APIState.Offline)); diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapMetadataDisplay.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapMetadataDisplay.cs index f91d3f595b..50ae673c06 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapMetadataDisplay.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapMetadataDisplay.cs @@ -140,7 +140,7 @@ namespace osu.Game.Tests.Visual.SongSelect } } - public override async Task GetDifficultyAsync(BeatmapInfo beatmapInfo, RulesetInfo rulesetInfo = null, IEnumerable mods = null, CancellationToken cancellationToken = default) + public override async Task GetDifficultyAsync(IBeatmapInfo beatmapInfo, IRulesetInfo rulesetInfo = null, IEnumerable mods = null, CancellationToken cancellationToken = default) { if (blockCalculation) await calculationBlocker.Task.ConfigureAwait(false); diff --git a/osu.Game.Tests/Visual/TestMultiplayerScreenStack.cs b/osu.Game.Tests/Visual/TestMultiplayerScreenStack.cs new file mode 100644 index 0000000000..7f1171db1f --- /dev/null +++ b/osu.Game.Tests/Visual/TestMultiplayerScreenStack.cs @@ -0,0 +1,83 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Screens; +using osu.Game.Online.API; +using osu.Game.Online.Multiplayer; +using osu.Game.Screens; +using osu.Game.Screens.OnlinePlay.Components; +using osu.Game.Tests.Visual.Multiplayer; +using osu.Game.Tests.Visual.OnlinePlay; + +namespace osu.Game.Tests.Visual +{ + /// + /// An loadable into s via , + /// which provides dependencies for and loads an isolated screen. + ///

+ /// This screen: + /// + /// Provides a to be resolved as a dependency in the screen, + /// which is typically a part of . + /// Rebinds the to handle requests via a . + /// Provides a for the screen. + /// + ///

+ ///
+ public class TestMultiplayerScreenStack : OsuScreen + { + public Screens.OnlinePlay.Multiplayer.Multiplayer MultiplayerScreen => multiplayerScreen; + + public TestMultiplayerRoomManager RoomManager => multiplayerScreen.RoomManager; + + public IScreen CurrentScreen => screenStack.CurrentScreen; + + public new bool IsLoaded => base.IsLoaded && MultiplayerScreen.IsLoaded; + + [Cached(typeof(MultiplayerClient))] + public readonly TestMultiplayerClient Client; + + private readonly OsuScreenStack screenStack; + private readonly TestMultiplayer multiplayerScreen; + + public TestMultiplayerScreenStack() + { + multiplayerScreen = new TestMultiplayer(); + + InternalChildren = new Drawable[] + { + Client = new TestMultiplayerClient(RoomManager), + screenStack = new OsuScreenStack { RelativeSizeAxes = Axes.Both } + }; + + screenStack.Push(multiplayerScreen); + } + + [BackgroundDependencyLoader] + private void load(IAPIProvider api, OsuGameBase game) + { + ((DummyAPIAccess)api).HandleRequest = request => multiplayerScreen.RequestsHandler.HandleRequest(request, api.LocalUser.Value, game); + } + + public override bool OnBackButton() => multiplayerScreen.OnBackButton(); + + public override bool OnExiting(IScreen next) + { + if (screenStack.CurrentScreen == null) + return base.OnExiting(next); + + screenStack.Exit(); + return true; + } + + private class TestMultiplayer : Screens.OnlinePlay.Multiplayer.Multiplayer + { + public new TestMultiplayerRoomManager RoomManager { get; private set; } + public TestRoomRequestsHandler RequestsHandler { get; private set; } + + protected override RoomManager CreateRoomManager() => RoomManager = new TestMultiplayerRoomManager(RequestsHandler = new TestRoomRequestsHandler()); + } + } +} diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchControl.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchControl.cs index a9fe7ed7d8..f17de75f5c 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchControl.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchControl.cs @@ -110,25 +110,19 @@ namespace osu.Game.Tests.Visual.UserInterface base.Dispose(isDisposing); } - private static readonly BeatmapSetInfo beatmap_set = new BeatmapSetInfo + private static readonly APIBeatmapSet beatmap_set = new APIBeatmapSet { - OnlineInfo = new APIBeatmapSet + Covers = new BeatmapSetOnlineCovers { - Covers = new BeatmapSetOnlineCovers - { - Cover = "https://assets.ppy.sh/beatmaps/1094296/covers/cover@2x.jpg?1581416305" - } + Cover = "https://assets.ppy.sh/beatmaps/1094296/covers/cover@2x.jpg?1581416305" } }; - private static readonly BeatmapSetInfo no_cover_beatmap_set = new BeatmapSetInfo + private static readonly APIBeatmapSet no_cover_beatmap_set = new APIBeatmapSet { - OnlineInfo = new APIBeatmapSet + Covers = new BeatmapSetOnlineCovers { - Covers = new BeatmapSetOnlineCovers - { - Cover = string.Empty - } + Cover = string.Empty } }; } diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneBreadcrumbControlHeader.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneBreadcrumbControlHeader.cs index 90c3e142df..7bc75f1c44 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneBreadcrumbControlHeader.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneBreadcrumbControlHeader.cs @@ -31,18 +31,18 @@ namespace osu.Game.Tests.Visual.UserInterface [Test] public void TestAddAndRemoveItem() { - foreach (var item in items.Skip(1)) + foreach (string item in items.Skip(1)) AddStep($"Add {item} item", () => header.AddItem(item)); - foreach (var item in items.Reverse().SkipLast(3)) + foreach (string item in items.Reverse().SkipLast(3)) AddStep($"Remove {item} item", () => header.RemoveItem(item)); AddStep("Clear items", () => header.ClearItems()); - foreach (var item in items) + foreach (string item in items) AddStep($"Add {item} item", () => header.AddItem(item)); - foreach (var item in items) + foreach (string item in items) AddStep($"Remove {item} item", () => header.RemoveItem(item)); } diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneDashboardBeatmapListing.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneDashboardBeatmapListing.cs index 6727c7560b..06c64a566e 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneDashboardBeatmapListing.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneDashboardBeatmapListing.cs @@ -56,95 +56,71 @@ namespace osu.Game.Tests.Visual.UserInterface AddStep("Set width to 300", () => content.ResizeWidthTo(300, 500)); } - private static readonly List new_beatmaps = new List + private static readonly List new_beatmaps = new List { - new BeatmapSetInfo + new APIBeatmapSet { - Metadata = new BeatmapMetadata + Title = "Very Long Title (TV size) [TATOE]", + Artist = "This artist has a really long name how is this possible", + Author = new User { - Title = "Very Long Title (TV size) [TATOE]", - Artist = "This artist has a really long name how is this possible", - Author = new User - { - Username = "author", - Id = 100 - } + Username = "author", + Id = 100 }, - OnlineInfo = new APIBeatmapSet + Covers = new BeatmapSetOnlineCovers { - Covers = new BeatmapSetOnlineCovers - { - Cover = "https://assets.ppy.sh/beatmaps/1189904/covers/cover.jpg?1595456608", - }, - Ranked = DateTimeOffset.Now - } + Cover = "https://assets.ppy.sh/beatmaps/1189904/covers/cover.jpg?1595456608", + }, + Ranked = DateTimeOffset.Now }, - new BeatmapSetInfo + new APIBeatmapSet { - Metadata = new BeatmapMetadata + Title = "Very Long Title (TV size) [TATOE]", + Artist = "This artist has a really long name how is this possible", + Author = new User { - Title = "Very Long Title (TV size) [TATOE]", - Artist = "This artist has a really long name how is this possible", - Author = new User - { - Username = "author", - Id = 100 - } + Username = "author", + Id = 100 }, - OnlineInfo = new APIBeatmapSet + Covers = new BeatmapSetOnlineCovers { - Covers = new BeatmapSetOnlineCovers - { - Cover = "https://assets.ppy.sh/beatmaps/1189904/covers/cover.jpg?1595456608", - }, - Ranked = DateTimeOffset.MinValue - } + Cover = "https://assets.ppy.sh/beatmaps/1189904/covers/cover.jpg?1595456608", + }, + Ranked = DateTimeOffset.Now } }; - private static readonly List popular_beatmaps = new List + private static readonly List popular_beatmaps = new List { - new BeatmapSetInfo + new APIBeatmapSet { - Metadata = new BeatmapMetadata + Title = "Very Long Title (TV size) [TATOE]", + Artist = "This artist has a really long name how is this possible", + Author = new User { - Title = "Title", - Artist = "Artist", - Author = new User - { - Username = "author", - Id = 100 - } + Username = "author", + Id = 100 }, - OnlineInfo = new APIBeatmapSet + Covers = new BeatmapSetOnlineCovers { - Covers = new BeatmapSetOnlineCovers - { - Cover = "https://assets.ppy.sh/beatmaps/1079428/covers/cover.jpg?1595295586", - }, - FavouriteCount = 100 - } + Cover = "https://assets.ppy.sh/beatmaps/1189904/covers/cover.jpg?1595456608", + }, + Ranked = DateTimeOffset.Now }, - new BeatmapSetInfo + new APIBeatmapSet { - Metadata = new BeatmapMetadata + Title = "Very Long Title (TV size) [TATOE]", + Artist = "This artist has a really long name how is this possible", + Author = new User { - Title = "Title 2", - Artist = "Artist 2", - Author = new User - { - Username = "someone", - Id = 100 - } + Username = "author", + Id = 100 }, - OnlineInfo = new APIBeatmapSet + Covers = new BeatmapSetOnlineCovers { - Covers = new BeatmapSetOnlineCovers - { - Cover = "https://assets.ppy.sh/beatmaps/1079428/covers/cover.jpg?1595295586", - }, - FavouriteCount = 10 - } + Cover = "https://assets.ppy.sh/beatmaps/1189904/covers/cover.jpg?1595456608", + }, + Ranked = DateTimeOffset.Now } }; } diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneFooterButtonMods.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneFooterButtonMods.cs index 8d1572769f..0631059d1a 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneFooterButtonMods.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneFooterButtonMods.cs @@ -73,8 +73,8 @@ namespace osu.Game.Tests.Visual.UserInterface private bool assertModsMultiplier(IEnumerable mods) { - var multiplier = mods.Aggregate(1.0, (current, mod) => current * mod.ScoreMultiplier); - var expectedValue = multiplier.Equals(1.0) ? string.Empty : $"{multiplier:N2}x"; + double multiplier = mods.Aggregate(1.0, (current, mod) => current * mod.ScoreMultiplier); + string expectedValue = multiplier.Equals(1.0) ? string.Empty : $"{multiplier:N2}x"; return expectedValue == footerButtonMods.MultiplierText.Current.Value; } diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs index d0f6f3fe47..a2aa0499d2 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs @@ -41,6 +41,44 @@ namespace osu.Game.Tests.Visual.UserInterface notificationOverlay.UnreadCount.ValueChanged += count => { displayedCount.Text = $"displayed count: {count.NewValue}"; }; }); + [Test] + public void TestCompleteProgress() + { + ProgressNotification notification = null; + AddStep("add progress notification", () => + { + notification = new ProgressNotification + { + Text = @"Uploading to BSS...", + CompletionText = "Uploaded to BSS!", + }; + notificationOverlay.Post(notification); + progressingNotifications.Add(notification); + }); + + AddUntilStep("wait completion", () => notification.State == ProgressNotificationState.Completed); + } + + [Test] + public void TestCancelProgress() + { + ProgressNotification notification = null; + AddStep("add progress notification", () => + { + notification = new ProgressNotification + { + Text = @"Uploading to BSS...", + CompletionText = "Uploaded to BSS!", + }; + notificationOverlay.Post(notification); + progressingNotifications.Add(notification); + }); + + AddWaitStep("wait 3", 3); + + AddStep("cancel notification", () => notification.State = ProgressNotificationState.Cancelled); + } + [Test] public void TestBasicFlow() { @@ -138,7 +176,7 @@ namespace osu.Game.Tests.Visual.UserInterface foreach (var n in progressingNotifications.FindAll(n => n.State == ProgressNotificationState.Active)) { if (n.Progress < 1) - n.Progress += (float)(Time.Elapsed / 400) * RNG.NextSingle(); + n.Progress += (float)(Time.Elapsed / 2000); else n.State = ProgressNotificationState.Completed; } diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneOsuIcon.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneOsuIcon.cs index 096bccae9e..0bc4ac12d6 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneOsuIcon.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneOsuIcon.cs @@ -48,7 +48,7 @@ namespace osu.Game.Tests.Visual.UserInterface foreach (var p in typeof(OsuIcon).GetProperties(BindingFlags.Public | BindingFlags.Static)) { - var propValue = p.GetValue(null); + object propValue = p.GetValue(null); Debug.Assert(propValue != null); flow.Add(new Icon($"{nameof(OsuIcon)}.{p.Name}", (IconUsage)propValue)); diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneUpdateableBeatmapBackgroundSprite.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneUpdateableBeatmapBackgroundSprite.cs index 74cd675a05..d30f1e8889 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneUpdateableBeatmapBackgroundSprite.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneUpdateableBeatmapBackgroundSprite.cs @@ -12,6 +12,7 @@ using osu.Game.Beatmaps.Drawables; using osu.Game.Graphics.Containers; using osu.Game.Online.API; using osu.Game.Online.API.Requests; +using osu.Game.Online.API.Requests.Responses; using osu.Game.Rulesets; using osu.Game.Tests.Beatmaps.IO; using osuTK; @@ -24,7 +25,6 @@ namespace osu.Game.Tests.Visual.UserInterface private BeatmapSetInfo testBeatmap; private IAPIProvider api; - private RulesetStore rulesets; [Resolved] private BeatmapManager beatmaps { get; set; } @@ -33,7 +33,6 @@ namespace osu.Game.Tests.Visual.UserInterface private void load(OsuGameBase osu, IAPIProvider api, RulesetStore rulesets) { this.api = api; - this.rulesets = rulesets; testBeatmap = ImportBeatmapTest.LoadOszIntoOsu(osu).Result; } @@ -81,7 +80,7 @@ namespace osu.Game.Tests.Visual.UserInterface Child = background = new TestUpdateableBeatmapBackgroundSprite { RelativeSizeAxes = Axes.Both, - Beatmap = { Value = new BeatmapInfo { BeatmapSet = req.Response?.ToBeatmapSet(rulesets) } } + Beatmap = { Value = new APIBeatmap { BeatmapSet = req.Response } } }; }); diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneUpdateableBeatmapSetCover.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneUpdateableBeatmapSetCover.cs index 3ac3002713..f67f6258cc 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneUpdateableBeatmapSetCover.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneUpdateableBeatmapSetCover.cs @@ -25,7 +25,7 @@ namespace osu.Game.Tests.Visual.UserInterface { AddStep("setup cover", () => Child = new UpdateableOnlineBeatmapSetCover(coverType) { - BeatmapSet = CreateBeatmap(Ruleset.Value).BeatmapInfo.BeatmapSet, + OnlineInfo = CreateBeatmap(Ruleset.Value).BeatmapInfo.BeatmapSet.OnlineInfo, RelativeSizeAxes = Axes.Both, Masking = true, }); @@ -68,7 +68,7 @@ namespace osu.Game.Tests.Visual.UserInterface var cover = new UpdateableOnlineBeatmapSetCover(coverType) { - BeatmapSet = setInfo, + OnlineInfo = setInfo.OnlineInfo, Height = 100, Masking = true, }; @@ -99,12 +99,12 @@ namespace osu.Game.Tests.Visual.UserInterface AddStep("setup cover", () => Child = updateableCover = new TestUpdateableOnlineBeatmapSetCover { - BeatmapSet = CreateBeatmap(Ruleset.Value).BeatmapInfo.BeatmapSet, + OnlineInfo = CreateBeatmap(Ruleset.Value).BeatmapInfo.BeatmapSet.OnlineInfo, RelativeSizeAxes = Axes.Both, Masking = true, }); - AddStep("change model", () => updateableCover.BeatmapSet = null); + AddStep("change model", () => updateableCover.OnlineInfo = null); AddWaitStep("wait some", 5); AddAssert("no cover added", () => !updateableCover.ChildrenOfType().Any()); } @@ -117,7 +117,7 @@ namespace osu.Game.Tests.Visual.UserInterface AddStep("setup cover", () => Child = updateableCover = new TestUpdateableOnlineBeatmapSetCover(0) { - BeatmapSet = createBeatmapWithCover("https://assets.ppy.sh/beatmaps/1189904/covers/cover.jpg"), + OnlineInfo = createBeatmapWithCover("https://assets.ppy.sh/beatmaps/1189904/covers/cover.jpg").OnlineInfo, RelativeSizeAxes = Axes.Both, Masking = true, Alpha = 0.4f @@ -128,7 +128,7 @@ namespace osu.Game.Tests.Visual.UserInterface AddUntilStep("wait for fade complete", () => initialCover.Alpha == 1); AddStep("switch beatmap", - () => updateableCover.BeatmapSet = createBeatmapWithCover("https://assets.ppy.sh/beatmaps/1079428/covers/cover.jpg")); + () => updateableCover.OnlineInfo = createBeatmapWithCover("https://assets.ppy.sh/beatmaps/1079428/covers/cover.jpg").OnlineInfo); AddUntilStep("new cover loaded", () => updateableCover.ChildrenOfType().Except(new[] { initialCover }).Any()); } diff --git a/osu.Game.Tests/osu.Game.Tests.csproj b/osu.Game.Tests/osu.Game.Tests.csproj index cd56cb51ae..57815d9273 100644 --- a/osu.Game.Tests/osu.Game.Tests.csproj +++ b/osu.Game.Tests/osu.Game.Tests.csproj @@ -3,7 +3,7 @@ - + diff --git a/osu.Game.Tournament.Tests/Components/TestSceneTournamentBeatmapPanel.cs b/osu.Game.Tournament.Tests/Components/TestSceneTournamentBeatmapPanel.cs index f9c553cb3f..8139387a96 100644 --- a/osu.Game.Tournament.Tests/Components/TestSceneTournamentBeatmapPanel.cs +++ b/osu.Game.Tournament.Tests/Components/TestSceneTournamentBeatmapPanel.cs @@ -3,7 +3,6 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; -using osu.Game.Beatmaps; using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests.Responses; @@ -23,14 +22,13 @@ namespace osu.Game.Tournament.Tests.Components [BackgroundDependencyLoader] private void load() { - var req = new GetBeatmapRequest(new BeatmapInfo { OnlineBeatmapID = 1091460 }); + var req = new GetBeatmapRequest(new APIBeatmap { OnlineID = 1091460 }); req.Success += success; api.Queue(req); } - private void success(APIBeatmap apiBeatmap) + private void success(APIBeatmap beatmap) { - var beatmap = apiBeatmap.ToBeatmapInfo(rulesets); Add(new TournamentBeatmapPanel(beatmap) { Anchor = Anchor.Centre, diff --git a/osu.Game.Tournament.Tests/Components/TestSceneTournamentModDisplay.cs b/osu.Game.Tournament.Tests/Components/TestSceneTournamentModDisplay.cs index 27eb55a9fb..3c22bdca03 100644 --- a/osu.Game.Tournament.Tests/Components/TestSceneTournamentModDisplay.cs +++ b/osu.Game.Tournament.Tests/Components/TestSceneTournamentModDisplay.cs @@ -4,12 +4,12 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Game.Beatmaps; using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests.Responses; using osu.Game.Rulesets; using osu.Game.Tournament.Components; +using osuTK; namespace osu.Game.Tournament.Tests.Components { @@ -23,12 +23,10 @@ namespace osu.Game.Tournament.Tests.Components private FillFlowContainer fillFlow; - private BeatmapInfo beatmapInfo; - [BackgroundDependencyLoader] private void load() { - var req = new GetBeatmapRequest(new BeatmapInfo { OnlineBeatmapID = 490154 }); + var req = new GetBeatmapRequest(new APIBeatmap { OnlineID = 490154 }); req.Success += success; api.Queue(req); @@ -38,18 +36,17 @@ namespace osu.Game.Tournament.Tests.Components Anchor = Anchor.Centre, Origin = Anchor.Centre, Direction = FillDirection.Full, - Spacing = new osuTK.Vector2(10) + Spacing = new Vector2(10) }); } - private void success(APIBeatmap apiBeatmap) + private void success(APIBeatmap beatmap) { - beatmapInfo = apiBeatmap.ToBeatmapInfo(rulesets); var mods = rulesets.GetRuleset(Ladder.Ruleset.Value.ID ?? 0).CreateInstance().AllMods; foreach (var mod in mods) { - fillFlow.Add(new TournamentBeatmapPanel(beatmapInfo, mod.Acronym) + fillFlow.Add(new TournamentBeatmapPanel(beatmap, mod.Acronym) { Anchor = Anchor.Centre, Origin = Anchor.Centre diff --git a/osu.Game.Tournament.Tests/NonVisual/LadderInfoSerialisationTest.cs b/osu.Game.Tournament.Tests/NonVisual/LadderInfoSerialisationTest.cs new file mode 100644 index 0000000000..8bdf909af3 --- /dev/null +++ b/osu.Game.Tournament.Tests/NonVisual/LadderInfoSerialisationTest.cs @@ -0,0 +1,65 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using Newtonsoft.Json; +using NUnit.Framework; +using osu.Game.Tournament.Models; + +namespace osu.Game.Tournament.Tests.NonVisual +{ + [TestFixture] + public class LadderInfoSerialisationTest + { + [Test] + public void TestDeserialise() + { + var ladder = createSampleLadder(); + string serialised = JsonConvert.SerializeObject(ladder); + + JsonConvert.DeserializeObject(serialised, new JsonPointConverter()); + } + + [Test] + public void TestSerialise() + { + var ladder = createSampleLadder(); + JsonConvert.SerializeObject(ladder); + } + + private static LadderInfo createSampleLadder() + { + var match = TournamentTestScene.CreateSampleMatch(); + + return new LadderInfo + { + PlayersPerTeam = { Value = 4 }, + Teams = + { + match.Team1.Value, + match.Team2.Value, + }, + Rounds = + { + new TournamentRound + { + Beatmaps = + { + new RoundBeatmap { Beatmap = TournamentTestScene.CreateSampleBeatmap() }, + new RoundBeatmap { Beatmap = TournamentTestScene.CreateSampleBeatmap() }, + } + } + }, + + Matches = + { + match, + }, + Progressions = + { + new TournamentProgression(1, 2), + new TournamentProgression(1, 3, true), + } + }; + } + } +} diff --git a/osu.Game.Tournament.Tests/Screens/TestSceneMapPoolScreen.cs b/osu.Game.Tournament.Tests/Screens/TestSceneMapPoolScreen.cs index f4032fdd54..f732c5582b 100644 --- a/osu.Game.Tournament.Tests/Screens/TestSceneMapPoolScreen.cs +++ b/osu.Game.Tournament.Tests/Screens/TestSceneMapPoolScreen.cs @@ -132,7 +132,7 @@ namespace osu.Game.Tournament.Tests.Screens { Ladder.CurrentMatch.Value.Round.Value.Beatmaps.Add(new RoundBeatmap { - BeatmapInfo = CreateSampleBeatmapInfo(), + Beatmap = CreateSampleBeatmap(), Mods = mods }); } diff --git a/osu.Game.Tournament.Tests/TournamentTestScene.cs b/osu.Game.Tournament.Tests/TournamentTestScene.cs index 93e1e018a5..81741a43a9 100644 --- a/osu.Game.Tournament.Tests/TournamentTestScene.cs +++ b/osu.Game.Tournament.Tests/TournamentTestScene.cs @@ -7,7 +7,7 @@ using osu.Framework.Allocation; using osu.Framework.Platform; using osu.Framework.Testing; using osu.Framework.Utils; -using osu.Game.Beatmaps; +using osu.Game.Online.API.Requests.Responses; using osu.Game.Rulesets; using osu.Game.Tests.Visual; using osu.Game.Tournament.IO; @@ -73,19 +73,19 @@ namespace osu.Game.Tournament.Tests { new SeedingBeatmap { - BeatmapInfo = CreateSampleBeatmapInfo(), + Beatmap = CreateSampleBeatmap(), Score = 12345672, Seed = { Value = 24 }, }, new SeedingBeatmap { - BeatmapInfo = CreateSampleBeatmapInfo(), + Beatmap = CreateSampleBeatmap(), Score = 1234567, Seed = { Value = 12 }, }, new SeedingBeatmap { - BeatmapInfo = CreateSampleBeatmapInfo(), + Beatmap = CreateSampleBeatmap(), Score = 1234567, Seed = { Value = 16 }, } @@ -99,19 +99,19 @@ namespace osu.Game.Tournament.Tests { new SeedingBeatmap { - BeatmapInfo = CreateSampleBeatmapInfo(), + Beatmap = CreateSampleBeatmap(), Score = 234567, Seed = { Value = 3 }, }, new SeedingBeatmap { - BeatmapInfo = CreateSampleBeatmapInfo(), + Beatmap = CreateSampleBeatmap(), Score = 234567, Seed = { Value = 6 }, }, new SeedingBeatmap { - BeatmapInfo = CreateSampleBeatmapInfo(), + Beatmap = CreateSampleBeatmap(), Score = 234567, Seed = { Value = 12 }, } @@ -151,8 +151,16 @@ namespace osu.Game.Tournament.Tests } }; - public static BeatmapInfo CreateSampleBeatmapInfo() => - new BeatmapInfo { Metadata = new BeatmapMetadata { Title = "Test Title", Artist = "Test Artist", ID = RNG.Next(0, 1000000) } }; + public static APIBeatmap CreateSampleBeatmap() => + new APIBeatmap + { + BeatmapSet = new APIBeatmapSet + { + Title = "Test Title", + Artist = "Test Artist", + }, + OnlineID = RNG.Next(0, 1000000), + }; protected override ITestSceneTestRunner CreateRunner() => new TournamentTestSceneTestRunner(); diff --git a/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj b/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj index 2673c9ec9f..c0f94d49c7 100644 --- a/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj +++ b/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj @@ -5,7 +5,7 @@ - + diff --git a/osu.Game.Tournament/Components/SongBar.cs b/osu.Game.Tournament/Components/SongBar.cs index 13888699ef..a74b88c592 100644 --- a/osu.Game.Tournament/Components/SongBar.cs +++ b/osu.Game.Tournament/Components/SongBar.cs @@ -12,6 +12,7 @@ using osu.Game.Beatmaps; using osu.Game.Beatmaps.Legacy; using osu.Game.Extensions; using osu.Game.Graphics; +using osu.Game.Online.API.Requests.Responses; using osu.Game.Rulesets; using osu.Game.Screens.Menu; using osuTK; @@ -21,22 +22,21 @@ namespace osu.Game.Tournament.Components { public class SongBar : CompositeDrawable { - private BeatmapInfo beatmapInfo; + private APIBeatmap beatmap; public const float HEIGHT = 145 / 2f; [Resolved] private IBindable ruleset { get; set; } - public BeatmapInfo BeatmapInfo + public APIBeatmap Beatmap { - get => beatmapInfo; set { - if (beatmapInfo == value) + if (beatmap == value) return; - beatmapInfo = value; + beatmap = value; update(); } } @@ -95,18 +95,18 @@ namespace osu.Game.Tournament.Components private void update() { - if (beatmapInfo == null) + if (beatmap == null) { flow.Clear(); return; } - var bpm = beatmapInfo.BeatmapSet.OnlineInfo.BPM; - var length = beatmapInfo.Length; + double bpm = beatmap.BPM; + double length = beatmap.Length; string hardRockExtra = ""; string srExtra = ""; - var ar = beatmapInfo.BaseDifficulty.ApproachRate; + float ar = beatmap.Difficulty.ApproachRate; if ((mods & LegacyMods.HardRock) > 0) { @@ -132,9 +132,9 @@ namespace osu.Game.Tournament.Components default: stats = new (string heading, string content)[] { - ("CS", $"{beatmapInfo.BaseDifficulty.CircleSize:0.#}{hardRockExtra}"), + ("CS", $"{beatmap.Difficulty.CircleSize:0.#}{hardRockExtra}"), ("AR", $"{ar:0.#}{hardRockExtra}"), - ("OD", $"{beatmapInfo.BaseDifficulty.OverallDifficulty:0.#}{hardRockExtra}"), + ("OD", $"{beatmap.Difficulty.OverallDifficulty:0.#}{hardRockExtra}"), }; break; @@ -142,15 +142,15 @@ namespace osu.Game.Tournament.Components case 3: stats = new (string heading, string content)[] { - ("OD", $"{beatmapInfo.BaseDifficulty.OverallDifficulty:0.#}{hardRockExtra}"), - ("HP", $"{beatmapInfo.BaseDifficulty.DrainRate:0.#}{hardRockExtra}") + ("OD", $"{beatmap.Difficulty.OverallDifficulty:0.#}{hardRockExtra}"), + ("HP", $"{beatmap.Difficulty.DrainRate:0.#}{hardRockExtra}") }; break; case 2: stats = new (string heading, string content)[] { - ("CS", $"{beatmapInfo.BaseDifficulty.CircleSize:0.#}{hardRockExtra}"), + ("CS", $"{beatmap.Difficulty.CircleSize:0.#}{hardRockExtra}"), ("AR", $"{ar:0.#}"), }; break; @@ -186,7 +186,7 @@ namespace osu.Game.Tournament.Components Children = new Drawable[] { new DiffPiece(stats), - new DiffPiece(("Star Rating", $"{beatmapInfo.StarDifficulty:0.#}{srExtra}")) + new DiffPiece(("Star Rating", $"{beatmap.StarRating:0.#}{srExtra}")) } }, new FillFlowContainer @@ -229,7 +229,7 @@ namespace osu.Game.Tournament.Components } } }, - new TournamentBeatmapPanel(beatmapInfo) + new TournamentBeatmapPanel(beatmap) { RelativeSizeAxes = Axes.X, Width = 0.5f, @@ -252,9 +252,9 @@ namespace osu.Game.Tournament.Components s.Font = OsuFont.Torus.With(weight: bold ? FontWeight.Bold : FontWeight.Regular, size: 15); } - for (var i = 0; i < tuples.Length; i++) + for (int i = 0; i < tuples.Length; i++) { - var (heading, content) = tuples[i]; + (string heading, string content) = tuples[i]; if (i > 0) { diff --git a/osu.Game.Tournament/Components/TournamentBeatmapPanel.cs b/osu.Game.Tournament/Components/TournamentBeatmapPanel.cs index be29566e07..f3550f7465 100644 --- a/osu.Game.Tournament/Components/TournamentBeatmapPanel.cs +++ b/osu.Game.Tournament/Components/TournamentBeatmapPanel.cs @@ -13,6 +13,7 @@ using osu.Framework.Graphics.Textures; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; using osu.Game.Graphics; +using osu.Game.Online.API.Requests.Responses; using osu.Game.Tournament.Models; using osuTK.Graphics; @@ -20,7 +21,7 @@ namespace osu.Game.Tournament.Components { public class TournamentBeatmapPanel : CompositeDrawable { - public readonly IBeatmapInfo BeatmapInfo; + public readonly APIBeatmap Beatmap; private readonly string mod; @@ -32,11 +33,11 @@ namespace osu.Game.Tournament.Components private readonly Bindable currentMatch = new Bindable(); private Box flash; - public TournamentBeatmapPanel(IBeatmapInfo beatmapInfo, string mod = null) + public TournamentBeatmapPanel(APIBeatmap beatmap, string mod = null) { - if (beatmapInfo == null) throw new ArgumentNullException(nameof(beatmapInfo)); + if (beatmap == null) throw new ArgumentNullException(nameof(beatmap)); - BeatmapInfo = beatmapInfo; + Beatmap = beatmap; this.mod = mod; Width = 400; @@ -62,7 +63,7 @@ namespace osu.Game.Tournament.Components { RelativeSizeAxes = Axes.Both, Colour = OsuColour.Gray(0.5f), - BeatmapSet = BeatmapInfo.BeatmapSet as IBeatmapSetOnlineInfo, + OnlineInfo = Beatmap.BeatmapSet, }, new FillFlowContainer { @@ -75,7 +76,7 @@ namespace osu.Game.Tournament.Components { new TournamentSpriteText { - Text = BeatmapInfo.GetDisplayTitleRomanisable(false), + Text = Beatmap.GetDisplayTitleRomanisable(false, false), Font = OsuFont.Torus.With(weight: FontWeight.Bold), }, new FillFlowContainer @@ -92,7 +93,7 @@ namespace osu.Game.Tournament.Components }, new TournamentSpriteText { - Text = BeatmapInfo.Metadata?.Author, + Text = Beatmap.Metadata.Author, Padding = new MarginPadding { Right = 20 }, Font = OsuFont.Torus.With(weight: FontWeight.Bold, size: 14) }, @@ -104,7 +105,7 @@ namespace osu.Game.Tournament.Components }, new TournamentSpriteText { - Text = BeatmapInfo.DifficultyName, + Text = Beatmap.DifficultyName, Font = OsuFont.Torus.With(weight: FontWeight.Bold, size: 14) }, } @@ -148,7 +149,7 @@ namespace osu.Game.Tournament.Components private void updateState() { - var found = currentMatch.Value.PicksBans.FirstOrDefault(p => p.BeatmapID == BeatmapInfo.OnlineID); + var found = currentMatch.Value.PicksBans.FirstOrDefault(p => p.BeatmapID == Beatmap.OnlineID); bool doFlash = found != choice; choice = found; diff --git a/osu.Game.Tournament/IPC/FileBasedIPC.cs b/osu.Game.Tournament/IPC/FileBasedIPC.cs index 7010a30eb7..a57f9fd691 100644 --- a/osu.Game.Tournament/IPC/FileBasedIPC.cs +++ b/osu.Game.Tournament/IPC/FileBasedIPC.cs @@ -11,10 +11,10 @@ using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Logging; using osu.Framework.Platform; using osu.Framework.Threading; -using osu.Game.Beatmaps; using osu.Game.Beatmaps.Legacy; using osu.Game.Online.API; using osu.Game.Online.API.Requests; +using osu.Game.Online.API.Requests.Responses; using osu.Game.Rulesets; using osu.Game.Tournament.Models; @@ -46,7 +46,7 @@ namespace osu.Game.Tournament.IPC [BackgroundDependencyLoader] private void load() { - var stablePath = stableInfo.StablePath ?? findStablePath(); + string stablePath = stableInfo.StablePath ?? findStablePath(); initialiseIPCStorage(stablePath); } @@ -78,8 +78,8 @@ namespace osu.Game.Tournament.IPC using (var stream = IPCStorage.GetStream(file_ipc_filename)) using (var sr = new StreamReader(stream)) { - var beatmapId = int.Parse(sr.ReadLine().AsNonNull()); - var mods = int.Parse(sr.ReadLine().AsNonNull()); + int beatmapId = int.Parse(sr.ReadLine().AsNonNull()); + int mods = int.Parse(sr.ReadLine().AsNonNull()); if (lastBeatmapId != beatmapId) { @@ -87,14 +87,14 @@ namespace osu.Game.Tournament.IPC lastBeatmapId = beatmapId; - var existing = ladder.CurrentMatch.Value?.Round.Value?.Beatmaps.FirstOrDefault(b => b.ID == beatmapId && b.BeatmapInfo != null); + var existing = ladder.CurrentMatch.Value?.Round.Value?.Beatmaps.FirstOrDefault(b => b.ID == beatmapId && b.Beatmap != null); if (existing != null) - Beatmap.Value = existing.BeatmapInfo; + Beatmap.Value = existing.Beatmap; else { - beatmapLookupRequest = new GetBeatmapRequest(new BeatmapInfo { OnlineBeatmapID = beatmapId }); - beatmapLookupRequest.Success += b => Beatmap.Value = b.ToBeatmapInfo(Rulesets); + beatmapLookupRequest = new GetBeatmapRequest(new APIBeatmap { OnlineID = beatmapId }); + beatmapLookupRequest.Success += b => Beatmap.Value = b; API.Queue(beatmapLookupRequest); } } @@ -187,10 +187,10 @@ namespace osu.Game.Tournament.IPC [CanBeNull] private string findStablePath() { - var stableInstallPath = findFromEnvVar() ?? - findFromRegistry() ?? - findFromLocalAppData() ?? - findFromDotFolder(); + string stableInstallPath = findFromEnvVar() ?? + findFromRegistry() ?? + findFromLocalAppData() ?? + findFromDotFolder(); Logger.Log($"Stable path for tourney usage: {stableInstallPath}"); return stableInstallPath; diff --git a/osu.Game.Tournament/IPC/MatchIPCInfo.cs b/osu.Game.Tournament/IPC/MatchIPCInfo.cs index 701258c6c7..fa7079b824 100644 --- a/osu.Game.Tournament/IPC/MatchIPCInfo.cs +++ b/osu.Game.Tournament/IPC/MatchIPCInfo.cs @@ -3,14 +3,14 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Game.Beatmaps; using osu.Game.Beatmaps.Legacy; +using osu.Game.Online.API.Requests.Responses; namespace osu.Game.Tournament.IPC { public class MatchIPCInfo : Component { - public Bindable Beatmap { get; } = new Bindable(); + public Bindable Beatmap { get; } = new Bindable(); public Bindable Mods { get; } = new Bindable(); public Bindable State { get; } = new Bindable(); public Bindable ChatChannel { get; } = new Bindable(); diff --git a/osu.Game.Tournament/JsonPointConverter.cs b/osu.Game.Tournament/JsonPointConverter.cs index 9c82f8ac06..32bbe2dc18 100644 --- a/osu.Game.Tournament/JsonPointConverter.cs +++ b/osu.Game.Tournament/JsonPointConverter.cs @@ -40,7 +40,7 @@ namespace osu.Game.Tournament if (reader.TokenType == JsonToken.PropertyName) { - var name = reader.Value?.ToString(); + string name = reader.Value?.ToString(); int? val = reader.ReadAsInt32(); if (val == null) diff --git a/osu.Game.Tournament/Models/RoundBeatmap.cs b/osu.Game.Tournament/Models/RoundBeatmap.cs index 5d43d0ca66..2fd79546f1 100644 --- a/osu.Game.Tournament/Models/RoundBeatmap.cs +++ b/osu.Game.Tournament/Models/RoundBeatmap.cs @@ -1,7 +1,8 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osu.Game.Beatmaps; +using Newtonsoft.Json; +using osu.Game.Online.API.Requests.Responses; namespace osu.Game.Tournament.Models { @@ -10,6 +11,7 @@ namespace osu.Game.Tournament.Models public int ID; public string Mods; - public BeatmapInfo BeatmapInfo; + [JsonProperty("BeatmapInfo")] + public APIBeatmap Beatmap; } } diff --git a/osu.Game.Tournament/Models/SeedingBeatmap.cs b/osu.Game.Tournament/Models/SeedingBeatmap.cs index 2cd6fa7188..26f3016ac8 100644 --- a/osu.Game.Tournament/Models/SeedingBeatmap.cs +++ b/osu.Game.Tournament/Models/SeedingBeatmap.cs @@ -1,8 +1,9 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using Newtonsoft.Json; using osu.Framework.Bindables; -using osu.Game.Beatmaps; +using osu.Game.Online.API.Requests.Responses; namespace osu.Game.Tournament.Models { @@ -10,7 +11,8 @@ namespace osu.Game.Tournament.Models { public int ID; - public BeatmapInfo BeatmapInfo; + [JsonProperty("BeatmapInfo")] + public APIBeatmap Beatmap; public long Score; diff --git a/osu.Game.Tournament/Models/TournamentTeam.cs b/osu.Game.Tournament/Models/TournamentTeam.cs index 7074ae413c..d895e4b538 100644 --- a/osu.Game.Tournament/Models/TournamentTeam.cs +++ b/osu.Game.Tournament/Models/TournamentTeam.cs @@ -36,10 +36,10 @@ namespace osu.Game.Tournament.Models { get { - var ranks = Players.Select(p => p.Statistics?.GlobalRank) - .Where(i => i.HasValue) - .Select(i => i.Value) - .ToArray(); + int[] ranks = Players.Select(p => p.Statistics?.GlobalRank) + .Where(i => i.HasValue) + .Select(i => i.Value) + .ToArray(); if (ranks.Length == 0) return 0; diff --git a/osu.Game.Tournament/Screens/BeatmapInfoScreen.cs b/osu.Game.Tournament/Screens/BeatmapInfoScreen.cs index b94b164116..77b816d24c 100644 --- a/osu.Game.Tournament/Screens/BeatmapInfoScreen.cs +++ b/osu.Game.Tournament/Screens/BeatmapInfoScreen.cs @@ -4,8 +4,8 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Game.Beatmaps; using osu.Game.Beatmaps.Legacy; +using osu.Game.Online.API.Requests.Responses; using osu.Game.Tournament.Components; using osu.Game.Tournament.IPC; @@ -37,10 +37,10 @@ namespace osu.Game.Tournament.Screens SongBar.Mods = mods.NewValue; } - private void beatmapChanged(ValueChangedEvent beatmap) + private void beatmapChanged(ValueChangedEvent beatmap) { SongBar.FadeInFromZero(300, Easing.OutQuint); - SongBar.BeatmapInfo = beatmap.NewValue; + SongBar.Beatmap = beatmap.NewValue; } } } diff --git a/osu.Game.Tournament/Screens/Editors/RoundEditorScreen.cs b/osu.Game.Tournament/Screens/Editors/RoundEditorScreen.cs index 1d8c4e7476..f6bc607447 100644 --- a/osu.Game.Tournament/Screens/Editors/RoundEditorScreen.cs +++ b/osu.Game.Tournament/Screens/Editors/RoundEditorScreen.cs @@ -7,10 +7,10 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Online.API; using osu.Game.Online.API.Requests; +using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays.Settings; using osu.Game.Rulesets; using osu.Game.Tournament.Components; @@ -226,25 +226,25 @@ namespace osu.Game.Tournament.Screens.Editors Model.ID = id.NewValue ?? 0; if (id.NewValue != id.OldValue) - Model.BeatmapInfo = null; + Model.Beatmap = null; - if (Model.BeatmapInfo != null) + if (Model.Beatmap != null) { updatePanel(); return; } - var req = new GetBeatmapRequest(new BeatmapInfo { OnlineBeatmapID = Model.ID }); + var req = new GetBeatmapRequest(new APIBeatmap { OnlineID = Model.ID }); req.Success += res => { - Model.BeatmapInfo = res.ToBeatmapInfo(rulesets); + Model.Beatmap = res; updatePanel(); }; req.Failure += _ => { - Model.BeatmapInfo = null; + Model.Beatmap = null; updatePanel(); }; @@ -259,9 +259,9 @@ namespace osu.Game.Tournament.Screens.Editors { drawableContainer.Clear(); - if (Model.BeatmapInfo != null) + if (Model.Beatmap != null) { - drawableContainer.Child = new TournamentBeatmapPanel(Model.BeatmapInfo, Model.Mods) + drawableContainer.Child = new TournamentBeatmapPanel(Model.Beatmap, Model.Mods) { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, diff --git a/osu.Game.Tournament/Screens/Editors/SeedingEditorScreen.cs b/osu.Game.Tournament/Screens/Editors/SeedingEditorScreen.cs index d5b55823a5..9abf1d3adb 100644 --- a/osu.Game.Tournament/Screens/Editors/SeedingEditorScreen.cs +++ b/osu.Game.Tournament/Screens/Editors/SeedingEditorScreen.cs @@ -7,10 +7,10 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Online.API; using osu.Game.Online.API.Requests; +using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays.Settings; using osu.Game.Rulesets; using osu.Game.Tournament.Components; @@ -234,25 +234,25 @@ namespace osu.Game.Tournament.Screens.Editors Model.ID = id.NewValue ?? 0; if (id.NewValue != id.OldValue) - Model.BeatmapInfo = null; + Model.Beatmap = null; - if (Model.BeatmapInfo != null) + if (Model.Beatmap != null) { updatePanel(); return; } - var req = new GetBeatmapRequest(new BeatmapInfo { OnlineBeatmapID = Model.ID }); + var req = new GetBeatmapRequest(new APIBeatmap { OnlineID = Model.ID }); req.Success += res => { - Model.BeatmapInfo = res.ToBeatmapInfo(rulesets); + Model.Beatmap = res; updatePanel(); }; req.Failure += _ => { - Model.BeatmapInfo = null; + Model.Beatmap = null; updatePanel(); }; @@ -267,9 +267,9 @@ namespace osu.Game.Tournament.Screens.Editors { drawableContainer.Clear(); - if (Model.BeatmapInfo != null) + if (Model.Beatmap != null) { - drawableContainer.Child = new TournamentBeatmapPanel(Model.BeatmapInfo, result.Mod.Value) + drawableContainer.Child = new TournamentBeatmapPanel(Model.Beatmap, result.Mod.Value) { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, diff --git a/osu.Game.Tournament/Screens/Gameplay/Components/TournamentMatchScoreDisplay.cs b/osu.Game.Tournament/Screens/Gameplay/Components/TournamentMatchScoreDisplay.cs index 77101e4023..813bed86ae 100644 --- a/osu.Game.Tournament/Screens/Gameplay/Components/TournamentMatchScoreDisplay.cs +++ b/osu.Game.Tournament/Screens/Gameplay/Components/TournamentMatchScoreDisplay.cs @@ -114,7 +114,7 @@ namespace osu.Game.Tournament.Screens.Gameplay.Components var winningBar = score1.Value > score2.Value ? score1Bar : score2Bar; var losingBar = score1.Value <= score2.Value ? score1Bar : score2Bar; - var diff = Math.Max(score1.Value, score2.Value) - Math.Min(score1.Value, score2.Value); + int diff = Math.Max(score1.Value, score2.Value) - Math.Min(score1.Value, score2.Value); losingBar.ResizeWidthTo(0, 400, Easing.OutQuint); winningBar.ResizeWidthTo(Math.Min(0.4f, MathF.Pow(diff / 1500000f, 0.5f) / 2), 400, Easing.OutQuint); diff --git a/osu.Game.Tournament/Screens/Ladder/Components/DrawableTournamentMatch.cs b/osu.Game.Tournament/Screens/Ladder/Components/DrawableTournamentMatch.cs index 6937c69dbf..3e950310cf 100644 --- a/osu.Game.Tournament/Screens/Ladder/Components/DrawableTournamentMatch.cs +++ b/osu.Game.Tournament/Screens/Ladder/Components/DrawableTournamentMatch.cs @@ -208,7 +208,7 @@ namespace osu.Game.Tournament.Screens.Ladder.Components { if (Match.Round.Value == null) return; - var instaWinAmount = Match.Round.Value.BestOf.Value / 2; + int instaWinAmount = 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); @@ -243,8 +243,8 @@ namespace osu.Game.Tournament.Screens.Ladder.Components { foreach (var conditional in Match.ConditionalMatches) { - var team1Match = conditional.Acronyms.Contains(Match.Team1Acronym); - var team2Match = conditional.Acronyms.Contains(Match.Team2Acronym); + bool team1Match = conditional.Acronyms.Contains(Match.Team1Acronym); + bool team2Match = conditional.Acronyms.Contains(Match.Team2Acronym); if (team1Match && team2Match) Match.Date.Value = conditional.Date.Value; diff --git a/osu.Game.Tournament/Screens/Ladder/LadderDragContainer.cs b/osu.Game.Tournament/Screens/Ladder/LadderDragContainer.cs index fa03518c47..f98bfd087d 100644 --- a/osu.Game.Tournament/Screens/Ladder/LadderDragContainer.cs +++ b/osu.Game.Tournament/Screens/Ladder/LadderDragContainer.cs @@ -34,7 +34,7 @@ namespace osu.Game.Tournament.Screens.Ladder protected override bool OnScroll(ScrollEvent e) { - var newScale = Math.Clamp(scale + e.ScrollDelta.Y / 15 * scale, min_scale, max_scale); + float newScale = Math.Clamp(scale + e.ScrollDelta.Y / 15 * scale, min_scale, max_scale); this.MoveTo(target -= e.MousePosition * (newScale - scale), 2000, Easing.OutQuint); this.ScaleTo(scale = newScale, 2000, Easing.OutQuint); diff --git a/osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs b/osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs index 5f6546c303..3ae007f955 100644 --- a/osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs +++ b/osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs @@ -8,8 +8,8 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Events; using osu.Framework.Threading; -using osu.Game.Beatmaps; using osu.Game.Graphics.UserInterface; +using osu.Game.Online.API.Requests.Responses; using osu.Game.Tournament.Components; using osu.Game.Tournament.IPC; using osu.Game.Tournament.Models; @@ -105,14 +105,14 @@ namespace osu.Game.Tournament.Screens.MapPool ipc.Beatmap.BindValueChanged(beatmapChanged); } - private void beatmapChanged(ValueChangedEvent beatmap) + private void beatmapChanged(ValueChangedEvent beatmap) { if (CurrentMatch.Value == null || CurrentMatch.Value.PicksBans.Count(p => p.Type == ChoiceType.Ban) < 2) return; // if bans have already been placed, beatmap changes result in a selection being made autoamtically - if (beatmap.NewValue.OnlineBeatmapID != null) - addForBeatmap(beatmap.NewValue.OnlineBeatmapID.Value); + if (beatmap.NewValue.OnlineID > 0) + addForBeatmap(beatmap.NewValue.OnlineID); } private void setMode(TeamColour colour, ChoiceType choiceType) @@ -147,11 +147,11 @@ namespace osu.Game.Tournament.Screens.MapPool if (map != null) { - if (e.Button == MouseButton.Left && map.BeatmapInfo.OnlineID > 0) - addForBeatmap(map.BeatmapInfo.OnlineID); + if (e.Button == MouseButton.Left && map.Beatmap.OnlineID > 0) + addForBeatmap(map.Beatmap.OnlineID); else { - var existing = CurrentMatch.Value.PicksBans.FirstOrDefault(p => p.BeatmapID == map.BeatmapInfo.OnlineID); + var existing = CurrentMatch.Value.PicksBans.FirstOrDefault(p => p.BeatmapID == map.Beatmap.OnlineID); if (existing != null) { @@ -179,7 +179,7 @@ namespace osu.Game.Tournament.Screens.MapPool if (CurrentMatch.Value == null) return; - if (CurrentMatch.Value.Round.Value.Beatmaps.All(b => b.BeatmapInfo.OnlineBeatmapID != beatmapId)) + if (CurrentMatch.Value.Round.Value.Beatmaps.All(b => b.Beatmap.OnlineID != beatmapId)) // don't attempt to add if the beatmap isn't in our pool return; @@ -245,7 +245,7 @@ namespace osu.Game.Tournament.Screens.MapPool flowCount = 1; } - currentFlow.Add(new TournamentBeatmapPanel(b.BeatmapInfo, b.Mods) + currentFlow.Add(new TournamentBeatmapPanel(b.Beatmap, b.Mods) { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, diff --git a/osu.Game.Tournament/Screens/Setup/ResolutionSelector.cs b/osu.Game.Tournament/Screens/Setup/ResolutionSelector.cs index 4b518ea7c7..e53110651b 100644 --- a/osu.Game.Tournament/Screens/Setup/ResolutionSelector.cs +++ b/osu.Game.Tournament/Screens/Setup/ResolutionSelector.cs @@ -31,7 +31,7 @@ namespace osu.Game.Tournament.Screens.Setup return; // box contains text - if (!int.TryParse(numberBox.Text, out var number)) + if (!int.TryParse(numberBox.Text, out int number)) { // at this point, the only reason we can arrive here is if the input number was too big to parse into an int // so clamp to max allowed value diff --git a/osu.Game.Tournament/Screens/Setup/StablePathSelectScreen.cs b/osu.Game.Tournament/Screens/Setup/StablePathSelectScreen.cs index 3752d9d3be..8e9b32231f 100644 --- a/osu.Game.Tournament/Screens/Setup/StablePathSelectScreen.cs +++ b/osu.Game.Tournament/Screens/Setup/StablePathSelectScreen.cs @@ -37,7 +37,7 @@ namespace osu.Game.Tournament.Screens.Setup private void load(Storage storage, OsuColour colours) { var initialStorage = (ipc as FileBasedIPC)?.IPCStorage ?? storage; - var initialPath = new DirectoryInfo(initialStorage.GetFullPath(string.Empty)).Parent?.FullName; + string initialPath = new DirectoryInfo(initialStorage.GetFullPath(string.Empty)).Parent?.FullName; AddRangeInternal(new Drawable[] { @@ -129,7 +129,7 @@ namespace osu.Game.Tournament.Screens.Setup protected virtual void ChangePath() { - var target = directorySelector.CurrentPath.Value.FullName; + string target = directorySelector.CurrentPath.Value.FullName; var fileBasedIpc = ipc as FileBasedIPC; Logger.Log($"Changing Stable CE location to {target}"); diff --git a/osu.Game.Tournament/Screens/TeamIntro/SeedingScreen.cs b/osu.Game.Tournament/Screens/TeamIntro/SeedingScreen.cs index 3a0bd232b0..d34a8583b9 100644 --- a/osu.Game.Tournament/Screens/TeamIntro/SeedingScreen.cs +++ b/osu.Game.Tournament/Screens/TeamIntro/SeedingScreen.cs @@ -141,9 +141,9 @@ namespace osu.Game.Tournament.Screens.TeamIntro Spacing = new Vector2(5), Children = new Drawable[] { - new TournamentSpriteText { Text = beatmap.BeatmapInfo.Metadata.Title, Colour = TournamentGame.TEXT_COLOUR, }, + new TournamentSpriteText { Text = beatmap.Beatmap.Metadata.Title, Colour = TournamentGame.TEXT_COLOUR, }, new TournamentSpriteText { Text = "by", Colour = TournamentGame.TEXT_COLOUR, Font = OsuFont.Torus.With(weight: FontWeight.Regular) }, - new TournamentSpriteText { Text = beatmap.BeatmapInfo.Metadata.Artist, Colour = TournamentGame.TEXT_COLOUR, Font = OsuFont.Torus.With(weight: FontWeight.Regular) }, + new TournamentSpriteText { Text = beatmap.Beatmap.Metadata.Artist, Colour = TournamentGame.TEXT_COLOUR, Font = OsuFont.Torus.With(weight: FontWeight.Regular) }, } }, new FillFlowContainer diff --git a/osu.Game.Tournament/TournamentGame.cs b/osu.Game.Tournament/TournamentGame.cs index 7a43fee013..f03f815b83 100644 --- a/osu.Game.Tournament/TournamentGame.cs +++ b/osu.Game.Tournament/TournamentGame.cs @@ -12,6 +12,7 @@ using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Handlers.Mouse; +using osu.Framework.Logging; using osu.Framework.Platform; using osu.Game.Graphics; using osu.Game.Graphics.Cursor; @@ -60,72 +61,89 @@ namespace osu.Game.Tournament loadingSpinner.Show(); - BracketLoadTask.ContinueWith(_ => LoadComponentsAsync(new[] + BracketLoadTask.ContinueWith(t => { - new Container + if (t.IsFaulted) { - CornerRadius = 10, - Depth = float.MinValue, - Position = new Vector2(5), - Masking = true, - AutoSizeAxes = Axes.Both, - Anchor = Anchor.BottomRight, - Origin = Anchor.BottomRight, - Children = new Drawable[] + Schedule(() => { - new Box - { - Colour = OsuColour.Gray(0.2f), - RelativeSizeAxes = Axes.Both, - }, - new TourneyButton - { - Text = "Save Changes", - Width = 140, - Height = 50, - Padding = new MarginPadding - { - Top = 10, - Left = 10, - }, - Margin = new MarginPadding - { - Right = 10, - Bottom = 10, - }, - Action = SaveChanges, - }, - } - }, - heightWarning = new WarningBox("Please make the window wider") - { - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - Margin = new MarginPadding(20), - }, - new OsuContextMenuContainer - { - RelativeSizeAxes = Axes.Both, - Child = new TournamentSceneManager() + loadingSpinner.Hide(); + loadingSpinner.Expire(); + + Logger.Error(t.Exception, "Couldn't load bracket with error"); + Add(new WarningBox("Your bracket.json file could not be parsed. Please check runtime.log for more details.")); + }); + + return; } - }, drawables => - { - loadingSpinner.Hide(); - loadingSpinner.Expire(); - AddRange(drawables); - - windowSize.BindValueChanged(size => ScheduleAfterChildren(() => + LoadComponentsAsync(new[] { - var minWidth = (int)(size.NewValue.Height / 768f * TournamentSceneManager.REQUIRED_WIDTH) - 1; - heightWarning.Alpha = size.NewValue.Width < minWidth ? 1 : 0; - }), true); - - windowMode.BindValueChanged(mode => ScheduleAfterChildren(() => + new Container + { + CornerRadius = 10, + Depth = float.MinValue, + Position = new Vector2(5), + Masking = true, + AutoSizeAxes = Axes.Both, + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomRight, + Children = new Drawable[] + { + new Box + { + Colour = OsuColour.Gray(0.2f), + RelativeSizeAxes = Axes.Both, + }, + new TourneyButton + { + Text = "Save Changes", + Width = 140, + Height = 50, + Padding = new MarginPadding + { + Top = 10, + Left = 10, + }, + Margin = new MarginPadding + { + Right = 10, + Bottom = 10, + }, + Action = SaveChanges, + }, + } + }, + heightWarning = new WarningBox("Please make the window wider") + { + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + Margin = new MarginPadding(20), + }, + new OsuContextMenuContainer + { + RelativeSizeAxes = Axes.Both, + Child = new TournamentSceneManager() + } + }, drawables => { - windowMode.Value = WindowMode.Windowed; - }), true); - })); + loadingSpinner.Hide(); + loadingSpinner.Expire(); + + AddRange(drawables); + + windowSize.BindValueChanged(size => ScheduleAfterChildren(() => + { + int minWidth = (int)(size.NewValue.Height / 768f * TournamentSceneManager.REQUIRED_WIDTH) - 1; + heightWarning.Alpha = size.NewValue.Width < minWidth ? 1 : 0; + }), true); + + windowMode.BindValueChanged(mode => ScheduleAfterChildren(() => + { + windowMode.Value = WindowMode.Windowed; + }), true); + }); + }); } } } diff --git a/osu.Game.Tournament/TournamentGameBase.cs b/osu.Game.Tournament/TournamentGameBase.cs index bdf7269c83..ee281466a2 100644 --- a/osu.Game.Tournament/TournamentGameBase.cs +++ b/osu.Game.Tournament/TournamentGameBase.cs @@ -7,12 +7,14 @@ using System.Linq; using System.Threading.Tasks; using Newtonsoft.Json; using osu.Framework.Allocation; +using osu.Framework.Graphics; using osu.Framework.Graphics.Textures; using osu.Framework.Input; using osu.Framework.IO.Stores; using osu.Framework.Platform; -using osu.Game.Beatmaps; +using osu.Game.Graphics; using osu.Game.Online.API.Requests; +using osu.Game.Online.API.Requests.Responses; using osu.Game.Tournament.IO; using osu.Game.Tournament.IPC; using osu.Game.Tournament.Models; @@ -39,9 +41,18 @@ namespace osu.Game.Tournament return dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); } + private TournamentSpriteText initialisationText; + [BackgroundDependencyLoader] private void load(Storage baseStorage) { + AddInternal(initialisationText = new TournamentSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Font = OsuFont.Torus.With(size: 32), + }); + Resources.AddStore(new DllResourceStore(typeof(TournamentGameBase).Assembly)); dependencies.CacheAs(storage = new TournamentStorage(baseStorage)); @@ -58,75 +69,84 @@ namespace osu.Game.Tournament private void readBracket() { - if (storage.Exists(bracket_filename)) + try { - using (Stream stream = storage.GetStream(bracket_filename, FileAccess.Read, FileMode.Open)) - using (var sr = new StreamReader(stream)) - ladder = JsonConvert.DeserializeObject(sr.ReadToEnd(), new JsonPointConverter()); - } - - ladder ??= new LadderInfo(); - - ladder.Ruleset.Value = RulesetStore.GetRuleset(ladder.Ruleset.Value?.ShortName) - ?? RulesetStore.AvailableRulesets.First(); - - bool addedInfo = false; - - // assign teams - foreach (var match in ladder.Matches) - { - match.Team1.Value = ladder.Teams.FirstOrDefault(t => t.Acronym.Value == match.Team1Acronym); - match.Team2.Value = ladder.Teams.FirstOrDefault(t => t.Acronym.Value == match.Team2Acronym); - - foreach (var conditional in match.ConditionalMatches) + if (storage.Exists(bracket_filename)) { - conditional.Team1.Value = ladder.Teams.FirstOrDefault(t => t.Acronym.Value == conditional.Team1Acronym); - conditional.Team2.Value = ladder.Teams.FirstOrDefault(t => t.Acronym.Value == conditional.Team2Acronym); - conditional.Round.Value = match.Round.Value; + using (Stream stream = storage.GetStream(bracket_filename, FileAccess.Read, FileMode.Open)) + using (var sr = new StreamReader(stream)) + ladder = JsonConvert.DeserializeObject(sr.ReadToEnd(), new JsonPointConverter()); } - } - // assign progressions - foreach (var pair in ladder.Progressions) - { - var src = ladder.Matches.FirstOrDefault(p => p.ID == pair.SourceID); - var dest = ladder.Matches.FirstOrDefault(p => p.ID == pair.TargetID); + ladder ??= new LadderInfo(); - if (src == null) - continue; + ladder.Ruleset.Value = RulesetStore.GetRuleset(ladder.Ruleset.Value?.ShortName) + ?? RulesetStore.AvailableRulesets.First(); - if (dest != null) + bool addedInfo = false; + + // assign teams + foreach (var match in ladder.Matches) { - if (pair.Losers) - src.LosersProgression.Value = dest; - else - src.Progression.Value = dest; - } - } + match.Team1.Value = ladder.Teams.FirstOrDefault(t => t.Acronym.Value == match.Team1Acronym); + match.Team2.Value = ladder.Teams.FirstOrDefault(t => t.Acronym.Value == match.Team2Acronym); - // link matches to rounds - foreach (var round in ladder.Rounds) - { - foreach (var id in round.Matches) - { - var found = ladder.Matches.FirstOrDefault(p => p.ID == id); - - if (found != null) + foreach (var conditional in match.ConditionalMatches) { - found.Round.Value = round; - if (round.StartDate.Value > found.Date.Value) - found.Date.Value = round.StartDate.Value; + conditional.Team1.Value = ladder.Teams.FirstOrDefault(t => t.Acronym.Value == conditional.Team1Acronym); + conditional.Team2.Value = ladder.Teams.FirstOrDefault(t => t.Acronym.Value == conditional.Team2Acronym); + conditional.Round.Value = match.Round.Value; } } + + // assign progressions + foreach (var pair in ladder.Progressions) + { + var src = ladder.Matches.FirstOrDefault(p => p.ID == pair.SourceID); + var dest = ladder.Matches.FirstOrDefault(p => p.ID == pair.TargetID); + + if (src == null) + continue; + + if (dest != null) + { + if (pair.Losers) + src.LosersProgression.Value = dest; + else + src.Progression.Value = dest; + } + } + + // link matches to rounds + foreach (var round in ladder.Rounds) + { + foreach (int id in round.Matches) + { + var found = ladder.Matches.FirstOrDefault(p => p.ID == id); + + if (found != null) + { + found.Round.Value = round; + if (round.StartDate.Value > found.Date.Value) + found.Date.Value = round.StartDate.Value; + } + } + } + + addedInfo |= addPlayers(); + addedInfo |= addRoundBeatmaps(); + addedInfo |= addSeedingBeatmaps(); + + if (addedInfo) + SaveChanges(); + + ladder.CurrentMatch.Value = ladder.Matches.FirstOrDefault(p => p.Current.Value); + } + catch (Exception e) + { + taskCompletionSource.SetException(e); + return; } - - addedInfo |= addPlayers(); - addedInfo |= addBeatmaps(); - - if (addedInfo) - SaveChanges(); - - ladder.CurrentMatch.Value = ladder.Matches.FirstOrDefault(p => p.Current.Value); Schedule(() => { @@ -137,6 +157,8 @@ namespace osu.Game.Tournament Add(ipc); taskCompletionSource.SetResult(true); + + initialisationText.Expire(); }); } @@ -145,81 +167,108 @@ namespace osu.Game.Tournament /// private bool addPlayers() { - bool addedInfo = false; + var playersRequiringPopulation = ladder.Teams + .SelectMany(t => t.Players) + .Where(p => string.IsNullOrEmpty(p.Username) + || p.Statistics?.GlobalRank == null + || p.Statistics?.CountryRank == null).ToList(); - foreach (var t in ladder.Teams) + if (playersRequiringPopulation.Count == 0) + return false; + + for (int i = 0; i < playersRequiringPopulation.Count; i++) { - foreach (var p in t.Players) - { - if (string.IsNullOrEmpty(p.Username) - || p.Statistics?.GlobalRank == null - || p.Statistics?.CountryRank == null) - { - PopulateUser(p, immediate: true); - addedInfo = true; - } - } + var p = playersRequiringPopulation[i]; + PopulateUser(p, immediate: true); + updateLoadProgressMessage($"Populating user stats ({i} / {playersRequiringPopulation.Count})"); } - return addedInfo; + return true; } /// /// Add missing beatmap info based on beatmap IDs /// - private bool addBeatmaps() + private bool addRoundBeatmaps() { - bool addedInfo = false; + var beatmapsRequiringPopulation = ladder.Rounds + .SelectMany(r => r.Beatmaps) + .Where(b => string.IsNullOrEmpty(b.Beatmap?.BeatmapSet?.Title) && b.ID > 0).ToList(); - foreach (var r in ladder.Rounds) + if (beatmapsRequiringPopulation.Count == 0) + return false; + + for (int i = 0; i < beatmapsRequiringPopulation.Count; i++) { - foreach (var b in r.Beatmaps.ToList()) - { - if (b.BeatmapInfo != null) - continue; + var b = beatmapsRequiringPopulation[i]; - if (b.ID > 0) - { - var req = new GetBeatmapRequest(new BeatmapInfo { OnlineBeatmapID = b.ID }); - API.Perform(req); - b.BeatmapInfo = req.Response?.ToBeatmapInfo(RulesetStore); + var req = new GetBeatmapRequest(new APIBeatmap { OnlineID = b.ID }); + API.Perform(req); + b.Beatmap = req.Response ?? new APIBeatmap(); - addedInfo = true; - } - - if (b.BeatmapInfo == null) - // if online population couldn't be performed, ensure we don't leave a null value behind - r.Beatmaps.Remove(b); - } + updateLoadProgressMessage($"Populating round beatmaps ({i} / {beatmapsRequiringPopulation.Count})"); } - foreach (var t in ladder.Teams) - { - foreach (var s in t.SeedingResults) - { - foreach (var b in s.Beatmaps) - { - if (b.BeatmapInfo == null && b.ID > 0) - { - var req = new GetBeatmapRequest(new BeatmapInfo { OnlineBeatmapID = b.ID }); - req.Perform(API); - b.BeatmapInfo = req.Response?.ToBeatmapInfo(RulesetStore); - - addedInfo = true; - } - } - } - } - - return addedInfo; + return true; } + /// + /// Add missing beatmap info based on beatmap IDs + /// + private bool addSeedingBeatmaps() + { + var beatmapsRequiringPopulation = ladder.Teams + .SelectMany(r => r.SeedingResults) + .SelectMany(r => r.Beatmaps) + .Where(b => string.IsNullOrEmpty(b.Beatmap?.BeatmapSet?.Title) && b.ID > 0).ToList(); + + if (beatmapsRequiringPopulation.Count == 0) + return false; + + for (int i = 0; i < beatmapsRequiringPopulation.Count; i++) + { + var b = beatmapsRequiringPopulation[i]; + + var req = new GetBeatmapRequest(new APIBeatmap { OnlineID = b.ID }); + API.Perform(req); + b.Beatmap = req.Response ?? new APIBeatmap(); + + updateLoadProgressMessage($"Populating seeding beatmaps ({i} / {beatmapsRequiringPopulation.Count})"); + } + + return true; + } + + private void updateLoadProgressMessage(string s) => Schedule(() => initialisationText.Text = s); + public void PopulateUser(User user, Action success = null, Action failure = null, bool immediate = false) { var req = new GetUserRequest(user.Id, Ruleset.Value); - req.Success += res => + if (immediate) { + API.Perform(req); + populate(); + } + else + { + req.Success += res => { populate(); }; + req.Failure += _ => + { + user.Id = 1; + failure?.Invoke(); + }; + + API.Queue(req); + } + + void populate() + { + var res = req.Response; + + if (res == null) + return; + user.Id = res.Id; user.Username = res.Username; @@ -228,18 +277,7 @@ namespace osu.Game.Tournament user.Cover = res.Cover; success?.Invoke(); - }; - - req.Failure += _ => - { - user.Id = 1; - failure?.Invoke(); - }; - - if (immediate) - API.Perform(req); - else - API.Queue(req); + } } protected override void LoadComplete() @@ -261,18 +299,19 @@ namespace osu.Game.Tournament ladder.Matches.Where(p => p.LosersProgression.Value != null).Select(p => new TournamentProgression(p.ID, p.LosersProgression.Value.ID, true))) .ToList(); + // Serialise before opening stream for writing, so if there's a failure it will leave the file in the previous state. + string serialisedLadder = JsonConvert.SerializeObject(ladder, + new JsonSerializerSettings + { + Formatting = Formatting.Indented, + NullValueHandling = NullValueHandling.Ignore, + DefaultValueHandling = DefaultValueHandling.Ignore, + Converters = new JsonConverter[] { new JsonPointConverter() } + }); + using (var stream = storage.GetStream(bracket_filename, FileAccess.Write, FileMode.Create)) using (var sw = new StreamWriter(stream)) - { - sw.Write(JsonConvert.SerializeObject(ladder, - new JsonSerializerSettings - { - Formatting = Formatting.Indented, - NullValueHandling = NullValueHandling.Ignore, - DefaultValueHandling = DefaultValueHandling.Ignore, - Converters = new JsonConverter[] { new JsonPointConverter() } - })); - } + sw.Write(serialisedLadder); } protected override UserInputManager CreateUserInputManager() => new TournamentInputManager(); diff --git a/osu.Game.Tournament/TournamentSceneManager.cs b/osu.Game.Tournament/TournamentSceneManager.cs index ced1a8ec72..914d1163ad 100644 --- a/osu.Game.Tournament/TournamentSceneManager.cs +++ b/osu.Game.Tournament/TournamentSceneManager.cs @@ -237,7 +237,7 @@ namespace osu.Game.Tournament { Type = type; BackgroundColour = OsuColour.Gray(0.2f); - Action = () => RequestSelection(type); + Action = () => RequestSelection?.Invoke(type); RelativeSizeAxes = Axes.X; } diff --git a/osu.Game/Audio/Effects/AudioFilter.cs b/osu.Game/Audio/Effects/AudioFilter.cs index d2a39e9db7..9446967173 100644 --- a/osu.Game/Audio/Effects/AudioFilter.cs +++ b/osu.Game/Audio/Effects/AudioFilter.cs @@ -103,7 +103,7 @@ namespace osu.Game.Audio.Effects ensureAttached(); - var filterIndex = mixer.Effects.IndexOf(filter); + int filterIndex = mixer.Effects.IndexOf(filter); if (filterIndex < 0) return; diff --git a/osu.Game/Audio/PreviewTrackManager.cs b/osu.Game/Audio/PreviewTrackManager.cs index 1de9e1561f..ca63add31d 100644 --- a/osu.Game/Audio/PreviewTrackManager.cs +++ b/osu.Game/Audio/PreviewTrackManager.cs @@ -43,11 +43,11 @@ namespace osu.Game.Audio } /// - /// Retrieves a for a . + /// Retrieves a for a . /// - /// The to retrieve the preview track for. + /// The to retrieve the preview track for. /// The playable . - public PreviewTrack Get(BeatmapSetInfo beatmapSetInfo) + public PreviewTrack Get(IBeatmapSetInfo beatmapSetInfo) { var track = CreatePreviewTrack(beatmapSetInfo, trackStore); @@ -91,7 +91,7 @@ namespace osu.Game.Audio /// /// Creates the . /// - protected virtual TrackManagerPreviewTrack CreatePreviewTrack(BeatmapSetInfo beatmapSetInfo, ITrackStore trackStore) => + protected virtual TrackManagerPreviewTrack CreatePreviewTrack(IBeatmapSetInfo beatmapSetInfo, ITrackStore trackStore) => new TrackManagerPreviewTrack(beatmapSetInfo, trackStore); public class TrackManagerPreviewTrack : PreviewTrack @@ -99,10 +99,10 @@ namespace osu.Game.Audio [Resolved(canBeNull: true)] public IPreviewTrackOwner Owner { get; private set; } - private readonly BeatmapSetInfo beatmapSetInfo; + private readonly IBeatmapSetInfo beatmapSetInfo; private readonly ITrackStore trackManager; - public TrackManagerPreviewTrack(BeatmapSetInfo beatmapSetInfo, ITrackStore trackManager) + public TrackManagerPreviewTrack(IBeatmapSetInfo beatmapSetInfo, ITrackStore trackManager) { this.beatmapSetInfo = beatmapSetInfo; this.trackManager = trackManager; @@ -114,7 +114,7 @@ namespace osu.Game.Audio Logger.Log($"A {nameof(PreviewTrack)} was created without a containing {nameof(IPreviewTrackOwner)}. An owner should be added for correct behaviour."); } - protected override Track GetTrack() => trackManager.Get($"https://b.ppy.sh/preview/{beatmapSetInfo?.OnlineBeatmapSetID}.mp3"); + protected override Track GetTrack() => trackManager.Get($"https://b.ppy.sh/preview/{beatmapSetInfo.OnlineID}.mp3"); } private class PreviewTrackStore : AudioCollectionManager, ITrackStore diff --git a/osu.Game/Beatmaps/BeatmapMetrics.cs b/osu.Game/Beatmaps/APIFailTimes.cs similarity index 96% rename from osu.Game/Beatmaps/BeatmapMetrics.cs rename to osu.Game/Beatmaps/APIFailTimes.cs index b164aa6b30..7218906b38 100644 --- a/osu.Game/Beatmaps/BeatmapMetrics.cs +++ b/osu.Game/Beatmaps/APIFailTimes.cs @@ -9,7 +9,7 @@ namespace osu.Game.Beatmaps /// /// Beatmap metrics based on accumulated online data from community plays. /// - public class BeatmapMetrics + public class APIFailTimes { /// /// Points of failure on a relative time scale (usually 0..100). diff --git a/osu.Game/Beatmaps/Beatmap.cs b/osu.Game/Beatmaps/Beatmap.cs index b2211e26cf..98087994b7 100644 --- a/osu.Game/Beatmaps/Beatmap.cs +++ b/osu.Game/Beatmaps/Beatmap.cs @@ -93,7 +93,7 @@ namespace osu.Game.Beatmaps if (t.Time > lastTime) return (beatLength: t.BeatLength, 0); - var nextTime = i == ControlPointInfo.TimingPoints.Count - 1 ? lastTime : ControlPointInfo.TimingPoints[i + 1].Time; + double nextTime = i == ControlPointInfo.TimingPoints.Count - 1 ? lastTime : ControlPointInfo.TimingPoints[i + 1].Time; return (beatLength: t.BeatLength, duration: nextTime - t.Time); }) // Aggregate durations into a set of (beatLength, duration) tuples for each beat length diff --git a/osu.Game/Beatmaps/BeatmapDifficultyCache.cs b/osu.Game/Beatmaps/BeatmapDifficultyCache.cs index 3777365088..9a0cdb387d 100644 --- a/osu.Game/Beatmaps/BeatmapDifficultyCache.cs +++ b/osu.Game/Beatmaps/BeatmapDifficultyCache.cs @@ -88,7 +88,7 @@ namespace osu.Game.Beatmaps /// The to get the difficulty of. /// An optional which stops updating the star difficulty for the given . /// A bindable that is updated to contain the star difficulty when it becomes available. Will be null while in an initial calculating state (but not during updates to ruleset and mods if a stale value is already propagated). - public IBindable GetBindableDifficulty([NotNull] BeatmapInfo beatmapInfo, CancellationToken cancellationToken = default) + public IBindable GetBindableDifficulty([NotNull] IBeatmapInfo beatmapInfo, CancellationToken cancellationToken = default) { var bindable = createBindable(beatmapInfo, currentRuleset.Value, currentMods.Value, cancellationToken); @@ -99,42 +99,45 @@ namespace osu.Game.Beatmaps } /// - /// Retrieves a bindable containing the star difficulty of a with a given and combination. + /// Retrieves a bindable containing the star difficulty of a with a given and combination. /// /// /// The bindable will not update to follow the currently-selected ruleset and mods or its settings. /// - /// The to get the difficulty of. - /// The to get the difficulty with. If null, the 's ruleset is used. + /// The to get the difficulty of. + /// The to get the difficulty with. If null, the 's ruleset is used. /// The s to get the difficulty with. If null, no mods will be assumed. - /// An optional which stops updating the star difficulty for the given . + /// An optional which stops updating the star difficulty for the given . /// A bindable that is updated to contain the star difficulty when it becomes available. Will be null while in an initial calculating state. - public IBindable GetBindableDifficulty([NotNull] BeatmapInfo beatmapInfo, [CanBeNull] RulesetInfo rulesetInfo, [CanBeNull] IEnumerable mods, + public IBindable GetBindableDifficulty([NotNull] IBeatmapInfo beatmapInfo, [CanBeNull] IRulesetInfo rulesetInfo, [CanBeNull] IEnumerable mods, CancellationToken cancellationToken = default) => createBindable(beatmapInfo, rulesetInfo, mods, cancellationToken); /// - /// Retrieves the difficulty of a . + /// Retrieves the difficulty of a . /// - /// The to get the difficulty of. - /// The to get the difficulty with. + /// The to get the difficulty of. + /// The to get the difficulty with. /// The s to get the difficulty with. /// An optional which stops computing the star difficulty. /// The . - public virtual Task GetDifficultyAsync([NotNull] BeatmapInfo beatmapInfo, [CanBeNull] RulesetInfo rulesetInfo = null, + public virtual Task GetDifficultyAsync([NotNull] IBeatmapInfo beatmapInfo, [CanBeNull] IRulesetInfo rulesetInfo = null, [CanBeNull] IEnumerable mods = null, CancellationToken cancellationToken = default) { // In the case that the user hasn't given us a ruleset, use the beatmap's default ruleset. rulesetInfo ??= beatmapInfo.Ruleset; + var localBeatmapInfo = beatmapInfo as BeatmapInfo; + var localRulesetInfo = rulesetInfo as RulesetInfo; + // Difficulty can only be computed if the beatmap and ruleset are locally available. - if (beatmapInfo.ID == 0 || rulesetInfo.ID == null) + if (localBeatmapInfo == null || localRulesetInfo == null) { // If not, fall back to the existing star difficulty (e.g. from an online source). - return Task.FromResult(new StarDifficulty(beatmapInfo.StarDifficulty, beatmapInfo.MaxCombo ?? 0)); + return Task.FromResult(new StarDifficulty(beatmapInfo.StarRating, (beatmapInfo as IBeatmapOnlineInfo)?.MaxCombo ?? 0)); } - return GetAsync(new DifficultyCacheLookup(beatmapInfo, rulesetInfo, mods), cancellationToken); + return GetAsync(new DifficultyCacheLookup(localBeatmapInfo, localRulesetInfo, mods), cancellationToken); } protected override Task ComputeValueAsync(DifficultyCacheLookup lookup, CancellationToken token = default) @@ -227,12 +230,12 @@ namespace osu.Game.Beatmaps /// /// Creates a new and triggers an initial value update. /// - /// The that star difficulty should correspond to. - /// The initial to get the difficulty with. + /// The that star difficulty should correspond to. + /// The initial to get the difficulty with. /// The initial s to get the difficulty with. - /// An optional which stops updating the star difficulty for the given . + /// An optional which stops updating the star difficulty for the given . /// The . - private BindableStarDifficulty createBindable([NotNull] BeatmapInfo beatmapInfo, [CanBeNull] RulesetInfo initialRulesetInfo, [CanBeNull] IEnumerable initialMods, + private BindableStarDifficulty createBindable([NotNull] IBeatmapInfo beatmapInfo, [CanBeNull] IRulesetInfo initialRulesetInfo, [CanBeNull] IEnumerable initialMods, CancellationToken cancellationToken) { var bindable = new BindableStarDifficulty(beatmapInfo, cancellationToken); @@ -244,12 +247,12 @@ namespace osu.Game.Beatmaps /// Updates the value of a with a given ruleset + mods. /// /// The to update. - /// The to update with. + /// The to update with. /// The s to update with. /// A token that may be used to cancel this update. - private void updateBindable([NotNull] BindableStarDifficulty bindable, [CanBeNull] RulesetInfo rulesetInfo, [CanBeNull] IEnumerable mods, CancellationToken cancellationToken = default) + private void updateBindable([NotNull] BindableStarDifficulty bindable, [CanBeNull] IRulesetInfo rulesetInfo, [CanBeNull] IEnumerable mods, CancellationToken cancellationToken = default) { - // GetDifficultyAsync will fall back to existing data from BeatmapInfo if not locally available + // GetDifficultyAsync will fall back to existing data from IBeatmapInfo if not locally available // (contrary to GetAsync) GetDifficultyAsync(bindable.BeatmapInfo, rulesetInfo, mods, cancellationToken) .ContinueWith(t => @@ -343,10 +346,10 @@ namespace osu.Game.Beatmaps private class BindableStarDifficulty : Bindable { - public readonly BeatmapInfo BeatmapInfo; + public readonly IBeatmapInfo BeatmapInfo; public readonly CancellationToken CancellationToken; - public BindableStarDifficulty(BeatmapInfo beatmapInfo, CancellationToken cancellationToken) + public BindableStarDifficulty(IBeatmapInfo beatmapInfo, CancellationToken cancellationToken) { BeatmapInfo = beatmapInfo; CancellationToken = cancellationToken; diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index 3bcc00f5de..9069ea4404 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -9,6 +9,7 @@ using System.Linq; using Newtonsoft.Json; using osu.Framework.Testing; using osu.Game.Database; +using osu.Game.Online.API.Requests.Responses; using osu.Game.Rulesets; using osu.Game.Scoring; @@ -16,7 +17,7 @@ namespace osu.Game.Beatmaps { [ExcludeFromDynamicCompile] [Serializable] - public class BeatmapInfo : IEquatable, IHasPrimaryKey, IBeatmapInfo + public class BeatmapInfo : IEquatable, IHasPrimaryKey, IBeatmapInfo, IBeatmapOnlineInfo { public int ID { get; set; } @@ -47,10 +48,7 @@ namespace osu.Game.Beatmaps public BeatmapDifficulty BaseDifficulty { get; set; } [NotMapped] - public BeatmapMetrics Metrics { get; set; } - - [NotMapped] - public BeatmapOnlineInfo OnlineInfo { get; set; } + public APIBeatmap OnlineInfo { get; set; } [NotMapped] public int? MaxCombo { get; set; } @@ -184,13 +182,43 @@ namespace osu.Game.Beatmaps #region Implementation of IBeatmapInfo + [JsonIgnore] string IBeatmapInfo.DifficultyName => Version; + + [JsonIgnore] IBeatmapMetadataInfo IBeatmapInfo.Metadata => Metadata; + + [JsonIgnore] IBeatmapDifficultyInfo IBeatmapInfo.Difficulty => BaseDifficulty; + + [JsonIgnore] IBeatmapSetInfo IBeatmapInfo.BeatmapSet => BeatmapSet; + + [JsonIgnore] IRulesetInfo IBeatmapInfo.Ruleset => Ruleset; + + [JsonIgnore] double IBeatmapInfo.StarRating => StarDifficulty; #endregion + + #region Implementation of IBeatmapOnlineInfo + + [JsonIgnore] + public int CircleCount => OnlineInfo.CircleCount; + + [JsonIgnore] + public int SliderCount => OnlineInfo.SliderCount; + + [JsonIgnore] + public int PlayCount => OnlineInfo.PlayCount; + + [JsonIgnore] + public int PassCount => OnlineInfo.PassCount; + + [JsonIgnore] + public APIFailTimes FailTimes => OnlineInfo.FailTimes; + + #endregion } } diff --git a/osu.Game/Beatmaps/BeatmapInfoExtensions.cs b/osu.Game/Beatmaps/BeatmapInfoExtensions.cs index 836302c424..2d69015933 100644 --- a/osu.Game/Beatmaps/BeatmapInfoExtensions.cs +++ b/osu.Game/Beatmaps/BeatmapInfoExtensions.cs @@ -16,13 +16,13 @@ namespace osu.Game.Beatmaps /// /// A user-presentable display title representing this beatmap, with localisation handling for potentially romanisable fields. /// - public static RomanisableString GetDisplayTitleRomanisable(this IBeatmapInfo beatmapInfo, bool includeDifficultyName = true) + public static RomanisableString GetDisplayTitleRomanisable(this IBeatmapInfo beatmapInfo, bool includeDifficultyName = true, bool includeCreator = true) { - var metadata = getClosestMetadata(beatmapInfo).GetDisplayTitleRomanisable(); + var metadata = getClosestMetadata(beatmapInfo).GetDisplayTitleRomanisable(includeCreator); if (includeDifficultyName) { - var versionString = getVersionString(beatmapInfo); + string versionString = getVersionString(beatmapInfo); return new RomanisableString($"{metadata.GetPreferred(true)} {versionString}".Trim(), $"{metadata.GetPreferred(false)} {versionString}".Trim()); } diff --git a/osu.Game/Beatmaps/BeatmapMetadataInfoExtensions.cs b/osu.Game/Beatmaps/BeatmapMetadataInfoExtensions.cs index ee946eeeec..fcaad17059 100644 --- a/osu.Game/Beatmaps/BeatmapMetadataInfoExtensions.cs +++ b/osu.Game/Beatmaps/BeatmapMetadataInfoExtensions.cs @@ -34,11 +34,11 @@ namespace osu.Game.Beatmaps /// /// A user-presentable display title representing this beatmap, with localisation handling for potentially romanisable fields. /// - public static RomanisableString GetDisplayTitleRomanisable(this IBeatmapMetadataInfo metadataInfo) + public static RomanisableString GetDisplayTitleRomanisable(this IBeatmapMetadataInfo metadataInfo, bool includeCreator = true) { - string author = string.IsNullOrEmpty(metadataInfo.Author) ? string.Empty : $"({metadataInfo.Author})"; - var artistUnicode = string.IsNullOrEmpty(metadataInfo.ArtistUnicode) ? metadataInfo.Artist : metadataInfo.ArtistUnicode; - var titleUnicode = string.IsNullOrEmpty(metadataInfo.TitleUnicode) ? metadataInfo.Title : metadataInfo.TitleUnicode; + string author = !includeCreator || string.IsNullOrEmpty(metadataInfo.Author) ? string.Empty : $"({metadataInfo.Author})"; + string artistUnicode = string.IsNullOrEmpty(metadataInfo.ArtistUnicode) ? metadataInfo.Artist : metadataInfo.ArtistUnicode; + string titleUnicode = string.IsNullOrEmpty(metadataInfo.TitleUnicode) ? metadataInfo.Title : metadataInfo.TitleUnicode; return new RomanisableString($"{artistUnicode} - {titleUnicode} {author}".Trim(), $"{metadataInfo.Artist} - {metadataInfo.Title} {author}".Trim()); } diff --git a/osu.Game/Beatmaps/BeatmapModelDownloader.cs b/osu.Game/Beatmaps/BeatmapModelDownloader.cs index 30dc95a966..001726e741 100644 --- a/osu.Game/Beatmaps/BeatmapModelDownloader.cs +++ b/osu.Game/Beatmaps/BeatmapModelDownloader.cs @@ -13,6 +13,9 @@ namespace osu.Game.Beatmaps protected override ArchiveDownloadRequest CreateDownloadRequest(BeatmapSetInfo set, bool minimiseDownloadSize) => new DownloadBeatmapSetRequest(set, minimiseDownloadSize); + public override ArchiveDownloadRequest GetExistingDownload(BeatmapSetInfo model) + => CurrentDownloads.Find(r => r.Model.OnlineID == model.OnlineID); + public BeatmapModelDownloader(IBeatmapModelManager beatmapModelManager, IAPIProvider api, GameHost host = null) : base(beatmapModelManager, api, host) { diff --git a/osu.Game/Beatmaps/BeatmapSetInfo.cs b/osu.Game/Beatmaps/BeatmapSetInfo.cs index c3e2399d53..79cc8b70fb 100644 --- a/osu.Game/Beatmaps/BeatmapSetInfo.cs +++ b/osu.Game/Beatmaps/BeatmapSetInfo.cs @@ -14,7 +14,7 @@ using osu.Game.Online.API.Requests.Responses; namespace osu.Game.Beatmaps { [ExcludeFromDynamicCompile] - public class BeatmapSetInfo : IHasPrimaryKey, IHasFiles, ISoftDelete, IEquatable, IBeatmapSetInfo, IBeatmapSetOnlineInfo + public class BeatmapSetInfo : IHasPrimaryKey, IHasFiles, ISoftDelete, IEquatable, IBeatmapSetInfo { public int ID { get; set; } @@ -35,12 +35,10 @@ namespace osu.Game.Beatmaps [NotNull] public List Files { get; set; } = new List(); + // This field is temporary and only used by `APIBeatmapSet.ToBeatmapSet` (soon to be removed) and tests (to be updated to provide APIBeatmapSet instead). [NotMapped] public APIBeatmapSet OnlineInfo { get; set; } - [NotMapped] - public BeatmapSetMetrics Metrics { get; set; } - /// /// The maximum star difficulty of all beatmaps in this set. /// @@ -107,135 +105,74 @@ namespace osu.Game.Beatmaps [NotMapped] [JsonIgnore] - public DateTimeOffset Submitted - { - get => OnlineInfo.Submitted; - set => OnlineInfo.Submitted = value; - } + public DateTimeOffset Submitted => OnlineInfo.Submitted; [NotMapped] [JsonIgnore] - public DateTimeOffset? Ranked - { - get => OnlineInfo.Ranked; - set => OnlineInfo.Ranked = value; - } + public DateTimeOffset? Ranked => OnlineInfo.Ranked; [NotMapped] [JsonIgnore] - public DateTimeOffset? LastUpdated - { - get => OnlineInfo.LastUpdated; - set => OnlineInfo.LastUpdated = value; - } + public DateTimeOffset? LastUpdated => OnlineInfo.LastUpdated; - [NotMapped] [JsonIgnore] public BeatmapSetOnlineStatus Status { get; set; } = BeatmapSetOnlineStatus.None; [NotMapped] [JsonIgnore] - public bool HasExplicitContent - { - get => OnlineInfo.HasExplicitContent; - set => OnlineInfo.HasExplicitContent = value; - } + public bool HasExplicitContent => OnlineInfo.HasExplicitContent; [NotMapped] [JsonIgnore] - public bool HasVideo - { - get => OnlineInfo.HasVideo; - set => OnlineInfo.HasVideo = value; - } + public bool HasVideo => OnlineInfo.HasVideo; [NotMapped] [JsonIgnore] - public bool HasStoryboard - { - get => OnlineInfo.HasStoryboard; - set => OnlineInfo.HasStoryboard = value; - } + public bool HasStoryboard => OnlineInfo.HasStoryboard; [NotMapped] [JsonIgnore] - public BeatmapSetOnlineCovers Covers - { - get => OnlineInfo.Covers; - set => OnlineInfo.Covers = value; - } + public BeatmapSetOnlineCovers Covers => OnlineInfo.Covers; [NotMapped] [JsonIgnore] - public string Preview - { - get => OnlineInfo.Preview; - set => OnlineInfo.Preview = value; - } + public string Preview => OnlineInfo.Preview; [NotMapped] [JsonIgnore] - public double BPM - { - get => OnlineInfo.BPM; - set => OnlineInfo.BPM = value; - } + public double BPM => OnlineInfo.BPM; [NotMapped] [JsonIgnore] - public int PlayCount - { - get => OnlineInfo.PlayCount; - set => OnlineInfo.PlayCount = value; - } + public int PlayCount => OnlineInfo.PlayCount; [NotMapped] [JsonIgnore] - public int FavouriteCount - { - get => OnlineInfo.FavouriteCount; - set => OnlineInfo.FavouriteCount = value; - } + public int FavouriteCount => OnlineInfo.FavouriteCount; [NotMapped] [JsonIgnore] - public bool HasFavourited - { - get => OnlineInfo.HasFavourited; - set => OnlineInfo.HasFavourited = value; - } + public bool HasFavourited => OnlineInfo.HasFavourited; [NotMapped] [JsonIgnore] - public BeatmapSetOnlineAvailability Availability - { - get => OnlineInfo.Availability; - set => OnlineInfo.Availability = value; - } + public BeatmapSetOnlineAvailability Availability => OnlineInfo.Availability; [NotMapped] [JsonIgnore] - public BeatmapSetOnlineGenre Genre - { - get => OnlineInfo.Genre; - set => OnlineInfo.Genre = value; - } + public BeatmapSetOnlineGenre Genre => OnlineInfo.Genre; [NotMapped] [JsonIgnore] - public BeatmapSetOnlineLanguage Language - { - get => OnlineInfo.Language; - set => OnlineInfo.Language = value; - } + public BeatmapSetOnlineLanguage Language => OnlineInfo.Language; [NotMapped] [JsonIgnore] - public int? TrackId - { - get => OnlineInfo.TrackId; - set => OnlineInfo.TrackId = value; - } + public int? TrackId => OnlineInfo?.TrackId; + + [NotMapped] + [JsonIgnore] + public int[] Ratings => OnlineInfo?.Ratings; #endregion } diff --git a/osu.Game/Beatmaps/BeatmapSetMetrics.cs b/osu.Game/Beatmaps/BeatmapSetMetrics.cs deleted file mode 100644 index 51c5de19a6..0000000000 --- a/osu.Game/Beatmaps/BeatmapSetMetrics.cs +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using System; -using Newtonsoft.Json; - -namespace osu.Game.Beatmaps -{ - public class BeatmapSetMetrics - { - /// - /// Total vote counts of user ratings on a scale of 0..10 where 0 is unused (probably will be fixed at API?). - /// - [JsonProperty("ratings")] - public int[] Ratings { get; set; } = Array.Empty(); - } -} diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs index 9d738ecbfb..246d1f8af5 100644 --- a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs +++ b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs @@ -180,8 +180,8 @@ namespace osu.Game.Beatmaps.ControlPoints private static double getClosestSnappedTime(TimingControlPoint timingPoint, double time, int beatDivisor) { - var beatLength = timingPoint.BeatLength / beatDivisor; - var beatLengths = (int)Math.Round((time - timingPoint.Time) / beatLength, MidpointRounding.AwayFromZero); + double beatLength = timingPoint.BeatLength / beatDivisor; + int beatLengths = (int)Math.Round((time - timingPoint.Time) / beatLength, MidpointRounding.AwayFromZero); return timingPoint.Time + beatLengths * beatLength; } diff --git a/osu.Game/Beatmaps/DifficultyRecommender.cs b/osu.Game/Beatmaps/DifficultyRecommender.cs index b1b1e58ab7..86f5e0dabf 100644 --- a/osu.Game/Beatmaps/DifficultyRecommender.cs +++ b/osu.Game/Beatmaps/DifficultyRecommender.cs @@ -59,12 +59,12 @@ namespace osu.Game.Beatmaps { foreach (var r in orderedRulesets) { - if (!recommendedDifficultyMapping.TryGetValue(r, out var recommendation)) + if (!recommendedDifficultyMapping.TryGetValue(r, out double recommendation)) continue; BeatmapInfo beatmapInfo = beatmaps.Where(b => b.Ruleset.Equals(r)).OrderBy(b => { - var difference = b.StarDifficulty - recommendation; + double difference = b.StarDifficulty - recommendation; return difference >= 0 ? difference * 2 : difference * -1; // prefer easier over harder }).FirstOrDefault(); diff --git a/osu.Game/Beatmaps/Drawables/BeatmapSetOnlineStatusPill.cs b/osu.Game/Beatmaps/Drawables/BeatmapSetOnlineStatusPill.cs index ffc010b3a3..ec098f4ca2 100644 --- a/osu.Game/Beatmaps/Drawables/BeatmapSetOnlineStatusPill.cs +++ b/osu.Game/Beatmaps/Drawables/BeatmapSetOnlineStatusPill.cs @@ -1,6 +1,9 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +#nullable enable + +using osu.Framework.Allocation; using osu.Framework.Extensions; using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics; @@ -8,15 +11,13 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; +using osu.Game.Overlays; using osuTK.Graphics; namespace osu.Game.Beatmaps.Drawables { public class BeatmapSetOnlineStatusPill : CircularContainer { - private readonly OsuSpriteText statusText; - private readonly Box background; - private BeatmapSetOnlineStatus status; public BeatmapSetOnlineStatus Status @@ -29,8 +30,8 @@ namespace osu.Game.Beatmaps.Drawables status = value; - Alpha = value == BeatmapSetOnlineStatus.None ? 0 : 1; - statusText.Text = value.GetLocalisableDescription().ToUpper(); + if (IsLoaded) + updateState(); } } @@ -46,15 +47,17 @@ namespace osu.Game.Beatmaps.Drawables set => statusText.Padding = value; } - public Color4 BackgroundColour - { - get => background.Colour; - set => background.Colour = value; - } + private readonly OsuSpriteText statusText; + private readonly Box background; + + [Resolved] + private OsuColour colours { get; set; } = null!; + + [Resolved(CanBeNull = true)] + private OverlayColourProvider? colourProvider { get; set; } public BeatmapSetOnlineStatusPill() { - AutoSizeAxes = Axes.Both; Masking = true; Children = new Drawable[] @@ -63,7 +66,6 @@ namespace osu.Game.Beatmaps.Drawables { RelativeSizeAxes = Axes.Both, Colour = Color4.Black, - Alpha = 0.5f, }, statusText = new OsuSpriteText { @@ -74,6 +76,27 @@ namespace osu.Game.Beatmaps.Drawables }; Status = BeatmapSetOnlineStatus.None; + TextPadding = new MarginPadding { Horizontal = 5, Bottom = 1 }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + updateState(); + } + + private void updateState() + { + Alpha = Status == BeatmapSetOnlineStatus.None ? 0 : 1; + + statusText.Text = Status.GetLocalisableDescription().ToUpper(); + + if (colourProvider != null) + statusText.Colour = status == BeatmapSetOnlineStatus.Graveyard ? colourProvider.Background1 : colourProvider.Background3; + else + statusText.Colour = status == BeatmapSetOnlineStatus.Graveyard ? colours.GreySeafoamLight : Color4.Black; + + background.Colour = OsuColour.ForBeatmapSetOnlineStatus(Status) ?? colourProvider?.Light1 ?? colours.GreySeafoamLighter; } } } diff --git a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs index 880d70aec2..64412675bb 100644 --- a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs +++ b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs @@ -37,10 +37,10 @@ namespace osu.Game.Beatmaps.Drawables } [NotNull] - private readonly BeatmapInfo beatmapInfo; + private readonly IBeatmapInfo beatmapInfo; [CanBeNull] - private readonly RulesetInfo ruleset; + private readonly IRulesetInfo ruleset; [CanBeNull] private readonly IReadOnlyList mods; @@ -60,7 +60,7 @@ namespace osu.Game.Beatmaps.Drawables /// The ruleset to show the difficulty with. /// The mods to show the difficulty with. /// Whether to display a tooltip when hovered. - public DifficultyIcon([NotNull] BeatmapInfo beatmapInfo, [CanBeNull] RulesetInfo ruleset, [CanBeNull] IReadOnlyList mods, bool shouldShowTooltip = true) + public DifficultyIcon([NotNull] IBeatmapInfo beatmapInfo, [CanBeNull] IRulesetInfo ruleset, [CanBeNull] IReadOnlyList mods, bool shouldShowTooltip = true) : this(beatmapInfo, shouldShowTooltip) { this.ruleset = ruleset ?? beatmapInfo.Ruleset; @@ -73,7 +73,7 @@ namespace osu.Game.Beatmaps.Drawables /// The beatmap to show the difficulty of. /// Whether to display a tooltip when hovered. /// Whether to perform difficulty lookup (including calculation if necessary). - public DifficultyIcon([NotNull] BeatmapInfo beatmapInfo, bool shouldShowTooltip = true, bool performBackgroundDifficultyLookup = true) + public DifficultyIcon([NotNull] IBeatmapInfo beatmapInfo, bool shouldShowTooltip = true, bool performBackgroundDifficultyLookup = true) { this.beatmapInfo = beatmapInfo ?? throw new ArgumentNullException(nameof(beatmapInfo)); this.shouldShowTooltip = shouldShowTooltip; @@ -84,6 +84,9 @@ namespace osu.Game.Beatmaps.Drawables InternalChild = iconContainer = new Container { Size = new Vector2(20f) }; } + [Resolved] + private RulesetStore rulesets { get; set; } + [BackgroundDependencyLoader] private void load(OsuColour colours) { @@ -105,7 +108,7 @@ namespace osu.Game.Beatmaps.Drawables Child = background = new Box { RelativeSizeAxes = Axes.Both, - Colour = colours.ForStarDifficulty(beatmapInfo.StarDifficulty) // Default value that will be re-populated once difficulty calculation completes + Colour = colours.ForStarDifficulty(beatmapInfo.StarRating) // Default value that will be re-populated once difficulty calculation completes }, }, new ConstrainedIconContainer @@ -114,18 +117,28 @@ namespace osu.Game.Beatmaps.Drawables Origin = Anchor.Centre, RelativeSizeAxes = Axes.Both, // the null coalesce here is only present to make unit tests work (ruleset dlls aren't copied correctly for testing at the moment) - Icon = (ruleset ?? beatmapInfo.Ruleset)?.CreateInstance()?.CreateIcon() ?? new SpriteIcon { Icon = FontAwesome.Regular.QuestionCircle } + Icon = getRulesetIcon() }, }; if (performBackgroundDifficultyLookup) iconContainer.Add(new DelayedLoadUnloadWrapper(() => new DifficultyRetriever(beatmapInfo, ruleset, mods) { StarDifficulty = { BindTarget = difficultyBindable } }, 0)); else - difficultyBindable.Value = new StarDifficulty(beatmapInfo.StarDifficulty, 0); + difficultyBindable.Value = new StarDifficulty(beatmapInfo.StarRating, 0); difficultyBindable.BindValueChanged(difficulty => background.Colour = colours.ForStarDifficulty(difficulty.NewValue.Stars)); } + private Drawable getRulesetIcon() + { + int? onlineID = (ruleset ?? beatmapInfo.Ruleset).OnlineID; + + if (onlineID >= 0 && rulesets.GetRuleset(onlineID.Value)?.CreateInstance() is Ruleset rulesetInstance) + return rulesetInstance.CreateIcon(); + + return new SpriteIcon { Icon = FontAwesome.Regular.QuestionCircle }; + } + ITooltip IHasCustomTooltip.GetCustomTooltip() => new DifficultyIconTooltip(); DifficultyIconTooltipContent IHasCustomTooltip.TooltipContent => shouldShowTooltip ? new DifficultyIconTooltipContent(beatmapInfo, difficultyBindable) : null; @@ -134,8 +147,8 @@ namespace osu.Game.Beatmaps.Drawables { public readonly Bindable StarDifficulty = new Bindable(); - private readonly BeatmapInfo beatmapInfo; - private readonly RulesetInfo ruleset; + private readonly IBeatmapInfo beatmapInfo; + private readonly IRulesetInfo ruleset; private readonly IReadOnlyList mods; private CancellationTokenSource difficultyCancellation; @@ -143,7 +156,7 @@ namespace osu.Game.Beatmaps.Drawables [Resolved] private BeatmapDifficultyCache difficultyCache { get; set; } - public DifficultyRetriever(BeatmapInfo beatmapInfo, RulesetInfo ruleset, IReadOnlyList mods) + public DifficultyRetriever(IBeatmapInfo beatmapInfo, IRulesetInfo ruleset, IReadOnlyList mods) { this.beatmapInfo = beatmapInfo; this.ruleset = ruleset; diff --git a/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs b/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs index d4c9f83a0a..ec4bcbd65f 100644 --- a/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs +++ b/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs @@ -89,7 +89,7 @@ namespace osu.Game.Beatmaps.Drawables public void SetContent(DifficultyIconTooltipContent content) { - difficultyName.Text = content.BeatmapInfo.Version; + difficultyName.Text = content.BeatmapInfo.DifficultyName; starDifficulty.UnbindAll(); starDifficulty.BindTo(content.Difficulty); @@ -109,10 +109,10 @@ namespace osu.Game.Beatmaps.Drawables internal class DifficultyIconTooltipContent { - public readonly BeatmapInfo BeatmapInfo; + public readonly IBeatmapInfo BeatmapInfo; public readonly IBindable Difficulty; - public DifficultyIconTooltipContent(BeatmapInfo beatmapInfo, IBindable difficulty) + public DifficultyIconTooltipContent(IBeatmapInfo beatmapInfo, IBindable difficulty) { BeatmapInfo = beatmapInfo; Difficulty = difficulty; diff --git a/osu.Game/Beatmaps/Drawables/DifficultySpectrumDisplay.cs b/osu.Game/Beatmaps/Drawables/DifficultySpectrumDisplay.cs new file mode 100644 index 0000000000..1feaa88350 --- /dev/null +++ b/osu.Game/Beatmaps/Drawables/DifficultySpectrumDisplay.cs @@ -0,0 +1,169 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Extensions.LocalisationExtensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Rulesets; +using osuTK; + +namespace osu.Game.Beatmaps.Drawables +{ + public class DifficultySpectrumDisplay : CompositeDrawable + { + private Vector2 dotSize = new Vector2(4, 8); + + public Vector2 DotSize + { + get => dotSize; + set + { + dotSize = value; + + if (IsLoaded) + updateDotDimensions(); + } + } + + private float dotSpacing = 1; + + public float DotSpacing + { + get => dotSpacing; + set + { + dotSpacing = value; + + if (IsLoaded) + updateDotDimensions(); + } + } + + private readonly FillFlowContainer flow; + + public DifficultySpectrumDisplay(IBeatmapSetInfo beatmapSet) + { + AutoSizeAxes = Axes.Both; + + InternalChild = flow = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Spacing = new Vector2(10, 0), + Direction = FillDirection.Horizontal, + }; + + // matching web: https://github.com/ppy/osu-web/blob/d06d8c5e735eb1f48799b1654b528e9a7afb0a35/resources/assets/lib/beatmapset-panel.tsx#L127 + bool collapsed = beatmapSet.Beatmaps.Count() > 12; + + foreach (var rulesetGrouping in beatmapSet.Beatmaps.GroupBy(beatmap => beatmap.Ruleset.OnlineID)) + { + flow.Add(new RulesetDifficultyGroup(rulesetGrouping.Key, rulesetGrouping, collapsed)); + } + } + + protected override void LoadComplete() + { + base.LoadComplete(); + updateDotDimensions(); + } + + private void updateDotDimensions() + { + foreach (var group in flow) + { + group.DotSize = DotSize; + group.DotSpacing = DotSpacing; + } + } + + private class RulesetDifficultyGroup : FillFlowContainer + { + private readonly int rulesetId; + private readonly IEnumerable beatmapInfos; + private readonly bool collapsed; + + public RulesetDifficultyGroup(int rulesetId, IEnumerable beatmapInfos, bool collapsed) + { + this.rulesetId = rulesetId; + this.beatmapInfos = beatmapInfos; + this.collapsed = collapsed; + } + + public Vector2 DotSize + { + set + { + foreach (var dot in Children.OfType()) + dot.Size = value; + } + } + + public float DotSpacing + { + set => Spacing = new Vector2(value, 0); + } + + [BackgroundDependencyLoader] + private void load(RulesetStore rulesets) + { + AutoSizeAxes = Axes.Both; + Spacing = new Vector2(1, 0); + Direction = FillDirection.Horizontal; + + var icon = rulesets.GetRuleset(rulesetId)?.CreateInstance()?.CreateIcon() ?? new SpriteIcon { Icon = FontAwesome.Regular.QuestionCircle }; + Add(icon.With(i => + { + i.Size = new Vector2(14); + i.Anchor = i.Origin = Anchor.Centre; + })); + + if (!collapsed) + { + foreach (var beatmapInfo in beatmapInfos.OrderBy(bi => bi.StarRating)) + Add(new DifficultyDot(beatmapInfo.StarRating)); + } + else + { + Add(new OsuSpriteText + { + Text = beatmapInfos.Count().ToLocalisableString(@"N0"), + Font = OsuFont.Default.With(size: 12), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Padding = new MarginPadding { Bottom = 1 } + }); + } + } + } + + private class DifficultyDot : CircularContainer + { + private readonly double starDifficulty; + + public DifficultyDot(double starDifficulty) + { + this.starDifficulty = starDifficulty; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + Anchor = Origin = Anchor.Centre; + Masking = true; + + Child = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colours.ForStarDifficulty(starDifficulty) + }; + } + } + } +} diff --git a/osu.Game/Beatmaps/Drawables/GroupedDifficultyIcon.cs b/osu.Game/Beatmaps/Drawables/GroupedDifficultyIcon.cs index fcee4c2f1a..799a02579e 100644 --- a/osu.Game/Beatmaps/Drawables/GroupedDifficultyIcon.cs +++ b/osu.Game/Beatmaps/Drawables/GroupedDifficultyIcon.cs @@ -19,8 +19,8 @@ namespace osu.Game.Beatmaps.Drawables /// public class GroupedDifficultyIcon : DifficultyIcon { - public GroupedDifficultyIcon(List beatmaps, RulesetInfo ruleset, Color4 counterColour) - : base(beatmaps.OrderBy(b => b.StarDifficulty).Last(), ruleset, null, false) + public GroupedDifficultyIcon(IEnumerable beatmaps, IRulesetInfo ruleset, Color4 counterColour) + : base(beatmaps.OrderBy(b => b.StarRating).Last(), ruleset, null, false) { AddInternal(new OsuSpriteText { @@ -29,7 +29,7 @@ namespace osu.Game.Beatmaps.Drawables Padding = new MarginPadding { Left = Size.X }, Margin = new MarginPadding { Left = 2, Right = 5 }, Font = OsuFont.GetFont(size: 14, weight: FontWeight.SemiBold), - Text = beatmaps.Count.ToString(), + Text = beatmaps.Count().ToString(), Colour = counterColour, }); } diff --git a/osu.Game/Beatmaps/Drawables/UpdateableBeatmapBackgroundSprite.cs b/osu.Game/Beatmaps/Drawables/UpdateableBeatmapBackgroundSprite.cs index 8943ad350e..4100fe9586 100644 --- a/osu.Game/Beatmaps/Drawables/UpdateableBeatmapBackgroundSprite.cs +++ b/osu.Game/Beatmaps/Drawables/UpdateableBeatmapBackgroundSprite.cs @@ -12,9 +12,9 @@ namespace osu.Game.Beatmaps.Drawables /// /// Display a beatmap background from a local source, but fallback to online source if not available. /// - public class UpdateableBeatmapBackgroundSprite : ModelBackedDrawable + public class UpdateableBeatmapBackgroundSprite : ModelBackedDrawable { - public readonly Bindable Beatmap = new Bindable(); + public readonly Bindable Beatmap = new Bindable(); protected override double LoadDelay => 500; @@ -39,7 +39,7 @@ namespace osu.Game.Beatmaps.Drawables protected override double TransformDuration => 400; - protected override Drawable CreateDrawable(BeatmapInfo model) + protected override Drawable CreateDrawable(IBeatmapInfo model) { var drawable = getDrawableForModel(model); drawable.RelativeSizeAxes = Axes.Both; @@ -50,15 +50,21 @@ namespace osu.Game.Beatmaps.Drawables return drawable; } - private Drawable getDrawableForModel(BeatmapInfo model) + private Drawable getDrawableForModel(IBeatmapInfo model) { // prefer online cover where available. - if (model?.BeatmapSet?.OnlineInfo != null) - return new OnlineBeatmapSetCover(model.BeatmapSet, beatmapSetCoverType); + if (model?.BeatmapSet is IBeatmapSetOnlineInfo online) + return new OnlineBeatmapSetCover(online, beatmapSetCoverType); - return model?.ID > 0 - ? new BeatmapBackgroundSprite(beatmaps.GetWorkingBeatmap(model)) - : new BeatmapBackgroundSprite(beatmaps.DefaultBeatmap); + if (model is BeatmapInfo localModel) + { + if (localModel.BeatmapSet?.OnlineInfo != null) + return new OnlineBeatmapSetCover(localModel.BeatmapSet.OnlineInfo, beatmapSetCoverType); + + return new BeatmapBackgroundSprite(beatmaps.GetWorkingBeatmap(localModel)); + } + + return new BeatmapBackgroundSprite(beatmaps.DefaultBeatmap); } } } diff --git a/osu.Game/Beatmaps/Drawables/UpdateableOnlineBeatmapSetCover.cs b/osu.Game/Beatmaps/Drawables/UpdateableOnlineBeatmapSetCover.cs index 73f87beb58..4a6a1b888e 100644 --- a/osu.Game/Beatmaps/Drawables/UpdateableOnlineBeatmapSetCover.cs +++ b/osu.Game/Beatmaps/Drawables/UpdateableOnlineBeatmapSetCover.cs @@ -13,7 +13,7 @@ namespace osu.Game.Beatmaps.Drawables { private readonly BeatmapSetCoverType coverType; - public IBeatmapSetOnlineInfo BeatmapSet + public IBeatmapSetOnlineInfo OnlineInfo { get => Model; set => Model = value; diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs index 1dc270ee63..89541a0845 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs @@ -295,7 +295,7 @@ namespace osu.Game.Beatmaps.Formats writer.WriteLine("[Colours]"); - for (var i = 0; i < colours.Count; i++) + for (int i = 0; i < colours.Count; i++) { var comboColour = colours[i]; @@ -460,7 +460,7 @@ namespace osu.Game.Beatmaps.Formats var curveData = pathData as IHasPathWithRepeats; writer.Write(FormattableString.Invariant($"{(curveData?.RepeatCount ?? 0) + 1},")); - writer.Write(FormattableString.Invariant($"{pathData.Path.Distance},")); + writer.Write(FormattableString.Invariant($"{pathData.Path.ExpectedDistance.Value ?? pathData.Path.Distance},")); if (curveData != null) { diff --git a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs index cf6c827af5..0276abc3ff 100644 --- a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs @@ -27,7 +27,7 @@ namespace osu.Game.Beatmaps.Formats protected override void ParseStreamInto(LineBufferedReader stream, T output) { - Section section = Section.None; + Section section = Section.General; string line; @@ -47,10 +47,7 @@ namespace osu.Game.Beatmaps.Formats if (line.StartsWith('[') && line.EndsWith(']')) { if (!Enum.TryParse(line[1..^1], out section)) - { Logger.Log($"Unknown section \"{line}\" in \"{output}\""); - section = Section.None; - } OnBeginNewSection(section); continue; @@ -89,7 +86,7 @@ namespace osu.Game.Beatmaps.Formats protected string StripComments(string line) { - var index = line.AsSpan().IndexOf("//".AsSpan()); + int index = line.AsSpan().IndexOf("//".AsSpan()); if (index > 0) return line.Substring(0, index); @@ -135,7 +132,7 @@ namespace osu.Game.Beatmaps.Formats protected KeyValuePair SplitKeyVal(string line, char separator = ':') { - var split = line.Split(separator, 2); + string[] split = line.Split(separator, 2); return new KeyValuePair ( @@ -148,7 +145,6 @@ namespace osu.Game.Beatmaps.Formats protected enum Section { - None, General, Editor, Metadata, diff --git a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs index 0f15e28c00..90a96e2ac8 100644 --- a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs @@ -77,7 +77,7 @@ namespace osu.Game.Beatmaps.Formats private void handleEvents(string line) { - var depth = 0; + int depth = 0; foreach (char c in line) { @@ -104,8 +104,8 @@ namespace osu.Game.Beatmaps.Formats { case LegacyEventType.Video: { - var offset = Parsing.ParseInt(split[1]); - var path = CleanFilename(split[2]); + int offset = Parsing.ParseInt(split[1]); + string path = CleanFilename(split[2]); storyboard.GetLayer("Video").Add(new StoryboardVideo(path, offset)); break; @@ -113,11 +113,11 @@ namespace osu.Game.Beatmaps.Formats case LegacyEventType.Sprite: { - var layer = parseLayer(split[1]); + string layer = parseLayer(split[1]); var origin = parseOrigin(split[2]); - var path = CleanFilename(split[3]); - var x = Parsing.ParseFloat(split[4], Parsing.MAX_COORDINATE_VALUE); - var y = Parsing.ParseFloat(split[5], Parsing.MAX_COORDINATE_VALUE); + string path = CleanFilename(split[3]); + float x = Parsing.ParseFloat(split[4], Parsing.MAX_COORDINATE_VALUE); + float y = Parsing.ParseFloat(split[5], Parsing.MAX_COORDINATE_VALUE); storyboardSprite = new StoryboardSprite(path, origin, new Vector2(x, y)); storyboard.GetLayer(layer).Add(storyboardSprite); break; @@ -125,13 +125,13 @@ namespace osu.Game.Beatmaps.Formats case LegacyEventType.Animation: { - var layer = parseLayer(split[1]); + string layer = parseLayer(split[1]); var origin = parseOrigin(split[2]); - var path = CleanFilename(split[3]); - var x = Parsing.ParseFloat(split[4], Parsing.MAX_COORDINATE_VALUE); - var y = Parsing.ParseFloat(split[5], Parsing.MAX_COORDINATE_VALUE); - var frameCount = Parsing.ParseInt(split[6]); - var frameDelay = Parsing.ParseDouble(split[7]); + string path = CleanFilename(split[3]); + float x = Parsing.ParseFloat(split[4], Parsing.MAX_COORDINATE_VALUE); + float y = Parsing.ParseFloat(split[5], Parsing.MAX_COORDINATE_VALUE); + int frameCount = Parsing.ParseInt(split[6]); + double frameDelay = Parsing.ParseDouble(split[7]); if (FormatVersion < 6) // this is random as hell but taken straight from osu-stable. @@ -145,10 +145,10 @@ namespace osu.Game.Beatmaps.Formats case LegacyEventType.Sample: { - var time = Parsing.ParseDouble(split[1]); - var layer = parseLayer(split[2]); - var path = CleanFilename(split[3]); - var volume = split.Length > 4 ? Parsing.ParseFloat(split[4]) : 100; + double time = Parsing.ParseDouble(split[1]); + string layer = parseLayer(split[2]); + string path = CleanFilename(split[3]); + float volume = split.Length > 4 ? Parsing.ParseFloat(split[4]) : 100; storyboard.GetLayer(layer).Add(new StoryboardSampleInfo(path, time, (int)volume)); break; } @@ -159,24 +159,24 @@ namespace osu.Game.Beatmaps.Formats if (depth < 2) timelineGroup = storyboardSprite?.TimelineGroup; - var commandType = split[0]; + string commandType = split[0]; switch (commandType) { case "T": { - var triggerName = split[1]; - var startTime = split.Length > 2 ? Parsing.ParseDouble(split[2]) : double.MinValue; - var endTime = split.Length > 3 ? Parsing.ParseDouble(split[3]) : double.MaxValue; - var groupNumber = split.Length > 4 ? Parsing.ParseInt(split[4]) : 0; + string triggerName = split[1]; + double startTime = split.Length > 2 ? Parsing.ParseDouble(split[2]) : double.MinValue; + double endTime = split.Length > 3 ? Parsing.ParseDouble(split[3]) : double.MaxValue; + int groupNumber = split.Length > 4 ? Parsing.ParseInt(split[4]) : 0; timelineGroup = storyboardSprite?.AddTrigger(triggerName, startTime, endTime, groupNumber); break; } case "L": { - var startTime = Parsing.ParseDouble(split[1]); - var repeatCount = Parsing.ParseInt(split[2]); + double startTime = Parsing.ParseDouble(split[1]); + int repeatCount = Parsing.ParseInt(split[2]); timelineGroup = storyboardSprite?.AddLoop(startTime, Math.Max(0, repeatCount - 1)); break; } @@ -187,51 +187,51 @@ namespace osu.Game.Beatmaps.Formats split[3] = split[2]; var easing = (Easing)Parsing.ParseInt(split[1]); - var startTime = Parsing.ParseDouble(split[2]); - var endTime = Parsing.ParseDouble(split[3]); + double startTime = Parsing.ParseDouble(split[2]); + double endTime = Parsing.ParseDouble(split[3]); switch (commandType) { case "F": { - var startValue = Parsing.ParseFloat(split[4]); - var endValue = split.Length > 5 ? Parsing.ParseFloat(split[5]) : startValue; + float startValue = Parsing.ParseFloat(split[4]); + float endValue = split.Length > 5 ? Parsing.ParseFloat(split[5]) : startValue; timelineGroup?.Alpha.Add(easing, startTime, endTime, startValue, endValue); break; } case "S": { - var startValue = Parsing.ParseFloat(split[4]); - var endValue = split.Length > 5 ? Parsing.ParseFloat(split[5]) : startValue; + float startValue = Parsing.ParseFloat(split[4]); + float endValue = split.Length > 5 ? Parsing.ParseFloat(split[5]) : startValue; timelineGroup?.Scale.Add(easing, startTime, endTime, startValue, endValue); break; } case "V": { - var startX = Parsing.ParseFloat(split[4]); - var startY = Parsing.ParseFloat(split[5]); - var endX = split.Length > 6 ? Parsing.ParseFloat(split[6]) : startX; - var endY = split.Length > 7 ? Parsing.ParseFloat(split[7]) : startY; + float startX = Parsing.ParseFloat(split[4]); + float startY = Parsing.ParseFloat(split[5]); + float endX = split.Length > 6 ? Parsing.ParseFloat(split[6]) : startX; + float endY = split.Length > 7 ? Parsing.ParseFloat(split[7]) : startY; timelineGroup?.VectorScale.Add(easing, startTime, endTime, new Vector2(startX, startY), new Vector2(endX, endY)); break; } case "R": { - var startValue = Parsing.ParseFloat(split[4]); - var endValue = split.Length > 5 ? Parsing.ParseFloat(split[5]) : startValue; + float startValue = Parsing.ParseFloat(split[4]); + float endValue = split.Length > 5 ? Parsing.ParseFloat(split[5]) : startValue; timelineGroup?.Rotation.Add(easing, startTime, endTime, MathUtils.RadiansToDegrees(startValue), MathUtils.RadiansToDegrees(endValue)); break; } case "M": { - var startX = Parsing.ParseFloat(split[4]); - var startY = Parsing.ParseFloat(split[5]); - var endX = split.Length > 6 ? Parsing.ParseFloat(split[6]) : startX; - var endY = split.Length > 7 ? Parsing.ParseFloat(split[7]) : startY; + float startX = Parsing.ParseFloat(split[4]); + float startY = Parsing.ParseFloat(split[5]); + float endX = split.Length > 6 ? Parsing.ParseFloat(split[6]) : startX; + float endY = split.Length > 7 ? Parsing.ParseFloat(split[7]) : startY; timelineGroup?.X.Add(easing, startTime, endTime, startX, endX); timelineGroup?.Y.Add(easing, startTime, endTime, startY, endY); break; @@ -239,28 +239,28 @@ namespace osu.Game.Beatmaps.Formats case "MX": { - var startValue = Parsing.ParseFloat(split[4]); - var endValue = split.Length > 5 ? Parsing.ParseFloat(split[5]) : startValue; + float startValue = Parsing.ParseFloat(split[4]); + float endValue = split.Length > 5 ? Parsing.ParseFloat(split[5]) : startValue; timelineGroup?.X.Add(easing, startTime, endTime, startValue, endValue); break; } case "MY": { - var startValue = Parsing.ParseFloat(split[4]); - var endValue = split.Length > 5 ? Parsing.ParseFloat(split[5]) : startValue; + float startValue = Parsing.ParseFloat(split[4]); + float endValue = split.Length > 5 ? Parsing.ParseFloat(split[5]) : startValue; timelineGroup?.Y.Add(easing, startTime, endTime, startValue, endValue); break; } case "C": { - var startRed = Parsing.ParseFloat(split[4]); - var startGreen = Parsing.ParseFloat(split[5]); - var startBlue = Parsing.ParseFloat(split[6]); - var endRed = split.Length > 7 ? Parsing.ParseFloat(split[7]) : startRed; - var endGreen = split.Length > 8 ? Parsing.ParseFloat(split[8]) : startGreen; - var endBlue = split.Length > 9 ? Parsing.ParseFloat(split[9]) : startBlue; + float startRed = Parsing.ParseFloat(split[4]); + float startGreen = Parsing.ParseFloat(split[5]); + float startBlue = Parsing.ParseFloat(split[6]); + float endRed = split.Length > 7 ? Parsing.ParseFloat(split[7]) : startRed; + float endGreen = split.Length > 8 ? Parsing.ParseFloat(split[8]) : startGreen; + float endBlue = split.Length > 9 ? Parsing.ParseFloat(split[9]) : startBlue; timelineGroup?.Colour.Add(easing, startTime, endTime, new Color4(startRed / 255f, startGreen / 255f, startBlue / 255f, 1), new Color4(endRed / 255f, endGreen / 255f, endBlue / 255f, 1)); @@ -269,7 +269,7 @@ namespace osu.Game.Beatmaps.Formats case "P": { - var type = split[4]; + string type = split[4]; switch (type) { diff --git a/osu.Game/Beatmaps/Formats/Parsing.cs b/osu.Game/Beatmaps/Formats/Parsing.cs index c4795a6931..4d512fdeed 100644 --- a/osu.Game/Beatmaps/Formats/Parsing.cs +++ b/osu.Game/Beatmaps/Formats/Parsing.cs @@ -17,7 +17,7 @@ namespace osu.Game.Beatmaps.Formats public static float ParseFloat(string input, float parseLimit = (float)MAX_PARSE_VALUE) { - var output = float.Parse(input, CultureInfo.InvariantCulture); + float output = float.Parse(input, CultureInfo.InvariantCulture); if (output < -parseLimit) throw new OverflowException("Value is too low"); if (output > parseLimit) throw new OverflowException("Value is too high"); @@ -29,7 +29,7 @@ namespace osu.Game.Beatmaps.Formats public static double ParseDouble(string input, double parseLimit = MAX_PARSE_VALUE) { - var output = double.Parse(input, CultureInfo.InvariantCulture); + double output = double.Parse(input, CultureInfo.InvariantCulture); if (output < -parseLimit) throw new OverflowException("Value is too low"); if (output > parseLimit) throw new OverflowException("Value is too high"); @@ -41,7 +41,7 @@ namespace osu.Game.Beatmaps.Formats public static int ParseInt(string input, int parseLimit = (int)MAX_PARSE_VALUE) { - var output = int.Parse(input, CultureInfo.InvariantCulture); + int output = int.Parse(input, CultureInfo.InvariantCulture); if (output < -parseLimit) throw new OverflowException("Value is too low"); if (output > parseLimit) throw new OverflowException("Value is too high"); diff --git a/osu.Game/Beatmaps/IBeatmapInfo.cs b/osu.Game/Beatmaps/IBeatmapInfo.cs index 3d51c5d4b6..d206cfaaed 100644 --- a/osu.Game/Beatmaps/IBeatmapInfo.cs +++ b/osu.Game/Beatmaps/IBeatmapInfo.cs @@ -11,7 +11,7 @@ namespace osu.Game.Beatmaps /// /// A single beatmap difficulty. /// - public interface IBeatmapInfo : IHasOnlineID + public interface IBeatmapInfo : IHasOnlineID { /// /// The user-specified name given to this beatmap. diff --git a/osu.Game/Beatmaps/BeatmapOnlineInfo.cs b/osu.Game/Beatmaps/IBeatmapOnlineInfo.cs similarity index 51% rename from osu.Game/Beatmaps/BeatmapOnlineInfo.cs rename to osu.Game/Beatmaps/IBeatmapOnlineInfo.cs index bfeacd9bfc..385646eeaa 100644 --- a/osu.Game/Beatmaps/BeatmapOnlineInfo.cs +++ b/osu.Game/Beatmaps/IBeatmapOnlineInfo.cs @@ -1,31 +1,40 @@ -// 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. +#nullable enable + namespace osu.Game.Beatmaps { /// - /// Beatmap info retrieved for previewing locally without having the beatmap downloaded. + /// Beatmap info retrieved for previewing locally. /// - public class BeatmapOnlineInfo + public interface IBeatmapOnlineInfo { + /// + /// The max combo of this beatmap. + /// + int? MaxCombo { get; } + /// /// The amount of circles in this beatmap. /// - public int CircleCount { get; set; } + public int CircleCount { get; } /// /// The amount of sliders in this beatmap. /// - public int SliderCount { get; set; } + public int SliderCount { get; } /// /// The amount of plays this beatmap has. /// - public int PlayCount { get; set; } + public int PlayCount { get; } /// /// The amount of passes this beatmap has. /// - public int PassCount { get; set; } + public int PassCount { get; } + + APIFailTimes? FailTimes { get; } } } diff --git a/osu.Game/Beatmaps/IBeatmapSetInfo.cs b/osu.Game/Beatmaps/IBeatmapSetInfo.cs index 0cfb0c4242..20c46d9063 100644 --- a/osu.Game/Beatmaps/IBeatmapSetInfo.cs +++ b/osu.Game/Beatmaps/IBeatmapSetInfo.cs @@ -12,7 +12,7 @@ namespace osu.Game.Beatmaps /// /// A representation of a collection of beatmap difficulties, generally packaged as an ".osz" archive. /// - public interface IBeatmapSetInfo : IHasOnlineID + public interface IBeatmapSetInfo : IHasOnlineID { /// /// The date when this beatmap was imported. diff --git a/osu.Game/Beatmaps/IBeatmapSetOnlineInfo.cs b/osu.Game/Beatmaps/IBeatmapSetOnlineInfo.cs index b9800bc2e6..6def6ec21d 100644 --- a/osu.Game/Beatmaps/IBeatmapSetOnlineInfo.cs +++ b/osu.Game/Beatmaps/IBeatmapSetOnlineInfo.cs @@ -15,87 +15,92 @@ namespace osu.Game.Beatmaps /// /// The date this beatmap set was submitted to the online listing. /// - DateTimeOffset Submitted { get; set; } + DateTimeOffset Submitted { get; } /// /// The date this beatmap set was ranked. /// - DateTimeOffset? Ranked { get; set; } + DateTimeOffset? Ranked { get; } /// /// The date this beatmap set was last updated. /// - DateTimeOffset? LastUpdated { get; set; } + DateTimeOffset? LastUpdated { get; } /// /// The status of this beatmap set. /// - BeatmapSetOnlineStatus Status { get; set; } + BeatmapSetOnlineStatus Status { get; } /// /// Whether or not this beatmap set has explicit content. /// - bool HasExplicitContent { get; set; } + bool HasExplicitContent { get; } /// /// Whether or not this beatmap set has a background video. /// - bool HasVideo { get; set; } + bool HasVideo { get; } /// /// Whether or not this beatmap set has a storyboard. /// - bool HasStoryboard { get; set; } + bool HasStoryboard { get; } /// /// The different sizes of cover art for this beatmap set. /// - BeatmapSetOnlineCovers Covers { get; set; } + BeatmapSetOnlineCovers Covers { get; } /// /// A small sample clip of this beatmap set's song. /// - string Preview { get; set; } + string Preview { get; } /// /// The beats per minute of this beatmap set's song. /// - double BPM { get; set; } + double BPM { get; } /// /// The amount of plays this beatmap set has. /// - int PlayCount { get; set; } + int PlayCount { get; } /// /// The amount of people who have favourited this beatmap set. /// - int FavouriteCount { get; set; } + int FavouriteCount { get; } /// /// Whether this beatmap set has been favourited by the current user. /// - bool HasFavourited { get; set; } + bool HasFavourited { get; } /// /// The availability of this beatmap set. /// - BeatmapSetOnlineAvailability Availability { get; set; } + BeatmapSetOnlineAvailability Availability { get; } /// /// The song genre of this beatmap set. /// - BeatmapSetOnlineGenre Genre { get; set; } + BeatmapSetOnlineGenre Genre { get; } /// /// The song language of this beatmap set. /// - BeatmapSetOnlineLanguage Language { get; set; } + BeatmapSetOnlineLanguage Language { get; } /// /// The track ID of this beatmap set. /// Non-null only if the track is linked to a featured artist track entry. /// - int? TrackId { get; set; } + int? TrackId { get; } + + /// + /// Total vote counts of user ratings on a scale of 0..10 where 0 is unused (probably will be fixed at API?). + /// + int[]? Ratings { get; } } } diff --git a/osu.Game/Beatmaps/MetadataUtils.cs b/osu.Game/Beatmaps/MetadataUtils.cs index 56f5e3fe35..b27c59b4de 100644 --- a/osu.Game/Beatmaps/MetadataUtils.cs +++ b/osu.Game/Beatmaps/MetadataUtils.cs @@ -35,7 +35,7 @@ namespace osu.Game.Beatmaps var stringBuilder = new StringBuilder(str.Length); - foreach (var c in str) + foreach (char c in str) { if (IsRomanised(c)) stringBuilder.Append(c); diff --git a/osu.Game/Beatmaps/WorkingBeatmapCache.cs b/osu.Game/Beatmaps/WorkingBeatmapCache.cs index cf83345e2a..65f84984c2 100644 --- a/osu.Game/Beatmaps/WorkingBeatmapCache.cs +++ b/osu.Game/Beatmaps/WorkingBeatmapCache.cs @@ -206,7 +206,7 @@ namespace osu.Game.Beatmaps { var decoder = Decoder.GetDecoder(stream); - var storyboardFilename = BeatmapSetInfo?.Files.FirstOrDefault(f => f.Filename.EndsWith(".osb", StringComparison.OrdinalIgnoreCase))?.Filename; + string storyboardFilename = BeatmapSetInfo?.Files.FirstOrDefault(f => f.Filename.EndsWith(".osb", StringComparison.OrdinalIgnoreCase))?.Filename; // todo: support loading from both set-wide storyboard *and* beatmap specific. if (string.IsNullOrEmpty(storyboardFilename)) diff --git a/osu.Game/Collections/CollectionManager.cs b/osu.Game/Collections/CollectionManager.cs index 6f9d9cd8a8..9ff92032b7 100644 --- a/osu.Game/Collections/CollectionManager.cs +++ b/osu.Game/Collections/CollectionManager.cs @@ -250,7 +250,7 @@ namespace osu.Game.Collections /// private void backgroundSave() { - var current = Interlocked.Increment(ref lastSave); + int current = Interlocked.Increment(ref lastSave); Task.Delay(100).ContinueWith(task => { if (current != lastSave) @@ -270,7 +270,7 @@ namespace osu.Game.Collections // This is NOT thread-safe!! try { - var tempPath = Path.GetTempFileName(); + string tempPath = Path.GetTempFileName(); using (var ms = new MemoryStream()) { @@ -296,8 +296,8 @@ namespace osu.Game.Collections using (var fs = File.OpenWrite(tempPath)) ms.WriteTo(fs); - var databasePath = storage.GetFullPath(database_name); - var databaseBackupPath = storage.GetFullPath(database_backup_name); + string databasePath = storage.GetFullPath(database_name); + string databaseBackupPath = storage.GetFullPath(database_backup_name); // Back up the existing database, clearing any existing backup. if (File.Exists(databaseBackupPath)) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index acb4a9ca02..1beef89b51 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -160,12 +160,12 @@ namespace osu.Game.Configuration public void Migrate() { // arrives as 2020.123.0 - var rawVersion = Get(OsuSetting.Version); + string rawVersion = Get(OsuSetting.Version); if (rawVersion.Length < 6) return; - var pieces = rawVersion.Split('.'); + string[] pieces = rawVersion.Split('.'); // on a fresh install or when coming from a non-release build, execution will end here. // we don't want to run migrations in such cases. @@ -218,7 +218,9 @@ namespace osu.Game.Configuration rawValue: skinName, name: SkinSettingsStrings.SkinSectionHeader, value: skinName, - shortcut: $"{GlobalActionKeyBindingStrings.RandomSkin}: {LookupKeyBindings(GlobalAction.RandomSkin)}" + shortcut: new TranslatableString(@"_", @"{0}: {1}", + GlobalActionKeyBindingStrings.RandomSkin, + LookupKeyBindings(GlobalAction.RandomSkin)) ); }), new TrackedSetting(OsuSetting.UIScale, scale => new SettingDescription( diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index 9c777d324b..49c9ac5c65 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -32,7 +32,7 @@ namespace osu.Game.Database /// The associated file join type. public abstract class ArchiveModelManager : IModelManager, IModelFileManager where TModel : class, IHasFiles, IHasPrimaryKey, ISoftDelete - where TFileModel : class, INamedFileInfo, new() + where TFileModel : class, INamedFileInfo, IHasPrimaryKey, new() { private const int import_queue_request_concurrency = 1; @@ -315,25 +315,29 @@ namespace osu.Game.Database /// /// In the case of no matching files, a hash will be generated from the passed archive's . /// - protected virtual string ComputeHash(TModel item, ArchiveReader reader = null) + protected virtual string ComputeHash(TModel item) { - if (reader != null) - // fast hashing for cases where the item's files may not be populated. - return computeHashFast(reader); + var hashableFiles = item.Files + .Where(f => HashableFileTypes.Any(ext => f.Filename.EndsWith(ext, StringComparison.OrdinalIgnoreCase))) + .OrderBy(f => f.Filename) + .ToArray(); - // for now, concatenate all hashable files in the set to create a unique hash. - MemoryStream hashable = new MemoryStream(); - - foreach (TFileModel file in item.Files.Where(f => HashableFileTypes.Any(ext => f.Filename.EndsWith(ext, StringComparison.OrdinalIgnoreCase))).OrderBy(f => f.Filename)) + if (hashableFiles.Length > 0) { - using (Stream s = Files.Store.GetStream(file.FileInfo.StoragePath)) - s.CopyTo(hashable); + // for now, concatenate all hashable files in the set to create a unique hash. + MemoryStream hashable = new MemoryStream(); + + foreach (TFileModel file in hashableFiles) + { + using (Stream s = Files.Store.GetStream(file.FileInfo.StoragePath)) + s.CopyTo(hashable); + } + + if (hashable.Length > 0) + return hashable.ComputeSHA2Hash(); } - if (hashable.Length > 0) - return hashable.ComputeSHA2Hash(); - - return item.Hash; + return generateFallbackHash(); } /// @@ -393,7 +397,7 @@ namespace osu.Game.Database LogForModel(item, @"Beginning import..."); item.Files = archive != null ? createFileInfos(archive, Files) : new List(); - item.Hash = ComputeHash(item, archive); + item.Hash = ComputeHash(item); await Populate(item, archive, cancellationToken).ConfigureAwait(false); @@ -462,10 +466,12 @@ namespace osu.Game.Database if (retrievedItem == null) throw new ArgumentException(@"Specified model could not be found", nameof(item)); - using (var outputStream = exportStorage.GetStream($"{getValidFilename(item.ToString())}{HandledExtensions.First()}", FileAccess.Write, FileMode.Create)) - ExportModelTo(retrievedItem, outputStream); + string filename = $"{getValidFilename(item.ToString())}{HandledExtensions.First()}"; - exportStorage.OpenInNativeExplorer(); + using (var stream = exportStorage.GetStream(filename, FileAccess.Write, FileMode.Create)) + ExportModelTo(retrievedItem, stream); + + exportStorage.PresentFileExternally(filename); } /// @@ -514,9 +520,12 @@ namespace osu.Game.Database { Files.Dereference(file.FileInfo); - // This shouldn't be required, but here for safety in case the provided TModel is not being change tracked - // Definitely can be removed once we rework the database backend. - usage.Context.Set().Remove(file); + if (file.ID > 0) + { + // This shouldn't be required, but here for safety in case the provided TModel is not being change tracked + // Definitely can be removed once we rework the database backend. + usage.Context.Set().Remove(file); + } } model.Files.Remove(file); @@ -538,9 +547,10 @@ namespace osu.Game.Database Filename = filename, FileInfo = Files.Add(contents) }); - - Update(model); } + + if (model.ID > 0) + Update(model); } /// @@ -673,7 +683,7 @@ namespace osu.Game.Database { MemoryStream hashable = new MemoryStream(); - foreach (var file in reader.Filenames.Where(f => HashableFileTypes.Any(ext => f.EndsWith(ext, StringComparison.OrdinalIgnoreCase))).OrderBy(f => f)) + foreach (string file in reader.Filenames.Where(f => HashableFileTypes.Any(ext => f.EndsWith(ext, StringComparison.OrdinalIgnoreCase))).OrderBy(f => f)) { using (Stream s = reader.GetStream(file)) s.CopyTo(hashable); @@ -682,7 +692,7 @@ namespace osu.Game.Database if (hashable.Length > 0) return hashable.ComputeSHA2Hash(); - return reader.Name.ComputeSHA2Hash(); + return generateFallbackHash(); } /// @@ -895,6 +905,14 @@ namespace osu.Game.Database #endregion + private static string generateFallbackHash() + { + // if a hash could no be generated from file content, presume a unique / new import. + // therefore, let's use a guaranteed unique hash. + // this doesn't follow the SHA2 hashing schema intentionally, so such entries on the data store can be identified. + return Guid.NewGuid().ToString(); + } + private string getValidFilename(string filename) { foreach (char c in Path.GetInvalidFileNameChars()) diff --git a/osu.Game/Database/DatabaseBackedStore.cs b/osu.Game/Database/DatabaseBackedStore.cs index e6b6a0ac2f..a11efba54b 100644 --- a/osu.Game/Database/DatabaseBackedStore.cs +++ b/osu.Game/Database/DatabaseBackedStore.cs @@ -27,7 +27,7 @@ namespace osu.Game.Database if (context.Entry(obj).State != EntityState.Detached) return; - var id = obj.ID; + int id = obj.ID; var foundObject = lookupSource?.SingleOrDefault(t => t.ID == id) ?? context.Find(id); if (foundObject != null) obj = foundObject; diff --git a/osu.Game/Database/IHasOnlineID.cs b/osu.Game/Database/IHasOnlineID.cs index 6e2be7e1f9..4e83ed8876 100644 --- a/osu.Game/Database/IHasOnlineID.cs +++ b/osu.Game/Database/IHasOnlineID.cs @@ -5,15 +5,15 @@ namespace osu.Game.Database { - public interface IHasOnlineID + public interface IHasOnlineID { /// - /// The server-side ID representing this instance, if one exists. Any value 0 or less denotes a missing ID. + /// The server-side ID representing this instance, if one exists. Any value 0 or less denotes a missing ID (except in special cases where autoincrement is not used, like rulesets). /// /// /// Generally we use -1 when specifying "missing" in code, but values of 0 are also considered missing as the online source /// is generally a MySQL autoincrement value, which can never be 0. /// - int OnlineID { get; } + T OnlineID { get; } } } diff --git a/osu.Game/Database/ModelDownloader.cs b/osu.Game/Database/ModelDownloader.cs index e613b39b6b..12bf5e9ce7 100644 --- a/osu.Game/Database/ModelDownloader.cs +++ b/osu.Game/Database/ModelDownloader.cs @@ -30,7 +30,7 @@ namespace osu.Game.Database private readonly IModelManager modelManager; private readonly IAPIProvider api; - private readonly List> currentDownloads = new List>(); + protected readonly List> CurrentDownloads = new List>(); protected ModelDownloader(IModelManager modelManager, IAPIProvider api, IIpcHost importHost = null) { @@ -74,7 +74,7 @@ namespace osu.Game.Database if (!imported.Any()) downloadFailed.Value = new WeakReference>(request); - currentDownloads.Remove(request); + CurrentDownloads.Remove(request); }, TaskCreationOptions.LongRunning); }; @@ -86,7 +86,7 @@ namespace osu.Game.Database return true; }; - currentDownloads.Add(request); + CurrentDownloads.Add(request); PostNotification?.Invoke(notification); api.PerformAsync(request); @@ -96,7 +96,7 @@ namespace osu.Game.Database void triggerFailure(Exception error) { - currentDownloads.Remove(request); + CurrentDownloads.Remove(request); downloadFailed.Value = new WeakReference>(request); @@ -107,7 +107,7 @@ namespace osu.Game.Database } } - public ArchiveDownloadRequest GetExistingDownload(TModel model) => currentDownloads.Find(r => r.Model.Equals(model)); + public abstract ArchiveDownloadRequest GetExistingDownload(TModel model); private bool canDownload(TModel model) => GetExistingDownload(model) == null && api != null; diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index 3d0bb34dc1..85f7dd6b89 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -6,9 +6,11 @@ using System.Linq; using System.Threading; using osu.Framework.Allocation; using osu.Framework.Development; +using osu.Framework.Input.Bindings; using osu.Framework.Logging; using osu.Framework.Platform; using osu.Framework.Statistics; +using osu.Game.Input.Bindings; using osu.Game.Models; using Realms; @@ -32,8 +34,9 @@ namespace osu.Game.Database /// Version history: /// 6 First tracked version (~20211018) /// 7 Changed OnlineID fields to non-nullable to add indexing support (20211018) + /// 8 Rebind scroll adjust keys to not have control modifier (20211029) /// - private const int schema_version = 7; + private const int schema_version = 8; /// /// Lock object which is held during sections, blocking context creation during blocking periods. @@ -148,6 +151,21 @@ namespace osu.Game.Database private void onMigration(Migration migration, ulong lastSchemaVersion) { + if (lastSchemaVersion < 8) + { + // Ctrl -/+ now adjusts UI scale so let's clear any bindings which overlap these combinations. + // New defaults will be populated by the key store afterwards. + var keyBindings = migration.NewRealm.All(); + + var increaseSpeedBinding = keyBindings.FirstOrDefault(k => k.ActionInt == (int)GlobalAction.IncreaseScrollSpeed); + if (increaseSpeedBinding != null && increaseSpeedBinding.KeyCombination.Keys.SequenceEqual(new[] { InputKey.Control, InputKey.Plus })) + migration.NewRealm.Remove(increaseSpeedBinding); + + var decreaseSpeedBinding = keyBindings.FirstOrDefault(k => k.ActionInt == (int)GlobalAction.DecreaseScrollSpeed); + if (decreaseSpeedBinding != null && decreaseSpeedBinding.KeyCombination.Keys.SequenceEqual(new[] { InputKey.Control, InputKey.Minus })) + migration.NewRealm.Remove(decreaseSpeedBinding); + } + if (lastSchemaVersion < 7) { convertOnlineIDs(); @@ -156,7 +174,7 @@ namespace osu.Game.Database void convertOnlineIDs() where T : RealmObject { - var className = typeof(T).Name.Replace(@"Realm", string.Empty); + string className = typeof(T).Name.Replace(@"Realm", string.Empty); // version was not bumped when the beatmap/ruleset models were added // therefore we must manually check for their presence to avoid throwing on the `DynamicApi` calls. @@ -170,8 +188,8 @@ namespace osu.Game.Database for (int i = 0; i < itemCount; i++) { - var oldItem = oldItems.ElementAt(i); - var newItem = newItems.ElementAt(i); + dynamic? oldItem = oldItems.ElementAt(i); + dynamic? newItem = newItems.ElementAt(i); long? nullableOnlineID = oldItem?.OnlineID; newItem.OnlineID = (int)(nullableOnlineID ?? -1); diff --git a/osu.Game/Database/StableImportManager.cs b/osu.Game/Database/StableImportManager.cs index 63a6db35c0..fe8c14c085 100644 --- a/osu.Game/Database/StableImportManager.cs +++ b/osu.Game/Database/StableImportManager.cs @@ -78,7 +78,7 @@ namespace osu.Game.Database var taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); Schedule(() => dialogOverlay.Push(new StableDirectoryLocationDialog(taskCompletionSource))); - var stablePath = await taskCompletionSource.Task.ConfigureAwait(false); + string stablePath = await taskCompletionSource.Task.ConfigureAwait(false); return cachedStorage = new StableStorage(stablePath, desktopGameHost); } diff --git a/osu.Game/Database/UserLookupCache.cs b/osu.Game/Database/UserLookupCache.cs index ff81637efb..3626f5e83a 100644 --- a/osu.Game/Database/UserLookupCache.cs +++ b/osu.Game/Database/UserLookupCache.cs @@ -37,7 +37,7 @@ namespace osu.Game.Database { var userLookupTasks = new List>(); - foreach (var u in userIds) + foreach (int u in userIds) { userLookupTasks.Add(GetUserAsync(u, token).ContinueWith(task => { diff --git a/osu.Game/Graphics/Backgrounds/Triangles.cs b/osu.Game/Graphics/Backgrounds/Triangles.cs index ab8763e576..450c93f37c 100644 --- a/osu.Game/Graphics/Backgrounds/Triangles.cs +++ b/osu.Game/Graphics/Backgrounds/Triangles.cs @@ -214,7 +214,7 @@ namespace osu.Game.Graphics.Backgrounds float u1 = 1 - nextRandom(); //uniform(0,1] random floats float u2 = 1 - nextRandom(); float randStdNormal = (float)(Math.Sqrt(-2.0 * Math.Log(u1)) * Math.Sin(2.0 * Math.PI * u2)); // random normal(0,1) - var scale = Math.Max(triangleScale * (mean + std_dev * randStdNormal), 0.1f); // random normal(mean,stdDev^2) + float scale = Math.Max(triangleScale * (mean + std_dev * randStdNormal), 0.1f); // random normal(mean,stdDev^2) return new TriangleParticle { Scale = scale }; } diff --git a/osu.Game/Graphics/Containers/ConstrainedIconContainer.cs b/osu.Game/Graphics/Containers/ConstrainedIconContainer.cs index 9d3f342a70..9e1af1944c 100644 --- a/osu.Game/Graphics/Containers/ConstrainedIconContainer.cs +++ b/osu.Game/Graphics/Containers/ConstrainedIconContainer.cs @@ -43,7 +43,7 @@ namespace osu.Game.Graphics.Containers // - If we were to use RelativeSize/FillMode, we'd need to set the Icon's RelativeSizeAxes directly. // We can't do this because we would need access to AutoSizeAxes to set it to none. // Other issues come up along the way too, so it's not a good solution. - var fitScale = Math.Min(DrawSize.X / InternalChild.DrawSize.X, DrawSize.Y / InternalChild.DrawSize.Y); + float fitScale = Math.Min(DrawSize.X / InternalChild.DrawSize.X, DrawSize.Y / InternalChild.DrawSize.Y); InternalChild.Scale = new Vector2(fitScale); InternalChild.Anchor = Anchor.Centre; InternalChild.Origin = Anchor.Centre; diff --git a/osu.Game/Graphics/Containers/LinkFlowContainer.cs b/osu.Game/Graphics/Containers/LinkFlowContainer.cs index 21c1d70d45..0b43c16ebe 100644 --- a/osu.Game/Graphics/Containers/LinkFlowContainer.cs +++ b/osu.Game/Graphics/Containers/LinkFlowContainer.cs @@ -9,6 +9,7 @@ using osu.Framework.Graphics.Sprites; using System.Collections.Generic; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Localisation; using osu.Framework.Platform; using osu.Game.Graphics.Sprites; @@ -58,39 +59,34 @@ namespace osu.Game.Graphics.Containers } public void AddLink(string text, string url, Action creationParameters = null) => - createLink(AddText(text, creationParameters), new LinkDetails(LinkAction.External, url), url); + createLink(CreateChunkFor(text, true, CreateSpriteText, creationParameters), new LinkDetails(LinkAction.External, url), url); public void AddLink(string text, Action action, string tooltipText = null, Action creationParameters = null) - => createLink(AddText(text, creationParameters), new LinkDetails(LinkAction.Custom, string.Empty), tooltipText, action); + => createLink(CreateChunkFor(text, true, CreateSpriteText, creationParameters), new LinkDetails(LinkAction.Custom, string.Empty), tooltipText, action); public void AddLink(string text, LinkAction action, string argument, string tooltipText = null, Action creationParameters = null) - => createLink(AddText(text, creationParameters), new LinkDetails(action, argument), tooltipText); + => createLink(CreateChunkFor(text, true, CreateSpriteText, creationParameters), new LinkDetails(action, argument), tooltipText); public void AddLink(LocalisableString text, LinkAction action, string argument, string tooltipText = null, Action creationParameters = null) { var spriteText = new OsuSpriteText { Text = text }; AddText(spriteText, creationParameters); - createLink(spriteText.Yield(), new LinkDetails(action, argument), tooltipText); + RemoveInternal(spriteText); // TODO: temporary, will go away when TextParts support localisation properly. + createLink(new TextPartManual(spriteText.Yield()), new LinkDetails(action, argument), tooltipText); } public void AddLink(IEnumerable text, LinkAction action, string linkArgument, string tooltipText = null) { - foreach (var t in text) - AddArbitraryDrawable(t); - - createLink(text, new LinkDetails(action, linkArgument), tooltipText); + createLink(new TextPartManual(text), new LinkDetails(action, linkArgument), tooltipText); } public void AddUserLink(User user, Action creationParameters = null) - => createLink(AddText(user.Username, creationParameters), new LinkDetails(LinkAction.OpenUserProfile, user.Id.ToString()), "view profile"); + => createLink(CreateChunkFor(user.Username, true, CreateSpriteText, creationParameters), new LinkDetails(LinkAction.OpenUserProfile, user.Id.ToString()), "view profile"); - private void createLink(IEnumerable drawables, LinkDetails link, string tooltipText, Action action = null) + private void createLink(ITextPart textPart, LinkDetails link, LocalisableString tooltipText, Action action = null) { - var linkCompiler = CreateLinkCompiler(drawables.OfType()); - linkCompiler.RelativeSizeAxes = Axes.Both; - linkCompiler.TooltipText = tooltipText; - linkCompiler.Action = () => + Action onClickAction = () => { if (action != null) action(); @@ -101,10 +97,41 @@ namespace osu.Game.Graphics.Containers host.OpenUrlExternally(link.Argument); }; - AddInternal(linkCompiler); + AddPart(new TextLink(textPart, tooltipText, onClickAction)); } - protected virtual DrawableLinkCompiler CreateLinkCompiler(IEnumerable parts) => new DrawableLinkCompiler(parts); + private class TextLink : TextPart + { + private readonly ITextPart innerPart; + private readonly LocalisableString tooltipText; + private readonly Action action; + + public TextLink(ITextPart innerPart, LocalisableString tooltipText, Action action) + { + this.innerPart = innerPart; + this.tooltipText = tooltipText; + this.action = action; + } + + protected override IEnumerable CreateDrawablesFor(TextFlowContainer textFlowContainer) + { + var linkFlowContainer = (LinkFlowContainer)textFlowContainer; + + innerPart.RecreateDrawablesFor(linkFlowContainer); + var drawables = innerPart.Drawables.ToList(); + + drawables.Add(linkFlowContainer.CreateLinkCompiler(innerPart).With(c => + { + c.RelativeSizeAxes = Axes.Both; + c.TooltipText = tooltipText; + c.Action = action; + })); + + return drawables; + } + } + + protected virtual DrawableLinkCompiler CreateLinkCompiler(ITextPart textPart) => new DrawableLinkCompiler(textPart); // We want the compilers to always be visible no matter where they are, so RelativeSizeAxes is used. // However due to https://github.com/ppy/osu-framework/issues/2073, it's possible for the compilers to be relative size in the flow's auto-size axes - an unsupported operation. diff --git a/osu.Game/Graphics/Containers/LogoTrackingContainer.cs b/osu.Game/Graphics/Containers/LogoTrackingContainer.cs index dadd7d5240..f89f3a5e76 100644 --- a/osu.Game/Graphics/Containers/LogoTrackingContainer.cs +++ b/osu.Game/Graphics/Containers/LogoTrackingContainer.cs @@ -109,7 +109,7 @@ namespace osu.Game.Graphics.Containers { double elapsedDuration = (double)(Time.Current - startTime); - var amount = (float)Interpolation.ApplyEasing(easing, Math.Min(elapsedDuration / duration, 1)); + float amount = (float)Interpolation.ApplyEasing(easing, Math.Min(elapsedDuration / duration, 1)); // Interpolate the position of the logo, where amount 0 is where the logo was when it first began interpolating, and amount 1 is the target location. Logo.Position = Vector2.Lerp(startPosition.Value, localPos, amount); diff --git a/osu.Game/Graphics/Containers/OsuTextFlowContainer.cs b/osu.Game/Graphics/Containers/OsuTextFlowContainer.cs index 6a87a4b8b9..b8237832a3 100644 --- a/osu.Game/Graphics/Containers/OsuTextFlowContainer.cs +++ b/osu.Game/Graphics/Containers/OsuTextFlowContainer.cs @@ -2,7 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Collections.Generic; +using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; @@ -19,8 +19,8 @@ namespace osu.Game.Graphics.Containers protected override SpriteText CreateSpriteText() => new OsuSpriteText(); - public void AddArbitraryDrawable(Drawable drawable) => AddInternal(drawable); + public ITextPart AddArbitraryDrawable(Drawable drawable) => AddPart(new TextPartManual(drawable.Yield())); - public IEnumerable AddIcon(IconUsage icon, Action creationParameters = null) => AddText(icon.Icon.ToString(), creationParameters); + public ITextPart AddIcon(IconUsage icon, Action creationParameters = null) => AddText(icon.Icon.ToString(), creationParameters); } } diff --git a/osu.Game/Graphics/Containers/SectionsContainer.cs b/osu.Game/Graphics/Containers/SectionsContainer.cs index 76492cab55..540ca85809 100644 --- a/osu.Game/Graphics/Containers/SectionsContainer.cs +++ b/osu.Game/Graphics/Containers/SectionsContainer.cs @@ -182,7 +182,7 @@ namespace osu.Game.Graphics.Containers protected override bool OnInvalidate(Invalidation invalidation, InvalidationSource source) { - var result = base.OnInvalidate(invalidation, source); + bool result = base.OnInvalidate(invalidation, source); if (source == InvalidationSource.Child && (invalidation & Invalidation.DrawSize) != 0) { @@ -240,7 +240,7 @@ namespace osu.Game.Graphics.Containers headerBackgroundContainer.Height = expandableHeaderSize + fixedHeaderSize; headerBackgroundContainer.Y = ExpandableHeader?.Y ?? 0; - var smallestSectionHeight = Children.Count > 0 ? Children.Min(d => d.Height) : 0; + float smallestSectionHeight = Children.Count > 0 ? Children.Min(d => d.Height) : 0; // scroll offset is our fixed header height if we have it plus 10% of content height // plus 5% to fix floating point errors and to not have a section instantly unselect when scrolling upwards diff --git a/osu.Game/Graphics/Containers/SelectionCycleFillFlowContainer.cs b/osu.Game/Graphics/Containers/SelectionCycleFillFlowContainer.cs index 90b2d20e4d..4ddaa09be6 100644 --- a/osu.Game/Graphics/Containers/SelectionCycleFillFlowContainer.cs +++ b/osu.Game/Graphics/Containers/SelectionCycleFillFlowContainer.cs @@ -40,7 +40,7 @@ namespace osu.Game.Graphics.Containers public void Select(T item) { - var newIndex = IndexOf(item); + int newIndex = IndexOf(item); if (newIndex < 0) setSelected(null); diff --git a/osu.Game/Graphics/Cursor/MenuCursor.cs b/osu.Game/Graphics/Cursor/MenuCursor.cs index fd8f016860..3fa90e2330 100644 --- a/osu.Game/Graphics/Cursor/MenuCursor.cs +++ b/osu.Game/Graphics/Cursor/MenuCursor.cs @@ -44,7 +44,7 @@ namespace osu.Game.Graphics.Cursor if (dragRotationState != DragRotationState.NotDragging) { var position = e.MousePosition; - var distance = Vector2Extensions.Distance(position, positionMouseDown); + float distance = Vector2Extensions.Distance(position, positionMouseDown); // don't start rotating until we're moved a minimum distance away from the mouse down location, // else it can have an annoying effect. diff --git a/osu.Game/Graphics/ErrorTextFlowContainer.cs b/osu.Game/Graphics/ErrorTextFlowContainer.cs index f17a2a2c3d..dafc363973 100644 --- a/osu.Game/Graphics/ErrorTextFlowContainer.cs +++ b/osu.Game/Graphics/ErrorTextFlowContainer.cs @@ -2,7 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; -using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Game.Graphics.Containers; using osuTK.Graphics; @@ -10,7 +10,7 @@ namespace osu.Game.Graphics { public class ErrorTextFlowContainer : OsuTextFlowContainer { - private readonly List errorDrawables = new List(); + private readonly List errorTextParts = new List(); public ErrorTextFlowContainer() : base(cp => cp.Font = cp.Font.With(size: 12)) @@ -19,7 +19,8 @@ namespace osu.Game.Graphics public void ClearErrors() { - errorDrawables.ForEach(d => d.Expire()); + foreach (var textPart in errorTextParts) + RemovePart(textPart); } public void AddErrors(string[] errors) @@ -28,8 +29,8 @@ namespace osu.Game.Graphics if (errors == null) return; - foreach (var error in errors) - errorDrawables.AddRange(AddParagraph(error, cp => cp.Colour = Color4.Red)); + foreach (string error in errors) + errorTextParts.Add(AddParagraph(error, cp => cp.Colour = Color4.Red)); } } } diff --git a/osu.Game/Graphics/OsuColour.cs b/osu.Game/Graphics/OsuColour.cs index 40d163635a..3aa4dbf1d8 100644 --- a/osu.Game/Graphics/OsuColour.cs +++ b/osu.Game/Graphics/OsuColour.cs @@ -118,6 +118,42 @@ namespace osu.Game.Graphics } } + /// + /// Retrieves a colour for the given . + /// A value indicates that a "background" shade from the local + /// (or another fallback colour) should be used. + /// + /// + /// Sourced from web: https://github.com/ppy/osu-web/blob/007eebb1916ed5cb6a7866d82d8011b1060a945e/resources/assets/less/layout.less#L36-L50 + /// + public static Color4? ForBeatmapSetOnlineStatus(BeatmapSetOnlineStatus status) + { + switch (status) + { + case BeatmapSetOnlineStatus.Ranked: + case BeatmapSetOnlineStatus.Approved: + return Color4Extensions.FromHex(@"b3ff66"); + + case BeatmapSetOnlineStatus.Loved: + return Color4Extensions.FromHex(@"ff66ab"); + + case BeatmapSetOnlineStatus.Qualified: + return Color4Extensions.FromHex(@"66ccff"); + + case BeatmapSetOnlineStatus.Pending: + return Color4Extensions.FromHex(@"ffd966"); + + case BeatmapSetOnlineStatus.WIP: + return Color4Extensions.FromHex(@"ff9966"); + + case BeatmapSetOnlineStatus.Graveyard: + return Color4.Black; + + default: + return null; + } + } + /// /// Returns a foreground text colour that is supposed to contrast well with /// the supplied . diff --git a/osu.Game/Graphics/ParticleExplosion.cs b/osu.Game/Graphics/ParticleExplosion.cs index 094cc87bbe..ec1077eb81 100644 --- a/osu.Game/Graphics/ParticleExplosion.cs +++ b/osu.Game/Graphics/ParticleExplosion.cs @@ -89,7 +89,7 @@ namespace osu.Game.Graphics protected override void Blit(Action vertexAction) { - var time = currentTime - startTime; + double time = currentTime - startTime; foreach (var p in parts) { @@ -136,7 +136,7 @@ namespace osu.Game.Graphics public Vector2 PositionAtTime(double time) { - var travelledDistance = distance * progressAtTime(time); + float travelledDistance = distance * progressAtTime(time); return new Vector2(0.5f) + travelledDistance * new Vector2(MathF.Sin(direction), MathF.Cos(direction)); } diff --git a/osu.Game/Graphics/ParticleSpewer.cs b/osu.Game/Graphics/ParticleSpewer.cs index 54a2b1e890..4fc6c4527f 100644 --- a/osu.Game/Graphics/ParticleSpewer.cs +++ b/osu.Game/Graphics/ParticleSpewer.cs @@ -109,18 +109,18 @@ namespace osu.Game.Graphics { foreach (var p in particles) { - var timeSinceStart = currentTime - p.StartTime; + float timeSinceStart = currentTime - p.StartTime; // ignore particles from the future. // these can appear when seeking in replays. if (timeSinceStart < 0) continue; - var alpha = p.AlphaAtTime(timeSinceStart); + float alpha = p.AlphaAtTime(timeSinceStart); if (alpha <= 0) continue; var pos = p.PositionAtTime(timeSinceStart, gravity, maxDuration); - var scale = p.ScaleAtTime(timeSinceStart); - var angle = p.AngleAtTime(timeSinceStart); + float scale = p.ScaleAtTime(timeSinceStart); + float angle = p.AngleAtTime(timeSinceStart); var rect = createDrawRect(pos, scale); @@ -139,8 +139,8 @@ namespace osu.Game.Graphics private RectangleF createDrawRect(Vector2 position, float scale) { - var width = Texture.DisplayWidth * scale; - var height = Texture.DisplayHeight * scale; + float width = Texture.DisplayWidth * scale; + float height = Texture.DisplayHeight * scale; if (relativePositionAxes.HasFlagFast(Axes.X)) position.X *= sourceSize.X; @@ -188,7 +188,7 @@ namespace osu.Game.Graphics public Vector2 PositionAtTime(float timeSinceStart, float gravity, float maxDuration) { - var progress = progressAtTime(timeSinceStart); + float progress = progressAtTime(timeSinceStart); var currentGravity = new Vector2(0, gravity * Duration / maxDuration * progress); return StartPosition + (Velocity + currentGravity) * timeSinceStart / maxDuration; diff --git a/osu.Game/Graphics/ScreenshotManager.cs b/osu.Game/Graphics/ScreenshotManager.cs index 9cd403f409..1f7f93b3c3 100644 --- a/osu.Game/Graphics/ScreenshotManager.cs +++ b/osu.Game/Graphics/ScreenshotManager.cs @@ -109,51 +109,53 @@ namespace osu.Game.Graphics if (Interlocked.Decrement(ref screenShotTasks) == 0 && cursorVisibility.Value == false) cursorVisibility.Value = true; - var fileName = getFileName(); - if (fileName == null) return; + string filename = getFilename(); - var stream = storage.GetStream(fileName, FileAccess.Write); + if (filename == null) return; - switch (screenshotFormat.Value) + using (var stream = storage.GetStream(filename, FileAccess.Write)) { - case ScreenshotFormat.Png: - await image.SaveAsPngAsync(stream).ConfigureAwait(false); - break; + switch (screenshotFormat.Value) + { + case ScreenshotFormat.Png: + await image.SaveAsPngAsync(stream).ConfigureAwait(false); + break; - case ScreenshotFormat.Jpg: - const int jpeg_quality = 92; + case ScreenshotFormat.Jpg: + const int jpeg_quality = 92; - await image.SaveAsJpegAsync(stream, new JpegEncoder { Quality = jpeg_quality }).ConfigureAwait(false); - break; + await image.SaveAsJpegAsync(stream, new JpegEncoder { Quality = jpeg_quality }).ConfigureAwait(false); + break; - default: - throw new InvalidOperationException($"Unknown enum member {nameof(ScreenshotFormat)} {screenshotFormat.Value}."); + default: + throw new InvalidOperationException($"Unknown enum member {nameof(ScreenshotFormat)} {screenshotFormat.Value}."); + } } notificationOverlay.Post(new SimpleNotification { - Text = $"{fileName} saved!", + Text = $"{filename} saved!", Activated = () => { - storage.OpenInNativeExplorer(); + storage.PresentFileExternally(filename); return true; } }); } }); - private string getFileName() + private string getFilename() { var dt = DateTime.Now; - var fileExt = screenshotFormat.ToString().ToLowerInvariant(); + string fileExt = screenshotFormat.ToString().ToLowerInvariant(); - var withoutIndex = $"osu_{dt:yyyy-MM-dd_HH-mm-ss}.{fileExt}"; + string withoutIndex = $"osu_{dt:yyyy-MM-dd_HH-mm-ss}.{fileExt}"; if (!storage.Exists(withoutIndex)) return withoutIndex; for (ulong i = 1; i < ulong.MaxValue; i++) { - var indexedName = $"osu_{dt:yyyy-MM-dd_HH-mm-ss}-{i}.{fileExt}"; + string indexedName = $"osu_{dt:yyyy-MM-dd_HH-mm-ss}-{i}.{fileExt}"; if (!storage.Exists(indexedName)) return indexedName; } diff --git a/osu.Game/Graphics/UserInterface/BreadcrumbControl.cs b/osu.Game/Graphics/UserInterface/BreadcrumbControl.cs index fb5ff4aad3..d06c227d4b 100644 --- a/osu.Game/Graphics/UserInterface/BreadcrumbControl.cs +++ b/osu.Game/Graphics/UserInterface/BreadcrumbControl.cs @@ -33,8 +33,8 @@ namespace osu.Game.Graphics.UserInterface { foreach (var t in TabContainer.Children.OfType()) { - var tIndex = TabContainer.IndexOf(t); - var tabIndex = TabContainer.IndexOf(TabMap[index.NewValue]); + int tIndex = TabContainer.IndexOf(t); + int tabIndex = TabContainer.IndexOf(TabMap[index.NewValue]); t.State = tIndex > tabIndex ? Visibility.Hidden : Visibility.Visible; t.Chevron.FadeTo(tIndex >= tabIndex ? 0f : 1f, 500, Easing.OutQuint); diff --git a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs index 6963f7335e..d4310dc901 100644 --- a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs +++ b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs @@ -216,10 +216,10 @@ namespace osu.Game.Graphics.UserInterface } else { - var decimalPrecision = normalise(CurrentNumber.Precision.ToDecimal(NumberFormatInfo.InvariantInfo), max_decimal_digits); + decimal decimalPrecision = normalise(CurrentNumber.Precision.ToDecimal(NumberFormatInfo.InvariantInfo), max_decimal_digits); // Find the number of significant digits (we could have less than 5 after normalize()) - var significantDigits = findPrecision(decimalPrecision); + int significantDigits = findPrecision(decimalPrecision); TooltipText = floatValue.ToString($"N{significantDigits}"); } diff --git a/osu.Game/Graphics/UserInterface/StarCounter.cs b/osu.Game/Graphics/UserInterface/StarCounter.cs index 32b788b5dc..b66f371801 100644 --- a/osu.Game/Graphics/UserInterface/StarCounter.cs +++ b/osu.Game/Graphics/UserInterface/StarCounter.cs @@ -89,7 +89,7 @@ namespace osu.Game.Graphics.UserInterface public void ReplayAnimation() { - var t = current; + float t = current; ResetCount(); Current = t; } @@ -105,7 +105,7 @@ namespace osu.Game.Graphics.UserInterface private void animate(float newValue) { - for (var i = 0; i < stars.Children.Count; i++) + for (int i = 0; i < stars.Children.Count; i++) { var star = stars.Children[i]; diff --git a/osu.Game/Graphics/UserInterface/TwoLayerButton.cs b/osu.Game/Graphics/UserInterface/TwoLayerButton.cs index 969309bc79..1f5d29571d 100644 --- a/osu.Game/Graphics/UserInterface/TwoLayerButton.cs +++ b/osu.Game/Graphics/UserInterface/TwoLayerButton.cs @@ -236,7 +236,7 @@ namespace osu.Game.Graphics.UserInterface { base.OnNewBeat(beatIndex, timingPoint, effectPoint, amplitudes); - var beatLength = timingPoint.BeatLength; + double beatLength = timingPoint.BeatLength; float amplitudeAdjust = Math.Min(1, 0.4f + amplitudes.Maximum); diff --git a/osu.Game/IO/LineBufferedReader.cs b/osu.Game/IO/LineBufferedReader.cs index 018321dc9a..a6b8c9492a 100644 --- a/osu.Game/IO/LineBufferedReader.cs +++ b/osu.Game/IO/LineBufferedReader.cs @@ -32,7 +32,7 @@ namespace osu.Game.IO if (lineBuffer.Count > 0) return lineBuffer.Peek(); - var line = streamReader.ReadLine(); + string line = streamReader.ReadLine(); if (line != null) lineBuffer.Enqueue(line); return line; @@ -50,7 +50,7 @@ namespace osu.Game.IO /// public string ReadToEnd() { - var remainingText = streamReader.ReadToEnd(); + string remainingText = streamReader.ReadToEnd(); if (lineBuffer.Count == 0) return remainingText; diff --git a/osu.Game/IO/Serialization/Converters/TypedListConverter.cs b/osu.Game/IO/Serialization/Converters/TypedListConverter.cs index 174fbf9983..715c83b07e 100644 --- a/osu.Game/IO/Serialization/Converters/TypedListConverter.cs +++ b/osu.Game/IO/Serialization/Converters/TypedListConverter.cs @@ -60,7 +60,7 @@ namespace osu.Game.IO.Serialization.Converters if (tok["$type"] == null) throw new JsonException("Expected $type token."); - var typeName = lookupTable[(int)tok["$type"]]; + string typeName = lookupTable[(int)tok["$type"]]; var instance = (T)Activator.CreateInstance(Type.GetType(typeName).AsNonNull()); serializer.Populate(itemReader, instance); @@ -80,7 +80,7 @@ namespace osu.Game.IO.Serialization.Converters var type = item.GetType(); var assemblyName = type.Assembly.GetName(); - var typeString = $"{type.FullName}, {assemblyName.Name}"; + string typeString = $"{type.FullName}, {assemblyName.Name}"; if (requiresTypeVersion) typeString += $", {assemblyName.Version}"; diff --git a/osu.Game/IO/StableStorage.cs b/osu.Game/IO/StableStorage.cs index d4b0d300ff..f5a8c4dc9e 100644 --- a/osu.Game/IO/StableStorage.cs +++ b/osu.Game/IO/StableStorage.cs @@ -34,7 +34,7 @@ namespace osu.Game.IO private string locateSongsDirectory() { - var configFile = GetFiles(".", $"osu!.{Environment.UserName}.cfg").SingleOrDefault(); + string configFile = GetFiles(".", $"osu!.{Environment.UserName}.cfg").SingleOrDefault(); if (configFile != null) { @@ -47,7 +47,7 @@ namespace osu.Game.IO { if (!line.StartsWith("BeatmapDirectory", StringComparison.OrdinalIgnoreCase)) continue; - var customDirectory = line.Split('=').LastOrDefault()?.Trim(); + string customDirectory = line.Split('=').LastOrDefault()?.Trim(); if (customDirectory != null && Path.IsPathFullyQualified(customDirectory)) return customDirectory; diff --git a/osu.Game/IO/WrappedStorage.cs b/osu.Game/IO/WrappedStorage.cs index b9ccc907d9..6f0f898de3 100644 --- a/osu.Game/IO/WrappedStorage.cs +++ b/osu.Game/IO/WrappedStorage.cs @@ -60,7 +60,7 @@ namespace osu.Game.IO { string localRoot = GetFullPath(string.Empty); - foreach (var path in paths) + foreach (string path in paths) yield return Path.GetRelativePath(localRoot, UnderlyingStorage.GetFullPath(path)); } @@ -70,7 +70,9 @@ namespace osu.Game.IO public override Stream GetStream(string path, FileAccess access = FileAccess.Read, FileMode mode = FileMode.OpenOrCreate) => UnderlyingStorage.GetStream(MutatePath(path), access, mode); - public override void OpenPathInNativeExplorer(string path) => UnderlyingStorage.OpenPathInNativeExplorer(MutatePath(path)); + public override void OpenFileExternally(string filename) => UnderlyingStorage.OpenFileExternally(MutatePath(filename)); + + public override void PresentFileExternally(string filename) => UnderlyingStorage.PresentFileExternally(MutatePath(filename)); public override Storage GetStorageForDirectory(string path) { diff --git a/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs b/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs index 10376c1866..5dced23614 100644 --- a/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs +++ b/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs @@ -52,7 +52,7 @@ namespace osu.Game.Input.Bindings { if (ruleset == null || ruleset.ID.HasValue) { - var rulesetId = ruleset?.ID; + int? rulesetId = ruleset?.ID; realmKeyBindings = realmFactory.Context.All() .Where(b => b.RulesetID == rulesetId && b.Variant == variant); diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs index 9fd7caadd0..22446634c1 100644 --- a/osu.Game/Input/Bindings/GlobalActionContainer.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs @@ -84,8 +84,8 @@ namespace osu.Game.Input.Bindings new KeyBinding(InputKey.ExtraMouseButton2, GlobalAction.SkipCutscene), new KeyBinding(InputKey.Tilde, GlobalAction.QuickRetry), new KeyBinding(new[] { InputKey.Control, InputKey.Tilde }, GlobalAction.QuickExit), - new KeyBinding(new[] { InputKey.Control, InputKey.Plus }, GlobalAction.IncreaseScrollSpeed), - new KeyBinding(new[] { InputKey.Control, InputKey.Minus }, GlobalAction.DecreaseScrollSpeed), + new KeyBinding(new[] { InputKey.F3 }, GlobalAction.DecreaseScrollSpeed), + new KeyBinding(new[] { InputKey.F4 }, GlobalAction.IncreaseScrollSpeed), new KeyBinding(new[] { InputKey.Shift, InputKey.Tab }, GlobalAction.ToggleInGameInterface), new KeyBinding(InputKey.MouseMiddle, GlobalAction.PauseGameplay), new KeyBinding(InputKey.Space, GlobalAction.TogglePauseReplay), diff --git a/osu.Game/Input/RealmKeyBindingStore.cs b/osu.Game/Input/RealmKeyBindingStore.cs index 5fa3ccdeb9..c65e36e478 100644 --- a/osu.Game/Input/RealmKeyBindingStore.cs +++ b/osu.Game/Input/RealmKeyBindingStore.cs @@ -65,7 +65,7 @@ namespace osu.Game.Input foreach (var ruleset in rulesets) { var instance = ruleset.CreateInstance(); - foreach (var variant in instance.AvailableVariants) + foreach (int variant in instance.AvailableVariants) insertDefaults(realm, existingBindings, instance.GetDefaultKeyBindings(variant), ruleset.ID, variant); } diff --git a/osu.Game/Localisation/GraphicsSettingsStrings.cs b/osu.Game/Localisation/GraphicsSettingsStrings.cs index f85cc0f2ae..996a1350eb 100644 --- a/osu.Game/Localisation/GraphicsSettingsStrings.cs +++ b/osu.Game/Localisation/GraphicsSettingsStrings.cs @@ -119,6 +119,16 @@ namespace osu.Game.Localisation /// public static LocalisableString ShowCursorInScreenshots => new TranslatableString(getKey(@"show_cursor_in_screenshots"), @"Show menu cursor in screenshots"); + /// + /// "Video" + /// + public static LocalisableString VideoHeader => new TranslatableString(getKey(@"video_header"), @"Video"); + + /// + /// "Use hardware acceleration" + /// + public static LocalisableString UseHardwareAcceleration => new TranslatableString(getKey(@"use_hardware_acceleration"), @"Use hardware acceleration"); + private static string getKey(string key) => $"{prefix}:{key}"; } } diff --git a/osu.Game/Localisation/ResourceManagerLocalisationStore.cs b/osu.Game/Localisation/ResourceManagerLocalisationStore.cs index 6a4e38fb38..82dc0ad110 100644 --- a/osu.Game/Localisation/ResourceManagerLocalisationStore.cs +++ b/osu.Game/Localisation/ResourceManagerLocalisationStore.cs @@ -27,7 +27,7 @@ namespace osu.Game.Localisation public string Get(string lookup) { - var split = lookup.Split(':'); + string[] split = lookup.Split(':'); if (split.Length < 2) return null; diff --git a/osu.Game/Migrations/20211020081609_ResetSkinHashes.cs b/osu.Game/Migrations/20211020081609_ResetSkinHashes.cs new file mode 100644 index 0000000000..6d53c019ec --- /dev/null +++ b/osu.Game/Migrations/20211020081609_ResetSkinHashes.cs @@ -0,0 +1,23 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using osu.Game.Database; + +namespace osu.Game.Migrations +{ + [DbContext(typeof(OsuDbContext))] + [Migration("20211020081609_ResetSkinHashes")] + public partial class ResetSkinHashes : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.Sql($"UPDATE SkinInfo SET Hash = null"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + } + } +} diff --git a/osu.Game/Online/API/APIDownloadRequest.cs b/osu.Game/Online/API/APIDownloadRequest.cs index 63bb3e2287..11753e05ba 100644 --- a/osu.Game/Online/API/APIDownloadRequest.cs +++ b/osu.Game/Online/API/APIDownloadRequest.cs @@ -23,7 +23,7 @@ namespace osu.Game.Online.API protected override WebRequest CreateWebRequest() { - var file = Path.GetTempFileName(); + string file = Path.GetTempFileName(); File.Move(file, filename = Path.ChangeExtension(file, FileExtension)); diff --git a/osu.Game/Online/API/Requests/GetBeatmapRequest.cs b/osu.Game/Online/API/Requests/GetBeatmapRequest.cs index 901f7365b8..6cd45a41df 100644 --- a/osu.Game/Online/API/Requests/GetBeatmapRequest.cs +++ b/osu.Game/Online/API/Requests/GetBeatmapRequest.cs @@ -1,20 +1,38 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Framework.IO.Network; using osu.Game.Beatmaps; using osu.Game.Online.API.Requests.Responses; +#nullable enable + namespace osu.Game.Online.API.Requests { public class GetBeatmapRequest : APIRequest { - private readonly BeatmapInfo beatmapInfo; + private readonly IBeatmapInfo beatmapInfo; - public GetBeatmapRequest(BeatmapInfo beatmapInfo) + private readonly string filename; + + public GetBeatmapRequest(IBeatmapInfo beatmapInfo) { this.beatmapInfo = beatmapInfo; + + filename = (beatmapInfo as BeatmapInfo)?.Path ?? string.Empty; } - protected override string Target => $@"beatmaps/lookup?id={beatmapInfo.OnlineBeatmapID}&checksum={beatmapInfo.MD5Hash}&filename={System.Uri.EscapeUriString(beatmapInfo.Path ?? string.Empty)}"; + protected override WebRequest CreateWebRequest() + { + var request = base.CreateWebRequest(); + + request.AddParameter(@"id", beatmapInfo.OnlineID.ToString()); + request.AddParameter(@"checksum", beatmapInfo.MD5Hash); + request.AddParameter(@"filename", filename); + + return request; + } + + protected override string Target => @"beatmaps/lookup"; } } diff --git a/osu.Game/Online/API/Requests/GetScoresRequest.cs b/osu.Game/Online/API/Requests/GetScoresRequest.cs index f3bf690ed5..8e447390e6 100644 --- a/osu.Game/Online/API/Requests/GetScoresRequest.cs +++ b/osu.Game/Online/API/Requests/GetScoresRequest.cs @@ -9,21 +9,20 @@ using osu.Game.Online.API.Requests.Responses; using osu.Game.Rulesets.Mods; using System.Text; using System.Collections.Generic; -using System.Diagnostics; namespace osu.Game.Online.API.Requests { - public class GetScoresRequest : APIRequest + public class GetScoresRequest : APIRequest { - private readonly BeatmapInfo beatmapInfo; + private readonly IBeatmapInfo beatmapInfo; private readonly BeatmapLeaderboardScope scope; - private readonly RulesetInfo ruleset; + private readonly IRulesetInfo ruleset; private readonly IEnumerable mods; - public GetScoresRequest(BeatmapInfo beatmapInfo, RulesetInfo ruleset, BeatmapLeaderboardScope scope = BeatmapLeaderboardScope.Global, IEnumerable mods = null) + public GetScoresRequest(IBeatmapInfo beatmapInfo, IRulesetInfo ruleset, BeatmapLeaderboardScope scope = BeatmapLeaderboardScope.Global, IEnumerable mods = null) { - if (!beatmapInfo.OnlineBeatmapID.HasValue) - throw new InvalidOperationException($"Cannot lookup a beatmap's scores without having a populated {nameof(BeatmapInfo.OnlineBeatmapID)}."); + if (beatmapInfo.OnlineID <= 0) + throw new InvalidOperationException($"Cannot lookup a beatmap's scores without having a populated {nameof(IBeatmapInfo.OnlineID)}."); if (scope == BeatmapLeaderboardScope.Local) throw new InvalidOperationException("Should not attempt to request online scores for a local scoped leaderboard"); @@ -32,30 +31,9 @@ namespace osu.Game.Online.API.Requests this.scope = scope; this.ruleset = ruleset ?? throw new ArgumentNullException(nameof(ruleset)); this.mods = mods ?? Array.Empty(); - - Success += onSuccess; } - private void onSuccess(APILegacyScores r) - { - Debug.Assert(ruleset.ID != null, "ruleset.ID != null"); - - foreach (APILegacyScoreInfo score in r.Scores) - { - score.BeatmapInfo = beatmapInfo; - score.OnlineRulesetID = ruleset.ID.Value; - } - - var userScore = r.UserScore; - - if (userScore != null) - { - userScore.Score.BeatmapInfo = beatmapInfo; - userScore.Score.OnlineRulesetID = ruleset.ID.Value; - } - } - - protected override string Target => $@"beatmaps/{beatmapInfo.OnlineBeatmapID}/scores{createQueryParameters()}"; + protected override string Target => $@"beatmaps/{beatmapInfo.OnlineID}/scores{createQueryParameters()}"; private string createQueryParameters() { diff --git a/osu.Game/Online/API/Requests/GetUserScoresRequest.cs b/osu.Game/Online/API/Requests/GetUserScoresRequest.cs index 7b4d66e7b2..e13ac8e539 100644 --- a/osu.Game/Online/API/Requests/GetUserScoresRequest.cs +++ b/osu.Game/Online/API/Requests/GetUserScoresRequest.cs @@ -8,7 +8,7 @@ using osu.Game.Rulesets; namespace osu.Game.Online.API.Requests { - public class GetUserScoresRequest : PaginatedAPIRequest> + public class GetUserScoresRequest : PaginatedAPIRequest> { private readonly long userId; private readonly ScoreType type; diff --git a/osu.Game/Online/API/Requests/Responses/APIBeatmap.cs b/osu.Game/Online/API/Requests/Responses/APIBeatmap.cs index 42e519223b..653b011f73 100644 --- a/osu.Game/Online/API/Requests/Responses/APIBeatmap.cs +++ b/osu.Game/Online/API/Requests/Responses/APIBeatmap.cs @@ -10,7 +10,7 @@ using osu.Game.Rulesets; namespace osu.Game.Online.API.Requests.Responses { - public class APIBeatmap : BeatmapMetadata, IBeatmapInfo + public class APIBeatmap : IBeatmapInfo, IBeatmapOnlineInfo { [JsonProperty(@"id")] public int OnlineID { get; set; } @@ -24,14 +24,17 @@ namespace osu.Game.Online.API.Requests.Responses [JsonProperty("checksum")] public string Checksum { get; set; } = string.Empty; + [JsonProperty(@"user_id")] + public int AuthorID { get; set; } + [JsonProperty(@"beatmapset")] public APIBeatmapSet? BeatmapSet { get; set; } [JsonProperty(@"playcount")] - private int playCount { get; set; } + public int PlayCount { get; set; } [JsonProperty(@"passcount")] - private int passCount { get; set; } + public int PassCount { get; set; } [JsonProperty(@"mode_int")] public int RulesetID { get; set; } @@ -40,34 +43,43 @@ namespace osu.Game.Online.API.Requests.Responses public double StarRating { get; set; } [JsonProperty(@"drain")] - private float drainRate { get; set; } + public float DrainRate { get; set; } [JsonProperty(@"cs")] - private float circleSize { get; set; } + public float CircleSize { get; set; } [JsonProperty(@"ar")] - private float approachRate { get; set; } + public float ApproachRate { get; set; } [JsonProperty(@"accuracy")] - private float overallDifficulty { get; set; } + public float OverallDifficulty { get; set; } - [JsonProperty(@"total_length")] + [JsonIgnore] public double Length { get; set; } + [JsonProperty(@"total_length")] + private double lengthInSeconds + { + get => TimeSpan.FromMilliseconds(Length).TotalSeconds; + set => Length = TimeSpan.FromSeconds(value).TotalMilliseconds; + } + [JsonProperty(@"count_circles")] - private int circleCount { get; set; } + public int CircleCount { get; set; } [JsonProperty(@"count_sliders")] - private int sliderCount { get; set; } + public int SliderCount { get; set; } [JsonProperty(@"version")] public string DifficultyName { get; set; } = string.Empty; [JsonProperty(@"failtimes")] - private BeatmapMetrics? metrics { get; set; } + public APIFailTimes? FailTimes { get; set; } [JsonProperty(@"max_combo")] - private int? maxCombo { get; set; } + public int? MaxCombo { get; set; } + + public double BPM { get; set; } public virtual BeatmapInfo ToBeatmapInfo(RulesetStore rulesets) { @@ -75,45 +87,38 @@ namespace osu.Game.Online.API.Requests.Responses return new BeatmapInfo { - Metadata = set?.Metadata ?? this, + Metadata = set?.Metadata ?? new BeatmapMetadata(), Ruleset = rulesets.GetRuleset(RulesetID), StarDifficulty = StarRating, OnlineBeatmapID = OnlineID, Version = DifficultyName, // this is actually an incorrect mapping (Length is calculated as drain length in lazer's import process, see BeatmapManager.calculateLength). - Length = TimeSpan.FromSeconds(Length).TotalMilliseconds, + Length = Length, Status = Status, MD5Hash = Checksum, BeatmapSet = set, - Metrics = metrics, - MaxCombo = maxCombo, + MaxCombo = MaxCombo, BaseDifficulty = new BeatmapDifficulty { - DrainRate = drainRate, - CircleSize = circleSize, - ApproachRate = approachRate, - OverallDifficulty = overallDifficulty, - }, - OnlineInfo = new BeatmapOnlineInfo - { - PlayCount = playCount, - PassCount = passCount, - CircleCount = circleCount, - SliderCount = sliderCount, + DrainRate = DrainRate, + CircleSize = CircleSize, + ApproachRate = ApproachRate, + OverallDifficulty = OverallDifficulty, }, + OnlineInfo = this, }; } #region Implementation of IBeatmapInfo - public IBeatmapMetadataInfo Metadata => this; + public IBeatmapMetadataInfo Metadata => (BeatmapSet as IBeatmapSetInfo)?.Metadata ?? new BeatmapMetadata(); public IBeatmapDifficultyInfo Difficulty => new BeatmapDifficulty { - DrainRate = drainRate, - CircleSize = circleSize, - ApproachRate = approachRate, - OverallDifficulty = overallDifficulty, + DrainRate = DrainRate, + CircleSize = CircleSize, + ApproachRate = ApproachRate, + OverallDifficulty = OverallDifficulty, }; IBeatmapSetInfo? IBeatmapInfo.BeatmapSet => BeatmapSet; @@ -122,7 +127,7 @@ namespace osu.Game.Online.API.Requests.Responses public IRulesetInfo Ruleset => new RulesetInfo { ID = RulesetID }; - public double BPM => throw new NotImplementedException(); + [JsonIgnore] public string Hash => throw new NotImplementedException(); #endregion diff --git a/osu.Game/Online/API/Requests/Responses/APIBeatmapSet.cs b/osu.Game/Online/API/Requests/Responses/APIBeatmapSet.cs index 47f880cf54..d8efa20b39 100644 --- a/osu.Game/Online/API/Requests/Responses/APIBeatmapSet.cs +++ b/osu.Game/Online/API/Requests/Responses/APIBeatmapSet.cs @@ -8,12 +8,13 @@ using Newtonsoft.Json; using osu.Game.Beatmaps; using osu.Game.Database; using osu.Game.Rulesets; +using osu.Game.Users; #nullable enable namespace osu.Game.Online.API.Requests.Responses { - public class APIBeatmapSet : BeatmapMetadata, IBeatmapSetOnlineInfo, IBeatmapSetInfo + public class APIBeatmapSet : IBeatmapSetOnlineInfo, IBeatmapSetInfo { [JsonProperty(@"covers")] public BeatmapSetOnlineCovers Covers { get; set; } @@ -57,16 +58,50 @@ namespace osu.Game.Online.API.Requests.Responses [JsonProperty(@"last_updated")] public DateTimeOffset? LastUpdated { get; set; } - [JsonProperty(@"ratings")] - private int[] ratings { get; set; } = Array.Empty(); + [JsonProperty("ratings")] + public int[] Ratings { get; set; } = Array.Empty(); [JsonProperty(@"track_id")] public int? TrackId { get; set; } + public string Title { get; set; } = string.Empty; + + [JsonProperty("title_unicode")] + public string TitleUnicode { get; set; } = string.Empty; + + public string Artist { get; set; } = string.Empty; + + [JsonProperty("artist_unicode")] + public string ArtistUnicode { get; set; } = string.Empty; + + public User? Author = new User(); + + /// + /// Helper property to deserialize a username to . + /// [JsonProperty(@"user_id")] - private int creatorId + public int AuthorID { - set => Author.Id = value; + get => Author?.Id ?? 1; + set + { + Author ??= new User(); + Author.Id = value; + } + } + + /// + /// Helper property to deserialize a username to . + /// + [JsonProperty(@"creator")] + public string AuthorString + { + get => Author?.Username ?? string.Empty; + set + { + Author ??= new User(); + Author.Username = value; + } } [JsonProperty(@"availability")] @@ -78,21 +113,25 @@ namespace osu.Game.Online.API.Requests.Responses [JsonProperty(@"language")] public BeatmapSetOnlineLanguage Language { get; set; } + public string Source { get; set; } = string.Empty; + + [JsonProperty(@"tags")] + public string Tags { get; set; } = string.Empty; + [JsonProperty(@"beatmaps")] - private IEnumerable beatmaps { get; set; } = Array.Empty(); + public IEnumerable Beatmaps { get; set; } = Array.Empty(); public virtual BeatmapSetInfo ToBeatmapSet(RulesetStore rulesets) { var beatmapSet = new BeatmapSetInfo { OnlineBeatmapSetID = OnlineID, - Metadata = this, + Metadata = metadata, Status = Status, - Metrics = new BeatmapSetMetrics { Ratings = ratings }, OnlineInfo = this }; - beatmapSet.Beatmaps = beatmaps.Select(b => + beatmapSet.Beatmaps = Beatmaps.Select(b => { var beatmap = b.ToBeatmapInfo(rulesets); beatmap.BeatmapSet = beatmapSet; @@ -103,17 +142,29 @@ namespace osu.Game.Online.API.Requests.Responses return beatmapSet; } + private BeatmapMetadata metadata => new BeatmapMetadata + { + Title = Title, + TitleUnicode = TitleUnicode, + Artist = Artist, + ArtistUnicode = ArtistUnicode, + AuthorID = AuthorID, + Author = Author, + Source = Source, + Tags = Tags, + }; + #region Implementation of IBeatmapSetInfo - IEnumerable IBeatmapSetInfo.Beatmaps => beatmaps; + IEnumerable IBeatmapSetInfo.Beatmaps => Beatmaps; - IBeatmapMetadataInfo IBeatmapSetInfo.Metadata => this; + IBeatmapMetadataInfo IBeatmapSetInfo.Metadata => metadata; DateTimeOffset IBeatmapSetInfo.DateAdded => throw new NotImplementedException(); IEnumerable IBeatmapSetInfo.Files => throw new NotImplementedException(); double IBeatmapSetInfo.MaxStarDifficulty => throw new NotImplementedException(); double IBeatmapSetInfo.MaxLength => throw new NotImplementedException(); - double IBeatmapSetInfo.MaxBPM => throw new NotImplementedException(); + double IBeatmapSetInfo.MaxBPM => BPM; #endregion } diff --git a/osu.Game/Online/API/Requests/Responses/APILegacyScores.cs b/osu.Game/Online/API/Requests/Responses/APILegacyScores.cs deleted file mode 100644 index 009639c1dc..0000000000 --- a/osu.Game/Online/API/Requests/Responses/APILegacyScores.cs +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using System.Collections.Generic; -using Newtonsoft.Json; -using osu.Game.Rulesets; -using osu.Game.Scoring; - -namespace osu.Game.Online.API.Requests.Responses -{ - public class APILegacyScores - { - [JsonProperty(@"scores")] - public List Scores; - - [JsonProperty(@"userScore")] - public APILegacyUserTopScoreInfo UserScore; - } - - public class APILegacyUserTopScoreInfo - { - [JsonProperty(@"position")] - public int? Position; - - [JsonProperty(@"score")] - public APILegacyScoreInfo Score; - - public ScoreInfo CreateScoreInfo(RulesetStore rulesets) - { - var score = Score.CreateScoreInfo(rulesets); - score.Position = Position; - return score; - } - } -} diff --git a/osu.Game/Online/API/Requests/Responses/APILegacyScoreInfo.cs b/osu.Game/Online/API/Requests/Responses/APIScoreInfo.cs similarity index 56% rename from osu.Game/Online/API/Requests/Responses/APILegacyScoreInfo.cs rename to osu.Game/Online/API/Requests/Responses/APIScoreInfo.cs index aaf2dccc82..2e41723f34 100644 --- a/osu.Game/Online/API/Requests/Responses/APILegacyScoreInfo.cs +++ b/osu.Game/Online/API/Requests/Responses/APIScoreInfo.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using JetBrains.Annotations; using Newtonsoft.Json; using Newtonsoft.Json.Converters; using osu.Game.Beatmaps; @@ -15,36 +16,102 @@ using osu.Game.Users; namespace osu.Game.Online.API.Requests.Responses { - public class APILegacyScoreInfo + public class APIScoreInfo : IScoreInfo { - public ScoreInfo CreateScoreInfo(RulesetStore rulesets) + [JsonProperty(@"score")] + public long TotalScore { get; set; } + + [JsonProperty(@"max_combo")] + public int MaxCombo { get; set; } + + [JsonProperty(@"user")] + public User User { get; set; } + + [JsonProperty(@"id")] + public long OnlineID { get; set; } + + [JsonProperty(@"replay")] + public bool HasReplay { get; set; } + + [JsonProperty(@"created_at")] + public DateTimeOffset Date { get; set; } + + [JsonProperty(@"beatmap")] + public APIBeatmap Beatmap { get; set; } + + [JsonProperty("accuracy")] + public double Accuracy { get; set; } + + [JsonProperty(@"pp")] + public double? PP { get; set; } + + [JsonProperty(@"beatmapset")] + public APIBeatmapSet BeatmapSet { - var ruleset = rulesets.GetRuleset(OnlineRulesetID); + set + { + // in the deserialisation case we need to ferry this data across. + // the order of properties returned by the API guarantees that the beatmap is populated by this point. + if (!(Beatmap is APIBeatmap apiBeatmap)) + throw new InvalidOperationException("Beatmap set metadata arrived before beatmap metadata in response"); + + apiBeatmap.BeatmapSet = value; + } + } + + [JsonProperty("statistics")] + public Dictionary Statistics { get; set; } + + [JsonProperty(@"mode_int")] + public int RulesetID { get; set; } + + [JsonProperty(@"mods")] + private string[] mods { set => Mods = value.Select(acronym => new APIMod { Acronym = acronym }); } + + [NotNull] + public IEnumerable Mods { get; set; } = Array.Empty(); + + [JsonProperty("rank")] + [JsonConverter(typeof(StringEnumConverter))] + public ScoreRank Rank { get; set; } + + /// + /// Create a from an API score instance. + /// + /// A ruleset store, used to populate a ruleset instance in the returned score. + /// An optional beatmap, copied into the returned score (for cases where the API does not populate the beatmap). + /// + public ScoreInfo CreateScoreInfo(RulesetStore rulesets, BeatmapInfo beatmap = null) + { + var ruleset = rulesets.GetRuleset(RulesetID); var rulesetInstance = ruleset.CreateInstance(); - var mods = Mods != null ? Mods.Select(acronym => rulesetInstance.CreateModFromAcronym(acronym)).Where(m => m != null).ToArray() : Array.Empty(); + var modInstances = Mods.Select(apiMod => rulesetInstance.CreateModFromAcronym(apiMod.Acronym)).Where(m => m != null).ToArray(); // all API scores provided by this class are considered to be legacy. - mods = mods.Append(rulesetInstance.CreateMod()).ToArray(); + modInstances = modInstances.Append(rulesetInstance.CreateMod()).ToArray(); var scoreInfo = new ScoreInfo { TotalScore = TotalScore, MaxCombo = MaxCombo, + BeatmapInfo = Beatmap.ToBeatmapInfo(rulesets), User = User, Accuracy = Accuracy, - OnlineScoreID = OnlineScoreID, + OnlineScoreID = OnlineID, Date = Date, PP = PP, - BeatmapInfo = BeatmapInfo, - RulesetID = OnlineRulesetID, - Hash = Replay ? "online" : string.Empty, // todo: temporary? + RulesetID = RulesetID, + Hash = HasReplay ? "online" : string.Empty, // todo: temporary? Rank = Rank, Ruleset = ruleset, - Mods = mods, + Mods = modInstances, }; + if (beatmap != null) + scoreInfo.BeatmapInfo = beatmap; + if (Statistics != null) { foreach (var kvp in Statistics) @@ -81,57 +148,8 @@ namespace osu.Game.Online.API.Requests.Responses return scoreInfo; } - [JsonProperty(@"score")] - public int TotalScore { get; set; } + public IRulesetInfo Ruleset => new RulesetInfo { ID = RulesetID }; - [JsonProperty(@"max_combo")] - public int MaxCombo { get; set; } - - [JsonProperty(@"user")] - public User User { get; set; } - - [JsonProperty(@"id")] - public long OnlineScoreID { get; set; } - - [JsonProperty(@"replay")] - public bool Replay { get; set; } - - [JsonProperty(@"created_at")] - public DateTimeOffset Date { get; set; } - - [JsonProperty(@"beatmap")] - public BeatmapInfo BeatmapInfo { get; set; } - - [JsonProperty("accuracy")] - public double Accuracy { get; set; } - - [JsonProperty(@"pp")] - public double? PP { get; set; } - - [JsonProperty(@"beatmapset")] - public BeatmapMetadata Metadata - { - set - { - // extract the set ID to its correct place. - BeatmapInfo.BeatmapSet = new BeatmapSetInfo { OnlineBeatmapSetID = value.ID }; - value.ID = 0; - - BeatmapInfo.Metadata = value; - } - } - - [JsonProperty(@"statistics")] - public Dictionary Statistics { get; set; } - - [JsonProperty(@"mode_int")] - public int OnlineRulesetID { get; set; } - - [JsonProperty(@"mods")] - public string[] Mods { get; set; } - - [JsonProperty("rank")] - [JsonConverter(typeof(StringEnumConverter))] - public ScoreRank Rank { get; set; } + IBeatmapInfo IScoreInfo.Beatmap => Beatmap; } } diff --git a/osu.Game/Online/API/Requests/Responses/APIScoreWithPosition.cs b/osu.Game/Online/API/Requests/Responses/APIScoreWithPosition.cs new file mode 100644 index 0000000000..48b7134901 --- /dev/null +++ b/osu.Game/Online/API/Requests/Responses/APIScoreWithPosition.cs @@ -0,0 +1,26 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using Newtonsoft.Json; +using osu.Game.Beatmaps; +using osu.Game.Rulesets; +using osu.Game.Scoring; + +namespace osu.Game.Online.API.Requests.Responses +{ + public class APIScoreWithPosition + { + [JsonProperty(@"position")] + public int? Position; + + [JsonProperty(@"score")] + public APIScoreInfo Score; + + public ScoreInfo CreateScoreInfo(RulesetStore rulesets, BeatmapInfo beatmap = null) + { + var score = Score.CreateScoreInfo(rulesets, beatmap); + score.Position = Position; + return score; + } + } +} diff --git a/osu.Game/Online/API/Requests/Responses/APIScoresCollection.cs b/osu.Game/Online/API/Requests/Responses/APIScoresCollection.cs new file mode 100644 index 0000000000..5304664bf8 --- /dev/null +++ b/osu.Game/Online/API/Requests/Responses/APIScoresCollection.cs @@ -0,0 +1,17 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace osu.Game.Online.API.Requests.Responses +{ + public class APIScoresCollection + { + [JsonProperty(@"scores")] + public List Scores; + + [JsonProperty(@"userScore")] + public APIScoreWithPosition UserScore; + } +} diff --git a/osu.Game/Online/API/Requests/Responses/APIUserMostPlayedBeatmap.cs b/osu.Game/Online/API/Requests/Responses/APIUserMostPlayedBeatmap.cs index 10f7ca6fe2..19c581bf95 100644 --- a/osu.Game/Online/API/Requests/Responses/APIUserMostPlayedBeatmap.cs +++ b/osu.Game/Online/API/Requests/Responses/APIUserMostPlayedBeatmap.cs @@ -2,8 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using Newtonsoft.Json; -using osu.Game.Beatmaps; -using osu.Game.Rulesets; namespace osu.Game.Online.API.Requests.Responses { @@ -16,17 +14,19 @@ namespace osu.Game.Online.API.Requests.Responses public int PlayCount { get; set; } [JsonProperty("beatmap")] - private BeatmapInfo beatmapInfo { get; set; } + private APIBeatmap beatmap { get; set; } - [JsonProperty] - private APIBeatmapSet beatmapSet { get; set; } - - public BeatmapInfo GetBeatmapInfo(RulesetStore rulesets) + public APIBeatmap BeatmapInfo { - BeatmapSetInfo setInfo = beatmapSet.ToBeatmapSet(rulesets); - beatmapInfo.BeatmapSet = setInfo; - beatmapInfo.Metadata = setInfo.Metadata; - return beatmapInfo; + get + { + // old osu-web code doesn't nest set. + beatmap.BeatmapSet = BeatmapSet; + return beatmap; + } } + + [JsonProperty("beatmapset")] + public APIBeatmapSet BeatmapSet { get; set; } } } diff --git a/osu.Game/Online/BeatmapDownloadTracker.cs b/osu.Game/Online/BeatmapDownloadTracker.cs new file mode 100644 index 0000000000..4a7d0b660a --- /dev/null +++ b/osu.Game/Online/BeatmapDownloadTracker.cs @@ -0,0 +1,155 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Game.Beatmaps; +using osu.Game.Online.API; + +#nullable enable + +namespace osu.Game.Online +{ + public class BeatmapDownloadTracker : DownloadTracker + { + [Resolved(CanBeNull = true)] + protected BeatmapManager? Manager { get; private set; } + + private ArchiveDownloadRequest? attachedRequest; + + public BeatmapDownloadTracker(IBeatmapSetInfo trackedItem) + : base(trackedItem) + { + } + + private IBindable>? managerUpdated; + private IBindable>? managerRemoved; + private IBindable>>? managerDownloadBegan; + private IBindable>>? managerDownloadFailed; + + [BackgroundDependencyLoader(true)] + private void load() + { + if (Manager == null) + return; + + // Used to interact with manager classes that don't support interface types. Will eventually be replaced. + var beatmapSetInfo = new BeatmapSetInfo { OnlineBeatmapSetID = TrackedItem.OnlineID }; + + if (Manager.IsAvailableLocally(beatmapSetInfo)) + UpdateState(DownloadState.LocallyAvailable); + else + attachDownload(Manager.GetExistingDownload(beatmapSetInfo)); + + managerDownloadBegan = Manager.DownloadBegan.GetBoundCopy(); + managerDownloadBegan.BindValueChanged(downloadBegan); + managerDownloadFailed = Manager.DownloadFailed.GetBoundCopy(); + managerDownloadFailed.BindValueChanged(downloadFailed); + managerUpdated = Manager.ItemUpdated.GetBoundCopy(); + managerUpdated.BindValueChanged(itemUpdated); + managerRemoved = Manager.ItemRemoved.GetBoundCopy(); + managerRemoved.BindValueChanged(itemRemoved); + } + + private void downloadBegan(ValueChangedEvent>> weakRequest) + { + if (weakRequest.NewValue.TryGetTarget(out var request)) + { + Schedule(() => + { + if (checkEquality(request.Model, TrackedItem)) + attachDownload(request); + }); + } + } + + private void downloadFailed(ValueChangedEvent>> weakRequest) + { + if (weakRequest.NewValue.TryGetTarget(out var request)) + { + Schedule(() => + { + if (checkEquality(request.Model, TrackedItem)) + attachDownload(null); + }); + } + } + + private void attachDownload(ArchiveDownloadRequest? request) + { + if (attachedRequest != null) + { + attachedRequest.Failure -= onRequestFailure; + attachedRequest.DownloadProgressed -= onRequestProgress; + attachedRequest.Success -= onRequestSuccess; + } + + attachedRequest = request; + + if (attachedRequest != null) + { + if (attachedRequest.Progress == 1) + { + UpdateProgress(1); + UpdateState(DownloadState.Importing); + } + else + { + UpdateProgress(attachedRequest.Progress); + UpdateState(DownloadState.Downloading); + + attachedRequest.Failure += onRequestFailure; + attachedRequest.DownloadProgressed += onRequestProgress; + attachedRequest.Success += onRequestSuccess; + } + } + else + { + UpdateState(DownloadState.NotDownloaded); + } + } + + private void onRequestSuccess(string _) => Schedule(() => UpdateState(DownloadState.Importing)); + + private void onRequestProgress(float progress) => Schedule(() => UpdateProgress(progress)); + + private void onRequestFailure(Exception e) => Schedule(() => attachDownload(null)); + + private void itemUpdated(ValueChangedEvent> weakItem) + { + if (weakItem.NewValue.TryGetTarget(out var item)) + { + Schedule(() => + { + if (checkEquality(item, TrackedItem)) + UpdateState(DownloadState.LocallyAvailable); + }); + } + } + + private void itemRemoved(ValueChangedEvent> weakItem) + { + if (weakItem.NewValue.TryGetTarget(out var item)) + { + Schedule(() => + { + if (checkEquality(item, TrackedItem)) + UpdateState(DownloadState.NotDownloaded); + }); + } + } + + private bool checkEquality(IBeatmapSetInfo x, IBeatmapSetInfo y) => x.OnlineID == y.OnlineID; + + #region Disposal + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + attachDownload(null); + } + + #endregion + } +} diff --git a/osu.Game/Online/Chat/Channel.cs b/osu.Game/Online/Chat/Channel.cs index 187a3e5dfc..9b463a6348 100644 --- a/osu.Game/Online/Chat/Channel.cs +++ b/osu.Game/Online/Chat/Channel.cs @@ -26,7 +26,7 @@ namespace osu.Game.Online.Chat { set { - foreach (var id in value) + foreach (int id in value) Users.Add(new User { Id = id }); } } @@ -131,7 +131,7 @@ namespace osu.Game.Online.Chat Messages.AddRange(messages); - var maxMessageId = messages.Max(m => m.Id); + long? maxMessageId = messages.Max(m => m.Id); if (maxMessageId > LastMessageId) LastMessageId = maxMessageId; diff --git a/osu.Game/Online/Chat/ChannelManager.cs b/osu.Game/Online/Chat/ChannelManager.cs index 47d5955fb0..52c9387185 100644 --- a/osu.Game/Online/Chat/ChannelManager.cs +++ b/osu.Game/Online/Chat/ChannelManager.cs @@ -218,7 +218,7 @@ namespace osu.Game.Online.Chat if (target == null) return; - var parameters = text.Split(' ', 2); + string[] parameters = text.Split(' ', 2); string command = parameters[0]; string content = parameters.Length == 2 ? parameters[1] : string.Empty; @@ -306,7 +306,7 @@ namespace osu.Game.Online.Chat { var req = new ListChannelsRequest(); - var joinDefaults = JoinedChannels.Count == 0; + bool joinDefaults = JoinedChannels.Count == 0; req.Success += channels => { diff --git a/osu.Game/Online/Chat/DrawableLinkCompiler.cs b/osu.Game/Online/Chat/DrawableLinkCompiler.cs index 4df60eba69..8356b36667 100644 --- a/osu.Game/Online/Chat/DrawableLinkCompiler.cs +++ b/osu.Game/Online/Chat/DrawableLinkCompiler.cs @@ -5,6 +5,8 @@ using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; @@ -30,6 +32,11 @@ namespace osu.Game.Online.Chat protected override HoverSounds CreateHoverSounds(HoverSampleSet sampleSet) => new LinkHoverSounds(sampleSet, Parts); + public DrawableLinkCompiler(ITextPart part) + : this(part.Drawables.OfType()) + { + } + public DrawableLinkCompiler(IEnumerable parts) : base(HoverSampleSet.Submit) { diff --git a/osu.Game/Online/Chat/MessageFormatter.cs b/osu.Game/Online/Chat/MessageFormatter.cs index 201ba6239b..5e0f66443b 100644 --- a/osu.Game/Online/Chat/MessageFormatter.cs +++ b/osu.Game/Online/Chat/MessageFormatter.cs @@ -70,14 +70,14 @@ namespace osu.Game.Online.Chat foreach (Match m in regex.Matches(result.Text, startIndex)) { - var index = m.Index - captureOffset; + int index = m.Index - captureOffset; - var displayText = string.Format(display, + string? displayText = string.Format(display, m.Groups[0], m.Groups["text"].Value, m.Groups["url"].Value).Trim(); - var linkText = string.Format(link, + string linkText = string.Format(link, m.Groups[0], m.Groups["text"].Value, m.Groups["url"].Value).Trim(); @@ -109,9 +109,9 @@ namespace osu.Game.Online.Chat { foreach (Match m in regex.Matches(result.Text, startIndex)) { - var index = m.Index; - var linkText = m.Groups["link"].Value; - var indexLength = linkText.Length; + int index = m.Index; + string? linkText = m.Groups["link"].Value; + int indexLength = linkText.Length; var details = GetLinkDetails(linkText); var link = new Link(linkText, index, indexLength, details.Action, details.Argument); @@ -126,7 +126,7 @@ namespace osu.Game.Online.Chat public static LinkDetails GetLinkDetails(string url) { - var args = url.Split('/', StringSplitOptions.RemoveEmptyEntries); + string[]? args = url.Split('/', StringSplitOptions.RemoveEmptyEntries); args[0] = args[0].TrimEnd(':'); switch (args[0]) @@ -136,7 +136,7 @@ namespace osu.Game.Online.Chat // length > 3 since all these links need another argument to work if (args.Length > 3 && args[1].EndsWith(websiteRootUrl, StringComparison.OrdinalIgnoreCase)) { - var mainArg = args[3]; + string mainArg = args[3]; switch (args[2]) { @@ -145,7 +145,7 @@ namespace osu.Game.Online.Chat case "beatmaps": { string trimmed = mainArg.Split('?').First(); - if (int.TryParse(trimmed, out var id)) + if (int.TryParse(trimmed, out int id)) return new LinkDetails(LinkAction.OpenBeatmap, id.ToString()); break; @@ -159,7 +159,7 @@ namespace osu.Game.Online.Chat // handle discussion links externally for now return new LinkDetails(LinkAction.External, url); - if (args.Length > 4 && int.TryParse(args[4], out var id)) + if (args.Length > 4 && int.TryParse(args[4], out int id)) // https://osu.ppy.sh/beatmapsets/1154158#osu/2768184 return new LinkDetails(LinkAction.OpenBeatmap, id.ToString()); @@ -273,7 +273,7 @@ namespace osu.Game.Online.Chat // handle channels handleMatches(channel_regex, "{0}", "osu://chan/{0}", result, startIndex, LinkAction.OpenChannel); - var empty = ""; + string empty = ""; while (space-- > 0) empty += "\0"; diff --git a/osu.Game/Online/Chat/NowPlayingCommand.cs b/osu.Game/Online/Chat/NowPlayingCommand.cs index 89eb00a45a..adb3d88df6 100644 --- a/osu.Game/Online/Chat/NowPlayingCommand.cs +++ b/osu.Game/Online/Chat/NowPlayingCommand.cs @@ -57,7 +57,7 @@ namespace osu.Game.Online.Chat break; } - var beatmapString = beatmapInfo.OnlineBeatmapID.HasValue ? $"[{api.WebsiteRootUrl}/b/{beatmapInfo.OnlineBeatmapID} {beatmapInfo}]" : beatmapInfo.ToString(); + string beatmapString = beatmapInfo.OnlineBeatmapID.HasValue ? $"[{api.WebsiteRootUrl}/b/{beatmapInfo.OnlineBeatmapID} {beatmapInfo}]" : beatmapInfo.ToString(); channelManager.PostMessage($"is {verb} {beatmapString}", true, target); Expire(); diff --git a/osu.Game/Online/Chat/StandAloneChatDisplay.cs b/osu.Game/Online/Chat/StandAloneChatDisplay.cs index 6ed2055e65..ede76235b1 100644 --- a/osu.Game/Online/Chat/StandAloneChatDisplay.cs +++ b/osu.Game/Online/Chat/StandAloneChatDisplay.cs @@ -85,7 +85,7 @@ namespace osu.Game.Online.Chat private void postMessage(TextBox sender, bool newtext) { - var text = Textbox.Text.Trim(); + string text = Textbox.Text.Trim(); if (string.IsNullOrWhiteSpace(text)) return; diff --git a/osu.Game/Online/DownloadTracker.cs b/osu.Game/Online/DownloadTracker.cs new file mode 100644 index 0000000000..357c64b6a3 --- /dev/null +++ b/osu.Game/Online/DownloadTracker.cs @@ -0,0 +1,39 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Bindables; +using osu.Framework.Graphics; + +#nullable enable + +namespace osu.Game.Online +{ + public abstract class DownloadTracker : Component + where T : class + { + public readonly T TrackedItem; + + /// + /// Holds the current download state of the download - whether is has already been downloaded, is in progress, or is not downloaded. + /// + public IBindable State => state; + + private readonly Bindable state = new Bindable(); + + /// + /// The progress of an active download. + /// + public IBindableNumber Progress => progress; + + private readonly BindableNumber progress = new BindableNumber { MinValue = 0, MaxValue = 1 }; + + protected DownloadTracker(T trackedItem) + { + TrackedItem = trackedItem; + } + + protected void UpdateState(DownloadState newState) => state.Value = newState; + + protected void UpdateProgress(double newProgress) => progress.Value = newProgress; + } +} diff --git a/osu.Game/Online/DownloadTrackingComposite.cs b/osu.Game/Online/DownloadTrackingComposite.cs deleted file mode 100644 index 2a96051427..0000000000 --- a/osu.Game/Online/DownloadTrackingComposite.cs +++ /dev/null @@ -1,196 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using System; -using JetBrains.Annotations; -using osu.Framework.Allocation; -using osu.Framework.Bindables; -using osu.Framework.Graphics.Containers; -using osu.Game.Database; -using osu.Game.Online.API; - -namespace osu.Game.Online -{ - /// - /// A component which tracks a through potential download/import/deletion. - /// - public abstract class DownloadTrackingComposite : CompositeDrawable - where TModel : class, IEquatable - where TModelManager : class, IModelDownloader, IModelManager - { - protected readonly Bindable Model = new Bindable(); - - [Resolved(CanBeNull = true)] - protected TModelManager Manager { get; private set; } - - /// - /// Holds the current download state of the , whether is has already been downloaded, is in progress, or is not downloaded. - /// - protected readonly Bindable State = new Bindable(); - - protected readonly BindableNumber Progress = new BindableNumber { MinValue = 0, MaxValue = 1 }; - - protected DownloadTrackingComposite(TModel model = null) - { - Model.Value = model; - } - - private IBindable> managerUpdated; - private IBindable> managerRemoved; - private IBindable>> managerDownloadBegan; - private IBindable>> managerDownloadFailed; - - [BackgroundDependencyLoader(true)] - private void load() - { - Model.BindValueChanged(modelInfo => - { - if (modelInfo.NewValue == null) - attachDownload(null); - else if (IsModelAvailableLocally()) - State.Value = DownloadState.LocallyAvailable; - else - attachDownload(Manager?.GetExistingDownload(modelInfo.NewValue)); - }, true); - - if (Manager == null) - return; - - managerDownloadBegan = Manager.DownloadBegan.GetBoundCopy(); - managerDownloadBegan.BindValueChanged(downloadBegan); - managerDownloadFailed = Manager.DownloadFailed.GetBoundCopy(); - managerDownloadFailed.BindValueChanged(downloadFailed); - managerUpdated = Manager.ItemUpdated.GetBoundCopy(); - managerUpdated.BindValueChanged(itemUpdated); - managerRemoved = Manager.ItemRemoved.GetBoundCopy(); - managerRemoved.BindValueChanged(itemRemoved); - } - - /// - /// Checks that a database model matches the one expected to be downloaded. - /// - /// - /// For online play, this could be used to check that the databased model matches the online beatmap. - /// - /// The model in database. - protected virtual bool VerifyDatabasedModel([NotNull] TModel databasedModel) => true; - - /// - /// Whether the given model is available in the database. - /// By default, this calls , - /// but can be overriden to add additional checks for verifying the model in database. - /// - protected virtual bool IsModelAvailableLocally() => Manager?.IsAvailableLocally(Model.Value) == true; - - private void downloadBegan(ValueChangedEvent>> weakRequest) - { - if (weakRequest.NewValue.TryGetTarget(out var request)) - { - Schedule(() => - { - if (request.Model.Equals(Model.Value)) - attachDownload(request); - }); - } - } - - private void downloadFailed(ValueChangedEvent>> weakRequest) - { - if (weakRequest.NewValue.TryGetTarget(out var request)) - { - Schedule(() => - { - if (request.Model.Equals(Model.Value)) - attachDownload(null); - }); - } - } - - private ArchiveDownloadRequest attachedRequest; - - private void attachDownload(ArchiveDownloadRequest request) - { - if (attachedRequest != null) - { - attachedRequest.Failure -= onRequestFailure; - attachedRequest.DownloadProgressed -= onRequestProgress; - attachedRequest.Success -= onRequestSuccess; - } - - attachedRequest = request; - - if (attachedRequest != null) - { - if (attachedRequest.Progress == 1) - { - Progress.Value = 1; - State.Value = DownloadState.Importing; - } - else - { - Progress.Value = attachedRequest.Progress; - State.Value = DownloadState.Downloading; - - attachedRequest.Failure += onRequestFailure; - attachedRequest.DownloadProgressed += onRequestProgress; - attachedRequest.Success += onRequestSuccess; - } - } - else - { - State.Value = DownloadState.NotDownloaded; - } - } - - private void onRequestSuccess(string _) => Schedule(() => State.Value = DownloadState.Importing); - - private void onRequestProgress(float progress) => Schedule(() => Progress.Value = progress); - - private void onRequestFailure(Exception e) => Schedule(() => attachDownload(null)); - - private void itemUpdated(ValueChangedEvent> weakItem) - { - if (weakItem.NewValue.TryGetTarget(out var item)) - { - Schedule(() => - { - if (!item.Equals(Model.Value)) - return; - - if (!VerifyDatabasedModel(item)) - { - State.Value = DownloadState.NotDownloaded; - return; - } - - State.Value = DownloadState.LocallyAvailable; - }); - } - } - - private void itemRemoved(ValueChangedEvent> weakItem) - { - if (weakItem.NewValue.TryGetTarget(out var item)) - { - Schedule(() => - { - if (item.Equals(Model.Value)) - State.Value = DownloadState.NotDownloaded; - }); - } - } - - #region Disposal - - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - - State.UnbindAll(); - - attachDownload(null); - } - - #endregion - } -} diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs index e3ac9f603d..515cc6fd73 100644 --- a/osu.Game/Online/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -350,8 +350,8 @@ namespace osu.Game.Online.Leaderboards { base.UpdateAfterChildren(); - var fadeBottom = scrollContainer.Current + scrollContainer.DrawHeight; - var fadeTop = scrollContainer.Current + LeaderboardScore.HEIGHT; + float fadeBottom = scrollContainer.Current + scrollContainer.DrawHeight; + float fadeTop = scrollContainer.Current + LeaderboardScore.HEIGHT; if (!scrollContainer.IsScrolledToEnd()) fadeBottom -= LeaderboardScore.HEIGHT; @@ -361,8 +361,8 @@ namespace osu.Game.Online.Leaderboards foreach (var c in scrollFlow.Children) { - var topY = c.ToSpaceOfOtherDrawable(Vector2.Zero, scrollFlow).Y; - var bottomY = topY + LeaderboardScore.HEIGHT; + float topY = c.ToSpaceOfOtherDrawable(Vector2.Zero, scrollFlow).Y; + float bottomY = topY + LeaderboardScore.HEIGHT; bool requireTopFade = FadeTop && topY <= fadeTop; bool requireBottomFade = FadeBottom && bottomY >= fadeBottom; diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index 28505f6b0e..0586e0ae60 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -148,6 +148,10 @@ namespace osu.Game.Online.Multiplayer { Room = joinedRoom; APIRoom = room; + + Debug.Assert(LocalUser != null); + addUserToAPIRoom(LocalUser); + foreach (var user in joinedRoom.Users) updateUserPlayingState(user.UserID, user.State); @@ -372,6 +376,8 @@ namespace osu.Game.Online.Multiplayer Room.Users.Add(user); + addUserToAPIRoom(user); + UserJoined?.Invoke(user); RoomUpdated?.Invoke(); }); @@ -391,6 +397,18 @@ namespace osu.Game.Online.Multiplayer return handleUserLeft(user, UserKicked); } + private void addUserToAPIRoom(MultiplayerRoomUser user) + { + Debug.Assert(APIRoom != null); + + APIRoom.RecentParticipants.Add(user.User ?? new User + { + Id = user.UserID, + Username = "[Unresolved]" + }); + APIRoom.ParticipantCount.Value++; + } + private Task handleUserLeft(MultiplayerRoomUser user, Action? callback) { if (Room == null) @@ -404,6 +422,10 @@ namespace osu.Game.Online.Multiplayer Room.Users.Remove(user); PlayingUserIds.Remove(user.UserID); + Debug.Assert(APIRoom != null); + APIRoom.RecentParticipants.RemoveAll(u => u.Id == user.UserID); + APIRoom.ParticipantCount.Value--; + callback?.Invoke(user); RoomUpdated?.Invoke(); }, false); diff --git a/osu.Game/Online/Rooms/IndexPlaylistScoresRequest.cs b/osu.Game/Online/Rooms/IndexPlaylistScoresRequest.cs index abce2093e3..bd9f254e1a 100644 --- a/osu.Game/Online/Rooms/IndexPlaylistScoresRequest.cs +++ b/osu.Game/Online/Rooms/IndexPlaylistScoresRequest.cs @@ -47,7 +47,7 @@ namespace osu.Game.Online.Rooms req.AddCursor(Cursor); - foreach (var (key, value) in IndexParams.Properties) + foreach ((string key, var value) in IndexParams.Properties) req.AddParameter(key, value.ToString()); } diff --git a/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs b/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs index 52aa115083..6cd735af23 100644 --- a/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs +++ b/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs @@ -2,8 +2,10 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Linq; +using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Logging; using osu.Framework.Threading; using osu.Game.Beatmaps; @@ -16,19 +18,27 @@ namespace osu.Game.Online.Rooms /// This differs from a regular download tracking composite as this accounts for the /// databased beatmap set's checksum, to disallow from playing with an altered version of the beatmap. /// - public class OnlinePlayBeatmapAvailabilityTracker : DownloadTrackingComposite + public sealed class OnlinePlayBeatmapAvailabilityTracker : CompositeDrawable { public readonly IBindable SelectedItem = new Bindable(); + // Required to allow child components to update. Can potentially be replaced with a `CompositeComponent` class if or when we make one. + protected override bool RequiresChildrenUpdate => true; + + [Resolved] + private BeatmapManager beatmapManager { get; set; } + /// /// The availability state of the currently selected playlist item. /// public IBindable Availability => availability; - private readonly Bindable availability = new Bindable(BeatmapAvailability.LocallyAvailable()); + private readonly Bindable availability = new Bindable(BeatmapAvailability.NotDownloaded()); private ScheduledDelegate progressUpdate; + private BeatmapDownloadTracker downloadTracker; + protected override void LoadComplete() { base.LoadComplete(); @@ -40,58 +50,38 @@ namespace osu.Game.Online.Rooms if (item.NewValue == null) return; - Model.Value = item.NewValue.Beatmap.Value.BeatmapSet; + downloadTracker?.RemoveAndDisposeImmediately(); + + downloadTracker = new BeatmapDownloadTracker(item.NewValue.Beatmap.Value.BeatmapSet); + downloadTracker.State.BindValueChanged(_ => updateAvailability()); + downloadTracker.Progress.BindValueChanged(_ => + { + if (downloadTracker.State.Value != DownloadState.Downloading) + return; + + // incoming progress changes are going to be at a very high rate. + // we don't want to flood the network with this, so rate limit how often we send progress updates. + if (progressUpdate?.Completed != false) + progressUpdate = Scheduler.AddDelayed(updateAvailability, progressUpdate == null ? 0 : 500); + }); + + AddInternal(downloadTracker); }, true); - - Progress.BindValueChanged(_ => - { - if (State.Value != DownloadState.Downloading) - return; - - // incoming progress changes are going to be at a very high rate. - // we don't want to flood the network with this, so rate limit how often we send progress updates. - if (progressUpdate?.Completed != false) - progressUpdate = Scheduler.AddDelayed(updateAvailability, progressUpdate == null ? 0 : 500); - }); - - State.BindValueChanged(_ => updateAvailability(), true); - } - - protected override bool VerifyDatabasedModel(BeatmapSetInfo databasedSet) - { - int beatmapId = SelectedItem.Value?.Beatmap.Value.OnlineID ?? -1; - string checksum = SelectedItem.Value?.Beatmap.Value.MD5Hash; - - var matchingBeatmap = databasedSet.Beatmaps.FirstOrDefault(b => b.OnlineBeatmapID == beatmapId && b.MD5Hash == checksum); - - if (matchingBeatmap == null) - { - Logger.Log("The imported beatmap set does not match the online version.", LoggingTarget.Runtime, LogLevel.Important); - return false; - } - - return true; - } - - protected override bool IsModelAvailableLocally() - { - int onlineId = SelectedItem.Value.Beatmap.Value.OnlineID; - string checksum = SelectedItem.Value.Beatmap.Value.MD5Hash; - - var beatmap = Manager.QueryBeatmap(b => b.OnlineBeatmapID == onlineId && b.MD5Hash == checksum); - return beatmap?.BeatmapSet.DeletePending == false; } private void updateAvailability() { - switch (State.Value) + if (downloadTracker == null) + return; + + switch (downloadTracker.State.Value) { case DownloadState.NotDownloaded: availability.Value = BeatmapAvailability.NotDownloaded(); break; case DownloadState.Downloading: - availability.Value = BeatmapAvailability.Downloading((float)Progress.Value); + availability.Value = BeatmapAvailability.Downloading((float)downloadTracker.Progress.Value); break; case DownloadState.Importing: @@ -99,12 +89,27 @@ namespace osu.Game.Online.Rooms break; case DownloadState.LocallyAvailable: - availability.Value = BeatmapAvailability.LocallyAvailable(); + bool hashMatches = checkHashValidity(); + + availability.Value = hashMatches ? BeatmapAvailability.LocallyAvailable() : BeatmapAvailability.NotDownloaded(); + + // only display a message to the user if a download seems to have just completed. + if (!hashMatches && downloadTracker.Progress.Value == 1) + Logger.Log("The imported beatmap set does not match the online version.", LoggingTarget.Runtime, LogLevel.Important); + break; default: - throw new ArgumentOutOfRangeException(nameof(State)); + throw new ArgumentOutOfRangeException(); } } + + private bool checkHashValidity() + { + int onlineId = SelectedItem.Value.Beatmap.Value.OnlineID; + string checksum = SelectedItem.Value.Beatmap.Value.MD5Hash; + + return beatmapManager.QueryBeatmap(b => b.OnlineBeatmapID == onlineId && b.MD5Hash == checksum && !b.BeatmapSet.DeletePending) != null; + } } } diff --git a/osu.Game/Online/ScoreDownloadTracker.cs b/osu.Game/Online/ScoreDownloadTracker.cs new file mode 100644 index 0000000000..675dbf608c --- /dev/null +++ b/osu.Game/Online/ScoreDownloadTracker.cs @@ -0,0 +1,155 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Game.Online.API; +using osu.Game.Scoring; + +#nullable enable + +namespace osu.Game.Online +{ + public class ScoreDownloadTracker : DownloadTracker + { + [Resolved(CanBeNull = true)] + protected ScoreManager? Manager { get; private set; } + + private ArchiveDownloadRequest? attachedRequest; + + public ScoreDownloadTracker(ScoreInfo trackedItem) + : base(trackedItem) + { + } + + private IBindable>? managerUpdated; + private IBindable>? managerRemoved; + private IBindable>>? managerDownloadBegan; + private IBindable>>? managerDownloadFailed; + + [BackgroundDependencyLoader(true)] + private void load() + { + if (Manager == null) + return; + + // Used to interact with manager classes that don't support interface types. Will eventually be replaced. + var scoreInfo = new ScoreInfo { OnlineScoreID = TrackedItem.OnlineScoreID }; + + if (Manager.IsAvailableLocally(scoreInfo)) + UpdateState(DownloadState.LocallyAvailable); + else + attachDownload(Manager.GetExistingDownload(scoreInfo)); + + managerDownloadBegan = Manager.DownloadBegan.GetBoundCopy(); + managerDownloadBegan.BindValueChanged(downloadBegan); + managerDownloadFailed = Manager.DownloadFailed.GetBoundCopy(); + managerDownloadFailed.BindValueChanged(downloadFailed); + managerUpdated = Manager.ItemUpdated.GetBoundCopy(); + managerUpdated.BindValueChanged(itemUpdated); + managerRemoved = Manager.ItemRemoved.GetBoundCopy(); + managerRemoved.BindValueChanged(itemRemoved); + } + + private void downloadBegan(ValueChangedEvent>> weakRequest) + { + if (weakRequest.NewValue.TryGetTarget(out var request)) + { + Schedule(() => + { + if (checkEquality(request.Model, TrackedItem)) + attachDownload(request); + }); + } + } + + private void downloadFailed(ValueChangedEvent>> weakRequest) + { + if (weakRequest.NewValue.TryGetTarget(out var request)) + { + Schedule(() => + { + if (checkEquality(request.Model, TrackedItem)) + attachDownload(null); + }); + } + } + + private void attachDownload(ArchiveDownloadRequest? request) + { + if (attachedRequest != null) + { + attachedRequest.Failure -= onRequestFailure; + attachedRequest.DownloadProgressed -= onRequestProgress; + attachedRequest.Success -= onRequestSuccess; + } + + attachedRequest = request; + + if (attachedRequest != null) + { + if (attachedRequest.Progress == 1) + { + UpdateProgress(1); + UpdateState(DownloadState.Importing); + } + else + { + UpdateProgress(attachedRequest.Progress); + UpdateState(DownloadState.Downloading); + + attachedRequest.Failure += onRequestFailure; + attachedRequest.DownloadProgressed += onRequestProgress; + attachedRequest.Success += onRequestSuccess; + } + } + else + { + UpdateState(DownloadState.NotDownloaded); + } + } + + private void onRequestSuccess(string _) => Schedule(() => UpdateState(DownloadState.Importing)); + + private void onRequestProgress(float progress) => Schedule(() => UpdateProgress(progress)); + + private void onRequestFailure(Exception e) => Schedule(() => attachDownload(null)); + + private void itemUpdated(ValueChangedEvent> weakItem) + { + if (weakItem.NewValue.TryGetTarget(out var item)) + { + Schedule(() => + { + if (checkEquality(item, TrackedItem)) + UpdateState(DownloadState.LocallyAvailable); + }); + } + } + + private void itemRemoved(ValueChangedEvent> weakItem) + { + if (weakItem.NewValue.TryGetTarget(out var item)) + { + Schedule(() => + { + if (checkEquality(item, TrackedItem)) + UpdateState(DownloadState.NotDownloaded); + }); + } + } + + private bool checkEquality(ScoreInfo x, ScoreInfo y) => x.OnlineScoreID == y.OnlineScoreID; + + #region Disposal + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + attachDownload(null); + } + + #endregion + } +} diff --git a/osu.Game/Online/Solo/SubmitSoloScoreRequest.cs b/osu.Game/Online/Solo/SubmitSoloScoreRequest.cs index 85fa3eeb34..25c2e5a61f 100644 --- a/osu.Game/Online/Solo/SubmitSoloScoreRequest.cs +++ b/osu.Game/Online/Solo/SubmitSoloScoreRequest.cs @@ -16,13 +16,13 @@ namespace osu.Game.Online.Solo private readonly int beatmapId; - private readonly ScoreInfo scoreInfo; + private readonly SubmittableScore score; public SubmitSoloScoreRequest(int beatmapId, long scoreId, ScoreInfo scoreInfo) { this.beatmapId = beatmapId; this.scoreId = scoreId; - this.scoreInfo = scoreInfo; + score = new SubmittableScore(scoreInfo); } protected override WebRequest CreateWebRequest() @@ -32,7 +32,7 @@ namespace osu.Game.Online.Solo req.ContentType = "application/json"; req.Method = HttpMethod.Put; - req.AddRaw(JsonConvert.SerializeObject(scoreInfo, new JsonSerializerSettings + req.AddRaw(JsonConvert.SerializeObject(score, new JsonSerializerSettings { ReferenceLoopHandling = ReferenceLoopHandling.Ignore })); diff --git a/osu.Game/Online/Solo/SubmittableScore.cs b/osu.Game/Online/Solo/SubmittableScore.cs new file mode 100644 index 0000000000..bafb308a13 --- /dev/null +++ b/osu.Game/Online/Solo/SubmittableScore.cs @@ -0,0 +1,75 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using JetBrains.Annotations; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests.Responses; +using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; +using osu.Game.Users; + +namespace osu.Game.Online.Solo +{ + /// + /// A class specifically for sending scores to the API during score submission. + /// This is used instead of due to marginally different serialisation naming requirements. + /// + [Serializable] + public class SubmittableScore + { + [JsonProperty("rank")] + [JsonConverter(typeof(StringEnumConverter))] + public ScoreRank Rank { get; set; } + + [JsonProperty("total_score")] + public long TotalScore { get; set; } + + [JsonProperty("accuracy")] + public double Accuracy { get; set; } + + [JsonProperty(@"pp")] + public double? PP { get; set; } + + [JsonProperty("max_combo")] + public int MaxCombo { get; set; } + + [JsonProperty("ruleset_id")] + public int RulesetID { get; set; } + + [JsonProperty("passed")] + public bool Passed { get; set; } + + // Used for API serialisation/deserialisation. + [JsonProperty("mods")] + public APIMod[] Mods { get; set; } + + [JsonProperty("user")] + public User User { get; set; } + + [JsonProperty("statistics")] + public Dictionary Statistics { get; set; } + + [UsedImplicitly] + public SubmittableScore() + { + } + + public SubmittableScore(ScoreInfo score) + { + Rank = score.Rank; + TotalScore = score.TotalScore; + Accuracy = score.Accuracy; + PP = score.PP; + MaxCombo = score.MaxCombo; + RulesetID = score.RulesetID; + Passed = score.Passed; + Mods = score.APIMods; + User = score.User; + Statistics = score.Statistics; + } + } +} diff --git a/osu.Game/Online/Spectator/SpectatorClient.cs b/osu.Game/Online/Spectator/SpectatorClient.cs index b597b2f214..f9366674d8 100644 --- a/osu.Game/Online/Spectator/SpectatorClient.cs +++ b/osu.Game/Online/Spectator/SpectatorClient.cs @@ -80,7 +80,7 @@ namespace osu.Game.Online.Spectator watchingUsers.Clear(); // resubscribe to watched users. - foreach (var userId in users) + foreach (int userId in users) WatchUser(userId); // re-send state in case it wasn't received diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 2cbe05fecd..a7ed7fedf5 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -158,6 +158,8 @@ namespace osu.Game private Bindable configRuleset; + private Bindable uiScale; + private Bindable configSkin; private readonly string[] args; @@ -219,6 +221,7 @@ namespace osu.Game // bind config int to database RulesetInfo configRuleset = LocalConfig.GetBindable(OsuSetting.Ruleset); + uiScale = LocalConfig.GetBindable(OsuSetting.UIScale); var preferredRuleset = RulesetStore.GetRuleset(configRuleset.Value); @@ -336,7 +339,7 @@ namespace osu.Game ShowChangelogListing(); else { - var changelogArgs = link.Argument.Split("/"); + string[] changelogArgs = link.Argument.Split("/"); ShowChangelogBuild(changelogArgs[0], changelogArgs[1]); } @@ -433,11 +436,15 @@ namespace osu.Game /// first beatmap from any ruleset. /// /// - public void PresentBeatmap(BeatmapSetInfo beatmap, Predicate difficultyCriteria = null) + public void PresentBeatmap(IBeatmapSetInfo beatmap, Predicate difficultyCriteria = null) { - var databasedSet = beatmap.OnlineBeatmapSetID != null - ? BeatmapManager.QueryBeatmapSet(s => s.OnlineBeatmapSetID == beatmap.OnlineBeatmapSetID) - : BeatmapManager.QueryBeatmapSet(s => s.Hash == beatmap.Hash); + BeatmapSetInfo databasedSet = null; + + if (beatmap.OnlineID > 0) + databasedSet = BeatmapManager.QueryBeatmapSet(s => s.OnlineBeatmapSetID == beatmap.OnlineID); + + if (beatmap is BeatmapSetInfo localBeatmap) + databasedSet ??= BeatmapManager.QueryBeatmapSet(s => s.Hash == localBeatmap.Hash); if (databasedSet == null) { @@ -622,7 +629,7 @@ namespace osu.Game foreach (var language in Enum.GetValues(typeof(Language)).OfType()) { - var cultureCode = language.ToCultureCode(); + string cultureCode = language.ToCultureCode(); try { @@ -870,7 +877,7 @@ namespace osu.Game { if (args?.Length > 0) { - var paths = args.Where(a => !a.StartsWith('-')).ToArray(); + string[] paths = args.Where(a => !a.StartsWith('-')).ToArray(); if (paths.Length > 0) Task.Run(() => Import(paths)); } @@ -913,13 +920,15 @@ namespace osu.Game } else if (recentLogCount == short_term_display_limit) { + string logFile = $@"{entry.Target.ToString().ToLowerInvariant()}.log"; + Schedule(() => Notifications.Post(new SimpleNotification { Icon = FontAwesome.Solid.EllipsisH, Text = "Subsequent messages have been logged. Click to view log files.", Activated = () => { - Storage.GetStorageForDirectory("logs").OpenInNativeExplorer(); + Storage.GetStorageForDirectory(@"logs").PresentFileExternally(logFile); return true; } })); @@ -1018,6 +1027,28 @@ namespace osu.Game return false; } + public override bool OnPressed(KeyBindingPressEvent e) + { + const float adjustment_increment = 0.05f; + + switch (e.Action) + { + case PlatformAction.ZoomIn: + uiScale.Value += adjustment_increment; + return true; + + case PlatformAction.ZoomOut: + uiScale.Value -= adjustment_increment; + return true; + + case PlatformAction.ZoomDefault: + uiScale.SetDefault(); + return true; + } + + return base.OnPressed(e); + } + #region Inactive audio dimming private readonly BindableDouble inactiveVolumeFade = new BindableDouble(); @@ -1057,7 +1088,7 @@ namespace osu.Game ScreenOffsetContainer.Padding = new MarginPadding { Top = toolbarOffset }; overlayOffsetContainer.Padding = new MarginPadding { Top = toolbarOffset }; - var horizontalOffset = 0f; + float horizontalOffset = 0f; // Content.ToLocalSpace() is used instead of this.ToLocalSpace() to correctly calculate the offset with scaling modes active. // Content is a child of a scaling container with ScalingMode.Everything set, while the game itself is never scaled. diff --git a/osu.Game/Overlays/AccountCreation/ScreenEntry.cs b/osu.Game/Overlays/AccountCreation/ScreenEntry.cs index bcb3d4b635..8ee3b1cb2e 100644 --- a/osu.Game/Overlays/AccountCreation/ScreenEntry.cs +++ b/osu.Game/Overlays/AccountCreation/ScreenEntry.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using osu.Framework.Allocation; @@ -37,7 +36,7 @@ namespace osu.Game.Overlays.AccountCreation private IAPIProvider api { get; set; } private ShakeContainer registerShake; - private IEnumerable characterCheckText; + private ITextPart characterCheckText; private OsuTextBox[] textboxes; private LoadingLayer loadingLayer; @@ -136,7 +135,7 @@ namespace osu.Game.Overlays.AccountCreation characterCheckText = passwordDescription.AddText("8 characters long"); passwordDescription.AddText(". Choose something long but also something you will remember, like a line from your favourite song."); - passwordTextBox.Current.ValueChanged += password => { characterCheckText.ForEach(s => s.Colour = password.NewValue.Length == 0 ? Color4.White : Interpolation.ValueAt(password.NewValue.Length, Color4.OrangeRed, Color4.YellowGreen, 0, 8, Easing.In)); }; + passwordTextBox.Current.ValueChanged += password => { characterCheckText.Drawables.ForEach(s => s.Colour = password.NewValue.Length == 0 ? Color4.White : Interpolation.ValueAt(password.NewValue.Length, Color4.OrangeRed, Color4.YellowGreen, 0, 8, Easing.In)); }; } public override void OnEntering(IScreen last) diff --git a/osu.Game/Overlays/BeatmapDownloadTrackingComposite.cs b/osu.Game/Overlays/BeatmapDownloadTrackingComposite.cs deleted file mode 100644 index f6b5b181c3..0000000000 --- a/osu.Game/Overlays/BeatmapDownloadTrackingComposite.cs +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using osu.Framework.Bindables; -using osu.Game.Beatmaps; -using osu.Game.Online; - -namespace osu.Game.Overlays -{ - public abstract class BeatmapDownloadTrackingComposite : DownloadTrackingComposite - { - public Bindable BeatmapSet => Model; - - protected BeatmapDownloadTrackingComposite(BeatmapSetInfo set = null) - : base(set) - { - } - } -} diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs b/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs index 03d36ff5df..fa57191ef3 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs @@ -12,9 +12,9 @@ using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Framework.Localisation; using osu.Framework.Threading; -using osu.Game.Beatmaps; using osu.Game.Online.API; using osu.Game.Online.API.Requests; +using osu.Game.Online.API.Requests.Responses; using osu.Game.Rulesets; using osu.Game.Resources.Localisation.Web; using osuTK; @@ -206,7 +206,7 @@ namespace osu.Game.Overlays.BeatmapListing getSetsRequest.Success += response => { - var sets = response.BeatmapSets.Select(responseJson => responseJson.ToBeatmapSet(rulesets)).ToList(); + var sets = response.BeatmapSets.ToList(); // If the previous request returned a null cursor, the API is indicating we can't paginate further (maybe there are no more beatmaps left). if (sets.Count == 0 || response.Cursor == null) @@ -289,7 +289,7 @@ namespace osu.Game.Overlays.BeatmapListing /// Contains the beatmap sets returned from API. /// Valid for read if and only if is . /// - public List Results { get; private set; } + public List Results { get; private set; } /// /// Contains the names of supporter-only filters requested by the user. @@ -297,7 +297,7 @@ namespace osu.Game.Overlays.BeatmapListing /// public List SupporterOnlyFiltersUsed { get; private set; } - public static SearchResult ResultsReturned(List results) => new SearchResult + public static SearchResult ResultsReturned(List results) => new SearchResult { Type = SearchResultType.ResultsReturned, Results = results diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs b/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs index da2dcfebdf..2474515802 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs @@ -8,12 +8,12 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; -using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; using osu.Game.Configuration; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osu.Game.Input.Bindings; +using osu.Game.Online.API.Requests.Responses; using osu.Game.Resources.Localisation.Web; using osu.Game.Rulesets; using osu.Game.Scoring; @@ -49,17 +49,17 @@ namespace osu.Game.Overlays.BeatmapListing public Bindable ExplicitContent => explicitContentFilter.Current; - public BeatmapSetInfo BeatmapSet + public APIBeatmapSet BeatmapSet { set { - if (value == null || string.IsNullOrEmpty(value.OnlineInfo.Covers.Cover)) + if (value == null || string.IsNullOrEmpty(value.Covers.Cover)) { beatmapCover.FadeOut(600, Easing.OutQuint); return; } - beatmapCover.BeatmapSet = value; + beatmapCover.OnlineInfo = value; beatmapCover.FadeTo(0.1f, 200, Easing.OutQuint); } } diff --git a/osu.Game/Overlays/BeatmapListing/Panels/BeatmapPanel.cs b/osu.Game/Overlays/BeatmapListing/Panels/BeatmapPanel.cs index 779f3860f2..2fd05f6742 100644 --- a/osu.Game/Overlays/BeatmapListing/Panels/BeatmapPanel.cs +++ b/osu.Game/Overlays/BeatmapListing/Panels/BeatmapPanel.cs @@ -23,6 +23,7 @@ using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; +using osu.Game.Online.API.Requests.Responses; using osuTK; using osuTK.Graphics; @@ -30,7 +31,7 @@ namespace osu.Game.Overlays.BeatmapListing.Panels { public abstract class BeatmapPanel : OsuClickableContainer, IHasContextMenu { - public readonly BeatmapSetInfo SetInfo; + public readonly APIBeatmapSet SetInfo; private const double hover_transition_time = 400; private const int maximum_difficulty_icons = 10; @@ -49,10 +50,10 @@ namespace osu.Game.Overlays.BeatmapListing.Panels protected Action ViewBeatmap; - protected BeatmapPanel(BeatmapSetInfo setInfo) + protected BeatmapPanel(APIBeatmapSet setInfo) : base(HoverSampleSet.Submit) { - Debug.Assert(setInfo.OnlineBeatmapSetID != null); + Debug.Assert(setInfo.OnlineID > 0); SetInfo = setInfo; } @@ -95,8 +96,8 @@ namespace osu.Game.Overlays.BeatmapListing.Panels Action = ViewBeatmap = () => { - Debug.Assert(SetInfo.OnlineBeatmapSetID != null); - beatmapSetOverlay?.FetchAndShowBeatmapSet(SetInfo.OnlineBeatmapSetID.Value); + Debug.Assert(SetInfo.OnlineID > 0); + beatmapSetOverlay?.FetchAndShowBeatmapSet(SetInfo.OnlineID); }; } @@ -146,14 +147,14 @@ namespace osu.Game.Overlays.BeatmapListing.Panels { var icons = new List(); - if (SetInfo.Beatmaps.Count > maximum_difficulty_icons) + if (SetInfo.Beatmaps.Count() > maximum_difficulty_icons) { foreach (var ruleset in SetInfo.Beatmaps.Select(b => b.Ruleset).Distinct()) - icons.Add(new GroupedDifficultyIcon(SetInfo.Beatmaps.FindAll(b => b.Ruleset.Equals(ruleset)), ruleset, this is ListBeatmapPanel ? Color4.White : colours.Gray5)); + icons.Add(new GroupedDifficultyIcon(SetInfo.Beatmaps.Where(b => b.RulesetID == ruleset.OnlineID).ToList(), ruleset, this is ListBeatmapPanel ? Color4.White : colours.Gray5)); } else { - foreach (var b in SetInfo.Beatmaps.OrderBy(beatmap => beatmap.Ruleset.ID).ThenBy(beatmap => beatmap.StarDifficulty)) + foreach (var b in SetInfo.Beatmaps.OrderBy(beatmap => beatmap.RulesetID).ThenBy(beatmap => beatmap.StarRating)) icons.Add(new DifficultyIcon(b)); } @@ -163,7 +164,7 @@ namespace osu.Game.Overlays.BeatmapListing.Panels protected Drawable CreateBackground() => new UpdateableOnlineBeatmapSetCover { RelativeSizeAxes = Axes.Both, - BeatmapSet = SetInfo, + OnlineInfo = SetInfo, }; public class Statistic : FillFlowContainer diff --git a/osu.Game/Overlays/BeatmapListing/Panels/BeatmapPanelDownloadButton.cs b/osu.Game/Overlays/BeatmapListing/Panels/BeatmapPanelDownloadButton.cs index a8c4334ffb..d7c2837f4d 100644 --- a/osu.Game/Overlays/BeatmapListing/Panels/BeatmapPanelDownloadButton.cs +++ b/osu.Game/Overlays/BeatmapListing/Panels/BeatmapPanelDownloadButton.cs @@ -5,37 +5,54 @@ using System; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osu.Game.Online; +using osu.Game.Online.API.Requests.Responses; namespace osu.Game.Overlays.BeatmapListing.Panels { - public class BeatmapPanelDownloadButton : BeatmapDownloadTrackingComposite + public class BeatmapPanelDownloadButton : CompositeDrawable { protected bool DownloadEnabled => button.Enabled.Value; /// /// Currently selected beatmap. Used to present the correct difficulty after completing a download. /// - public readonly IBindable SelectedBeatmap = new Bindable(); + public readonly IBindable SelectedBeatmap = new Bindable(); private readonly ShakeContainer shakeContainer; private readonly DownloadButton button; private Bindable noVideoSetting; - public BeatmapPanelDownloadButton(BeatmapSetInfo beatmapSet) - : base(beatmapSet) + protected readonly BeatmapDownloadTracker DownloadTracker; + + protected readonly Bindable State = new Bindable(); + + private readonly IBeatmapSetInfo beatmapSet; + + public BeatmapPanelDownloadButton(IBeatmapSetInfo beatmapSet) { - InternalChild = shakeContainer = new ShakeContainer + this.beatmapSet = beatmapSet; + + InternalChildren = new Drawable[] { - RelativeSizeAxes = Axes.Both, - Child = button = new DownloadButton + shakeContainer = new ShakeContainer { RelativeSizeAxes = Axes.Both, + Child = button = new DownloadButton + { + RelativeSizeAxes = Axes.Both, + State = { BindTarget = State } + }, }, + DownloadTracker = new BeatmapDownloadTracker(beatmapSet) + { + State = { BindTarget = State } + } }; button.Add(new DownloadProgressBar(beatmapSet) @@ -46,14 +63,6 @@ namespace osu.Game.Overlays.BeatmapListing.Panels }); } - protected override void LoadComplete() - { - base.LoadComplete(); - - button.State.BindTo(State); - FinishTransforms(true); - } - [BackgroundDependencyLoader(true)] private void load(OsuGame game, BeatmapManager beatmaps, OsuConfigManager osuConfig) { @@ -61,7 +70,7 @@ namespace osu.Game.Overlays.BeatmapListing.Panels button.Action = () => { - switch (State.Value) + switch (DownloadTracker.State.Value) { case DownloadState.Downloading: case DownloadState.Importing: @@ -71,13 +80,13 @@ namespace osu.Game.Overlays.BeatmapListing.Panels case DownloadState.LocallyAvailable: Predicate findPredicate = null; if (SelectedBeatmap.Value != null) - findPredicate = b => b.OnlineBeatmapID == SelectedBeatmap.Value.OnlineBeatmapID; + findPredicate = b => b.OnlineBeatmapID == SelectedBeatmap.Value.OnlineID; - game?.PresentBeatmap(BeatmapSet.Value, findPredicate); + game?.PresentBeatmap(beatmapSet, findPredicate); break; default: - beatmaps.Download(BeatmapSet.Value, noVideoSetting.Value); + beatmaps.Download(new BeatmapSetInfo { OnlineBeatmapSetID = beatmapSet.OnlineID }, noVideoSetting.Value); break; } }; @@ -92,7 +101,7 @@ namespace osu.Game.Overlays.BeatmapListing.Panels break; default: - if (BeatmapSet.Value?.OnlineInfo?.Availability.DownloadDisabled ?? false) + if ((beatmapSet as IBeatmapSetOnlineInfo)?.Availability.DownloadDisabled == true) { button.Enabled.Value = false; button.TooltipText = "this beatmap is currently not available for download."; @@ -102,5 +111,11 @@ namespace osu.Game.Overlays.BeatmapListing.Panels } }, true); } + + protected override void LoadComplete() + { + base.LoadComplete(); + FinishTransforms(true); + } } } diff --git a/osu.Game/Overlays/BeatmapListing/Panels/DownloadProgressBar.cs b/osu.Game/Overlays/BeatmapListing/Panels/DownloadProgressBar.cs index ca94078401..93eaf775e0 100644 --- a/osu.Game/Overlays/BeatmapListing/Panels/DownloadProgressBar.cs +++ b/osu.Game/Overlays/BeatmapListing/Panels/DownloadProgressBar.cs @@ -4,6 +4,7 @@ using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; @@ -12,13 +13,22 @@ using osuTK.Graphics; namespace osu.Game.Overlays.BeatmapListing.Panels { - public class DownloadProgressBar : BeatmapDownloadTrackingComposite + public class DownloadProgressBar : CompositeDrawable { private readonly ProgressBar progressBar; + private readonly BeatmapDownloadTracker downloadTracker; - public DownloadProgressBar(BeatmapSetInfo beatmapSet) - : base(beatmapSet) + public DownloadProgressBar(IBeatmapSetInfo beatmapSet) { + InternalChildren = new Drawable[] + { + progressBar = new ProgressBar(false) + { + Height = 0, + Alpha = 0, + }, + downloadTracker = new BeatmapDownloadTracker(beatmapSet), + }; AddInternal(progressBar = new ProgressBar(false) { Height = 0, @@ -34,9 +44,9 @@ namespace osu.Game.Overlays.BeatmapListing.Panels { progressBar.FillColour = colours.Blue; progressBar.BackgroundColour = Color4.Black.Opacity(0.7f); - progressBar.Current = Progress; + progressBar.Current.BindTarget = downloadTracker.Progress; - State.BindValueChanged(state => + downloadTracker.State.BindValueChanged(state => { switch (state.NewValue) { diff --git a/osu.Game/Overlays/BeatmapListing/Panels/GridBeatmapPanel.cs b/osu.Game/Overlays/BeatmapListing/Panels/GridBeatmapPanel.cs index c078127353..770e5af7bd 100644 --- a/osu.Game/Overlays/BeatmapListing/Panels/GridBeatmapPanel.cs +++ b/osu.Game/Overlays/BeatmapListing/Panels/GridBeatmapPanel.cs @@ -9,11 +9,11 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; using osu.Framework.Localisation; -using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; +using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays.BeatmapSet; using osuTK; using osuTK.Graphics; @@ -32,7 +32,7 @@ namespace osu.Game.Overlays.BeatmapListing.Panels protected override PlayButton PlayButton => playButton; protected override Box PreviewBar => progressBar; - public GridBeatmapPanel(BeatmapSetInfo beatmap) + public GridBeatmapPanel(APIBeatmapSet beatmap) : base(beatmap) { Width = 380; @@ -84,7 +84,7 @@ namespace osu.Game.Overlays.BeatmapListing.Panels { new OsuSpriteText { - Text = new RomanisableString(SetInfo.Metadata.TitleUnicode, SetInfo.Metadata.Title), + Text = new RomanisableString(SetInfo.TitleUnicode, SetInfo.Title), Font = OsuFont.GetFont(size: 18, weight: FontWeight.Bold, italics: true) }, } @@ -97,7 +97,7 @@ namespace osu.Game.Overlays.BeatmapListing.Panels { new OsuSpriteText { - Text = new RomanisableString(SetInfo.Metadata.ArtistUnicode, SetInfo.Metadata.Artist), + Text = new RomanisableString(SetInfo.ArtistUnicode, SetInfo.Artist), Font = OsuFont.GetFont(weight: FontWeight.Bold, italics: true) } } @@ -145,7 +145,7 @@ namespace osu.Game.Overlays.BeatmapListing.Panels { d.AutoSizeAxes = Axes.Both; d.AddText("mapped by ", t => t.Colour = colours.Gray5); - d.AddUserLink(SetInfo.Metadata.Author); + d.AddUserLink(SetInfo.Author); }), new Container { @@ -155,11 +155,11 @@ namespace osu.Game.Overlays.BeatmapListing.Panels { new OsuSpriteText { - Text = SetInfo.Metadata.Source, + Text = SetInfo.Source, Font = OsuFont.GetFont(size: 14), Shadow = false, Colour = colours.Gray5, - Alpha = string.IsNullOrEmpty(SetInfo.Metadata.Source) ? 0f : 1f, + Alpha = string.IsNullOrEmpty(SetInfo.Source) ? 0f : 1f, }, }, }, @@ -193,8 +193,8 @@ namespace osu.Game.Overlays.BeatmapListing.Panels Margin = new MarginPadding { Top = vertical_padding, Right = vertical_padding }, Children = new[] { - new Statistic(FontAwesome.Solid.PlayCircle, SetInfo.OnlineInfo?.PlayCount ?? 0), - new Statistic(FontAwesome.Solid.Heart, SetInfo.OnlineInfo?.FavouriteCount ?? 0), + new Statistic(FontAwesome.Solid.PlayCircle, SetInfo.PlayCount), + new Statistic(FontAwesome.Solid.Heart, SetInfo.FavouriteCount), }, }, statusContainer = new FillFlowContainer @@ -211,7 +211,7 @@ namespace osu.Game.Overlays.BeatmapListing.Panels }, }); - if (SetInfo.OnlineInfo?.HasExplicitContent ?? false) + if (SetInfo.HasExplicitContent) { titleContainer.Add(new ExplicitContentBeatmapPill { @@ -221,7 +221,7 @@ namespace osu.Game.Overlays.BeatmapListing.Panels }); } - if (SetInfo.OnlineInfo?.TrackId != null) + if (SetInfo.TrackId != null) { artistContainer.Add(new FeaturedArtistBeatmapPill { @@ -231,21 +231,22 @@ namespace osu.Game.Overlays.BeatmapListing.Panels }); } - if (SetInfo.OnlineInfo?.HasVideo ?? false) + if (SetInfo.HasVideo) { statusContainer.Add(new IconPill(FontAwesome.Solid.Film)); } - if (SetInfo.OnlineInfo?.HasStoryboard ?? false) + if (SetInfo.HasStoryboard) { statusContainer.Add(new IconPill(FontAwesome.Solid.Image)); } statusContainer.Add(new BeatmapSetOnlineStatusPill { + AutoSizeAxes = Axes.Both, TextSize = 12, TextPadding = new MarginPadding { Horizontal = 10, Vertical = 5 }, - Status = SetInfo.OnlineInfo?.Status ?? BeatmapSetOnlineStatus.None, + Status = SetInfo.Status, }); PreviewPlaying.ValueChanged += _ => updateStatusContainer(); diff --git a/osu.Game/Overlays/BeatmapListing/Panels/ListBeatmapPanel.cs b/osu.Game/Overlays/BeatmapListing/Panels/ListBeatmapPanel.cs index 5011749c5f..dcd676724a 100644 --- a/osu.Game/Overlays/BeatmapListing/Panels/ListBeatmapPanel.cs +++ b/osu.Game/Overlays/BeatmapListing/Panels/ListBeatmapPanel.cs @@ -9,11 +9,11 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Localisation; -using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; +using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays.BeatmapSet; using osuTK; using osuTK.Graphics; @@ -37,7 +37,7 @@ namespace osu.Game.Overlays.BeatmapListing.Panels protected override PlayButton PlayButton => playButton; protected override Box PreviewBar => progressBar; - public ListBeatmapPanel(BeatmapSetInfo beatmap) + public ListBeatmapPanel(APIBeatmapSet beatmap) : base(beatmap) { RelativeSizeAxes = Axes.X; @@ -107,7 +107,7 @@ namespace osu.Game.Overlays.BeatmapListing.Panels { new OsuSpriteText { - Text = new RomanisableString(SetInfo.Metadata.TitleUnicode, SetInfo.Metadata.Title), + Text = new RomanisableString(SetInfo.TitleUnicode, SetInfo.Title), Font = OsuFont.GetFont(size: 18, weight: FontWeight.Bold, italics: true) }, } @@ -120,7 +120,7 @@ namespace osu.Game.Overlays.BeatmapListing.Panels { new OsuSpriteText { - Text = new RomanisableString(SetInfo.Metadata.ArtistUnicode, SetInfo.Metadata.Artist), + Text = new RomanisableString(SetInfo.ArtistUnicode, SetInfo.Artist), Font = OsuFont.GetFont(weight: FontWeight.Bold, italics: true) }, }, @@ -182,8 +182,8 @@ namespace osu.Game.Overlays.BeatmapListing.Panels Direction = FillDirection.Vertical, Children = new Drawable[] { - new Statistic(FontAwesome.Solid.PlayCircle, SetInfo.OnlineInfo?.PlayCount ?? 0), - new Statistic(FontAwesome.Solid.Heart, SetInfo.OnlineInfo?.FavouriteCount ?? 0), + new Statistic(FontAwesome.Solid.PlayCircle, SetInfo.PlayCount), + new Statistic(FontAwesome.Solid.Heart, SetInfo.FavouriteCount), new LinkFlowContainer(s => { s.Shadow = false; @@ -197,15 +197,15 @@ namespace osu.Game.Overlays.BeatmapListing.Panels { d.AutoSizeAxes = Axes.Both; d.AddText("mapped by "); - d.AddUserLink(SetInfo.Metadata.Author); + d.AddUserLink(SetInfo.Author); }), new OsuSpriteText { - Text = SetInfo.Metadata.Source, + Text = SetInfo.Source, Anchor = Anchor.TopRight, Origin = Anchor.TopRight, Font = OsuFont.GetFont(size: 14), - Alpha = string.IsNullOrEmpty(SetInfo.Metadata.Source) ? 0f : 1f, + Alpha = string.IsNullOrEmpty(SetInfo.Source) ? 0f : 1f, }, }, }, @@ -225,7 +225,7 @@ namespace osu.Game.Overlays.BeatmapListing.Panels }, }); - if (SetInfo.OnlineInfo?.HasExplicitContent ?? false) + if (SetInfo.HasExplicitContent) { titleContainer.Add(new ExplicitContentBeatmapPill { @@ -235,7 +235,7 @@ namespace osu.Game.Overlays.BeatmapListing.Panels }); } - if (SetInfo.OnlineInfo?.TrackId != null) + if (SetInfo.TrackId != null) { artistContainer.Add(new FeaturedArtistBeatmapPill { @@ -245,21 +245,22 @@ namespace osu.Game.Overlays.BeatmapListing.Panels }); } - if (SetInfo.OnlineInfo?.HasVideo ?? false) + if (SetInfo.HasVideo) { statusContainer.Add(new IconPill(FontAwesome.Solid.Film) { IconSize = new Vector2(20) }); } - if (SetInfo.OnlineInfo?.HasStoryboard ?? false) + if (SetInfo.HasStoryboard) { statusContainer.Add(new IconPill(FontAwesome.Solid.Image) { IconSize = new Vector2(20) }); } statusContainer.Add(new BeatmapSetOnlineStatusPill { + AutoSizeAxes = Axes.Both, TextSize = 12, TextPadding = new MarginPadding { Horizontal = 10, Vertical = 4 }, - Status = SetInfo.OnlineInfo?.Status ?? BeatmapSetOnlineStatus.None, + Status = SetInfo.Status, }); } } diff --git a/osu.Game/Overlays/BeatmapListing/Panels/PlayButton.cs b/osu.Game/Overlays/BeatmapListing/Panels/PlayButton.cs index 3aa9aa5ca5..c352fe0223 100644 --- a/osu.Game/Overlays/BeatmapListing/Panels/PlayButton.cs +++ b/osu.Game/Overlays/BeatmapListing/Panels/PlayButton.cs @@ -8,9 +8,9 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; using osu.Game.Audio; -using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; +using osu.Game.Online.API.Requests.Responses; using osuTK; using osuTK.Graphics; @@ -24,9 +24,9 @@ namespace osu.Game.Overlays.BeatmapListing.Panels public PreviewTrack Preview { get; private set; } - private BeatmapSetInfo beatmapSet; + private APIBeatmapSet beatmapSet; - public BeatmapSetInfo BeatmapSet + public APIBeatmapSet BeatmapSet { get => beatmapSet; set @@ -66,7 +66,7 @@ namespace osu.Game.Overlays.BeatmapListing.Panels } } - public PlayButton(BeatmapSetInfo setInfo = null) + public PlayButton(APIBeatmapSet setInfo = null) { BeatmapSet = setInfo; AddRange(new Drawable[] diff --git a/osu.Game/Overlays/BeatmapListingOverlay.cs b/osu.Game/Overlays/BeatmapListingOverlay.cs index 935a89b99b..e08af52a72 100644 --- a/osu.Game/Overlays/BeatmapListingOverlay.cs +++ b/osu.Game/Overlays/BeatmapListingOverlay.cs @@ -15,10 +15,10 @@ using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; using osu.Framework.Input.Events; using osu.Game.Audio; -using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Containers; +using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays.BeatmapListing; using osu.Game.Overlays.BeatmapListing.Panels; using osu.Game.Resources.Localisation.Web; @@ -136,7 +136,7 @@ namespace osu.Game.Overlays return; } - var newPanels = searchResult.Results.Select(b => new GridBeatmapPanel(b) + var newPanels = searchResult.Results.Select(b => new GridBeatmapPanel(b) { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, diff --git a/osu.Game/Overlays/BeatmapSet/AuthorInfo.cs b/osu.Game/Overlays/BeatmapSet/AuthorInfo.cs index 265d9bf125..c90e2e2085 100644 --- a/osu.Game/Overlays/BeatmapSet/AuthorInfo.cs +++ b/osu.Game/Overlays/BeatmapSet/AuthorInfo.cs @@ -6,7 +6,6 @@ using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Game.Beatmaps; using osu.Game.Graphics.Sprites; using osu.Game.Users.Drawables; using osuTK; @@ -16,6 +15,7 @@ using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; using osu.Game.Users; using osu.Game.Graphics.Containers; +using osu.Game.Online.API.Requests.Responses; namespace osu.Game.Overlays.BeatmapSet { @@ -26,9 +26,9 @@ namespace osu.Game.Overlays.BeatmapSet private UpdateableAvatar avatar; private FillFlowContainer fields; - private BeatmapSetInfo beatmapSet; + private APIBeatmapSet beatmapSet; - public BeatmapSetInfo BeatmapSet + public APIBeatmapSet BeatmapSet { get => beatmapSet; set @@ -78,30 +78,28 @@ namespace osu.Game.Overlays.BeatmapSet private void updateDisplay() { - avatar.User = BeatmapSet?.Metadata.Author; + avatar.User = BeatmapSet?.Author; fields.Clear(); if (BeatmapSet == null) return; - var online = BeatmapSet.OnlineInfo; - fields.Children = new Drawable[] { - new Field("mapped by", BeatmapSet.Metadata.Author, OsuFont.GetFont(weight: FontWeight.Regular, italics: true)), - new Field("submitted", online.Submitted, OsuFont.GetFont(weight: FontWeight.Bold)) + new Field("mapped by", BeatmapSet.Author, OsuFont.GetFont(weight: FontWeight.Regular, italics: true)), + new Field("submitted", BeatmapSet.Submitted, OsuFont.GetFont(weight: FontWeight.Bold)) { Margin = new MarginPadding { Top = 5 }, }, }; - if (online.Ranked.HasValue) + if (BeatmapSet.Ranked.HasValue) { - fields.Add(new Field(online.Status.ToString().ToLowerInvariant(), online.Ranked.Value, OsuFont.GetFont(weight: FontWeight.Bold))); + fields.Add(new Field(BeatmapSet.Status.ToString().ToLowerInvariant(), BeatmapSet.Ranked.Value, OsuFont.GetFont(weight: FontWeight.Bold))); } - else if (online.LastUpdated.HasValue) + else if (BeatmapSet.LastUpdated.HasValue) { - fields.Add(new Field("last updated", online.LastUpdated.Value, OsuFont.GetFont(weight: FontWeight.Bold))); + fields.Add(new Field("last updated", BeatmapSet.LastUpdated.Value, OsuFont.GetFont(weight: FontWeight.Bold))); } } diff --git a/osu.Game/Overlays/BeatmapSet/BasicStats.cs b/osu.Game/Overlays/BeatmapSet/BasicStats.cs index 683f4f0c49..8f848edf24 100644 --- a/osu.Game/Overlays/BeatmapSet/BasicStats.cs +++ b/osu.Game/Overlays/BeatmapSet/BasicStats.cs @@ -14,6 +14,7 @@ using osu.Game.Beatmaps; using osu.Game.Extensions; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; +using osu.Game.Online.API.Requests.Responses; using osu.Game.Resources.Localisation.Web; using osuTK; @@ -23,9 +24,9 @@ namespace osu.Game.Overlays.BeatmapSet { private readonly Statistic length, bpm, circleCount, sliderCount; - private BeatmapSetInfo beatmapSet; + private APIBeatmapSet beatmapSet; - public BeatmapSetInfo BeatmapSet + public APIBeatmapSet BeatmapSet { get => beatmapSet; set @@ -38,9 +39,9 @@ namespace osu.Game.Overlays.BeatmapSet } } - private BeatmapInfo beatmapInfo; + private IBeatmapInfo beatmapInfo; - public BeatmapInfo BeatmapInfo + public IBeatmapInfo BeatmapInfo { get => beatmapInfo; set @@ -55,7 +56,7 @@ namespace osu.Game.Overlays.BeatmapSet private void updateDisplay() { - bpm.Value = BeatmapSet?.OnlineInfo?.BPM.ToLocalisableString(@"0.##") ?? (LocalisableString)"-"; + bpm.Value = BeatmapSet?.BPM.ToLocalisableString(@"0.##") ?? (LocalisableString)"-"; if (beatmapInfo == null) { @@ -68,8 +69,10 @@ namespace osu.Game.Overlays.BeatmapSet length.TooltipText = BeatmapsetsStrings.ShowStatsTotalLength(TimeSpan.FromMilliseconds(beatmapInfo.Length).ToFormattedDuration()); length.Value = TimeSpan.FromMilliseconds(beatmapInfo.Length).ToFormattedDuration(); - circleCount.Value = beatmapInfo.OnlineInfo.CircleCount.ToLocalisableString(@"N0"); - sliderCount.Value = beatmapInfo.OnlineInfo.SliderCount.ToLocalisableString(@"N0"); + var onlineInfo = beatmapInfo as IBeatmapOnlineInfo; + + circleCount.Value = (onlineInfo?.CircleCount ?? 0).ToLocalisableString(@"N0"); + sliderCount.Value = (onlineInfo?.SliderCount ?? 0).ToLocalisableString(@"N0"); } } diff --git a/osu.Game/Overlays/BeatmapSet/BeatmapAvailability.cs b/osu.Game/Overlays/BeatmapSet/BeatmapAvailability.cs index f005a37eaa..dc46452dcb 100644 --- a/osu.Game/Overlays/BeatmapSet/BeatmapAvailability.cs +++ b/osu.Game/Overlays/BeatmapSet/BeatmapAvailability.cs @@ -5,19 +5,19 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Containers; +using osu.Game.Online.API.Requests.Responses; using osuTK.Graphics; namespace osu.Game.Overlays.BeatmapSet { public class BeatmapAvailability : Container { - private BeatmapSetInfo beatmapSet; + private APIBeatmapSet beatmapSet; - private bool downloadDisabled => BeatmapSet?.OnlineInfo.Availability.DownloadDisabled ?? false; - private bool hasExternalLink => !string.IsNullOrEmpty(BeatmapSet?.OnlineInfo.Availability.ExternalLink); + private bool downloadDisabled => BeatmapSet?.Availability.DownloadDisabled ?? false; + private bool hasExternalLink => !string.IsNullOrEmpty(BeatmapSet?.Availability.ExternalLink); private readonly LinkFlowContainer textContainer; @@ -44,7 +44,7 @@ namespace osu.Game.Overlays.BeatmapSet }; } - public BeatmapSetInfo BeatmapSet + public APIBeatmapSet BeatmapSet { get => beatmapSet; @@ -76,7 +76,7 @@ namespace osu.Game.Overlays.BeatmapSet { textContainer.NewParagraph(); textContainer.NewParagraph(); - textContainer.AddLink("Check here for more information.", BeatmapSet.OnlineInfo.Availability.ExternalLink, creationParameters: t => t.Font = OsuFont.GetFont(size: 10)); + textContainer.AddLink("Check here for more information.", BeatmapSet.Availability.ExternalLink, creationParameters: t => t.Font = OsuFont.GetFont(size: 10)); } } } diff --git a/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs b/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs index 3df275c6d3..b152375062 100644 --- a/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs +++ b/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs @@ -17,6 +17,7 @@ using osu.Game.Beatmaps.Drawables; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; +using osu.Game.Online.API.Requests.Responses; using osu.Game.Resources.Localisation.Web; using osu.Game.Rulesets; using osuTK; @@ -34,10 +35,10 @@ namespace osu.Game.Overlays.BeatmapSet public readonly DifficultiesContainer Difficulties; - public readonly Bindable Beatmap = new Bindable(); - private BeatmapSetInfo beatmapSet; + public readonly Bindable Beatmap = new Bindable(); + private APIBeatmapSet beatmapSet; - public BeatmapSetInfo BeatmapSet + public APIBeatmapSet BeatmapSet { get => beatmapSet; set @@ -164,35 +165,38 @@ namespace osu.Game.Overlays.BeatmapSet if (BeatmapSet != null) { - Difficulties.ChildrenEnumerable = BeatmapSet.Beatmaps.Where(b => b.Ruleset.Equals(ruleset.Value)).OrderBy(b => b.StarDifficulty).Select(b => new DifficultySelectorButton(b) - { - State = DifficultySelectorState.NotSelected, - OnHovered = beatmap => - { - showBeatmap(beatmap); - starRating.Text = beatmap.StarDifficulty.ToLocalisableString(@"0.##"); - starRatingContainer.FadeIn(100); - }, - OnClicked = beatmap => { Beatmap.Value = beatmap; }, - }); + Difficulties.ChildrenEnumerable = BeatmapSet.Beatmaps + .Where(b => b.Ruleset.OnlineID == ruleset.Value?.OnlineID) + .OrderBy(b => b.StarRating) + .Select(b => new DifficultySelectorButton(b) + { + State = DifficultySelectorState.NotSelected, + OnHovered = beatmap => + { + showBeatmap(beatmap); + starRating.Text = beatmap.StarRating.ToLocalisableString(@"0.##"); + starRatingContainer.FadeIn(100); + }, + OnClicked = beatmap => { Beatmap.Value = beatmap; }, + }); } starRatingContainer.FadeOut(100); - Beatmap.Value = Difficulties.FirstOrDefault()?.BeatmapInfo; - plays.Value = BeatmapSet?.OnlineInfo.PlayCount ?? 0; - favourites.Value = BeatmapSet?.OnlineInfo.FavouriteCount ?? 0; + Beatmap.Value = Difficulties.FirstOrDefault()?.Beatmap; + plays.Value = BeatmapSet?.PlayCount ?? 0; + favourites.Value = BeatmapSet?.FavouriteCount ?? 0; updateDifficultyButtons(); } - private void showBeatmap(BeatmapInfo beatmapInfo) + private void showBeatmap(IBeatmapInfo beatmapInfo) { - version.Text = beatmapInfo?.Version; + version.Text = beatmapInfo?.DifficultyName; } private void updateDifficultyButtons() { - Difficulties.Children.ToList().ForEach(diff => diff.State = diff.BeatmapInfo == Beatmap.Value ? DifficultySelectorState.Selected : DifficultySelectorState.NotSelected); + Difficulties.Children.ToList().ForEach(diff => diff.State = diff.Beatmap == Beatmap.Value ? DifficultySelectorState.Selected : DifficultySelectorState.NotSelected); } public class DifficultiesContainer : FillFlowContainer @@ -216,10 +220,10 @@ namespace osu.Game.Overlays.BeatmapSet private readonly Box backgroundBox; private readonly DifficultyIcon icon; - public readonly BeatmapInfo BeatmapInfo; + public readonly APIBeatmap Beatmap; - public Action OnHovered; - public Action OnClicked; + public Action OnHovered; + public Action OnClicked; public event Action StateChanged; private DifficultySelectorState state; @@ -241,9 +245,9 @@ namespace osu.Game.Overlays.BeatmapSet } } - public DifficultySelectorButton(BeatmapInfo beatmapInfo) + public DifficultySelectorButton(APIBeatmap beatmapInfo) { - BeatmapInfo = beatmapInfo; + Beatmap = beatmapInfo; Size = new Vector2(size); Margin = new MarginPadding { Horizontal = tile_spacing / 2 }; @@ -273,7 +277,7 @@ namespace osu.Game.Overlays.BeatmapSet protected override bool OnHover(HoverEvent e) { fadeIn(); - OnHovered?.Invoke(BeatmapInfo); + OnHovered?.Invoke(Beatmap); return base.OnHover(e); } @@ -286,7 +290,7 @@ namespace osu.Game.Overlays.BeatmapSet protected override bool OnClick(ClickEvent e) { - OnClicked?.Invoke(BeatmapInfo); + OnClicked?.Invoke(Beatmap); return base.OnClick(e); } diff --git a/osu.Game/Overlays/BeatmapSet/BeatmapRulesetSelector.cs b/osu.Game/Overlays/BeatmapSet/BeatmapRulesetSelector.cs index 005d21726b..6564ca3d41 100644 --- a/osu.Game/Overlays/BeatmapSet/BeatmapRulesetSelector.cs +++ b/osu.Game/Overlays/BeatmapSet/BeatmapRulesetSelector.cs @@ -3,17 +3,17 @@ using osu.Framework.Bindables; using osu.Framework.Graphics.UserInterface; -using osu.Game.Beatmaps; using osu.Game.Rulesets; using System.Linq; +using osu.Game.Online.API.Requests.Responses; namespace osu.Game.Overlays.BeatmapSet { public class BeatmapRulesetSelector : OverlayRulesetSelector { - private readonly Bindable beatmapSet = new Bindable(); + private readonly Bindable beatmapSet = new Bindable(); - public BeatmapSetInfo BeatmapSet + public APIBeatmapSet BeatmapSet { get => beatmapSet.Value; set diff --git a/osu.Game/Overlays/BeatmapSet/BeatmapRulesetTabItem.cs b/osu.Game/Overlays/BeatmapSet/BeatmapRulesetTabItem.cs index cb258edced..b3b3d1980b 100644 --- a/osu.Game/Overlays/BeatmapSet/BeatmapRulesetTabItem.cs +++ b/osu.Game/Overlays/BeatmapSet/BeatmapRulesetTabItem.cs @@ -1,22 +1,22 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; +using osu.Game.Online.API.Requests.Responses; using osu.Game.Rulesets; -using System.Linq; namespace osu.Game.Overlays.BeatmapSet { public class BeatmapRulesetTabItem : OverlayRulesetTabItem { - public readonly Bindable BeatmapSet = new Bindable(); + public readonly Bindable BeatmapSet = new Bindable(); [Resolved] private OverlayColourProvider colourProvider { get; set; } @@ -64,7 +64,7 @@ namespace osu.Game.Overlays.BeatmapSet BeatmapSet.BindValueChanged(setInfo => { - var beatmapsCount = setInfo.NewValue?.Beatmaps.Count(b => b.Ruleset.Equals(Value)) ?? 0; + int beatmapsCount = setInfo.NewValue?.Beatmaps.Count(b => b.Ruleset.OnlineID == Value.OnlineID) ?? 0; count.Text = beatmapsCount.ToString(); countContainer.FadeTo(beatmapsCount > 0 ? 1 : 0); diff --git a/osu.Game/Overlays/BeatmapSet/BeatmapSetHeader.cs b/osu.Game/Overlays/BeatmapSet/BeatmapSetHeader.cs index 4a0c0e9f75..102cddfa92 100644 --- a/osu.Game/Overlays/BeatmapSet/BeatmapSetHeader.cs +++ b/osu.Game/Overlays/BeatmapSet/BeatmapSetHeader.cs @@ -6,7 +6,7 @@ using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Effects; -using osu.Game.Beatmaps; +using osu.Game.Online.API.Requests.Responses; using osu.Game.Resources.Localisation.Web; using osu.Game.Rulesets; using osuTK; @@ -16,7 +16,7 @@ namespace osu.Game.Overlays.BeatmapSet { public class BeatmapSetHeader : OverlayHeader { - public readonly Bindable BeatmapSet = new Bindable(); + public readonly Bindable BeatmapSet = new Bindable(); public BeatmapSetHeaderContent HeaderContent { get; private set; } diff --git a/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs b/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs index c1029923f7..547b8a6ec3 100644 --- a/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs +++ b/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs @@ -3,6 +3,7 @@ using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; @@ -15,14 +16,17 @@ using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Online; using osu.Game.Online.API; +using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays.BeatmapListing.Panels; using osu.Game.Overlays.BeatmapSet.Buttons; using osuTK; namespace osu.Game.Overlays.BeatmapSet { - public class BeatmapSetHeaderContent : BeatmapDownloadTrackingComposite + public class BeatmapSetHeaderContent : CompositeDrawable { + public readonly Bindable BeatmapSet = new Bindable(); + private const float transition_duration = 200; private const float buttons_height = 45; private const float buttons_spacing = 5; @@ -45,6 +49,8 @@ namespace osu.Game.Overlays.BeatmapSet private readonly FillFlowContainer fadeContent; private readonly LoadingSpinner loading; + private BeatmapDownloadTracker downloadTracker; + [Resolved] private IAPIProvider api { get; set; } @@ -198,6 +204,7 @@ namespace osu.Game.Overlays.BeatmapSet { onlineStatusPill = new BeatmapSetOnlineStatusPill { + AutoSizeAxes = Axes.Both, Anchor = Anchor.TopRight, Origin = Anchor.TopRight, TextSize = 14, @@ -212,7 +219,7 @@ namespace osu.Game.Overlays.BeatmapSet Picker.Beatmap.ValueChanged += b => { Details.BeatmapInfo = b.NewValue; - externalLink.Link = $@"{api.WebsiteRootUrl}/beatmapsets/{BeatmapSet.Value?.OnlineBeatmapSetID}#{b.NewValue?.Ruleset.ShortName}/{b.NewValue?.OnlineBeatmapID}"; + externalLink.Link = $@"{api.WebsiteRootUrl}/beatmapsets/{BeatmapSet.Value?.OnlineID}#{b.NewValue?.Ruleset.ShortName}/{b.NewValue?.OnlineID}"; }; } @@ -220,14 +227,13 @@ namespace osu.Game.Overlays.BeatmapSet private void load(OverlayColourProvider colourProvider) { coverGradient.Colour = ColourInfo.GradientVertical(colourProvider.Background6.Opacity(0.3f), colourProvider.Background6.Opacity(0.8f)); - onlineStatusPill.BackgroundColour = colourProvider.Background6; - - State.BindValueChanged(_ => updateDownloadButtons()); BeatmapSet.BindValueChanged(setInfo => { Picker.BeatmapSet = rulesetSelector.BeatmapSet = author.BeatmapSet = beatmapAvailability.BeatmapSet = Details.BeatmapSet = setInfo.NewValue; - cover.BeatmapSet = setInfo.NewValue; + cover.OnlineInfo = setInfo.NewValue; + + downloadTracker?.RemoveAndDisposeImmediately(); if (setInfo.NewValue == null) { @@ -241,18 +247,22 @@ namespace osu.Game.Overlays.BeatmapSet } else { + downloadTracker = new BeatmapDownloadTracker(setInfo.NewValue); + downloadTracker.State.BindValueChanged(_ => updateDownloadButtons()); + AddInternal(downloadTracker); + fadeContent.FadeIn(500, Easing.OutQuint); loading.Hide(); - title.Text = new RomanisableString(setInfo.NewValue.Metadata.TitleUnicode, setInfo.NewValue.Metadata.Title); - artist.Text = new RomanisableString(setInfo.NewValue.Metadata.ArtistUnicode, setInfo.NewValue.Metadata.Artist); + title.Text = new RomanisableString(setInfo.NewValue.TitleUnicode, setInfo.NewValue.Title); + artist.Text = new RomanisableString(setInfo.NewValue.ArtistUnicode, setInfo.NewValue.Artist); - explicitContentPill.Alpha = setInfo.NewValue.OnlineInfo.HasExplicitContent ? 1 : 0; - featuredArtistPill.Alpha = setInfo.NewValue.OnlineInfo.TrackId != null ? 1 : 0; + explicitContentPill.Alpha = setInfo.NewValue.HasExplicitContent ? 1 : 0; + featuredArtistPill.Alpha = setInfo.NewValue.TrackId != null ? 1 : 0; onlineStatusPill.FadeIn(500, Easing.OutQuint); - onlineStatusPill.Status = setInfo.NewValue.OnlineInfo.Status; + onlineStatusPill.Status = setInfo.NewValue.Status; downloadButtonsContainer.FadeIn(transition_duration); favouriteButton.FadeIn(transition_duration); @@ -266,13 +276,13 @@ namespace osu.Game.Overlays.BeatmapSet { if (BeatmapSet.Value == null) return; - if (BeatmapSet.Value.OnlineInfo.Availability.DownloadDisabled && State.Value != DownloadState.LocallyAvailable) + if (BeatmapSet.Value.Availability.DownloadDisabled && downloadTracker.State.Value != DownloadState.LocallyAvailable) { downloadButtonsContainer.Clear(); return; } - switch (State.Value) + switch (downloadTracker.State.Value) { case DownloadState.LocallyAvailable: // temporary for UX until new design is implemented. @@ -292,7 +302,7 @@ namespace osu.Game.Overlays.BeatmapSet default: downloadButtonsContainer.Child = new HeaderDownloadButton(BeatmapSet.Value); - if (BeatmapSet.Value.OnlineInfo.HasVideo) + if (BeatmapSet.Value.HasVideo) downloadButtonsContainer.Add(new HeaderDownloadButton(BeatmapSet.Value, true)); break; } diff --git a/osu.Game/Overlays/BeatmapSet/Buttons/FavouriteButton.cs b/osu.Game/Overlays/BeatmapSet/Buttons/FavouriteButton.cs index 43dd1438f1..d4873f241c 100644 --- a/osu.Game/Overlays/BeatmapSet/Buttons/FavouriteButton.cs +++ b/osu.Game/Overlays/BeatmapSet/Buttons/FavouriteButton.cs @@ -8,10 +8,10 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Sprites; using osu.Framework.Localisation; -using osu.Game.Beatmaps; using osu.Game.Graphics.UserInterface; using osu.Game.Online.API; using osu.Game.Online.API.Requests; +using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays.Notifications; using osu.Game.Resources.Localisation.Web; using osu.Game.Users; @@ -21,7 +21,7 @@ namespace osu.Game.Overlays.BeatmapSet.Buttons { public class FavouriteButton : HeaderButton, IHasTooltip { - public readonly Bindable BeatmapSet = new Bindable(); + public readonly Bindable BeatmapSet = new Bindable(); private readonly BindableBool favourited = new BindableBool(); @@ -61,13 +61,13 @@ namespace osu.Game.Overlays.BeatmapSet.Buttons Action = () => { // guaranteed by disabled state above. - Debug.Assert(BeatmapSet.Value.OnlineBeatmapSetID != null); + Debug.Assert(BeatmapSet.Value.OnlineID > 0); loading.Show(); request?.Cancel(); - request = new PostBeatmapFavouriteRequest(BeatmapSet.Value.OnlineBeatmapSetID.Value, favourited.Value ? BeatmapFavouriteAction.UnFavourite : BeatmapFavouriteAction.Favourite); + request = new PostBeatmapFavouriteRequest(BeatmapSet.Value.OnlineID, favourited.Value ? BeatmapFavouriteAction.UnFavourite : BeatmapFavouriteAction.Favourite); request.Success += () => { @@ -98,11 +98,11 @@ namespace osu.Game.Overlays.BeatmapSet.Buttons BeatmapSet.BindValueChanged(setInfo => { updateEnabled(); - favourited.Value = setInfo.NewValue?.OnlineInfo?.HasFavourited ?? false; + favourited.Value = setInfo.NewValue?.HasFavourited ?? false; }, true); } - private void updateEnabled() => Enabled.Value = !(localUser.Value is GuestUser) && BeatmapSet.Value?.OnlineBeatmapSetID > 0; + private void updateEnabled() => Enabled.Value = !(localUser.Value is GuestUser) && BeatmapSet.Value?.OnlineID > 0; protected override void UpdateAfterChildren() { diff --git a/osu.Game/Overlays/BeatmapSet/Buttons/HeaderDownloadButton.cs b/osu.Game/Overlays/BeatmapSet/Buttons/HeaderDownloadButton.cs index e7a55079ec..6862864c55 100644 --- a/osu.Game/Overlays/BeatmapSet/Buttons/HeaderDownloadButton.cs +++ b/osu.Game/Overlays/BeatmapSet/Buttons/HeaderDownloadButton.cs @@ -14,15 +14,17 @@ using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Online; using osu.Game.Online.API; +using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays.BeatmapListing.Panels; using osu.Game.Resources.Localisation.Web; using osu.Game.Users; using osuTK; using osuTK.Graphics; +using CommonStrings = osu.Game.Localisation.CommonStrings; namespace osu.Game.Overlays.BeatmapSet.Buttons { - public class HeaderDownloadButton : BeatmapDownloadTrackingComposite, IHasTooltip + public class HeaderDownloadButton : CompositeDrawable, IHasTooltip { private const int text_size = 12; @@ -35,9 +37,13 @@ namespace osu.Game.Overlays.BeatmapSet.Buttons private ShakeContainer shakeContainer; private HeaderButton button; - public HeaderDownloadButton(BeatmapSetInfo beatmapSet, bool noVideo = false) - : base(beatmapSet) + private BeatmapDownloadTracker downloadTracker; + + private readonly APIBeatmapSet beatmapSet; + + public HeaderDownloadButton(APIBeatmapSet beatmapSet, bool noVideo = false) { + this.beatmapSet = beatmapSet; this.noVideo = noVideo; Width = 120; @@ -49,13 +55,17 @@ namespace osu.Game.Overlays.BeatmapSet.Buttons { FillFlowContainer textSprites; - AddInternal(shakeContainer = new ShakeContainer + InternalChildren = new Drawable[] { - RelativeSizeAxes = Axes.Both, - Masking = true, - CornerRadius = 5, - Child = button = new HeaderButton { RelativeSizeAxes = Axes.Both }, - }); + shakeContainer = new ShakeContainer + { + RelativeSizeAxes = Axes.Both, + Masking = true, + CornerRadius = 5, + Child = button = new HeaderButton { RelativeSizeAxes = Axes.Both }, + }, + downloadTracker = new BeatmapDownloadTracker(beatmapSet), + }; button.AddRange(new Drawable[] { @@ -83,7 +93,7 @@ namespace osu.Game.Overlays.BeatmapSet.Buttons }, } }, - new DownloadProgressBar(BeatmapSet.Value) + new DownloadProgressBar(beatmapSet) { Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft, @@ -92,20 +102,20 @@ namespace osu.Game.Overlays.BeatmapSet.Buttons button.Action = () => { - if (State.Value != DownloadState.NotDownloaded) + if (downloadTracker.State.Value != DownloadState.NotDownloaded) { shakeContainer.Shake(); return; } - beatmaps.Download(BeatmapSet.Value, noVideo); + beatmaps.Download(new BeatmapSetInfo { OnlineBeatmapSetID = beatmapSet.OnlineID }, noVideo); }; localUser.BindTo(api.LocalUser); localUser.BindValueChanged(userChanged, true); button.Enabled.BindValueChanged(enabledChanged, true); - State.BindValueChanged(state => + downloadTracker.State.BindValueChanged(state => { switch (state.NewValue) { @@ -114,7 +124,7 @@ namespace osu.Game.Overlays.BeatmapSet.Buttons { new OsuSpriteText { - Text = Localisation.CommonStrings.Downloading, + Text = CommonStrings.Downloading, Font = OsuFont.GetFont(size: text_size, weight: FontWeight.Bold) }, }; @@ -125,7 +135,7 @@ namespace osu.Game.Overlays.BeatmapSet.Buttons { new OsuSpriteText { - Text = Localisation.CommonStrings.Importing, + Text = CommonStrings.Importing, Font = OsuFont.GetFont(size: text_size, weight: FontWeight.Bold) }, }; @@ -161,7 +171,7 @@ namespace osu.Game.Overlays.BeatmapSet.Buttons private LocalisableString getVideoSuffixText() { - if (!BeatmapSet.Value.OnlineInfo.HasVideo) + if (!beatmapSet.HasVideo) return string.Empty; return noVideo ? BeatmapsetsStrings.ShowDetailsDownloadNoVideo : BeatmapsetsStrings.ShowDetailsDownloadVideo; diff --git a/osu.Game/Overlays/BeatmapSet/Buttons/PreviewButton.cs b/osu.Game/Overlays/BeatmapSet/Buttons/PreviewButton.cs index 5b3c142a66..6bcdb7bdc5 100644 --- a/osu.Game/Overlays/BeatmapSet/Buttons/PreviewButton.cs +++ b/osu.Game/Overlays/BeatmapSet/Buttons/PreviewButton.cs @@ -8,9 +8,9 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; using osu.Game.Audio; -using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Containers; +using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays.BeatmapListing.Panels; using osuTK; @@ -25,7 +25,7 @@ namespace osu.Game.Overlays.BeatmapSet.Buttons public IBindable Playing => playButton.Playing; - public BeatmapSetInfo BeatmapSet + public APIBeatmapSet BeatmapSet { get => playButton.BeatmapSet; set => playButton.BeatmapSet = value; diff --git a/osu.Game/Overlays/BeatmapSet/Details.cs b/osu.Game/Overlays/BeatmapSet/Details.cs index 92361ae4f8..dfc8d5e680 100644 --- a/osu.Game/Overlays/BeatmapSet/Details.cs +++ b/osu.Game/Overlays/BeatmapSet/Details.cs @@ -6,6 +6,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Beatmaps; +using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays.BeatmapSet.Buttons; using osu.Game.Screens.Select.Details; using osuTK; @@ -21,9 +22,9 @@ namespace osu.Game.Overlays.BeatmapSet private readonly AdvancedStats advanced; private readonly DetailBox ratingBox; - private BeatmapSetInfo beatmapSet; + private APIBeatmapSet beatmapSet; - public BeatmapSetInfo BeatmapSet + public APIBeatmapSet BeatmapSet { get => beatmapSet; set @@ -37,9 +38,9 @@ namespace osu.Game.Overlays.BeatmapSet } } - private BeatmapInfo beatmapInfo; + private IBeatmapInfo beatmapInfo; - public BeatmapInfo BeatmapInfo + public IBeatmapInfo BeatmapInfo { get => beatmapInfo; set @@ -52,8 +53,8 @@ namespace osu.Game.Overlays.BeatmapSet private void updateDisplay() { - Ratings.Metrics = BeatmapSet?.Metrics; - ratingBox.Alpha = BeatmapSet?.OnlineInfo?.Status > 0 ? 1 : 0; + Ratings.Ratings = BeatmapSet?.Ratings; + ratingBox.Alpha = BeatmapSet?.Status > 0 ? 1 : 0; } public Details() diff --git a/osu.Game/Overlays/BeatmapSet/Info.cs b/osu.Game/Overlays/BeatmapSet/Info.cs index 8bc5c6d27e..3ef52d718d 100644 --- a/osu.Game/Overlays/BeatmapSet/Info.cs +++ b/osu.Game/Overlays/BeatmapSet/Info.cs @@ -6,9 +6,9 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; +using osu.Game.Online.API.Requests.Responses; namespace osu.Game.Overlays.BeatmapSet { @@ -22,12 +22,12 @@ namespace osu.Game.Overlays.BeatmapSet private readonly Box background; private readonly SuccessRate successRate; - public readonly Bindable BeatmapSet = new Bindable(); + public readonly Bindable BeatmapSet = new Bindable(); - public BeatmapInfo BeatmapInfo + public APIBeatmap BeatmapInfo { - get => successRate.BeatmapInfo; - set => successRate.BeatmapInfo = value; + get => successRate.Beatmap; + set => successRate.Beatmap = value; } public Info() @@ -115,11 +115,11 @@ namespace osu.Game.Overlays.BeatmapSet BeatmapSet.ValueChanged += b => { - source.Text = b.NewValue?.Metadata.Source ?? string.Empty; - tags.Text = b.NewValue?.Metadata.Tags ?? string.Empty; - genre.Text = b.NewValue?.OnlineInfo?.Genre.Name ?? string.Empty; - language.Text = b.NewValue?.OnlineInfo?.Language.Name ?? string.Empty; - var setHasLeaderboard = b.NewValue?.OnlineInfo?.Status > 0; + source.Text = b.NewValue?.Source ?? string.Empty; + tags.Text = b.NewValue?.Tags ?? string.Empty; + genre.Text = b.NewValue?.Genre.Name ?? string.Empty; + language.Text = b.NewValue?.Language.Name ?? string.Empty; + bool setHasLeaderboard = b.NewValue?.Status > 0; successRate.Alpha = setHasLeaderboard ? 1 : 0; notRankedPlaceholder.Alpha = setHasLeaderboard ? 0 : 1; Height = setHasLeaderboard ? 270 : base_height; diff --git a/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs b/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs index 6349f115cb..a9723c9c62 100644 --- a/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs +++ b/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs @@ -13,6 +13,7 @@ using osu.Game.Graphics.UserInterface; using osuTK.Graphics; using System; using System.Linq; +using osu.Framework.Allocation; using osu.Framework.Extensions.IEnumerableExtensions; namespace osu.Game.Overlays.BeatmapSet @@ -20,7 +21,7 @@ namespace osu.Game.Overlays.BeatmapSet public class LeaderboardModSelector : CompositeDrawable { public readonly BindableList SelectedMods = new BindableList(); - public readonly Bindable Ruleset = new Bindable(); + public readonly Bindable Ruleset = new Bindable(); private readonly FillFlowContainer modsContainer; @@ -45,7 +46,10 @@ namespace osu.Game.Overlays.BeatmapSet Ruleset.BindValueChanged(onRulesetChanged, true); } - private void onRulesetChanged(ValueChangedEvent ruleset) + [Resolved] + private RulesetStore rulesets { get; set; } + + private void onRulesetChanged(ValueChangedEvent ruleset) { SelectedMods.Clear(); modsContainer.Clear(); @@ -54,7 +58,7 @@ namespace osu.Game.Overlays.BeatmapSet return; modsContainer.Add(new ModButton(new ModNoMod())); - modsContainer.AddRange(ruleset.NewValue.CreateInstance().AllMods.Where(m => m.UserPlayable).Select(m => new ModButton(m))); + modsContainer.AddRange(rulesets.GetRuleset(ruleset.NewValue.OnlineID).CreateInstance().AllMods.Where(m => m.UserPlayable).Select(m => new ModButton(m))); modsContainer.ForEach(button => { diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTableRowBackground.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTableRowBackground.cs index d84e1eff8c..b6079b36ab 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTableRowBackground.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTableRowBackground.cs @@ -50,7 +50,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores [BackgroundDependencyLoader] private void load(OsuColour colours, OverlayColourProvider colourProvider, IAPIProvider api) { - var isOwnScore = api.LocalUser.Value.Id == score.UserID; + bool isOwnScore = api.LocalUser.Value.Id == score.UserID; if (isOwnScore) background.Colour = colours.GreenDarker; diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs index 82657afc86..4a00c8b4a0 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs @@ -1,24 +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 osuTK; using System.Linq; using System.Threading; using System.Threading.Tasks; -using osu.Game.Online.API.Requests.Responses; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; using osu.Game.Beatmaps; +using osu.Game.Graphics.UserInterface; using osu.Game.Online.API; using osu.Game.Online.API.Requests; -using osu.Framework.Bindables; -using osu.Game.Graphics.UserInterface; +using osu.Game.Online.API.Requests.Responses; using osu.Game.Rulesets; using osu.Game.Scoring; using osu.Game.Screens.Select.Leaderboards; using osu.Game.Users; +using osuTK; namespace osu.Game.Overlays.BeatmapSet.Scores { @@ -26,8 +26,8 @@ namespace osu.Game.Overlays.BeatmapSet.Scores { private const int spacing = 15; - public readonly Bindable Beatmap = new Bindable(); - private readonly Bindable ruleset = new Bindable(); + public readonly Bindable Beatmap = new Bindable(); + private readonly Bindable ruleset = new Bindable(); private readonly Bindable scope = new Bindable(BeatmapLeaderboardScope.Global); private readonly IBindable user = new Bindable(); @@ -52,7 +52,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores private CancellationTokenSource loadCancellationSource; - protected APILegacyScores Scores + protected APIScoresCollection Scores { set => Schedule(() => { @@ -66,7 +66,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores if (value?.Scores.Any() != true) return; - scoreManager.OrderByTotalScoreAsync(value.Scores.Select(s => s.CreateScoreInfo(rulesets)).ToArray(), loadCancellationSource.Token) + scoreManager.OrderByTotalScoreAsync(value.Scores.Select(s => s.CreateScoreInfo(rulesets, Beatmap.Value.ToBeatmapInfo(rulesets))).ToArray(), loadCancellationSource.Token) .ContinueWith(ordered => Schedule(() => { if (loadCancellationSource.IsCancellationRequested) @@ -78,7 +78,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores scoreTable.Show(); var userScore = value.UserScore; - var userScoreInfo = userScore?.Score.CreateScoreInfo(rulesets); + var userScoreInfo = userScore?.Score.CreateScoreInfo(rulesets, Beatmap.Value.ToBeatmapInfo(rulesets)); topScoresContainer.Add(new DrawableTopScore(topScore)); @@ -200,11 +200,11 @@ namespace osu.Game.Overlays.BeatmapSet.Scores user.BindValueChanged(onUserChanged, true); } - private void onBeatmapChanged(ValueChangedEvent beatmap) + private void onBeatmapChanged(ValueChangedEvent beatmap) { var beatmapRuleset = beatmap.NewValue?.Ruleset; - if (ruleset.Value?.Equals(beatmapRuleset) ?? false) + if (ruleset.Value?.OnlineID == beatmapRuleset?.OnlineID) { modSelector.DeselectAll(); ruleset.TriggerChange(); @@ -232,7 +232,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores noScoresPlaceholder.Hide(); - if (Beatmap.Value?.OnlineBeatmapID.HasValue != true || Beatmap.Value.Status <= BeatmapSetOnlineStatus.Pending) + if (Beatmap.Value == null || Beatmap.Value.OnlineID <= 0 || (Beatmap.Value?.BeatmapSet as IBeatmapSetOnlineInfo)?.Status <= BeatmapSetOnlineStatus.Pending) { Scores = null; Hide(); diff --git a/osu.Game/Overlays/BeatmapSet/SuccessRate.cs b/osu.Game/Overlays/BeatmapSet/SuccessRate.cs index 4a9b8244a5..e08f099226 100644 --- a/osu.Game/Overlays/BeatmapSet/SuccessRate.cs +++ b/osu.Game/Overlays/BeatmapSet/SuccessRate.cs @@ -5,10 +5,10 @@ using osu.Framework.Allocation; using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; +using osu.Game.Online.API.Requests.Responses; using osu.Game.Resources.Localisation.Web; using osu.Game.Screens.Select.Details; @@ -23,16 +23,16 @@ namespace osu.Game.Overlays.BeatmapSet private readonly Bar successRate; private readonly Container percentContainer; - private BeatmapInfo beatmapInfo; + private APIBeatmap beatmap; - public BeatmapInfo BeatmapInfo + public APIBeatmap Beatmap { - get => beatmapInfo; + get => beatmap; set { - if (value == beatmapInfo) return; + if (value == beatmap) return; - beatmapInfo = value; + beatmap = value; updateDisplay(); } @@ -40,15 +40,15 @@ namespace osu.Game.Overlays.BeatmapSet private void updateDisplay() { - int passCount = beatmapInfo?.OnlineInfo?.PassCount ?? 0; - int playCount = beatmapInfo?.OnlineInfo?.PlayCount ?? 0; + int passCount = beatmap?.PassCount ?? 0; + int playCount = beatmap?.PlayCount ?? 0; - var rate = playCount != 0 ? (float)passCount / playCount : 0; + float rate = playCount != 0 ? (float)passCount / playCount : 0; successPercent.Text = rate.ToLocalisableString(@"0.#%"); successRate.Length = rate; percentContainer.ResizeWidthTo(successRate.Length, 250, Easing.InOutCubic); - Graph.Metrics = beatmapInfo?.Metrics; + Graph.FailTimes = beatmap?.FailTimes; } public SuccessRate() diff --git a/osu.Game/Overlays/BeatmapSetOverlay.cs b/osu.Game/Overlays/BeatmapSetOverlay.cs index f987b57d6e..fa5a7c66d0 100644 --- a/osu.Game/Overlays/BeatmapSetOverlay.cs +++ b/osu.Game/Overlays/BeatmapSetOverlay.cs @@ -7,8 +7,8 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Events; -using osu.Game.Beatmaps; using osu.Game.Online.API.Requests; +using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays.BeatmapSet; using osu.Game.Overlays.BeatmapSet.Scores; using osu.Game.Overlays.Comments; @@ -27,7 +27,7 @@ namespace osu.Game.Overlays [Resolved] private RulesetStore rulesets { get; set; } - private readonly Bindable beatmapSet = new Bindable(); + private readonly Bindable beatmapSet = new Bindable(); // receive input outside our bounds so we can trigger a close event on ourselves. public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; @@ -89,8 +89,8 @@ namespace osu.Game.Overlays var req = new GetBeatmapSetRequest(beatmapId, BeatmapSetLookupType.BeatmapId); req.Success += res => { - beatmapSet.Value = res.ToBeatmapSet(rulesets); - Header.HeaderContent.Picker.Beatmap.Value = Header.BeatmapSet.Value.Beatmaps.First(b => b.OnlineBeatmapID == beatmapId); + beatmapSet.Value = res; + Header.HeaderContent.Picker.Beatmap.Value = Header.BeatmapSet.Value.Beatmaps.First(b => b.OnlineID == beatmapId); }; API.Queue(req); @@ -102,7 +102,7 @@ namespace osu.Game.Overlays beatmapSet.Value = null; var req = new GetBeatmapSetRequest(beatmapSetId); - req.Success += res => beatmapSet.Value = res.ToBeatmapSet(rulesets); + req.Success += res => beatmapSet.Value = res; API.Queue(req); Show(); @@ -112,7 +112,7 @@ namespace osu.Game.Overlays /// Show an already fully-populated beatmap set. /// /// The set to show. - public void ShowBeatmapSet(BeatmapSetInfo set) + public void ShowBeatmapSet(APIBeatmapSet set) { beatmapSet.Value = set; Show(); @@ -120,7 +120,7 @@ namespace osu.Game.Overlays private class CommentsSection : BeatmapSetLayoutSection { - public readonly Bindable BeatmapSet = new Bindable(); + public readonly Bindable BeatmapSet = new Bindable(); public CommentsSection() { @@ -130,10 +130,10 @@ namespace osu.Game.Overlays BeatmapSet.BindValueChanged(beatmapSet => { - if (beatmapSet.NewValue?.OnlineBeatmapSetID is int onlineBeatmapSetID) + if (beatmapSet.NewValue?.OnlineID > 0) { Show(); - comments.ShowComments(CommentableType.Beatmapset, onlineBeatmapSetID); + comments.ShowComments(CommentableType.Beatmapset, beatmapSet.NewValue.OnlineID); } else { diff --git a/osu.Game/Overlays/Changelog/ChangelogSupporterPromo.cs b/osu.Game/Overlays/Changelog/ChangelogSupporterPromo.cs index 1b0a62dc4a..5cc598ae70 100644 --- a/osu.Game/Overlays/Changelog/ChangelogSupporterPromo.cs +++ b/osu.Game/Overlays/Changelog/ChangelogSupporterPromo.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; @@ -166,12 +165,12 @@ namespace osu.Game.Overlays.Changelog { } - protected override DrawableLinkCompiler CreateLinkCompiler(IEnumerable parts) => new SupporterPromoLinkCompiler(parts); + protected override DrawableLinkCompiler CreateLinkCompiler(ITextPart textPart) => new SupporterPromoLinkCompiler(textPart); private class SupporterPromoLinkCompiler : DrawableLinkCompiler { - public SupporterPromoLinkCompiler(IEnumerable parts) - : base(parts) + public SupporterPromoLinkCompiler(ITextPart part) + : base(part) { } diff --git a/osu.Game/Overlays/ChatOverlay.cs b/osu.Game/Overlays/ChatOverlay.cs index 4b27335c7c..cc3ce63bf7 100644 --- a/osu.Game/Overlays/ChatOverlay.cs +++ b/osu.Game/Overlays/ChatOverlay.cs @@ -479,7 +479,7 @@ namespace osu.Game.Overlays private void postMessage(TextBox textbox, bool newText) { - var text = textbox.Text.Trim(); + string text = textbox.Text.Trim(); if (string.IsNullOrWhiteSpace(text)) return; diff --git a/osu.Game/Overlays/Comments/CommentMarkdownContainer.cs b/osu.Game/Overlays/Comments/CommentMarkdownContainer.cs index aeab292b0d..3971a61363 100644 --- a/osu.Game/Overlays/Comments/CommentMarkdownContainer.cs +++ b/osu.Game/Overlays/Comments/CommentMarkdownContainer.cs @@ -29,7 +29,7 @@ namespace osu.Game.Overlays.Comments protected override float GetFontSizeByLevel(int level) { - var defaultFontSize = base.GetFontSizeByLevel(6); + float defaultFontSize = base.GetFontSizeByLevel(6); switch (level) { diff --git a/osu.Game/Overlays/Comments/DrawableComment.cs b/osu.Game/Overlays/Comments/DrawableComment.cs index a44f3a7643..43f4177bd0 100644 --- a/osu.Game/Overlays/Comments/DrawableComment.cs +++ b/osu.Game/Overlays/Comments/DrawableComment.cs @@ -362,8 +362,8 @@ namespace osu.Game.Overlays.Comments private void updateButtonsState() { - var loadedReplesCount = loadedReplies.Count; - var hasUnloadedReplies = loadedReplesCount != Comment.RepliesCount; + int loadedReplesCount = loadedReplies.Count; + bool hasUnloadedReplies = loadedReplesCount != Comment.RepliesCount; loadRepliesButton.FadeTo(hasUnloadedReplies && loadedReplesCount == 0 ? 1 : 0); showMoreButton.FadeTo(hasUnloadedReplies && loadedReplesCount > 0 ? 1 : 0); diff --git a/osu.Game/Overlays/Comments/VotePill.cs b/osu.Game/Overlays/Comments/VotePill.cs index cf3c470f96..093cdce66e 100644 --- a/osu.Game/Overlays/Comments/VotePill.cs +++ b/osu.Game/Overlays/Comments/VotePill.cs @@ -67,7 +67,7 @@ namespace osu.Game.Overlays.Comments AccentColour = borderContainer.BorderColour = sideNumber.Colour = colours.GreenLight; hoverLayer.Colour = Color4.Black.Opacity(0.5f); - var ownComment = api.LocalUser.Value.Id == comment.UserId; + bool ownComment = api.LocalUser.Value.Id == comment.UserId; if (!ownComment) Action = onAction; diff --git a/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs b/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs index 3051ca7dbe..454dd500fe 100644 --- a/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs +++ b/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs @@ -61,7 +61,7 @@ namespace osu.Game.Overlays.Dashboard switch (e.Action) { case NotifyCollectionChangedAction.Add: - foreach (var id in e.NewItems.OfType().ToArray()) + foreach (int id in e.NewItems.OfType().ToArray()) { users.GetUserAsync(id).ContinueWith(u => { @@ -81,7 +81,7 @@ namespace osu.Game.Overlays.Dashboard break; case NotifyCollectionChangedAction.Remove: - foreach (var u in e.OldItems.OfType()) + foreach (int u in e.OldItems.OfType()) userFlow.FirstOrDefault(card => card.User.Id == u)?.Expire(); break; diff --git a/osu.Game/Overlays/Dashboard/Friends/FriendOnlineStreamControl.cs b/osu.Game/Overlays/Dashboard/Friends/FriendOnlineStreamControl.cs index 28546ceab8..d7f66e35b5 100644 --- a/osu.Game/Overlays/Dashboard/Friends/FriendOnlineStreamControl.cs +++ b/osu.Game/Overlays/Dashboard/Friends/FriendOnlineStreamControl.cs @@ -15,8 +15,8 @@ namespace osu.Game.Overlays.Dashboard.Friends { Clear(); - var userCount = users.Count; - var onlineUsersCount = users.Count(user => user.IsOnline); + int userCount = users.Count; + int onlineUsersCount = users.Count(user => user.IsOnline); AddItem(new FriendStream(OnlineStatus.All, userCount)); AddItem(new FriendStream(OnlineStatus.Online, onlineUsersCount)); diff --git a/osu.Game/Overlays/Dashboard/Home/DashboardBeatmapListing.cs b/osu.Game/Overlays/Dashboard/Home/DashboardBeatmapListing.cs index 4d96825353..c781aa0cfb 100644 --- a/osu.Game/Overlays/Dashboard/Home/DashboardBeatmapListing.cs +++ b/osu.Game/Overlays/Dashboard/Home/DashboardBeatmapListing.cs @@ -5,17 +5,17 @@ using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Game.Beatmaps; +using osu.Game.Online.API.Requests.Responses; using osuTK; namespace osu.Game.Overlays.Dashboard.Home { public class DashboardBeatmapListing : CompositeDrawable { - private readonly List newBeatmaps; - private readonly List popularBeatmaps; + private readonly List newBeatmaps; + private readonly List popularBeatmaps; - public DashboardBeatmapListing(List newBeatmaps, List popularBeatmaps) + public DashboardBeatmapListing(List newBeatmaps, List popularBeatmaps) { this.newBeatmaps = newBeatmaps; this.popularBeatmaps = popularBeatmaps; diff --git a/osu.Game/Overlays/Dashboard/Home/DashboardBeatmapPanel.cs b/osu.Game/Overlays/Dashboard/Home/DashboardBeatmapPanel.cs index edc737d8fe..9276e6ce80 100644 --- a/osu.Game/Overlays/Dashboard/Home/DashboardBeatmapPanel.cs +++ b/osu.Game/Overlays/Dashboard/Home/DashboardBeatmapPanel.cs @@ -7,11 +7,11 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; -using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; +using osu.Game.Online.API.Requests.Responses; using osuTK; namespace osu.Game.Overlays.Dashboard.Home @@ -24,14 +24,14 @@ namespace osu.Game.Overlays.Dashboard.Home [Resolved(canBeNull: true)] private BeatmapSetOverlay beatmapOverlay { get; set; } - protected readonly BeatmapSetInfo SetInfo; + protected readonly APIBeatmapSet BeatmapSet; private Box hoverBackground; private SpriteIcon chevron; - protected DashboardBeatmapPanel(BeatmapSetInfo setInfo) + protected DashboardBeatmapPanel(APIBeatmapSet beatmapSet) { - SetInfo = setInfo; + BeatmapSet = beatmapSet; } [BackgroundDependencyLoader] @@ -82,7 +82,7 @@ namespace osu.Game.Overlays.Dashboard.Home RelativeSizeAxes = Axes.Both, Anchor = Anchor.Centre, Origin = Anchor.Centre, - BeatmapSet = SetInfo + OnlineInfo = BeatmapSet } }, new Container @@ -103,14 +103,14 @@ namespace osu.Game.Overlays.Dashboard.Home RelativeSizeAxes = Axes.X, Truncate = true, Font = OsuFont.GetFont(weight: FontWeight.Regular), - Text = SetInfo.Metadata.Title + Text = BeatmapSet.Title }, new OsuSpriteText { RelativeSizeAxes = Axes.X, Truncate = true, Font = OsuFont.GetFont(size: 12, weight: FontWeight.Regular), - Text = SetInfo.Metadata.Artist + Text = BeatmapSet.Artist }, new LinkFlowContainer(f => f.Font = OsuFont.GetFont(size: 10, weight: FontWeight.Regular)) { @@ -121,7 +121,7 @@ namespace osu.Game.Overlays.Dashboard.Home }.With(c => { c.AddText("by"); - c.AddUserLink(SetInfo.Metadata.Author); + c.AddUserLink(BeatmapSet.Author); c.AddArbitraryDrawable(CreateInfo()); }) } @@ -143,8 +143,8 @@ namespace osu.Game.Overlays.Dashboard.Home Action = () => { - if (SetInfo.OnlineBeatmapSetID.HasValue) - beatmapOverlay?.FetchAndShowBeatmapSet(SetInfo.OnlineBeatmapSetID.Value); + if (BeatmapSet.OnlineID > 0) + beatmapOverlay?.FetchAndShowBeatmapSet(BeatmapSet.OnlineID); }; } diff --git a/osu.Game/Overlays/Dashboard/Home/DashboardNewBeatmapPanel.cs b/osu.Game/Overlays/Dashboard/Home/DashboardNewBeatmapPanel.cs index b212eaf20a..249b355be3 100644 --- a/osu.Game/Overlays/Dashboard/Home/DashboardNewBeatmapPanel.cs +++ b/osu.Game/Overlays/Dashboard/Home/DashboardNewBeatmapPanel.cs @@ -3,19 +3,19 @@ using System; using osu.Framework.Graphics; -using osu.Game.Beatmaps; using osu.Game.Graphics; +using osu.Game.Online.API.Requests.Responses; namespace osu.Game.Overlays.Dashboard.Home { public class DashboardNewBeatmapPanel : DashboardBeatmapPanel { - public DashboardNewBeatmapPanel(BeatmapSetInfo setInfo) - : base(setInfo) + public DashboardNewBeatmapPanel(APIBeatmapSet beatmapSet) + : base(beatmapSet) { } - protected override Drawable CreateInfo() => new DrawableDate(SetInfo.OnlineInfo.Ranked ?? DateTimeOffset.Now, 10, false) + protected override Drawable CreateInfo() => new DrawableDate(BeatmapSet.Ranked ?? DateTimeOffset.Now, 10, false) { Colour = ColourProvider.Foreground1 }; diff --git a/osu.Game/Overlays/Dashboard/Home/DashboardPopularBeatmapPanel.cs b/osu.Game/Overlays/Dashboard/Home/DashboardPopularBeatmapPanel.cs index e9066c0657..4e50cce890 100644 --- a/osu.Game/Overlays/Dashboard/Home/DashboardPopularBeatmapPanel.cs +++ b/osu.Game/Overlays/Dashboard/Home/DashboardPopularBeatmapPanel.cs @@ -4,17 +4,17 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; -using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; +using osu.Game.Online.API.Requests.Responses; using osuTK; namespace osu.Game.Overlays.Dashboard.Home { public class DashboardPopularBeatmapPanel : DashboardBeatmapPanel { - public DashboardPopularBeatmapPanel(BeatmapSetInfo setInfo) - : base(setInfo) + public DashboardPopularBeatmapPanel(APIBeatmapSet beatmapSet) + : base(beatmapSet) { } @@ -34,7 +34,7 @@ namespace osu.Game.Overlays.Dashboard.Home new OsuSpriteText { Font = OsuFont.GetFont(size: 10, weight: FontWeight.Regular), - Text = SetInfo.OnlineInfo.FavouriteCount.ToString() + Text = BeatmapSet.FavouriteCount.ToString() } } }; diff --git a/osu.Game/Overlays/Dashboard/Home/DrawableBeatmapList.cs b/osu.Game/Overlays/Dashboard/Home/DrawableBeatmapList.cs index f6535b7db3..c73cc828e2 100644 --- a/osu.Game/Overlays/Dashboard/Home/DrawableBeatmapList.cs +++ b/osu.Game/Overlays/Dashboard/Home/DrawableBeatmapList.cs @@ -6,20 +6,20 @@ using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; +using osu.Game.Online.API.Requests.Responses; using osuTK; namespace osu.Game.Overlays.Dashboard.Home { public abstract class DrawableBeatmapList : CompositeDrawable { - private readonly List beatmaps; + private readonly List beatmapSets; - protected DrawableBeatmapList(List beatmaps) + protected DrawableBeatmapList(List beatmapSets) { - this.beatmaps = beatmaps; + this.beatmapSets = beatmapSets; } [BackgroundDependencyLoader] @@ -46,11 +46,11 @@ namespace osu.Game.Overlays.Dashboard.Home } }; - flow.AddRange(beatmaps.Select(CreateBeatmapPanel)); + flow.AddRange(beatmapSets.Select(CreateBeatmapPanel)); } protected abstract string Title { get; } - protected abstract DashboardBeatmapPanel CreateBeatmapPanel(BeatmapSetInfo setInfo); + protected abstract DashboardBeatmapPanel CreateBeatmapPanel(APIBeatmapSet beatmapSet); } } diff --git a/osu.Game/Overlays/Dashboard/Home/DrawableNewBeatmapList.cs b/osu.Game/Overlays/Dashboard/Home/DrawableNewBeatmapList.cs index 75e8ca336d..714e07a7ed 100644 --- a/osu.Game/Overlays/Dashboard/Home/DrawableNewBeatmapList.cs +++ b/osu.Game/Overlays/Dashboard/Home/DrawableNewBeatmapList.cs @@ -2,18 +2,18 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; -using osu.Game.Beatmaps; +using osu.Game.Online.API.Requests.Responses; namespace osu.Game.Overlays.Dashboard.Home { public class DrawableNewBeatmapList : DrawableBeatmapList { - public DrawableNewBeatmapList(List beatmaps) - : base(beatmaps) + public DrawableNewBeatmapList(List beatmapSets) + : base(beatmapSets) { } - protected override DashboardBeatmapPanel CreateBeatmapPanel(BeatmapSetInfo setInfo) => new DashboardNewBeatmapPanel(setInfo); + protected override DashboardBeatmapPanel CreateBeatmapPanel(APIBeatmapSet beatmapSet) => new DashboardNewBeatmapPanel(beatmapSet); protected override string Title => "New Ranked Beatmaps"; } diff --git a/osu.Game/Overlays/Dashboard/Home/DrawablePopularBeatmapList.cs b/osu.Game/Overlays/Dashboard/Home/DrawablePopularBeatmapList.cs index 90bd00008c..48b100b04e 100644 --- a/osu.Game/Overlays/Dashboard/Home/DrawablePopularBeatmapList.cs +++ b/osu.Game/Overlays/Dashboard/Home/DrawablePopularBeatmapList.cs @@ -2,18 +2,18 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; -using osu.Game.Beatmaps; +using osu.Game.Online.API.Requests.Responses; namespace osu.Game.Overlays.Dashboard.Home { public class DrawablePopularBeatmapList : DrawableBeatmapList { - public DrawablePopularBeatmapList(List beatmaps) - : base(beatmaps) + public DrawablePopularBeatmapList(List beatmapSets) + : base(beatmapSets) { } - protected override DashboardBeatmapPanel CreateBeatmapPanel(BeatmapSetInfo setInfo) => new DashboardPopularBeatmapPanel(setInfo); + protected override DashboardBeatmapPanel CreateBeatmapPanel(APIBeatmapSet beatmapSet) => new DashboardPopularBeatmapPanel(beatmapSet); protected override string Title => "Popular Beatmaps"; } diff --git a/osu.Game/Overlays/HoldToConfirmOverlay.cs b/osu.Game/Overlays/HoldToConfirmOverlay.cs index 0542f66b5b..cb6275bd7c 100644 --- a/osu.Game/Overlays/HoldToConfirmOverlay.cs +++ b/osu.Game/Overlays/HoldToConfirmOverlay.cs @@ -49,7 +49,7 @@ namespace osu.Game.Overlays Progress.ValueChanged += p => { - var target = p.NewValue * finalFillAlpha; + double target = p.NewValue * finalFillAlpha; audioVolume.Value = 1 - target; overlay.Alpha = (float)target; diff --git a/osu.Game/Overlays/Mods/ModSection.cs b/osu.Game/Overlays/Mods/ModSection.cs index faad23a4e1..5bf8cddd0c 100644 --- a/osu.Game/Overlays/Mods/ModSection.cs +++ b/osu.Game/Overlays/Mods/ModSection.cs @@ -96,7 +96,7 @@ namespace osu.Game.Overlays.Mods if (ToggleKeys != null) { - var index = Array.IndexOf(ToggleKeys, e.Key); + int index = Array.IndexOf(ToggleKeys, e.Key); if (index > -1 && index < Buttons.Count) Buttons[index].SelectNext(e.ShiftPressed ? -1 : 1); } @@ -196,7 +196,7 @@ namespace osu.Game.Overlays.Mods { foreach (var mod in newSelectedMods) { - var index = Array.FindIndex(button.Mods, m1 => mod.GetType() == m1.GetType()); + int index = Array.FindIndex(button.Mods, m1 => mod.GetType() == m1.GetType()); if (index < 0) continue; diff --git a/osu.Game/Overlays/Music/PlaylistItem.cs b/osu.Game/Overlays/Music/PlaylistItem.cs index ef25de77c6..eea2a9dc7e 100644 --- a/osu.Game/Overlays/Music/PlaylistItem.cs +++ b/osu.Game/Overlays/Music/PlaylistItem.cs @@ -3,12 +3,10 @@ using System; using System.Collections.Generic; -using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; using osu.Framework.Localisation; using osu.Game.Beatmaps; @@ -25,7 +23,7 @@ namespace osu.Game.Overlays.Music public Action RequestSelection; private TextFlowContainer text; - private IEnumerable titleSprites; + private ITextPart titlePart; private ILocalisedBindableString title; private ILocalisedBindableString artist; @@ -63,11 +61,16 @@ namespace osu.Game.Overlays.Music if (set.OldValue?.Equals(Model) != true && set.NewValue?.Equals(Model) != true) return; - foreach (Drawable s in titleSprites) - s.FadeColour(set.NewValue.Equals(Model) ? selectedColour : Color4.White, FADE_DURATION); + updateSelectionState(false); }, true); } + private void updateSelectionState(bool instant) + { + foreach (Drawable s in titlePart.Drawables) + s.FadeColour(SelectedSet.Value?.Equals(Model) == true ? selectedColour : Color4.White, instant ? 0 : FADE_DURATION); + } + protected override Drawable CreateContent() => text = new OsuTextFlowContainer { RelativeSizeAxes = Axes.X, @@ -79,7 +82,8 @@ namespace osu.Game.Overlays.Music text.Clear(); // space after the title to put a space between the title and artist - titleSprites = text.AddText(title.Value + @" ", sprite => sprite.Font = OsuFont.GetFont(weight: FontWeight.Regular)).OfType(); + titlePart = text.AddText(title.Value + @" ", sprite => sprite.Font = OsuFont.GetFont(weight: FontWeight.Regular)); + updateSelectionState(true); text.AddText(artist.Value, sprite => { diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index 8fd50c3df2..97c7aaeaeb 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -238,7 +238,7 @@ namespace osu.Game.Overlays if (beatmap.Disabled) return PreviousTrackResult.None; - var currentTrackPosition = CurrentTrack.CurrentTime; + double currentTrackPosition = CurrentTrack.CurrentTime; if (currentTrackPosition >= restart_cutoff_point) { @@ -329,8 +329,8 @@ namespace osu.Game.Overlays else { // figure out the best direction based on order in playlist. - var last = BeatmapSets.TakeWhile(b => b.ID != current.BeatmapSetInfo?.ID).Count(); - var next = newWorking == null ? -1 : BeatmapSets.TakeWhile(b => b.ID != newWorking.BeatmapSetInfo?.ID).Count(); + int last = BeatmapSets.TakeWhile(b => b.ID != current.BeatmapSetInfo?.ID).Count(); + int next = newWorking == null ? -1 : BeatmapSets.TakeWhile(b => b.ID != newWorking.BeatmapSetInfo?.ID).Count(); direction = last > next ? TrackChangeDirection.Prev : TrackChangeDirection.Next; } diff --git a/osu.Game/Overlays/News/Sidebar/NewsSidebar.cs b/osu.Game/Overlays/News/Sidebar/NewsSidebar.cs index 35cd6eb03b..fe965385d8 100644 --- a/osu.Game/Overlays/News/Sidebar/NewsSidebar.cs +++ b/osu.Game/Overlays/News/Sidebar/NewsSidebar.cs @@ -61,11 +61,11 @@ namespace osu.Game.Overlays.News.Sidebar var keys = lookup.Select(kvp => kvp.Key); var sortedKeys = keys.OrderByDescending(k => k).ToList(); - var year = metadata.NewValue.CurrentYear; + int year = metadata.NewValue.CurrentYear; for (int i = 0; i < sortedKeys.Count; i++) { - var month = sortedKeys[i]; + int month = sortedKeys[i]; var posts = lookup[month]; monthsFlow.Add(new MonthSection(month, year, posts) diff --git a/osu.Game/Overlays/News/Sidebar/YearsPanel.cs b/osu.Game/Overlays/News/Sidebar/YearsPanel.cs index b07c9924b9..58c0f6ac82 100644 --- a/osu.Game/Overlays/News/Sidebar/YearsPanel.cs +++ b/osu.Game/Overlays/News/Sidebar/YearsPanel.cs @@ -69,9 +69,9 @@ namespace osu.Game.Overlays.News.Sidebar return; } - var currentYear = metadata.Value.CurrentYear; + int currentYear = metadata.Value.CurrentYear; - foreach (var y in metadata.Value.Years) + foreach (int y in metadata.Value.Years) yearsFlow.Add(new YearButton(y, y == currentYear)); Show(); diff --git a/osu.Game/Overlays/Notifications/ProgressNotification.cs b/osu.Game/Overlays/Notifications/ProgressNotification.cs index f8cd31f193..b27e15dd2c 100644 --- a/osu.Game/Overlays/Notifications/ProgressNotification.cs +++ b/osu.Game/Overlays/Notifications/ProgressNotification.cs @@ -4,11 +4,15 @@ using System; using System.Threading; using osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; +using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; using osu.Game.Graphics.Containers; +using osu.Game.Graphics.UserInterface; using osuTK; using osuTK.Graphics; @@ -16,6 +20,8 @@ namespace osu.Game.Overlays.Notifications { public class ProgressNotification : Notification, IHasCompletionTarget { + private const float loading_spinner_size = 22; + public string Text { set => Schedule(() => textDrawable.Text = value); @@ -65,29 +71,53 @@ namespace osu.Game.Overlays.Notifications private void updateState() { + const double colour_fade_duration = 200; + switch (state) { case ProgressNotificationState.Queued: Light.Colour = colourQueued; Light.Pulsate = false; progressBar.Active = false; + + iconBackground.FadeColour(ColourInfo.GradientVertical(colourQueued, colourQueued.Lighten(0.5f)), colour_fade_duration); + loadingSpinner.Show(); break; case ProgressNotificationState.Active: Light.Colour = colourActive; Light.Pulsate = true; progressBar.Active = true; + + iconBackground.FadeColour(ColourInfo.GradientVertical(colourActive, colourActive.Lighten(0.5f)), colour_fade_duration); + loadingSpinner.Show(); break; case ProgressNotificationState.Cancelled: cancellationTokenSource.Cancel(); + iconBackground.FadeColour(ColourInfo.GradientVertical(Color4.Gray, Color4.Gray.Lighten(0.5f)), colour_fade_duration); + loadingSpinner.Hide(); + + var icon = new SpriteIcon + { + Icon = FontAwesome.Solid.Ban, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(loading_spinner_size), + }; + + IconContent.Add(icon); + + icon.FadeInFromZero(200, Easing.OutQuint); + Light.Colour = colourCancelled; Light.Pulsate = false; progressBar.Active = false; break; case ProgressNotificationState.Completed: + loadingSpinner.Hide(); NotificationContent.MoveToY(-DrawSize.Y / 2, 200, Easing.OutQuint); this.FadeOut(200).Finally(d => Completed()); break; @@ -115,15 +145,13 @@ namespace osu.Game.Overlays.Notifications private Color4 colourActive; private Color4 colourCancelled; + private Box iconBackground; + private LoadingSpinner loadingSpinner; + private readonly TextFlowContainer textDrawable; public ProgressNotification() { - IconContent.Add(new Box - { - RelativeSizeAxes = Axes.Both, - }); - Content.Add(textDrawable = new OsuTextFlowContainer { Colour = OsuColour.Gray(128), @@ -138,6 +166,9 @@ namespace osu.Game.Overlays.Notifications RelativeSizeAxes = Axes.X, }); + // make some extra space for the progress bar. + IconContent.Margin = new MarginPadding { Bottom = 5 }; + State = ProgressNotificationState.Queued; // don't close on click by default. @@ -150,6 +181,19 @@ namespace osu.Game.Overlays.Notifications colourQueued = colours.YellowDark; colourActive = colours.Blue; colourCancelled = colours.Red; + + IconContent.AddRange(new Drawable[] + { + iconBackground = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.White, + }, + loadingSpinner = new LoadingSpinner + { + Size = new Vector2(loading_spinner_size), + } + }); } public override void Close() diff --git a/osu.Game/Overlays/Profile/Header/Components/PreviousUsernames.cs b/osu.Game/Overlays/Profile/Header/Components/PreviousUsernames.cs index 14eeb4e5f0..b1076ba39b 100644 --- a/osu.Game/Overlays/Profile/Header/Components/PreviousUsernames.cs +++ b/osu.Game/Overlays/Profile/Header/Components/PreviousUsernames.cs @@ -111,7 +111,7 @@ namespace osu.Game.Overlays.Profile.Header.Components { text.Text = string.Empty; - var usernames = user.NewValue?.PreviousUsernames; + string[] usernames = user.NewValue?.PreviousUsernames; if (usernames?.Any() ?? false) { diff --git a/osu.Game/Overlays/Profile/Header/Components/RankGraph.cs b/osu.Game/Overlays/Profile/Header/Components/RankGraph.cs index ca5f26e375..d6e515d8a1 100644 --- a/osu.Game/Overlays/Profile/Header/Components/RankGraph.cs +++ b/osu.Game/Overlays/Profile/Header/Components/RankGraph.cs @@ -62,7 +62,7 @@ namespace osu.Game.Overlays.Profile.Header.Components protected override UserGraphTooltipContent GetTooltipContent(int index, int rank) { - var days = ranked_days - index + 1; + int days = ranked_days - index + 1; return new UserGraphTooltipContent( UsersStrings.ShowRankGlobalSimple, diff --git a/osu.Game/Overlays/Profile/Header/MedalHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/MedalHeaderContainer.cs index e7df4eb5eb..f6419db540 100644 --- a/osu.Game/Overlays/Profile/Header/MedalHeaderContainer.cs +++ b/osu.Game/Overlays/Profile/Header/MedalHeaderContainer.cs @@ -72,7 +72,7 @@ namespace osu.Game.Overlays.Profile.Header { Show(); - for (var index = 0; index < badges.Length; index++) + for (int index = 0; index < badges.Length; index++) { int displayIndex = index; LoadComponentAsync(new DrawableBadge(badges[index]), asyncBadge => diff --git a/osu.Game/Overlays/Profile/Sections/BeatmapMetadataContainer.cs b/osu.Game/Overlays/Profile/Sections/BeatmapMetadataContainer.cs index 7812a81f30..94ef5e5d86 100644 --- a/osu.Game/Overlays/Profile/Sections/BeatmapMetadataContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/BeatmapMetadataContainer.cs @@ -15,9 +15,9 @@ namespace osu.Game.Overlays.Profile.Sections /// public abstract class BeatmapMetadataContainer : OsuHoverContainer { - private readonly BeatmapInfo beatmapInfo; + private readonly IBeatmapInfo beatmapInfo; - protected BeatmapMetadataContainer(BeatmapInfo beatmapInfo) + protected BeatmapMetadataContainer(IBeatmapInfo beatmapInfo) : base(HoverSampleSet.Submit) { this.beatmapInfo = beatmapInfo; @@ -30,10 +30,7 @@ namespace osu.Game.Overlays.Profile.Sections { Action = () => { - if (beatmapInfo.OnlineBeatmapID != null) - beatmapSetOverlay?.FetchAndShowBeatmap(beatmapInfo.OnlineBeatmapID.Value); - else if (beatmapInfo.BeatmapSet?.OnlineBeatmapSetID != null) - beatmapSetOverlay?.FetchAndShowBeatmapSet(beatmapInfo.BeatmapSet.OnlineBeatmapSetID.Value); + beatmapSetOverlay?.FetchAndShowBeatmap(beatmapInfo.OnlineID); }; Child = new FillFlowContainer @@ -43,6 +40,6 @@ namespace osu.Game.Overlays.Profile.Sections }; } - protected abstract Drawable[] CreateText(BeatmapInfo beatmapInfo); + protected abstract Drawable[] CreateText(IBeatmapInfo beatmapInfo); } } diff --git a/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs b/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs index c1e56facd9..fffa20dc11 100644 --- a/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs @@ -61,7 +61,7 @@ namespace osu.Game.Overlays.Profile.Sections.Beatmaps new GetUserBeatmapsRequest(User.Value.Id, type, VisiblePages++, ItemsPerPage); protected override Drawable CreateDrawableItem(APIBeatmapSet model) => model.OnlineID > 0 - ? new GridBeatmapPanel(model.ToBeatmapSet(Rulesets)) + ? new GridBeatmapPanel(model) { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, diff --git a/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedBeatmap.cs b/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedBeatmap.cs index 32201e36a9..3ed4bd9e50 100644 --- a/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedBeatmap.cs +++ b/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedBeatmap.cs @@ -1,18 +1,20 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Diagnostics; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Sprites; +using osu.Framework.Localisation; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osuTK; -using osu.Framework.Graphics.Cursor; -using osu.Framework.Localisation; +using osu.Game.Online.API.Requests.Responses; using osu.Game.Resources.Localisation.Web; namespace osu.Game.Overlays.Profile.Sections.Historical @@ -22,13 +24,11 @@ namespace osu.Game.Overlays.Profile.Sections.Historical private const int cover_width = 100; private const int corner_radius = 6; - private readonly BeatmapInfo beatmapInfo; - private readonly int playCount; + private readonly APIUserMostPlayedBeatmap mostPlayed; - public DrawableMostPlayedBeatmap(BeatmapInfo beatmapInfo, int playCount) + public DrawableMostPlayedBeatmap(APIUserMostPlayedBeatmap mostPlayed) { - this.beatmapInfo = beatmapInfo; - this.playCount = playCount; + this.mostPlayed = mostPlayed; RelativeSizeAxes = Axes.X; Height = 50; @@ -46,7 +46,7 @@ namespace osu.Game.Overlays.Profile.Sections.Historical { RelativeSizeAxes = Axes.Y, Width = cover_width, - BeatmapSet = beatmapInfo.BeatmapSet, + OnlineInfo = mostPlayed.BeatmapSet, }, new Container { @@ -77,7 +77,7 @@ namespace osu.Game.Overlays.Profile.Sections.Historical Direction = FillDirection.Vertical, Children = new Drawable[] { - new MostPlayedBeatmapMetadataContainer(beatmapInfo), + new MostPlayedBeatmapMetadataContainer(mostPlayed.BeatmapInfo), new LinkFlowContainer(t => { t.Font = OsuFont.GetFont(size: 12, weight: FontWeight.Regular); @@ -89,11 +89,11 @@ namespace osu.Game.Overlays.Profile.Sections.Historical }.With(d => { d.AddText("mapped by "); - d.AddUserLink(beatmapInfo.Metadata.Author); + d.AddUserLink(mostPlayed.BeatmapSet.Author); }), } }, - new PlayCountText(playCount) + new PlayCountText(mostPlayed.PlayCount) { Anchor = Anchor.CentreRight, Origin = Anchor.CentreRight @@ -120,26 +120,41 @@ namespace osu.Game.Overlays.Profile.Sections.Historical private class MostPlayedBeatmapMetadataContainer : BeatmapMetadataContainer { - public MostPlayedBeatmapMetadataContainer(BeatmapInfo beatmapInfo) + public MostPlayedBeatmapMetadataContainer(IBeatmapInfo beatmapInfo) : base(beatmapInfo) { } - protected override Drawable[] CreateText(BeatmapInfo beatmapInfo) => new Drawable[] + protected override Drawable[] CreateText(IBeatmapInfo beatmapInfo) { - new OsuSpriteText + var metadata = beatmapInfo.Metadata; + + Debug.Assert(metadata != null); + + return new Drawable[] { - Text = new RomanisableString( - $"{beatmapInfo.Metadata.TitleUnicode ?? beatmapInfo.Metadata.Title} [{beatmapInfo.Version}] ", - $"{beatmapInfo.Metadata.Title ?? beatmapInfo.Metadata.TitleUnicode} [{beatmapInfo.Version}] "), - Font = OsuFont.GetFont(weight: FontWeight.Bold) - }, - new OsuSpriteText - { - Text = "by " + new RomanisableString(beatmapInfo.Metadata.ArtistUnicode, beatmapInfo.Metadata.Artist), - Font = OsuFont.GetFont(weight: FontWeight.Regular) - }, - }; + new OsuSpriteText + { + Text = new RomanisableString(metadata.TitleUnicode, metadata.Title), + Font = OsuFont.GetFont(weight: FontWeight.Bold) + }, + new OsuSpriteText + { + Text = $" [{beatmapInfo.DifficultyName}]", + Font = OsuFont.GetFont(weight: FontWeight.Bold) + }, + new OsuSpriteText + { + Text = " by ", + Font = OsuFont.GetFont(weight: FontWeight.Regular) + }, + new OsuSpriteText + { + Text = new RomanisableString(metadata.ArtistUnicode, metadata.Artist), + Font = OsuFont.GetFont(weight: FontWeight.Regular) + }, + }; + } } private class PlayCountText : CompositeDrawable, IHasTooltip diff --git a/osu.Game/Overlays/Profile/Sections/Historical/PaginatedMostPlayedBeatmapContainer.cs b/osu.Game/Overlays/Profile/Sections/Historical/PaginatedMostPlayedBeatmapContainer.cs index d0979526da..428d04f985 100644 --- a/osu.Game/Overlays/Profile/Sections/Historical/PaginatedMostPlayedBeatmapContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Historical/PaginatedMostPlayedBeatmapContainer.cs @@ -33,7 +33,7 @@ namespace osu.Game.Overlays.Profile.Sections.Historical protected override APIRequest> CreateRequest() => new GetUserMostPlayedBeatmapsRequest(User.Value.Id, VisiblePages++, ItemsPerPage); - protected override Drawable CreateDrawableItem(APIUserMostPlayedBeatmap model) => - new DrawableMostPlayedBeatmap(model.GetBeatmapInfo(Rulesets), model.PlayCount); + protected override Drawable CreateDrawableItem(APIUserMostPlayedBeatmap mostPlayed) => + new DrawableMostPlayedBeatmap(mostPlayed); } } diff --git a/osu.Game/Overlays/Profile/Sections/Historical/ProfileLineChart.cs b/osu.Game/Overlays/Profile/Sections/Historical/ProfileLineChart.cs index a75235359a..d402438376 100644 --- a/osu.Game/Overlays/Profile/Sections/Historical/ProfileLineChart.cs +++ b/osu.Game/Overlays/Profile/Sections/Historical/ProfileLineChart.cs @@ -116,10 +116,10 @@ namespace osu.Game.Overlays.Profile.Sections.Historical rowTicksContainer.Clear(); rowLinesContainer.Clear(); - var min = values.Select(v => v.Count).Min(); - var max = values.Select(v => v.Count).Max(); + long min = values.Select(v => v.Count).Min(); + long max = values.Select(v => v.Count).Max(); - var tickInterval = getTickInterval(max - min, 6); + long tickInterval = getTickInterval(max - min, 6); for (long currentTick = 0; currentTick <= max; currentTick += tickInterval) { @@ -145,7 +145,7 @@ namespace osu.Game.Overlays.Profile.Sections.Historical columnTicksContainer.Clear(); columnLinesContainer.Clear(); - var totalMonths = values.Length; + int totalMonths = values.Length; int monthsPerTick = 1; @@ -158,7 +158,7 @@ namespace osu.Game.Overlays.Profile.Sections.Historical for (int i = 0; i < totalMonths; i += monthsPerTick) { - var x = (float)i / (totalMonths - 1); + float x = (float)i / (totalMonths - 1); addColumnTick(x, values[i].Date); } } @@ -215,15 +215,15 @@ namespace osu.Game.Overlays.Profile.Sections.Historical { // this interval is what would be achieved if the interval was divided perfectly evenly into maxTicksCount ticks. // can contain ugly fractional parts. - var exactTickInterval = (float)range / (maxTicksCount - 1); + float exactTickInterval = (float)range / (maxTicksCount - 1); // the ideal ticks start with a 1, 2 or 5, and are multipliers of powers of 10. // first off, use log10 to calculate the number of digits in the "exact" interval. - var numberOfDigits = Math.Floor(Math.Log10(exactTickInterval)); - var tickBase = Math.Pow(10, numberOfDigits); + double numberOfDigits = Math.Floor(Math.Log10(exactTickInterval)); + double tickBase = Math.Pow(10, numberOfDigits); // then see how the exact tick relates to the power of 10. - var exactTickMultiplier = exactTickInterval / tickBase; + double exactTickMultiplier = exactTickInterval / tickBase; double tickMultiplier; diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs index 3561e9700e..ca5534dbc2 100644 --- a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs +++ b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Diagnostics; using System.Linq; using JetBrains.Annotations; using osu.Framework.Allocation; @@ -11,9 +12,11 @@ using osu.Framework.Localisation; using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; +using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Leaderboards; +using osu.Game.Rulesets; using osu.Game.Rulesets.UI; -using osu.Game.Scoring; +using osu.Game.Utils; using osuTK; namespace osu.Game.Overlays.Profile.Sections.Ranks @@ -25,7 +28,7 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks private const float performance_background_shear = 0.45f; - protected readonly ScoreInfo Score; + protected readonly APIScoreInfo Score; [Resolved] private OsuColour colours { get; set; } @@ -33,7 +36,7 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks [Resolved] private OverlayColourProvider colourProvider { get; set; } - public DrawableProfileScore(ScoreInfo score) + public DrawableProfileScore(APIScoreInfo score) { Score = score; @@ -42,7 +45,7 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks } [BackgroundDependencyLoader] - private void load() + private void load(RulesetStore rulesets) { AddInternal(new ProfileItemContainer { @@ -78,7 +81,7 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks Spacing = new Vector2(0, 2), Children = new Drawable[] { - new ScoreBeatmapMetadataContainer(Score.BeatmapInfo), + new ScoreBeatmapMetadataContainer(Score.Beatmap), new FillFlowContainer { AutoSizeAxes = Axes.Both, @@ -88,7 +91,7 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks { new OsuSpriteText { - Text = $"{Score.BeatmapInfo.Version}", + Text = $"{Score.Beatmap.DifficultyName}", Font = OsuFont.GetFont(size: 12, weight: FontWeight.Regular), Colour = colours.Yellow }, @@ -128,7 +131,7 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks Origin = Anchor.CentreRight, Direction = FillDirection.Horizontal, Spacing = new Vector2(2), - Children = Score.Mods.Select(mod => new ModIcon(mod) + Children = Score.Mods.Select(mod => new ModIcon(rulesets.GetRuleset(Score.RulesetID).CreateInstance().CreateModFromAcronym(mod.Acronym)) { Scale = new Vector2(0.35f) }).ToList(), @@ -197,7 +200,7 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks RelativeSizeAxes = Axes.Y, Child = new OsuSpriteText { - Text = Score.DisplayAccuracy, + Text = Score.Accuracy.FormatAccuracy(), Font = OsuFont.GetFont(size: 14, weight: FontWeight.Bold, italics: true), Colour = colours.Yellow, Anchor = Anchor.CentreLeft, @@ -245,30 +248,42 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks private class ScoreBeatmapMetadataContainer : BeatmapMetadataContainer { - public ScoreBeatmapMetadataContainer(BeatmapInfo beatmapInfo) + public ScoreBeatmapMetadataContainer(IBeatmapInfo beatmapInfo) : base(beatmapInfo) { } - protected override Drawable[] CreateText(BeatmapInfo beatmapInfo) => new Drawable[] + protected override Drawable[] CreateText(IBeatmapInfo beatmapInfo) { - new OsuSpriteText + var metadata = beatmapInfo.Metadata; + + Debug.Assert(metadata != null); + + return new Drawable[] { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - Text = new RomanisableString( - $"{beatmapInfo.Metadata.TitleUnicode ?? beatmapInfo.Metadata.Title} ", - $"{beatmapInfo.Metadata.Title ?? beatmapInfo.Metadata.TitleUnicode} "), - Font = OsuFont.GetFont(size: 14, weight: FontWeight.SemiBold, italics: true) - }, - new OsuSpriteText - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - Text = "by " + new RomanisableString(beatmapInfo.Metadata.ArtistUnicode, beatmapInfo.Metadata.Artist), - Font = OsuFont.GetFont(size: 12, italics: true) - }, - }; + new OsuSpriteText + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Text = new RomanisableString(metadata.TitleUnicode, metadata.Title), + Font = OsuFont.GetFont(size: 14, weight: FontWeight.SemiBold, italics: true) + }, + new OsuSpriteText + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Text = " by ", + Font = OsuFont.GetFont(size: 12, italics: true) + }, + new OsuSpriteText + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Text = new RomanisableString(metadata.ArtistUnicode, metadata.Artist), + Font = OsuFont.GetFont(size: 12, italics: true) + }, + }; + } } } } diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileWeightedScore.cs b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileWeightedScore.cs index f77464ecb9..e653be5cfa 100644 --- a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileWeightedScore.cs +++ b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileWeightedScore.cs @@ -6,8 +6,8 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; +using osu.Game.Online.API.Requests.Responses; using osu.Game.Resources.Localisation.Web; -using osu.Game.Scoring; using osuTK; namespace osu.Game.Overlays.Profile.Sections.Ranks @@ -16,7 +16,7 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks { private readonly double weight; - public DrawableProfileWeightedScore(ScoreInfo score, double weight) + public DrawableProfileWeightedScore(APIScoreInfo score, double weight) : base(score) { this.weight = weight; diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs b/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs index 7c04b331c2..cde386bc7b 100644 --- a/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs @@ -15,7 +15,7 @@ using osu.Framework.Localisation; namespace osu.Game.Overlays.Profile.Sections.Ranks { - public class PaginatedScoreContainer : PaginatedProfileSubsection + public class PaginatedScoreContainer : PaginatedProfileSubsection { private readonly ScoreType type; @@ -51,7 +51,7 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks } } - protected override void OnItemsReceived(List items) + protected override void OnItemsReceived(List items) { if (VisiblePages == 0) drawableItemIndex = 0; @@ -59,20 +59,20 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks base.OnItemsReceived(items); } - protected override APIRequest> CreateRequest() => + protected override APIRequest> CreateRequest() => new GetUserScoresRequest(User.Value.Id, type, VisiblePages++, ItemsPerPage); private int drawableItemIndex; - protected override Drawable CreateDrawableItem(APILegacyScoreInfo model) + protected override Drawable CreateDrawableItem(APIScoreInfo model) { switch (type) { default: - return new DrawableProfileScore(model.CreateScoreInfo(Rulesets)); + return new DrawableProfileScore(model); case ScoreType.Best: - return new DrawableProfileWeightedScore(model.CreateScoreInfo(Rulesets), Math.Pow(0.95, drawableItemIndex++)); + return new DrawableProfileWeightedScore(model, Math.Pow(0.95, drawableItemIndex++)); } } } diff --git a/osu.Game/Overlays/Rankings/SpotlightsLayout.cs b/osu.Game/Overlays/Rankings/SpotlightsLayout.cs index b16e0a4908..b95b0a1afc 100644 --- a/osu.Game/Overlays/Rankings/SpotlightsLayout.cs +++ b/osu.Game/Overlays/Rankings/SpotlightsLayout.cs @@ -143,7 +143,7 @@ namespace osu.Game.Overlays.Rankings AutoSizeAxes = Axes.Y, RelativeSizeAxes = Axes.X, Spacing = new Vector2(10), - Children = response.BeatmapSets.Select(b => new GridBeatmapPanel(b.ToBeatmapSet(rulesets)) + Children = response.BeatmapSets.Select(b => new GridBeatmapPanel(b) { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, diff --git a/osu.Game/Overlays/Settings/Sections/Audio/AudioDevicesSettings.cs b/osu.Game/Overlays/Settings/Sections/Audio/AudioDevicesSettings.cs index 0c54ae2763..858d555f06 100644 --- a/osu.Game/Overlays/Settings/Sections/Audio/AudioDevicesSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Audio/AudioDevicesSettings.cs @@ -47,7 +47,7 @@ namespace osu.Game.Overlays.Settings.Sections.Audio var deviceItems = new List { string.Empty }; deviceItems.AddRange(audio.AudioDeviceNames); - var preferredDeviceName = audio.AudioDevice.Value; + string preferredDeviceName = audio.AudioDevice.Value; if (deviceItems.All(kv => kv != preferredDeviceName)) deviceItems.Add(preferredDeviceName); diff --git a/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs b/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs index aa37748653..6bcb5ef715 100644 --- a/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs @@ -67,7 +67,7 @@ namespace osu.Game.Overlays.Settings.Sections.General Add(new SettingsButton { Text = GeneralSettingsStrings.OpenOsuFolder, - Action = storage.OpenInNativeExplorer, + Action = storage.PresentExternally, }); Add(new SettingsButton diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/VideoSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/VideoSettings.cs new file mode 100644 index 0000000000..921eab63ed --- /dev/null +++ b/osu.Game/Overlays/Settings/Sections/Graphics/VideoSettings.cs @@ -0,0 +1,43 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Configuration; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Video; +using osu.Framework.Localisation; +using osu.Game.Localisation; + +namespace osu.Game.Overlays.Settings.Sections.Graphics +{ + public class VideoSettings : SettingsSubsection + { + protected override LocalisableString Header => GraphicsSettingsStrings.VideoHeader; + + private Bindable hardwareVideoDecoder; + private SettingsCheckbox hwAccelCheckbox; + + [BackgroundDependencyLoader] + private void load(FrameworkConfigManager config) + { + hardwareVideoDecoder = config.GetBindable(FrameworkSetting.HardwareVideoDecoder); + + Children = new Drawable[] + { + hwAccelCheckbox = new SettingsCheckbox + { + LabelText = GraphicsSettingsStrings.UseHardwareAcceleration, + }, + }; + + hwAccelCheckbox.Current.Default = hardwareVideoDecoder.Default != HardwareVideoDecoder.None; + hwAccelCheckbox.Current.Value = hardwareVideoDecoder.Value != HardwareVideoDecoder.None; + + hwAccelCheckbox.Current.BindValueChanged(val => + { + hardwareVideoDecoder.Value = val.NewValue ? HardwareVideoDecoder.Any : HardwareVideoDecoder.None; + }); + } + } +} diff --git a/osu.Game/Overlays/Settings/Sections/GraphicsSection.cs b/osu.Game/Overlays/Settings/Sections/GraphicsSection.cs index 591848506a..8cd3b841c2 100644 --- a/osu.Game/Overlays/Settings/Sections/GraphicsSection.cs +++ b/osu.Game/Overlays/Settings/Sections/GraphicsSection.cs @@ -24,6 +24,7 @@ namespace osu.Game.Overlays.Settings.Sections { new LayoutSettings(), new RendererSettings(), + new VideoSettings(), new ScreenshotSettings(), }; } diff --git a/osu.Game/Overlays/Settings/Sections/Input/KeyBindingsSubsection.cs b/osu.Game/Overlays/Settings/Sections/Input/KeyBindingsSubsection.cs index 2051af6f3c..be0830a7c2 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/KeyBindingsSubsection.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/KeyBindingsSubsection.cs @@ -32,7 +32,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input [BackgroundDependencyLoader] private void load(RealmContextFactory realmFactory) { - var rulesetId = Ruleset?.ID; + int? rulesetId = Ruleset?.ID; List bindings; diff --git a/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs b/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs index a4da17c5cd..0334167759 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs @@ -86,7 +86,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input handlerSensitivity.BindValueChanged(val => { - var disabled = localSensitivity.Disabled; + bool disabled = localSensitivity.Disabled; localSensitivity.Disabled = false; localSensitivity.Value = val.NewValue; @@ -97,7 +97,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input windowMode.BindValueChanged(mode => { - var isFullscreen = mode.NewValue == WindowMode.Fullscreen; + bool isFullscreen = mode.NewValue == WindowMode.Fullscreen; if (isFullscreen) { diff --git a/osu.Game/Overlays/Settings/Sections/Input/RotationPresetButtons.cs b/osu.Game/Overlays/Settings/Sections/Input/RotationPresetButtons.cs index 3ef5ce8941..dbdf600002 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/RotationPresetButtons.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/RotationPresetButtons.cs @@ -59,7 +59,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input for (int i = 0; i < preset_count; i++) { - var rotationValue = i * 90; + int rotationValue = i * 90; var rotationPreset = new RotationButton(rotationValue) { diff --git a/osu.Game/Overlays/Settings/Sections/Input/RulesetBindingsSection.cs b/osu.Game/Overlays/Settings/Sections/Input/RulesetBindingsSection.cs index 5246051a4a..48cbe1b59e 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/RulesetBindingsSection.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/RulesetBindingsSection.cs @@ -26,7 +26,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input var r = ruleset.CreateInstance(); - foreach (var variant in r.AvailableVariants) + foreach (int variant in r.AvailableVariants) Add(new VariantBindingsSubsection(ruleset, variant)); } } diff --git a/osu.Game/Overlays/Settings/Sections/SkinSection.cs b/osu.Game/Overlays/Settings/Sections/SkinSection.cs index 00198235c5..cf5d70ba91 100644 --- a/osu.Game/Overlays/Settings/Sections/SkinSection.cs +++ b/osu.Game/Overlays/Settings/Sections/SkinSection.cs @@ -45,7 +45,7 @@ namespace osu.Game.Overlays.Settings.Sections { get { - var index = skinItems.FindIndex(s => s.ID > 0); + int index = skinItems.FindIndex(s => s.ID > 0); if (index < 0) index = skinItems.Count; diff --git a/osu.Game/Overlays/Settings/SettingsNumberBox.cs b/osu.Game/Overlays/Settings/SettingsNumberBox.cs index aca7a210b3..545f1050b2 100644 --- a/osu.Game/Overlays/Settings/SettingsNumberBox.cs +++ b/osu.Game/Overlays/Settings/SettingsNumberBox.cs @@ -46,7 +46,7 @@ namespace osu.Game.Overlays.Settings { int? value = null; - if (int.TryParse(e.NewValue, out var intVal)) + if (int.TryParse(e.NewValue, out int intVal)) value = intVal; current.Value = value; diff --git a/osu.Game/Overlays/TabControlOverlayHeader.cs b/osu.Game/Overlays/TabControlOverlayHeader.cs index e6f7e250a7..1b0bd658d9 100644 --- a/osu.Game/Overlays/TabControlOverlayHeader.cs +++ b/osu.Game/Overlays/TabControlOverlayHeader.cs @@ -111,7 +111,7 @@ namespace osu.Game.Overlays else { var localisableDescription = enumValue.GetLocalisableDescription(); - var nonLocalisableDescription = enumValue.GetDescription(); + string nonLocalisableDescription = enumValue.GetDescription(); // If localisable == non-localisable, then we must have a basic string, so .ToLower() is used. Text.Text = localisableDescription.Equals(nonLocalisableDescription) diff --git a/osu.Game/Overlays/Toolbar/ToolbarButton.cs b/osu.Game/Overlays/Toolbar/ToolbarButton.cs index dd554200ca..ab37b3b355 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarButton.cs @@ -207,7 +207,7 @@ namespace osu.Game.Overlays.Toolbar if (realmKeyBinding != null) { - var keyBindingString = realmKeyBinding.KeyCombination.ReadableString(); + string keyBindingString = realmKeyBinding.KeyCombination.ReadableString(); if (!string.IsNullOrEmpty(keyBindingString)) keyBindingTooltip.Text = $" ({keyBindingString})"; diff --git a/osu.Game/Overlays/Volume/VolumeMeter.cs b/osu.Game/Overlays/Volume/VolumeMeter.cs index ff28b45ebb..e2afd46c18 100644 --- a/osu.Game/Overlays/Volume/VolumeMeter.cs +++ b/osu.Game/Overlays/Volume/VolumeMeter.cs @@ -325,7 +325,7 @@ namespace osu.Game.Overlays.Volume delta *= accelerationModifier; accelerationModifier = Math.Min(max_acceleration, accelerationModifier * acceleration_multiplier); - var precision = Bindable.Precision; + double precision = Bindable.Precision; if (isPrecise) { diff --git a/osu.Game/Overlays/Wiki/Markdown/WikiNoticeContainer.cs b/osu.Game/Overlays/Wiki/Markdown/WikiNoticeContainer.cs index 421806eea8..a22c18b0a4 100644 --- a/osu.Game/Overlays/Wiki/Markdown/WikiNoticeContainer.cs +++ b/osu.Game/Overlays/Wiki/Markdown/WikiNoticeContainer.cs @@ -22,7 +22,7 @@ namespace osu.Game.Overlays.Wiki.Markdown AutoSizeAxes = Axes.Y; Direction = FillDirection.Vertical; - foreach (var line in yamlFrontMatterBlock.Lines) + foreach (object line in yamlFrontMatterBlock.Lines) { switch (line.ToString()) { diff --git a/osu.Game/Overlays/Wiki/WikiMainPage.cs b/osu.Game/Overlays/Wiki/WikiMainPage.cs index c9ee2cbfd5..9416ec77f1 100644 --- a/osu.Game/Overlays/Wiki/WikiMainPage.cs +++ b/osu.Game/Overlays/Wiki/WikiMainPage.cs @@ -72,11 +72,11 @@ namespace osu.Game.Overlays.Wiki Debug.Assert(panelsNode.Length > 1); - var i = 0; + int i = 0; while (i < panelsNode.Length) { - var isFullWidth = panelsNode[i].HasClass("wiki-main-page-panel--full"); + bool isFullWidth = panelsNode[i].HasClass("wiki-main-page-panel--full"); if (isFullWidth) { diff --git a/osu.Game/Overlays/WikiOverlay.cs b/osu.Game/Overlays/WikiOverlay.cs index bde73b6180..44713d637d 100644 --- a/osu.Game/Overlays/WikiOverlay.cs +++ b/osu.Game/Overlays/WikiOverlay.cs @@ -140,7 +140,7 @@ namespace osu.Game.Overlays private void showParentPage() { - var parentPath = string.Join("/", path.Value.Split('/').SkipLast(1)); + string parentPath = string.Join("/", path.Value.Split('/').SkipLast(1)); ShowPage(parentPath); } diff --git a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs index 200bbf3f92..eab81186d5 100644 --- a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs @@ -158,7 +158,7 @@ namespace osu.Game.Rulesets.Difficulty // Apply the rest of the remaining mods recursively. for (int i = 0; i < remainingMods.Length; i++) { - var (nextSet, nextCount) = flatten(remainingMods.Span[i]); + (var nextSet, int nextCount) = flatten(remainingMods.Span[i]); // Check if any mods in the next set are incompatible with any of the current set. if (currentSet.SelectMany(m => m.IncompatibleMods).Any(c => nextSet.Any(c.IsInstanceOfType))) @@ -185,7 +185,7 @@ namespace osu.Game.Rulesets.Difficulty foreach (var nested in multi.Mods) { - var (nestedSet, nestedCount) = flatten(nested); + (var nestedSet, int nestedCount) = flatten(nested); set = set.Concat(nestedSet); count += nestedCount; } diff --git a/osu.Game/Rulesets/Edit/Checks/CheckAudioInVideo.cs b/osu.Game/Rulesets/Edit/Checks/CheckAudioInVideo.cs index ac2542beb0..255671c807 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckAudioInVideo.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckAudioInVideo.cs @@ -40,7 +40,7 @@ namespace osu.Game.Rulesets.Edit.Checks } } - foreach (var filename in videoPaths) + foreach (string filename in videoPaths) { string storagePath = beatmapSet.GetPathForFile(filename); diff --git a/osu.Game/Rulesets/Edit/Checks/CheckAudioQuality.cs b/osu.Game/Rulesets/Edit/Checks/CheckAudioQuality.cs index 70d11883b7..08e0312b64 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckAudioQuality.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckAudioQuality.cs @@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Edit.Checks public IEnumerable Run(BeatmapVerifierContext context) { - var audioFile = context.Beatmap.Metadata?.AudioFile; + string audioFile = context.Beatmap.Metadata?.AudioFile; if (audioFile == null) yield break; diff --git a/osu.Game/Rulesets/Edit/Checks/CheckBackgroundQuality.cs b/osu.Game/Rulesets/Edit/Checks/CheckBackgroundQuality.cs index 085c558eaf..8fa79e2ee8 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckBackgroundQuality.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckBackgroundQuality.cs @@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Edit.Checks public IEnumerable Run(BeatmapVerifierContext context) { - var backgroundFile = context.Beatmap.Metadata?.BackgroundFile; + string backgroundFile = context.Beatmap.Metadata?.BackgroundFile; if (backgroundFile == null) yield break; diff --git a/osu.Game/Rulesets/Edit/Checks/CheckFewHitsounds.cs b/osu.Game/Rulesets/Edit/Checks/CheckFewHitsounds.cs index 5185ba6c99..3358e81d5f 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckFewHitsounds.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckFewHitsounds.cs @@ -67,7 +67,7 @@ namespace osu.Game.Rulesets.Edit.Checks } var hitObjectsByEndTime = hitObjectsIncludingNested.OrderBy(o => o.GetEndTime()).ToList(); - var hitObjectCount = hitObjectsByEndTime.Count; + int hitObjectCount = hitObjectsByEndTime.Count; for (int i = 0; i < hitObjectCount; ++i) { @@ -86,7 +86,7 @@ namespace osu.Game.Rulesets.Edit.Checks private IEnumerable applyHitsoundUpdate(HitObject hitObject, bool isLastObject = false) { - var time = hitObject.GetEndTime(); + double time = hitObject.GetEndTime(); bool hasHitsound = hitObject.Samples.Any(isHitsound); bool couldHaveHitsound = hitObject.Samples.Any(isHitnormal); @@ -94,7 +94,7 @@ namespace osu.Game.Rulesets.Edit.Checks // If there are no hitsounds we let the "No hitsounds" template take precedence. if (hasHitsound || (isLastObject && mapHasHitsounds)) { - var timeWithoutHitsounds = time - lastHitsoundTime; + double timeWithoutHitsounds = time - lastHitsoundTime; if (timeWithoutHitsounds > problem_threshold_time && objectsWithoutHitsounds > problem_threshold_objects) yield return new IssueTemplateLongPeriodProblem(this).Create(lastHitsoundTime, timeWithoutHitsounds); diff --git a/osu.Game/Rulesets/Edit/Checks/CheckFilePresence.cs b/osu.Game/Rulesets/Edit/Checks/CheckFilePresence.cs index 36a0bf8c5d..abedee143a 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckFilePresence.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckFilePresence.cs @@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Edit.Checks public IEnumerable Run(BeatmapVerifierContext context) { - var filename = GetFilename(context.Beatmap); + string filename = GetFilename(context.Beatmap); if (filename == null) { @@ -33,7 +33,7 @@ namespace osu.Game.Rulesets.Edit.Checks } // If the file is set, also make sure it still exists. - var storagePath = context.Beatmap.BeatmapInfo.BeatmapSet.GetPathForFile(filename); + string storagePath = context.Beatmap.BeatmapInfo.BeatmapSet.GetPathForFile(filename); if (storagePath != null) yield break; diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index 91cc80e930..8bad9aa11f 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -223,7 +223,7 @@ namespace osu.Game.Rulesets.Edit if (e.ControlPressed || e.AltPressed || e.SuperPressed) return false; - if (checkLeftToggleFromKey(e.Key, out var leftIndex)) + if (checkLeftToggleFromKey(e.Key, out int leftIndex)) { var item = toolboxCollection.Items.ElementAtOrDefault(leftIndex); @@ -235,7 +235,7 @@ namespace osu.Game.Rulesets.Edit } } - if (checkRightToggleFromKey(e.Key, out var rightIndex)) + if (checkRightToggleFromKey(e.Key, out int rightIndex)) { var item = togglesCollection.ElementAtOrDefault(rightIndex); diff --git a/osu.Game/Rulesets/IRulesetInfo.cs b/osu.Game/Rulesets/IRulesetInfo.cs index 779433dc81..c3bc6c1995 100644 --- a/osu.Game/Rulesets/IRulesetInfo.cs +++ b/osu.Game/Rulesets/IRulesetInfo.cs @@ -11,7 +11,7 @@ namespace osu.Game.Rulesets /// /// A representation of a ruleset's metadata. /// - public interface IRulesetInfo : IHasOnlineID + public interface IRulesetInfo : IHasOnlineID { /// /// The user-exposed name of this ruleset. diff --git a/osu.Game/Rulesets/Mods/ModBarrelRoll.cs b/osu.Game/Rulesets/Mods/ModBarrelRoll.cs index 872daadd46..4acbcf3e74 100644 --- a/osu.Game/Rulesets/Mods/ModBarrelRoll.cs +++ b/osu.Game/Rulesets/Mods/ModBarrelRoll.cs @@ -49,8 +49,8 @@ namespace osu.Game.Rulesets.Mods // scale the playfield to allow all hitobjects to stay within the visible region. var playfieldSize = drawableRuleset.Playfield.DrawSize; - var minSide = MathF.Min(playfieldSize.X, playfieldSize.Y); - var maxSide = MathF.Max(playfieldSize.X, playfieldSize.Y); + float minSide = MathF.Min(playfieldSize.X, playfieldSize.Y); + float maxSide = MathF.Max(playfieldSize.X, playfieldSize.Y); drawableRuleset.Playfield.Scale = new Vector2(minSide / maxSide); } } diff --git a/osu.Game/Rulesets/Objects/BarLineGenerator.cs b/osu.Game/Rulesets/Objects/BarLineGenerator.cs index 9556b52735..e78aa5a5a0 100644 --- a/osu.Game/Rulesets/Objects/BarLineGenerator.cs +++ b/osu.Game/Rulesets/Objects/BarLineGenerator.cs @@ -47,7 +47,7 @@ namespace osu.Game.Rulesets.Objects for (double t = currentTimingPoint.Time; Precision.DefinitelyBigger(endTime, t); t += barLength, currentBeat++) { - var roundedTime = Math.Round(t, MidpointRounding.AwayFromZero); + double roundedTime = Math.Round(t, MidpointRounding.AwayFromZero); // in the case of some bar lengths, rounding errors can cause t to be slightly less than // the expected whole number value due to floating point inaccuracies. diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 29d8a475ef..01817147ae 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -568,7 +568,7 @@ namespace osu.Game.Rulesets.Objects.Drawables if (Result != null && Result.HasResult) { - var endTime = HitObject.GetEndTime(); + double endTime = HitObject.GetEndTime(); if (Result.TimeOffset + endTime > Time.Current) { diff --git a/osu.Game/Rulesets/Objects/HitObject.cs b/osu.Game/Rulesets/Objects/HitObject.cs index 035ebe10cb..a80b3d0fa5 100644 --- a/osu.Game/Rulesets/Objects/HitObject.cs +++ b/osu.Game/Rulesets/Objects/HitObject.cs @@ -119,6 +119,8 @@ namespace osu.Game.Rulesets.Objects DifficultyControlPoint = (DifficultyControlPoint)legacyInfo.DifficultyPointAt(StartTime).DeepClone(); DifficultyControlPoint.Time = StartTime; } + else if (DifficultyControlPoint == DifficultyControlPoint.DEFAULT) + DifficultyControlPoint = new DifficultyControlPoint(); ApplyDefaultsToSelf(controlPointInfo, difficulty); @@ -128,6 +130,8 @@ namespace osu.Game.Rulesets.Objects SampleControlPoint = (SampleControlPoint)legacyInfo.SamplePointAt(this.GetEndTime() + control_point_leniency).DeepClone(); SampleControlPoint.Time = this.GetEndTime() + control_point_leniency; } + else if (SampleControlPoint == SampleControlPoint.DEFAULT) + SampleControlPoint = new SampleControlPoint(); nestedHitObjects.Clear(); diff --git a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHitObjectParser.cs index c29179f749..2beb6bdbf2 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHitObjectParser.cs @@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Catch } protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, PathControlPoint[] controlPoints, double? length, int repeatCount, - List> nodeSamples) + IList> nodeSamples) { newCombo |= forceNewCombo; comboOffset += extraComboOffset; diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs index 0942a7264d..cf19d64080 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs @@ -130,7 +130,7 @@ namespace osu.Game.Rulesets.Objects.Legacy if (i >= adds.Length) break; - int.TryParse(adds[i], out var sound); + int.TryParse(adds[i], out int sound); nodeSoundTypes[i] = (LegacyHitSoundType)sound; } } @@ -408,7 +408,7 @@ namespace osu.Game.Rulesets.Objects.Legacy /// The samples to be played when the slider nodes are hit. This includes the head and tail of the slider. /// The hit object. protected abstract HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, PathControlPoint[] controlPoints, double? length, int repeatCount, - List> nodeSamples); + IList> nodeSamples); /// /// Creates a legacy Spinner-type hit object. @@ -481,7 +481,7 @@ namespace osu.Game.Rulesets.Objects.Legacy /// /// /// Layered hit samples are automatically added in all modes (except osu!mania), but can be disabled - /// using the skin config option. + /// using the skin config option. /// public readonly bool IsLayered; diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs index ad191f7ff5..9ff92fcc75 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs @@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Objects.Legacy public double Distance => Path.Distance; - public List> NodeSamples { get; set; } + public IList> NodeSamples { get; set; } public int RepeatCount { get; set; } [JsonIgnore] diff --git a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHitObjectParser.cs index bc64518f40..386eb8d3ee 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHitObjectParser.cs @@ -26,7 +26,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Mania } protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, PathControlPoint[] controlPoints, double? length, int repeatCount, - List> nodeSamples) + IList> nodeSamples) { return new ConvertSlider { diff --git a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHitObjectParser.cs index 75ecab0b8f..cb98721be5 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHitObjectParser.cs @@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Osu } protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, PathControlPoint[] controlPoints, double? length, int repeatCount, - List> nodeSamples) + IList> nodeSamples) { newCombo |= forceNewCombo; comboOffset += extraComboOffset; diff --git a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHitObjectParser.cs index 13e3e84c6a..1eafc4e68b 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHitObjectParser.cs @@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Taiko } protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, PathControlPoint[] controlPoints, double? length, int repeatCount, - List> nodeSamples) + IList> nodeSamples) { return new ConvertSlider { diff --git a/osu.Game/Rulesets/Objects/SliderEventGenerator.cs b/osu.Game/Rulesets/Objects/SliderEventGenerator.cs index ba38c7f77d..bae5a5e8d9 100644 --- a/osu.Game/Rulesets/Objects/SliderEventGenerator.cs +++ b/osu.Game/Rulesets/Objects/SliderEventGenerator.cs @@ -18,10 +18,10 @@ namespace osu.Game.Rulesets.Objects // This exists for edge cases such as /b/1573664 where the beatmap has been edited by the user, and should never be reached in normal usage. const double max_length = 100000; - var length = Math.Min(max_length, totalDistance); + double length = Math.Min(max_length, totalDistance); tickDistance = Math.Clamp(tickDistance, 0, length); - var minDistanceFromEnd = velocity * 10; + double minDistanceFromEnd = velocity * 10; yield return new SliderEventDescriptor { @@ -34,10 +34,10 @@ namespace osu.Game.Rulesets.Objects if (tickDistance != 0) { - for (var span = 0; span < spanCount; span++) + for (int span = 0; span < spanCount; span++) { - var spanStartTime = startTime + span * spanDuration; - var reversed = span % 2 == 1; + double spanStartTime = startTime + span * spanDuration; + bool reversed = span % 2 == 1; var ticks = generateTicks(span, spanStartTime, spanDuration, reversed, length, tickDistance, minDistanceFromEnd, cancellationToken); @@ -115,7 +115,7 @@ namespace osu.Game.Rulesets.Objects private static IEnumerable generateTicks(int spanIndex, double spanStartTime, double spanDuration, bool reversed, double length, double tickDistance, double minDistanceFromEnd, CancellationToken cancellationToken = default) { - for (var d = tickDistance; d <= length; d += tickDistance) + for (double d = tickDistance; d <= length; d += tickDistance) { cancellationToken.ThrowIfCancellationRequested(); @@ -123,8 +123,8 @@ namespace osu.Game.Rulesets.Objects break; // Always generate ticks from the start of the path rather than the span to ensure that ticks in repeat spans are positioned identically to those in non-repeat spans - var pathProgress = d / length; - var timeProgress = reversed ? 1 - pathProgress : pathProgress; + double pathProgress = d / length; + double timeProgress = reversed ? 1 - pathProgress : pathProgress; yield return new SliderEventDescriptor { diff --git a/osu.Game/Rulesets/Objects/SliderPath.cs b/osu.Game/Rulesets/Objects/SliderPath.cs index 9cc215589b..0dec0655b9 100644 --- a/osu.Game/Rulesets/Objects/SliderPath.cs +++ b/osu.Game/Rulesets/Objects/SliderPath.cs @@ -280,6 +280,13 @@ namespace osu.Game.Rulesets.Objects if (ExpectedDistance.Value is double expectedDistance && calculatedLength != expectedDistance) { + // In osu-stable, if the last two control points of a slider are equal, extension is not performed. + if (ControlPoints.Count >= 2 && ControlPoints[^1].Position == ControlPoints[^2].Position && expectedDistance > calculatedLength) + { + cumulativeLength.Add(calculatedLength); + return; + } + // The last length is always incorrect cumulativeLength.RemoveAt(cumulativeLength.Count - 1); diff --git a/osu.Game/Rulesets/Objects/SliderPathExtensions.cs b/osu.Game/Rulesets/Objects/SliderPathExtensions.cs index 663746bfca..1308fff7ae 100644 --- a/osu.Game/Rulesets/Objects/SliderPathExtensions.cs +++ b/osu.Game/Rulesets/Objects/SliderPathExtensions.cs @@ -19,20 +19,23 @@ namespace osu.Game.Rulesets.Objects public static void Reverse(this SliderPath sliderPath, out Vector2 positionalOffset) { var points = sliderPath.ControlPoints.ToArray(); - positionalOffset = points.Last().Position; + positionalOffset = sliderPath.PositionAt(1); sliderPath.ControlPoints.Clear(); PathType? lastType = null; - for (var i = 0; i < points.Length; i++) + for (int i = 0; i < points.Length; i++) { var p = points[i]; p.Position -= positionalOffset; // propagate types forwards to last null type if (i == points.Length - 1) + { p.Type = lastType; + p.Position = Vector2.Zero; + } else if (p.Type != null) (p.Type, lastType) = (lastType, p.Type); diff --git a/osu.Game/Rulesets/Objects/Types/IHasRepeats.cs b/osu.Game/Rulesets/Objects/Types/IHasRepeats.cs index 674e2aee88..2a4215b960 100644 --- a/osu.Game/Rulesets/Objects/Types/IHasRepeats.cs +++ b/osu.Game/Rulesets/Objects/Types/IHasRepeats.cs @@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Objects.Types /// n-1: The last repeat.
/// n: The last node. ///
- List> NodeSamples { get; } + IList> NodeSamples { get; } } public static class HasRepeatsExtensions diff --git a/osu.Game/Rulesets/RulesetInfo.cs b/osu.Game/Rulesets/RulesetInfo.cs index 8cd3fa8c63..8083041a3b 100644 --- a/osu.Game/Rulesets/RulesetInfo.cs +++ b/osu.Game/Rulesets/RulesetInfo.cs @@ -45,7 +45,7 @@ namespace osu.Game.Rulesets { unchecked { - var hashCode = ID.HasValue ? ID.GetHashCode() : 0; + int hashCode = ID.HasValue ? ID.GetHashCode() : 0; hashCode = (hashCode * 397) ^ (InstantiationInfo != null ? InstantiationInfo.GetHashCode() : 0); hashCode = (hashCode * 397) ^ (Name != null ? Name.GetHashCode() : 0); hashCode = (hashCode * 397) ^ Available.GetHashCode(); diff --git a/osu.Game/Rulesets/RulesetStore.cs b/osu.Game/Rulesets/RulesetStore.cs index a9e04a02b5..391bf2c07d 100644 --- a/osu.Game/Rulesets/RulesetStore.cs +++ b/osu.Game/Rulesets/RulesetStore.cs @@ -168,7 +168,7 @@ namespace osu.Game.Rulesets var rulesets = rulesetStorage.GetFiles(".", $"{ruleset_library_prefix}.*.dll"); - foreach (var ruleset in rulesets.Where(f => !f.Contains("Tests"))) + foreach (string ruleset in rulesets.Where(f => !f.Contains("Tests"))) loadRulesetFromFile(rulesetStorage.GetFullPath(ruleset)); } @@ -176,7 +176,7 @@ namespace osu.Game.Rulesets { try { - var files = Directory.GetFiles(RuntimeInfo.StartupDirectory, $"{ruleset_library_prefix}.*.dll"); + string[] files = Directory.GetFiles(RuntimeInfo.StartupDirectory, $"{ruleset_library_prefix}.*.dll"); foreach (string file in files.Where(f => !Path.GetFileName(f).Contains("Tests"))) loadRulesetFromFile(file); @@ -189,7 +189,7 @@ namespace osu.Game.Rulesets private void loadRulesetFromFile(string file) { - var filename = Path.GetFileNameWithoutExtension(file); + string filename = Path.GetFileNameWithoutExtension(file); if (loadedAssemblies.Values.Any(t => Path.GetFileNameWithoutExtension(t.Assembly.Location) == filename)) return; diff --git a/osu.Game/Rulesets/Scoring/HitWindows.cs b/osu.Game/Rulesets/Scoring/HitWindows.cs index 3ffd1eb66b..2d008b58ba 100644 --- a/osu.Game/Rulesets/Scoring/HitWindows.cs +++ b/osu.Game/Rulesets/Scoring/HitWindows.cs @@ -86,7 +86,7 @@ namespace osu.Game.Rulesets.Scoring { foreach (var range in GetRanges()) { - var value = IBeatmapDifficultyInfo.DifficultyRange(difficulty, (range.Min, range.Average, range.Max)); + double value = IBeatmapDifficultyInfo.DifficultyRange(difficulty, (range.Min, range.Average, range.Max)); switch (range.Result) { diff --git a/osu.Game/Rulesets/UI/Scrolling/Algorithms/ConstantScrollAlgorithm.cs b/osu.Game/Rulesets/UI/Scrolling/Algorithms/ConstantScrollAlgorithm.cs index 0d4283e319..ab6e07f424 100644 --- a/osu.Game/Rulesets/UI/Scrolling/Algorithms/ConstantScrollAlgorithm.cs +++ b/osu.Game/Rulesets/UI/Scrolling/Algorithms/ConstantScrollAlgorithm.cs @@ -7,7 +7,7 @@ namespace osu.Game.Rulesets.UI.Scrolling.Algorithms { public double GetDisplayStartTime(double originTime, float offset, double timeRange, float scrollLength) { - var adjustedTime = TimeAt(-offset, originTime, timeRange, scrollLength); + double adjustedTime = TimeAt(-offset, originTime, timeRange, scrollLength); return adjustedTime - timeRange; } diff --git a/osu.Game/Rulesets/UI/Scrolling/Algorithms/SequentialScrollAlgorithm.cs b/osu.Game/Rulesets/UI/Scrolling/Algorithms/SequentialScrollAlgorithm.cs index a1f68d7201..45d3b3bcd4 100644 --- a/osu.Game/Rulesets/UI/Scrolling/Algorithms/SequentialScrollAlgorithm.cs +++ b/osu.Game/Rulesets/UI/Scrolling/Algorithms/SequentialScrollAlgorithm.cs @@ -32,7 +32,7 @@ namespace osu.Game.Rulesets.UI.Scrolling.Algorithms public float GetLength(double startTime, double endTime, double timeRange, float scrollLength) { - var objectLength = relativePositionAt(endTime, timeRange) - relativePositionAt(startTime, timeRange); + double objectLength = relativePositionAt(endTime, timeRange) - relativePositionAt(startTime, timeRange); return (float)(objectLength * scrollLength); } @@ -90,7 +90,7 @@ namespace osu.Game.Rulesets.UI.Scrolling.Algorithms { generatePositionMappings(timeRange); - var mappingIndex = positionMappings.BinarySearch(search, comparer ?? Comparer.Default); + int mappingIndex = positionMappings.BinarySearch(search, comparer ?? Comparer.Default); if (mappingIndex < 0) { diff --git a/osu.Game/Scoring/IScoreInfo.cs b/osu.Game/Scoring/IScoreInfo.cs new file mode 100644 index 0000000000..77579f23d9 --- /dev/null +++ b/osu.Game/Scoring/IScoreInfo.cs @@ -0,0 +1,39 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Game.Beatmaps; +using osu.Game.Database; +using osu.Game.Rulesets; +using osu.Game.Users; + +namespace osu.Game.Scoring +{ + public interface IScoreInfo : IHasOnlineID + { + User User { get; } + + long TotalScore { get; } + + int MaxCombo { get; } + + double Accuracy { get; } + + bool HasReplay { get; } + + DateTimeOffset Date { get; } + + double? PP { get; } + + IBeatmapInfo Beatmap { get; } + + IRulesetInfo Ruleset { get; } + + ScoreRank Rank { get; } + + // Mods is currently missing from this interface as the `IMod` class has properties which can't be fulfilled by `APIMod`, + // but also doesn't expose `Settings`. We can consider how to implement this in the future if required. + + // Statistics is also missing. This can be reconsidered once changes in serialisation have been completed. + } +} diff --git a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs index a1658b4cf3..379718195c 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs @@ -39,7 +39,7 @@ namespace osu.Game.Scoring.Legacy score.ScoreInfo = scoreInfo; - var version = sr.ReadInt32(); + int version = sr.ReadInt32(); workingBeatmap = GetBeatmap(sr.ReadString()); if (workingBeatmap is DummyWorkingBeatmap) @@ -77,7 +77,7 @@ namespace osu.Game.Scoring.Legacy scoreInfo.Date = sr.ReadDateTime(); - var compressedReplay = sr.ReadByteArray(); + byte[] compressedReplay = sr.ReadByteArray(); if (version >= 20140721) scoreInfo.OnlineScoreID = sr.ReadInt64(); @@ -228,11 +228,11 @@ namespace osu.Game.Scoring.Legacy float lastTime = 0; ReplayFrame currentFrame = null; - var frames = reader.ReadToEnd().Split(','); + string[] frames = reader.ReadToEnd().Split(','); - for (var i = 0; i < frames.Length; i++) + for (int i = 0; i < frames.Length; i++) { - var split = frames[i].Split('|'); + string[] split = frames[i].Split('|'); if (split.Length < 4) continue; @@ -243,9 +243,9 @@ namespace osu.Game.Scoring.Legacy continue; } - var diff = Parsing.ParseFloat(split[0]); - var mouseX = Parsing.ParseFloat(split[1], Parsing.MAX_COORDINATE_VALUE); - var mouseY = Parsing.ParseFloat(split[2], Parsing.MAX_COORDINATE_VALUE); + float diff = Parsing.ParseFloat(split[0]); + float mouseX = Parsing.ParseFloat(split[1], Parsing.MAX_COORDINATE_VALUE); + float mouseY = Parsing.ParseFloat(split[2], Parsing.MAX_COORDINATE_VALUE); lastTime += diff; diff --git a/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs b/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs index 58e4192f77..5769406948 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs @@ -72,7 +72,7 @@ namespace osu.Game.Scoring.Legacy private byte[] createReplayData() { - var content = new ASCIIEncoding().GetBytes(replayStringContent); + byte[] content = new ASCIIEncoding().GetBytes(replayStringContent); using (var outStream = new MemoryStream()) { diff --git a/osu.Game/Scoring/Legacy/ScoreInfoExtensions.cs b/osu.Game/Scoring/Legacy/ScoreInfoExtensions.cs index b58f65800d..fc27261225 100644 --- a/osu.Game/Scoring/Legacy/ScoreInfoExtensions.cs +++ b/osu.Game/Scoring/Legacy/ScoreInfoExtensions.cs @@ -130,7 +130,7 @@ namespace osu.Game.Scoring.Legacy private static int? getCount(ScoreInfo scoreInfo, HitResult result) { - if (scoreInfo.Statistics.TryGetValue(result, out var existing)) + if (scoreInfo.Statistics.TryGetValue(result, out int existing)) return existing; return null; diff --git a/osu.Game/Scoring/LegacyDatabasedScore.cs b/osu.Game/Scoring/LegacyDatabasedScore.cs index 8908775472..81892f65d0 100644 --- a/osu.Game/Scoring/LegacyDatabasedScore.cs +++ b/osu.Game/Scoring/LegacyDatabasedScore.cs @@ -16,7 +16,7 @@ namespace osu.Game.Scoring { ScoreInfo = score; - var replayFilename = score.Files.FirstOrDefault(f => f.Filename.EndsWith(".osr", StringComparison.InvariantCultureIgnoreCase))?.FileInfo.StoragePath; + string replayFilename = score.Files.FirstOrDefault(f => f.Filename.EndsWith(".osr", StringComparison.InvariantCultureIgnoreCase))?.FileInfo.StoragePath; if (replayFilename == null) return; diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index 5cf22f7945..36608e2a74 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -6,7 +6,6 @@ using System.Collections.Generic; using System.ComponentModel.DataAnnotations.Schema; using System.Linq; using Newtonsoft.Json; -using Newtonsoft.Json.Converters; using osu.Framework.Localisation; using osu.Game.Beatmaps; using osu.Game.Database; @@ -19,47 +18,36 @@ using osu.Game.Utils; namespace osu.Game.Scoring { - public class ScoreInfo : IHasFiles, IHasPrimaryKey, ISoftDelete, IEquatable, IDeepCloneable + public class ScoreInfo : IScoreInfo, IHasFiles, IHasPrimaryKey, ISoftDelete, IEquatable, IDeepCloneable { public int ID { get; set; } - [JsonProperty("rank")] - [JsonConverter(typeof(StringEnumConverter))] public ScoreRank Rank { get; set; } - [JsonProperty("total_score")] public long TotalScore { get; set; } - [JsonProperty("accuracy")] [Column(TypeName = "DECIMAL(1,4)")] // TODO: This data type is wrong (should contain more precision). But at the same time, we probably don't need to be storing this in the database. public double Accuracy { get; set; } - [JsonIgnore] public LocalisableString DisplayAccuracy => Accuracy.FormatAccuracy(); - [JsonProperty(@"pp")] public double? PP { get; set; } - [JsonProperty("max_combo")] public int MaxCombo { get; set; } - [JsonIgnore] public int Combo { get; set; } // Todo: Shouldn't exist in here - [JsonProperty("ruleset_id")] public int RulesetID { get; set; } - [JsonProperty("passed")] [NotMapped] public bool Passed { get; set; } = true; - [JsonIgnore] - public virtual RulesetInfo Ruleset { get; set; } + public RulesetInfo Ruleset { get; set; } private APIMod[] localAPIMods; + private Mod[] mods; - [JsonIgnore] [NotMapped] public Mod[] Mods { @@ -74,7 +62,7 @@ namespace osu.Game.Scoring if (mods != null) scoreMods = mods; else if (localAPIMods != null) - scoreMods = apiMods.Select(m => m.ToMod(rulesetInstance)).ToArray(); + scoreMods = APIMods.Select(m => m.ToMod(rulesetInstance)).ToArray(); return scoreMods; } @@ -86,9 +74,8 @@ namespace osu.Game.Scoring } // Used for API serialisation/deserialisation. - [JsonProperty("mods")] [NotMapped] - private APIMod[] apiMods + public APIMod[] APIMods { get { @@ -110,19 +97,16 @@ namespace osu.Game.Scoring } // Used for database serialisation/deserialisation. - [JsonIgnore] [Column("Mods")] public string ModsJson { - get => JsonConvert.SerializeObject(apiMods); - set => apiMods = JsonConvert.DeserializeObject(value); + get => JsonConvert.SerializeObject(APIMods); + set => APIMods = JsonConvert.DeserializeObject(value); } [NotMapped] - [JsonProperty("user")] public User User { get; set; } - [JsonIgnore] [Column("User")] public string UserString { @@ -134,7 +118,6 @@ namespace osu.Game.Scoring } } - [JsonIgnore] [Column("UserID")] public int? UserID { @@ -146,23 +129,18 @@ namespace osu.Game.Scoring } } - [JsonIgnore] public int BeatmapInfoID { get; set; } - [JsonIgnore] [Column("Beatmap")] - public virtual BeatmapInfo BeatmapInfo { get; set; } + public BeatmapInfo BeatmapInfo { get; set; } - [JsonIgnore] public long? OnlineScoreID { get; set; } - [JsonIgnore] public DateTimeOffset Date { get; set; } - [JsonProperty("statistics")] - public Dictionary Statistics = new Dictionary(); + [NotMapped] + public Dictionary Statistics { get; set; } = new Dictionary(); - [JsonIgnore] [Column("Statistics")] public string StatisticsJson { @@ -180,29 +158,23 @@ namespace osu.Game.Scoring } [NotMapped] - [JsonIgnore] public List HitEvents { get; set; } - [JsonIgnore] public List Files { get; set; } - [JsonIgnore] public string Hash { get; set; } - [JsonIgnore] public bool DeletePending { get; set; } /// /// The position of this score, starting at 1. /// [NotMapped] - [JsonProperty("position")] - public int? Position { get; set; } + public int? Position { get; set; } // TODO: remove after all calls to `CreateScoreInfo` are gone. /// /// Whether this represents a legacy (osu!stable) score. /// - [JsonIgnore] [NotMapped] public bool IsLegacyScore => Mods.OfType().Any(); @@ -271,5 +243,19 @@ namespace osu.Game.Scoring return ReferenceEquals(this, other); } + + #region Implementation of IHasOnlineID + + public long OnlineID => OnlineScoreID ?? -1; + + #endregion + + #region Implementation of IScoreInfo + + IBeatmapInfo IScoreInfo.Beatmap => BeatmapInfo; + IRulesetInfo IScoreInfo.Ruleset => Ruleset; + bool IScoreInfo.HasReplay => Files.Any(); + + #endregion } } diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index a9791fba7e..253591eb56 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -72,7 +72,7 @@ namespace osu.Game.Scoring } } - var totalScores = await Task.WhenAll(scores.Select(s => GetTotalScoreAsync(s, cancellationToken: cancellationToken))).ConfigureAwait(false); + long[] totalScores = await Task.WhenAll(scores.Select(s => GetTotalScoreAsync(s, cancellationToken: cancellationToken))).ConfigureAwait(false); return scores.Select((score, index) => (score, totalScore: totalScores[index])) .OrderByDescending(g => g.totalScore) diff --git a/osu.Game/Scoring/ScoreModelDownloader.cs b/osu.Game/Scoring/ScoreModelDownloader.cs index b3c1e2928a..52355585a9 100644 --- a/osu.Game/Scoring/ScoreModelDownloader.cs +++ b/osu.Game/Scoring/ScoreModelDownloader.cs @@ -16,5 +16,8 @@ namespace osu.Game.Scoring } protected override ArchiveDownloadRequest CreateDownloadRequest(ScoreInfo score, bool minimiseDownload) => new DownloadReplayRequest(score); + + public override ArchiveDownloadRequest GetExistingDownload(ScoreInfo model) + => CurrentDownloads.Find(r => r.Model.OnlineScoreID == model.OnlineScoreID); } } diff --git a/osu.Game/Screens/Edit/BindableBeatDivisor.cs b/osu.Game/Screens/Edit/BindableBeatDivisor.cs index dfe2992a7c..1a350d7261 100644 --- a/osu.Game/Screens/Edit/BindableBeatDivisor.cs +++ b/osu.Game/Screens/Edit/BindableBeatDivisor.cs @@ -92,7 +92,7 @@ namespace osu.Game.Screens.Edit { int beat = index % beatDivisor; - foreach (var divisor in BindableBeatDivisor.VALID_DIVISORS) + foreach (int divisor in BindableBeatDivisor.VALID_DIVISORS) { if ((beat * divisor) % beatDivisor == 0) return divisor; diff --git a/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs b/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs index 2dec3fd22e..de63b265d2 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs @@ -205,7 +205,7 @@ namespace osu.Game.Screens.Edit.Compose.Components [BackgroundDependencyLoader] private void load() { - foreach (var t in availableDivisors) + foreach (int t in availableDivisors) { AddInternal(new Tick { @@ -287,7 +287,7 @@ namespace osu.Game.Screens.Edit.Compose.Components private void handleMouseInput(Vector2 screenSpaceMousePosition) { // copied from SliderBar so we can do custom spacing logic. - var xPosition = (ToLocalSpace(screenSpaceMousePosition).X - RangePadding) / UsableWidth; + float xPosition = (ToLocalSpace(screenSpaceMousePosition).X - RangePadding) / UsableWidth; CurrentNumber.Value = availableDivisors.OrderBy(d => Math.Abs(getMappedPosition(d) - xPosition)).First(); OnUserChange(Current.Value); diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index 75d4d13f94..d7d4642a39 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -58,13 +58,13 @@ namespace osu.Game.Screens.Edit.Compose.Components switch (args.Action) { case NotifyCollectionChangedAction.Add: - foreach (var o in args.NewItems) + foreach (object o in args.NewItems) SelectionBlueprints.FirstOrDefault(b => b.Item == o)?.Select(); break; case NotifyCollectionChangedAction.Remove: - foreach (var o in args.OldItems) + foreach (object o in args.OldItems) SelectionBlueprints.FirstOrDefault(b => b.Item == o)?.Deselect(); break; @@ -468,7 +468,7 @@ namespace osu.Game.Screens.Edit.Compose.Components if (snapProvider != null) { // check for positional snap for every object in selection (for things like object-object snapping) - for (var i = 0; i < movementBlueprintOriginalPositions.Length; i++) + for (int i = 0; i < movementBlueprintOriginalPositions.Length; i++) { Vector2 originalPosition = movementBlueprintOriginalPositions[i]; var testPosition = originalPosition + distanceTravelled; diff --git a/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs index 9d43e3258a..05bf405f3c 100644 --- a/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs @@ -132,8 +132,8 @@ namespace osu.Game.Screens.Edit.Compose.Components protected ColourInfo GetColourForIndexFromPlacement(int placementIndex) { var timingPoint = beatmap.ControlPointInfo.TimingPointAt(StartTime); - var beatLength = timingPoint.BeatLength / beatDivisor.Value; - var beatIndex = (int)Math.Round((StartTime - timingPoint.Time) / beatLength); + double beatLength = timingPoint.BeatLength / beatDivisor.Value; + int beatIndex = (int)Math.Round((StartTime - timingPoint.Time) / beatLength); var colour = BindableBeatDivisor.GetColourFor(BindableBeatDivisor.GetDivisorForBeatIndex(beatIndex + placementIndex + 1, beatDivisor.Value), Colours); diff --git a/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs index 246d4aa8d7..5f6e8de557 100644 --- a/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs @@ -55,7 +55,7 @@ namespace osu.Game.Screens.Edit.Compose.Components /// private void createStateBindables() { - foreach (var sampleName in HitSampleInfo.AllAdditions) + foreach (string sampleName in HitSampleInfo.AllAdditions) { var bindable = new Bindable { @@ -102,7 +102,7 @@ namespace osu.Game.Screens.Edit.Compose.Components { SelectionNewComboState.Value = GetStateFromSelection(SelectedItems.OfType(), h => h.NewCombo); - foreach (var (sampleName, bindable) in SelectionSampleStates) + foreach ((string sampleName, var bindable) in SelectionSampleStates) { bindable.Value = GetStateFromSelection(SelectedItems, h => h.Samples.Any(s => s.Name == sampleName)); } diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs index 73c38ba23f..a9e9ef5001 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs @@ -176,7 +176,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline if (timeline != null) { var timelineQuad = timeline.ScreenSpaceDrawQuad; - var mouseX = e.ScreenSpaceMousePosition.X; + float mouseX = e.ScreenSpaceMousePosition.X; // scroll if in a drag and dragging outside visible extents if (mouseX > timelineQuad.TopRight.X) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs index e2458d45c9..80aa6972b1 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs @@ -397,7 +397,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline if (hitObject.DifficultyControlPoint == DifficultyControlPoint.DEFAULT) hitObject.DifficultyControlPoint = new DifficultyControlPoint(); - var newVelocity = hitObject.DifficultyControlPoint.SliderVelocity * (repeatHitObject.Duration / proposedDuration); + double newVelocity = hitObject.DifficultyControlPoint.SliderVelocity * (repeatHitObject.Duration / proposedDuration); if (Precision.AlmostEquals(newVelocity, hitObject.DifficultyControlPoint.SliderVelocity)) return; @@ -408,8 +408,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline else { // find the number of repeats which can fit in the requested time. - var lengthOfOneRepeat = repeatHitObject.Duration / (repeatHitObject.RepeatCount + 1); - var proposedCount = Math.Max(0, (int)Math.Round(proposedDuration / lengthOfOneRepeat) - 1); + double lengthOfOneRepeat = repeatHitObject.Duration / (repeatHitObject.RepeatCount + 1); + int proposedCount = Math.Max(0, (int)Math.Round(proposedDuration / lengthOfOneRepeat) - 1); if (proposedCount == repeatHitObject.RepeatCount) return; @@ -421,7 +421,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline break; case IHasDuration endTimeHitObject: - var snappedTime = Math.Max(hitObject.StartTime, beatSnapProvider.SnapTime(time)); + double snappedTime = Math.Max(hitObject.StartTime, beatSnapProvider.SnapTime(time)); if (endTimeHitObject.EndTime == snappedTime || Precision.AlmostEquals(snappedTime, hitObject.StartTime, beatmap.GetBeatLengthAtTime(snappedTime))) return; diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs index 3aaf0451c8..1415014e59 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs @@ -104,10 +104,10 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline nextMinTick = null; nextMaxTick = null; - for (var i = 0; i < beatmap.ControlPointInfo.TimingPoints.Count; i++) + for (int i = 0; i < beatmap.ControlPointInfo.TimingPoints.Count; i++) { var point = beatmap.ControlPointInfo.TimingPoints[i]; - var until = i + 1 < beatmap.ControlPointInfo.TimingPoints.Count ? beatmap.ControlPointInfo.TimingPoints[i + 1].Time : working.Value.Track.Length; + double until = i + 1 < beatmap.ControlPointInfo.TimingPoints.Count ? beatmap.ControlPointInfo.TimingPoints[i + 1].Time : working.Value.Track.Length; int beat = 0; @@ -127,7 +127,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline int indexInBar = beat % ((int)point.TimeSignature * beatDivisor.Value); - var divisor = BindableBeatDivisor.GetDivisorForBeatIndex(beat, beatDivisor.Value); + int divisor = BindableBeatDivisor.GetDivisorForBeatIndex(beat, beatDivisor.Value); var colour = BindableBeatDivisor.GetColourFor(divisor, colours); // even though "bar lines" take up the full vertical space, we render them in two pieces because it allows for less anchor/origin churn. diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 512226413b..81b2847443 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -301,7 +301,7 @@ namespace osu.Game.Screens.Edit editorBeatmap.SelectedHitObjects.BindCollectionChanged((_, __) => { - var hasObjects = editorBeatmap.SelectedHitObjects.Count > 0; + bool hasObjects = editorBeatmap.SelectedHitObjects.Count > 0; cutMenuItem.Action.Disabled = !hasObjects; copyMenuItem.Action.Disabled = !hasObjects; diff --git a/osu.Game/Screens/Edit/EditorBeatmap.cs b/osu.Game/Screens/Edit/EditorBeatmap.cs index 2e84ef437a..98fad09192 100644 --- a/osu.Game/Screens/Edit/EditorBeatmap.cs +++ b/osu.Game/Screens/Edit/EditorBeatmap.cs @@ -183,7 +183,7 @@ namespace osu.Game.Screens.Edit public void Add(HitObject hitObject) { // Preserve existing sorting order in the beatmap - var insertionIndex = findInsertionIndex(PlayableBeatmap.HitObjects, hitObject.StartTime); + int insertionIndex = findInsertionIndex(PlayableBeatmap.HitObjects, hitObject.StartTime); Insert(insertionIndex + 1, hitObject); } @@ -332,7 +332,7 @@ namespace osu.Game.Screens.Edit // For now we'll remove and re-add the hitobject. This is not optimal and can be improved if required. mutableHitObjects.Remove(hitObject); - var insertionIndex = findInsertionIndex(PlayableBeatmap.HitObjects, hitObject.StartTime); + int insertionIndex = findInsertionIndex(PlayableBeatmap.HitObjects, hitObject.StartTime); mutableHitObjects.Insert(insertionIndex + 1, hitObject); Update(hitObject); diff --git a/osu.Game/Screens/Edit/EditorChangeHandler.cs b/osu.Game/Screens/Edit/EditorChangeHandler.cs index 2dcb416a03..333c518d3a 100644 --- a/osu.Game/Screens/Edit/EditorChangeHandler.cs +++ b/osu.Game/Screens/Edit/EditorChangeHandler.cs @@ -73,7 +73,7 @@ namespace osu.Game.Screens.Edit using (var sw = new StreamWriter(stream, Encoding.UTF8, 1024, true)) new LegacyBeatmapEncoder(editorBeatmap, editorBeatmap.BeatmapSkin).Encode(sw); - var newState = stream.ToArray(); + byte[] newState = stream.ToArray(); // if the previous state is binary equal we don't need to push a new one, unless this is the initial state. if (savedStates.Count > 0 && newState.SequenceEqual(savedStates[currentState])) return; diff --git a/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs b/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs index d26856365e..3ed2a7efe2 100644 --- a/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs +++ b/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs @@ -82,7 +82,7 @@ namespace osu.Game.Screens.Edit if (toAdd.Count > 0) { IBeatmap newBeatmap = readBeatmap(newState); - foreach (var i in toAdd) + foreach (int i in toAdd) editorBeatmap.Insert(i, newBeatmap.HitObjects[i]); } diff --git a/osu.Game/Screens/Edit/Timing/GroupSection.cs b/osu.Game/Screens/Edit/Timing/GroupSection.cs index 2e2c380d4a..03059ff6e1 100644 --- a/osu.Game/Screens/Edit/Timing/GroupSection.cs +++ b/osu.Game/Screens/Edit/Timing/GroupSection.cs @@ -68,7 +68,7 @@ namespace osu.Game.Screens.Edit.Timing if (!isNew) return; - if (double.TryParse(sender.Text, out var newTime)) + if (double.TryParse(sender.Text, out double newTime)) { changeSelectedGroupTime(newTime); } diff --git a/osu.Game/Screens/Menu/Disclaimer.cs b/osu.Game/Screens/Menu/Disclaimer.cs index 7f34e1e395..b8abc131fd 100644 --- a/osu.Game/Screens/Menu/Disclaimer.cs +++ b/osu.Game/Screens/Menu/Disclaimer.cs @@ -37,7 +37,7 @@ namespace osu.Game.Screens.Menu private readonly Bindable currentUser = new Bindable(); private FillFlowContainer fill; - private readonly List expendableText = new List(); + private readonly List expendableText = new List(); public Disclaimer(OsuScreen nextScreen = null) { @@ -97,7 +97,7 @@ namespace osu.Game.Screens.Menu textFlow.AddText("this is osu!", t => t.Font = t.Font.With(Typeface.Torus, 30, FontWeight.Regular)); - expendableText.AddRange(textFlow.AddText("lazer", t => + expendableText.Add(textFlow.AddText("lazer", t => { t.Font = t.Font.With(Typeface.Torus, 30, FontWeight.Regular); t.Colour = colours.PinkLight; @@ -114,7 +114,7 @@ namespace osu.Game.Screens.Menu t.Font = t.Font.With(Typeface.Torus, 20, FontWeight.SemiBold); t.Colour = colours.Pink; }); - expendableText.AddRange(textFlow.AddText(" coming to osu!", formatRegular)); + expendableText.Add(textFlow.AddText(" coming to osu!", formatRegular)); textFlow.AddText(".", formatRegular); textFlow.NewParagraph(); @@ -152,7 +152,7 @@ namespace osu.Game.Screens.Menu t.Font = t.Font.With(size: 20); t.Origin = Anchor.Centre; t.Colour = colours.Pink; - }).First(); + }).Drawables.First(); if (IsLoaded) animateHeart(); @@ -193,7 +193,7 @@ namespace osu.Game.Screens.Menu using (BeginDelayedSequence(520 + 160)) { fill.MoveToOffset(new Vector2(0, 15), 160, Easing.OutQuart); - Schedule(() => expendableText.ForEach(t => + Schedule(() => expendableText.SelectMany(t => t.Drawables).ForEach(t => { t.FadeOut(100); t.ScaleTo(new Vector2(0, 1), 100, Easing.OutQuart); diff --git a/osu.Game/Screens/Menu/IntroScreen.cs b/osu.Game/Screens/Menu/IntroScreen.cs index 32fb9f1d6d..eb8d3dfea6 100644 --- a/osu.Game/Screens/Menu/IntroScreen.cs +++ b/osu.Game/Screens/Menu/IntroScreen.cs @@ -211,7 +211,7 @@ namespace osu.Game.Screens.Menu else { const int quick_appear = 350; - var initialMovementTime = logo.Alpha > 0.2f ? quick_appear : 0; + int initialMovementTime = logo.Alpha > 0.2f ? quick_appear : 0; logo.MoveTo(new Vector2(0.5f), initialMovementTime, Easing.OutQuint); diff --git a/osu.Game/Screens/Menu/OsuLogo.cs b/osu.Game/Screens/Menu/OsuLogo.cs index a9376325cd..f9388097ac 100644 --- a/osu.Game/Screens/Menu/OsuLogo.cs +++ b/osu.Game/Screens/Menu/OsuLogo.cs @@ -272,7 +272,7 @@ namespace osu.Game.Screens.Menu lastBeatIndex = beatIndex; - var beatLength = timingPoint.BeatLength; + double beatLength = timingPoint.BeatLength; float amplitudeAdjust = Math.Min(1, 0.4f + amplitudes.Maximum); @@ -337,7 +337,7 @@ namespace osu.Game.Screens.Menu if (musicController.CurrentTrack.IsRunning) { - var maxAmplitude = lastBeatIndex >= 0 ? musicController.CurrentTrack.CurrentAmplitudes.Maximum : 0; + float maxAmplitude = lastBeatIndex >= 0 ? musicController.CurrentTrack.CurrentAmplitudes.Maximum : 0; logoAmplitudeContainer.Scale = new Vector2((float)Interpolation.Damp(logoAmplitudeContainer.Scale.X, 1 - Math.Max(0, maxAmplitude - scale_adjust_cutoff) * 0.04f, 0.9f, Time.Elapsed)); if (maxAmplitude > velocity_adjust_cutoff) diff --git a/osu.Game/Screens/OnlinePlay/Components/BeatmapTypeInfo.cs b/osu.Game/Screens/OnlinePlay/Components/BeatmapTypeInfo.cs deleted file mode 100644 index 3aa13458a4..0000000000 --- a/osu.Game/Screens/OnlinePlay/Components/BeatmapTypeInfo.cs +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using System.Linq; -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Game.Graphics; -using osu.Game.Graphics.Containers; -using osuTK; - -namespace osu.Game.Screens.OnlinePlay.Components -{ - public class BeatmapTypeInfo : OnlinePlayComposite - { - private LinkFlowContainer beatmapAuthor; - - public BeatmapTypeInfo() - { - AutoSizeAxes = Axes.Both; - } - - [BackgroundDependencyLoader] - private void load() - { - InternalChild = new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - LayoutDuration = 100, - Spacing = new Vector2(5, 0), - Children = new Drawable[] - { - new ModeTypeInfo(), - new Container - { - AutoSizeAxes = Axes.X, - Height = 30, - Margin = new MarginPadding { Left = 5 }, - Children = new Drawable[] - { - new BeatmapTitle(), - beatmapAuthor = new LinkFlowContainer(s => s.Font = s.Font.With(size: 14)) - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - AutoSizeAxes = Axes.Both - }, - }, - }, - } - }; - - Playlist.CollectionChanged += (_, __) => updateInfo(); - - updateInfo(); - } - - private void updateInfo() - { - beatmapAuthor.Clear(); - - var beatmap = Playlist.FirstOrDefault()?.Beatmap; - - if (beatmap != null) - { - beatmapAuthor.AddText("mapped by ", s => s.Colour = OsuColour.Gray(0.8f)); - beatmapAuthor.AddUserLink(beatmap.Value.Metadata.Author); - } - } - } -} diff --git a/osu.Game/Screens/OnlinePlay/Components/OnlinePlayBackgroundScreen.cs b/osu.Game/Screens/OnlinePlay/Components/OnlinePlayBackgroundScreen.cs index 8b6077b9f2..e2088c77d5 100644 --- a/osu.Game/Screens/OnlinePlay/Components/OnlinePlayBackgroundScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Components/OnlinePlayBackgroundScreen.cs @@ -95,7 +95,7 @@ namespace osu.Game.Screens.OnlinePlay.Components public override bool OnExiting(IScreen next) { - var result = base.OnExiting(next); + bool result = base.OnExiting(next); this.MoveToX(0); return result; } diff --git a/osu.Game/Screens/OnlinePlay/Components/ReadyButton.cs b/osu.Game/Screens/OnlinePlay/Components/ReadyButton.cs index 8f85608b29..5a4a0a0fba 100644 --- a/osu.Game/Screens/OnlinePlay/Components/ReadyButton.cs +++ b/osu.Game/Screens/OnlinePlay/Components/ReadyButton.cs @@ -3,13 +3,15 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Graphics.Cursor; +using osu.Framework.Localisation; using osu.Game.Graphics.UserInterface; using osu.Game.Online; using osu.Game.Online.Rooms; namespace osu.Game.Screens.OnlinePlay.Components { - public abstract class ReadyButton : TriangleButton + public abstract class ReadyButton : TriangleButton, IHasTooltip { public new readonly BindableBool Enabled = new BindableBool(); @@ -24,6 +26,18 @@ namespace osu.Game.Screens.OnlinePlay.Components Enabled.BindValueChanged(_ => updateState(), true); } - private void updateState() => base.Enabled.Value = availability.Value.State == DownloadState.LocallyAvailable && Enabled.Value; + private void updateState() => + base.Enabled.Value = availability.Value.State == DownloadState.LocallyAvailable && Enabled.Value; + + public virtual LocalisableString TooltipText + { + get + { + if (Enabled.Value) + return string.Empty; + + return "Beatmap not downloaded"; + } + } } } diff --git a/osu.Game/Screens/OnlinePlay/Components/RoomLocalUserInfo.cs b/osu.Game/Screens/OnlinePlay/Components/RoomLocalUserInfo.cs index 1fcf7f2277..3bad6cb183 100644 --- a/osu.Game/Screens/OnlinePlay/Components/RoomLocalUserInfo.cs +++ b/osu.Game/Screens/OnlinePlay/Components/RoomLocalUserInfo.cs @@ -14,6 +14,9 @@ namespace osu.Game.Screens.OnlinePlay.Components { private OsuSpriteText attemptDisplay; + [Resolved] + private OsuColour colours { get; set; } + public RoomLocalUserInfo() { AutoSizeAxes = Axes.Both; @@ -54,6 +57,9 @@ namespace osu.Game.Screens.OnlinePlay.Components { int remaining = MaxAttempts.Value.Value - UserScore.Value.PlaylistItemAttempts.Sum(a => a.Attempts); attemptDisplay.Text += $" ({remaining} remaining)"; + + if (remaining == 0) + attemptDisplay.Colour = colours.RedLight; } } else diff --git a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs index 585b024623..69ab7225ac 100644 --- a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs +++ b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs @@ -248,10 +248,7 @@ namespace osu.Game.Screens.OnlinePlay protected virtual IEnumerable CreateButtons() => new Drawable[] { - new PlaylistDownloadButton(Item) - { - Size = new Vector2(50, 30) - }, + new PlaylistDownloadButton(Item), new PlaylistRemoveButton { Size = new Vector2(30, 30), @@ -282,28 +279,33 @@ namespace osu.Game.Screens.OnlinePlay return true; } - private class PlaylistDownloadButton : BeatmapPanelDownloadButton + private sealed class PlaylistDownloadButton : BeatmapPanelDownloadButton { private readonly PlaylistItem playlistItem; [Resolved] private BeatmapManager beatmapManager { get; set; } - public override bool IsPresent => base.IsPresent || Scheduler.HasPendingTasks; + // required for download tracking, as this button hides itself. can probably be removed with a bit of consideration. + public override bool IsPresent => true; + + private const float width = 50; public PlaylistDownloadButton(PlaylistItem playlistItem) : base(playlistItem.Beatmap.Value.BeatmapSet) { this.playlistItem = playlistItem; + + Size = new Vector2(width, 30); Alpha = 0; } protected override void LoadComplete() { - base.LoadComplete(); - State.BindValueChanged(stateChanged, true); - FinishTransforms(true); + + // base implementation calls FinishTransforms, so should be run after the above state update. + base.LoadComplete(); } private void stateChanged(ValueChangedEvent state) @@ -315,12 +317,16 @@ namespace osu.Game.Screens.OnlinePlay if (beatmapManager.QueryBeatmap(b => b.MD5Hash == playlistItem.Beatmap.Value.MD5Hash) == null) State.Value = DownloadState.NotDownloaded; else - this.FadeTo(0, 500); + { + this.FadeTo(0, 500) + .ResizeWidthTo(0, 500, Easing.OutQuint); + } break; default: - this.FadeTo(1, 500); + this.ResizeWidthTo(width, 500, Easing.OutQuint) + .FadeTo(1, 500); break; } } @@ -333,13 +339,14 @@ namespace osu.Game.Screens.OnlinePlay public PanelBackground() { + UpdateableBeatmapBackgroundSprite backgroundSprite; + InternalChildren = new Drawable[] { - new UpdateableBeatmapBackgroundSprite + backgroundSprite = new UpdateableBeatmapBackgroundSprite { RelativeSizeAxes = Axes.Both, FillMode = FillMode.Fill, - Beatmap = { BindTarget = Beatmap } }, new FillFlowContainer { @@ -374,6 +381,10 @@ namespace osu.Game.Screens.OnlinePlay } } }; + + // manual binding required as playlists don't expose IBeatmapInfo currently. + // may be removed in the future if this changes. + Beatmap.BindValueChanged(beatmap => backgroundSprite.Beatmap.Value = beatmap.NewValue); } } } diff --git a/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs b/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs index fe7c7cc364..72574b729a 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs @@ -103,7 +103,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge public IEnumerable FilterTerms => new[] { Room.Name.Value }; - private bool matchingFilter; + private bool matchingFilter = true; public bool MatchingFilter { @@ -181,6 +181,10 @@ namespace osu.Game.Screens.OnlinePlay.Lounge [Resolved(canBeNull: true)] private LoungeSubScreen lounge { get; set; } + public override bool HandleNonPositionalInput => true; + + protected override bool BlockNonPositionalInput => true; + public PasswordEntryPopover(Room room) { this.room = room; @@ -200,6 +204,8 @@ namespace osu.Game.Screens.OnlinePlay.Lounge Spacing = new Vector2(5), AutoSizeAxes = Axes.Both, Direction = FillDirection.Vertical, + LayoutDuration = 500, + LayoutEasing = Easing.OutQuint, Children = new Drawable[] { new FillFlowContainer @@ -230,10 +236,24 @@ namespace osu.Game.Screens.OnlinePlay.Lounge sampleJoinFail = audio.Samples.Get(@"UI/password-fail"); - joinButton.Action = () => lounge?.Join(room, passwordTextbox.Text, null, joinFailed); + joinButton.Action = performJoin; } - private void joinFailed(string error) + protected override void LoadComplete() + { + base.LoadComplete(); + + Schedule(() => GetContainingInputManager().ChangeFocus(passwordTextbox)); + passwordTextbox.OnCommit += (_, __) => performJoin(); + } + + private void performJoin() + { + lounge?.Join(room, passwordTextbox.Text, null, joinFailed); + GetContainingInputManager().TriggerFocusContention(passwordTextbox); + } + + private void joinFailed(string error) => Schedule(() => { passwordTextbox.Text = string.Empty; @@ -249,15 +269,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge Body.Shake(); sampleJoinFail?.Play(); - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - Schedule(() => GetContainingInputManager().ChangeFocus(passwordTextbox)); - passwordTextbox.OnCommit += (_, __) => lounge?.Join(room, passwordTextbox.Text, null, joinFailed); - } + }); } } } diff --git a/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs b/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs index 62012906a7..cd1c8a0a64 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs @@ -289,7 +289,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge popoverContainer.HidePopover(); } - public void Join(Room room, string password, Action onSuccess = null, Action onFailure = null) => Schedule(() => + public virtual void Join(Room room, string password, Action onSuccess = null, Action onFailure = null) => Schedule(() => { if (joiningRoomOperation != null) return; diff --git a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs index bcb793062b..2015a050bb 100644 --- a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs @@ -65,9 +65,9 @@ namespace osu.Game.Screens.OnlinePlay.Match private IBindable> managerUpdated; [Cached] - protected OnlinePlayBeatmapAvailabilityTracker BeatmapAvailabilityTracker { get; private set; } + private OnlinePlayBeatmapAvailabilityTracker beatmapAvailabilityTracker { get; set; } - protected IBindable BeatmapAvailability => BeatmapAvailabilityTracker.Availability; + protected IBindable BeatmapAvailability => beatmapAvailabilityTracker.Availability; public readonly Room Room; private readonly bool allowEdit; @@ -88,7 +88,7 @@ namespace osu.Game.Screens.OnlinePlay.Match Padding = new MarginPadding { Top = Header.HEIGHT }; - BeatmapAvailabilityTracker = new OnlinePlayBeatmapAvailabilityTracker + beatmapAvailabilityTracker = new OnlinePlayBeatmapAvailabilityTracker { SelectedItem = { BindTarget = SelectedItem } }; @@ -103,7 +103,7 @@ namespace osu.Game.Screens.OnlinePlay.Match InternalChildren = new Drawable[] { - BeatmapAvailabilityTracker, + beatmapAvailabilityTracker, new GridContainer { RelativeSizeAxes = Axes.Both, diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/GameplayChatDisplay.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/GameplayChatDisplay.cs index af0c50a848..0e73f65f8b 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/GameplayChatDisplay.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/GameplayChatDisplay.cs @@ -75,6 +75,15 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer { switch (e.Action) { + case GlobalAction.Back: + if (Textbox.HasFocus) + { + Schedule(() => Textbox.KillFocus()); + return true; + } + + break; + case GlobalAction.ToggleChatFocus: if (Textbox.HasFocus) { diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs index 79e305b765..e77b5d7b8c 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs @@ -186,7 +186,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants var ruleset = rulesets.GetRuleset(Room.Settings.RulesetID).CreateInstance(); - var currentModeRank = User.User?.RulesetsStatistics?.GetValueOrDefault(ruleset.ShortName)?.GlobalRank; + int? currentModeRank = User.User?.RulesetsStatistics?.GetValueOrDefault(ruleset.ShortName)?.GlobalRank; userRankText.Text = currentModeRank != null ? $"#{currentModeRank.Value:N0}" : string.Empty; userStateDisplay.UpdateStatus(User.State, User.BeatmapAvailability); diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs index 7bf8ce0e1a..57d0d2c198 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs @@ -180,11 +180,11 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate { // Seek the master clock to the gameplay time. // This is chosen as the first available frame in the players' replays, which matches the seek by each individual SpectatorPlayer. - var startTime = instances.Where(i => i.Score != null) - .SelectMany(i => i.Score.Replay.Frames) - .Select(f => f.Time) - .DefaultIfEmpty(0) - .Min(); + double startTime = instances.Where(i => i.Score != null) + .SelectMany(i => i.Score.Replay.Frames) + .Select(f => f.Time) + .DefaultIfEmpty(0) + .Min(); masterClockContainer.Seek(startTime); masterClockContainer.Start(); diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsReadyButton.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsReadyButton.cs index 9ac1fe1722..24f112ef0c 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsReadyButton.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsReadyButton.cs @@ -2,8 +2,10 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Localisation; using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Online.Rooms; @@ -16,6 +18,12 @@ namespace osu.Game.Screens.OnlinePlay.Playlists [Resolved(typeof(Room), nameof(Room.EndDate))] private Bindable endDate { get; set; } + [Resolved(typeof(Room), nameof(Room.MaxAttempts))] + private Bindable maxAttempts { get; set; } + + [Resolved(typeof(Room), nameof(Room.UserScore))] + private Bindable userScore { get; set; } + [Resolved] private IBindable gameBeatmap { get; set; } @@ -32,11 +40,49 @@ namespace osu.Game.Screens.OnlinePlay.Playlists Triangles.ColourLight = colours.GreenLight; } + private bool hasRemainingAttempts = true; + + protected override void LoadComplete() + { + base.LoadComplete(); + + userScore.BindValueChanged(aggregate => + { + if (maxAttempts.Value == null) + return; + + int remaining = maxAttempts.Value.Value - aggregate.NewValue.PlaylistItemAttempts.Sum(a => a.Attempts); + + hasRemainingAttempts = remaining > 0; + }); + } + protected override void Update() { base.Update(); - Enabled.Value = endDate.Value != null && DateTimeOffset.UtcNow.AddSeconds(30).AddMilliseconds(gameBeatmap.Value.Track.Length) < endDate.Value; + Enabled.Value = hasRemainingAttempts && enoughTimeLeft; } + + public override LocalisableString TooltipText + { + get + { + if (Enabled.Value) + return string.Empty; + + if (!enoughTimeLeft) + return "No time left!"; + + if (!hasRemainingAttempts) + return "Attempts exhausted!"; + + return base.TooltipText; + } + } + + private bool enoughTimeLeft => + // This should probably consider the length of the currently selected item, rather than a constant 30 seconds. + endDate.Value != null && DateTimeOffset.UtcNow.AddSeconds(30).AddMilliseconds(gameBeatmap.Value.Track.Length) < endDate.Value; } } diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs index d5e423a438..6d2a426e70 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs @@ -37,6 +37,8 @@ namespace osu.Game.Screens.OnlinePlay.Playlists private MatchLeaderboard leaderboard; private SelectionPollingComponent selectionPollingComponent; + private FillFlowContainer progressSection; + public PlaylistsRoomSubScreen(Room room) : base(room, false) // Editing is temporarily not allowed. { @@ -67,6 +69,8 @@ namespace osu.Game.Screens.OnlinePlay.Playlists Schedule(() => SelectedItem.Value = Room.Playlist.FirstOrDefault()); } }, true); + + Room.MaxAttempts.BindValueChanged(attempts => progressSection.Alpha = Room.MaxAttempts.Value != null ? 1 : 0, true); } protected override Drawable CreateMainContent() => new GridContainer @@ -153,6 +157,22 @@ namespace osu.Game.Screens.OnlinePlay.Playlists }, }, new Drawable[] + { + progressSection = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Alpha = 0, + Margin = new MarginPadding { Bottom = 10 }, + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + new OverlinedHeader("Progress"), + new RoomLocalUserInfo(), + } + }, + }, + new Drawable[] { new OverlinedHeader("Leaderboard") }, @@ -162,6 +182,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists }, RowDimensions = new[] { + new Dimension(GridSizeMode.AutoSize), new Dimension(GridSizeMode.AutoSize), new Dimension(GridSizeMode.AutoSize), new Dimension(), diff --git a/osu.Game/Screens/Play/Break/BreakInfoLine.cs b/osu.Game/Screens/Play/Break/BreakInfoLine.cs index 0bc79d6e77..87f514ffd5 100644 --- a/osu.Game/Screens/Play/Break/BreakInfoLine.cs +++ b/osu.Game/Screens/Play/Break/BreakInfoLine.cs @@ -56,7 +56,7 @@ namespace osu.Game.Screens.Play.Break private void currentValueChanged(ValueChangedEvent e) { - var newText = prefix + Format(e.NewValue); + string newText = prefix + Format(e.NewValue); if (valueText.Text == newText) return; diff --git a/osu.Game/Screens/Play/BreakTracker.cs b/osu.Game/Screens/Play/BreakTracker.cs index 2f3673e91f..8441b7657e 100644 --- a/osu.Game/Screens/Play/BreakTracker.cs +++ b/osu.Game/Screens/Play/BreakTracker.cs @@ -51,7 +51,7 @@ namespace osu.Game.Screens.Play private void updateBreakTime() { - var time = Clock.CurrentTime; + double time = Clock.CurrentTime; isBreakTime.Value = breaks?.IsInAny(time) == true || time < gameplayStartTime diff --git a/osu.Game/Screens/Play/FailAnimation.cs b/osu.Game/Screens/Play/FailAnimation.cs index 242d997dd7..f3676baf80 100644 --- a/osu.Game/Screens/Play/FailAnimation.cs +++ b/osu.Game/Screens/Play/FailAnimation.cs @@ -10,6 +10,7 @@ using ManagedBass.Fx; using osu.Framework.Allocation; using osu.Framework.Audio.Sample; using osu.Framework.Audio.Track; +using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -83,7 +84,7 @@ namespace osu.Game.Screens.Play Content, redFlashLayer = new Box { - Colour = Color4.Red, + Colour = Color4.Red.Opacity(0.6f), RelativeSizeAxes = Axes.Both, Blending = BlendingParameters.Additive, Depth = float.MinValue, @@ -106,6 +107,7 @@ namespace osu.Game.Screens.Play this.TransformBindableTo(trackFreq, 0, duration).OnComplete(_ => { + RemoveFilters(); OnComplete?.Invoke(); }); @@ -137,6 +139,9 @@ namespace osu.Game.Screens.Play public void RemoveFilters() { + if (filters.Parent == null) + return; + RemoveInternal(filters); filters.Dispose(); diff --git a/osu.Game/Screens/Play/HUD/FailingLayer.cs b/osu.Game/Screens/Play/HUD/FailingLayer.cs index 424ee55766..ceb81f6b8d 100644 --- a/osu.Game/Screens/Play/HUD/FailingLayer.cs +++ b/osu.Game/Screens/Play/HUD/FailingLayer.cs @@ -90,7 +90,7 @@ namespace osu.Game.Screens.Play.HUD private void updateState() { // Don't display ever if the ruleset is not using a draining health display. - var showLayer = HealthProcessor is DrainingHealthProcessor && fadePlayfieldWhenHealthLow.Value && ShowHealth.Value; + bool showLayer = HealthProcessor is DrainingHealthProcessor && fadePlayfieldWhenHealthLow.Value && ShowHealth.Value; this.FadeTo(showLayer ? 1 : 0, fade_time, Easing.OutQuint); } diff --git a/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs b/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs index fb49dedce7..a8141c57da 100644 --- a/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs +++ b/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs @@ -157,11 +157,11 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters // max to avoid div-by-zero. maxHitWindow = Math.Max(1, windows.First().length); - for (var i = 0; i < windows.Length; i++) + for (int i = 0; i < windows.Length; i++) { - var (result, length) = windows[i]; + (var result, double length) = windows[i]; - var hitWindow = (float)(length / maxHitWindow); + float hitWindow = (float)(length / maxHitWindow); colourBarsEarly.Add(createColourBar(result, hitWindow, i == 0)); colourBarsLate.Add(createColourBar(result, hitWindow, i == 0)); diff --git a/osu.Game/Screens/Play/HUD/MatchScoreDisplay.cs b/osu.Game/Screens/Play/HUD/MatchScoreDisplay.cs index b1c07512dd..88cf9529bf 100644 --- a/osu.Game/Screens/Play/HUD/MatchScoreDisplay.cs +++ b/osu.Game/Screens/Play/HUD/MatchScoreDisplay.cs @@ -133,7 +133,7 @@ namespace osu.Game.Screens.Play.HUD var winningBar = Team1Score.Value > Team2Score.Value ? score1Bar : score2Bar; var losingBar = Team1Score.Value <= Team2Score.Value ? score1Bar : score2Bar; - var diff = Math.Max(Team1Score.Value, Team2Score.Value) - Math.Min(Team1Score.Value, Team2Score.Value); + int diff = Math.Max(Team1Score.Value, Team2Score.Value) - Math.Min(Team1Score.Value, Team2Score.Value); losingBar.ResizeWidthTo(0, 400, Easing.OutQuint); winningBar.ResizeWidthTo(Math.Min(0.4f, MathF.Pow(diff / 1500000f, 0.5f) / 2), 400, Easing.OutQuint); diff --git a/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs b/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs index 19cb6aeb50..7caf90f610 100644 --- a/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs +++ b/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs @@ -148,7 +148,7 @@ namespace osu.Game.Screens.Play.HUD switch (e.Action) { case NotifyCollectionChangedAction.Remove: - foreach (var userId in e.OldItems.OfType()) + foreach (int userId in e.OldItems.OfType()) { spectatorClient.StopWatchingUser(userId); diff --git a/osu.Game/Screens/Play/SoloSpectator.cs b/osu.Game/Screens/Play/SoloSpectator.cs index 9d4dad8bdc..83ea660092 100644 --- a/osu.Game/Screens/Play/SoloSpectator.cs +++ b/osu.Game/Screens/Play/SoloSpectator.cs @@ -223,7 +223,7 @@ namespace osu.Game.Screens.Play onlineBeatmapRequest.Success += res => Schedule(() => { onlineBeatmap = res.ToBeatmapSet(rulesets); - beatmapPanelContainer.Child = new GridBeatmapPanel(onlineBeatmap); + beatmapPanelContainer.Child = new GridBeatmapPanel(res); checkForAutomaticDownload(); }); diff --git a/osu.Game/Screens/Play/SongProgressGraph.cs b/osu.Game/Screens/Play/SongProgressGraph.cs index 78eb456bb5..f96de149ba 100644 --- a/osu.Game/Screens/Play/SongProgressGraph.cs +++ b/osu.Game/Screens/Play/SongProgressGraph.cs @@ -24,17 +24,17 @@ namespace osu.Game.Screens.Play if (!objects.Any()) return; - var firstHit = objects.First().StartTime; - var lastHit = objects.Max(o => o.GetEndTime()); + double firstHit = objects.First().StartTime; + double lastHit = objects.Max(o => o.GetEndTime()); if (lastHit == 0) lastHit = objects.Last().StartTime; - var interval = (lastHit - firstHit + 1) / granularity; + double interval = (lastHit - firstHit + 1) / granularity; foreach (var h in objects) { - var endTime = h.GetEndTime(); + double endTime = h.GetEndTime(); Debug.Assert(endTime >= h.StartTime); diff --git a/osu.Game/Screens/Play/SongProgressInfo.cs b/osu.Game/Screens/Play/SongProgressInfo.cs index 7441c335d2..7a458cdde0 100644 --- a/osu.Game/Screens/Play/SongProgressInfo.cs +++ b/osu.Game/Screens/Play/SongProgressInfo.cs @@ -82,7 +82,7 @@ namespace osu.Game.Screens.Play { base.Update(); - var time = gameplayClock?.CurrentTime ?? Time.Current; + double time = gameplayClock?.CurrentTime ?? Time.Current; double songCurrentTime = time - startTime; int currentPercent = Math.Max(0, Math.Min(100, (int)(songCurrentTime / songLength * 100))); diff --git a/osu.Game/Screens/Play/SquareGraph.cs b/osu.Game/Screens/Play/SquareGraph.cs index 36ce131411..67abcb66e6 100644 --- a/osu.Game/Screens/Play/SquareGraph.cs +++ b/osu.Game/Screens/Play/SquareGraph.cs @@ -165,7 +165,7 @@ namespace osu.Game.Screens.Play return; } - var max = values.Max(); + int max = values.Max(); float step = values.Length / (float)ColumnCount; diff --git a/osu.Game/Screens/Play/SubmittingPlayer.cs b/osu.Game/Screens/Play/SubmittingPlayer.cs index 5faa384d03..76411c8c6b 100644 --- a/osu.Game/Screens/Play/SubmittingPlayer.cs +++ b/osu.Game/Screens/Play/SubmittingPlayer.cs @@ -116,7 +116,7 @@ namespace osu.Game.Screens.Play public override bool OnExiting(IScreen next) { - var exiting = base.OnExiting(next); + bool exiting = base.OnExiting(next); submitScore(Score.DeepClone()); diff --git a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs index 262d1e8293..c27d5227b5 100644 --- a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs +++ b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs @@ -62,12 +62,12 @@ namespace osu.Game.Screens.Ranking.Expanded { var beatmap = score.BeatmapInfo; var metadata = beatmap.BeatmapSet?.Metadata ?? beatmap.Metadata; - var creator = metadata.Author?.Username; + string creator = metadata.Author?.Username; var topStatistics = new List { new AccuracyStatistic(score.Accuracy), - new ComboStatistic(score.MaxCombo, !score.Statistics.TryGetValue(HitResult.Miss, out var missCount) || missCount == 0), + new ComboStatistic(score.MaxCombo, !score.Statistics.TryGetValue(HitResult.Miss, out int missCount) || missCount == 0), new PerformanceStatistic(score), }; diff --git a/osu.Game/Screens/Ranking/ReplayDownloadButton.cs b/osu.Game/Screens/Ranking/ReplayDownloadButton.cs index e644eb671a..66b3c973f5 100644 --- a/osu.Game/Screens/Ranking/ReplayDownloadButton.cs +++ b/osu.Game/Screens/Ranking/ReplayDownloadButton.cs @@ -4,6 +4,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osu.Game.Online; @@ -12,13 +13,17 @@ using osuTK; namespace osu.Game.Screens.Ranking { - public class ReplayDownloadButton : DownloadTrackingComposite + public class ReplayDownloadButton : CompositeDrawable { - public Bindable Score => Model; + public readonly Bindable Score = new Bindable(); + + protected readonly Bindable State = new Bindable(); private DownloadButton button; private ShakeContainer shakeContainer; + private ScoreDownloadTracker downloadTracker; + private ReplayAvailability replayAvailability { get @@ -26,7 +31,7 @@ namespace osu.Game.Screens.Ranking if (State.Value == DownloadState.LocallyAvailable) return ReplayAvailability.Local; - if (!string.IsNullOrEmpty(Model.Value?.Hash)) + if (!string.IsNullOrEmpty(Score.Value?.Hash)) return ReplayAvailability.Online; return ReplayAvailability.NotAvailable; @@ -34,8 +39,8 @@ namespace osu.Game.Screens.Ranking } public ReplayDownloadButton(ScoreInfo score) - : base(score) { + Score.Value = score; Size = new Vector2(50, 30); } @@ -56,11 +61,11 @@ namespace osu.Game.Screens.Ranking switch (State.Value) { case DownloadState.LocallyAvailable: - game?.PresentScore(Model.Value, ScorePresentType.Gameplay); + game?.PresentScore(Score.Value, ScorePresentType.Gameplay); break; case DownloadState.NotDownloaded: - scores.Download(Model.Value, false); + scores.Download(Score.Value, false); break; case DownloadState.Importing: @@ -70,17 +75,25 @@ namespace osu.Game.Screens.Ranking } }; - State.BindValueChanged(state => + Score.BindValueChanged(score => { - button.State.Value = state.NewValue; + downloadTracker?.RemoveAndDisposeImmediately(); + if (score.NewValue != null) + { + AddInternal(downloadTracker = new ScoreDownloadTracker(score.NewValue) + { + State = { BindTarget = State } + }); + } + + button.Enabled.Value = replayAvailability != ReplayAvailability.NotAvailable; updateTooltip(); }, true); - Model.BindValueChanged(_ => + State.BindValueChanged(state => { - button.Enabled.Value = replayAvailability != ReplayAvailability.NotAvailable; - + button.State.Value = state.NewValue; updateTooltip(); }, true); } diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index af60296344..dacc4f5f9e 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -287,7 +287,7 @@ namespace osu.Game.Screens.Ranking detachedPanelContainer.Add(expandedPanel); // Move into its original location in the local container first, then to the final location. - var origLocation = detachedPanelContainer.ToLocalSpace(screenSpacePos).X; + float origLocation = detachedPanelContainer.ToLocalSpace(screenSpacePos).X; expandedPanel.MoveToX(origLocation) .Then() .MoveToX(StatisticsPanel.SIDE_PADDING, 150, Easing.OutQuint); diff --git a/osu.Game/Screens/Ranking/SoloResultsScreen.cs b/osu.Game/Screens/Ranking/SoloResultsScreen.cs index 5e582a8dcb..4f4dfa4909 100644 --- a/osu.Game/Screens/Ranking/SoloResultsScreen.cs +++ b/osu.Game/Screens/Ranking/SoloResultsScreen.cs @@ -31,7 +31,7 @@ namespace osu.Game.Screens.Ranking return null; getScoreRequest = new GetScoresRequest(Score.BeatmapInfo, Score.Ruleset); - getScoreRequest.Success += r => scoresCallback?.Invoke(r.Scores.Where(s => s.OnlineScoreID != Score.OnlineScoreID).Select(s => s.CreateScoreInfo(rulesets))); + getScoreRequest.Success += r => scoresCallback?.Invoke(r.Scores.Where(s => s.OnlineID != Score.OnlineScoreID).Select(s => s.CreateScoreInfo(rulesets, Beatmap.Value.BeatmapInfo))); return getScoreRequest; } diff --git a/osu.Game/Screens/Ranking/Statistics/UnstableRate.cs b/osu.Game/Screens/Ranking/Statistics/UnstableRate.cs index 055db143d1..cd2b292547 100644 --- a/osu.Game/Screens/Ranking/Statistics/UnstableRate.cs +++ b/osu.Game/Screens/Ranking/Statistics/UnstableRate.cs @@ -20,8 +20,8 @@ namespace osu.Game.Screens.Ranking.Statistics public UnstableRate(IEnumerable hitEvents) : base("Unstable Rate") { - var timeOffsets = hitEvents.Where(e => !(e.HitObject.HitWindows is HitWindows.EmptyHitWindows) && e.Result.IsHit()) - .Select(ev => ev.TimeOffset).ToArray(); + double[] timeOffsets = hitEvents.Where(e => !(e.HitObject.HitWindows is HitWindows.EmptyHitWindows) && e.Result.IsHit()) + .Select(ev => ev.TimeOffset).ToArray(); Value = 10 * standardDeviation(timeOffsets); } @@ -30,8 +30,8 @@ namespace osu.Game.Screens.Ranking.Statistics if (timeOffsets.Length == 0) return double.NaN; - var mean = timeOffsets.Average(); - var squares = timeOffsets.Select(offset => Math.Pow(offset - mean, 2)).Sum(); + double mean = timeOffsets.Average(); + double squares = timeOffsets.Select(offset => Math.Pow(offset - mean, 2)).Sum(); return Math.Sqrt(squares / timeOffsets.Length); } diff --git a/osu.Game/Screens/Select/BeatmapDetails.cs b/osu.Game/Screens/Select/BeatmapDetails.cs index 6ace92370c..ece16a8e57 100644 --- a/osu.Game/Screens/Select/BeatmapDetails.cs +++ b/osu.Game/Screens/Select/BeatmapDetails.cs @@ -29,7 +29,7 @@ namespace osu.Game.Screens.Select private const float transition_duration = 250; private readonly AdvancedStats advanced; - private readonly UserRatings ratings; + private readonly UserRatings ratingsDisplay; private readonly MetadataSection description, source, tags; private readonly Container failRetryContainer; private readonly FailRetryGraph failRetryGraph; @@ -43,6 +43,10 @@ namespace osu.Game.Screens.Select private BeatmapInfo beatmapInfo; + private APIFailTimes failTimes; + + private int[] ratings; + public BeatmapInfo BeatmapInfo { get => beatmapInfo; @@ -52,6 +56,9 @@ namespace osu.Game.Screens.Select beatmapInfo = value; + failTimes = beatmapInfo?.OnlineInfo?.FailTimes; + ratings = beatmapInfo?.BeatmapSet?.OnlineInfo?.Ratings; + Scheduler.AddOnce(updateStatistics); } } @@ -110,7 +117,7 @@ namespace osu.Game.Screens.Select RelativeSizeAxes = Axes.X, Height = 134, Padding = new MarginPadding { Horizontal = spacing, Top = spacing }, - Child = ratings = new UserRatings + Child = ratingsDisplay = new UserRatings { RelativeSizeAxes = Axes.Both, }, @@ -175,8 +182,8 @@ namespace osu.Game.Screens.Select source.Text = BeatmapInfo?.Metadata?.Source; tags.Text = BeatmapInfo?.Metadata?.Tags; - // metrics may have been previously fetched - if (BeatmapInfo?.BeatmapSet?.Metrics != null && BeatmapInfo?.Metrics != null) + // failTimes may have been previously fetched + if (ratings != null && failTimes != null) { updateMetrics(); return; @@ -201,14 +208,8 @@ namespace osu.Game.Screens.Select // the beatmap has been changed since we started the lookup. return; - var b = res.ToBeatmapInfo(rulesets); - - if (requestedBeatmap.BeatmapSet == null) - requestedBeatmap.BeatmapSet = b.BeatmapSet; - else - requestedBeatmap.BeatmapSet.Metrics = b.BeatmapSet.Metrics; - - requestedBeatmap.Metrics = b.Metrics; + ratings = res.BeatmapSet?.Ratings; + failTimes = res.FailTimes; updateMetrics(); }); @@ -232,29 +233,28 @@ namespace osu.Game.Screens.Select private void updateMetrics() { - var hasRatings = beatmapInfo?.BeatmapSet?.Metrics?.Ratings?.Any() ?? false; - var hasRetriesFails = (beatmapInfo?.Metrics?.Retries?.Any() ?? false) || (beatmapInfo?.Metrics?.Fails?.Any() ?? false); + bool hasMetrics = (failTimes?.Retries?.Any() ?? false) || (failTimes?.Fails?.Any() ?? false); - if (hasRatings) + if (ratings?.Any() ?? false) { - ratings.Metrics = beatmapInfo.BeatmapSet.Metrics; - ratings.FadeIn(transition_duration); + ratingsDisplay.Ratings = ratings; + ratingsDisplay.FadeIn(transition_duration); } else { // loading or just has no data server-side. - ratings.Metrics = new BeatmapSetMetrics { Ratings = new int[10] }; - ratings.FadeTo(0.25f, transition_duration); + ratingsDisplay.Ratings = new int[10]; + ratingsDisplay.FadeTo(0.25f, transition_duration); } - if (hasRetriesFails) + if (hasMetrics) { - failRetryGraph.Metrics = beatmapInfo.Metrics; + failRetryGraph.FailTimes = failTimes; failRetryContainer.FadeIn(transition_duration); } else { - failRetryGraph.Metrics = new BeatmapMetrics + failRetryGraph.FailTimes = new APIFailTimes { Fails = new int[100], Retries = new int[100], diff --git a/osu.Game/Screens/Select/BeatmapInfoWedge.cs b/osu.Game/Screens/Select/BeatmapInfoWedge.cs index ac191a38f2..2de72beaad 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedge.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedge.cs @@ -257,6 +257,7 @@ namespace osu.Game.Screens.Select }, StatusPill = new BeatmapSetOnlineStatusPill { + AutoSizeAxes = Axes.Both, Anchor = Anchor.TopRight, Origin = Anchor.TopRight, Shear = -wedged_container_shear, diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs index d8c5aa760e..9e057808a7 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs @@ -57,9 +57,9 @@ namespace osu.Game.Screens.Select.Carousel if (match) { - var terms = BeatmapInfo.GetSearchableTerms(); + string[] terms = BeatmapInfo.GetSearchableTerms(); - foreach (var criteriaTerm in criteria.SearchTerms) + foreach (string criteriaTerm in criteria.SearchTerms) match &= terms.Any(term => term.Contains(criteriaTerm, StringComparison.InvariantCultureIgnoreCase)); // if a match wasn't found via text matching of terms, do a second catch-all check matching against online IDs. @@ -89,7 +89,7 @@ namespace osu.Game.Screens.Select.Carousel { default: case SortMode.Difficulty: - var ruleset = BeatmapInfo.RulesetID.CompareTo(otherBeatmap.BeatmapInfo.RulesetID); + int ruleset = BeatmapInfo.RulesetID.CompareTo(otherBeatmap.BeatmapInfo.RulesetID); if (ruleset != 0) return ruleset; return BeatmapInfo.StarDifficulty.CompareTo(otherBeatmap.BeatmapInfo.StarDifficulty); diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs index 0d7882bf17..e465f423bc 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs @@ -95,8 +95,8 @@ namespace osu.Game.Screens.Select.Carousel private int compareUsingAggregateMax(CarouselBeatmapSet other, Func func) { - var ourBeatmaps = ValidBeatmaps.Any(); - var otherBeatmaps = other.ValidBeatmaps.Any(); + bool ourBeatmaps = ValidBeatmaps.Any(); + bool otherBeatmaps = other.ValidBeatmaps.Any(); if (!ourBeatmaps && !otherBeatmaps) return 0; if (!ourBeatmaps) return -1; diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index 9773bd5ce9..173b804d90 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -241,7 +241,7 @@ namespace osu.Game.Screens.Select.Carousel TernaryState state; - var countExisting = beatmapSet.Beatmaps.Count(b => collection.Beatmaps.Contains(b)); + int countExisting = beatmapSet.Beatmaps.Count(b => collection.Beatmaps.Contains(b)); if (countExisting == beatmapSet.Beatmaps.Count) state = TernaryState.True; diff --git a/osu.Game/Screens/Select/Carousel/SetPanelContent.cs b/osu.Game/Screens/Select/Carousel/SetPanelContent.cs index 9fb640ba1a..f2054677b0 100644 --- a/osu.Game/Screens/Select/Carousel/SetPanelContent.cs +++ b/osu.Game/Screens/Select/Carousel/SetPanelContent.cs @@ -60,6 +60,7 @@ namespace osu.Game.Screens.Select.Carousel { new BeatmapSetOnlineStatusPill { + AutoSizeAxes = Axes.Both, Origin = Anchor.CentreLeft, Anchor = Anchor.CentreLeft, Margin = new MarginPadding { Right = 5 }, diff --git a/osu.Game/Screens/Select/Details/AdvancedStats.cs b/osu.Game/Screens/Select/Details/AdvancedStats.cs index 562ebad9fe..91c8ce441e 100644 --- a/osu.Game/Screens/Select/Details/AdvancedStats.cs +++ b/osu.Game/Screens/Select/Details/AdvancedStats.cs @@ -38,9 +38,9 @@ namespace osu.Game.Screens.Select.Details protected readonly StatisticRow FirstValue, HpDrain, Accuracy, ApproachRate; private readonly StatisticRow starDifficulty; - private BeatmapInfo beatmapInfo; + private IBeatmapInfo beatmapInfo; - public BeatmapInfo BeatmapInfo + public IBeatmapInfo BeatmapInfo { get => beatmapInfo; set @@ -94,9 +94,6 @@ namespace osu.Game.Screens.Select.Details modSettingChangeTracker = new ModSettingChangeTracker(mods.NewValue); modSettingChangeTracker.SettingChanged += m => { - if (!(m is IApplicableToDifficulty)) - return; - debouncedStatisticsUpdate?.Cancel(); debouncedStatisticsUpdate = Scheduler.AddDelayed(updateStatistics, 100); }; @@ -106,7 +103,7 @@ namespace osu.Game.Screens.Select.Details private void updateStatistics() { - IBeatmapDifficultyInfo baseDifficulty = BeatmapInfo?.BaseDifficulty; + IBeatmapDifficultyInfo baseDifficulty = BeatmapInfo?.Difficulty; BeatmapDifficulty adjustedDifficulty = null; if (baseDifficulty != null && mods.Value.Any(m => m is IApplicableToDifficulty)) @@ -117,7 +114,7 @@ namespace osu.Game.Screens.Select.Details mod.ApplyToDifficulty(adjustedDifficulty); } - switch (BeatmapInfo?.Ruleset?.ID ?? 0) + switch (BeatmapInfo?.Ruleset.OnlineID) { case 3: // Account for mania differences locally for now diff --git a/osu.Game/Screens/Select/Details/FailRetryGraph.cs b/osu.Game/Screens/Select/Details/FailRetryGraph.cs index 7cc80acfd3..312c55b242 100644 --- a/osu.Game/Screens/Select/Details/FailRetryGraph.cs +++ b/osu.Game/Screens/Select/Details/FailRetryGraph.cs @@ -16,20 +16,20 @@ namespace osu.Game.Screens.Select.Details { private readonly BarGraph retryGraph, failGraph; - private BeatmapMetrics metrics; + private APIFailTimes failTimes; - public BeatmapMetrics Metrics + public APIFailTimes FailTimes { - get => metrics; + get => failTimes; set { - if (value == metrics) return; + if (value == failTimes) return; - metrics = value; + failTimes = value; - var retries = Metrics?.Retries ?? Array.Empty(); - var fails = Metrics?.Fails ?? Array.Empty(); - var retriesAndFails = sumRetriesAndFails(retries, fails); + int[] retries = FailTimes?.Retries ?? Array.Empty(); + int[] fails = FailTimes?.Fails ?? Array.Empty(); + int[] retriesAndFails = sumRetriesAndFails(retries, fails); float maxValue = retriesAndFails.Any() ? retriesAndFails.Max() : 0; failGraph.MaxValue = maxValue; @@ -42,7 +42,7 @@ namespace osu.Game.Screens.Select.Details private int[] sumRetriesAndFails(int[] retries, int[] fails) { - var result = new int[Math.Max(retries.Length, fails.Length)]; + int[] result = new int[Math.Max(retries.Length, fails.Length)]; for (int i = 0; i < retries.Length; ++i) result[i] = retries[i]; diff --git a/osu.Game/Screens/Select/Details/UserRatings.cs b/osu.Game/Screens/Select/Details/UserRatings.cs index eabc476db9..c2be3528fc 100644 --- a/osu.Game/Screens/Select/Details/UserRatings.cs +++ b/osu.Game/Screens/Select/Details/UserRatings.cs @@ -1,15 +1,14 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; -using System.Linq; -using osu.Framework.Extensions.LocalisationExtensions; -using osu.Game.Beatmaps; using osu.Game.Resources.Localisation.Web; namespace osu.Game.Screens.Select.Details @@ -22,20 +21,20 @@ namespace osu.Game.Screens.Select.Details private readonly Container graphContainer; private readonly BarGraph graph; - private BeatmapSetMetrics metrics; + private int[] ratings; - public BeatmapSetMetrics Metrics + public int[] Ratings { - get => metrics; + get => ratings; set { - if (value == metrics) return; + if (value == ratings) return; - metrics = value; + ratings = value; const int rating_range = 10; - if (metrics == null) + if (ratings == null) { negativeRatings.Text = 0.ToLocalisableString(@"N0"); positiveRatings.Text = 0.ToLocalisableString(@"N0"); @@ -44,15 +43,15 @@ namespace osu.Game.Screens.Select.Details } else { - var ratings = Metrics.Ratings.Skip(1).Take(rating_range); // adjust for API returning weird empty data at 0. + var usableRange = Ratings.Skip(1).Take(rating_range); // adjust for API returning weird empty data at 0. - var negativeCount = ratings.Take(rating_range / 2).Sum(); - var totalCount = ratings.Sum(); + int negativeCount = usableRange.Take(rating_range / 2).Sum(); + int totalCount = usableRange.Sum(); negativeRatings.Text = negativeCount.ToLocalisableString(@"N0"); positiveRatings.Text = (totalCount - negativeCount).ToLocalisableString(@"N0"); ratingsBar.Length = totalCount == 0 ? 0 : (float)negativeCount / totalCount; - graph.Values = ratings.Take(rating_range).Select(r => (float)r); + graph.Values = usableRange.Take(rating_range).Select(r => (float)r); } } } diff --git a/osu.Game/Screens/Select/FilterControl.cs b/osu.Game/Screens/Select/FilterControl.cs index 298b6e49bd..e95bd7f653 100644 --- a/osu.Game/Screens/Select/FilterControl.cs +++ b/osu.Game/Screens/Select/FilterControl.cs @@ -39,7 +39,7 @@ namespace osu.Game.Screens.Select { Debug.Assert(ruleset.Value.ID != null); - var query = searchTextBox.Text; + string query = searchTextBox.Text; var criteria = new FilterCriteria { diff --git a/osu.Game/Screens/Select/FilterQueryParser.cs b/osu.Game/Screens/Select/FilterQueryParser.cs index a882148392..94df8addb3 100644 --- a/osu.Game/Screens/Select/FilterQueryParser.cs +++ b/osu.Game/Screens/Select/FilterQueryParser.cs @@ -22,9 +22,9 @@ namespace osu.Game.Screens.Select { foreach (Match match in query_syntax_regex.Matches(query)) { - var key = match.Groups["key"].Value.ToLower(); + string key = match.Groups["key"].Value.ToLower(); var op = parseOperator(match.Groups["op"].Value); - var value = match.Groups["value"].Value; + string value = match.Groups["value"].Value; if (tryParseKeywordCriteria(criteria, key, value, op)) query = query.Replace(match.ToString(), ""); @@ -310,10 +310,10 @@ namespace osu.Game.Screens.Select private static bool tryUpdateLengthRange(FilterCriteria criteria, Operator op, string val) { - if (!tryParseDoubleWithPoint(val.TrimEnd('m', 's', 'h'), out var length)) + if (!tryParseDoubleWithPoint(val.TrimEnd('m', 's', 'h'), out double length)) return false; - var scale = getLengthScale(val); + int scale = getLengthScale(val); return tryUpdateCriteriaRange(ref criteria.Length, op, length * scale, scale / 2.0); } } diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs index 07300635aa..de1ba5cf2e 100644 --- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs @@ -192,14 +192,14 @@ namespace osu.Game.Screens.Select.Leaderboards req.Success += r => { - scoreManager.OrderByTotalScoreAsync(r.Scores.Select(s => s.CreateScoreInfo(rulesets)).ToArray(), loadCancellationSource.Token) + scoreManager.OrderByTotalScoreAsync(r.Scores.Select(s => s.CreateScoreInfo(rulesets, BeatmapInfo)).ToArray(), loadCancellationSource.Token) .ContinueWith(ordered => Schedule(() => { if (loadCancellationSource.IsCancellationRequested) return; scoresCallback?.Invoke(ordered.Result); - TopScore = r.UserScore?.CreateScoreInfo(rulesets); + TopScore = r.UserScore?.CreateScoreInfo(rulesets, BeatmapInfo); }), TaskContinuationOptions.OnlyOnRanToCompletion); }; diff --git a/osu.Game/Screens/Spectate/SpectatorScreen.cs b/osu.Game/Screens/Spectate/SpectatorScreen.cs index 7861d4cb72..c8df01dae6 100644 --- a/osu.Game/Screens/Spectate/SpectatorScreen.cs +++ b/osu.Game/Screens/Spectate/SpectatorScreen.cs @@ -76,7 +76,7 @@ namespace osu.Game.Screens.Spectate managerUpdated = beatmaps.ItemUpdated.GetBoundCopy(); managerUpdated.BindValueChanged(beatmapUpdated); - foreach (var (id, _) in userMap) + foreach ((int id, var _) in userMap) spectatorClient.WatchUser(id); })); } @@ -86,7 +86,7 @@ namespace osu.Game.Screens.Spectate if (!e.NewValue.TryGetTarget(out var beatmapSet)) return; - foreach (var (userId, _) in userMap) + foreach ((int userId, var _) in userMap) { if (!playingUserStates.TryGetValue(userId, out var userState)) continue; @@ -101,20 +101,20 @@ namespace osu.Game.Screens.Spectate switch (e.Action) { case NotifyDictionaryChangedAction.Add: - foreach (var (userId, state) in e.NewItems.AsNonNull()) + foreach ((int userId, var state) in e.NewItems.AsNonNull()) onUserStateAdded(userId, state); break; case NotifyDictionaryChangedAction.Remove: - foreach (var (userId, _) in e.OldItems.AsNonNull()) + foreach ((int userId, var _) in e.OldItems.AsNonNull()) onUserStateRemoved(userId); break; case NotifyDictionaryChangedAction.Replace: - foreach (var (userId, _) in e.OldItems.AsNonNull()) + foreach ((int userId, var _) in e.OldItems.AsNonNull()) onUserStateRemoved(userId); - foreach (var (userId, state) in e.NewItems.AsNonNull()) + foreach ((int userId, var state) in e.NewItems.AsNonNull()) onUserStateAdded(userId, state); break; } @@ -219,7 +219,7 @@ namespace osu.Game.Screens.Spectate if (spectatorClient != null) { - foreach (var (userId, _) in userMap) + foreach ((int userId, var _) in userMap) spectatorClient.StopWatchingUser(userId); } diff --git a/osu.Game/Skinning/DefaultLegacySkin.cs b/osu.Game/Skinning/DefaultLegacySkin.cs index 16ac17546d..cd6dbd9ddd 100644 --- a/osu.Game/Skinning/DefaultLegacySkin.cs +++ b/osu.Game/Skinning/DefaultLegacySkin.cs @@ -19,7 +19,14 @@ namespace osu.Game.Skinning [UsedImplicitly(ImplicitUseKindFlags.InstantiatedWithFixedConstructorSignature)] public DefaultLegacySkin(SkinInfo skin, IStorageResourceProvider resources) - : base(skin, new NamespacedResourceStore(resources.Resources, "Skins/Legacy"), resources, string.Empty) + : base( + skin, + new NamespacedResourceStore(resources.Resources, "Skins/Legacy"), + resources, + // A default legacy skin may still have a skin.ini if it is modified by the user. + // We must specify the stream directly as we are redirecting storage to the osu-resources location for other files. + new LegacySkinResourceStore(skin, resources.Files).GetStream("skin.ini") + ) { Configuration.CustomColours["SliderBall"] = new Color4(2, 170, 255, 255); Configuration.CustomComboColours = new List diff --git a/osu.Game/Skinning/DefaultSkin.cs b/osu.Game/Skinning/DefaultSkin.cs index 8e03bddb4d..c377f16f8b 100644 --- a/osu.Game/Skinning/DefaultSkin.cs +++ b/osu.Game/Skinning/DefaultSkin.cs @@ -35,14 +35,13 @@ namespace osu.Game.Skinning : base(skin, resources) { this.resources = resources; - Configuration = new DefaultSkinConfiguration(); } public override Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => null; public override ISample GetSample(ISampleInfo sampleInfo) { - foreach (var lookup in sampleInfo.LookupNames) + foreach (string lookup in sampleInfo.LookupNames) { var sample = resources.AudioManager.Samples.Get(lookup); if (sample != null) diff --git a/osu.Game/Skinning/DefaultSkinConfiguration.cs b/osu.Game/Skinning/DefaultSkinConfiguration.cs deleted file mode 100644 index 5842ee82ee..0000000000 --- a/osu.Game/Skinning/DefaultSkinConfiguration.cs +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -namespace osu.Game.Skinning -{ - /// - /// A skin configuration pre-populated with sane defaults. - /// - public class DefaultSkinConfiguration : SkinConfiguration - { - } -} diff --git a/osu.Game/Skinning/Editor/SkinBlueprintContainer.cs b/osu.Game/Skinning/Editor/SkinBlueprintContainer.cs index c0cc2ab40e..d67bfb89ab 100644 --- a/osu.Game/Skinning/Editor/SkinBlueprintContainer.cs +++ b/osu.Game/Skinning/Editor/SkinBlueprintContainer.cs @@ -41,7 +41,7 @@ namespace osu.Game.Skinning.Editor if (targetContainers.Length == 0) { - var targetScreen = target.ChildrenOfType().LastOrDefault()?.GetType().Name ?? "this screen"; + string targetScreen = target.ChildrenOfType().LastOrDefault()?.GetType().Name ?? "this screen"; AddInternal(new ScreenWhiteBox.UnderConstructionMessage(targetScreen, "doesn't support skin customisation just yet.")); return; diff --git a/osu.Game/Skinning/LegacyBeatmapSkin.cs b/osu.Game/Skinning/LegacyBeatmapSkin.cs index 2093182dcc..8720a55076 100644 --- a/osu.Game/Skinning/LegacyBeatmapSkin.cs +++ b/osu.Game/Skinning/LegacyBeatmapSkin.cs @@ -50,7 +50,7 @@ namespace osu.Game.Skinning { switch (lookup) { - case LegacySkinConfiguration.LegacySetting s when s == LegacySkinConfiguration.LegacySetting.Version: + case SkinConfiguration.LegacySetting s when s == SkinConfiguration.LegacySetting.Version: // For lookup simplicity, ignore beatmap-level versioning completely. // If it is decided that we need this due to beatmaps somehow using it, the default (1.0 specified in LegacySkinDecoder.CreateTemplateObject) diff --git a/osu.Game/Skinning/LegacyManiaSkinDecoder.cs b/osu.Game/Skinning/LegacyManiaSkinDecoder.cs index 5308640bdd..7214c847a7 100644 --- a/osu.Game/Skinning/LegacyManiaSkinDecoder.cs +++ b/osu.Game/Skinning/LegacyManiaSkinDecoder.cs @@ -66,7 +66,7 @@ namespace osu.Game.Skinning { Debug.Assert(currentConfig != null); - foreach (var line in pendingLines) + foreach (string line in pendingLines) { var pair = SplitKeyVal(line); diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index b09620411b..0e7ae95169 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -47,12 +47,6 @@ namespace osu.Game.Skinning /// protected virtual bool UseCustomSampleBanks => false; - public new LegacySkinConfiguration Configuration - { - get => base.Configuration as LegacySkinConfiguration; - set => base.Configuration = value; - } - private readonly Dictionary maniaConfigurations = new Dictionary(); [UsedImplicitly(ImplicitUseKindFlags.InstantiatedWithFixedConstructorSignature)] @@ -69,29 +63,20 @@ namespace osu.Game.Skinning /// Access to raw game resources. /// The user-facing filename of the configuration file to be parsed. Can accept an .osu or skin.ini file. protected LegacySkin(SkinInfo skin, [CanBeNull] IResourceStore storage, [CanBeNull] IStorageResourceProvider resources, string configurationFilename) - : base(skin, resources) + : this(skin, storage, resources, storage?.GetStream(configurationFilename)) { - using (var stream = storage?.GetStream(configurationFilename)) - { - if (stream != null) - { - using (LineBufferedReader reader = new LineBufferedReader(stream, true)) - Configuration = new LegacySkinDecoder().Decode(reader); - - stream.Seek(0, SeekOrigin.Begin); - - using (LineBufferedReader reader = new LineBufferedReader(stream)) - { - var maniaList = new LegacyManiaSkinDecoder().Decode(reader); - - foreach (var config in maniaList) - maniaConfigurations[config.Keys] = config; - } - } - else - Configuration = new LegacySkinConfiguration(); - } + } + /// + /// Construct a new legacy skin instance. + /// + /// The model for this skin. + /// A storage for looking up files within this skin using user-facing filenames. + /// Access to raw game resources. + /// An optional stream containing the contents of a skin.ini file. + protected LegacySkin(SkinInfo skin, [CanBeNull] IResourceStore storage, [CanBeNull] IStorageResourceProvider resources, [CanBeNull] Stream configurationStream) + : base(skin, resources, configurationStream) + { if (storage != null) { var samples = resources?.AudioManager?.GetSampleStore(storage); @@ -110,6 +95,21 @@ namespace osu.Game.Skinning true) != null); } + protected override void ParseConfigurationStream(Stream stream) + { + base.ParseConfigurationStream(stream); + + stream.Seek(0, SeekOrigin.Begin); + + using (LineBufferedReader reader = new LineBufferedReader(stream)) + { + var maniaList = new LegacyManiaSkinDecoder().Decode(reader); + + foreach (var config in maniaList) + maniaConfigurations[config.Keys] = config; + } + } + public override IBindable GetConfig(TLookup lookup) { switch (lookup) @@ -146,7 +146,7 @@ namespace osu.Game.Skinning break; - case LegacySkinConfiguration.LegacySetting legacy: + case SkinConfiguration.LegacySetting legacy: return legacySettingLookup(legacy); default: @@ -189,7 +189,7 @@ namespace osu.Game.Skinning case LegacyManiaSkinConfigurationLookups.ExplosionScale: Debug.Assert(maniaLookup.TargetColumn != null); - if (GetConfig(LegacySkinConfiguration.LegacySetting.Version)?.Value < 2.5m) + if (GetConfig(SkinConfiguration.LegacySetting.Version)?.Value < 2.5m) return SkinUtils.As(new Bindable(1)); if (existing.ExplosionWidth[maniaLookup.TargetColumn.Value] != 0) @@ -236,7 +236,7 @@ namespace osu.Game.Skinning case LegacyManiaSkinConfigurationLookups.HoldNoteLightScale: Debug.Assert(maniaLookup.TargetColumn != null); - if (GetConfig(LegacySkinConfiguration.LegacySetting.Version)?.Value < 2.5m) + if (GetConfig(SkinConfiguration.LegacySetting.Version)?.Value < 2.5m) return SkinUtils.As(new Bindable(1)); if (existing.HoldNoteLightWidth[maniaLookup.TargetColumn.Value] != 0) @@ -306,18 +306,18 @@ namespace osu.Game.Skinning => source.CustomColours.TryGetValue(lookup, out var col) ? new Bindable(col) : null; private IBindable getManiaImage(LegacyManiaSkinConfiguration source, string lookup) - => source.ImageLookups.TryGetValue(lookup, out var image) ? new Bindable(image) : null; + => source.ImageLookups.TryGetValue(lookup, out string image) ? new Bindable(image) : null; [CanBeNull] - private IBindable legacySettingLookup(LegacySkinConfiguration.LegacySetting legacySetting) + private IBindable legacySettingLookup(SkinConfiguration.LegacySetting legacySetting) { switch (legacySetting) { - case LegacySkinConfiguration.LegacySetting.Version: - return SkinUtils.As(new Bindable(Configuration.LegacyVersion ?? LegacySkinConfiguration.LATEST_VERSION)); + case SkinConfiguration.LegacySetting.Version: + return SkinUtils.As(new Bindable(Configuration.LegacyVersion ?? SkinConfiguration.LATEST_VERSION)); default: - return genericLookup(legacySetting); + return genericLookup(legacySetting); } } @@ -326,7 +326,7 @@ namespace osu.Game.Skinning { try { - if (Configuration.ConfigDictionary.TryGetValue(lookup.ToString(), out var val)) + if (Configuration.ConfigDictionary.TryGetValue(lookup.ToString(), out string val)) { // special case for handling skins which use 1 or 0 to signify a boolean state. if (typeof(TValue) == typeof(bool)) @@ -472,7 +472,7 @@ namespace osu.Game.Skinning public override Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) { - foreach (var name in getFallbackNames(componentName)) + foreach (string name in getFallbackNames(componentName)) { float ratio = 2; var texture = Textures?.Get($"{name}@2x", wrapModeS, wrapModeT); @@ -504,7 +504,7 @@ namespace osu.Game.Skinning lookupNames = sampleInfo.LookupNames.SelectMany(getFallbackNames); } - foreach (var lookup in lookupNames) + foreach (string lookup in lookupNames) { var sample = Samples?.Get(lookup); @@ -529,7 +529,7 @@ namespace osu.Game.Skinning lookupNames = lookupNames.Where(name => !name.EndsWith(hitSample.Suffix, StringComparison.Ordinal)); } - foreach (var l in lookupNames) + foreach (string l in lookupNames) yield return l; // also for compatibility, try falling back to non-bank samples (so-called "universal" samples) as the last resort. diff --git a/osu.Game/Skinning/LegacySkinConfiguration.cs b/osu.Game/Skinning/LegacySkinConfiguration.cs deleted file mode 100644 index 20d1da8aaa..0000000000 --- a/osu.Game/Skinning/LegacySkinConfiguration.cs +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -namespace osu.Game.Skinning -{ - public class LegacySkinConfiguration : SkinConfiguration - { - public const decimal LATEST_VERSION = 2.7m; - - /// - /// Legacy version of this skin. - /// - public decimal? LegacyVersion { get; internal set; } - - public enum LegacySetting - { - Version, - ComboPrefix, - ComboOverlap, - ScorePrefix, - ScoreOverlap, - HitCirclePrefix, - HitCircleOverlap, - AnimationFramerate, - LayeredHitSounds - } - } -} diff --git a/osu.Game/Skinning/LegacySkinDecoder.cs b/osu.Game/Skinning/LegacySkinDecoder.cs index 2700f84815..aac343d710 100644 --- a/osu.Game/Skinning/LegacySkinDecoder.cs +++ b/osu.Game/Skinning/LegacySkinDecoder.cs @@ -6,14 +6,14 @@ using osu.Game.Beatmaps.Formats; namespace osu.Game.Skinning { - public class LegacySkinDecoder : LegacyDecoder + public class LegacySkinDecoder : LegacyDecoder { public LegacySkinDecoder() : base(1) { } - protected override void ParseLine(LegacySkinConfiguration skin, Section section, string line) + protected override void ParseLine(SkinConfiguration skin, Section section, string line) { if (section != Section.Colours) { @@ -34,8 +34,8 @@ namespace osu.Game.Skinning case @"Version": if (pair.Value == "latest") - skin.LegacyVersion = LegacySkinConfiguration.LATEST_VERSION; - else if (decimal.TryParse(pair.Value, NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out var version)) + skin.LegacyVersion = SkinConfiguration.LATEST_VERSION; + else if (decimal.TryParse(pair.Value, NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out decimal version)) skin.LegacyVersion = version; return; @@ -57,7 +57,7 @@ namespace osu.Game.Skinning base.ParseLine(skin, section, line); } - protected override LegacySkinConfiguration CreateTemplateObject() + protected override SkinConfiguration CreateTemplateObject() { var config = base.CreateTemplateObject(); config.LegacyVersion = 1.0m; diff --git a/osu.Game/Skinning/LegacySkinExtensions.cs b/osu.Game/Skinning/LegacySkinExtensions.cs index fd1f905868..479afabb00 100644 --- a/osu.Game/Skinning/LegacySkinExtensions.cs +++ b/osu.Game/Skinning/LegacySkinExtensions.cs @@ -12,7 +12,7 @@ using osu.Framework.Graphics.Animations; using osu.Framework.Graphics.OpenGL.Textures; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; -using static osu.Game.Skinning.LegacySkinConfiguration; +using static osu.Game.Skinning.SkinConfiguration; namespace osu.Game.Skinning { diff --git a/osu.Game/Skinning/LegacySkinResourceStore.cs b/osu.Game/Skinning/LegacySkinResourceStore.cs index 05d0dee05f..fb06bb54d0 100644 --- a/osu.Game/Skinning/LegacySkinResourceStore.cs +++ b/osu.Game/Skinning/LegacySkinResourceStore.cs @@ -26,9 +26,9 @@ namespace osu.Game.Skinning if (source.Files == null) yield break; - foreach (var filename in base.GetFilenames(name)) + foreach (string filename in base.GetFilenames(name)) { - var path = getPathForFile(filename.ToStandardisedPath()); + string path = getPathForFile(filename.ToStandardisedPath()); if (path != null) yield return path; } diff --git a/osu.Game/Skinning/LegacySkinTransformer.cs b/osu.Game/Skinning/LegacySkinTransformer.cs index 92b7a04dee..97084f34e0 100644 --- a/osu.Game/Skinning/LegacySkinTransformer.cs +++ b/osu.Game/Skinning/LegacySkinTransformer.cs @@ -10,7 +10,7 @@ using osu.Framework.Graphics.OpenGL.Textures; using osu.Framework.Graphics.Textures; using osu.Game.Audio; using osu.Game.Rulesets.Objects.Legacy; -using static osu.Game.Skinning.LegacySkinConfiguration; +using static osu.Game.Skinning.SkinConfiguration; namespace osu.Game.Skinning { diff --git a/osu.Game/Skinning/LegacySpriteText.cs b/osu.Game/Skinning/LegacySpriteText.cs index 8fc6cbde7d..94383834fc 100644 --- a/osu.Game/Skinning/LegacySpriteText.cs +++ b/osu.Game/Skinning/LegacySpriteText.cs @@ -49,7 +49,7 @@ namespace osu.Game.Skinning public ITexturedCharacterGlyph Get(string fontName, char character) { - var lookup = getLookupName(character); + string lookup = getLookupName(character); var texture = skin.GetTexture($"{fontName}-{lookup}"); diff --git a/osu.Game/Skinning/ResourceStoreBackedSkin.cs b/osu.Game/Skinning/ResourceStoreBackedSkin.cs index f041b82cf4..4787b5a4e9 100644 --- a/osu.Game/Skinning/ResourceStoreBackedSkin.cs +++ b/osu.Game/Skinning/ResourceStoreBackedSkin.cs @@ -36,7 +36,7 @@ namespace osu.Game.Skinning public ISample? GetSample(ISampleInfo sampleInfo) { - foreach (var lookup in sampleInfo.LookupNames) + foreach (string? lookup in sampleInfo.LookupNames) { ISample? sample = samples.Get(lookup); if (sample != null) diff --git a/osu.Game/Skinning/Skin.cs b/osu.Game/Skinning/Skin.cs index 92441f40da..f8f9c1172d 100644 --- a/osu.Game/Skinning/Skin.cs +++ b/osu.Game/Skinning/Skin.cs @@ -3,8 +3,10 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Text; +using JetBrains.Annotations; using Newtonsoft.Json; using osu.Framework.Audio.Sample; using osu.Framework.Bindables; @@ -21,8 +23,9 @@ namespace osu.Game.Skinning public abstract class Skin : IDisposable, ISkin { public readonly SkinInfo SkinInfo; + private readonly IStorageResourceProvider resources; - public SkinConfiguration Configuration { get; protected set; } + public SkinConfiguration Configuration { get; set; } public IDictionary DrawableComponentInfo => drawableComponentInfo; @@ -36,9 +39,18 @@ namespace osu.Game.Skinning public abstract IBindable GetConfig(TLookup lookup); - protected Skin(SkinInfo skin, IStorageResourceProvider resources) + protected Skin(SkinInfo skin, IStorageResourceProvider resources, [CanBeNull] Stream configurationStream = null) { SkinInfo = skin; + this.resources = resources; + + configurationStream ??= getConfigurationStream(); + + if (configurationStream != null) + // stream will be closed after use by LineBufferedReader. + ParseConfigurationStream(configurationStream); + else + Configuration = new SkinConfiguration(); // we may want to move this to some kind of async operation in the future. foreach (SkinnableTarget skinnableTarget in Enum.GetValues(typeof(SkinnableTarget))) @@ -51,7 +63,7 @@ namespace osu.Game.Skinning if (fileInfo == null) continue; - var bytes = resources?.Files.Get(fileInfo.FileInfo.StoragePath); + byte[] bytes = resources?.Files.Get(fileInfo.FileInfo.StoragePath); if (bytes == null) continue; @@ -73,6 +85,22 @@ namespace osu.Game.Skinning } } + protected virtual void ParseConfigurationStream(Stream stream) + { + using (LineBufferedReader reader = new LineBufferedReader(stream, true)) + Configuration = new LegacySkinDecoder().Decode(reader); + } + + private Stream getConfigurationStream() + { + string path = SkinInfo.Files.SingleOrDefault(f => f.Filename.Equals(@"skin.ini", StringComparison.OrdinalIgnoreCase))?.FileInfo.StoragePath; + + if (string.IsNullOrEmpty(path)) + return null; + + return resources?.Files.GetStream(path); + } + /// /// Remove all stored customisations for the provided target. /// diff --git a/osu.Game/Skinning/SkinConfiguration.cs b/osu.Game/Skinning/SkinConfiguration.cs index a18144246f..f71f6811e8 100644 --- a/osu.Game/Skinning/SkinConfiguration.cs +++ b/osu.Game/Skinning/SkinConfiguration.cs @@ -14,11 +14,31 @@ namespace osu.Game.Skinning { public readonly SkinInfo SkinInfo = new SkinInfo(); + public const decimal LATEST_VERSION = 2.7m; + /// /// Whether to allow as a fallback list for when no combo colours are provided. /// internal bool AllowDefaultComboColoursFallback = true; + /// + /// Legacy version of this skin. + /// + public decimal? LegacyVersion { get; internal set; } + + public enum LegacySetting + { + Version, + ComboPrefix, + ComboOverlap, + ScorePrefix, + ScoreOverlap, + HitCirclePrefix, + HitCircleOverlap, + AnimationFramerate, + LayeredHitSounds + } + public static List DefaultComboColours { get; } = new List { new Color4(255, 192, 0, 255), diff --git a/osu.Game/Skinning/SkinInfo.cs b/osu.Game/Skinning/SkinInfo.cs index 2bf8668ec6..3b34e23d57 100644 --- a/osu.Game/Skinning/SkinInfo.cs +++ b/osu.Game/Skinning/SkinInfo.cs @@ -18,12 +18,12 @@ namespace osu.Game.Skinning public int ID { get; set; } - public string Name { get; set; } + public string Name { get; set; } = string.Empty; + + public string Creator { get; set; } = string.Empty; public string Hash { get; set; } - public string Creator { get; set; } - public string InstantiationInfo { get; set; } public virtual Skin CreateInstance(IStorageResourceProvider resources) diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index 3842acab74..0739026544 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -14,11 +14,11 @@ using Newtonsoft.Json; 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; using osu.Framework.IO.Stores; +using osu.Framework.Logging; using osu.Framework.Platform; using osu.Framework.Testing; using osu.Framework.Utils; @@ -51,7 +51,7 @@ namespace osu.Game.Skinning public override IEnumerable HandledExtensions => new[] { ".osk" }; - protected override string[] HashableFileTypes => new[] { ".ini" }; + protected override string[] HashableFileTypes => new[] { ".ini", ".json" }; protected override string ImportFromStablePath => "Skins"; @@ -85,9 +85,30 @@ namespace osu.Game.Skinning SourceChanged?.Invoke(); }; + + // can be removed 20220420. + populateMissingHashes(); } - protected override bool ShouldDeleteArchive(string path) => Path.GetExtension(path)?.ToLowerInvariant() == ".osk"; + private void populateMissingHashes() + { + var skinsWithoutHashes = ModelStore.ConsumableItems.Where(i => i.Hash == null).ToArray(); + + foreach (SkinInfo skin in skinsWithoutHashes) + { + try + { + Update(skin); + } + catch (Exception e) + { + Delete(skin); + Logger.Error(e, $"Existing skin {skin} has been deleted during hash recomputation due to being invalid"); + } + } + } + + protected override bool ShouldDeleteArchive(string path) => Path.GetExtension(path)?.ToLowerInvariant() == @".osk"; /// /// Returns a list of all usable s. Includes the special default skin plus all skins from . @@ -128,28 +149,122 @@ namespace osu.Game.Skinning CurrentSkinInfo.Value = ModelStore.ConsumableItems.Single(i => i.ID == chosen.ID); } - protected override SkinInfo CreateModel(ArchiveReader archive) => new SkinInfo { Name = archive.Name }; + protected override SkinInfo CreateModel(ArchiveReader archive) => new SkinInfo { Name = archive.Name ?? @"No name" }; - private const string unknown_creator_string = "Unknown"; + private const string unknown_creator_string = @"Unknown"; protected override bool HasCustomHashFunction => true; - protected override string ComputeHash(SkinInfo item, ArchiveReader reader = null) + protected override string ComputeHash(SkinInfo item) { var instance = GetSkin(item); - // in the case the skin has a skin.ini file, we are going to create a hash based on that. - // we don't want to do this in the case we don't have a skin.ini, as it would match only on the filename portion, - // causing potentially unique skin imports to be considered as a duplicate. - if (!string.IsNullOrEmpty(instance.Configuration.SkinInfo.Name)) - { - // we need to populate early to create a hash based off skin.ini contents - populateMetadata(item, instance, reader?.Name); + // This function can be run on fresh import or save. The logic here ensures a skin.ini file is in a good state for both operations. - return item.ToString().ComputeSHA2Hash(); + // `Skin` will parse the skin.ini and populate `Skin.Configuration` during construction above. + string skinIniSourcedName = instance.Configuration.SkinInfo.Name; + string skinIniSourcedCreator = instance.Configuration.SkinInfo.Creator; + string archiveName = item.Name.Replace(@".osk", string.Empty, StringComparison.OrdinalIgnoreCase); + + bool isImport = item.ID == 0; + + if (isImport) + { + item.Name = !string.IsNullOrEmpty(skinIniSourcedName) ? skinIniSourcedName : archiveName; + item.Creator = !string.IsNullOrEmpty(skinIniSourcedCreator) ? skinIniSourcedCreator : unknown_creator_string; + + // For imports, we want to use the archive or folder name as part of the metadata, in addition to any existing skin.ini metadata. + // In an ideal world, skin.ini would be the only source of metadata, but a lot of skin creators and users don't update it when making modifications. + // In both of these cases, the expectation from the user is that the filename or folder name is displayed somewhere to identify the skin. + if (archiveName != item.Name) + item.Name = @$"{item.Name} [{archiveName}]"; } - return base.ComputeHash(item, reader); + // By this point, the metadata in SkinInfo will be correct. + // Regardless of whether this is an import or not, let's write the skin.ini if non-existing or non-matching. + // This is (weirdly) done inside ComputeHash to avoid adding a new method to handle this case. After switching to realm it can be moved into another place. + if (skinIniSourcedName != item.Name) + updateSkinIniMetadata(item); + + return base.ComputeHash(item); + } + + private void updateSkinIniMetadata(SkinInfo item) + { + string nameLine = @$"Name: {item.Name}"; + string authorLine = @$"Author: {item.Creator}"; + + string[] newLines = + { + @"// The following content was automatically added by osu! during import, based on filename / folder metadata.", + @"[General]", + nameLine, + authorLine, + }; + + var existingFile = item.Files.SingleOrDefault(f => f.Filename.Equals(@"skin.ini", StringComparison.OrdinalIgnoreCase)); + + if (existingFile == null) + { + // In the case a skin doesn't have a skin.ini yet, let's create one. + writeNewSkinIni(); + return; + } + + using (Stream stream = new MemoryStream()) + { + using (var sw = new StreamWriter(stream, Encoding.UTF8, 1024, true)) + { + using (var existingStream = Files.Storage.GetStream(existingFile.FileInfo.StoragePath)) + using (var sr = new StreamReader(existingStream)) + { + string line; + while ((line = sr.ReadLine()) != null) + sw.WriteLine(line); + } + + sw.WriteLine(); + + foreach (string line in newLines) + sw.WriteLine(line); + } + + ReplaceFile(item, existingFile, stream); + + // can be removed 20220502. + if (!ensureIniWasUpdated(item)) + { + Logger.Log($"Skin {item}'s skin.ini had issues and has been removed. Please report this and provide the problematic skin.", LoggingTarget.Database, LogLevel.Important); + + DeleteFile(item, item.Files.SingleOrDefault(f => f.Filename.Equals(@"skin.ini", StringComparison.OrdinalIgnoreCase))); + writeNewSkinIni(); + } + } + + void writeNewSkinIni() + { + using (Stream stream = new MemoryStream()) + { + using (var sw = new StreamWriter(stream, Encoding.UTF8, 1024, true)) + { + foreach (string line in newLines) + sw.WriteLine(line); + } + + AddFile(item, stream, @"skin.ini"); + } + } + } + + private bool ensureIniWasUpdated(SkinInfo item) + { + // This is a final consistency check to ensure that hash computation doesn't enter an infinite loop. + // With other changes to the surrounding code this should never be hit, but until we are 101% sure that there + // are no other cases let's avoid a hard startup crash by bailing and alerting. + + var instance = GetSkin(item); + + return instance.Configuration.SkinInfo.Name == item.Name; } protected override Task Populate(SkinInfo model, ArchiveReader archive, CancellationToken cancellationToken = default) @@ -158,32 +273,12 @@ namespace osu.Game.Skinning model.InstantiationInfo ??= instance.GetType().GetInvariantInstantiationInfo(); - populateMetadata(model, instance, archive?.Name); + model.Name = instance.Configuration.SkinInfo.Name; + model.Creator = instance.Configuration.SkinInfo.Creator; return Task.CompletedTask; } - private void populateMetadata(SkinInfo item, Skin instance, string archiveName) - { - if (!string.IsNullOrEmpty(instance.Configuration.SkinInfo.Name)) - { - item.Name = instance.Configuration.SkinInfo.Name; - item.Creator = instance.Configuration.SkinInfo.Creator; - } - else - { - item.Name = item.Name.Replace(".osk", "", StringComparison.OrdinalIgnoreCase); - item.Creator ??= unknown_creator_string; - } - - // generally when importing from a folder, the ".osk" extension will not be present. - // if we ever need a more reliable method of determining this, the type of `ArchiveReader` can be checked. - bool isArchiveImport = archiveName?.Contains(".osk", StringComparison.OrdinalIgnoreCase) == true; - - if (archiveName != null && !isArchiveImport && archiveName != item.Name) - item.Name = $"{item.Name} [{archiveName}]"; - } - /// /// Retrieve a instance for the provided /// @@ -204,7 +299,7 @@ namespace osu.Game.Skinning // if the user is attempting to save one of the default skin implementations, create a copy first. CurrentSkinInfo.Value = Import(new SkinInfo { - Name = skin.SkinInfo.Name + " (modified)", + Name = skin.SkinInfo.Name + @" (modified)", Creator = skin.SkinInfo.Creator, InstantiationInfo = skin.SkinInfo.InstantiationInfo, }).Result.Value; @@ -221,7 +316,7 @@ namespace osu.Game.Skinning using (var streamContent = new MemoryStream(Encoding.UTF8.GetBytes(json))) { - string filename = $"{drawableInfo.Key}.json"; + string filename = @$"{drawableInfo.Key}.json"; var oldFile = skin.SkinInfo.Files.FirstOrDefault(f => f.Filename == filename); diff --git a/osu.Game/Stores/BeatmapImporter.cs b/osu.Game/Stores/BeatmapImporter.cs index 254127cc7e..787b1ddd60 100644 --- a/osu.Game/Stores/BeatmapImporter.cs +++ b/osu.Game/Stores/BeatmapImporter.cs @@ -123,7 +123,7 @@ namespace osu.Game.Stores // find any existing beatmaps in the database that have matching online ids List existingBeatmaps = new List(); - foreach (var id in beatmapIds) + foreach (int id in beatmapIds) existingBeatmaps.AddRange(realm.All().Where(b => b.OnlineID == id)); if (existingBeatmaps.Any()) diff --git a/osu.Game/Stores/RealmArchiveModelImporter.cs b/osu.Game/Stores/RealmArchiveModelImporter.cs index ec454d25fa..3398cc114d 100644 --- a/osu.Game/Stores/RealmArchiveModelImporter.cs +++ b/osu.Game/Stores/RealmArchiveModelImporter.cs @@ -426,7 +426,7 @@ namespace osu.Game.Stores { MemoryStream hashable = new MemoryStream(); - foreach (var file in reader.Filenames.Where(f => HashableFileTypes.Any(ext => f.EndsWith(ext, StringComparison.OrdinalIgnoreCase))).OrderBy(f => f)) + foreach (string? file in reader.Filenames.Where(f => HashableFileTypes.Any(ext => f.EndsWith(ext, StringComparison.OrdinalIgnoreCase))).OrderBy(f => f)) { using (Stream s = reader.GetStream(file)) s.CopyTo(hashable); diff --git a/osu.Game/Stores/RealmRulesetStore.cs b/osu.Game/Stores/RealmRulesetStore.cs index 27eb5d797f..e9c04f652d 100644 --- a/osu.Game/Stores/RealmRulesetStore.cs +++ b/osu.Game/Stores/RealmRulesetStore.cs @@ -193,7 +193,7 @@ namespace osu.Game.Stores { var rulesets = rulesetStorage.GetFiles(@".", @$"{ruleset_library_prefix}.*.dll"); - foreach (var ruleset in rulesets.Where(f => !f.Contains(@"Tests"))) + foreach (string? ruleset in rulesets.Where(f => !f.Contains(@"Tests"))) loadRulesetFromFile(rulesetStorage.GetFullPath(ruleset)); } @@ -201,7 +201,7 @@ namespace osu.Game.Stores { try { - var files = Directory.GetFiles(RuntimeInfo.StartupDirectory, @$"{ruleset_library_prefix}.*.dll"); + string[] files = Directory.GetFiles(RuntimeInfo.StartupDirectory, @$"{ruleset_library_prefix}.*.dll"); foreach (string file in files.Where(f => !Path.GetFileName(f).Contains("Tests"))) loadRulesetFromFile(file); @@ -214,7 +214,7 @@ namespace osu.Game.Stores private void loadRulesetFromFile(string file) { - var filename = Path.GetFileNameWithoutExtension(file); + string? filename = Path.GetFileNameWithoutExtension(file); if (loadedAssemblies.Values.Any(t => Path.GetFileNameWithoutExtension(t.Assembly.Location) == filename)) return; diff --git a/osu.Game/Storyboards/CommandLoop.cs b/osu.Game/Storyboards/CommandLoop.cs index 66db965803..0713cb8670 100644 --- a/osu.Game/Storyboards/CommandLoop.cs +++ b/osu.Game/Storyboards/CommandLoop.cs @@ -33,9 +33,9 @@ namespace osu.Game.Storyboards public override IEnumerable.TypedCommand> GetCommands(CommandTimelineSelector timelineSelector, double offset = 0) { - for (var loop = 0; loop < TotalIterations; loop++) + for (int loop = 0; loop < TotalIterations; loop++) { - var loopOffset = LoopStartTime + loop * CommandsDuration; + double loopOffset = LoopStartTime + loop * CommandsDuration; foreach (var command in base.GetCommands(timelineSelector, offset + loopOffset)) yield return command; } diff --git a/osu.Game/Storyboards/CommandTimeline.cs b/osu.Game/Storyboards/CommandTimeline.cs index c71806352d..8ded3ee975 100644 --- a/osu.Game/Storyboards/CommandTimeline.cs +++ b/osu.Game/Storyboards/CommandTimeline.cs @@ -57,7 +57,7 @@ namespace osu.Game.Storyboards public int CompareTo(ICommand other) { - var result = StartTime.CompareTo(other.StartTime); + int result = StartTime.CompareTo(other.StartTime); if (result != 0) return result; return EndTime.CompareTo(other.EndTime); diff --git a/osu.Game/Storyboards/CommandTimelineGroup.cs b/osu.Game/Storyboards/CommandTimelineGroup.cs index c478b91c22..e7de135ce8 100644 --- a/osu.Game/Storyboards/CommandTimelineGroup.cs +++ b/osu.Game/Storyboards/CommandTimelineGroup.cs @@ -65,7 +65,7 @@ namespace osu.Game.Storyboards { // if the first alpha command starts at zero it should be given priority over anything else. // this is due to it creating a state where the target is not present before that time, causing any other events to not be visible. - var earliestDisplay = EarliestDisplayedTime; + double? earliestDisplay = EarliestDisplayedTime; if (earliestDisplay != null) return earliestDisplay.Value; diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs index d746ff5ae5..d21616955a 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs @@ -29,7 +29,7 @@ namespace osu.Game.Storyboards.Drawables [BackgroundDependencyLoader(true)] private void load(IBindable beatmap, TextureStore textureStore) { - var path = beatmap.Value.BeatmapSetInfo?.Files.Find(f => f.Filename.Equals(Video.Path, StringComparison.OrdinalIgnoreCase))?.FileInfo.StoragePath; + string path = beatmap.Value.BeatmapSetInfo?.Files.Find(f => f.Filename.Equals(Video.Path, StringComparison.OrdinalIgnoreCase))?.FileInfo.StoragePath; if (path == null) return; diff --git a/osu.Game/Storyboards/Storyboard.cs b/osu.Game/Storyboards/Storyboard.cs index 38e0e4e38c..a25bf24491 100644 --- a/osu.Game/Storyboards/Storyboard.cs +++ b/osu.Game/Storyboards/Storyboard.cs @@ -77,7 +77,7 @@ namespace osu.Game.Storyboards { get { - var backgroundPath = BeatmapInfo.BeatmapSet?.Metadata?.BackgroundFile?.ToLowerInvariant(); + string backgroundPath = BeatmapInfo.BeatmapSet?.Metadata?.BackgroundFile?.ToLowerInvariant(); if (backgroundPath == null) return false; @@ -91,7 +91,7 @@ namespace osu.Game.Storyboards public Drawable CreateSpriteFromResourcePath(string path, TextureStore textureStore) { Drawable drawable = null; - var storyboardPath = BeatmapInfo.BeatmapSet?.Files.Find(f => f.Filename.Equals(path, StringComparison.OrdinalIgnoreCase))?.FileInfo.StoragePath; + string storyboardPath = BeatmapInfo.BeatmapSet?.Files.Find(f => f.Filename.Equals(path, StringComparison.OrdinalIgnoreCase))?.FileInfo.StoragePath; if (storyboardPath != null) drawable = new Sprite { Texture = textureStore.Get(storyboardPath) }; diff --git a/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs b/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs index 6d63525011..651874e4de 100644 --- a/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs +++ b/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs @@ -150,7 +150,7 @@ namespace osu.Game.Tests.Beatmaps using (var resStream = openResource($"{resource_namespace}.{name}{expected_conversion_suffix}.json")) using (var reader = new StreamReader(resStream)) { - var contents = reader.ReadToEnd(); + string contents = reader.ReadToEnd(); return JsonConvert.DeserializeObject(contents); } } @@ -173,7 +173,7 @@ namespace osu.Game.Tests.Beatmaps private Stream openResource(string name) { - var localPath = Path.GetDirectoryName(Uri.UnescapeDataString(new UriBuilder(Assembly.GetExecutingAssembly().CodeBase).Path)).AsNonNull(); + string localPath = Path.GetDirectoryName(Uri.UnescapeDataString(new UriBuilder(Assembly.GetExecutingAssembly().CodeBase).Path)).AsNonNull(); return Assembly.LoadFrom(Path.Combine(localPath, $"{ResourceAssembly}.dll")).GetManifestResourceStream($@"{ResourceAssembly}.Resources.{name}"); } diff --git a/osu.Game/Tests/Beatmaps/DifficultyCalculatorTest.cs b/osu.Game/Tests/Beatmaps/DifficultyCalculatorTest.cs index fdb3e1d465..e5b641b606 100644 --- a/osu.Game/Tests/Beatmaps/DifficultyCalculatorTest.cs +++ b/osu.Game/Tests/Beatmaps/DifficultyCalculatorTest.cs @@ -49,7 +49,7 @@ namespace osu.Game.Tests.Beatmaps private Stream openResource(string name) { - var localPath = Path.GetDirectoryName(Uri.UnescapeDataString(new UriBuilder(Assembly.GetExecutingAssembly().CodeBase).Path)).AsNonNull(); + string localPath = Path.GetDirectoryName(Uri.UnescapeDataString(new UriBuilder(Assembly.GetExecutingAssembly().CodeBase).Path)).AsNonNull(); return Assembly.LoadFrom(Path.Combine(localPath, $"{ResourceAssembly}.dll")).GetManifestResourceStream($@"{ResourceAssembly}.Resources.{name}"); } diff --git a/osu.Game/Tests/Beatmaps/TestBeatmap.cs b/osu.Game/Tests/Beatmaps/TestBeatmap.cs index d8e72d31a7..caf83973c4 100644 --- a/osu.Game/Tests/Beatmaps/TestBeatmap.cs +++ b/osu.Game/Tests/Beatmaps/TestBeatmap.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.IO; using System.Text; +using System.Threading; using osu.Framework.Extensions; using osu.Game.Beatmaps; using osu.Game.IO; @@ -16,6 +17,9 @@ namespace osu.Game.Tests.Beatmaps { public class TestBeatmap : Beatmap { + private static int onlineSetID; + private static int onlineBeatmapID; + public TestBeatmap(RulesetInfo ruleset, bool withHitObjects = true) { var baseBeatmap = CreateBeatmap(); @@ -31,8 +35,10 @@ namespace osu.Game.Tests.Beatmaps BeatmapInfo.RulesetID = ruleset.ID ?? 0; BeatmapInfo.BeatmapSet.Metadata = BeatmapInfo.Metadata; BeatmapInfo.BeatmapSet.Beatmaps = new List { BeatmapInfo }; + BeatmapInfo.BeatmapSet.OnlineBeatmapSetID = Interlocked.Increment(ref onlineSetID); BeatmapInfo.Length = 75000; - BeatmapInfo.OnlineInfo = new BeatmapOnlineInfo(); + BeatmapInfo.OnlineInfo = new APIBeatmap(); + BeatmapInfo.OnlineBeatmapID = Interlocked.Increment(ref onlineBeatmapID); BeatmapInfo.BeatmapSet.OnlineInfo = new APIBeatmapSet { Status = BeatmapSetOnlineStatus.Ranked, diff --git a/osu.Game/Tests/Visual/DependencyProvidingContainer.cs b/osu.Game/Tests/Visual/DependencyProvidingContainer.cs index c799cad61a..d1290fc5ac 100644 --- a/osu.Game/Tests/Visual/DependencyProvidingContainer.cs +++ b/osu.Game/Tests/Visual/DependencyProvidingContainer.cs @@ -25,7 +25,7 @@ namespace osu.Game.Tests.Visual { var dependencyContainer = new DependencyContainer(base.CreateChildDependencies(parent)); - foreach (var (type, value) in CachedDependencies) + foreach ((var type, object value) in CachedDependencies) dependencyContainer.CacheAs(type, value); return dependencyContainer; diff --git a/osu.Game/Tests/Visual/EditorTestScene.cs b/osu.Game/Tests/Visual/EditorTestScene.cs index 798b0d01ee..4b02306d33 100644 --- a/osu.Game/Tests/Visual/EditorTestScene.cs +++ b/osu.Game/Tests/Visual/EditorTestScene.cs @@ -10,7 +10,6 @@ using osu.Framework.Platform; using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Database; -using osu.Game.IO.Archives; using osu.Game.Online.API; using osu.Game.Rulesets; using osu.Game.Rulesets.Edit; @@ -154,7 +153,7 @@ namespace osu.Game.Tests.Visual { } - protected override string ComputeHash(BeatmapSetInfo item, ArchiveReader reader = null) + protected override string ComputeHash(BeatmapSetInfo item) => string.Empty; } diff --git a/osu.Game/Tests/Visual/Multiplayer/IMultiplayerTestSceneDependencies.cs b/osu.Game/Tests/Visual/Multiplayer/IMultiplayerTestSceneDependencies.cs index 3362ebbbd6..204c189591 100644 --- a/osu.Game/Tests/Visual/Multiplayer/IMultiplayerTestSceneDependencies.cs +++ b/osu.Game/Tests/Visual/Multiplayer/IMultiplayerTestSceneDependencies.cs @@ -22,7 +22,7 @@ namespace osu.Game.Tests.Visual.Multiplayer /// /// The cached . /// - new TestRequestHandlingMultiplayerRoomManager RoomManager { get; } + new TestMultiplayerRoomManager RoomManager { get; } /// /// The cached . diff --git a/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs b/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs index f259784170..c628541825 100644 --- a/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs +++ b/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs @@ -18,7 +18,7 @@ namespace osu.Game.Tests.Visual.Multiplayer public const int PLAYER_2_ID = 56; public TestMultiplayerClient Client => OnlinePlayDependencies.Client; - public new TestRequestHandlingMultiplayerRoomManager RoomManager => OnlinePlayDependencies.RoomManager; + public new TestMultiplayerRoomManager RoomManager => OnlinePlayDependencies.RoomManager; public TestUserLookupCache LookupCache => OnlinePlayDependencies?.LookupCache; public TestSpectatorClient SpectatorClient => OnlinePlayDependencies?.SpectatorClient; @@ -35,12 +35,7 @@ namespace osu.Game.Tests.Visual.Multiplayer public new void Setup() => Schedule(() => { if (joinRoom) - { - var room = CreateRoom(); - - RoomManager.CreateRoom(room); - SelectedRoom.Value = room; - } + SelectedRoom.Value = CreateRoom(); }); protected virtual Room CreateRoom() @@ -64,7 +59,10 @@ namespace osu.Game.Tests.Visual.Multiplayer base.SetUpSteps(); if (joinRoom) + { + AddStep("join room", () => RoomManager.CreateRoom(SelectedRoom.Value)); AddUntilStep("wait for room join", () => Client.Room != null); + } } protected override OnlinePlayTestSceneDependencies CreateOnlinePlayDependencies() => new MultiplayerTestSceneDependencies(); diff --git a/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestSceneDependencies.cs b/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestSceneDependencies.cs index 2e13fb6a56..ed349a7103 100644 --- a/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestSceneDependencies.cs +++ b/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestSceneDependencies.cs @@ -18,7 +18,7 @@ namespace osu.Game.Tests.Visual.Multiplayer public TestMultiplayerClient Client { get; } public TestUserLookupCache LookupCache { get; } public TestSpectatorClient SpectatorClient { get; } - public new TestRequestHandlingMultiplayerRoomManager RoomManager => (TestRequestHandlingMultiplayerRoomManager)base.RoomManager; + public new TestMultiplayerRoomManager RoomManager => (TestMultiplayerRoomManager)base.RoomManager; public MultiplayerTestSceneDependencies() { @@ -31,7 +31,7 @@ namespace osu.Game.Tests.Visual.Multiplayer CacheAs(SpectatorClient); } - protected override IRoomManager CreateRoomManager() => new TestRequestHandlingMultiplayerRoomManager(); + protected override IRoomManager CreateRoomManager() => new TestMultiplayerRoomManager(RequestsHandler); protected virtual TestSpectatorClient CreateSpectatorClient() => new TestSpectatorClient(); } diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs index 5e4e5942d9..c6634abe82 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs @@ -39,9 +39,9 @@ namespace osu.Game.Tests.Visual.Multiplayer [Resolved] private BeatmapManager beatmaps { get; set; } = null!; - private readonly TestRequestHandlingMultiplayerRoomManager roomManager; + private readonly TestMultiplayerRoomManager roomManager; - public TestMultiplayerClient(TestRequestHandlingMultiplayerRoomManager roomManager) + public TestMultiplayerClient(TestMultiplayerRoomManager roomManager) { this.roomManager = roomManager; } @@ -62,7 +62,7 @@ namespace osu.Game.Tests.Visual.Multiplayer return roomUser; } - public void AddNullUser() => addUser(new MultiplayerRoomUser(TestUserLookupCache.NULL_USER_ID)); + public void TestAddUnresolvedUser() => addUser(new MultiplayerRoomUser(TestUserLookupCache.UNRESOLVED_USER_ID)); private void addUser(MultiplayerRoomUser user) { diff --git a/osu.Game/Tests/Visual/Multiplayer/TestRequestHandlingMultiplayerRoomManager.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerRoomManager.cs similarity index 55% rename from osu.Game/Tests/Visual/Multiplayer/TestRequestHandlingMultiplayerRoomManager.cs rename to osu.Game/Tests/Visual/Multiplayer/TestMultiplayerRoomManager.cs index 5de518990a..4129d190be 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestRequestHandlingMultiplayerRoomManager.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerRoomManager.cs @@ -2,8 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; -using osu.Framework.Allocation; -using osu.Game.Online.API; using osu.Game.Online.Rooms; using osu.Game.Screens.OnlinePlay.Components; using osu.Game.Screens.OnlinePlay.Multiplayer; @@ -12,25 +10,24 @@ using osu.Game.Tests.Visual.OnlinePlay; namespace osu.Game.Tests.Visual.Multiplayer { /// - /// A for use in multiplayer test scenes, backed by a . + /// A for use in multiplayer test scenes. /// Should generally not be used by itself outside of a . /// - public class TestRequestHandlingMultiplayerRoomManager : MultiplayerRoomManager + public class TestMultiplayerRoomManager : MultiplayerRoomManager { - public IReadOnlyList ServerSideRooms => handler.ServerSideRooms; + private readonly TestRoomRequestsHandler requestsHandler; - private readonly TestRoomRequestsHandler handler = new TestRoomRequestsHandler(); - - [BackgroundDependencyLoader] - private void load(IAPIProvider api, OsuGameBase game) + public TestMultiplayerRoomManager(TestRoomRequestsHandler requestsHandler) { - ((DummyAPIAccess)api).HandleRequest = request => handler.HandleRequest(request, api.LocalUser.Value, game); + this.requestsHandler = requestsHandler; } + public IReadOnlyList ServerSideRooms => requestsHandler.ServerSideRooms; + /// /// Adds a room to a local "server-side" list that's returned when a is fired. /// /// The room. - public void AddServerSideRoom(Room room) => handler.AddServerSideRoom(room); + public void AddServerSideRoom(Room room) => requestsHandler.AddServerSideRoom(room); } } diff --git a/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs b/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs index 8716646074..430aae72f8 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs @@ -7,6 +7,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Game.Online.API; using osu.Game.Online.Rooms; using osu.Game.Screens.OnlinePlay; @@ -27,11 +28,14 @@ namespace osu.Game.Tests.Visual.OnlinePlay /// protected OnlinePlayTestSceneDependencies OnlinePlayDependencies => dependencies?.OnlinePlayDependencies; - private DelegatedDependencyContainer dependencies; - protected override Container Content => content; + + [Resolved] + private OsuGameBase game { get; set; } + private readonly Container content; private readonly Container drawableDependenciesContainer; + private DelegatedDependencyContainer dependencies; protected OnlinePlayTestScene() { @@ -57,6 +61,17 @@ namespace osu.Game.Tests.Visual.OnlinePlay drawableDependenciesContainer.AddRange(OnlinePlayDependencies.DrawableComponents); }); + public override void SetUpSteps() + { + base.SetUpSteps(); + + AddStep("setup API", () => + { + var handler = OnlinePlayDependencies.RequestsHandler; + ((DummyAPIAccess)API).HandleRequest = request => handler.HandleRequest(request, API.LocalUser.Value, game); + }); + } + /// /// Creates the room dependencies. Called every . /// diff --git a/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestSceneDependencies.cs b/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestSceneDependencies.cs index defc971eef..24c4ff79d4 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestSceneDependencies.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestSceneDependencies.cs @@ -21,6 +21,7 @@ namespace osu.Game.Tests.Visual.OnlinePlay public IRoomManager RoomManager { get; } public OngoingOperationTracker OngoingOperationTracker { get; } public OnlinePlayBeatmapAvailabilityTracker AvailabilityTracker { get; } + public TestRoomRequestsHandler RequestsHandler { get; } /// /// All cached dependencies which are also components. @@ -33,12 +34,14 @@ namespace osu.Game.Tests.Visual.OnlinePlay public OnlinePlayTestSceneDependencies() { SelectedRoom = new Bindable(); - RoomManager = CreateRoomManager(); + RequestsHandler = new TestRoomRequestsHandler(); OngoingOperationTracker = new OngoingOperationTracker(); AvailabilityTracker = new OnlinePlayBeatmapAvailabilityTracker(); + RoomManager = CreateRoomManager(); dependencies = new DependencyContainer(new CachedModelDependencyContainer(null) { Model = { BindTarget = SelectedRoom } }); + CacheAs(RequestsHandler); CacheAs(SelectedRoom); CacheAs(RoomManager); CacheAs(OngoingOperationTracker); @@ -71,6 +74,6 @@ namespace osu.Game.Tests.Visual.OnlinePlay drawableComponents.Add(drawable); } - protected virtual IRoomManager CreateRoomManager() => new TestRequestHandlingRoomManager(); + protected virtual IRoomManager CreateRoomManager() => new TestRoomManager(); } } diff --git a/osu.Game/Tests/Visual/OnlinePlay/TestRequestHandlingRoomManager.cs b/osu.Game/Tests/Visual/OnlinePlay/TestRoomManager.cs similarity index 82% rename from osu.Game/Tests/Visual/OnlinePlay/TestRequestHandlingRoomManager.cs rename to osu.Game/Tests/Visual/OnlinePlay/TestRoomManager.cs index d88fd68b20..5fe3dc8406 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/TestRequestHandlingRoomManager.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/TestRoomManager.cs @@ -2,9 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; -using osu.Framework.Allocation; using osu.Game.Beatmaps; -using osu.Game.Online.API; using osu.Game.Online.Rooms; using osu.Game.Rulesets; using osu.Game.Screens.OnlinePlay.Components; @@ -15,20 +13,12 @@ namespace osu.Game.Tests.Visual.OnlinePlay /// /// A very simple for use in online play test scenes. /// - public class TestRequestHandlingRoomManager : RoomManager + public class TestRoomManager : RoomManager { public Action JoinRoomRequested; private int currentRoomId; - private readonly TestRoomRequestsHandler handler = new TestRoomRequestsHandler(); - - [BackgroundDependencyLoader] - private void load(IAPIProvider api, OsuGameBase game) - { - ((DummyAPIAccess)api).HandleRequest = request => handler.HandleRequest(request, api.LocalUser.Value, game); - } - public override void JoinRoom(Room room, string password = null, Action onSuccess = null, Action onError = null) { JoinRoomRequested?.Invoke(room, password); diff --git a/osu.Game/Tests/Visual/OsuTestScene.cs b/osu.Game/Tests/Visual/OsuTestScene.cs index 03434961ea..90e85f7716 100644 --- a/osu.Game/Tests/Visual/OsuTestScene.cs +++ b/osu.Game/Tests/Visual/OsuTestScene.cs @@ -20,6 +20,7 @@ using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Database; using osu.Game.Online.API; +using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; @@ -174,6 +175,56 @@ namespace osu.Game.Tests.Visual protected virtual IBeatmap CreateBeatmap(RulesetInfo ruleset) => new TestBeatmap(ruleset); + protected APIBeatmapSet CreateAPIBeatmapSet(RulesetInfo ruleset) + { + var beatmap = CreateBeatmap(ruleset).BeatmapInfo; + + return new APIBeatmapSet + { + Covers = beatmap.BeatmapSet.Covers, + OnlineID = beatmap.BeatmapSet.OnlineID, + Status = beatmap.BeatmapSet.Status, + Preview = beatmap.BeatmapSet.Preview, + HasFavourited = beatmap.BeatmapSet.HasFavourited, + PlayCount = beatmap.BeatmapSet.PlayCount, + FavouriteCount = beatmap.BeatmapSet.FavouriteCount, + BPM = beatmap.BeatmapSet.BPM, + HasExplicitContent = beatmap.BeatmapSet.HasExplicitContent, + HasVideo = beatmap.BeatmapSet.HasVideo, + HasStoryboard = beatmap.BeatmapSet.HasStoryboard, + Submitted = beatmap.BeatmapSet.Submitted, + Ranked = beatmap.BeatmapSet.Ranked, + LastUpdated = beatmap.BeatmapSet.LastUpdated, + TrackId = beatmap.BeatmapSet.TrackId, + Title = beatmap.BeatmapSet.Metadata.Title, + TitleUnicode = beatmap.BeatmapSet.Metadata.TitleUnicode, + Artist = beatmap.BeatmapSet.Metadata.Artist, + ArtistUnicode = beatmap.BeatmapSet.Metadata.ArtistUnicode, + Author = beatmap.BeatmapSet.Metadata.Author, + AuthorID = beatmap.BeatmapSet.Metadata.AuthorID, + AuthorString = beatmap.BeatmapSet.Metadata.AuthorString, + Availability = beatmap.BeatmapSet.Availability, + Genre = beatmap.BeatmapSet.Genre, + Language = beatmap.BeatmapSet.Language, + Source = beatmap.BeatmapSet.Metadata.Source, + Tags = beatmap.BeatmapSet.Metadata.Tags, + Beatmaps = new[] + { + new APIBeatmap + { + OnlineID = beatmap.OnlineID, + OnlineBeatmapSetID = beatmap.BeatmapSet.OnlineID, + Status = beatmap.Status, + Checksum = beatmap.MD5Hash, + AuthorID = beatmap.Metadata.AuthorID, + RulesetID = beatmap.RulesetID, + StarRating = beatmap.StarDifficulty, + DifficultyName = beatmap.Version, + } + } + }; + } + protected WorkingBeatmap CreateWorkingBeatmap(RulesetInfo ruleset) => CreateWorkingBeatmap(CreateBeatmap(ruleset)); diff --git a/osu.Game/Tests/Visual/SkinnableTestScene.cs b/osu.Game/Tests/Visual/SkinnableTestScene.cs index ef44d0df24..000e7194bc 100644 --- a/osu.Game/Tests/Visual/SkinnableTestScene.cs +++ b/osu.Game/Tests/Visual/SkinnableTestScene.cs @@ -127,7 +127,7 @@ namespace osu.Game.Tests.Visual void updateSizing() { - var autoSize = created.RelativeSizeAxes == Axes.None; + bool autoSize = created.RelativeSizeAxes == Axes.None; foreach (var c in new[] { mainProvider, childContainer, skinProvider }) { @@ -202,7 +202,7 @@ namespace osu.Game.Tests.Visual { var match = Regex.Match(componentName, "-([0-9]*)"); - if (match.Length > 0 && int.TryParse(match.Groups[1].Value, out var number) && number < 60) + if (match.Length > 0 && int.TryParse(match.Groups[1].Value, out int number) && number < 60) return base.GetTexture(componentName.Replace($"-{number}", $"-{number % 2}"), wrapModeS, wrapModeT); } diff --git a/osu.Game/Tests/Visual/TestUserLookupCache.cs b/osu.Game/Tests/Visual/TestUserLookupCache.cs index b73e81d0dd..fcb9c070ff 100644 --- a/osu.Game/Tests/Visual/TestUserLookupCache.cs +++ b/osu.Game/Tests/Visual/TestUserLookupCache.cs @@ -14,11 +14,11 @@ namespace osu.Game.Tests.Visual /// A special user ID which would return a for. /// As a simulation to what a regular would return in the case of failing to fetch the user. /// - public const int NULL_USER_ID = -1; + public const int UNRESOLVED_USER_ID = -1; protected override Task ComputeValueAsync(int lookup, CancellationToken token = default) { - if (lookup == NULL_USER_ID) + if (lookup == UNRESOLVED_USER_ID) return Task.FromResult((User)null); return Task.FromResult(new User diff --git a/osu.Game/Updater/SimpleUpdateManager.cs b/osu.Game/Updater/SimpleUpdateManager.cs index e0409e34df..5e466cc57f 100644 --- a/osu.Game/Updater/SimpleUpdateManager.cs +++ b/osu.Game/Updater/SimpleUpdateManager.cs @@ -45,7 +45,7 @@ namespace osu.Game.Updater // avoid any discrepancies due to build suffixes for now. // eventually we will want to support release streams and consider these. version = version.Split('-').First(); - var latestTagName = latest.TagName.Split('-').First(); + string latestTagName = latest.TagName.Split('-').First(); if (latestTagName != version) { diff --git a/osu.Game/Updater/UpdateManager.cs b/osu.Game/Updater/UpdateManager.cs index 98ce2cb46c..28b828804c 100644 --- a/osu.Game/Updater/UpdateManager.cs +++ b/osu.Game/Updater/UpdateManager.cs @@ -39,9 +39,9 @@ namespace osu.Game.Updater Schedule(() => Task.Run(CheckForUpdateAsync)); - var version = game.Version; + string version = game.Version; - var lastVersion = config.Get(OsuSetting.Version); + string lastVersion = config.Get(OsuSetting.Version); if (game.IsDeployedBuild && version != lastVersion) { diff --git a/osu.Game/Utils/StatelessRNG.cs b/osu.Game/Utils/StatelessRNG.cs index cd169229e3..3db632fc42 100644 --- a/osu.Game/Utils/StatelessRNG.cs +++ b/osu.Game/Utils/StatelessRNG.cs @@ -37,7 +37,7 @@ namespace osu.Game.Utils { unchecked { - var combined = ((ulong)(uint)series << 32) | (uint)seed; + ulong combined = ((ulong)(uint)series << 32) | (uint)seed; // The xor operation is to not map (0, 0) to 0. return mix(combined ^ 0x12345678); } diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 32d6eeab29..c1c3336b5c 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -23,9 +23,9 @@ - - - + + + @@ -36,10 +36,10 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - - - - + + + + diff --git a/osu.iOS.props b/osu.iOS.props index 92abab036a..0baf067a63 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,8 +70,8 @@ - - + + @@ -93,8 +93,8 @@ - - + + diff --git a/osu.sln.DotSettings b/osu.sln.DotSettings index 3af986543e..f35bdfce66 100644 --- a/osu.sln.DotSettings +++ b/osu.sln.DotSettings @@ -207,7 +207,7 @@ HINT WARNING WARNING - DO_NOT_SHOW + SUGGESTION DO_NOT_SHOW DO_NOT_SHOW WARNING @@ -301,6 +301,9 @@ True 200 CHOP_IF_LONG + UseExplicitType + UseVarWhenEvident + UseVarWhenEvident False False AABB