diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index 84673fe2ba..b3f7c67c51 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -2,12 +2,6 @@ "version": 1, "isRoot": true, "tools": { - "cake.tool": { - "version": "0.35.0", - "commands": [ - "dotnet-cake" - ] - }, "dotnet-format": { "version": "3.1.37601", "commands": [ @@ -20,14 +14,14 @@ "jb" ] }, - "nvika": { - "version": "2.0.0", + "smoogipoo.nvika": { + "version": "1.0.1", "commands": [ "nvika" ] }, "codefilesanity": { - "version": "15.0.0", + "version": "0.0.36", "commands": [ "CodeFileSanity" ] @@ -39,4 +33,4 @@ ] } } -} +} \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000000..ed3e99cb61 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,93 @@ +on: [push, pull_request] +name: Continuous Integration + +jobs: + test: + name: Test + runs-on: ${{matrix.os.fullname}} + env: + OSU_EXECUTION_MODE: ${{matrix.threadingMode}} + strategy: + fail-fast: false + matrix: + os: + - { prettyname: Windows, fullname: windows-latest } + - { prettyname: macOS, fullname: macos-latest } + - { prettyname: Linux, fullname: ubuntu-latest } + threadingMode: ['SingleThread', 'MultiThreaded'] + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Install .NET 5.0.x + uses: actions/setup-dotnet@v1 + with: + dotnet-version: "5.0.x" + + # FIXME: libavformat is not included in Ubuntu. Let's fix that. + # https://github.com/ppy/osu-framework/issues/4349 + # Remove this once https://github.com/actions/virtual-environments/issues/3306 has been resolved. + - name: Install libavformat-dev + if: ${{matrix.os.fullname == 'ubuntu-latest'}} + run: | + sudo apt-get update && \ + sudo apt-get -y install libavformat-dev + + - name: Compile + run: dotnet build -c Debug -warnaserror osu.Desktop.slnf + + - name: Test + run: dotnet test $pwd/*.Tests/bin/Debug/*/*.Tests.dll --logger "trx;LogFileName=TestResults-${{matrix.os.prettyname}}-${{matrix.threadingMode}}.trx" + shell: pwsh + + # Attempt to upload results even if test fails. + # https://docs.github.com/en/actions/reference/context-and-expression-syntax-for-github-actions#always + - name: Upload Test Results + uses: actions/upload-artifact@v2 + if: ${{ always() }} + with: + name: osu-test-results-${{matrix.os.prettyname}}-${{matrix.threadingMode}} + path: ${{github.workspace}}/TestResults/TestResults-${{matrix.os.prettyname}}-${{matrix.threadingMode}}.trx + + inspect-code: + name: Code Quality + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + + # FIXME: Tools won't run in .NET 5.0 unless you install 3.1.x LTS side by side. + # https://itnext.io/how-to-support-multiple-net-sdks-in-github-actions-workflows-b988daa884e + - name: Install .NET 3.1.x LTS + uses: actions/setup-dotnet@v1 + with: + dotnet-version: "3.1.x" + + - name: Install .NET 5.0.x + uses: actions/setup-dotnet@v1 + with: + dotnet-version: "5.0.x" + + - name: Restore Tools + run: dotnet tool restore + + - name: Restore Packages + run: dotnet restore + + - name: CodeFileSanity + run: | + # TODO: Add ignore filters and GitHub Workflow Command Reporting in CFS. That way we don't have to do this workaround. + # FIXME: Suppress warnings from templates project + dotnet codefilesanity | while read -r line; do + echo "::warning::$line" + done + + # Temporarily disabled due to test failures, but it won't work anyway until the tool is upgraded. + # - name: .NET Format (Dry Run) + # run: dotnet format --dry-run --check + + - name: InspectCode + run: dotnet jb inspectcode $(pwd)/osu.Desktop.slnf --output=$(pwd)/inspectcodereport.xml --cachesDir=$(pwd)/inspectcode --verbosity=WARN + + - name: NVika + run: dotnet nvika parsereport "${{github.workspace}}/inspectcodereport.xml" --treatwarningsaserrors diff --git a/.github/workflows/report-nunit.yml b/.github/workflows/report-nunit.yml new file mode 100644 index 0000000000..381d2d49c5 --- /dev/null +++ b/.github/workflows/report-nunit.yml @@ -0,0 +1,31 @@ +# This is a workaround to allow PRs to report their coverage. This will run inside the base repository. +# See: +# * https://github.com/dorny/test-reporter#recommended-setup-for-public-repositories +# * https://docs.github.com/en/actions/reference/authentication-in-a-workflow#permissions-for-the-github_token +name: Annotate CI run with test results +on: + workflow_run: + workflows: ["Continuous Integration"] + types: + - completed +jobs: + annotate: + name: Annotate CI run with test results + runs-on: ubuntu-latest + if: ${{ github.event.workflow_run.conclusion != 'cancelled' }} + strategy: + fail-fast: false + matrix: + os: + - { prettyname: Windows } + - { prettyname: macOS } + - { prettyname: Linux } + threadingMode: ['SingleThread', 'MultiThreaded'] + steps: + - name: Annotate CI run with test results + uses: dorny/test-reporter@v1.4.2 + with: + artifact: osu-test-results-${{matrix.os.prettyname}}-${{matrix.threadingMode}} + name: Test Results (${{matrix.os.prettyname}}, ${{matrix.threadingMode}}) + path: "*.trx" + reporter: dotnet-trx diff --git a/.vscode/launch.json b/.vscode/launch.json index afd997f91d..1b590008cd 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -113,20 +113,6 @@ "cwd": "${workspaceRoot}", "preLaunchTask": "Build benchmarks", "console": "internalConsole" - }, - { - "name": "Cake: Debug Script", - "type": "coreclr", - "request": "launch", - "program": "${workspaceRoot}/build/tools/Cake.CoreCLR/0.30.0/Cake.dll", - "args": [ - "${workspaceRoot}/build/build.cake", - "--debug", - "--verbosity=diagnostic" - ], - "cwd": "${workspaceRoot}/build", - "stopAtEntry": true, - "externalConsole": false } ] } diff --git a/InspectCode.ps1 b/InspectCode.ps1 index 6ed935fdbb..8316f48ff3 100644 --- a/InspectCode.ps1 +++ b/InspectCode.ps1 @@ -1,27 +1,11 @@ -[CmdletBinding()] -Param( - [string]$Target, - [string]$Configuration, - [ValidateSet("Quiet", "Minimal", "Normal", "Verbose", "Diagnostic")] - [string]$Verbosity, - [switch]$ShowDescription, - [Alias("WhatIf", "Noop")] - [switch]$DryRun, - [Parameter(Position = 0, Mandatory = $false, ValueFromRemainingArguments = $true)] - [string[]]$ScriptArgs -) - -# Build Cake arguments -$cakeArguments = ""; -if ($Target) { $cakeArguments += "-target=$Target" } -if ($Configuration) { $cakeArguments += "-configuration=$Configuration" } -if ($Verbosity) { $cakeArguments += "-verbosity=$Verbosity" } -if ($ShowDescription) { $cakeArguments += "-showdescription" } -if ($DryRun) { $cakeArguments += "-dryrun" } -if ($Experimental) { $cakeArguments += "-experimental" } -$cakeArguments += $ScriptArgs - dotnet tool restore -dotnet cake ./build/InspectCode.cake --bootstrap -dotnet cake ./build/InspectCode.cake $cakeArguments -exit $LASTEXITCODE \ No newline at end of file + +# Temporarily disabled until the tool is upgraded to 5.0. + # The version specified in .config/dotnet-tools.json (3.1.37601) won't run on .NET hosts >=5.0.7. + # - cmd: dotnet format --dry-run --check + +dotnet CodeFileSanity +dotnet jb inspectcode "osu.Desktop.slnf" --output="inspectcodereport.xml" --caches-home="inspectcode" --verbosity=WARN +dotnet nvika parsereport "inspectcodereport.xml" --treatwarningsaserrors + +exit $LASTEXITCODE diff --git a/InspectCode.sh b/InspectCode.sh new file mode 100755 index 0000000000..cf2bc18175 --- /dev/null +++ b/InspectCode.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +dotnet tool restore +dotnet CodeFileSanity +dotnet jb inspectcode "osu.Desktop.slnf" --output="inspectcodereport.xml" --caches-home="inspectcode" --verbosity=WARN +dotnet nvika parsereport "inspectcodereport.xml" --treatwarningsaserrors diff --git a/appveyor.yml b/appveyor.yml index 845751ef07..5be73f9875 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,28 +1,27 @@ clone_depth: 1 version: '{branch}-{build}' image: Visual Studio 2019 +cache: + - '%LOCALAPPDATA%\NuGet\v3-cache -> appveyor.yml' + dotnet_csproj: patch: true file: 'osu.Game\osu.Game.csproj' # Use wildcard when it's able to exclude Xamarin projects version: '0.0.{build}' -cache: - - '%LOCALAPPDATA%\NuGet\v3-cache -> appveyor.yml' + before_build: - - ps: dotnet --info # Useful when version mismatch between CI and local - - ps: nuget restore -verbosity quiet # Only nuget.exe knows both new (.NET Core) and old (Xamarin) projects + - cmd: dotnet --info # Useful when version mismatch between CI and local + - cmd: nuget restore -verbosity quiet # Only nuget.exe knows both new (.NET Core) and old (Xamarin) projects + build: project: osu.sln parallel: true verbosity: minimal publish_nuget: true + after_build: - - ps: dotnet tool restore - - # Temporarily disabled until the tool is upgraded to 5.0. - # The version specified in .config/dotnet-tools.json (3.1.37601) won't run on .NET hosts >=5.0.7. - # - ps: dotnet format --dry-run --check - - ps: .\InspectCode.ps1 + test: assemblies: except: diff --git a/build/Desktop.proj b/build/Desktop.proj deleted file mode 100644 index b1c6b065e8..0000000000 --- a/build/Desktop.proj +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/build/InspectCode.cake b/build/InspectCode.cake deleted file mode 100644 index 6836d9071b..0000000000 --- a/build/InspectCode.cake +++ /dev/null @@ -1,41 +0,0 @@ -#addin "nuget:?package=CodeFileSanity&version=0.0.36" - -/////////////////////////////////////////////////////////////////////////////// -// ARGUMENTS -/////////////////////////////////////////////////////////////////////////////// - -var target = Argument("target", "CodeAnalysis"); -var configuration = Argument("configuration", "Release"); - -var rootDirectory = new DirectoryPath(".."); -var sln = rootDirectory.CombineWithFilePath("osu.sln"); -var desktopSlnf = rootDirectory.CombineWithFilePath("osu.Desktop.slnf"); - -/////////////////////////////////////////////////////////////////////////////// -// TASKS -/////////////////////////////////////////////////////////////////////////////// - -Task("InspectCode") - .Does(() => { - var inspectcodereport = "inspectcodereport.xml"; - var cacheDir = "inspectcode"; - var verbosity = AppVeyor.IsRunningOnAppVeyor ? "WARN" : "INFO"; // Don't flood CI output - - DotNetCoreTool(rootDirectory.FullPath, - "jb", $@"inspectcode ""{desktopSlnf}"" --output=""{inspectcodereport}"" --caches-home=""{cacheDir}"" --verbosity={verbosity}"); - DotNetCoreTool(rootDirectory.FullPath, "nvika", $@"parsereport ""{inspectcodereport}"" --treatwarningsaserrors"); - }); - -Task("CodeFileSanity") - .Does(() => { - ValidateCodeSanity(new ValidateCodeSanitySettings { - RootDirectory = rootDirectory.FullPath, - IsAppveyorBuild = AppVeyor.IsRunningOnAppVeyor - }); - }); - -Task("CodeAnalysis") - .IsDependentOn("CodeFileSanity") - .IsDependentOn("InspectCode"); - -RunTarget(target); \ No newline at end of file diff --git a/cake.config b/cake.config deleted file mode 100644 index 187d825591..0000000000 --- a/cake.config +++ /dev/null @@ -1,5 +0,0 @@ - -[Nuget] -Source=https://api.nuget.org/v3/index.json -UseInProcessClient=true -LoadDependencies=true diff --git a/osu.Android.props b/osu.Android.props index c020b1d783..1f60f02fb1 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -51,7 +51,7 @@ - - + + diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModApproachDifferent.cs b/osu.Game.Rulesets.Osu/Mods/OsuModApproachDifferent.cs new file mode 100644 index 0000000000..3e638c4833 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Mods/OsuModApproachDifferent.cs @@ -0,0 +1,102 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using osu.Framework.Bindables; +using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; +using osu.Game.Configuration; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Osu.Objects.Drawables; + +namespace osu.Game.Rulesets.Osu.Mods +{ + public class OsuModApproachDifferent : Mod, IApplicableToDrawableHitObjects + { + public override string Name => "Approach Different"; + public override string Acronym => "AD"; + public override string Description => "Never trust the approach circles..."; + public override double ScoreMultiplier => 1; + public override IconUsage? Icon { get; } = FontAwesome.Regular.Circle; + + [SettingSource("Initial size", "Change the initial size of the approach circle, relative to hit circles.", 0)] + public BindableFloat Scale { get; } = new BindableFloat(4) + { + Precision = 0.1f, + MinValue = 2, + MaxValue = 10, + }; + + [SettingSource("Style", "Change the animation style of the approach circles.", 1)] + public Bindable Style { get; } = new Bindable(); + + public void ApplyToDrawableHitObjects(IEnumerable drawables) + { + drawables.ForEach(drawable => + { + drawable.ApplyCustomUpdateState += (drawableObject, state) => + { + if (!(drawableObject is DrawableHitCircle drawableHitCircle)) return; + + var hitCircle = drawableHitCircle.HitObject; + + drawableHitCircle.ApproachCircle.ClearTransforms(targetMember: nameof(Scale)); + + using (drawableHitCircle.BeginAbsoluteSequence(hitCircle.StartTime - hitCircle.TimePreempt)) + drawableHitCircle.ApproachCircle.ScaleTo(Scale.Value).ScaleTo(1f, hitCircle.TimePreempt, getEasing(Style.Value)); + }; + }); + } + + private Easing getEasing(AnimationStyle style) + { + switch (style) + { + default: + return Easing.None; + + case AnimationStyle.Accelerate1: + return Easing.In; + + case AnimationStyle.Accelerate2: + return Easing.InCubic; + + case AnimationStyle.Accelerate3: + return Easing.InQuint; + + case AnimationStyle.Gravity: + return Easing.InBack; + + case AnimationStyle.Decelerate1: + return Easing.Out; + + case AnimationStyle.Decelerate2: + return Easing.OutCubic; + + case AnimationStyle.Decelerate3: + return Easing.OutQuint; + + case AnimationStyle.InOut1: + return Easing.InOutCubic; + + case AnimationStyle.InOut2: + return Easing.InOutQuint; + } + } + + public enum AnimationStyle + { + Gravity, + InOut1, + InOut2, + Accelerate1, + Accelerate2, + Accelerate3, + Decelerate1, + Decelerate2, + Decelerate3, + } + } +} diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index b50d3ad2b4..1b9bcd19fd 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -187,6 +187,7 @@ namespace osu.Game.Rulesets.Osu new MultiMod(new ModWindUp(), new ModWindDown()), new OsuModTraceable(), new OsuModBarrelRoll(), + new OsuModApproachDifferent(), }; case ModType.System: diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyOldStyleSpinner.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyOldStyleSpinner.cs index 19cb55c16e..d80e061662 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyOldStyleSpinner.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyOldStyleSpinner.cs @@ -12,6 +12,7 @@ using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Skinning; using osuTK; +using osuTK.Graphics; namespace osu.Game.Rulesets.Osu.Skinning.Legacy { @@ -40,6 +41,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy Anchor = Anchor.TopCentre, Origin = Anchor.Centre, Texture = source.GetTexture("spinner-background"), + Colour = source.GetConfig(OsuSkinColour.SpinnerBackground)?.Value ?? new Color4(100, 100, 100, 255), Scale = new Vector2(SPRITE_SCALE), Y = SPINNER_Y_CENTRE, }, diff --git a/osu.Game.Rulesets.Osu/Skinning/OsuSkinColour.cs b/osu.Game.Rulesets.Osu/Skinning/OsuSkinColour.cs index 4e6d3ef0e4..f7ba8b9fc4 100644 --- a/osu.Game.Rulesets.Osu/Skinning/OsuSkinColour.cs +++ b/osu.Game.Rulesets.Osu/Skinning/OsuSkinColour.cs @@ -7,6 +7,7 @@ namespace osu.Game.Rulesets.Osu.Skinning { SliderTrackOverride, SliderBorder, - SliderBall + SliderBall, + SpinnerBackground, } } diff --git a/osu.Game.Rulesets.Taiko.Tests/Mods/TaikoModTestScene.cs b/osu.Game.Rulesets.Taiko.Tests/Mods/TaikoModTestScene.cs new file mode 100644 index 0000000000..3090facf8c --- /dev/null +++ b/osu.Game.Rulesets.Taiko.Tests/Mods/TaikoModTestScene.cs @@ -0,0 +1,12 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Tests.Visual; + +namespace osu.Game.Rulesets.Taiko.Tests.Mods +{ + public abstract class TaikoModTestScene : ModTestScene + { + protected sealed override Ruleset CreatePlayerRuleset() => new TaikoRuleset(); + } +} diff --git a/osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModHidden.cs b/osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModHidden.cs new file mode 100644 index 0000000000..7abbb9d186 --- /dev/null +++ b/osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModHidden.cs @@ -0,0 +1,24 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using NUnit.Framework; +using osu.Game.Rulesets.Taiko.Mods; + +namespace osu.Game.Rulesets.Taiko.Tests.Mods +{ + public class TestSceneTaikoModHidden : TaikoModTestScene + { + [Test] + public void TestDefaultBeatmapTest() => CreateModTest(new ModTestData + { + Mod = new TaikoModHidden(), + Autoplay = true, + PassCondition = checkSomeAutoplayHits + }); + + private bool checkSomeAutoplayHits() + => Player.ScoreProcessor.JudgedHits >= 4 + && Player.Results.All(result => result.Type == result.Judgement.MaxResult); + } +} diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs index 7739ecaf5b..0fd3625a93 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs @@ -1,23 +1,93 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Framework.Graphics; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Scoring; +using osu.Game.Rulesets.Taiko.Objects.Drawables; namespace osu.Game.Rulesets.Taiko.Mods { - public class TaikoModHidden : ModHidden + public class TaikoModHidden : ModHidden, IApplicableToDifficulty { public override string Description => @"Beats fade out before you hit them!"; public override double ScoreMultiplier => 1.06; - public override bool HasImplementation => false; + + /// + /// In osu-stable, the hit position is 160, so the active playfield is essentially 160 pixels shorter + /// than the actual screen width. The normalized playfield height is 480, so on a 4:3 screen the + /// playfield ratio of the active area up to the hit position will actually be (640 - 160) / 480 = 1. + /// For custom resolutions/aspect ratios (x:y), the screen width given the normalized height becomes 480 * x / y instead, + /// and the playfield ratio becomes (480 * x / y - 160) / 480 = x / y - 1/3. + /// This constant is equal to the playfield ratio on 4:3 screens divided by the playfield ratio on 16:9 screens. + /// + private const double hd_sv_scale = (4.0 / 3.0 - 1.0 / 3.0) / (16.0 / 9.0 - 1.0 / 3.0); + + private double originalSliderMultiplier; + + private ControlPointInfo controlPointInfo; protected override void ApplyIncreasedVisibilityState(DrawableHitObject hitObject, ArmedState state) { + ApplyNormalVisibilityState(hitObject, state); + } + + protected double MultiplierAt(double position) + { + double beatLength = controlPointInfo.TimingPointAt(position).BeatLength; + double speedMultiplier = controlPointInfo.DifficultyPointAt(position).SpeedMultiplier; + + return originalSliderMultiplier * speedMultiplier * TimingControlPoint.DEFAULT_BEAT_LENGTH / beatLength; } protected override void ApplyNormalVisibilityState(DrawableHitObject hitObject, ArmedState state) { + switch (hitObject) + { + case DrawableDrumRollTick _: + case DrawableHit _: + double preempt = 10000 / MultiplierAt(hitObject.HitObject.StartTime); + double start = hitObject.HitObject.StartTime - preempt * 0.6; + double duration = preempt * 0.3; + + using (hitObject.BeginAbsoluteSequence(start)) + { + hitObject.FadeOut(duration); + + // DrawableHitObject sets LifetimeEnd to LatestTransformEndTime if it isn't manually changed. + // in order for the object to not be killed before its actual end time (as the latest transform ends earlier), set lifetime end explicitly. + hitObject.LifetimeEnd = state == ArmedState.Idle || !hitObject.AllJudged + ? hitObject.HitObject.GetEndTime() + hitObject.HitObject.HitWindows.WindowFor(HitResult.Miss) + : hitObject.HitStateUpdateTime; + } + + break; + } + } + + public void ReadFromDifficulty(BeatmapDifficulty difficulty) + { + } + + public void ApplyToDifficulty(BeatmapDifficulty difficulty) + { + // needs to be read after all processing has been run (TaikoBeatmapConverter applies an adjustment which would otherwise be omitted). + originalSliderMultiplier = difficulty.SliderMultiplier; + + // osu-stable has an added playfield cover that essentially forces a 4:3 playfield ratio, by cutting off all objects past that size. + // This is not yet implemented; instead a playfield adjustment container is present which maintains a 16:9 ratio. + // For now, increase the slider multiplier proportionally so that the notes stay on the screen for the same amount of time as on stable. + // Note that this means that the notes will scroll faster as they have a longer distance to travel on the screen in that same amount of time. + difficulty.SliderMultiplier /= hd_sv_scale; + } + + public override void ApplyToBeatmap(IBeatmap beatmap) + { + controlPointInfo = beatmap.ControlPointInfo; } } } diff --git a/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs b/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs index a47631a83b..8f5ebf53bd 100644 --- a/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs +++ b/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs @@ -113,7 +113,6 @@ namespace osu.Game.Tests.Collections.IO await importCollectionsFromStream(osu, ms); } - Assert.That(host.UpdateThread.Running, Is.True); Assert.That(exceptionThrown, Is.False); Assert.That(osu.CollectionManager.Collections.Count, Is.EqualTo(0)); } diff --git a/osu.Game.Tests/Visual/Background/TestSceneSeasonalBackgroundLoader.cs b/osu.Game.Tests/Visual/Background/TestSceneSeasonalBackgroundLoader.cs index dc5a4f4a3e..0bd1263076 100644 --- a/osu.Game.Tests/Visual/Background/TestSceneSeasonalBackgroundLoader.cs +++ b/osu.Game.Tests/Visual/Background/TestSceneSeasonalBackgroundLoader.cs @@ -161,15 +161,18 @@ namespace osu.Game.Tests.Visual.Background private void loadNextBackground() { + SeasonalBackground previousBackground = null; SeasonalBackground background = null; AddStep("create next background", () => { + previousBackground = (SeasonalBackground)backgroundContainer.SingleOrDefault(); background = backgroundLoader.LoadNextBackground(); LoadComponentAsync(background, bg => backgroundContainer.Child = bg); }); AddUntilStep("background loaded", () => background.IsLoaded); + AddAssert("background is different", () => !background.Equals(previousBackground)); } private void assertAnyBackground() diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs index d3a4b635f5..25d0843a71 100644 --- a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs +++ b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using JetBrains.Annotations; using Newtonsoft.Json; using osu.Framework.Bindables; using osu.Framework.Lists; @@ -66,6 +67,7 @@ namespace osu.Game.Beatmaps.ControlPoints /// /// The time to find the difficulty control point at. /// The difficulty control point. + [NotNull] public DifficultyControlPoint DifficultyPointAt(double time) => binarySearchWithFallback(DifficultyPoints, time, DifficultyControlPoint.DEFAULT); /// @@ -73,6 +75,7 @@ namespace osu.Game.Beatmaps.ControlPoints /// /// The time to find the effect control point at. /// The effect control point. + [NotNull] public EffectControlPoint EffectPointAt(double time) => binarySearchWithFallback(EffectPoints, time, EffectControlPoint.DEFAULT); /// @@ -80,6 +83,7 @@ namespace osu.Game.Beatmaps.ControlPoints /// /// The time to find the sound control point at. /// The sound control point. + [NotNull] public SampleControlPoint SamplePointAt(double time) => binarySearchWithFallback(SamplePoints, time, SamplePoints.Count > 0 ? SamplePoints[0] : SampleControlPoint.DEFAULT); /// @@ -87,6 +91,7 @@ namespace osu.Game.Beatmaps.ControlPoints /// /// The time to find the timing control point at. /// The timing control point. + [NotNull] public TimingControlPoint TimingPointAt(double time) => binarySearchWithFallback(TimingPoints, time, TimingPoints.Count > 0 ? TimingPoints[0] : TimingControlPoint.DEFAULT); /// diff --git a/osu.Game/Beatmaps/DifficultyRecommender.cs b/osu.Game/Beatmaps/DifficultyRecommender.cs index 340c47d89b..ca910e70b8 100644 --- a/osu.Game/Beatmaps/DifficultyRecommender.cs +++ b/osu.Game/Beatmaps/DifficultyRecommender.cs @@ -101,10 +101,20 @@ namespace osu.Game.Beatmaps /// Rulesets ordered descending by their respective recommended difficulties. /// The currently selected ruleset will always be first. /// - private IEnumerable orderedRulesets => - recommendedDifficultyMapping - .OrderByDescending(pair => pair.Value).Select(pair => pair.Key).Where(r => !r.Equals(ruleset.Value)) - .Prepend(ruleset.Value); + private IEnumerable orderedRulesets + { + get + { + if (LoadState < LoadState.Ready || ruleset.Value == null) + return Enumerable.Empty(); + + return recommendedDifficultyMapping + .OrderByDescending(pair => pair.Value) + .Select(pair => pair.Key) + .Where(r => !r.Equals(ruleset.Value)) + .Prepend(ruleset.Value); + } + } private void onlineStateChanged(ValueChangedEvent state) => Schedule(() => { diff --git a/osu.Game/Extensions/LanguageExtensions.cs b/osu.Game/Extensions/LanguageExtensions.cs new file mode 100644 index 0000000000..b67e7fb6fc --- /dev/null +++ b/osu.Game/Extensions/LanguageExtensions.cs @@ -0,0 +1,33 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Globalization; +using osu.Game.Localisation; + +namespace osu.Game.Extensions +{ + /// + /// Conversion utilities for the enum. + /// + public static class LanguageExtensions + { + /// + /// Returns the culture code of the that corresponds to the supplied . + /// + /// + /// This is required as enum member names are not allowed to contain hyphens. + /// + public static string ToCultureCode(this Language language) + => language.ToString().Replace("_", "-"); + + /// + /// Attempts to parse the supplied to a value. + /// + /// The code of the culture to parse. + /// The parsed . Valid only if the return value of the method is . + /// Whether the parsing succeeded. + public static bool TryParseCultureCode(string cultureCode, out Language language) + => Enum.TryParse(cultureCode.Replace("-", "_"), out language); + } +} diff --git a/osu.Game/Graphics/Backgrounds/SeasonalBackgroundLoader.cs b/osu.Game/Graphics/Backgrounds/SeasonalBackgroundLoader.cs index a48da37804..f01a26a3a8 100644 --- a/osu.Game/Graphics/Backgrounds/SeasonalBackgroundLoader.cs +++ b/osu.Game/Graphics/Backgrounds/SeasonalBackgroundLoader.cs @@ -99,5 +99,14 @@ namespace osu.Game.Graphics.Backgrounds // ensure we're not loading in without a transition. this.FadeInFromZero(200, Easing.InOutSine); } + + public override bool Equals(Background other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + + return other.GetType() == GetType() + && ((SeasonalBackground)other).url == url; + } } } diff --git a/osu.Game/Graphics/UserInterface/OsuTabControl.cs b/osu.Game/Graphics/UserInterface/OsuTabControl.cs index dbcce9a84a..0c220336a5 100644 --- a/osu.Game/Graphics/UserInterface/OsuTabControl.cs +++ b/osu.Game/Graphics/UserInterface/OsuTabControl.cs @@ -160,7 +160,7 @@ namespace osu.Game.Graphics.UserInterface Margin = new MarginPadding { Top = 5, Bottom = 5 }, Origin = Anchor.BottomLeft, Anchor = Anchor.BottomLeft, - Text = (value as IHasDescription)?.Description ?? (value as Enum)?.GetDescription() ?? value.ToString(), + Text = (value as IHasDescription)?.Description ?? (value as Enum)?.GetLocalisableDescription() ?? value.ToString(), Font = OsuFont.GetFont(size: 14) }, Bar = new Box diff --git a/osu.Game/Graphics/UserInterface/PageTabControl.cs b/osu.Game/Graphics/UserInterface/PageTabControl.cs index d05a08108a..1ba9ad53bb 100644 --- a/osu.Game/Graphics/UserInterface/PageTabControl.cs +++ b/osu.Game/Graphics/UserInterface/PageTabControl.cs @@ -11,6 +11,7 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; +using osu.Framework.Localisation; using osu.Game.Graphics.Sprites; namespace osu.Game.Graphics.UserInterface @@ -81,7 +82,7 @@ namespace osu.Game.Graphics.UserInterface Active.BindValueChanged(active => Text.Font = Text.Font.With(Typeface.Torus, weight: active.NewValue ? FontWeight.Bold : FontWeight.Medium), true); } - protected virtual string CreateText() => (Value as Enum)?.GetDescription() ?? Value.ToString(); + protected virtual LocalisableString CreateText() => (Value as Enum)?.GetLocalisableDescription() ?? Value.ToString(); protected override bool OnHover(HoverEvent e) { diff --git a/osu.Game/Localisation/Language.cs b/osu.Game/Localisation/Language.cs index a3e845f229..96bfde8596 100644 --- a/osu.Game/Localisation/Language.cs +++ b/osu.Game/Localisation/Language.cs @@ -10,7 +10,104 @@ namespace osu.Game.Localisation [Description(@"English")] en, + // TODO: Requires Arabic glyphs to be added to resources (and possibly also RTL support). + // [Description(@"اَلْعَرَبِيَّةُ")] + // ar, + + // TODO: Some accented glyphs are missing. Revisit when adding Inter. + // [Description(@"Беларуская мова")] + // be, + + [Description(@"Български")] + bg, + + [Description(@"Česky")] + cs, + + [Description(@"Dansk")] + da, + + [Description(@"Deutsch")] + de, + + // TODO: Some accented glyphs are missing. Revisit when adding Inter. + // [Description(@"Ελληνικά")] + // el, + + [Description(@"español")] + es, + + [Description(@"Suomi")] + fi, + + [Description(@"français")] + fr, + + [Description(@"Magyar")] + hu, + + [Description(@"Bahasa Indonesia")] + id, + + [Description(@"Italiano")] + it, + [Description(@"日本語")] - ja + ja, + + [Description(@"한국어")] + ko, + + [Description(@"Nederlands")] + nl, + + [Description(@"Norsk")] + no, + + [Description(@"polski")] + pl, + + [Description(@"Português")] + pt, + + [Description(@"Português (Brasil)")] + pt_br, + + [Description(@"Română")] + ro, + + [Description(@"Русский")] + ru, + + [Description(@"Slovenčina")] + sk, + + [Description(@"Svenska")] + sv, + + [Description(@"ไทย")] + th, + + [Description(@"Tagalog")] + tl, + + [Description(@"Türkçe")] + tr, + + // TODO: Some accented glyphs are missing. Revisit when adding Inter. + // [Description(@"Українська мова")] + // uk, + + [Description(@"Tiếng Việt")] + vi, + + [Description(@"简体中文")] + zh, + + [Description(@"繁體中文(香港)")] + zh_hk, + + [Description(@"繁體中文(台灣)")] + zh_tw } } diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 019d3b3cd0..0c4d035728 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -50,8 +50,10 @@ using osu.Game.Updater; using osu.Game.Utils; using LogLevel = osu.Framework.Logging.LogLevel; using osu.Game.Database; +using osu.Game.Extensions; using osu.Game.IO; using osu.Game.Localisation; +using osu.Game.Performance; using osu.Game.Skinning.Editor; namespace osu.Game @@ -426,9 +428,12 @@ namespace osu.Game { // The given ScoreInfo may have missing properties if it was retrieved from online data. Re-retrieve it from the database // to ensure all the required data for presenting a replay are present. - var databasedScoreInfo = score.OnlineScoreID != null - ? ScoreManager.Query(s => s.OnlineScoreID == score.OnlineScoreID) - : ScoreManager.Query(s => s.Hash == score.Hash); + ScoreInfo databasedScoreInfo = null; + + if (score.OnlineScoreID != null) + databasedScoreInfo = ScoreManager.Query(s => s.OnlineScoreID == score.OnlineScoreID); + + databasedScoreInfo ??= ScoreManager.Query(s => s.Hash == score.Hash); if (databasedScoreInfo == null) { @@ -484,6 +489,8 @@ namespace osu.Game protected virtual UpdateManager CreateUpdateManager() => new UpdateManager(); + protected virtual HighPerformanceSession CreateHighPerformanceSession() => new HighPerformanceSession(); + protected override Container CreateScalingContainer() => new ScalingContainer(ScalingMode.Everything); #region Beatmap progression @@ -577,7 +584,7 @@ namespace osu.Game foreach (var language in Enum.GetValues(typeof(Language)).OfType()) { - var cultureCode = language.ToString(); + var cultureCode = language.ToCultureCode(); Localisation.AddLanguage(cultureCode, new ResourceManagerLocalisationStore(cultureCode)); } @@ -712,7 +719,6 @@ namespace osu.Game PostNotification = n => notifications.Post(n), }, Add, true); - loadComponentSingleFile(difficultyRecommender, Add); loadComponentSingleFile(stableImportManager, Add); loadComponentSingleFile(screenshotManager, Add); @@ -753,8 +759,11 @@ namespace osu.Game loadComponentSingleFile(new AccountCreationOverlay(), topMostOverlayContent.Add, true); loadComponentSingleFile(new DialogOverlay(), topMostOverlayContent.Add, true); + loadComponentSingleFile(CreateHighPerformanceSession(), Add); + chatOverlay.State.ValueChanged += state => channelManager.HighPollRate.Value = state.NewValue == Visibility.Visible; + Add(difficultyRecommender); Add(externalLinkOpener = new ExternalLinkOpener()); Add(new MusicKeyBindingHandler()); diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs b/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs index 97ccb66599..0626f236b8 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs @@ -14,6 +14,7 @@ using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; +using osu.Game.Resources.Localisation.Web; using osuTK.Graphics; using osu.Game.Rulesets; using osu.Game.Scoring; @@ -126,15 +127,15 @@ namespace osu.Game.Overlays.BeatmapListing Padding = new MarginPadding { Horizontal = 10 }, Children = new Drawable[] { - generalFilter = new BeatmapSearchMultipleSelectionFilterRow(@"General"), + generalFilter = new BeatmapSearchMultipleSelectionFilterRow(BeatmapsStrings.ListingSearchFiltersGeneral), modeFilter = new BeatmapSearchRulesetFilterRow(), - categoryFilter = new BeatmapSearchFilterRow(@"Categories"), - genreFilter = new BeatmapSearchFilterRow(@"Genre"), - languageFilter = new BeatmapSearchFilterRow(@"Language"), - extraFilter = new BeatmapSearchMultipleSelectionFilterRow(@"Extra"), + categoryFilter = new BeatmapSearchFilterRow(BeatmapsStrings.ListingSearchFiltersStatus), + genreFilter = new BeatmapSearchFilterRow(BeatmapsStrings.ListingSearchFiltersGenre), + languageFilter = new BeatmapSearchFilterRow(BeatmapsStrings.ListingSearchFiltersLanguage), + extraFilter = new BeatmapSearchMultipleSelectionFilterRow(BeatmapsStrings.ListingSearchFiltersExtra), ranksFilter = new BeatmapSearchScoreFilterRow(), - playedFilter = new BeatmapSearchFilterRow(@"Played"), - explicitContentFilter = new BeatmapSearchFilterRow(@"Explicit Content"), + playedFilter = new BeatmapSearchFilterRow(BeatmapsStrings.ListingSearchFiltersPlayed), + explicitContentFilter = new BeatmapSearchFilterRow(BeatmapsStrings.ListingSearchFiltersNsfw), } } } @@ -172,7 +173,7 @@ namespace osu.Game.Overlays.BeatmapListing public BeatmapSearchTextBox() { - PlaceholderText = @"type in keywords..."; + PlaceholderText = BeatmapsStrings.ListingSearchPrompt; } protected override bool OnKeyDown(KeyDownEvent e) diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapSearchFilterRow.cs b/osu.Game/Overlays/BeatmapListing/BeatmapSearchFilterRow.cs index 01bcbd3244..4c831543fe 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapSearchFilterRow.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapSearchFilterRow.cs @@ -11,8 +11,8 @@ using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osuTK; -using Humanizer; using osu.Framework.Extensions.EnumExtensions; +using osu.Framework.Localisation; namespace osu.Game.Overlays.BeatmapListing { @@ -26,7 +26,7 @@ namespace osu.Game.Overlays.BeatmapListing set => current.Current = value; } - public BeatmapSearchFilterRow(string headerName) + public BeatmapSearchFilterRow(LocalisableString header) { Drawable filter; AutoSizeAxes = Axes.Y; @@ -53,7 +53,7 @@ namespace osu.Game.Overlays.BeatmapListing Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft, Font = OsuFont.GetFont(size: 13), - Text = headerName.Titleize() + Text = header }, filter = CreateFilter() } diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs b/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs index 5dfa8e6109..e0632ace58 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs @@ -9,6 +9,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Events; +using osu.Framework.Localisation; using osuTK; namespace osu.Game.Overlays.BeatmapListing @@ -19,8 +20,8 @@ namespace osu.Game.Overlays.BeatmapListing private MultipleSelectionFilter filter; - public BeatmapSearchMultipleSelectionFilterRow(string headerName) - : base(headerName) + public BeatmapSearchMultipleSelectionFilterRow(LocalisableString header) + : base(header) { Current.BindTo(filter.Current); } diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapSearchRulesetFilterRow.cs b/osu.Game/Overlays/BeatmapListing/BeatmapSearchRulesetFilterRow.cs index a8dc088e52..c2d0eea80c 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapSearchRulesetFilterRow.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapSearchRulesetFilterRow.cs @@ -3,6 +3,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Game.Resources.Localisation.Web; using osu.Game.Rulesets; namespace osu.Game.Overlays.BeatmapListing @@ -10,7 +11,7 @@ namespace osu.Game.Overlays.BeatmapListing public class BeatmapSearchRulesetFilterRow : BeatmapSearchFilterRow { public BeatmapSearchRulesetFilterRow() - : base(@"Mode") + : base(BeatmapsStrings.ListingSearchFiltersMode) { } diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapSearchScoreFilterRow.cs b/osu.Game/Overlays/BeatmapListing/BeatmapSearchScoreFilterRow.cs index 804962adfb..b39934b56f 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapSearchScoreFilterRow.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapSearchScoreFilterRow.cs @@ -4,6 +4,8 @@ using System.Collections.Generic; using System.Linq; using osu.Framework.Extensions; +using osu.Framework.Localisation; +using osu.Game.Resources.Localisation.Web; using osu.Game.Scoring; namespace osu.Game.Overlays.BeatmapListing @@ -11,7 +13,7 @@ namespace osu.Game.Overlays.BeatmapListing public class BeatmapSearchScoreFilterRow : BeatmapSearchMultipleSelectionFilterRow { public BeatmapSearchScoreFilterRow() - : base(@"Rank Achieved") + : base(BeatmapsStrings.ListingSearchFiltersRank) { } @@ -31,20 +33,7 @@ namespace osu.Game.Overlays.BeatmapListing { } - protected override string LabelFor(ScoreRank value) - { - switch (value) - { - case ScoreRank.XH: - return @"Silver SS"; - - case ScoreRank.SH: - return @"Silver S"; - - default: - return value.GetDescription(); - } - } + protected override LocalisableString LabelFor(ScoreRank value) => value.GetLocalisableDescription(); } } } diff --git a/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs b/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs index f02b515755..46cb1e822f 100644 --- a/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs +++ b/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs @@ -7,6 +7,7 @@ using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; +using osu.Framework.Localisation; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; @@ -66,7 +67,7 @@ namespace osu.Game.Overlays.BeatmapListing /// /// Returns the label text to be used for the supplied . /// - protected virtual string LabelFor(T value) => (value as Enum)?.GetDescription() ?? value.ToString(); + protected virtual LocalisableString LabelFor(T value) => (value as Enum)?.GetLocalisableDescription() ?? value.ToString(); private void updateState() { diff --git a/osu.Game/Overlays/BeatmapListing/SearchCategory.cs b/osu.Game/Overlays/BeatmapListing/SearchCategory.cs index 84859bf5b5..8a9df76af3 100644 --- a/osu.Game/Overlays/BeatmapListing/SearchCategory.cs +++ b/osu.Game/Overlays/BeatmapListing/SearchCategory.cs @@ -1,10 +1,14 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.ComponentModel; +using osu.Framework.Localisation; +using osu.Game.Resources.Localisation.Web; namespace osu.Game.Overlays.BeatmapListing { + [LocalisableEnum(typeof(SearchCategoryEnumLocalisationMapper))] public enum SearchCategory { Any, @@ -23,4 +27,43 @@ namespace osu.Game.Overlays.BeatmapListing [Description("My Maps")] Mine, } + + public class SearchCategoryEnumLocalisationMapper : EnumLocalisationMapper + { + public override LocalisableString Map(SearchCategory value) + { + switch (value) + { + case SearchCategory.Any: + return BeatmapsStrings.StatusAny; + + case SearchCategory.Leaderboard: + return BeatmapsStrings.StatusLeaderboard; + + case SearchCategory.Ranked: + return BeatmapsStrings.StatusRanked; + + case SearchCategory.Qualified: + return BeatmapsStrings.StatusQualified; + + case SearchCategory.Loved: + return BeatmapsStrings.StatusLoved; + + case SearchCategory.Favourites: + return BeatmapsStrings.StatusFavourites; + + case SearchCategory.Pending: + return BeatmapsStrings.StatusPending; + + case SearchCategory.Graveyard: + return BeatmapsStrings.StatusGraveyard; + + case SearchCategory.Mine: + return BeatmapsStrings.StatusMine; + + default: + throw new ArgumentOutOfRangeException(nameof(value), value, null); + } + } + } } diff --git a/osu.Game/Overlays/BeatmapListing/SearchExplicit.cs b/osu.Game/Overlays/BeatmapListing/SearchExplicit.cs index 3e57cdd48c..78e6a4e094 100644 --- a/osu.Game/Overlays/BeatmapListing/SearchExplicit.cs +++ b/osu.Game/Overlays/BeatmapListing/SearchExplicit.cs @@ -1,11 +1,34 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; +using osu.Framework.Localisation; +using osu.Game.Resources.Localisation.Web; + namespace osu.Game.Overlays.BeatmapListing { + [LocalisableEnum(typeof(SearchExplicitEnumLocalisationMapper))] public enum SearchExplicit { Hide, Show } + + public class SearchExplicitEnumLocalisationMapper : EnumLocalisationMapper + { + public override LocalisableString Map(SearchExplicit value) + { + switch (value) + { + case SearchExplicit.Hide: + return BeatmapsStrings.NsfwExclude; + + case SearchExplicit.Show: + return BeatmapsStrings.NsfwInclude; + + default: + throw new ArgumentOutOfRangeException(nameof(value), value, null); + } + } + } } diff --git a/osu.Game/Overlays/BeatmapListing/SearchExtra.cs b/osu.Game/Overlays/BeatmapListing/SearchExtra.cs index af37e3264f..4b3fb6e833 100644 --- a/osu.Game/Overlays/BeatmapListing/SearchExtra.cs +++ b/osu.Game/Overlays/BeatmapListing/SearchExtra.cs @@ -1,10 +1,14 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.ComponentModel; +using osu.Framework.Localisation; +using osu.Game.Resources.Localisation.Web; namespace osu.Game.Overlays.BeatmapListing { + [LocalisableEnum(typeof(SearchExtraEnumLocalisationMapper))] public enum SearchExtra { [Description("Has Video")] @@ -13,4 +17,22 @@ namespace osu.Game.Overlays.BeatmapListing [Description("Has Storyboard")] Storyboard } + + public class SearchExtraEnumLocalisationMapper : EnumLocalisationMapper + { + public override LocalisableString Map(SearchExtra value) + { + switch (value) + { + case SearchExtra.Video: + return BeatmapsStrings.ExtraVideo; + + case SearchExtra.Storyboard: + return BeatmapsStrings.ExtraStoryboard; + + default: + throw new ArgumentOutOfRangeException(nameof(value), value, null); + } + } + } } diff --git a/osu.Game/Overlays/BeatmapListing/SearchGeneral.cs b/osu.Game/Overlays/BeatmapListing/SearchGeneral.cs index 175942c626..b4c629f7fa 100644 --- a/osu.Game/Overlays/BeatmapListing/SearchGeneral.cs +++ b/osu.Game/Overlays/BeatmapListing/SearchGeneral.cs @@ -1,10 +1,14 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.ComponentModel; +using osu.Framework.Localisation; +using osu.Game.Resources.Localisation.Web; namespace osu.Game.Overlays.BeatmapListing { + [LocalisableEnum(typeof(SearchGeneralEnumLocalisationMapper))] public enum SearchGeneral { [Description("Recommended difficulty")] @@ -16,4 +20,25 @@ namespace osu.Game.Overlays.BeatmapListing [Description("Subscribed mappers")] Follows } + + public class SearchGeneralEnumLocalisationMapper : EnumLocalisationMapper + { + public override LocalisableString Map(SearchGeneral value) + { + switch (value) + { + case SearchGeneral.Recommended: + return BeatmapsStrings.GeneralRecommended; + + case SearchGeneral.Converts: + return BeatmapsStrings.GeneralConverts; + + case SearchGeneral.Follows: + return BeatmapsStrings.GeneralFollows; + + default: + throw new ArgumentOutOfRangeException(nameof(value), value, null); + } + } + } } diff --git a/osu.Game/Overlays/BeatmapListing/SearchGenre.cs b/osu.Game/Overlays/BeatmapListing/SearchGenre.cs index de437fac3e..b2709ecd2e 100644 --- a/osu.Game/Overlays/BeatmapListing/SearchGenre.cs +++ b/osu.Game/Overlays/BeatmapListing/SearchGenre.cs @@ -1,10 +1,14 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.ComponentModel; +using osu.Framework.Localisation; +using osu.Game.Resources.Localisation.Web; namespace osu.Game.Overlays.BeatmapListing { + [LocalisableEnum(typeof(SearchGenreEnumLocalisationMapper))] public enum SearchGenre { Any = 0, @@ -26,4 +30,58 @@ namespace osu.Game.Overlays.BeatmapListing Folk = 13, Jazz = 14 } + + public class SearchGenreEnumLocalisationMapper : EnumLocalisationMapper + { + public override LocalisableString Map(SearchGenre value) + { + switch (value) + { + case SearchGenre.Any: + return BeatmapsStrings.GenreAny; + + case SearchGenre.Unspecified: + return BeatmapsStrings.GenreUnspecified; + + case SearchGenre.VideoGame: + return BeatmapsStrings.GenreVideoGame; + + case SearchGenre.Anime: + return BeatmapsStrings.GenreAnime; + + case SearchGenre.Rock: + return BeatmapsStrings.GenreRock; + + case SearchGenre.Pop: + return BeatmapsStrings.GenrePop; + + case SearchGenre.Other: + return BeatmapsStrings.GenreOther; + + case SearchGenre.Novelty: + return BeatmapsStrings.GenreNovelty; + + case SearchGenre.HipHop: + return BeatmapsStrings.GenreHipHop; + + case SearchGenre.Electronic: + return BeatmapsStrings.GenreElectronic; + + case SearchGenre.Metal: + return BeatmapsStrings.GenreMetal; + + case SearchGenre.Classical: + return BeatmapsStrings.GenreClassical; + + case SearchGenre.Folk: + return BeatmapsStrings.GenreFolk; + + case SearchGenre.Jazz: + return BeatmapsStrings.GenreJazz; + + default: + throw new ArgumentOutOfRangeException(nameof(value), value, null); + } + } + } } diff --git a/osu.Game/Overlays/BeatmapListing/SearchLanguage.cs b/osu.Game/Overlays/BeatmapListing/SearchLanguage.cs index 015cee8ce3..fc176c305a 100644 --- a/osu.Game/Overlays/BeatmapListing/SearchLanguage.cs +++ b/osu.Game/Overlays/BeatmapListing/SearchLanguage.cs @@ -1,10 +1,14 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; +using osu.Framework.Localisation; using osu.Framework.Utils; +using osu.Game.Resources.Localisation.Web; namespace osu.Game.Overlays.BeatmapListing { + [LocalisableEnum(typeof(SearchLanguageEnumLocalisationMapper))] [HasOrderedElements] public enum SearchLanguage { @@ -53,4 +57,61 @@ namespace osu.Game.Overlays.BeatmapListing [Order(13)] Other } + + public class SearchLanguageEnumLocalisationMapper : EnumLocalisationMapper + { + public override LocalisableString Map(SearchLanguage value) + { + switch (value) + { + case SearchLanguage.Any: + return BeatmapsStrings.LanguageAny; + + case SearchLanguage.Unspecified: + return BeatmapsStrings.LanguageUnspecified; + + case SearchLanguage.English: + return BeatmapsStrings.LanguageEnglish; + + case SearchLanguage.Japanese: + return BeatmapsStrings.LanguageJapanese; + + case SearchLanguage.Chinese: + return BeatmapsStrings.LanguageChinese; + + case SearchLanguage.Instrumental: + return BeatmapsStrings.LanguageInstrumental; + + case SearchLanguage.Korean: + return BeatmapsStrings.LanguageKorean; + + case SearchLanguage.French: + return BeatmapsStrings.LanguageFrench; + + case SearchLanguage.German: + return BeatmapsStrings.LanguageGerman; + + case SearchLanguage.Swedish: + return BeatmapsStrings.LanguageSwedish; + + case SearchLanguage.Spanish: + return BeatmapsStrings.LanguageSpanish; + + case SearchLanguage.Italian: + return BeatmapsStrings.LanguageItalian; + + case SearchLanguage.Russian: + return BeatmapsStrings.LanguageRussian; + + case SearchLanguage.Polish: + return BeatmapsStrings.LanguagePolish; + + case SearchLanguage.Other: + return BeatmapsStrings.LanguageOther; + + default: + throw new ArgumentOutOfRangeException(nameof(value), value, null); + } + } + } } diff --git a/osu.Game/Overlays/BeatmapListing/SearchPlayed.cs b/osu.Game/Overlays/BeatmapListing/SearchPlayed.cs index eb7fb46158..f24cf46c2d 100644 --- a/osu.Game/Overlays/BeatmapListing/SearchPlayed.cs +++ b/osu.Game/Overlays/BeatmapListing/SearchPlayed.cs @@ -1,12 +1,38 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; +using osu.Framework.Localisation; +using osu.Game.Resources.Localisation.Web; + namespace osu.Game.Overlays.BeatmapListing { + [LocalisableEnum(typeof(SearchPlayedEnumLocalisationMapper))] public enum SearchPlayed { Any, Played, Unplayed } + + public class SearchPlayedEnumLocalisationMapper : EnumLocalisationMapper + { + public override LocalisableString Map(SearchPlayed value) + { + switch (value) + { + case SearchPlayed.Any: + return BeatmapsStrings.PlayedAny; + + case SearchPlayed.Played: + return BeatmapsStrings.PlayedPlayed; + + case SearchPlayed.Unplayed: + return BeatmapsStrings.PlayedUnplayed; + + default: + throw new ArgumentOutOfRangeException(nameof(value), value, null); + } + } + } } diff --git a/osu.Game/Overlays/BeatmapListing/SortCriteria.cs b/osu.Game/Overlays/BeatmapListing/SortCriteria.cs index e409cbdda7..5ea885eecc 100644 --- a/osu.Game/Overlays/BeatmapListing/SortCriteria.cs +++ b/osu.Game/Overlays/BeatmapListing/SortCriteria.cs @@ -1,8 +1,13 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; +using osu.Framework.Localisation; +using osu.Game.Resources.Localisation.Web; + namespace osu.Game.Overlays.BeatmapListing { + [LocalisableEnum(typeof(SortCriteriaLocalisationMapper))] public enum SortCriteria { Title, @@ -14,4 +19,40 @@ namespace osu.Game.Overlays.BeatmapListing Favourites, Relevance } + + public class SortCriteriaLocalisationMapper : EnumLocalisationMapper + { + public override LocalisableString Map(SortCriteria value) + { + switch (value) + { + case SortCriteria.Title: + return BeatmapsStrings.ListingSearchSortingTitle; + + case SortCriteria.Artist: + return BeatmapsStrings.ListingSearchSortingArtist; + + case SortCriteria.Difficulty: + return BeatmapsStrings.ListingSearchSortingDifficulty; + + case SortCriteria.Ranked: + return BeatmapsStrings.ListingSearchSortingRanked; + + case SortCriteria.Rating: + return BeatmapsStrings.ListingSearchSortingRating; + + case SortCriteria.Plays: + return BeatmapsStrings.ListingSearchSortingPlays; + + case SortCriteria.Favourites: + return BeatmapsStrings.ListingSearchSortingFavourites; + + case SortCriteria.Relevance: + return BeatmapsStrings.ListingSearchSortingRelevance; + + default: + throw new ArgumentOutOfRangeException(nameof(value), value, null); + } + } + } } diff --git a/osu.Game/Overlays/BeatmapListingOverlay.cs b/osu.Game/Overlays/BeatmapListingOverlay.cs index 5df7a4650e..5e65cd9488 100644 --- a/osu.Game/Overlays/BeatmapListingOverlay.cs +++ b/osu.Game/Overlays/BeatmapListingOverlay.cs @@ -18,6 +18,7 @@ using osu.Game.Beatmaps; using osu.Game.Graphics.Sprites; using osu.Game.Overlays.BeatmapListing; using osu.Game.Overlays.BeatmapListing.Panels; +using osu.Game.Resources.Localisation.Web; using osuTK; using osuTK.Graphics; @@ -232,7 +233,7 @@ namespace osu.Game.Overlays { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Text = @"... nope, nothing found.", + Text = BeatmapsStrings.ListingSearchNotFoundQuote, } } }); diff --git a/osu.Game/Overlays/OverlaySortTabControl.cs b/osu.Game/Overlays/OverlaySortTabControl.cs index 0ebabd424f..b230acca11 100644 --- a/osu.Game/Overlays/OverlaySortTabControl.cs +++ b/osu.Game/Overlays/OverlaySortTabControl.cs @@ -18,6 +18,7 @@ using JetBrains.Annotations; using System; using osu.Framework.Extensions; using osu.Framework.Localisation; +using osu.Game.Resources.Localisation.Web; namespace osu.Game.Overlays { @@ -54,7 +55,7 @@ namespace osu.Game.Overlays Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, Font = OsuFont.GetFont(size: 12, weight: FontWeight.SemiBold), - Text = @"Sort by" + Text = SortStrings.Default }, CreateControl().With(c => { @@ -143,7 +144,7 @@ namespace osu.Game.Overlays Anchor = Anchor.Centre, Origin = Anchor.Centre, Font = OsuFont.GetFont(size: 12, weight: FontWeight.SemiBold), - Text = (value as Enum)?.GetDescription() ?? value.ToString() + Text = (value as Enum)?.GetLocalisableDescription() ?? value.ToString() } } }); diff --git a/osu.Game/Overlays/Settings/Sections/General/LanguageSettings.cs b/osu.Game/Overlays/Settings/Sections/General/LanguageSettings.cs index c2767f61b4..dfcdb8e340 100644 --- a/osu.Game/Overlays/Settings/Sections/General/LanguageSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/General/LanguageSettings.cs @@ -1,11 +1,11 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Configuration; using osu.Framework.Graphics; +using osu.Game.Extensions; using osu.Game.Localisation; namespace osu.Game.Overlays.Settings.Sections.General @@ -35,11 +35,11 @@ namespace osu.Game.Overlays.Settings.Sections.General }, }; - if (!Enum.TryParse(frameworkLocale.Value, out var locale)) + if (!LanguageExtensions.TryParseCultureCode(frameworkLocale.Value, out var locale)) locale = Language.en; languageSelection.Current.Value = locale; - languageSelection.Current.BindValueChanged(val => frameworkLocale.Value = val.NewValue.ToString()); + languageSelection.Current.BindValueChanged(val => frameworkLocale.Value = val.NewValue.ToCultureCode()); } } } diff --git a/osu.Game/Overlays/Settings/SettingsEnumDropdown.cs b/osu.Game/Overlays/Settings/SettingsEnumDropdown.cs index c77d14632b..9987a0c607 100644 --- a/osu.Game/Overlays/Settings/SettingsEnumDropdown.cs +++ b/osu.Game/Overlays/Settings/SettingsEnumDropdown.cs @@ -19,6 +19,8 @@ namespace osu.Game.Overlays.Settings Margin = new MarginPadding { Top = 5 }; RelativeSizeAxes = Axes.X; } + + protected override DropdownMenu CreateMenu() => base.CreateMenu().With(m => m.MaxHeight = 200); } } } diff --git a/osu.Game/Overlays/Settings/SettingsNumberBox.cs b/osu.Game/Overlays/Settings/SettingsNumberBox.cs index cb7e63ae6f..ca9a8e9c08 100644 --- a/osu.Game/Overlays/Settings/SettingsNumberBox.cs +++ b/osu.Game/Overlays/Settings/SettingsNumberBox.cs @@ -2,16 +2,20 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; -using osu.Game.Graphics.UserInterface; namespace osu.Game.Overlays.Settings { public class SettingsNumberBox : SettingsItem { - protected override Drawable CreateControl() => new OsuNumberBox + protected override Drawable CreateControl() => new NumberBox { Margin = new MarginPadding { Top = 5 }, RelativeSizeAxes = Axes.X, }; + + public class NumberBox : SettingsTextBox.TextBox + { + protected override bool CanAddCharacter(char character) => char.IsNumber(character); + } } } diff --git a/osu.Game/Overlays/Settings/SettingsTextBox.cs b/osu.Game/Overlays/Settings/SettingsTextBox.cs index 5e700a1d6b..25424e85a1 100644 --- a/osu.Game/Overlays/Settings/SettingsTextBox.cs +++ b/osu.Game/Overlays/Settings/SettingsTextBox.cs @@ -1,18 +1,60 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; +using osu.Framework.Input.Events; +using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; +using osuTK.Graphics; namespace osu.Game.Overlays.Settings { public class SettingsTextBox : SettingsItem { - protected override Drawable CreateControl() => new OsuTextBox + protected override Drawable CreateControl() => new TextBox { Margin = new MarginPadding { Top = 5 }, RelativeSizeAxes = Axes.X, CommitOnFocusLost = true, }; + + public class TextBox : OsuTextBox + { + private const float border_thickness = 3; + + private Color4 borderColourFocused; + private Color4 borderColourUnfocused; + + [BackgroundDependencyLoader] + private void load(OsuColour colour) + { + borderColourUnfocused = colour.Gray4.Opacity(0.5f); + borderColourFocused = BorderColour; + + updateBorder(); + } + + protected override void OnFocus(FocusEvent e) + { + base.OnFocus(e); + + updateBorder(); + } + + protected override void OnFocusLost(FocusLostEvent e) + { + base.OnFocusLost(e); + + updateBorder(); + } + + private void updateBorder() + { + BorderThickness = border_thickness; + BorderColour = HasFocus ? borderColourFocused : borderColourUnfocused; + } + } } } diff --git a/osu.Game/Overlays/TabControlOverlayHeader.cs b/osu.Game/Overlays/TabControlOverlayHeader.cs index 7798dfa576..e6f7e250a7 100644 --- a/osu.Game/Overlays/TabControlOverlayHeader.cs +++ b/osu.Game/Overlays/TabControlOverlayHeader.cs @@ -106,7 +106,19 @@ namespace osu.Game.Overlays public OverlayHeaderTabItem(T value) : base(value) { - Text.Text = ((Value as Enum)?.GetDescription() ?? Value.ToString()).ToLower(); + if (!(Value is Enum enumValue)) + Text.Text = Value.ToString().ToLower(); + else + { + var localisableDescription = enumValue.GetLocalisableDescription(); + var nonLocalisableDescription = enumValue.GetDescription(); + + // If localisable == non-localisable, then we must have a basic string, so .ToLower() is used. + Text.Text = localisableDescription.Equals(nonLocalisableDescription) + ? nonLocalisableDescription.ToLower() + : localisableDescription; + } + Text.Font = OsuFont.GetFont(size: 14); Text.Margin = new MarginPadding { Vertical = 16.5f }; // 15px padding + 1.5px line-height difference compensation Bar.Margin = new MarginPadding { Bottom = bar_height }; diff --git a/osu.Game/Performance/HighPerformanceSession.cs b/osu.Game/Performance/HighPerformanceSession.cs new file mode 100644 index 0000000000..96e67669c5 --- /dev/null +++ b/osu.Game/Performance/HighPerformanceSession.cs @@ -0,0 +1,47 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Runtime; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; + +namespace osu.Game.Performance +{ + public class HighPerformanceSession : Component + { + private readonly IBindable localUserPlaying = new Bindable(); + private GCLatencyMode originalGCMode; + + [BackgroundDependencyLoader] + private void load(OsuGame game) + { + localUserPlaying.BindTo(game.LocalUserPlaying); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + localUserPlaying.BindValueChanged(playing => + { + if (playing.NewValue) + EnableHighPerformanceSession(); + else + DisableHighPerformanceSession(); + }, true); + } + + protected virtual void EnableHighPerformanceSession() + { + originalGCMode = GCSettings.LatencyMode; + GCSettings.LatencyMode = GCLatencyMode.LowLatency; + } + + protected virtual void DisableHighPerformanceSession() + { + if (GCSettings.LatencyMode == GCLatencyMode.LowLatency) + GCSettings.LatencyMode = originalGCMode; + } + } +} diff --git a/osu.Game/Rulesets/Mods/ModRandom.cs b/osu.Game/Rulesets/Mods/ModRandom.cs index 3f14263420..e0c3008ae8 100644 --- a/osu.Game/Rulesets/Mods/ModRandom.cs +++ b/osu.Game/Rulesets/Mods/ModRandom.cs @@ -8,7 +8,6 @@ using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.UserInterface; using osu.Game.Configuration; using osu.Game.Graphics; -using osu.Game.Graphics.UserInterface; using osu.Game.Overlays.Settings; namespace osu.Game.Rulesets.Mods @@ -50,7 +49,7 @@ namespace osu.Game.Rulesets.Mods } } - private readonly OsuNumberBox seedNumberBox; + private readonly SettingsNumberBox.NumberBox seedNumberBox; public SeedControl() { @@ -76,7 +75,7 @@ namespace osu.Game.Rulesets.Mods { new Drawable[] { - seedNumberBox = new OsuNumberBox + seedNumberBox = new SettingsNumberBox.NumberBox { RelativeSizeAxes = Axes.X, CommitOnFocusLost = true diff --git a/osu.Game/Scoring/ScoreRank.cs b/osu.Game/Scoring/ScoreRank.cs index 696d493830..f3b4551ff8 100644 --- a/osu.Game/Scoring/ScoreRank.cs +++ b/osu.Game/Scoring/ScoreRank.cs @@ -1,10 +1,14 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.ComponentModel; +using osu.Framework.Localisation; +using osu.Game.Resources.Localisation.Web; namespace osu.Game.Scoring { + [LocalisableEnum(typeof(ScoreRankEnumLocalisationMapper))] public enum ScoreRank { [Description(@"D")] @@ -31,4 +35,40 @@ namespace osu.Game.Scoring [Description(@"SS+")] XH, } + + public class ScoreRankEnumLocalisationMapper : EnumLocalisationMapper + { + public override LocalisableString Map(ScoreRank value) + { + switch (value) + { + case ScoreRank.XH: + return BeatmapsStrings.RankXH; + + case ScoreRank.X: + return BeatmapsStrings.RankX; + + case ScoreRank.SH: + return BeatmapsStrings.RankSH; + + case ScoreRank.S: + return BeatmapsStrings.RankS; + + case ScoreRank.A: + return BeatmapsStrings.RankA; + + case ScoreRank.B: + return BeatmapsStrings.RankB; + + case ScoreRank.C: + return BeatmapsStrings.RankC; + + case ScoreRank.D: + return BeatmapsStrings.RankD; + + default: + throw new ArgumentOutOfRangeException(nameof(value), value, null); + } + } + } } diff --git a/osu.Game/Skinning/SkinnableTargetContainer.cs b/osu.Game/Skinning/SkinnableTargetContainer.cs index 1338462dd6..53b142f09a 100644 --- a/osu.Game/Skinning/SkinnableTargetContainer.cs +++ b/osu.Game/Skinning/SkinnableTargetContainer.cs @@ -18,6 +18,8 @@ namespace osu.Game.Skinning private readonly BindableList components = new BindableList(); + public bool ComponentsLoaded { get; private set; } + public SkinnableTargetContainer(SkinnableTarget target) { Target = target; @@ -30,6 +32,7 @@ namespace osu.Game.Skinning { ClearInternal(); components.Clear(); + ComponentsLoaded = false; content = CurrentSkin.GetDrawableComponent(new SkinnableTargetComponent(Target)) as SkinnableTargetComponentsContainer; @@ -39,8 +42,11 @@ namespace osu.Game.Skinning { AddInternal(wrapper); components.AddRange(wrapper.Children.OfType()); + ComponentsLoaded = true; }); } + else + ComponentsLoaded = true; } /// diff --git a/osu.Game/Tests/Visual/LegacySkinPlayerTestScene.cs b/osu.Game/Tests/Visual/LegacySkinPlayerTestScene.cs index b810bbf6ae..d74be70df8 100644 --- a/osu.Game/Tests/Visual/LegacySkinPlayerTestScene.cs +++ b/osu.Game/Tests/Visual/LegacySkinPlayerTestScene.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Extensions.IEnumerableExtensions; @@ -47,6 +48,8 @@ namespace osu.Game.Tests.Visual LegacySkin.ResetDrawableTarget(t); t.Reload(); })); + + AddUntilStep("wait for components to load", () => this.ChildrenOfType().All(t => t.ComponentsLoaded)); } public class SkinProvidingPlayer : TestPlayer diff --git a/osu.Game/Tests/Visual/OsuTestScene.cs b/osu.Game/Tests/Visual/OsuTestScene.cs index a4c78f24e3..98aad821ce 100644 --- a/osu.Game/Tests/Visual/OsuTestScene.cs +++ b/osu.Game/Tests/Visual/OsuTestScene.cs @@ -350,7 +350,7 @@ namespace osu.Game.Tests.Visual if (CurrentTime >= Length) { Stop(); - RaiseCompleted(); + // `RaiseCompleted` is not called here to prevent transitioning to the next song. } } } diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index a7bd5f2e9f..68ffb87c6c 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -34,8 +34,8 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - - + + diff --git a/osu.iOS.props b/osu.iOS.props index 5b3efb4ba4..8aa79762fc 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,8 +70,8 @@ - - + + @@ -93,7 +93,7 @@ - +