diff --git a/.idea/.idea.osu/.idea/runConfigurations/RulesetTests__catch_.xml b/.idea/.idea.osu/.idea/runConfigurations/RulesetTests__catch_.xml new file mode 100644 index 0000000000..be69e92748 --- /dev/null +++ b/.idea/.idea.osu/.idea/runConfigurations/RulesetTests__catch_.xml @@ -0,0 +1,22 @@ + + + + \ No newline at end of file diff --git a/.idea/.idea.osu/.idea/runConfigurations/RulesetTests__mania_.xml b/.idea/.idea.osu/.idea/runConfigurations/RulesetTests__mania_.xml new file mode 100644 index 0000000000..da968b54b7 --- /dev/null +++ b/.idea/.idea.osu/.idea/runConfigurations/RulesetTests__mania_.xml @@ -0,0 +1,22 @@ + + + + \ No newline at end of file diff --git a/.idea/.idea.osu/.idea/runConfigurations/RulesetTests__osu__.xml b/.idea/.idea.osu/.idea/runConfigurations/RulesetTests__osu__.xml new file mode 100644 index 0000000000..f2b8155e37 --- /dev/null +++ b/.idea/.idea.osu/.idea/runConfigurations/RulesetTests__osu__.xml @@ -0,0 +1,22 @@ + + + + \ No newline at end of file diff --git a/.idea/.idea.osu/.idea/runConfigurations/RulesetTests__taiko_.xml b/.idea/.idea.osu/.idea/runConfigurations/RulesetTests__taiko_.xml new file mode 100644 index 0000000000..941455f8b4 --- /dev/null +++ b/.idea/.idea.osu/.idea/runConfigurations/RulesetTests__taiko_.xml @@ -0,0 +1,22 @@ + + + + \ No newline at end of file diff --git a/.idea/.idea.osu/.idea/runConfigurations/VisualTests__net461_.xml b/.idea/.idea.osu/.idea/runConfigurations/VisualTests__net471_.xml similarity index 82% rename from .idea/.idea.osu/.idea/runConfigurations/VisualTests__net461_.xml rename to .idea/.idea.osu/.idea/runConfigurations/VisualTests__net471_.xml index cf4bccfe60..20a15f985f 100644 --- a/.idea/.idea.osu/.idea/runConfigurations/VisualTests__net461_.xml +++ b/.idea/.idea.osu/.idea/runConfigurations/VisualTests__net471_.xml @@ -1,6 +1,6 @@ - - \ No newline at end of file diff --git a/.idea/.idea.osu/.idea/runConfigurations/osu___net461_.xml b/.idea/.idea.osu/.idea/runConfigurations/osu___net471_.xml similarity index 83% rename from .idea/.idea.osu/.idea/runConfigurations/osu___net461_.xml rename to .idea/.idea.osu/.idea/runConfigurations/osu___net471_.xml index 971868a81b..7196e486d2 100644 --- a/.idea/.idea.osu/.idea/runConfigurations/osu___net461_.xml +++ b/.idea/.idea.osu/.idea/runConfigurations/osu___net471_.xml @@ -1,6 +1,6 @@ - - \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json index 624e584f10..df5b11f63a 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -2,13 +2,13 @@ "version": "0.2.0", "configurations": [ { - "name": "VisualTests (Debug, net461)", + "name": "VisualTests (Debug, net471)", "windows": { "type": "clr" }, "type": "mono", "request": "launch", - "program": "${workspaceRoot}/osu.Game.Tests/bin/Debug/net461/osu.Game.Tests.exe", + "program": "${workspaceRoot}/osu.Game.Tests/bin/Debug/net471/osu.Game.Tests.exe", "cwd": "${workspaceRoot}", "preLaunchTask": "Build (Debug, msbuild)", "runtimeExecutable": null, @@ -16,13 +16,13 @@ "console": "internalConsole" }, { - "name": "VisualTests (Release, net461)", + "name": "VisualTests (Release, net471)", "windows": { "type": "clr" }, "type": "mono", "request": "launch", - "program": "${workspaceRoot}/osu.Game.Tests/bin/Debug/net461/osu.Game.Tests.exe", + "program": "${workspaceRoot}/osu.Game.Tests/bin/Debug/net471/osu.Game.Tests.exe", "cwd": "${workspaceRoot}", "preLaunchTask": "Build (Release, msbuild)", "runtimeExecutable": null, @@ -30,13 +30,13 @@ "console": "internalConsole" }, { - "name": "osu! (Debug, net461)", + "name": "osu! (Debug, net471)", "windows": { "type": "clr" }, "type": "mono", "request": "launch", - "program": "${workspaceRoot}/osu.Desktop/bin/Debug/net461/osu!.exe", + "program": "${workspaceRoot}/osu.Desktop/bin/Debug/net471/osu!.exe", "cwd": "${workspaceRoot}", "preLaunchTask": "Build (Debug, msbuild)", "runtimeExecutable": null, @@ -44,13 +44,13 @@ "console": "internalConsole" }, { - "name": "osu! (Release, net461)", + "name": "osu! (Release, net471)", "windows": { "type": "clr" }, "type": "mono", "request": "launch", - "program": "${workspaceRoot}/osu.Desktop/bin/Release/net461/osu!.exe", + "program": "${workspaceRoot}/osu.Desktop/bin/Release/net471/osu!.exe", "cwd": "${workspaceRoot}", "preLaunchTask": "Build (Release, msbuild)", "runtimeExecutable": null, diff --git a/.vscode/tasks.json b/.vscode/tasks.json index b1d2c6b57d..7144a584f3 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -8,7 +8,7 @@ "type": "shell", "command": "msbuild", "args": [ - "/p:TargetFramework=net461", + "/p:TargetFramework=net471", "/p:GenerateFullPaths=true", "/m", "/verbosity:m" @@ -22,7 +22,7 @@ "command": "msbuild", "args": [ "/p:Configuration=Release", - "/p:TargetFramework=net461", + "/p:TargetFramework=net471", "/p:GenerateFullPaths=true", "/m", "/verbosity:m" @@ -64,7 +64,7 @@ "problemMatcher": "$msCompile" }, { - "label": "Restore (net461)", + "label": "Restore (net471)", "type": "shell", "command": "nuget", "args": [ diff --git a/README.md b/README.md index 47df86f57e..9d19f16ebd 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ This is still heavily under development and is not intended for end-user use. Th # Requirements -- A desktop platform that can compile .NET 4.6.1. We recommend using [Visual Studio Community Edition](https://www.visualstudio.com/) (Windows), [Visual Studio for Mac](https://www.visualstudio.com/vs/visual-studio-mac/) (macOS) or [MonoDevelop](http://www.monodevelop.com/download/) (Linux), all of which are free. [Visual Studio Code](https://code.visualstudio.com/) may also be used but requires further setup steps which are not covered here. +- A desktop platform that can compile .NET 4.7.1. We recommend using [Visual Studio Community Edition](https://www.visualstudio.com/) (Windows), [Visual Studio for Mac](https://www.visualstudio.com/vs/visual-studio-mac/) (macOS) or [MonoDevelop](http://www.monodevelop.com/download/) (Linux), all of which are free. [Visual Studio Code](https://code.visualstudio.com/) may also be used but requires further setup steps which are not covered here. # Getting Started - Clone the repository including submodules (`git clone --recurse-submodules https://github.com/ppy/osu`) diff --git a/appveyor.yml b/appveyor.yml index 4c4b70827f..15484e4c68 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,27 +1,26 @@ -# 2017-09-14 -clone_depth: 1 -version: '{branch}-{build}' -image: Visual Studio 2017 -configuration: Debug -cache: - - C:\ProgramData\chocolatey\bin -> appveyor.yml - - C:\ProgramData\chocolatey\lib -> appveyor.yml - - inspectcode -> appveyor.yml - - packages -> **\packages.config -install: - - cmd: git submodule update --init --recursive --depth=5 - - cmd: choco install resharper-clt -y - - cmd: choco install nvika -y - - cmd: appveyor DownloadFile https://github.com/peppy/CodeFileSanity/releases/download/v0.2.4/CodeFileSanity.exe -before_build: - - cmd: CodeFileSanity.exe - - cmd: nuget restore -verbosity quiet -environment: - TargetFramework: net461 -build: - project: osu.sln - parallel: true - verbosity: minimal -after_build: - - cmd: inspectcode --o="inspectcodereport.xml" --projects:osu.Game* --caches-home="inspectcode" osu.sln > NUL +clone_depth: 1 +version: '{branch}-{build}' +image: Visual Studio 2017 +configuration: Debug +cache: + - C:\ProgramData\chocolatey\bin -> appveyor.yml + - C:\ProgramData\chocolatey\lib -> appveyor.yml + - inspectcode -> appveyor.yml + - packages -> **\packages.config +install: + - cmd: git submodule update --init --recursive --depth=5 + - cmd: choco install resharper-clt -y + - cmd: choco install nvika -y + - cmd: appveyor DownloadFile https://github.com/peppy/CodeFileSanity/releases/download/v0.2.4/CodeFileSanity.exe +before_build: + - cmd: CodeFileSanity.exe + - cmd: nuget restore -verbosity quiet +environment: + TargetFramework: net471 +build: + project: osu.sln + parallel: true + verbosity: minimal +after_build: + - cmd: inspectcode --o="inspectcodereport.xml" --projects:osu.Game* --caches-home="inspectcode" osu.sln > NUL - cmd: NVika parsereport "inspectcodereport.xml" --treatwarningsaserrors \ No newline at end of file diff --git a/appveyor_deploy.yml b/appveyor_deploy.yml new file mode 100644 index 0000000000..cadebd9d11 --- /dev/null +++ b/appveyor_deploy.yml @@ -0,0 +1,34 @@ +branches: + only: + - release +skip_tags: true +skip_branch_with_pr: true +clone_depth: 1 +version: '{branch}-{build}' +image: Visual Studio 2017 +configuration: Debug +cache: + - packages -> **\packages.config +install: + - cmd: git submodule update --init --recursive --depth=5 +before_build: + - cmd: nuget restore -verbosity quiet +build: + project: osu.Desktop.Deploy/osu.Desktop.Deploy.csproj + verbosity: minimal +after_build: + - ps: iex ((New-Object Net.WebClient).DownloadString('https://raw.githubusercontent.com/appveyor/secure-file/master/install.ps1')) + - appveyor DownloadFile https://puu.sh/A6g5K/4d08705438.enc # signing certificate + - cmd: appveyor-tools\secure-file -decrypt 4d08705438.enc -secret %decode_secret% -out %HOMEPATH%\deanherbert.pfx + - appveyor DownloadFile https://puu.sh/A6g75/fdc6f19b04.enc # deploy configuration + - cmd: appveyor-tools\secure-file -decrypt fdc6f19b04.enc -secret %decode_secret% -out osu.Desktop.Deploy\bin\Debug\net471\osu.Desktop.Deploy.exe.config + - cd osu.Desktop.Deploy\bin\Debug\net471\ + - osu.Desktop.Deploy.exe %code_signing_password% +environment: + TargetFramework: net471 + decode_secret: + secure: i67IC2xj6DjjxmA6Oj2jing3+MwzLkq6CbGsjfZ7rdY= + code_signing_password: + secure: 34tLNqvjmmZEi97MLKfrnQ== +artifacts: + - path: 'Releases\*' \ No newline at end of file diff --git a/osu-framework b/osu-framework index 16e6a453db..0773d895d9 160000 --- a/osu-framework +++ b/osu-framework @@ -1 +1 @@ -Subproject commit 16e6a453db9a8f4454238a2911eb5f1444b7ec2a +Subproject commit 0773d895d9aa0729995cd4a23efc28238e35ceed diff --git a/osu.Desktop.Deploy/.vscode/launch.json b/osu.Desktop.Deploy/.vscode/launch.json index 82cd6b4c13..8c35d211bd 100644 --- a/osu.Desktop.Deploy/.vscode/launch.json +++ b/osu.Desktop.Deploy/.vscode/launch.json @@ -7,7 +7,7 @@ "name": "Deploy (Debug)", "request": "launch", "type": "mono", - "program": "${workspaceRoot}/bin/Debug/net461/osu.Desktop.Deploy.exe", + "program": "${workspaceRoot}/bin/Debug/net471/osu.Desktop.Deploy.exe", "cwd": "${workspaceRoot}", "preLaunchTask": "Build (Debug)", "runtimeExecutable": null, @@ -18,7 +18,7 @@ "name": "Deploy (Release)", "request": "launch", "type": "clr", - "program": "${workspaceRoot}/bin/Release/net461/osu.Desktop.Deploy.exe", + "program": "${workspaceRoot}/bin/Release/net471/osu.Desktop.Deploy.exe", "cwd": "${workspaceRoot}", "preLaunchTask": "Build (Release)", "runtimeExecutable": null, diff --git a/osu.Desktop.Deploy/Program.cs b/osu.Desktop.Deploy/Program.cs index 6095ce062d..a1c2a8aef2 100644 --- a/osu.Desktop.Deploy/Program.cs +++ b/osu.Desktop.Deploy/Program.cs @@ -7,6 +7,7 @@ using System.Configuration; using System.Diagnostics; using System.IO; using System.Linq; +using System.Management.Automation; using Newtonsoft.Json; using osu.Framework.IO.Network; using FileWebRequest = osu.Framework.IO.Network.FileWebRequest; @@ -18,7 +19,7 @@ namespace osu.Desktop.Deploy { private static string packages => Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".nuget", "packages"); private static string nugetPath => Path.Combine(packages, @"nuget.commandline\4.5.1\tools\NuGet.exe"); - private static string squirrelPath => Path.Combine(packages, @"squirrel.windows\1.7.8\tools\Squirrel.exe"); + private static string squirrelPath => Path.Combine(packages, @"squirrel.windows\1.8.0\tools\Squirrel.exe"); private const string msbuild_path = @"C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\MSBuild\15.0\Bin\MSBuild.exe"; public static string StagingFolder = ConfigurationManager.AppSettings["StagingFolder"]; @@ -57,8 +58,12 @@ namespace osu.Desktop.Deploy private static string codeSigningPassword; + private static bool interactive; + public static void Main(string[] args) { + interactive = args.Length == 0; + displayHeader(); findSolutionPath(); @@ -82,19 +87,20 @@ namespace osu.Desktop.Deploy string version = $"{verBase}{increment}"; Console.ForegroundColor = ConsoleColor.White; - Console.Write($"Ready to deploy {version}: "); - Console.ReadLine(); + Console.Write($"Ready to deploy {version}!"); + pauseIfInteractive(); sw.Start(); if (!string.IsNullOrEmpty(CodeSigningCertificate)) { Console.Write("Enter code signing password: "); - codeSigningPassword = readLineMasked(); + codeSigningPassword = args.Length > 0 ? args[0] : readLineMasked(); } write("Updating AssemblyInfo..."); updateCsprojVersion(version); + updateAppveyorVersion(version); write("Running build process..."); foreach (string targetName in TargetNames.Split(',')) @@ -109,7 +115,7 @@ namespace osu.Desktop.Deploy checkReleaseFiles(); write("Running squirrel build..."); - runCommand(squirrelPath, $"--releasify {stagingPath}\\{nupkgFilename(version)} --setupIcon {iconPath} --icon {iconPath} {codeSigningCmd} --no-msi"); + runCommand(squirrelPath, $"--releasify {stagingPath}\\{nupkgFilename(version)} --framework-version=net471 --setupIcon {iconPath} --icon {iconPath} {codeSigningCmd} --no-msi"); //prune again to clean up before upload. pruneReleases(); @@ -124,7 +130,7 @@ namespace osu.Desktop.Deploy updateCsprojVersion("0.0.0"); write("Done!", ConsoleColor.White); - Console.ReadLine(); + pauseIfInteractive(); } private static void displayHeader() @@ -388,10 +394,37 @@ namespace osu.Desktop.Deploy Console.ForegroundColor = ConsoleColor.Red; Console.WriteLine($"FATAL ERROR: {message}"); - Console.ReadLine(); + pauseIfInteractive(); Environment.Exit(-1); } + private static void pauseIfInteractive() + { + if (interactive) + Console.ReadLine(); + else + Console.WriteLine(); + } + + private static bool updateAppveyorVersion(string version) + { + try + { + using (PowerShell ps = PowerShell.Create()) + { + ps.AddScript($"Update-AppveyorBuild -Version \"{version}\""); + ps.Invoke(); + } + return true; + } + catch + { + // we don't have appveyor and don't care + } + + return false; + } + private static void write(string message, ConsoleColor col = ConsoleColor.Gray) { if (sw.ElapsedMilliseconds > 0) diff --git a/osu.Desktop.Deploy/osu.Desktop.Deploy.csproj b/osu.Desktop.Deploy/osu.Desktop.Deploy.csproj index 6b7509a381..063fb89918 100644 --- a/osu.Desktop.Deploy/osu.Desktop.Deploy.csproj +++ b/osu.Desktop.Deploy/osu.Desktop.Deploy.csproj @@ -1,7 +1,7 @@ - + - net461 + net471 Exe AnyCPU true @@ -12,7 +12,8 @@ - + + \ No newline at end of file diff --git a/osu.Desktop/osu.Desktop.csproj b/osu.Desktop/osu.Desktop.csproj index 2ad7b67842..3d64cab84e 100644 --- a/osu.Desktop/osu.Desktop.csproj +++ b/osu.Desktop/osu.Desktop.csproj @@ -1,7 +1,7 @@ - + - net461;netcoreapp2.0 + net471;netcoreapp2.0 WinExe AnyCPU true @@ -10,11 +10,11 @@ osu!lazer osu!lazer lazer.ico - 0.0.0.0 - 0.0.0.0 + 0.0.0 + 0.0.0 - $(DefineConstants);NET_FRAMEWORK + $(DefineConstants);NET_FRAMEWORK osu.Desktop.Program @@ -31,9 +31,9 @@ - + - \ No newline at end of file + diff --git a/osu.Game.Rulesets.Catch.Tests/.vscode/launch.json b/osu.Game.Rulesets.Catch.Tests/.vscode/launch.json index 5098b78a42..eb80f4474c 100644 --- a/osu.Game.Rulesets.Catch.Tests/.vscode/launch.json +++ b/osu.Game.Rulesets.Catch.Tests/.vscode/launch.json @@ -2,13 +2,13 @@ "version": "0.2.0", "configurations": [ { - "name": "VisualTests (Debug, net461)", + "name": "VisualTests (Debug, net471)", "windows": { "type": "clr" }, "type": "mono", "request": "launch", - "program": "${workspaceRoot}/bin/Debug/net461/osu.Game.Rulesets.Catch.Tests.exe", + "program": "${workspaceRoot}/bin/Debug/net471/osu.Game.Rulesets.Catch.Tests.exe", "cwd": "${workspaceRoot}", "preLaunchTask": "Build (Debug, msbuild)", "runtimeExecutable": null, @@ -16,13 +16,13 @@ "console": "internalConsole" }, { - "name": "VisualTests (Release, net461)", + "name": "VisualTests (Release, net471)", "windows": { "type": "clr" }, "type": "mono", "request": "launch", - "program": "${workspaceRoot}/bin/Debug/net461/osu.Game.Rulesets.Catch.Tests.exe", + "program": "${workspaceRoot}/bin/Debug/net471/osu.Game.Rulesets.Catch.Tests.exe", "cwd": "${workspaceRoot}", "preLaunchTask": "Build (Release, msbuild)", "runtimeExecutable": null, diff --git a/osu.Game.Rulesets.Catch.Tests/.vscode/tasks.json b/osu.Game.Rulesets.Catch.Tests/.vscode/tasks.json index d21bb8a69a..41ae88f425 100644 --- a/osu.Game.Rulesets.Catch.Tests/.vscode/tasks.json +++ b/osu.Game.Rulesets.Catch.Tests/.vscode/tasks.json @@ -9,7 +9,7 @@ "command": "msbuild", "args": [ "osu.Game.Rulesets.Catch.Tests.csproj", - "/p:TargetFramework=net461", + "/p:TargetFramework=net471", "/p:GenerateFullPaths=true", "/m", "/verbosity:m" @@ -24,7 +24,7 @@ "args": [ "osu.Game.Rulesets.Catch.Tests.csproj", "/p:Configuration=Release", - "/p:TargetFramework=net461", + "/p:TargetFramework=net471", "/p:GenerateFullPaths=true", "/m", "/verbosity:m" @@ -66,7 +66,7 @@ "problemMatcher": "$msCompile" }, { - "label": "Restore (net461)", + "label": "Restore (net471)", "type": "shell", "command": "nuget", "args": [ 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 7a4c7b3f1c..3797edde61 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 @@ WinExe - netcoreapp2.0;net461 + netcoreapp2.0;net471 diff --git a/osu.Game.Rulesets.Mania.Tests/.vscode/launch.json b/osu.Game.Rulesets.Mania.Tests/.vscode/launch.json index c71178059b..fceb403f30 100644 --- a/osu.Game.Rulesets.Mania.Tests/.vscode/launch.json +++ b/osu.Game.Rulesets.Mania.Tests/.vscode/launch.json @@ -2,13 +2,13 @@ "version": "0.2.0", "configurations": [ { - "name": "VisualTests (Debug, net461)", + "name": "VisualTests (Debug, net471)", "windows": { "type": "clr" }, "type": "mono", "request": "launch", - "program": "${workspaceRoot}/bin/Debug/net461/osu.Game.Rulesets.Mania.Tests.exe", + "program": "${workspaceRoot}/bin/Debug/net471/osu.Game.Rulesets.Mania.Tests.exe", "cwd": "${workspaceRoot}", "preLaunchTask": "Build (Debug, msbuild)", "runtimeExecutable": null, @@ -16,13 +16,13 @@ "console": "internalConsole" }, { - "name": "VisualTests (Release, net461)", + "name": "VisualTests (Release, net471)", "windows": { "type": "clr" }, "type": "mono", "request": "launch", - "program": "${workspaceRoot}/bin/Debug/net461/osu.Game.Rulesets.Mania.Tests.exe", + "program": "${workspaceRoot}/bin/Debug/net471/osu.Game.Rulesets.Mania.Tests.exe", "cwd": "${workspaceRoot}", "preLaunchTask": "Build (Release, msbuild)", "runtimeExecutable": null, diff --git a/osu.Game.Rulesets.Mania.Tests/.vscode/tasks.json b/osu.Game.Rulesets.Mania.Tests/.vscode/tasks.json index 781e89598f..b04b068b0d 100644 --- a/osu.Game.Rulesets.Mania.Tests/.vscode/tasks.json +++ b/osu.Game.Rulesets.Mania.Tests/.vscode/tasks.json @@ -9,7 +9,7 @@ "command": "msbuild", "args": [ "osu.Game.Rulesets.Mania.Tests.csproj", - "/p:TargetFramework=net461", + "/p:TargetFramework=net471", "/p:GenerateFullPaths=true", "/m", "/verbosity:m" @@ -24,7 +24,7 @@ "args": [ "osu.Game.Rulesets.Mania.Tests.csproj", "/p:Configuration=Release", - "/p:TargetFramework=net461", + "/p:TargetFramework=net471", "/p:GenerateFullPaths=true", "/m", "/verbosity:m" @@ -66,7 +66,7 @@ "problemMatcher": "$msCompile" }, { - "label": "Restore (net461)", + "label": "Restore (net471)", "type": "shell", "command": "nuget", "args": [ 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 02040fd23f..e90155568e 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 @@ WinExe - netcoreapp2.0;net461 + netcoreapp2.0;net471 diff --git a/osu.Game.Rulesets.Osu.Tests/.vscode/launch.json b/osu.Game.Rulesets.Osu.Tests/.vscode/launch.json index 24431eb8de..714fb6db6f 100644 --- a/osu.Game.Rulesets.Osu.Tests/.vscode/launch.json +++ b/osu.Game.Rulesets.Osu.Tests/.vscode/launch.json @@ -2,13 +2,13 @@ "version": "0.2.0", "configurations": [ { - "name": "VisualTests (Debug, net461)", + "name": "VisualTests (Debug, net471)", "windows": { "type": "clr" }, "type": "mono", "request": "launch", - "program": "${workspaceRoot}/bin/Debug/net461/osu.Game.Rulesets.Osu.Tests.exe", + "program": "${workspaceRoot}/bin/Debug/net471/osu.Game.Rulesets.Osu.Tests.exe", "cwd": "${workspaceRoot}", "preLaunchTask": "Build (Debug, msbuild)", "runtimeExecutable": null, @@ -16,13 +16,13 @@ "console": "internalConsole" }, { - "name": "VisualTests (Release, net461)", + "name": "VisualTests (Release, net471)", "windows": { "type": "clr" }, "type": "mono", "request": "launch", - "program": "${workspaceRoot}/bin/Debug/net461/osu.Game.Rulesets.Osu.Tests.exe", + "program": "${workspaceRoot}/bin/Debug/net471/osu.Game.Rulesets.Osu.Tests.exe", "cwd": "${workspaceRoot}", "preLaunchTask": "Build (Release, msbuild)", "runtimeExecutable": null, diff --git a/osu.Game.Rulesets.Osu.Tests/.vscode/tasks.json b/osu.Game.Rulesets.Osu.Tests/.vscode/tasks.json index 734e15353b..657fe07e1a 100644 --- a/osu.Game.Rulesets.Osu.Tests/.vscode/tasks.json +++ b/osu.Game.Rulesets.Osu.Tests/.vscode/tasks.json @@ -9,7 +9,7 @@ "command": "msbuild", "args": [ "osu.Game.Rulesets.Osu.Tests.csproj", - "/p:TargetFramework=net461", + "/p:TargetFramework=net471", "/p:GenerateFullPaths=true", "/m", "/verbosity:m" @@ -24,7 +24,7 @@ "args": [ "osu.Game.Rulesets.Osu.Tests.csproj", "/p:Configuration=Release", - "/p:TargetFramework=net461", + "/p:TargetFramework=net471", "/p:GenerateFullPaths=true", "/m", "/verbosity:m" @@ -66,7 +66,7 @@ "problemMatcher": "$msCompile" }, { - "label": "Restore (net461)", + "label": "Restore (net471)", "type": "shell", "command": "nuget", "args": [ 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 49c5f15713..1695ceacee 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 @@ WinExe - netcoreapp2.0;net461 + netcoreapp2.0;net471 diff --git a/osu.Game.Rulesets.Osu/Edit/OsuEditPlayfield.cs b/osu.Game.Rulesets.Osu/Edit/OsuEditPlayfield.cs index 3518e030b6..6213bb1329 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuEditPlayfield.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuEditPlayfield.cs @@ -7,7 +7,6 @@ namespace osu.Game.Rulesets.Osu.Edit { public class OsuEditPlayfield : OsuPlayfield { - protected override bool ProxyApproachCircles => false; protected override bool DisplayJudgements => false; } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index f9d21ea5bb..10539f85a2 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -83,7 +83,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables Anchor = Anchor.Centre, Origin = Anchor.Centre, RelativeSizeAxes = Axes.Y, - Children = new Drawable[] + Children = new[] { Background = new SpinnerBackground { diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index 8330aa8e9d..b63f85623e 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -21,9 +21,6 @@ namespace osu.Game.Rulesets.Osu.UI private readonly JudgementContainer judgementLayer; private readonly ConnectionRenderer connectionLayer; - // Todo: This should not be a thing, but is currently required for the editor - // https://github.com/ppy/osu-framework/issues/1283 - protected virtual bool ProxyApproachCircles => true; protected virtual bool DisplayJudgements => true; public static readonly Vector2 BASE_SIZE = new Vector2(512, 384); @@ -59,7 +56,7 @@ namespace osu.Game.Rulesets.Osu.UI h.OnJudgement += onJudgement; var c = h as IDrawableHitObjectWithProxiedApproach; - if (c != null && ProxyApproachCircles) + if (c != null) approachCircles.Add(c.ProxiedLayer.CreateProxy()); base.Add(h); diff --git a/osu.Game.Rulesets.Taiko.Tests/.vscode/launch.json b/osu.Game.Rulesets.Taiko.Tests/.vscode/launch.json index caa90c32ce..e1df54e99b 100644 --- a/osu.Game.Rulesets.Taiko.Tests/.vscode/launch.json +++ b/osu.Game.Rulesets.Taiko.Tests/.vscode/launch.json @@ -2,13 +2,13 @@ "version": "0.2.0", "configurations": [ { - "name": "VisualTests (Debug, net461)", + "name": "VisualTests (Debug, net471)", "windows": { "type": "clr" }, "type": "mono", "request": "launch", - "program": "${workspaceRoot}/bin/Debug/net461/osu.Game.Rulesets.Taiko.Tests.exe", + "program": "${workspaceRoot}/bin/Debug/net471/osu.Game.Rulesets.Taiko.Tests.exe", "cwd": "${workspaceRoot}", "preLaunchTask": "Build (Debug, msbuild)", "runtimeExecutable": null, @@ -16,13 +16,13 @@ "console": "internalConsole" }, { - "name": "VisualTests (Release, net461)", + "name": "VisualTests (Release, net471)", "windows": { "type": "clr" }, "type": "mono", "request": "launch", - "program": "${workspaceRoot}/bin/Debug/net461/osu.Game.Rulesets.Taiko.Tests.exe", + "program": "${workspaceRoot}/bin/Debug/net471/osu.Game.Rulesets.Taiko.Tests.exe", "cwd": "${workspaceRoot}", "preLaunchTask": "Build (Release, msbuild)", "runtimeExecutable": null, diff --git a/osu.Game.Rulesets.Taiko.Tests/.vscode/tasks.json b/osu.Game.Rulesets.Taiko.Tests/.vscode/tasks.json index 13044e1ccb..8bdbcd8e8e 100644 --- a/osu.Game.Rulesets.Taiko.Tests/.vscode/tasks.json +++ b/osu.Game.Rulesets.Taiko.Tests/.vscode/tasks.json @@ -9,7 +9,7 @@ "command": "msbuild", "args": [ "osu.Game.Rulesets.Taiko.Tests.csproj", - "/p:TargetFramework=net461", + "/p:TargetFramework=net471", "/p:GenerateFullPaths=true", "/m", "/verbosity:m" @@ -24,7 +24,7 @@ "args": [ "osu.Game.Rulesets.Taiko.Tests.csproj", "/p:Configuration=Release", - "/p:TargetFramework=net461", + "/p:TargetFramework=net471", "/p:GenerateFullPaths=true", "/m", "/verbosity:m" @@ -66,7 +66,7 @@ "problemMatcher": "$msCompile" }, { - "label": "Restore (net461)", + "label": "Restore (net471)", "type": "shell", "command": "nuget", "args": [ 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 ccd69c574d..1221584a2b 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 @@ WinExe - netcoreapp2.0;net461 + netcoreapp2.0;net471 diff --git a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs index 9c9589f398..6e0cf6be2e 100644 --- a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs @@ -116,8 +116,8 @@ namespace osu.Game.Tests.Beatmaps.Formats // [TestCase(with_sb)] public void TestParity(string beatmap) { - var beatmaps = decode(beatmap); - beatmaps.jsonDecoded.ShouldDeepEqual(beatmaps.legacyDecoded); + var legacy = decode(beatmap, out Beatmap json); + json.ShouldDeepEqual(legacy); } /// @@ -126,15 +126,20 @@ namespace osu.Game.Tests.Beatmaps.Formats /// /// The .osu file to decode. /// The after being decoded by an . - private Beatmap decodeAsJson(string filename) => decode(filename).jsonDecoded; + private Beatmap decodeAsJson(string filename) + { + decode(filename, out Beatmap jsonDecoded); + return jsonDecoded; + } /// /// Reads a .osu file first with a , serializes the resulting to JSON /// and then deserializes the result back into a through an . /// /// The .osu file to decode. + /// The after being decoded by an . /// The after being decoded by an . - private (Beatmap legacyDecoded, Beatmap jsonDecoded) decode(string filename) + private Beatmap decode(string filename, out Beatmap jsonDecoded) { using (var stream = Resource.OpenResource(filename)) using (var sr = new StreamReader(stream)) @@ -149,7 +154,9 @@ namespace osu.Game.Tests.Beatmaps.Formats sw.Flush(); ms.Position = 0; - return (legacyDecoded, new JsonBeatmapDecoder().Decode(sr2)); + + jsonDecoded = new JsonBeatmapDecoder().Decode(sr2); + return legacyDecoded; } } } diff --git a/osu.Game.Tests/Visual/TestCaseBeatmapInfoWedge.cs b/osu.Game.Tests/Visual/TestCaseBeatmapInfoWedge.cs index 0c5c5dba22..efd6c36ce2 100644 --- a/osu.Game.Tests/Visual/TestCaseBeatmapInfoWedge.cs +++ b/osu.Game.Tests/Visual/TestCaseBeatmapInfoWedge.cs @@ -52,6 +52,9 @@ namespace osu.Game.Tests.Visual infoWedge.UpdateBeatmap(beatmap); }); + // select part is redundant, but wait for load isn't + selectBeatmap(beatmap.Value.Beatmap); + AddWaitStep(3); AddStep("hide", () => { infoWedge.State = Visibility.Hidden; }); @@ -63,10 +66,11 @@ namespace osu.Game.Tests.Visual foreach (var rulesetInfo in rulesets.AvailableRulesets) { var ruleset = rulesetInfo.CreateInstance(); - beatmaps.Add(createTestBeatmap(rulesetInfo)); + var testBeatmap = createTestBeatmap(rulesetInfo); - var name = rulesetInfo.ShortName; - selectBeatmap(name); + beatmaps.Add(testBeatmap); + + selectBeatmap(testBeatmap); // TODO: adjust cases once more info is shown for other gamemodes switch (ruleset) @@ -108,14 +112,14 @@ namespace osu.Game.Tests.Visual AddAssert("check no infolabels", () => !infoWedge.Info.InfoLabelContainer.Children.Any()); } - private void selectBeatmap(string name) + private void selectBeatmap(IBeatmap b) { - var infoBefore = infoWedge.Info; + BeatmapInfoWedge.BufferedWedgeInfo infoBefore = null; - AddStep($"select {name} beatmap", () => + AddStep($"select {b.Metadata.Title} beatmap", () => { - beatmap.Value = new TestWorkingBeatmap(beatmaps.First(b => b.BeatmapInfo.Ruleset.ShortName == name)); - infoWedge.UpdateBeatmap(beatmap); + infoBefore = infoWedge.Info; + infoWedge.UpdateBeatmap(beatmap.Value = new TestWorkingBeatmap(b)); }); AddUntilStep(() => infoWedge.Info != infoBefore, "wait for async load"); diff --git a/osu.Game.Tests/Visual/TestCaseBeatmapScoresContainer.cs b/osu.Game.Tests/Visual/TestCaseBeatmapScoresContainer.cs index 6cb6a342a8..5be7386238 100644 --- a/osu.Game.Tests/Visual/TestCaseBeatmapScoresContainer.cs +++ b/osu.Game.Tests/Visual/TestCaseBeatmapScoresContainer.cs @@ -14,6 +14,8 @@ using osu.Game.Rulesets.Scoring; using osu.Game.Users; using System.Collections.Generic; using osu.Framework.Graphics.Containers; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Osu; namespace osu.Game.Tests.Visual { @@ -47,11 +49,11 @@ namespace osu.Game.Tests.Visual AddStep("scores pack 1", () => scoresContainer.Scores = scores); AddStep("scores pack 2", () => scoresContainer.Scores = anotherScores); AddStep("only top score", () => scoresContainer.Scores = new[] { topScore }); - AddStep("remove scores", scoresContainer.CleanAllScores); - AddStep("turn on loading", () => scoresContainer.IsLoading = true); - AddStep("turn off loading", () => scoresContainer.IsLoading = false); + AddStep("remove scores", () => scoresContainer.Scores = null); AddStep("resize to big", () => container.ResizeWidthTo(1, 300)); AddStep("resize to normal", () => container.ResizeWidthTo(0.8f, 300)); + AddStep("online scores", () => scoresContainer.Beatmap = new BeatmapInfo { OnlineBeatmapSetID = 1, OnlineBeatmapID = 75, Ruleset = new OsuRuleset().RulesetInfo }); + scores = new[] { diff --git a/osu.Game.Tests/Visual/TestCaseBeatmapSetOverlay.cs b/osu.Game.Tests/Visual/TestCaseBeatmapSetOverlay.cs index 69955a90c4..025562f75f 100644 --- a/osu.Game.Tests/Visual/TestCaseBeatmapSetOverlay.cs +++ b/osu.Game.Tests/Visual/TestCaseBeatmapSetOverlay.cs @@ -8,6 +8,9 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Game.Beatmaps; using osu.Game.Overlays; +using osu.Game.Overlays.BeatmapSet; +using osu.Game.Overlays.BeatmapSet.Buttons; +using osu.Game.Overlays.BeatmapSet.Scores; using osu.Game.Rulesets; using osu.Game.Users; @@ -18,6 +21,26 @@ namespace osu.Game.Tests.Visual { private readonly BeatmapSetOverlay overlay; + public override IReadOnlyList RequiredTypes => new[] + { + typeof(Header), + typeof(ClickableUsername), + typeof(DrawableScore), + typeof(DrawableTopScore), + typeof(ScoresContainer), + typeof(AuthorInfo), + typeof(BasicStats), + typeof(BeatmapPicker), + typeof(Details), + typeof(DownloadButton), + typeof(FavouriteButton), + typeof(Header), + typeof(HeaderButton), + typeof(Info), + typeof(PreviewButton), + typeof(SuccessRate), + }; + public TestCaseBeatmapSetOverlay() { Add(overlay = new BeatmapSetOverlay()); @@ -29,6 +52,10 @@ namespace osu.Game.Tests.Visual var mania = rulesets.GetRuleset(3); var taiko = rulesets.GetRuleset(1); + AddStep(@"show loading", () => overlay.ShowBeatmapSet(null)); + + AddStep(@"show online", () => overlay.FetchAndShowBeatmapSet(55)); + AddStep(@"show first", () => { overlay.ShowBeatmapSet(new BeatmapSetInfo diff --git a/osu.Game.Tests/Visual/TestCasePlayerLoader.cs b/osu.Game.Tests/Visual/TestCasePlayerLoader.cs new file mode 100644 index 0000000000..1e7618232d --- /dev/null +++ b/osu.Game.Tests/Visual/TestCasePlayerLoader.cs @@ -0,0 +1,24 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Game.Beatmaps; +using osu.Game.Screens.Play; + +namespace osu.Game.Tests.Visual +{ + public class TestCasePlayerLoader : OsuTestCase + { + [BackgroundDependencyLoader] + private void load(OsuGameBase game) + { + AddStep("load dummy beatmap", () => Add(new PlayerLoader(new Player + { + InitialBeatmap = new DummyWorkingBeatmap(game), + AllowPause = false, + AllowLeadIn = false, + AllowResults = false, + }))); + } + } +} diff --git a/osu.Game.Tests/Visual/TestCaseUserProfile.cs b/osu.Game.Tests/Visual/TestCaseUserProfile.cs index 0fdc01a974..aca832110a 100644 --- a/osu.Game.Tests/Visual/TestCaseUserProfile.cs +++ b/osu.Game.Tests/Visual/TestCaseUserProfile.cs @@ -5,7 +5,9 @@ using System; using System.Collections.Generic; using System.Linq; using NUnit.Framework; +using osu.Framework.Allocation; using osu.Game.Graphics.UserInterface; +using osu.Game.Online.API; using osu.Game.Overlays; using osu.Game.Overlays.Profile; using osu.Game.Overlays.Profile.Header; @@ -17,6 +19,7 @@ namespace osu.Game.Tests.Visual public class TestCaseUserProfile : OsuTestCase { private readonly TestUserProfileOverlay profile; + private APIAccess api; public override IReadOnlyList RequiredTypes => new[] { @@ -32,6 +35,12 @@ namespace osu.Game.Tests.Visual Add(profile = new TestUserProfileOverlay()); } + [BackgroundDependencyLoader] + private void load(APIAccess api) + { + this.api = api; + } + protected override void LoadComplete() { base.LoadComplete(); @@ -79,9 +88,10 @@ namespace osu.Game.Tests.Visual { Username = @"peppy", Id = 2, + IsSupporter = true, Country = new Country { FullName = @"Australia", FlagName = @"AU" }, CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c3.jpg" - })); + }, api.IsLoggedIn)); checkSupporterTag(true); @@ -91,7 +101,7 @@ namespace osu.Game.Tests.Visual Id = 3103765, Country = new Country { FullName = @"Japan", FlagName = @"JP" }, CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c6.jpg" - })); + }, api.IsLoggedIn)); AddStep("Hide", profile.Hide); AddStep("Show without reload", profile.Show); diff --git a/osu.Game.Tests/osu.Game.Tests.csproj b/osu.Game.Tests/osu.Game.Tests.csproj index 2646e953cf..057c2c2de1 100644 --- a/osu.Game.Tests/osu.Game.Tests.csproj +++ b/osu.Game.Tests/osu.Game.Tests.csproj @@ -2,7 +2,7 @@ WinExe - netcoreapp2.0;net461 + netcoreapp2.0;net471 diff --git a/osu.Game/Beatmaps/Formats/Decoder.cs b/osu.Game/Beatmaps/Formats/Decoder.cs index bcd11f1fc8..2927654f62 100644 --- a/osu.Game/Beatmaps/Formats/Decoder.cs +++ b/osu.Game/Beatmaps/Formats/Decoder.cs @@ -21,7 +21,7 @@ namespace osu.Game.Beatmaps.Formats return output; } - protected abstract void ParseStreamInto(StreamReader stream, TOutput beatmap); + protected abstract void ParseStreamInto(StreamReader stream, TOutput output); } public abstract class Decoder diff --git a/osu.Game/Beatmaps/Formats/JsonBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/JsonBeatmapDecoder.cs index e5574cd82e..fba89b8ac1 100644 --- a/osu.Game/Beatmaps/Formats/JsonBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/JsonBeatmapDecoder.cs @@ -13,15 +13,15 @@ namespace osu.Game.Beatmaps.Formats AddDecoder("{", m => new JsonBeatmapDecoder()); } - protected override void ParseStreamInto(StreamReader stream, Beatmap beatmap) + protected override void ParseStreamInto(StreamReader stream, Beatmap output) { stream.BaseStream.Position = 0; stream.DiscardBufferedData(); - stream.ReadToEnd().DeserializeInto(beatmap); + stream.ReadToEnd().DeserializeInto(output); - foreach (var hitObject in beatmap.HitObjects) - hitObject.ApplyDefaults(beatmap.ControlPointInfo, beatmap.BeatmapInfo.BaseDifficulty); + foreach (var hitObject in output.HitObjects) + hitObject.ApplyDefaults(output.ControlPointInfo, output.BeatmapInfo.BaseDifficulty); } } } diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index 366b4f163f..655355913c 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -373,17 +373,18 @@ namespace osu.Game.Beatmaps.Formats if (parser == null) parser = new Rulesets.Objects.Legacy.Osu.ConvertHitObjectParser(); - var obj = parser.Parse(line); + var obj = parser.Parse(line, getOffsetTime()); if (obj != null) { - obj.StartTime = getOffsetTime(obj.StartTime); beatmap.HitObjects.Add(obj); } } private int getOffsetTime(int time) => time + (ApplyOffsets ? offset : 0); + private double getOffsetTime() => ApplyOffsets ? offset : 0; + private double getOffsetTime(double time) => time + (ApplyOffsets ? offset : 0); } } diff --git a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs index 06160a87e0..e77efd8508 100644 --- a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs @@ -19,7 +19,7 @@ namespace osu.Game.Beatmaps.Formats FormatVersion = version; } - protected override void ParseStreamInto(StreamReader stream, T beatmap) + protected override void ParseStreamInto(StreamReader stream, T output) { Section section = Section.None; @@ -33,14 +33,21 @@ namespace osu.Game.Beatmaps.Formats { if (!Enum.TryParse(line.Substring(1, line.Length - 2), out section)) { - Logger.Log($"Unknown section \"{line}\" in {beatmap}"); + Logger.Log($"Unknown section \"{line}\" in {output}"); section = Section.None; } continue; } - ParseLine(beatmap, section, line); + try + { + ParseLine(output, section, line); + } + catch (Exception e) + { + Logger.Error(e, $"Failed to process line \"{line}\" into {output}"); + } } } diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 3efaa02a31..509622c2fe 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -60,6 +60,9 @@ namespace osu.Game.Configuration Set(OsuSetting.ShowFpsDisplay, false); Set(OsuSetting.ShowStoryboard, true); + Set(OsuSetting.BeatmapSkins, true); + Set(OsuSetting.BeatmapHitsounds, true); + Set(OsuSetting.CursorRotation, true); Set(OsuSetting.MenuParallax, true); @@ -133,6 +136,8 @@ namespace osu.Game.Configuration Skin, ScreenshotFormat, ScreenshotCaptureMenuCursor, - SongSelectRightMouseScroll + SongSelectRightMouseScroll, + BeatmapSkins, + BeatmapHitsounds } } diff --git a/osu.Game/Graphics/Containers/LinkFlowContainer.cs b/osu.Game/Graphics/Containers/LinkFlowContainer.cs index 8e18dbd2f5..157c814f55 100644 --- a/osu.Game/Graphics/Containers/LinkFlowContainer.cs +++ b/osu.Game/Graphics/Containers/LinkFlowContainer.cs @@ -61,9 +61,9 @@ namespace osu.Game.Graphics.Containers AddText(text.Substring(previousLinkEnd)); } - public void AddLink(string text, string url, LinkAction linkType = LinkAction.External, string linkArgument = null, string tooltipText = null) + public void AddLink(string text, string url, LinkAction linkType = LinkAction.External, string linkArgument = null, string tooltipText = null, Action creationParameters = null) { - AddInternal(new DrawableLinkCompiler(AddText(text).ToList()) + AddInternal(new DrawableLinkCompiler(AddText(text, creationParameters).ToList()) { TooltipText = tooltipText ?? (url != text ? url : string.Empty), Action = () => @@ -71,9 +71,9 @@ namespace osu.Game.Graphics.Containers switch (linkType) { case LinkAction.OpenBeatmap: - // todo: replace this with overlay.ShowBeatmap(id) once an appropriate API call is implemented. - if (int.TryParse(linkArgument, out int beatmapId)) - Process.Start($"https://osu.ppy.sh/b/{beatmapId}"); + // TODO: proper query params handling + if (linkArgument != null && int.TryParse(linkArgument.Contains('?') ? linkArgument.Split('?')[0] : linkArgument, out int beatmapId)) + game?.ShowBeatmap(beatmapId); break; case LinkAction.OpenBeatmapSet: if (int.TryParse(linkArgument, out int setId)) diff --git a/osu.Game/IO/Archives/ArchiveReader.cs b/osu.Game/IO/Archives/ArchiveReader.cs index d14080de5b..808ce159bb 100644 --- a/osu.Game/IO/Archives/ArchiveReader.cs +++ b/osu.Game/IO/Archives/ArchiveReader.cs @@ -1,14 +1,13 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using System; using System.Collections.Generic; using System.IO; using osu.Framework.IO.Stores; namespace osu.Game.IO.Archives { - public abstract class ArchiveReader : IDisposable, IResourceStore + public abstract class ArchiveReader : IResourceStore { /// /// Opens a stream for reading a specific file from this archive. diff --git a/osu.Game/Online/API/Requests/GetBeatmapSetRequest.cs b/osu.Game/Online/API/Requests/GetBeatmapSetRequest.cs index b031791900..37dd77af46 100644 --- a/osu.Game/Online/API/Requests/GetBeatmapSetRequest.cs +++ b/osu.Game/Online/API/Requests/GetBeatmapSetRequest.cs @@ -5,13 +5,21 @@ namespace osu.Game.Online.API.Requests { public class GetBeatmapSetRequest : APIRequest { - private readonly int beatmapSetId; + private readonly int id; + private readonly BeatmapSetLookupType type; - public GetBeatmapSetRequest(int beatmapSetId) + public GetBeatmapSetRequest(int id, BeatmapSetLookupType type = BeatmapSetLookupType.SetId) { - this.beatmapSetId = beatmapSetId; + this.id = id; + this.type = type; } - protected override string Target => $@"beatmapsets/{beatmapSetId}"; + protected override string Target => type == BeatmapSetLookupType.SetId ? $@"beatmapsets/{id}" : $@"beatmapsets/lookup?beatmap_id={id}"; + } + + public enum BeatmapSetLookupType + { + SetId, + BeatmapId, } } diff --git a/osu.Game/Online/API/Requests/GetMessagesRequest.cs b/osu.Game/Online/API/Requests/GetMessagesRequest.cs index d600f40716..68de194bae 100644 --- a/osu.Game/Online/API/Requests/GetMessagesRequest.cs +++ b/osu.Game/Online/API/Requests/GetMessagesRequest.cs @@ -11,7 +11,7 @@ namespace osu.Game.Online.API.Requests public class GetMessagesRequest : APIRequest> { private readonly List channels; - private long? since; + private readonly long? since; public GetMessagesRequest(List channels, long? sinceId) { diff --git a/osu.Game/Online/API/Requests/GetUserRequest.cs b/osu.Game/Online/API/Requests/GetUserRequest.cs index 9026d10334..607e8e5127 100644 --- a/osu.Game/Online/API/Requests/GetUserRequest.cs +++ b/osu.Game/Online/API/Requests/GetUserRequest.cs @@ -7,7 +7,7 @@ namespace osu.Game.Online.API.Requests { public class GetUserRequest : APIRequest { - private long? userId; + private readonly long? userId; public GetUserRequest(long? userId = null) { diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index f69135e5c9..d443ed36ae 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -158,7 +158,7 @@ namespace osu.Game /// Show a beatmap set as an overlay. /// /// The set to display. - public void ShowBeatmapSet(int setId) => beatmapSetOverlay.ShowBeatmapSet(setId); + public void ShowBeatmapSet(int setId) => beatmapSetOverlay.FetchAndShowBeatmapSet(setId); /// /// Show a user's profile as an overlay. @@ -166,6 +166,12 @@ namespace osu.Game /// The user to display. public void ShowUser(long userId) => userProfile.ShowUser(userId); + /// + /// Show a beatmap's set as an overlay, displaying the given beatmap. + /// + /// The beatmap to show. + public void ShowBeatmap(int beatmapId) => beatmapSetOverlay.FetchAndShowBeatmap(beatmapId); + protected void LoadScore(Score s) { scoreLoad?.Cancel(); @@ -195,6 +201,8 @@ namespace osu.Game return; } + Ruleset.Value = s.Ruleset; + Beatmap.Value = BeatmapManager.GetWorkingBeatmap(s.Beatmap); Beatmap.Value.Mods.Value = s.Mods; diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index b76510ea1e..487cb50c9a 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -188,6 +188,20 @@ namespace osu.Game FileStore.Cleanup(); AddInternal(api); + + GlobalActionContainer globalBinding; + + CursorOverrideContainer = new CursorOverrideContainer { RelativeSizeAxes = Axes.Both }; + CursorOverrideContainer.Child = globalBinding = new GlobalActionContainer(this) + { + RelativeSizeAxes = Axes.Both, + Child = content = new OsuTooltipContainer(CursorOverrideContainer.Cursor) { RelativeSizeAxes = Axes.Both } + }; + + base.Content.Add(new DrawSizePreservingFillContainer { Child = CursorOverrideContainer }); + + KeyBindingStore.Register(globalBinding); + dependencies.Cache(globalBinding); } private void runMigrations() @@ -217,20 +231,6 @@ namespace osu.Game { base.LoadComplete(); - GlobalActionContainer globalBinding; - - CursorOverrideContainer = new CursorOverrideContainer { RelativeSizeAxes = Axes.Both }; - CursorOverrideContainer.Child = globalBinding = new GlobalActionContainer(this) - { - RelativeSizeAxes = Axes.Both, - Child = content = new OsuTooltipContainer(CursorOverrideContainer.Cursor) { RelativeSizeAxes = Axes.Both } - }; - - base.Content.Add(new DrawSizePreservingFillContainer { Child = CursorOverrideContainer }); - - KeyBindingStore.Register(globalBinding); - dependencies.Cache(globalBinding); - // TODO: This is temporary until we reimplement the local FPS display. // It's just to allow end-users to access the framework FPS display without knowing the shortcut key. fpsDisplayVisible = LocalConfig.GetBindable(OsuSetting.ShowFpsDisplay); diff --git a/osu.Game/Overlays/BeatmapSet/AuthorInfo.cs b/osu.Game/Overlays/BeatmapSet/AuthorInfo.cs index 8b19bca671..66e3148065 100644 --- a/osu.Game/Overlays/BeatmapSet/AuthorInfo.cs +++ b/osu.Game/Overlays/BeatmapSet/AuthorInfo.cs @@ -23,9 +23,8 @@ namespace osu.Game.Overlays.BeatmapSet private readonly ClickableArea clickableArea; private readonly FillFlowContainer fields; - private UserProfileOverlay profile; - private BeatmapSetInfo beatmapSet; + public BeatmapSetInfo BeatmapSet { get { return beatmapSet; } @@ -34,28 +33,36 @@ namespace osu.Game.Overlays.BeatmapSet if (value == beatmapSet) return; beatmapSet = value; - var i = BeatmapSet.OnlineInfo; + updateDisplay(); + } + } - avatar.User = BeatmapSet.Metadata.Author; - clickableArea.Action = () => profile?.ShowUser(avatar.User); + private void updateDisplay() + { + avatar.User = BeatmapSet?.Metadata.Author; - fields.Children = new Drawable[] - { - new Field("made by", BeatmapSet.Metadata.Author.Username, @"Exo2.0-RegularItalic"), - new Field("submitted on", i.Submitted.ToString(@"MMM d, yyyy"), @"Exo2.0-Bold") - { - Margin = new MarginPadding { Top = 5 }, - }, - }; + fields.Clear(); + if (BeatmapSet == null) + return; - if (i.Ranked.HasValue) + var online = BeatmapSet.OnlineInfo; + + fields.Children = new Drawable[] + { + new Field("made by", BeatmapSet.Metadata.Author.Username, @"Exo2.0-RegularItalic"), + new Field("submitted on", online.Submitted.ToString(@"MMM d, yyyy"), @"Exo2.0-Bold") { - fields.Add(new Field("ranked on ", i.Ranked.Value.ToString(@"MMM d, yyyy"), @"Exo2.0-Bold")); - } - else if (i.LastUpdated.HasValue) - { - fields.Add(new Field("last updated on ", i.LastUpdated.Value.ToString(@"MMM d, yyyy"), @"Exo2.0-Bold")); - } + Margin = new MarginPadding { Top = 5 }, + }, + }; + + if (online.Ranked.HasValue) + { + fields.Add(new Field("ranked on ", online.Ranked.Value.ToString(@"MMM d, yyyy"), @"Exo2.0-Bold")); + } + else if (online.LastUpdated.HasValue) + { + fields.Add(new Field("last updated on ", online.LastUpdated.Value.ToString(@"MMM d, yyyy"), @"Exo2.0-Bold")); } } @@ -73,6 +80,7 @@ namespace osu.Game.Overlays.BeatmapSet Masking = true, Child = avatar = new UpdateableAvatar { + ShowGuestOnNull = false, Size = new Vector2(height), }, EdgeEffect = new EdgeEffectParameters @@ -95,8 +103,12 @@ namespace osu.Game.Overlays.BeatmapSet [BackgroundDependencyLoader(true)] private void load(UserProfileOverlay profile) { - this.profile = profile; - clickableArea.Action = () => profile?.ShowUser(avatar.User); + clickableArea.Action = () => + { + if (avatar.User != null) profile?.ShowUser(avatar.User); + }; + + updateDisplay(); } private class Field : FillFlowContainer diff --git a/osu.Game/Overlays/BeatmapSet/BasicStats.cs b/osu.Game/Overlays/BeatmapSet/BasicStats.cs index 8fd34914a7..a7b6b16dcc 100644 --- a/osu.Game/Overlays/BeatmapSet/BasicStats.cs +++ b/osu.Game/Overlays/BeatmapSet/BasicStats.cs @@ -18,6 +18,7 @@ namespace osu.Game.Overlays.BeatmapSet private readonly Statistic length, bpm, circleCount, sliderCount; private BeatmapSetInfo beatmapSet; + public BeatmapSetInfo BeatmapSet { get { return beatmapSet; } @@ -26,11 +27,12 @@ namespace osu.Game.Overlays.BeatmapSet if (value == beatmapSet) return; beatmapSet = value; - bpm.Value = BeatmapSet.OnlineInfo.BPM.ToString(@"0.##"); + updateDisplay(); } } private BeatmapInfo beatmap; + public BeatmapInfo Beatmap { get { return beatmap; } @@ -39,6 +41,22 @@ namespace osu.Game.Overlays.BeatmapSet if (value == beatmap) return; beatmap = value; + updateDisplay(); + } + } + + private void updateDisplay() + { + bpm.Value = BeatmapSet?.OnlineInfo.BPM.ToString(@"0.##") ?? "-"; + + if (beatmap == null) + { + length.Value = string.Empty; + circleCount.Value = string.Empty; + sliderCount.Value = string.Empty; + } + else + { length.Value = TimeSpan.FromSeconds(beatmap.OnlineInfo.Length).ToString(@"m\:ss"); circleCount.Value = beatmap.OnlineInfo.CircleCount.ToString(); sliderCount.Value = beatmap.OnlineInfo.SliderCount.ToString(); @@ -62,12 +80,19 @@ namespace osu.Game.Overlays.BeatmapSet }; } + [BackgroundDependencyLoader] + private void load() + { + updateDisplay(); + } + private class Statistic : Container, IHasTooltip { private readonly string name; private readonly OsuSpriteText value; public string TooltipText => name; + public string Value { get { return value.Text; } diff --git a/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs b/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs index 87dc9b104b..6b75ac095d 100644 --- a/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs +++ b/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs @@ -41,9 +41,16 @@ namespace osu.Game.Overlays.BeatmapSet if (value == beatmapSet) return; beatmapSet = value; - Beatmap.Value = BeatmapSet.Beatmaps.First(); - plays.Value = BeatmapSet.OnlineInfo.PlayCount; - favourites.Value = BeatmapSet.OnlineInfo.FavouriteCount; + updateDisplay(); + } + } + + private void updateDisplay() + { + difficulties.Clear(); + + if (BeatmapSet != null) + { difficulties.ChildrenEnumerable = BeatmapSet.Beatmaps.OrderBy(beatmap => beatmap.StarDifficulty).Select(b => new DifficultySelectorButton(b) { State = DifficultySelectorState.NotSelected, @@ -53,14 +60,16 @@ namespace osu.Game.Overlays.BeatmapSet starRating.Text = beatmap.StarDifficulty.ToString("Star Difficulty 0.##"); starRating.FadeIn(100); }, - OnClicked = beatmap => - { - Beatmap.Value = beatmap; - }, + OnClicked = beatmap => { Beatmap.Value = beatmap; }, }); - - updateDifficultyButtons(); } + + starRating.FadeOut(100); + Beatmap.Value = BeatmapSet?.Beatmaps.FirstOrDefault(); + plays.Value = BeatmapSet?.OnlineInfo.PlayCount ?? 0; + favourites.Value = BeatmapSet?.OnlineInfo.FavouriteCount ?? 0; + + updateDifficultyButtons(); } public BeatmapPicker() @@ -140,6 +149,7 @@ namespace osu.Game.Overlays.BeatmapSet private void load(OsuColour colours) { starRating.Colour = colours.Yellow; + updateDisplay(); } protected override void LoadComplete() @@ -150,7 +160,10 @@ namespace osu.Game.Overlays.BeatmapSet Beatmap.TriggerChange(); } - private void showBeatmap(BeatmapInfo beatmap) => version.Text = beatmap.Version; + private void showBeatmap(BeatmapInfo beatmap) + { + version.Text = beatmap?.Version; + } private void updateDifficultyButtons() { diff --git a/osu.Game/Overlays/BeatmapSet/DownloadButton.cs b/osu.Game/Overlays/BeatmapSet/Buttons/DownloadButton.cs similarity index 97% rename from osu.Game/Overlays/BeatmapSet/DownloadButton.cs rename to osu.Game/Overlays/BeatmapSet/Buttons/DownloadButton.cs index 0c6414c718..c699ae2328 100644 --- a/osu.Game/Overlays/BeatmapSet/DownloadButton.cs +++ b/osu.Game/Overlays/BeatmapSet/Buttons/DownloadButton.cs @@ -7,7 +7,7 @@ using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using OpenTK; -namespace osu.Game.Overlays.BeatmapSet +namespace osu.Game.Overlays.BeatmapSet.Buttons { public class DownloadButton : HeaderButton { diff --git a/osu.Game/Overlays/BeatmapSet/FavouriteButton.cs b/osu.Game/Overlays/BeatmapSet/Buttons/FavouriteButton.cs similarity index 98% rename from osu.Game/Overlays/BeatmapSet/FavouriteButton.cs rename to osu.Game/Overlays/BeatmapSet/Buttons/FavouriteButton.cs index 29bc00038c..3821c96369 100644 --- a/osu.Game/Overlays/BeatmapSet/FavouriteButton.cs +++ b/osu.Game/Overlays/BeatmapSet/Buttons/FavouriteButton.cs @@ -10,7 +10,7 @@ using osu.Game.Graphics; using osu.Game.Graphics.Backgrounds; using OpenTK; -namespace osu.Game.Overlays.BeatmapSet +namespace osu.Game.Overlays.BeatmapSet.Buttons { public class FavouriteButton : HeaderButton { diff --git a/osu.Game/Overlays/BeatmapSet/HeaderButton.cs b/osu.Game/Overlays/BeatmapSet/Buttons/HeaderButton.cs similarity index 94% rename from osu.Game/Overlays/BeatmapSet/HeaderButton.cs rename to osu.Game/Overlays/BeatmapSet/Buttons/HeaderButton.cs index e1c4f5cc67..b46b5d2a0e 100644 --- a/osu.Game/Overlays/BeatmapSet/HeaderButton.cs +++ b/osu.Game/Overlays/BeatmapSet/Buttons/HeaderButton.cs @@ -1,12 +1,12 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; -using osu.Framework.Allocation; -namespace osu.Game.Overlays.BeatmapSet +namespace osu.Game.Overlays.BeatmapSet.Buttons { public class HeaderButton : TriangleButton { diff --git a/osu.Game/Overlays/BeatmapSet/PreviewButton.cs b/osu.Game/Overlays/BeatmapSet/Buttons/PreviewButton.cs similarity index 97% rename from osu.Game/Overlays/BeatmapSet/PreviewButton.cs rename to osu.Game/Overlays/BeatmapSet/Buttons/PreviewButton.cs index 6b5ffa57ad..08a99f1aea 100644 --- a/osu.Game/Overlays/BeatmapSet/PreviewButton.cs +++ b/osu.Game/Overlays/BeatmapSet/Buttons/PreviewButton.cs @@ -3,6 +3,7 @@ using osu.Framework.Allocation; using osu.Framework.Audio.Track; +using osu.Framework.Configuration; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -11,12 +12,11 @@ using osu.Framework.Input; using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Containers; +using osu.Game.Overlays.Direct; using OpenTK; using OpenTK.Graphics; -using osu.Game.Overlays.Direct; -using osu.Framework.Configuration; -namespace osu.Game.Overlays.BeatmapSet +namespace osu.Game.Overlays.BeatmapSet.Buttons { public class PreviewButton : OsuClickableContainer { @@ -85,6 +85,8 @@ namespace osu.Game.Overlays.BeatmapSet // prevent negative (potential infinite) width if a track without length was loaded progress.Width = preview.Length > 0 ? (float)(preview.CurrentTime / preview.Length) : 0f; } + else + progress.Width = 0; } protected override void Dispose(bool isDisposing) diff --git a/osu.Game/Overlays/BeatmapSet/Details.cs b/osu.Game/Overlays/BeatmapSet/Details.cs index 7f3b6d1584..5264caf936 100644 --- a/osu.Game/Overlays/BeatmapSet/Details.cs +++ b/osu.Game/Overlays/BeatmapSet/Details.cs @@ -1,11 +1,13 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using osu.Framework.Allocation; 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.Overlays.BeatmapSet.Buttons; using osu.Game.Screens.Select.Details; using OpenTK; using OpenTK.Graphics; @@ -20,6 +22,7 @@ namespace osu.Game.Overlays.BeatmapSet private readonly UserRatings ratings; private BeatmapSetInfo beatmapSet; + public BeatmapSetInfo BeatmapSet { get { return beatmapSet; } @@ -33,19 +36,24 @@ namespace osu.Game.Overlays.BeatmapSet } private BeatmapInfo beatmap; + public BeatmapInfo Beatmap { get { return beatmap; } set { if (value == beatmap) return; - beatmap = value; - basic.Beatmap = advanced.Beatmap = Beatmap; - ratings.Metrics = Beatmap.Metrics; + basic.Beatmap = advanced.Beatmap = beatmap = value; + updateDisplay(); } } + private void updateDisplay() + { + ratings.Metrics = Beatmap?.Metrics; + } + public Details() { Width = BeatmapSetOverlay.RIGHT_WIDTH; @@ -88,6 +96,12 @@ namespace osu.Game.Overlays.BeatmapSet }; } + [BackgroundDependencyLoader] + private void load() + { + updateDisplay(); + } + public void StopPreview() => preview.Playing.Value = false; private class DetailBox : Container diff --git a/osu.Game/Overlays/BeatmapSet/Header.cs b/osu.Game/Overlays/BeatmapSet/Header.cs index 755039e7bc..9b25d61f58 100644 --- a/osu.Game/Overlays/BeatmapSet/Header.cs +++ b/osu.Game/Overlays/BeatmapSet/Header.cs @@ -11,6 +11,7 @@ using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; +using osu.Game.Overlays.BeatmapSet.Buttons; using OpenTK; using OpenTK.Graphics; @@ -18,7 +19,7 @@ namespace osu.Game.Overlays.BeatmapSet { public class Header : Container { - private const float transition_duration = 250; + private const float transition_duration = 200; private const float tabs_height = 50; private const float buttons_height = 45; private const float buttons_spacing = 5; @@ -34,12 +35,13 @@ namespace osu.Game.Overlays.BeatmapSet public Details Details; private BeatmapManager beatmaps; - private DelayedLoadWrapper cover; public readonly BeatmapPicker Picker; private BeatmapSetInfo beatmapSet; + private readonly FavouriteButton favouriteButton; + public BeatmapSetInfo BeatmapSet { get { return beatmapSet; } @@ -49,15 +51,26 @@ namespace osu.Game.Overlays.BeatmapSet beatmapSet = value; Picker.BeatmapSet = author.BeatmapSet = Details.BeatmapSet = BeatmapSet; - title.Text = BeatmapSet.Metadata.Title; - artist.Text = BeatmapSet.Metadata.Artist; - onlineStatusPill.Status = BeatmapSet.OnlineInfo.Status; - downloadButtonsContainer.FadeIn(); + updateDisplay(); + } + } + + private void updateDisplay() + { + title.Text = BeatmapSet?.Metadata.Title ?? string.Empty; + artist.Text = BeatmapSet?.Metadata.Artist ?? string.Empty; + onlineStatusPill.Status = BeatmapSet?.OnlineInfo.Status ?? BeatmapSetOnlineStatus.None; + + cover?.FadeOut(400, Easing.Out); + if (BeatmapSet != null) + { + downloadButtonsContainer.FadeIn(transition_duration); + favouriteButton.FadeIn(transition_duration); + noVideoButtons.FadeTo(BeatmapSet.OnlineInfo.HasVideo ? 0 : 1, transition_duration); videoButtons.FadeTo(BeatmapSet.OnlineInfo.HasVideo ? 1 : 0, transition_duration); - cover?.FadeOut(400, Easing.Out); coverContainer.Add(cover = new DelayedLoadWrapper( new BeatmapSetCover(BeatmapSet) { @@ -71,6 +84,11 @@ namespace osu.Game.Overlays.BeatmapSet RelativeSizeAxes = Axes.Both, }); } + else + { + downloadButtonsContainer.FadeOut(transition_duration); + favouriteButton.FadeOut(transition_duration); + } } public Header() @@ -166,7 +184,7 @@ namespace osu.Game.Overlays.BeatmapSet Margin = new MarginPadding { Top = 10 }, Children = new Drawable[] { - new FavouriteButton(), + favouriteButton = new FavouriteButton(), downloadButtonsContainer = new Container { RelativeSizeAxes = Axes.Both, @@ -238,6 +256,8 @@ namespace osu.Game.Overlays.BeatmapSet this.beatmaps = beatmaps; beatmaps.ItemAdded += handleBeatmapAdd; + + updateDisplay(); } protected override void Dispose(bool isDisposing) diff --git a/osu.Game/Overlays/BeatmapSet/Info.cs b/osu.Game/Overlays/BeatmapSet/Info.cs index 9a402515ae..cd0b7386e8 100644 --- a/osu.Game/Overlays/BeatmapSet/Info.cs +++ b/osu.Game/Overlays/BeatmapSet/Info.cs @@ -34,11 +34,16 @@ namespace osu.Game.Overlays.BeatmapSet if (value == beatmapSet) return; beatmapSet = value; - source.Text = BeatmapSet.Metadata.Source; - tags.Text = BeatmapSet.Metadata.Tags; + updateDisplay(); } } + private void updateDisplay() + { + source.Text = BeatmapSet?.Metadata.Source ?? string.Empty; + tags.Text = BeatmapSet?.Metadata.Tags ?? string.Empty; + } + public BeatmapInfo Beatmap { get { return successRate.Beatmap; } @@ -132,6 +137,8 @@ namespace osu.Game.Overlays.BeatmapSet successRateBackground.Colour = colours.GrayE; source.TextColour = description.TextColour = colours.Gray5; tags.TextColour = colours.BlueDark; + + updateDisplay(); } private class MetadataSection : FillFlowContainer diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs index d5c5bd8ddd..185282bec9 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs @@ -2,15 +2,15 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using OpenTK; -using OpenTK.Graphics; -using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; using osu.Game.Graphics.UserInterface; using osu.Game.Online.API.Requests; using System.Collections.Generic; using System.Linq; +using osu.Framework.Allocation; +using osu.Game.Beatmaps; +using osu.Game.Online.API; namespace osu.Game.Overlays.BeatmapSet.Scores { @@ -22,49 +22,75 @@ namespace osu.Game.Overlays.BeatmapSet.Scores private readonly FillFlowContainer flow; private readonly DrawableTopScore topScore; private readonly LoadingAnimation loadingAnimation; - private readonly Box foreground; - private bool isLoading; - public bool IsLoading + private bool loading { - get { return isLoading; } - set - { - if (isLoading == value) return; - isLoading = value; - - foreground.FadeTo(isLoading ? 1 : 0, fade_duration); - loadingAnimation.FadeTo(isLoading ? 1 : 0, fade_duration); - } + set => loadingAnimation.FadeTo(value ? 1 : 0, fade_duration); } private IEnumerable scores; + private BeatmapInfo beatmap; + public IEnumerable Scores { get { return scores; } set { + getScoresRequest?.Cancel(); scores = value; - var scoresAmount = scores.Count(); - if (scoresAmount == 0) - { - CleanAllScores(); - return; - } - topScore.Score = scores.FirstOrDefault(); - topScore.Show(); - - flow.Clear(); - - if (scoresAmount < 2) - return; - - for (int i = 1; i < scoresAmount; i++) - flow.Add(new DrawableScore(i, scores.ElementAt(i))); + updateDisplay(); } } + private GetScoresRequest getScoresRequest; + private APIAccess api; + + public BeatmapInfo Beatmap + { + get => beatmap; + set + { + beatmap = value; + + Scores = null; + + if (beatmap?.OnlineBeatmapID.HasValue != true) + return; + + loading = true; + + getScoresRequest = new GetScoresRequest(beatmap, beatmap.Ruleset); + getScoresRequest.Success += r => Scores = r.Scores; + api.Queue(getScoresRequest); + } + } + + private void updateDisplay() + { + loading = false; + + var scoreCount = scores?.Count() ?? 0; + + if (scoreCount == 0) + { + topScore.Hide(); + flow.Clear(); + return; + } + + topScore.Score = scores.FirstOrDefault(); + topScore.Show(); + + flow.Clear(); + + if (scoreCount < 2) + return; + + for (int i = 1; i < scoreCount; i++) + flow.Add(new DrawableScore(i, scores.ElementAt(i))); + } + public ScoresContainer() { RelativeSizeAxes = Axes.X; @@ -93,23 +119,19 @@ namespace osu.Game.Overlays.BeatmapSet.Scores }, } }, - foreground = new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.Black.Opacity(0.7f), - Alpha = 0, - }, loadingAnimation = new LoadingAnimation { Alpha = 0, + Margin = new MarginPadding(20) }, }; } - public void CleanAllScores() + [BackgroundDependencyLoader] + private void load(APIAccess api) { - topScore.Hide(); - flow.Clear(); + this.api = api; + updateDisplay(); } } } diff --git a/osu.Game/Overlays/BeatmapSet/SuccessRate.cs b/osu.Game/Overlays/BeatmapSet/SuccessRate.cs index c64d3988a6..6572e520bd 100644 --- a/osu.Game/Overlays/BeatmapSet/SuccessRate.cs +++ b/osu.Game/Overlays/BeatmapSet/SuccessRate.cs @@ -29,18 +29,23 @@ namespace osu.Game.Overlays.BeatmapSet if (value == beatmap) return; beatmap = value; - int passCount = beatmap.OnlineInfo.PassCount; - int playCount = beatmap.OnlineInfo.PlayCount; - - var rate = playCount != 0 ? (float)passCount / playCount : 0; - successPercent.Text = rate.ToString("P0"); - successRate.Length = rate; - percentContainer.ResizeWidthTo(successRate.Length, 250, Easing.InOutCubic); - - graph.Metrics = Beatmap.Metrics; + updateDisplay(); } } + private void updateDisplay() + { + int passCount = beatmap?.OnlineInfo.PassCount ?? 0; + int playCount = beatmap?.OnlineInfo.PlayCount ?? 0; + + var rate = playCount != 0 ? (float)passCount / playCount : 0; + successPercent.Text = rate.ToString("P0"); + successRate.Length = rate; + percentContainer.ResizeWidthTo(successRate.Length, 250, Easing.InOutCubic); + + graph.Metrics = beatmap?.Metrics; + } + public SuccessRate() { Children = new Drawable[] @@ -74,7 +79,6 @@ namespace osu.Game.Overlays.BeatmapSet { Anchor = Anchor.TopRight, Origin = Anchor.TopCentre, - Text = @"0%", TextSize = 13, }, }, @@ -103,6 +107,8 @@ namespace osu.Game.Overlays.BeatmapSet successRateLabel.Colour = successPercent.Colour = graphLabel.Colour = colours.Gray5; successRate.AccentColour = colours.Green; successRate.BackgroundColour = colours.GrayD; + + updateDisplay(); } protected override void UpdateAfterChildren() diff --git a/osu.Game/Overlays/BeatmapSetOverlay.cs b/osu.Game/Overlays/BeatmapSetOverlay.cs index d2cf0a6ef1..096f7bb63c 100644 --- a/osu.Game/Overlays/BeatmapSetOverlay.cs +++ b/osu.Game/Overlays/BeatmapSetOverlay.cs @@ -17,29 +17,45 @@ using osu.Game.Online.API.Requests; using osu.Game.Overlays.BeatmapSet; using osu.Game.Rulesets; using osu.Game.Overlays.BeatmapSet.Scores; +using System.Linq; namespace osu.Game.Overlays { public class BeatmapSetOverlay : WaveOverlayContainer { + private const int fade_duration = 300; + public const float X_PADDING = 40; public const float RIGHT_WIDTH = 275; private readonly Header header; private readonly Info info; - private readonly ScoresContainer scores; private APIAccess api; private RulesetStore rulesets; - private GetScoresRequest getScoresRequest; private readonly ScrollContainer scroll; + private BeatmapSetInfo beatmapSet; + + public BeatmapSetInfo BeatmapSet + { + get => beatmapSet; + set + { + if (value == beatmapSet) + return; + + header.BeatmapSet = info.BeatmapSet = beatmapSet = value; + } + } + // receive input outside our bounds so we can trigger a close event on ourselves. public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => true; public BeatmapSetOverlay() { + ScoresContainer scores; Waves.FirstWaveColour = OsuColour.Gray(0.4f); Waves.SecondWaveColour = OsuColour.Gray(0.3f); Waves.ThirdWaveColour = OsuColour.Gray(0.2f); @@ -88,31 +104,10 @@ namespace osu.Game.Overlays header.Picker.Beatmap.ValueChanged += b => { info.Beatmap = b; - updateScores(b); + scores.Beatmap = b; }; } - private void updateScores(BeatmapInfo beatmap) - { - getScoresRequest?.Cancel(); - - if (!beatmap.OnlineBeatmapID.HasValue) - { - scores.CleanAllScores(); - return; - } - - scores.IsLoading = true; - - getScoresRequest = new GetScoresRequest(beatmap, beatmap.Ruleset); - getScoresRequest.Success += r => - { - scores.Scores = r.Scores; - scores.IsLoading = false; - }; - api.Queue(getScoresRequest); - } - [BackgroundDependencyLoader] private void load(APIAccess api, RulesetStore rulesets) { @@ -130,7 +125,8 @@ namespace osu.Game.Overlays { base.PopOut(); header.Details.StopPreview(); - FadeEdgeEffectTo(0, WaveContainer.DISAPPEAR_DURATION, Easing.Out); + + FadeEdgeEffectTo(0, WaveContainer.DISAPPEAR_DURATION, Easing.Out).OnComplete(_ => BeatmapSet = null); } protected override bool OnClick(InputState state) @@ -139,17 +135,31 @@ namespace osu.Game.Overlays return true; } - public void ShowBeatmapSet(int beatmapSetId) + public void FetchAndShowBeatmap(int beatmapId) { - // todo: display the overlay while we are loading here. we need to support setting BeatmapSet to null for this to work. + BeatmapSet = null; + var req = new GetBeatmapSetRequest(beatmapId, BeatmapSetLookupType.BeatmapId); + req.Success += res => + { + ShowBeatmapSet(res.ToBeatmapSet(rulesets)); + header.Picker.Beatmap.Value = header.BeatmapSet.Beatmaps.First(b => b.OnlineBeatmapID == beatmapId); + }; + api.Queue(req); + Show(); + } + + public void FetchAndShowBeatmapSet(int beatmapSetId) + { + BeatmapSet = null; var req = new GetBeatmapSetRequest(beatmapSetId); req.Success += res => ShowBeatmapSet(res.ToBeatmapSet(rulesets)); api.Queue(req); + Show(); } public void ShowBeatmapSet(BeatmapSetInfo set) { - header.BeatmapSet = info.BeatmapSet = set; + BeatmapSet = set; Show(); scroll.ScrollTo(0); } diff --git a/osu.Game/Overlays/Chat/ChatTabControl.cs b/osu.Game/Overlays/Chat/ChatTabControl.cs index 77a30e6389..91b790ece4 100644 --- a/osu.Game/Overlays/Chat/ChatTabControl.cs +++ b/osu.Game/Overlays/Chat/ChatTabControl.cs @@ -307,6 +307,8 @@ namespace osu.Game.Overlays.Chat { public override bool IsRemovable => false; + public override bool IsSwitchable => false; + public ChannelSelectorTabItem(Channel value) : base(value) { Depth = float.MaxValue; diff --git a/osu.Game/Overlays/Direct/PlayButton.cs b/osu.Game/Overlays/Direct/PlayButton.cs index 44e24d8157..4913b11ae1 100644 --- a/osu.Game/Overlays/Direct/PlayButton.cs +++ b/osu.Game/Overlays/Direct/PlayButton.cs @@ -78,12 +78,7 @@ namespace osu.Game.Overlays.Direct loadingAnimation = new LoadingAnimation(), }); - Playing.ValueChanged += playing => - { - icon.Icon = playing ? FontAwesome.fa_pause : FontAwesome.fa_play; - icon.FadeColour(playing || IsHovered ? hoverColour : Color4.White, 120, Easing.InOutQuint); - updatePreviewTrack(playing); - }; + Playing.ValueChanged += updatePreviewTrack; } [BackgroundDependencyLoader] @@ -125,6 +120,15 @@ namespace osu.Game.Overlays.Direct private void updatePreviewTrack(bool playing) { + if (playing && BeatmapSet == null) + { + Playing.Value = false; + return; + } + + icon.Icon = playing ? FontAwesome.fa_pause : FontAwesome.fa_play; + icon.FadeColour(playing || IsHovered ? hoverColour : Color4.White, 120, Easing.InOutQuint); + if (playing) { if (Preview == null) diff --git a/osu.Game/Overlays/Profile/Header/BadgeContainer.cs b/osu.Game/Overlays/Profile/Header/BadgeContainer.cs index 291db45e97..36a9a9b01a 100644 --- a/osu.Game/Overlays/Profile/Header/BadgeContainer.cs +++ b/osu.Game/Overlays/Profile/Header/BadgeContainer.cs @@ -92,22 +92,18 @@ namespace osu.Game.Overlays.Profile.Header public void ShowBadges(Badge[] badges) { - switch (badges.Length) + if (badges == null || badges.Length == 0) { - case 0: - Hide(); - return; - case 1: - badgeCountText.Hide(); - break; - default: - badgeCountText.Show(); - badgeCountText.Text = $"{badges.Length} badges"; - break; + Hide(); + return; } - Show(); badgeCount = badges.Length; + + badgeCountText.FadeTo(badgeCount > 1 ? 1 : 0); + badgeCountText.Text = $"{badges.Length} badges"; + + Show(); visibleBadge = 0; badgeFlowContainer.Clear(); diff --git a/osu.Game/Overlays/Profile/ProfileHeader.cs b/osu.Game/Overlays/Profile/ProfileHeader.cs index ec0e45d5ca..4c411b3210 100644 --- a/osu.Game/Overlays/Profile/ProfileHeader.cs +++ b/osu.Game/Overlays/Profile/ProfileHeader.cs @@ -24,7 +24,7 @@ namespace osu.Game.Overlays.Profile { public class ProfileHeader : Container { - private readonly OsuTextFlowContainer infoTextLeft; + private readonly LinkFlowContainer infoTextLeft; private readonly LinkFlowContainer infoTextRight; private readonly FillFlowContainer scoreText, scoreNumberText; private readonly RankGraph rankGraph; @@ -141,7 +141,7 @@ namespace osu.Game.Overlays.Profile } } }, - infoTextLeft = new OsuTextFlowContainer(t => t.TextSize = 14) + infoTextLeft = new LinkFlowContainer(t => t.TextSize = 14) { X = UserProfileOverlay.CONTENT_X_MARGIN, Y = cover_height + 20, @@ -350,7 +350,7 @@ namespace osu.Game.Overlays.Profile if (user.Country != null) { - infoTextLeft.AddText("from ", lightText); + infoTextLeft.AddText("From ", lightText); infoTextLeft.AddText(user.Country.FullName, boldItalic); countryFlag.Country = user.Country; } @@ -378,6 +378,10 @@ namespace osu.Game.Overlays.Profile infoTextLeft.AddText(string.Join(", ", user.PlayStyle), boldItalic); } + infoTextLeft.NewLine(); + infoTextLeft.AddText("Contributed ", lightText); + infoTextLeft.AddLink($@"{user.PostCount} forum posts", url: $"https://osu.ppy.sh/users/{user.Id}/posts", creationParameters: boldItalic); + string websiteWithoutProtcol = user.Website; if (!string.IsNullOrEmpty(websiteWithoutProtcol)) { @@ -392,8 +396,10 @@ namespace osu.Game.Overlays.Profile infoTextRight.NewParagraph(); if (!string.IsNullOrEmpty(user.Twitter)) tryAddInfoRightLine(FontAwesome.fa_twitter, "@" + user.Twitter, $@"https://twitter.com/{user.Twitter}"); - tryAddInfoRightLine(FontAwesome.fa_globe, websiteWithoutProtcol, user.Website); + tryAddInfoRightLine(FontAwesome.fa_gamepad, user.Discord); tryAddInfoRightLine(FontAwesome.fa_skype, user.Skype, @"skype:" + user.Skype + @"?chat"); + tryAddInfoRightLine(FontAwesome.fa_lastfm, user.Lastfm, $@"https://last.fm/users/{user.Lastfm}"); + tryAddInfoRightLine(FontAwesome.fa_globe, websiteWithoutProtcol, user.Website); if (user.Statistics != null) { diff --git a/osu.Game/Overlays/Profile/Sections/BeatmapMetadataContainer.cs b/osu.Game/Overlays/Profile/Sections/BeatmapMetadataContainer.cs index da08c08179..97079c77f3 100644 --- a/osu.Game/Overlays/Profile/Sections/BeatmapMetadataContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/BeatmapMetadataContainer.cs @@ -32,7 +32,7 @@ namespace osu.Game.Overlays.Profile.Sections { Action = () => { - if (beatmap.OnlineBeatmapSetID.HasValue) beatmapSetOverlay?.ShowBeatmapSet(beatmap.OnlineBeatmapSetID.Value); + if (beatmap.OnlineBeatmapSetID.HasValue) beatmapSetOverlay?.FetchAndShowBeatmapSet(beatmap.OnlineBeatmapSetID.Value); }; Child = new FillFlowContainer diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs index 95abc4edb3..9edd0f1f34 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs @@ -19,6 +19,11 @@ namespace osu.Game.Rulesets.Objects.Legacy public abstract class ConvertHitObjectParser : HitObjectParser { public override HitObject Parse(string text) + { + return Parse(text, 0); + } + + public HitObject Parse(string text, double offset) { try { @@ -146,7 +151,7 @@ namespace osu.Game.Rulesets.Objects.Legacy } else if ((type & ConvertHitObjectType.Spinner) > 0) { - result = CreateSpinner(new Vector2(512, 384) / 2, Convert.ToDouble(split[5], CultureInfo.InvariantCulture)); + result = CreateSpinner(new Vector2(512, 384) / 2, Convert.ToDouble(split[5], CultureInfo.InvariantCulture) + offset); if (split.Length > 6) readCustomSampleBanks(split[6], bankInfo); @@ -164,13 +169,13 @@ namespace osu.Game.Rulesets.Objects.Legacy readCustomSampleBanks(string.Join(":", ss.Skip(1)), bankInfo); } - result = CreateHold(new Vector2(int.Parse(split[0]), int.Parse(split[1])), combo, endTime); + result = CreateHold(new Vector2(int.Parse(split[0]), int.Parse(split[1])), combo, endTime + offset); } if (result == null) throw new InvalidOperationException($@"Unknown hit object type {type}."); - result.StartTime = Convert.ToDouble(split[2], CultureInfo.InvariantCulture); + result.StartTime = Convert.ToDouble(split[2], CultureInfo.InvariantCulture) + offset; result.Samples = convertSoundType(soundType, bankInfo); return result; diff --git a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs index edfea57e94..36c6b07f54 100644 --- a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs @@ -110,7 +110,7 @@ namespace osu.Game.Rulesets.UI.Scrolling base.UpdateAfterChildrenLife(); // We need to calculate this as soon as possible after lifetimes so that hitobjects get the final say in their positions - speedChangeVisualiser.ComputePositions(AliveObjects, direction, Time.Current, TimeRange, DrawSize); + speedChangeVisualiser.UpdatePositions(AliveObjects, direction, Time.Current, TimeRange, DrawSize); } } } diff --git a/osu.Game/Rulesets/UI/Scrolling/Visualisers/ISpeedChangeVisualiser.cs b/osu.Game/Rulesets/UI/Scrolling/Visualisers/ISpeedChangeVisualiser.cs index 02791e0517..097e28b2dc 100644 --- a/osu.Game/Rulesets/UI/Scrolling/Visualisers/ISpeedChangeVisualiser.cs +++ b/osu.Game/Rulesets/UI/Scrolling/Visualisers/ISpeedChangeVisualiser.cs @@ -10,24 +10,23 @@ namespace osu.Game.Rulesets.UI.Scrolling.Visualisers public interface ISpeedChangeVisualiser { /// - /// Computes the states of s that are constant, such as lifetime and spatial length. + /// Computes the states of s that remain constant while scrolling, such as lifetime and spatial length. /// This is invoked once whenever or changes. /// /// The s whose states should be computed. /// The scrolling direction. - /// The duration required to scroll through one length of the screen before any control point adjustments. + /// The duration required to scroll through one length of the screen before any speed adjustments. /// The length of the screen that is scrolled through. void ComputeInitialStates(IEnumerable hitObjects, ScrollingDirection direction, double timeRange, Vector2 length); /// - /// Computes the states of s that change depending on , such as position. - /// This is invoked once per frame. + /// Updates the positions of s, depending on the current time. This is invoked once per frame. /// - /// The s whose states should be computed. + /// The s whose positions should be computed. /// The scrolling direction. /// The current time. - /// The duration required to scroll through one length of the screen before any control point adjustments. + /// The duration required to scroll through one length of the screen before any speed adjustments. /// The length of the screen that is scrolled through. - void ComputePositions(IEnumerable hitObjects, ScrollingDirection direction, double currentTime, double timeRange, Vector2 length); + void UpdatePositions(IEnumerable hitObjects, ScrollingDirection direction, double currentTime, double timeRange, Vector2 length); } } diff --git a/osu.Game/Rulesets/UI/Scrolling/Visualisers/OverlappingSpeedChangeVisualiser.cs b/osu.Game/Rulesets/UI/Scrolling/Visualisers/OverlappingSpeedChangeVisualiser.cs index 2365582645..35a91275a7 100644 --- a/osu.Game/Rulesets/UI/Scrolling/Visualisers/OverlappingSpeedChangeVisualiser.cs +++ b/osu.Game/Rulesets/UI/Scrolling/Visualisers/OverlappingSpeedChangeVisualiser.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using osu.Framework.Lists; using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Timing; using OpenTK; @@ -22,24 +23,45 @@ namespace osu.Game.Rulesets.UI.Scrolling.Visualisers { foreach (var obj in hitObjects) { - var controlPoint = controlPointAt(obj.HitObject.StartTime); - obj.LifetimeStart = obj.HitObject.StartTime - timeRange / controlPoint.Multiplier; + // The total amount of time that the hitobject will remain visible within the timeRange, which decreases as the speed multiplier increases + double visibleDuration = timeRange / controlPointAt(obj.HitObject.StartTime).Multiplier; + + obj.LifetimeStart = obj.HitObject.StartTime - visibleDuration; + + if (obj.HitObject is IHasEndTime endTime) + { + // At the hitobject's end time, the hitobject will be positioned such that its end rests at the origin. + // This results in a negative-position value, and the absolute of it indicates the length of the hitobject. + var hitObjectLength = -hitObjectPositionAt(obj, endTime.EndTime, timeRange); + + switch (direction) + { + case ScrollingDirection.Up: + case ScrollingDirection.Down: + obj.Height = (float)(hitObjectLength * length.Y); + break; + case ScrollingDirection.Left: + case ScrollingDirection.Right: + obj.Width = (float)(hitObjectLength * length.X); + break; + } + } if (obj.HasNestedHitObjects) { ComputeInitialStates(obj.NestedHitObjects, direction, timeRange, length); - ComputePositions(obj.NestedHitObjects, direction, obj.HitObject.StartTime, timeRange, length); + + // Nested hitobjects don't need to scroll, but they do need accurate positions + UpdatePositions(obj.NestedHitObjects, direction, obj.HitObject.StartTime, timeRange, length); } } } - public void ComputePositions(IEnumerable hitObjects, ScrollingDirection direction, double currentTime, double timeRange, Vector2 length) + public void UpdatePositions(IEnumerable hitObjects, ScrollingDirection direction, double currentTime, double timeRange, Vector2 length) { foreach (var obj in hitObjects) { - var controlPoint = controlPointAt(obj.HitObject.StartTime); - - var position = (obj.HitObject.StartTime - currentTime) * controlPoint.Multiplier / timeRange; + var position = hitObjectPositionAt(obj, currentTime, timeRange); switch (direction) { @@ -59,7 +81,28 @@ namespace osu.Game.Rulesets.UI.Scrolling.Visualisers } } + /// + /// Computes the position of a at a point in time. + /// + /// At t < startTime, position > 0.
+ /// At t = startTime, position = 0.
+ /// At t > startTime, position < 0. + ///
+ ///
+ /// The . + /// The time to find the position of at. + /// The amount of time visualised by the scrolling area. + /// The position of in the scrolling area at time = . + private double hitObjectPositionAt(DrawableHitObject obj, double time, double timeRange) + => (obj.HitObject.StartTime - time) / timeRange * controlPointAt(obj.HitObject.StartTime).Multiplier; + private readonly MultiplierControlPoint searchPoint = new MultiplierControlPoint(); + + /// + /// Finds the which affects the speed of hitobjects at a specific time. + /// + /// The time which the should affect. + /// The . private MultiplierControlPoint controlPointAt(double time) { if (controlPoints.Count == 0) diff --git a/osu.Game/Rulesets/UI/Scrolling/Visualisers/SequentialSpeedChangeVisualiser.cs b/osu.Game/Rulesets/UI/Scrolling/Visualisers/SequentialSpeedChangeVisualiser.cs index 708a2f173b..e353c07e9f 100644 --- a/osu.Game/Rulesets/UI/Scrolling/Visualisers/SequentialSpeedChangeVisualiser.cs +++ b/osu.Game/Rulesets/UI/Scrolling/Visualisers/SequentialSpeedChangeVisualiser.cs @@ -25,23 +25,25 @@ namespace osu.Game.Rulesets.UI.Scrolling.Visualisers { foreach (var obj in hitObjects) { + // To reduce iterations when updating hitobject positions later on, their initial positions are cached var startPosition = hitObjectPositions[obj] = positionAt(obj.HitObject.StartTime, timeRange); + // Todo: This is approximate and will be incorrect in the case of extreme speed changes obj.LifetimeStart = obj.HitObject.StartTime - timeRange - 1000; if (obj.HitObject is IHasEndTime endTime) { - var diff = positionAt(endTime.EndTime, timeRange) - startPosition; + var hitObjectLength = positionAt(endTime.EndTime, timeRange) - startPosition; switch (direction) { case ScrollingDirection.Up: case ScrollingDirection.Down: - obj.Height = (float)(diff * length.Y); + obj.Height = (float)(hitObjectLength * length.Y); break; case ScrollingDirection.Left: case ScrollingDirection.Right: - obj.Width = (float)(diff * length.X); + obj.Width = (float)(hitObjectLength * length.X); break; } } @@ -49,12 +51,14 @@ namespace osu.Game.Rulesets.UI.Scrolling.Visualisers if (obj.HasNestedHitObjects) { ComputeInitialStates(obj.NestedHitObjects, direction, timeRange, length); - ComputePositions(obj.NestedHitObjects, direction, obj.HitObject.StartTime, timeRange, length); + + // Nested hitobjects don't need to scroll, but they do need accurate positions + UpdatePositions(obj.NestedHitObjects, direction, obj.HitObject.StartTime, timeRange, length); } } } - public void ComputePositions(IEnumerable hitObjects, ScrollingDirection direction, double currentTime, double timeRange, Vector2 length) + public void UpdatePositions(IEnumerable hitObjects, ScrollingDirection direction, double currentTime, double timeRange, Vector2 length) { var timelinePosition = positionAt(currentTime, timeRange); @@ -80,21 +84,38 @@ namespace osu.Game.Rulesets.UI.Scrolling.Visualisers } } + /// + /// Finds the position which corresponds to a point in time. + /// This is a non-linear operation that depends on all the control points up to and including the one active at the time value. + /// + /// The time to find the position at. + /// The amount of time visualised by the scrolling area. + /// A positive value indicating the position at . private double positionAt(double time, double timeRange) { double length = 0; + + // We need to consider all timing points until the specified time and not just the currently-active one, + // since each timing point individually affects the positions of _all_ hitobjects after its start time for (int i = 0; i < controlPoints.Count; i++) { var current = controlPoints[i]; var next = i < controlPoints.Count - 1 ? controlPoints[i + 1] : null; + // We don't need to consider any control points beyond the current time, since it will not yet + // affect any hitobjects if (i > 0 && current.StartTime > time) continue; // Duration of the current control point var currentDuration = (next?.StartTime ?? double.PositiveInfinity) - current.StartTime; - length += Math.Min(currentDuration, time - current.StartTime) * current.Multiplier / timeRange; + // We want to consider the minimal amount of time that this control point has affected, + // which may be either its duration, or the amount of time that has passed within it + var durationInCurrent = Math.Min(currentDuration, time - current.StartTime); + + // Figure out how much of the time range the duration represents, and adjust it by the speed multiplier + length += durationInCurrent / timeRange * current.Multiplier; } return length; diff --git a/osu.Game/Screens/BackgroundScreen.cs b/osu.Game/Screens/BackgroundScreen.cs index e7d3690f59..5e9863f642 100644 --- a/osu.Game/Screens/BackgroundScreen.cs +++ b/osu.Game/Screens/BackgroundScreen.cs @@ -26,14 +26,14 @@ namespace osu.Game.Screens return false; } - public override bool Push(Screen screen) + public override void Push(Screen screen) { // When trying to push a non-loaded screen, load it asynchronously and re-invoke Push // once it's done. if (screen.LoadState == LoadState.NotLoaded) { LoadComponentAsync(screen, d => Push((BackgroundScreen)d)); - return true; + return; } // Make sure the in-progress loading is complete before pushing the screen. @@ -41,8 +41,6 @@ namespace osu.Game.Screens Thread.Sleep(1); base.Push(screen); - - return true; } protected override void Update() diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 4ad11e6a91..a9324e3fb0 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -178,7 +178,7 @@ namespace osu.Game.Screens.Edit } currentScreen.Beatmap.BindTo(Beatmap); - screenContainer.Add(currentScreen); + LoadComponentAsync(currentScreen, screenContainer.Add); } protected override bool OnWheel(InputState state) diff --git a/osu.Game/Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs b/osu.Game/Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs index ede98d986a..b9a8e9914a 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs @@ -46,7 +46,7 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers var dragLayer = new DragLayer(maskContainer.Select); dragLayer.DragEnd += () => maskSelection.UpdateVisibility(); - InternalChildren = new Drawable[] + InternalChildren = new[] { dragLayer, maskSelection, diff --git a/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskContainer.cs b/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskContainer.cs index 6d75b8dc15..df2691c28e 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskContainer.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskContainer.cs @@ -43,6 +43,8 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers public override void Add(HitObjectMask drawable) { + if (drawable == null) throw new ArgumentNullException(nameof(drawable)); + base.Add(drawable); drawable.Selected += onMaskSelected; @@ -53,6 +55,8 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers public override bool Remove(HitObjectMask drawable) { + if (drawable == null) throw new ArgumentNullException(nameof(drawable)); + var result = base.Remove(drawable); if (result) diff --git a/osu.Game/Screens/Menu/Intro.cs b/osu.Game/Screens/Menu/Intro.cs index c62a622c3c..4de76e530a 100644 --- a/osu.Game/Screens/Menu/Intro.cs +++ b/osu.Game/Screens/Menu/Intro.cs @@ -39,6 +39,7 @@ namespace osu.Game.Screens.Menu private Bindable menuVoice; private Bindable menuMusic; private Track track; + private WorkingBeatmap beatmap; [BackgroundDependencyLoader] private void load(AudioManager audio, OsuConfigManager config, BeatmapManager beatmaps, Framework.Game game) @@ -69,9 +70,8 @@ namespace osu.Game.Screens.Menu } } - Beatmap.Value = beatmaps.GetWorkingBeatmap(setInfo.Beatmaps[0]); - - track = Beatmap.Value.Track; + beatmap = beatmaps.GetWorkingBeatmap(setInfo.Beatmaps[0]); + track = beatmap.Track; welcome = audio.Sample.Get(@"welcome"); seeya = audio.Sample.Get(@"seeya"); @@ -81,6 +81,8 @@ namespace osu.Game.Screens.Menu { base.OnEntering(last); + Game.Beatmap.Value = beatmap; + if (menuVoice) welcome.Play(); diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index bfc0cec18f..d5edff3a0c 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -77,7 +77,7 @@ namespace osu.Game.Screens.Play private DrawableStoryboard storyboard; private Container storyboardContainer; - private bool loadedSuccessfully => RulesetContainer?.Objects.Any() == true; + public bool LoadedBeatmapSuccessfully => RulesetContainer?.Objects.Any() == true; [BackgroundDependencyLoader] private void load(AudioManager audio, APIAccess api, OsuConfigManager config) @@ -86,10 +86,7 @@ namespace osu.Game.Screens.Play WorkingBeatmap working = Beatmap.Value; if (working is DummyWorkingBeatmap) - { - Exit(); return; - } sampleRestart = audio.Sample.Get(@"Gameplay/restart"); @@ -122,14 +119,15 @@ namespace osu.Game.Screens.Play } if (!RulesetContainer.Objects.Any()) - throw new InvalidOperationException("Beatmap contains no hit objects!"); + { + Logger.Error(new InvalidOperationException("Beatmap contains no hit objects!"), "Beatmap contains no hit objects!"); + return; + } } catch (Exception e) { Logger.Error(e, "Could not load beatmap sucessfully!"); - //couldn't load, hard abort! - Exit(); return; } @@ -293,7 +291,7 @@ namespace osu.Game.Screens.Play { base.OnEntering(last); - if (!loadedSuccessfully) + if (!LoadedBeatmapSuccessfully) return; Content.Alpha = 0; @@ -343,7 +341,7 @@ namespace osu.Game.Screens.Play return base.OnExiting(next); } - if (loadedSuccessfully) + if (LoadedBeatmapSuccessfully) pauseContainer?.Pause(); return true; diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index ca22b274a4..56fbd7b6e7 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -159,14 +159,14 @@ namespace osu.Game.Screens.Play loadTask = null; - if (!Push(player)) - Exit(); + //By default, we want to load the player and never be returned to. + //Note that this may change if the player we load requested a re-run. + ValidForResume = false; + + if (player.LoadedBeatmapSuccessfully) + Push(player); else - { - //By default, we want to load the player and never be returned to. - //Note that this may change if the player we load requested a re-run. - ValidForResume = false; - } + Exit(); }); }, 500); } diff --git a/osu.Game/Screens/Play/PlayerSettings/VisualSettings.cs b/osu.Game/Screens/Play/PlayerSettings/VisualSettings.cs index 92d069a224..439e344020 100644 --- a/osu.Game/Screens/Play/PlayerSettings/VisualSettings.cs +++ b/osu.Game/Screens/Play/PlayerSettings/VisualSettings.cs @@ -15,6 +15,8 @@ namespace osu.Game.Screens.Play.PlayerSettings private readonly PlayerSliderBar dimSliderBar; private readonly PlayerSliderBar blurSliderBar; private readonly PlayerCheckbox showStoryboardToggle; + private readonly PlayerCheckbox beatmapSkinsToggle; + private readonly PlayerCheckbox beatmapHitsoundsToggle; public VisualSettings() { @@ -34,7 +36,9 @@ namespace osu.Game.Screens.Play.PlayerSettings { Text = "Toggles:" }, - showStoryboardToggle = new PlayerCheckbox { LabelText = "Storyboards" } + showStoryboardToggle = new PlayerCheckbox { LabelText = "Storyboards" }, + beatmapSkinsToggle = new PlayerCheckbox { LabelText = "Beatmap skins" }, + beatmapHitsoundsToggle = new PlayerCheckbox { LabelText = "Beatmap hitsounds" } }; } @@ -44,6 +48,8 @@ namespace osu.Game.Screens.Play.PlayerSettings dimSliderBar.Bindable = config.GetBindable(OsuSetting.DimLevel); blurSliderBar.Bindable = config.GetBindable(OsuSetting.BlurLevel); showStoryboardToggle.Bindable = config.GetBindable(OsuSetting.ShowStoryboard); + beatmapSkinsToggle.Bindable = config.GetBindable(OsuSetting.BeatmapSkins); + beatmapHitsoundsToggle.Bindable = config.GetBindable(OsuSetting.BeatmapHitsounds); } } } diff --git a/osu.Game/Screens/Ranking/ResultsPageScore.cs b/osu.Game/Screens/Ranking/ResultsPageScore.cs index 6d3812ac4b..862f77d219 100644 --- a/osu.Game/Screens/Ranking/ResultsPageScore.cs +++ b/osu.Game/Screens/Ranking/ResultsPageScore.cs @@ -223,11 +223,11 @@ namespace osu.Game.Screens.Ranking private class DateTimeDisplay : Container { - private DateTime datetime; + private readonly DateTime date; - public DateTimeDisplay(DateTime datetime) + public DateTimeDisplay(DateTime date) { - this.datetime = datetime; + this.date = date; AutoSizeAxes = Axes.Y; @@ -251,7 +251,7 @@ namespace osu.Game.Screens.Ranking { Origin = Anchor.CentreLeft, Anchor = Anchor.CentreLeft, - Text = datetime.ToShortDateString(), + Text = date.ToShortDateString(), Padding = new MarginPadding { Horizontal = 10, Vertical = 5 }, Colour = Color4.White, }, @@ -259,7 +259,7 @@ namespace osu.Game.Screens.Ranking { Origin = Anchor.CentreRight, Anchor = Anchor.CentreRight, - Text = datetime.ToShortTimeString(), + Text = date.ToShortTimeString(), Padding = new MarginPadding { Horizontal = 10, Vertical = 5 }, Colour = Color4.White, } diff --git a/osu.Game/Screens/Select/Carousel/CarouselGroup.cs b/osu.Game/Screens/Select/Carousel/CarouselGroup.cs index 7ea6e7c82b..2118cfdc78 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselGroup.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselGroup.cs @@ -16,11 +16,22 @@ namespace osu.Game.Screens.Select.Carousel protected List InternalChildren = new List(); + /// + /// Used to assign a monotonically increasing ID to children as they are added. This member is + /// incremented whenever a child is added. + /// + private ulong currentChildID; + public override List Drawables { get { var drawables = base.Drawables; + + // if we are explicitly not present, don't ever present children. + // without this check, children drawables can potentially be presented without their group header. + if (DrawableRepresentation.Value?.IsPresent == false) return drawables; + foreach (var c in InternalChildren) drawables.AddRange(c.Drawables); return drawables; @@ -39,6 +50,7 @@ namespace osu.Game.Screens.Select.Carousel public virtual void AddChild(CarouselItem i) { i.State.ValueChanged += v => ChildItemStateChanged(i, v); + i.ChildID = ++currentChildID; InternalChildren.Add(i); } diff --git a/osu.Game/Screens/Select/Carousel/CarouselItem.cs b/osu.Game/Screens/Select/Carousel/CarouselItem.cs index 2efc928984..c0781f09c0 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselItem.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselItem.cs @@ -24,16 +24,18 @@ namespace osu.Game.Screens.Select.Carousel { var items = new List(); - var self = drawableRepresentation.Value; + var self = DrawableRepresentation.Value; if (self?.IsPresent == true) items.Add(self); return items; } } + private int creationOrder; + protected CarouselItem() { - drawableRepresentation = new Lazy(CreateDrawableRepresentation); + DrawableRepresentation = new Lazy(CreateDrawableRepresentation); Filtered.ValueChanged += v => { @@ -42,15 +44,23 @@ namespace osu.Game.Screens.Select.Carousel }; } - private readonly Lazy drawableRepresentation; + protected readonly Lazy DrawableRepresentation; + /// + /// Used as a default sort method for s of differing types. + /// + internal ulong ChildID; + + /// + /// Create a fresh drawable version of this item. If you wish to consume the current representation, use instead. + /// protected abstract DrawableCarouselItem CreateDrawableRepresentation(); public virtual void Filter(FilterCriteria criteria) { } - public virtual int CompareTo(FilterCriteria criteria, CarouselItem other) => GetHashCode().CompareTo(other.GetHashCode()); + public virtual int CompareTo(FilterCriteria criteria, CarouselItem other) => ChildID.CompareTo(other.ChildID); } public enum CarouselItemState diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs index cda8a549da..f39952dc31 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs @@ -17,6 +17,7 @@ using osu.Game.Graphics; using osu.Game.Graphics.Backgrounds; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; +using osu.Game.Overlays; using OpenTK; using OpenTK.Graphics; @@ -35,6 +36,8 @@ namespace osu.Game.Screens.Select.Carousel private Triangles triangles; private StarCounter starCounter; + private BeatmapSetOverlay beatmapOverlay; + public DrawableCarouselBeatmap(CarouselBeatmap panel) : base(panel) { beatmap = panel.Beatmap; @@ -42,8 +45,10 @@ namespace osu.Game.Screens.Select.Carousel } [BackgroundDependencyLoader(true)] - private void load(SongSelect songSelect, BeatmapManager manager) + private void load(SongSelect songSelect, BeatmapManager manager, BeatmapSetOverlay beatmapOverlay) { + this.beatmapOverlay = beatmapOverlay; + if (songSelect != null) { startRequested = songSelect.FinaliseSelection; @@ -171,6 +176,10 @@ namespace osu.Game.Screens.Select.Carousel new OsuMenuItem("Play", MenuItemType.Highlighted, () => startRequested?.Invoke(beatmap)), new OsuMenuItem("Edit", MenuItemType.Standard, () => editRequested?.Invoke(beatmap)), new OsuMenuItem("Hide", MenuItemType.Destructive, () => hideRequested?.Invoke(beatmap)), + new OsuMenuItem("Details", MenuItemType.Standard, () => + { + if (beatmap.OnlineBeatmapID.HasValue) beatmapOverlay?.FetchAndShowBeatmap(beatmap.OnlineBeatmapID.Value); + }), }; } } diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index ad7588edde..d554a22735 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -46,7 +46,7 @@ namespace osu.Game.Screens.Select.Carousel restoreHiddenRequested = s => s.Beatmaps.ForEach(manager.Restore); dialogOverlay = overlay; if (beatmapOverlay != null) - viewDetails = beatmapOverlay.ShowBeatmapSet; + viewDetails = beatmapOverlay.FetchAndShowBeatmapSet; Children = new Drawable[] { diff --git a/osu.Game/Screens/Select/Details/AdvancedStats.cs b/osu.Game/Screens/Select/Details/AdvancedStats.cs index cad7ed7d81..852e4f190f 100644 --- a/osu.Game/Screens/Select/Details/AdvancedStats.cs +++ b/osu.Game/Screens/Select/Details/AdvancedStats.cs @@ -40,10 +40,10 @@ namespace osu.Game.Screens.Select.Details firstValue.Value = Beatmap?.BaseDifficulty?.CircleSize ?? 0; } - hpDrain.Value = beatmap.BaseDifficulty?.DrainRate ?? 0; - accuracy.Value = beatmap.BaseDifficulty?.OverallDifficulty ?? 0; - approachRate.Value = beatmap.BaseDifficulty?.ApproachRate ?? 0; - starDifficulty.Value = (float)beatmap.StarDifficulty; + hpDrain.Value = Beatmap?.BaseDifficulty?.DrainRate ?? 0; + accuracy.Value = Beatmap?.BaseDifficulty?.OverallDifficulty ?? 0; + approachRate.Value = Beatmap?.BaseDifficulty?.ApproachRate ?? 0; + starDifficulty.Value = (float)(Beatmap?.StarDifficulty ?? 0); } } diff --git a/osu.Game/Screens/Select/Details/FailRetryGraph.cs b/osu.Game/Screens/Select/Details/FailRetryGraph.cs index a33cee21ed..bf4eb07108 100644 --- a/osu.Game/Screens/Select/Details/FailRetryGraph.cs +++ b/osu.Game/Screens/Select/Details/FailRetryGraph.cs @@ -25,10 +25,10 @@ namespace osu.Game.Screens.Select.Details if (value == metrics) return; metrics = value; - var retries = Metrics.Retries; - var fails = Metrics.Fails; + var retries = Metrics?.Retries ?? new int[0]; + var fails = Metrics?.Fails ?? new int[0]; - float maxValue = fails.Zip(retries, (fail, retry) => fail + retry).Max(); + float maxValue = fails.Any() ? fails.Zip(retries, (fail, retry) => fail + retry).Max() : 0; failGraph.MaxValue = maxValue; retryGraph.MaxValue = maxValue; diff --git a/osu.Game/Screens/Select/Details/UserRatings.cs b/osu.Game/Screens/Select/Details/UserRatings.cs index bf50217048..787b22f965 100644 --- a/osu.Game/Screens/Select/Details/UserRatings.cs +++ b/osu.Game/Screens/Select/Details/UserRatings.cs @@ -21,6 +21,7 @@ namespace osu.Game.Screens.Select.Details private readonly BarGraph graph; private BeatmapMetrics metrics; + public BeatmapMetrics Metrics { get { return metrics; } @@ -31,15 +32,25 @@ namespace osu.Game.Screens.Select.Details const int rating_range = 10; - var ratings = Metrics.Ratings.Skip(1).Take(rating_range); // adjust for API returning weird empty data at 0. + if (metrics == null) + { + negativeRatings.Text = "0"; + positiveRatings.Text = "0"; + ratingsBar.Length = 0; + graph.Values = new float[rating_range]; + } + else + { + var ratings = Metrics.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(); + var negativeCount = ratings.Take(rating_range / 2).Sum(); + var totalCount = ratings.Sum(); - negativeRatings.Text = negativeCount.ToString(); - positiveRatings.Text = (totalCount - negativeCount).ToString(); - ratingsBar.Length = totalCount == 0 ? 0 : (float)negativeCount / totalCount; - graph.Values = ratings.Take(rating_range).Select(r => (float)r); + negativeRatings.Text = negativeCount.ToString(); + positiveRatings.Text = (totalCount - negativeCount).ToString(); + ratingsBar.Length = totalCount == 0 ? 0 : (float)negativeCount / totalCount; + graph.Values = ratings.Take(rating_range).Select(r => (float)r); + } } } diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 0a05137a87..45c6ec80aa 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -113,6 +113,31 @@ namespace osu.Game.Skinning string path = getPathForFile(name); return path == null ? null : underlyingStore.Get(path); } + + #region IDisposable Support + + private bool isDisposed; + + protected virtual void Dispose(bool disposing) + { + if (!isDisposed) + { + isDisposed = true; + } + } + + ~LegacySkinResourceStore() + { + Dispose(false); + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + #endregion } } } diff --git a/osu.Game/Skinning/LegacySkinDecoder.cs b/osu.Game/Skinning/LegacySkinDecoder.cs index 995518af0a..0ef54c7310 100644 --- a/osu.Game/Skinning/LegacySkinDecoder.cs +++ b/osu.Game/Skinning/LegacySkinDecoder.cs @@ -12,7 +12,7 @@ namespace osu.Game.Skinning { } - protected override void ParseLine(SkinConfiguration output, Section section, string line) + protected override void ParseLine(SkinConfiguration skin, Section section, string line) { switch (section) { @@ -22,17 +22,17 @@ namespace osu.Game.Skinning switch (pair.Key) { case @"Name": - output.SkinInfo.Name = pair.Value; + skin.SkinInfo.Name = pair.Value; break; case @"Author": - output.SkinInfo.Creator = pair.Value; + skin.SkinInfo.Creator = pair.Value; break; } break; } - base.ParseLine(output, section, line); + base.ParseLine(skin, section, line); } } } diff --git a/osu.Game/Skinning/LocalSkinOverrideContainer.cs b/osu.Game/Skinning/LocalSkinOverrideContainer.cs index bfbb1719df..ad6a033936 100644 --- a/osu.Game/Skinning/LocalSkinOverrideContainer.cs +++ b/osu.Game/Skinning/LocalSkinOverrideContainer.cs @@ -4,9 +4,11 @@ using System; using osu.Framework.Allocation; using osu.Framework.Audio.Sample; +using osu.Framework.Configuration; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Textures; +using osu.Game.Configuration; namespace osu.Game.Skinning { @@ -14,17 +16,37 @@ namespace osu.Game.Skinning { public event Action SourceChanged; - public Drawable GetDrawableComponent(string componentName) => source.GetDrawableComponent(componentName) ?? fallbackSource?.GetDrawableComponent(componentName); + private Bindable beatmapSkins = new Bindable(); + private Bindable beatmapHitsounds = new Bindable(); - public Texture GetTexture(string componentName) => source.GetTexture(componentName) ?? fallbackSource.GetTexture(componentName); + public Drawable GetDrawableComponent(string componentName) + { + Drawable sourceDrawable; + if (beatmapSkins && (sourceDrawable = source.GetDrawableComponent(componentName)) != null) + return sourceDrawable; + return fallbackSource?.GetDrawableComponent(componentName); + } - public SampleChannel GetSample(string sampleName) => source.GetSample(sampleName) ?? fallbackSource?.GetSample(sampleName); + public Texture GetTexture(string componentName) + { + Texture sourceTexture; + if (beatmapSkins && (sourceTexture = source.GetTexture(componentName)) != null) + return sourceTexture; + return fallbackSource.GetTexture(componentName); + } + + public SampleChannel GetSample(string sampleName) + { + SampleChannel sourceChannel; + if (beatmapHitsounds && (sourceChannel = source.GetSample(sampleName)) != null) + return sourceChannel; + return fallbackSource?.GetSample(sampleName); + } public TValue? GetValue(Func query) where TConfiguration : SkinConfiguration where TValue : struct { TValue? val = null; - var conf = (source as Skin)?.Configuration as TConfiguration; - if (conf != null) + if ((source as Skin)?.Configuration is TConfiguration conf) val = query?.Invoke(conf); return val ?? fallbackSource?.GetValue(query); @@ -33,8 +55,7 @@ namespace osu.Game.Skinning public TValue GetValue(Func query) where TConfiguration : SkinConfiguration where TValue : class { TValue val = null; - var conf = (source as Skin)?.Configuration as TConfiguration; - if (conf != null) + if ((source as Skin)?.Configuration is TConfiguration conf) val = query?.Invoke(conf); return val ?? fallbackSource?.GetValue(query); @@ -60,6 +81,18 @@ namespace osu.Game.Skinning return dependencies; } + [BackgroundDependencyLoader] + private void load(OsuConfigManager config) + { + beatmapSkins = config.GetBindable(OsuSetting.BeatmapSkins); + beatmapSkins.ValueChanged += val => onSourceChanged(); + beatmapSkins.TriggerChange(); + + beatmapHitsounds = config.GetBindable(OsuSetting.BeatmapHitsounds); + beatmapHitsounds.ValueChanged += val => onSourceChanged(); + beatmapHitsounds.TriggerChange(); + } + protected override void LoadComplete() { base.LoadComplete(); diff --git a/osu.Game/Tests/Visual/OsuTestCase.cs b/osu.Game/Tests/Visual/OsuTestCase.cs index 02f425c296..2b677f1f42 100644 --- a/osu.Game/Tests/Visual/OsuTestCase.cs +++ b/osu.Game/Tests/Visual/OsuTestCase.cs @@ -15,18 +15,14 @@ namespace osu.Game.Tests.Visual { protected override string MainResourceFile => File.Exists(base.MainResourceFile) ? base.MainResourceFile : Assembly.GetExecutingAssembly().Location; - private readonly TestCaseTestRunner.TestRunner runner; + private TestCaseTestRunner.TestRunner runner; - public OsuTestCaseTestRunner() + protected override void LoadAsyncComplete() { - runner = new TestCaseTestRunner.TestRunner(); - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - Add(runner); + // this has to be run here rather than LoadComplete because + // TestCase.cs is checking the IsLoaded state (on another thread) and expects + // the runner to be loaded at that point. + Add(runner = new TestCaseTestRunner.TestRunner()); } public void RunTestBlocking(TestCase test) => runner.RunTestBlocking(test); diff --git a/osu.Game/Users/UpdateableAvatar.cs b/osu.Game/Users/UpdateableAvatar.cs index 31455801da..6c0b841abf 100644 --- a/osu.Game/Users/UpdateableAvatar.cs +++ b/osu.Game/Users/UpdateableAvatar.cs @@ -15,6 +15,11 @@ namespace osu.Game.Users private User user; + /// + /// Whether to show a default guest representation on null user (as opposed to nothing). + /// + public bool ShowGuestOnNull = true; + public User User { get { return user; } @@ -40,13 +45,16 @@ namespace osu.Game.Users { displayedAvatar?.FadeOut(300); displayedAvatar?.Expire(); - Add(displayedAvatar = new DelayedLoadWrapper( - new Avatar(user) - { - RelativeSizeAxes = Axes.Both, - OnLoadComplete = d => d.FadeInFromZero(300, Easing.OutQuint), - }) - ); + if (user != null || ShowGuestOnNull) + { + Add(displayedAvatar = new DelayedLoadWrapper( + new Avatar(user) + { + RelativeSizeAxes = Axes.Both, + OnLoadComplete = d => d.FadeInFromZero(300, Easing.OutQuint), + }) + ); + } } } } diff --git a/osu.Game/Users/User.cs b/osu.Game/Users/User.cs index b983b639f0..e1f68e1ce8 100644 --- a/osu.Game/Users/User.cs +++ b/osu.Game/Users/User.cs @@ -98,9 +98,15 @@ namespace osu.Game.Users [JsonProperty(@"skype")] public string Skype; + [JsonProperty(@"discord")] + public string Discord; + [JsonProperty(@"website")] public string Website; + [JsonProperty(@"post_count")] + public int PostCount; + [JsonProperty(@"playstyle")] public string[] PlayStyle; diff --git a/osu.Game/Users/UserStatus.cs b/osu.Game/Users/UserStatus.cs index 22bc9ed1a0..1584605166 100644 --- a/osu.Game/Users/UserStatus.cs +++ b/osu.Game/Users/UserStatus.cs @@ -12,12 +12,13 @@ namespace osu.Game.Users public abstract Color4 GetAppropriateColour(OsuColour colours); } - public abstract class UserStatusAvailable : UserStatus + public class UserStatusOnline : UserStatus { + public override string Message => @"Online"; public override Color4 GetAppropriateColour(OsuColour colours) => colours.BlueDarker; } - public abstract class UserStatusBusy : UserStatus + public abstract class UserStatusBusy : UserStatusOnline { public override Color4 GetAppropriateColour(OsuColour colours) => colours.YellowDark; } @@ -28,17 +29,12 @@ namespace osu.Game.Users public override Color4 GetAppropriateColour(OsuColour colours) => colours.Gray7; } - public class UserStatusOnline : UserStatusAvailable - { - public override string Message => @"Online"; - } - - public class UserStatusSpectating : UserStatusAvailable + public class UserStatusSpectating : UserStatusOnline { public override string Message => @"Spectating a game"; } - public class UserStatusInLobby : UserStatusAvailable + public class UserStatusInLobby : UserStatusOnline { public override string Message => @"in Multiplayer Lobby"; } @@ -53,13 +49,13 @@ namespace osu.Game.Users public override string Message => @"Multiplaying"; } - public class UserStatusModding : UserStatus + public class UserStatusModding : UserStatusOnline { public override string Message => @"Modding a map"; public override Color4 GetAppropriateColour(OsuColour colours) => colours.PurpleDark; } - public class UserStatusDoNotDisturb : UserStatus + public class UserStatusDoNotDisturb : UserStatusBusy { public override string Message => @"Do not disturb"; public override Color4 GetAppropriateColour(OsuColour colours) => colours.RedDark;